import type { ModelGraph } from "../loader/model-graph.js"; /** * Model-side coverage: how much of the model is actually bound to something * checkable. This is the OPPOSITE direction from `reconcile`, which asks * "does the code contain a route/queue/poller the model forgot to register?" * — a whole-repo, code→model measure that reads near-100% on a model that * covers one flow, because unregistered facts are the only thing it can see. * Nothing in that number tells a reader how much of the MODEL is anchored, * so a report carrying only `reconcile` invites "96% — looks covered". * * Every count here is DIRECT attachment (`Loop.anchors`, `Loop.scenarios`, * `Junction.scenarios`). Resolving `applies_to` into the covered counts would * defeat the point: one `owner_match` invariant matches every loop whose free-text * `owner` contains the pattern, which silently flips large parts of the model to * "covered" without any loop-specific behavior being written down. That number is * still worth seeing, so it is reported on its own labelled line * (`loopsOnlyViaSelector`) and never folded into `loopsWithScenarios`. */ export interface FlowCoverage { id: string; title: string; /** Loops in `traverses` (guarded_by/references deliberately excluded — see computeCoverage). */ loops: number; /** Of those, how many carry >=1 directly-attached scenario. */ loopsWithScenarios: number; /** * Whether the flow itself carries a directly-attached anchor (F2a) — its OWN * claim, distinct from any anchor a traversed loop carries. This is the axis * `conformance`'s code check grades a flow on (conformance.ts's `gradeFlow`), * so a zero-loop flow (traverses empty) that is anchored this way is NOT the * same as an unbound flow — see `flowsWithAnchors` on `Coverage`. */ hasOwnAnchor: boolean; /** * Whether the flow itself carries a directly-attached scenario (F2b), same * direct-attachment rule as `loopsWithScenarios` (not resolved via * `applies_to`) and the same axis conformance's `gradeFlow` test-checks. */ hasOwnScenario: boolean; } export interface Coverage { loops: number; loopsWithAnchors: number; loopsWithScenarios: number; /** * Loops with no directly-attached scenario that are nonetheless selected by * some scenario's `applies_to`. Reported separately so query-time selection * can't masquerade as attached coverage — deliberately NOT split by selector * kind: `nodes` names a loop explicitly and `owner_match` sweeps a package, * but neither is written on the loop, which is the distinction this line draws. */ loopsOnlyViaSelector: number; /** * Model files that failed to parse. Non-zero means every count here is a LOWER * BOUND on a partially-loaded graph — critically, it can move the reported * PERCENTAGES either way: dropping an unanchored loop shrinks the denominator * and makes coverage look better (measured: one broken file took 27/67=40% to * 26/57=46%). Silently reporting the higher number would make a command whose * whole purpose is to stop over-reading coverage do exactly that, so this is * surfaced in the output rather than left to `check` in the same job. */ parseErrors: number; junctions: number; junctionsWithScenarios: number; scenarios: number; /** Scenarios with a non-empty `verified_by` (the rest are unverified by construction). */ scenariosVerified: number; /** DISTINCT `verified_by` anchor strings — the real anti-rot surface, always <= the sum of references. */ uniqueTestAnchors: number; /** * Flows `conformance` actually grades (`isGradedFlow`) — the denominator the * two counts below are reported over. Using ALL flows would leave coverage * and conformance disagreeing about the same model from the other side: a * model with 1 anchored flow and 9 composition-only ones would read 1/10 * here while conformance grades exactly one flow and excludes nine. */ gradedFlows: number; /** * Flows with >=1 directly-attached anchor of their OWN (F2a) — separate * denominator from `loopsWithAnchors`, and NOT folded into any `flows[].loops` * figure. This is what closes the disagreement `conformance` and `coverage` * used to have on a flow-shaped repo: `conformance`'s `gradeFlow` already * grades a flow on these same own anchors, so a zero-`traverses` flow that is * anchored this way is "covered" on both reports now, not just one (issue #16). */ flowsWithAnchors: number; /** Flows with >=1 directly-attached scenario of their OWN (F2b) — see `flowsWithAnchors`. */ flowsWithScenarios: number; flows: FlowCoverage[]; } /** * Per-flow coverage counts only `traverses`. `guarded_by` holds watchdog loops * that cover a flow without sitting in its sequence, and `references` holds * sub-flows — folding either in would let C1's coverage leak into every flow * that C1 guards or references, which is precisely the "looks more covered than * it is" failure this command exists to prevent. * * A loop traversed by several flows counts toward each of them. That is * intentional: the question a flow row answers is "is THIS chain's behavior * written down", and a shared loop genuinely is written down for both. */ export declare function computeCoverage(graph: ModelGraph, parseErrors?: number): Coverage; /** * Plain-text report, one fact per line, shaped to drop into the same job * summary as `check`/`reconcile` output. Flows with zero covered loops are * listed explicitly rather than summarized as a count — "C4 会话复活: 0/3" * is the line that stops a reader assuming the whole model is anchored. * A flow's own anchor/scenario (F2a/F2b) is reported on the SAME line as its * traverses-derived ratio but never merged into it — see the `own` bracket * below — so a zero-loop, flow-shaped-repo flow (issue #16) reads as covered * without turning `X/Y` into a ratio nobody can reconstruct from the parts. */ export declare function formatCoverage(coverage: Coverage): string[]; //# sourceMappingURL=coverage.d.ts.map