/** * GUCDI — `trace check` core (pure). The keystone lint that keeps the living, * bidirectional requirements spine coherent. It checks PROVENANCE-COMPLETENESS, * not requirement-coverage: every node must have an honest "why", from one of * - requirement — a PRD initiative (`prd_ref`) or a GitHub issue (`source_issue`) * - convention — a project-global standing doc (`conventions.md`) * - craft — a genuinely novel inline-tagged decision * A node with NONE of these is a true orphan → error. A craft-tagged node PASSES * (honest provenance) and is reported for PM ratification. This neither blocks * legitimate craft nor forces faking a requirement (the "block-or-lie" trap). * * It NEVER demands a PRD — brownfield specs trace via `source_issue`. Pure + * unit-tested; the IO (reading specs / PRD / scanning the LCR) is in ./index.ts. * * See docs/plans/gucdi-greenfield.md. */ export type LcrProvenance = { kind: "story"; id: string; } | { kind: "convention"; ref: string; } | { kind: "craft"; rationale: string; }; /** A spec, reduced to its requirement-provenance facts. */ export interface SpecNode { storyId: string; prdAnchor?: string; sourceIssue?: string; } /** An LCR file (mock component/page) + the provenance it declares in comments. */ export interface LcrNode { file: string; provenance: LcrProvenance[]; } export type TraceCode = "dangling-prd-ref" | "orphan-spec" | "orphan-lcr" | "dangling-lcr-story"; export interface TraceViolation { code: TraceCode; subject: string; detail: string; } export interface TraceResult { violations: TraceViolation[]; /** Craft decisions (not in any requirement) — the PM-ratification report. */ craft: { file: string; rationale: string; }[]; ok: boolean; } /** * Parse provenance comments from a source file. Recognised forms (case-insensitive * tag, anywhere in a comment): * // @story story-007 (or // story-007) * // @convention * // @craft (or // craft: ) */ export declare function parseLcrProvenance(source: string): LcrProvenance[]; /** * COVERAGE — the inverse of provenance. Provenance asks "does every surface * trace UP to a story?"; coverage asks "does every story trace DOWN to a * surface?". A story with zero LCR surfaces is either a real gap (a persona / * requirement the mock forgot to build) or a legitimately surface-less backend * story (DB schema, CI). The lint can't tell those apart on its own, so it * REPORTS uncovered stories and only FAILS when the caller opts in * (`--coverage`), or when a spec marks itself surface-bearing (future * `expects_surface`). This is the dogfood fix for "the mock covered only one * persona" — every story now gets checked for a home. */ export interface CoverageResult { /** story ids (story-NNN) that no LCR file references. */ uncovered: string[]; coveredCount: number; totalStories: number; ok: boolean; } export declare function checkCoverage(input: { specs: SpecNode[]; lcrNodes: LcrNode[]; }): CoverageResult; export interface SpecSurface { storyId: string; persona: string; route: string; home?: boolean; } /** A concrete surface route is satisfied by a router path when every segment * matches — a param segment (`:slug`) matches anything. e.g. `/u/:handle` * satisfies `/u/you`. */ export declare function routeSatisfies(routerPath: string, surfaceRoute: string): boolean; export interface SurfaceResult { /** declared surfaces whose route resolves to no real router path. */ dangling: SpecSurface[]; /** personas whose declared home route doesn't resolve (an unreachable persona). */ unreachablePersonas: string[]; surfaceCount: number; ok: boolean; } export declare function checkSurfaces(input: { surfaces: SpecSurface[]; routes: string[]; }): SurfaceResult; /** Normalise an anchor body so cosmetic reflow (trailing ws, blank-line runs) * doesn't churn the fingerprint — only semantic edits move it. */ export declare function normalizeAnchorBody(body: string): string; /** Dependency-free FNV-1a (64-bit) content hash → 16-hex. Not cryptographic; * it only needs to answer "did this PRD section change?". Pure + deterministic * across environments (no node:crypto, keeping this module IO-free). */ export declare function contentHash(s: string): string; /** Fingerprint of a PRD anchor's body (normalised). */ export declare function anchorHash(body: string): string; /** Current PRD: anchor → fingerprint. */ export interface PrdAnchorState { anchor: string; hash: string; } /** A spec's recorded link to the PRD (anchor it was refined against + the * fingerprint at stamp time, if stamped). */ export interface SpecLink { storyId: string; prdAnchor?: string; /** `prd_ref.sha` recorded at stamp time; absent = never stamped. */ prdSha?: string; } export interface StaleSpec { storyId: string; anchor: string; recorded: string; current: string; } export interface FreshnessResult { /** specs whose recorded fingerprint ≠ the current PRD anchor (the PRD moved). */ stale: StaleSpec[]; /** specs that link a PRD anchor but were never stamped (unknown freshness). */ unstamped: { storyId: string; anchor: string; }[]; freshCount: number; } /** * FRESHNESS — does each spec's recorded fingerprint still match its PRD anchor? * `stale` = the anchor body changed since the spec was stamped (the rot we want * to catch). Specs with no `prdAnchor` are skipped (orphans are `checkTrace`'s * job); anchors that no longer exist are skipped here (dangling-prd-ref already * flags those). Deterministic; advisory by default (cosmetic edits can move a * section hash, so the caller decides whether to fail). */ export declare function checkFreshness(input: { specs: SpecLink[]; anchors: PrdAnchorState[]; }): FreshnessResult; /** * IMPACT — given the set of PRD anchors whose content changed (e.g. from a git * diff between two PRD revisions), which stories link them? Forward direction * (PRD→stories). The reverse (story→PRD) is the same link read backward: a * changed story's `prdAnchor` is the PRD section to review. */ export declare function computeImpact(input: { specs: SpecLink[]; changedAnchors: string[]; }): { affected: { storyId: string; anchor: string; }[]; }; /** Diff two PRD states (anchor→hash) → anchors that changed, were added, or * removed. `changed` is what impact analysis keys on. */ export declare function diffPrdStates(before: PrdAnchorState[], after: PrdAnchorState[]): { changed: string[]; added: string[]; removed: string[]; }; export declare function checkTrace(input: { specs: SpecNode[]; /** PRD initiative anchors. Empty = no PRD (brownfield) → prd-ref checks skipped. */ prdAnchors: string[]; lcrNodes: LcrNode[]; }): TraceResult; //# sourceMappingURL=check.d.ts.map