import type { ModelGraph } from "../loader/model-graph.js"; import type { TestTextAnchor } from "../schema/index.js"; /** * Implementation conformance: a MODEL→CODE report card. `coverage` asks how * much of the model is bound to anything checkable (a model self-audit); * `reconcile` asks what the code contains that the model forgot (code→model). * This third view holds the model up as the acceptance checklist and asks, per * node, "does the implementation measure up — is the declared behavior anchored * in real code and guarded by a real test, and where are the gaps?". * * The direction of authority never inverts: a gap is a debt the IMPLEMENTATION * owes the model, not a mark against the model's credibility. When the model * declares a boundary/queue/junction the code can't be shown to satisfy, that * is a code/test gap — the model stays the source of truth (001 §2 三分离; * the whole reason loopgraph can catch drift a descriptive code-graph can't). * * Like `coverage`, this counts only DIRECTLY-attached scenarios (Loop.scenarios, * Junction.scenarios). `applies_to`-selected scenarios are resolved at query * time and not written on the node, so folding them in would let one * cross-cutting invariant flip large parts of the model to "met" without any * node-specific test being written — exactly the over-report coverage.ts guards * against. Zero LLM, zero network, zero code execution: anchor/test resolution * is file existence PLUS symbol/text presence (exactly `checkAnchorExistence`'s * fidelity, from the same module — see presence.ts and `staleAnchors` below), * and queue matching is name set-membership over already-extracted facts. */ export type ConformanceVerdict = "met" | "partial" | "gap"; /** * Every gap is defined BY THE MODEL — it names a declared obligation the * implementation side can't be shown to satisfy: * - `no-anchor` loop declares behavior but no code anchor binds it * - `anchor-missing` an anchor's file does not exist under the repo (repo-resolved only) * - `anchor-stale` every anchor's file exists but none still names its symbol (repo-resolved only) * - `no-scenario` node's behavior/risk is not pinned to any GWT scenario (unguarded) * - `scenario-unverified` an attached scenario has no `verified_by` test * - `test-missing` a `verified_by` test file does not exist under the repo (repo-resolved only) * - `test-stale` a `verified_by` test file exists but no longer names the test (repo-resolved only) * - `queue-unmatched` a declared `consumes_queues` name matched no extracted fact (adapter only) * - `evidence-missing` a junction evidence anchor's file does not exist under the repo (repo-resolved only) */ export type GapKind = "no-anchor" | "anchor-missing" | "anchor-stale" | "no-scenario" | "scenario-unverified" | "test-missing" | "test-stale" | "queue-unmatched" | "evidence-missing"; export interface Gap { nodeId: string; nodeKind: "loop" | "junction" | "flow"; kind: GapKind; /** Which anchor / queue / scenario is missing — the actionable pointer. */ detail: string; } /** "present"/"missing" are structural unless `repoResolved`, when they also mean the file exists. */ export type AxisStatus = "present" | "missing"; export interface NodeConformance { id: string; kind: "loop" | "junction" | "flow"; title: string; verdict: ConformanceVerdict; /** implementation anchored (and, when repo-resolved, the anchor file exists). */ code: AxisStatus; /** behavior guarded by a verified scenario (and, when repo-resolved, its test file exists). */ test: AxisStatus; /** * Distinct `level`s of the scenarios that actually counted toward the test * axis, sorted. Reported, NOT judged (Proposal 016 D9): `test✓` today cannot * tell a unit test CI runs on every push from an e2e script that needs an API * key and a live provider, and a reader deciding how much to trust a green * node deserves to see which it is. Turning level into a grading input needs * a policy nobody has agreed on yet; showing the evidence needs nothing. */ testLevels: string[]; gaps: Gap[]; } export interface Conformance { nodes: NodeConformance[]; /** Every node's gaps, flattened — the punch-list. */ gaps: Gap[]; counts: { met: number; partial: number; gap: number; }; /** * Non-dormant loops + all junctions + flows that carry their OWN code binding * are graded; dormant loops and composition-only flows are excluded and only * counted (see `dormantExcluded` / `flowsExcluded`). */ graded: number; dormantExcluded: number; /** * Flows excluded from grading because they compose other model nodes * (`traverses`/`references`/…) and carry no own anchors: their constituent * loops are already graded, so grading the flow "met" would double-count that * green and inflate the headline (the same over-report `applies_to` scenarios * are kept out of grading to avoid). Counted, never silently dropped. */ flowsExcluded: number; /** * Whether anchor/test FILE existence was actually checked (a repo root was * supplied). When false, `present`/`missing` are STRUCTURAL — a `met` means * "declared", not "verified against code" — and no *-missing gaps are emitted. */ repoResolved: boolean; /** * How many DISTINCT repo files the whole model anchors into (`= * anchorFilesToResolve(graph).length`). Reported at the top of the report as * a SEARCH-THOROUGHNESS proxy, never as business completeness (Proposal 016 * T5): it says how much of the codebase the modelling pass actually touched, * so a 3-file model can no longer present itself as a system map with the * same voice as a 96-file one. The real completeness question — which * behaviours were missed — has no machine-computable denominator. */ anchoredFileCount: number; /** Whether an adapter was available to extract queue facts (else queue gaps are skipped). */ queueChecked: boolean; /** How many `consumes_queues` obligations the graded loops declare (for the skip note). */ declaredQueueCount: number; parseErrors: number; } export interface ConformanceInputs { /** * Repo-relative file paths that EXIST under the repo root. `undefined` ⇒ no * repo root given: file existence is not checked and declared anchors/tests * are trusted structurally (see `repoResolved`). Authoritative when present: * a file computed here but absent from the set is treated as missing, so the * caller MUST have stat'd every file `anchorFilesToResolve` returns. */ existingFiles?: Set; /** * Anchors whose FILE exists but which no longer name anything in it — * `AnchorPresence.staleSymbolAnchors` ∪ `staleTextAnchors`, keyed by the * anchor string (symbol form) or `testTextAnchorLabel` (text form). * * This is the P0 fix of Proposal 016 T6/D1. Before it, a renamed symbol left * the score untouched — `check` warned, the report card said `met code✓ * test✓`, and on a real target the model's only `met` node stood on five * test anchors that named nothing in their files. `undefined` ⇒ not computed * (no repo root): nothing is stale, same posture as `existingFiles`. * * NOT symmetrical with `existingFiles`, and the asymmetry is the point: * a missing file is a hard fact, while "the file no longer mentions this * name" is whole-word text matching (symbol.ts) that a legitimate refactor * can trip. So a stale anchor never fails the gate (`check` keeps it a * warning) and downgrades the CODE axis only when EVERY anchor on the node * is stale — one stale anchor among eight still leaves the node bound to * real code. On the TEST axis a stale anchor does disqualify its scenario, * because each `verified_by` entry is a separate claim that a specific test * covers this behaviour, and a claim that points at nothing is not evidence. */ staleAnchors?: Set; /** * Names of extracted queue facts (`ImplementationFact.name` for the adapter's * name-matchable signal kinds). `undefined` ⇒ no adapter: queue matching is * skipped (not treated as all-missing — an absent adapter is not a gap). */ queueFactNames?: Set; } /** * Every repo-relative file path this computation will test for existence, as a * VIEW over the same two enumerations the CLI resolves (`collectAnchorStrings` * + `textAnchorsToResolve`) — not a third hand-maintained list of anchor- * bearing fields. * * It used to be that list, and the drift it invites is not hypothetical: the * caller must stat exactly the files this computation asks about, or an anchor * it forgot reads as `anchor-missing` on every repo-resolved run — a fabricated * gap on a node that is perfectly fine. Deriving both from one enumeration is * the same 派生判断只定义一处 rule that made `check` and `conformance` share * presence.ts in the first place. Table-style anchors (no `#`) have no file and * are excluded — like `checkAnchorExistence`, never resolved, never a gap. */ export declare function anchorFilesToResolve(graph: ModelGraph): string[]; /** Every `{file, text}` test anchor in the graph — the text half of `verified_by`. */ export declare function textAnchorsToResolve(graph: ModelGraph): TestTextAnchor[]; /** * Grade every non-dormant Loop and every Junction against the code/test the * model declares. Dormant loops (N-series baseline placeholders — owner-null, * unwired) are NOT graded: they exist to quiet reconcile's advisory signal, not * to assert modeled behavior, so grading them would manufacture gaps for * something deliberately left unimplemented. They are counted in * `dormantExcluded` so the exclusion is visible, never silent (001 §12). */ export declare function computeConformance(graph: ModelGraph, inputs?: ConformanceInputs, parseErrors?: number): Conformance; /** * Plain-text report, shaped to drop into the same job summary as * `check`/`coverage`. Leads with the honesty banners (parse errors, unresolved * mode) for the same reason coverage does — a reader who stops at the headline * must not walk away with a number that a broken file or an unresolved run * silently inflated. Graded nodes are listed worst-first; the gap punch-list is * the actionable tail. */ export declare function formatConformance(c: Conformance): string[]; //# sourceMappingURL=conformance.d.ts.map