/** * Coverage Evidence reader for the istanbul `coverage-summary.json` shape (Fix-6B). * * - pure helper module, no side effects beyond FS reads * - reads the istanbul-reporter JSON shape a `--reporter=json-summary` run emits * (the project's instrument is `.husky/coverage-ratchet.mjs`, which emits it) * - resolves a `coverage-summary.json` from a fixed discovery order * - validates per-capability coverage against a `## Capability Mapping` block * * The module is hermetic: every function takes its inputs explicitly and returns * either a typed success result or a typed error code. There is no global state. * Throws only when the input shape is structurally malformed (the caller cannot * recover by branching on a Result variant); otherwise returns Result. * * Conventions: * - All paths are POSIX-normalized for JSON safety. * - The summary's per-file keys are project-root-relative POSIX paths * (e.g. `src/services/openspec/openspec-archive-service.ts`). * - Capability mapping `source` field is also project-root-relative, may be * a file (`foo.ts`) or a directory (`src/services/openspec/`). */ import { type Result } from './artifact-boundary.js'; export type CoverageSummaryPathError = { code: 'missing'; triedPaths: ReadonlyArray; } | { code: 'not-readable'; path: string; message: string; }; export type CoverageSummary = { /** Resolved absolute path to the summary file. */ path: string; /** ISO timestamp of the summary file's mtime. */ capturedAt: string; /** Project-root-relative per-file coverage entries. */ files: ReadonlyMap; }; export type CoverageFileEntry = { /** Absolute path the summary entry covers (POSIX). */ path: string; /** Project-root-relative POSIX path (the key the summary uses). */ relativePath: string; statements: { pct: number; covered: number; total: number; }; branches: { pct: number; covered: number; total: number; }; functions: { pct: number; covered: number; total: number; }; lines: { pct: number; covered: number; total: number; }; }; /** * A single row from the proposal.md `## Capability Mapping` table. * Mirror shape of CoverageRequirementRow (Fix-6A) for symmetry. */ export type CapabilityMappingRow = { /** Capability name (e.g. `quality-gates`). */ capability: string; /** Project-root-relative source path — file or directory. */ source: string; /** Optional test anchor (informational; Fix-6B does not validate). */ testAnchor?: string; /** Source line for diagnostics. */ line: number; }; export type CapabilityCoverageMismatch = { capability: string; source: string; failingFiles: ReadonlyArray<{ path: string; actual: { statements: number; branches: number; functions: number; lines: number; }; reason: 'below-threshold' | 'missing-from-summary'; }>; }; export type CoverageValidation = { ok: true; } | { ok: false; mismatches: ReadonlyArray; }; /** * Resolve the path to a coverage-summary.json. * * Discovery order (per Fix-6B AC1): * 1. `explicitPath` (passed via `--coverage-summary`) * 2. `/coverage/coverage-summary.json` (the coverage run's default) * 3. `/openspec/coverage-summary.json` (per-project override) * * Returns: * - `{ ok: true, value: path }` when the file exists. * - `{ ok: false, error: { code: 'missing', triedPaths } }` otherwise. */ export declare function resolveCoverageSummaryPath(input: { projectRoot: string; explicitPath?: string; }): Promise>; /** * Read and parse a coverage-summary.json into a typed `CoverageSummary`. * * Expected istanbul-reporter shape: * * { * "total": { "lines": { "pct": 100, ... }, ... }, * "src/foo.ts": { "lines": { "pct": 100, ... }, ... }, * ... * } * * Throws only on structural malformation (caller cannot branch on a Result). * The fix-6B gate handles a missing summary via the discovery layer, not here. */ export declare function readC8Summary(path: string): Promise; /** * Parse the `## Capability Mapping` block out of a proposal.md. * * Accepted shape (markdown table): * * ## Capability Mapping * * | capability | source | testAnchor | * | --- | --- | --- | * | quality-gates | src/services/openspec/openspec-archive-service.ts | tests/unit/openspec-archive-service.test.ts | * | artifact-workspace | src/services/openspec/artifact-boundary.ts | tests/unit/services/openspec/artifact-boundary.test.ts | * * Returns `{ present: false }` when the heading is missing (Fix-6B back-compat: * the change does not declare a mapping, so no per-capability check is possible * and the gate stays at Fix-6A's declarative level). */ export declare function parseCapabilityMapping(proposalPath: string): Promise<{ rows: ReadonlyArray; present: boolean; }>; /** * Resolve the source files that a capability row covers. * * - File path: returns `[file]`. * - Directory path: returns every `*.ts` file recursively under it (relative * to `projectRoot`, POSIX separators). Non-TS files are excluded because * the summary only carries TS source modules. */ export declare function resolveCapabilityFiles(input: { projectRoot: string; row: CapabilityMappingRow; }): Promise>; /** * Validate per-capability coverage. Each capability row's `source` is resolved * to one or more files; each file must appear in the summary with all four * metrics at exactly 100%. * * Returns `{ ok: true }` when every capability passes; `{ ok: false, mismatches }` * otherwise. The caller is responsible for rendering the mismatches into the * `OPENSPEC_COVERAGE_EVIDENCE_MISMATCH` error envelope. */ export declare function validateCapabilityCoverage(input: { projectRoot: string; summary: CoverageSummary; rows: ReadonlyArray; }): Promise; /** * Compare the mtime of every spec/proposal/tasks file under the change against * the mtime of the summary. If any change file is newer than the summary, * the captured V8 counters no longer match the source on disk and the summary * is stale. * * Returns the list of stale files (project-root-relative POSIX paths). An empty * array means the summary is fresh. */ export declare function findStaleChangeFiles(input: { projectRoot: string; openspecRoot: string; changeId: string; summary: CoverageSummary; }): Promise>;