/** * Recurrence-stats schemas + pure helpers for `totem stats --pattern-recurrence` * (mmnto-ai/totem#1715). * * Substrate of the four-honest-signals "signal 2" instrumentation * (same-class-mistake frequency). The data model is consumed by the * recurrence-stats command and downstream by the bot-tax circuit * breaker (mmnto-ai/totem#1713) and the pre-flight estimator * (mmnto-ai/totem#1714). * * Everything in this file is pure: Zod schemas + deterministic string * helpers. No I/O, no command logic — that lives in the cli package. */ import { z } from 'zod'; /** Source classification of a clustered pattern. */ export declare const RecurrenceToolSchema: z.ZodEnum<["coderabbit", "gca", "greptile", "ghcq", "sarif", "override", "mixed", "unknown"]>; export type RecurrenceTool = z.infer; /** Normalized severity bucket across CR/GCA/override sources. */ export declare const RecurrenceSeverityBucketSchema: z.ZodEnum<["critical", "high", "medium", "low", "nit"]>; export type RecurrenceSeverityBucket = z.infer; /** * Map a tool-specific raw severity string into the normalized * `RecurrenceSeverityBucket` vocabulary. Single source of truth across * the bot-tax cluster (mmnto-ai/totem#1715, #1714, #1713). * * Tool inputs: * - `'coderabbit'`: `critical` / `major` / `minor` / `*` → `critical` / `high` / `medium` / `low` * - `'gca'` / `'greptile'`: `critical` / `high` / `medium` / `low` → identical buckets; default `low` (greptile P0 → `critical`) * - `'sarif'`: SARIF level vocabulary (`error` / `warning` / `note` / `none`) → `high` / `medium` / `low` / `low` * - `'override'`: trap-ledger override events surface as `medium` * - `'ghcq'`: no native severity vocabulary (mmnto-ai/totem#2626) — its `info` * findings ride the generic branch to `low` * - any other tool (`'unknown'`, `undefined`): keyword-mapped from the raw severity string */ /** * Derived from `RecurrenceTool` to prevent type drift; `'mixed'` is a * post-cluster aggregate label that no caller passes here. Per CR * mmnto-ai/totem#1734 round-2. */ export type SeverityBucketTool = Exclude | undefined; export declare function toSeverityBucket(tool: SeverityBucketTool, severity: string): RecurrenceSeverityBucket; /** A single cross-PR cluster of bot/override findings sharing one signature. */ export declare const RecurrencePatternSchema: z.ZodObject<{ /** Stable hash of the normalized pattern body — used as cluster key */ signature: z.ZodString; /** Source classification (`mixed` when one signature spans bots/overrides) */ tool: z.ZodEnum<["coderabbit", "gca", "greptile", "ghcq", "sarif", "override", "mixed", "unknown"]>; /** Normalized severity across CR/GCA's different severity vocabularies */ severityBucket: z.ZodEnum<["critical", "high", "medium", "low", "nit"]>; /** Total finding count for this signature (>= 1) */ occurrences: z.ZodNumber; /** PR numbers where this pattern fired (deduped, sorted ascending numerically) */ prs: z.ZodArray; /** First 3 raw bodies seen — for human triage */ sampleBodies: z.ZodArray; /** Earliest finding timestamp (ISO 8601) */ firstSeen: z.ZodString; /** Latest finding timestamp (ISO 8601) */ lastSeen: z.ZodString; /** File paths where the pattern fired (deduped, ≤ 10) */ paths: z.ZodArray; /** True if signature heuristically maps to an existing compiled rule */ coveredByRule: z.ZodBoolean; }, "strip", z.ZodTypeAny, { paths: string[]; tool: "unknown" | "mixed" | "override" | "coderabbit" | "gca" | "greptile" | "ghcq" | "sarif"; signature: string; severityBucket: "high" | "critical" | "medium" | "low" | "nit"; occurrences: number; prs: string[]; sampleBodies: string[]; firstSeen: string; lastSeen: string; coveredByRule: boolean; }, { paths: string[]; tool: "unknown" | "mixed" | "override" | "coderabbit" | "gca" | "greptile" | "ghcq" | "sarif"; signature: string; severityBucket: "high" | "critical" | "medium" | "low" | "nit"; occurrences: number; prs: string[]; sampleBodies: string[]; firstSeen: string; lastSeen: string; coveredByRule: boolean; }>; export type RecurrencePattern = z.infer; /** Top-level shape persisted at `.totem/recurrence-stats.json`. */ export declare const RecurrenceStatsSchema: z.ZodObject<{ /** Schema version for forward-compat */ version: z.ZodLiteral<1>; /** When this run wrote the file (ISO 8601) */ lastUpdated: z.ZodString; /** The `--threshold` value used; informs reproducibility */ thresholdApplied: z.ZodNumber; /** Number of PRs requested via `--history-depth` */ historyDepth: z.ZodNumber; /** PR numbers actually fetched (post-filter) */ prsScanned: z.ZodArray; /** Patterns at-or-above threshold; sorted by occurrences descending */ patterns: z.ZodArray; /** Normalized severity across CR/GCA's different severity vocabularies */ severityBucket: z.ZodEnum<["critical", "high", "medium", "low", "nit"]>; /** Total finding count for this signature (>= 1) */ occurrences: z.ZodNumber; /** PR numbers where this pattern fired (deduped, sorted ascending numerically) */ prs: z.ZodArray; /** First 3 raw bodies seen — for human triage */ sampleBodies: z.ZodArray; /** Earliest finding timestamp (ISO 8601) */ firstSeen: z.ZodString; /** Latest finding timestamp (ISO 8601) */ lastSeen: z.ZodString; /** File paths where the pattern fired (deduped, ≤ 10) */ paths: z.ZodArray; /** True if signature heuristically maps to an existing compiled rule */ coveredByRule: z.ZodBoolean; }, "strip", z.ZodTypeAny, { paths: string[]; tool: "unknown" | "mixed" | "override" | "coderabbit" | "gca" | "greptile" | "ghcq" | "sarif"; signature: string; severityBucket: "high" | "critical" | "medium" | "low" | "nit"; occurrences: number; prs: string[]; sampleBodies: string[]; firstSeen: string; lastSeen: string; coveredByRule: boolean; }, { paths: string[]; tool: "unknown" | "mixed" | "override" | "coderabbit" | "gca" | "greptile" | "ghcq" | "sarif"; signature: string; severityBucket: "high" | "critical" | "medium" | "low" | "nit"; occurrences: number; prs: string[]; sampleBodies: string[]; firstSeen: string; lastSeen: string; coveredByRule: boolean; }>, "many">; /** Patterns that hit existing rules — separate so coverage rate is observable */ coveredPatterns: z.ZodArray; /** Normalized severity across CR/GCA's different severity vocabularies */ severityBucket: z.ZodEnum<["critical", "high", "medium", "low", "nit"]>; /** Total finding count for this signature (>= 1) */ occurrences: z.ZodNumber; /** PR numbers where this pattern fired (deduped, sorted ascending numerically) */ prs: z.ZodArray; /** First 3 raw bodies seen — for human triage */ sampleBodies: z.ZodArray; /** Earliest finding timestamp (ISO 8601) */ firstSeen: z.ZodString; /** Latest finding timestamp (ISO 8601) */ lastSeen: z.ZodString; /** File paths where the pattern fired (deduped, ≤ 10) */ paths: z.ZodArray; /** True if signature heuristically maps to an existing compiled rule */ coveredByRule: z.ZodBoolean; }, "strip", z.ZodTypeAny, { paths: string[]; tool: "unknown" | "mixed" | "override" | "coderabbit" | "gca" | "greptile" | "ghcq" | "sarif"; signature: string; severityBucket: "high" | "critical" | "medium" | "low" | "nit"; occurrences: number; prs: string[]; sampleBodies: string[]; firstSeen: string; lastSeen: string; coveredByRule: boolean; }, { paths: string[]; tool: "unknown" | "mixed" | "override" | "coderabbit" | "gca" | "greptile" | "ghcq" | "sarif"; signature: string; severityBucket: "high" | "critical" | "medium" | "low" | "nit"; occurrences: number; prs: string[]; sampleBodies: string[]; firstSeen: string; lastSeen: string; coveredByRule: boolean; }>, "many">; }, "strip", z.ZodTypeAny, { version: 1; patterns: { paths: string[]; tool: "unknown" | "mixed" | "override" | "coderabbit" | "gca" | "greptile" | "ghcq" | "sarif"; signature: string; severityBucket: "high" | "critical" | "medium" | "low" | "nit"; occurrences: number; prs: string[]; sampleBodies: string[]; firstSeen: string; lastSeen: string; coveredByRule: boolean; }[]; lastUpdated: string; thresholdApplied: number; historyDepth: number; prsScanned: string[]; coveredPatterns: { paths: string[]; tool: "unknown" | "mixed" | "override" | "coderabbit" | "gca" | "greptile" | "ghcq" | "sarif"; signature: string; severityBucket: "high" | "critical" | "medium" | "low" | "nit"; occurrences: number; prs: string[]; sampleBodies: string[]; firstSeen: string; lastSeen: string; coveredByRule: boolean; }[]; }, { version: 1; patterns: { paths: string[]; tool: "unknown" | "mixed" | "override" | "coderabbit" | "gca" | "greptile" | "ghcq" | "sarif"; signature: string; severityBucket: "high" | "critical" | "medium" | "low" | "nit"; occurrences: number; prs: string[]; sampleBodies: string[]; firstSeen: string; lastSeen: string; coveredByRule: boolean; }[]; lastUpdated: string; thresholdApplied: number; historyDepth: number; prsScanned: string[]; coveredPatterns: { paths: string[]; tool: "unknown" | "mixed" | "override" | "coderabbit" | "gca" | "greptile" | "ghcq" | "sarif"; signature: string; severityBucket: "high" | "critical" | "medium" | "low" | "nit"; occurrences: number; prs: string[]; sampleBodies: string[]; firstSeen: string; lastSeen: string; coveredByRule: boolean; }[]; }>; export type RecurrenceStats = z.infer; /** * Q5 normalization pipeline for finding bodies before signature hashing. * * Strips, in order: * - Triple-backtick code fences (```…```) * - URLs (http(s)://…) * - Backtick-spans (`code`) * - File paths with optional :line / :line:col suffix * (e.g. `packages/cli/src/foo.ts:42`, `src/x.ts:42:7`) * - Standalone line references (`line 42`, `line: 42`, ` :42`) * - Leading severity prefixes (`CRITICAL: `, `**Critical**`, etc.) * * Then lowercases and collapses internal whitespace. */ export declare function normalizeFindingBody(body: string): string; /** * Compute a 16-char hex SHA-256 prefix of the normalized text. * Stable + deterministic; collision probability negligible at this * input scale (per-repo PR history). */ export declare function computeSignature(normalized: string): string; /** * Tokenize text for Jaccard similarity: * - Split on whitespace + non-alphanumerics * - Lowercase * - Drop tokens of length ≤ 2 * - Drop a small stopword list */ export declare function tokenizeForJaccard(text: string): Set; /** * Jaccard similarity |A ∩ B| / |A ∪ B|. Returns 1.0 on identical sets, * 0.0 on disjoint sets, undefined-protected against the empty/empty case * (treated as 0 — they're not "the same pattern", they're both empty). */ export declare function jaccard(a: Set, b: Set): number; //# sourceMappingURL=recurrence-stats.d.ts.map