/** * Root-cause clustering. * * The judge files one finding per shot, so a single code cause shows up N * times: a theme that never switches produces a colour-scheme finding on every * light shot of every route. Dispatching those as N pieces of work would have N * sessions race each other to the same edit. * * A cluster is one target + category + attribute. That is the unit of work a * fix session is given, and the unit `verify-fix` rules on. Its KEY is derived * from those axes alone, so a defect re-found next week clusters onto the same * key it had today. * * Its ID is six random digits, minted once against that key and kept in the * backlog registry. The key is the identity lookout computes; the id is the * name people use. Keeping both means a re-found defect still merges (the key * decides that) while the number on the folder, the handoff and the commit * message never moves. */ import type { BacklogFinding, FindingStatus } from "../backlog/lib.js"; import { type PlatformKind, type Severity } from "../types.js"; import type { Category } from "../judge/rubric.js"; export interface FixCluster { /** Six digits: the `--issue` argument, the folder name, what people say. */ id: string; /** Derived identity: target + category + attribute. What dedupe turns on. */ key: string; target: string; /** Where the members were photographed: web, or the device platform. */ platform: PlatformKind; category: Category; attribute: string; /** Every distinct defect in the cluster, worst first; one entry per attribute. */ defects: { attribute: string; severity: Severity; title: string; problem: string; }[]; /** Worst severity among the members. */ severity: Severity; /** Representative title, taken from the worst member. */ title: string; problem: string; expected: string; observed: string; routes: string[]; fingerprints: string[]; members: BacklogFinding[]; /** Distinct screenshots the cluster appears on. */ shotCount: number; /** Findings in the cluster; higher than shotCount when rules share a shot. */ findingCount: number; /** Attempts already spent on this cluster (max across members). */ attemptsSpent: number; /** True when the adversarial verifier confirmed at least one member. */ verified: boolean; channel: BacklogFinding["channel"]; /** * The judge panel that filed the cluster's members: one category per AI * cluster, so one owner. Absent on other channels and on findings stamped * before the panels existed. */ judge?: string; } export declare function slug(s: string): string; type ClusterKeyAxes = Pick & { region?: BacklogFinding["region"]; platform?: BacklogFinding["platform"]; }; export declare function clusterKeyOf(f: ClusterKeyAxes): string; /** The key emitted before route identity became collision-free. */ export declare function legacyClusterKeyOf(f: ClusterKeyAxes): string; /** * The key a finding clustered under before regions existed, or null when the * new rules derive the same key. Frozen on purpose: issue-id succession * compares against this, and editing it retroactively re-answers which old * issue a new key descends from. */ export declare function priorClusterKeyOf(f: ClusterKeyAxes): string | null; export interface ClusterOptions { /** Worst-acceptable severity to include; defaults to every severity. */ minSeverity?: Severity; /** Statuses to draw from; defaults to open only. */ statuses?: FindingStatus[]; /** Clusters at or past this many attempts are left out (they are blocked). */ maxAttempts?: number; /** Restrict to these targets. */ targets?: string[]; /** * Issue id by cluster key. Every key present in the backlog has one: the * registry is reconciled whenever the backlog is loaded or saved. A missing * one is a bug in that reconciliation, not a state to render around, so it * throws rather than producing a nameless issue. */ issueIds?: Record; } /** * Group a backlog's findings into dispatchable clusters, worst first, and * within a severity the broadest blast radius first: fixing the thing that * affects the most shots is the fastest way to shrink the backlog. */ export declare function clusterFindings(findings: BacklogFinding[], opts?: ClusterOptions): FixCluster[]; /** A shell verdict needs at least this many routes behind it, and no more than the cap. */ export declare const SHELL_MIN_ROUTES = 2; export declare const SHELL_MAX_ROUTES = 3; /** * The routes a target's config lists, as paths. What `clusterScope` tops a * shell verify up from, and what the issue document names when it says which * routes lookout will photograph: one derivation, so the two cannot disagree. */ export declare function configuredRoutesOf(config: { targets?: { name: string; routes?: (string | { path: string; })[]; }[]; }, target: string): string[]; /** The capture scope that covers a cluster, for a scoped re-check. */ export declare function clusterScope(c: FixCluster, configuredRoutes?: string[]): { targets: string[]; routes: string[]; }; export {};