/** * Spec link index — resolution and persistence. * * The I/O shell around the pure builder in `spec-link-index.ts`. It answers one * question for every mapping-dependent caller (audit, Repair, `mapping refresh`, * standalone generation finalization): *what are the current deterministic links?* * * The persisted `mapping.json` is a CACHE, never a prerequisite. When it is * absent, legacy, invalid, or bound to different inputs, the index is derived in * memory from the specs on disk plus the current graph — so Repair works on a * repository that has never run standalone generation (change * `harden-spec-workflow-lifecycle`, decision 671084e7). * * Coverage is unavailable only when an INPUT is missing (no analysis, no specs), * never merely because the cache was unusable. */ import type { MappingCoverageReason } from '../../types/index.js'; import type { DependencyGraphResult } from '../analyzer/dependency-graph.js'; import type { PipelineResult } from './spec-pipeline.js'; import { type SpecLinkIndex, type SpecLinkIndexSpecInput, type SpecSymbolRef } from './spec-link-index.js'; export interface LinkIndexResolutionAvailable { state: 'available'; index: SpecLinkIndex; /** `cache` when the persisted artifact was current, `derived` when rebuilt in memory. */ source: 'cache' | 'derived'; /** Why the cache was not used, when it was not. Reported, never fatal. */ cacheReason?: MappingCoverageReason; artifactPath: string; } export interface LinkIndexResolutionUnavailable { state: 'unavailable'; reason: MappingCoverageReason; message: string; remediation: string; artifactPath: string; } export type LinkIndexResolution = LinkIndexResolutionAvailable | LinkIndexResolutionUnavailable; export interface ResolveLinkIndexOptions { rootPath: string; /** Repo-relative openspec directory; defaults to `openspec`. */ openspecPath?: string; /** Restrict the corpus to these domains. Omit for the whole corpus. */ domains?: string[]; /** Persist a freshly derived index. Read-only callers leave this false. */ persist?: boolean; /** Pre-loaded graph, when the caller already read it. */ graph?: DependencyGraphResult | null; now?: () => Date; } export declare function analysisDirOf(rootPath: string): string; export declare function mappingArtifactPath(rootPath: string): string; /** * Identity of the analysis the links are resolved against. * * Today this is the exported-symbol inventory fingerprint — the same key the * legacy artifact used, so provenance stays comparable. Section 5 of this change * replaces it with the published generation id; this is the single place to swap. */ export declare function analysisGenerationId(graph: DependencyGraphResult): string; /** * Read every domain spec as link-index input. * * A spec path that escapes the repository root is dropped at the source rather * than parsed — anchors from it would be evidence about another tree. */ export declare function loadSpecCorpus(rootPath: string, openspecPath?: string, domains?: string[]): Promise; /** * The boundary that makes a cited file's export inventory unable to vouch for an absent symbol * (change: ground-generated-specs-in-the-graph): * * - `language-not-extracted` — exports are never extracted for this language; * - `file-not-analyzed` — the file exists but the analysis did not cover it. * * Parse health is deliberately NOT a boundary: its error regions come from the tree-sitter call-graph * extractors, while the export inventory comes from the import parser, which they do not affect — so * a tree-sitter error is no evidence the export list is incomplete. * * A boundary is named only for a file that is ANALYZED or EXISTS AS A REGULAR FILE, resolved to its * real spelling (symlinks, and letter case on a case-insensitive volume). A cited file that exists * nowhere, or is a directory, is no boundary: its absence is evidence, so the anchor stays `stale`. */ export declare function buildFileAssessor(rootPath: string, graph: DependencyGraphResult): Promise<(file: string) => string | undefined>; /** How the link index sees cited files on disk: the boundary, and the real spelling. */ export interface SpecFileView { assessFile(file: string): string | undefined; canonicalFile(file: string): string | undefined; } /** * Build the file view behind {@link buildFileAssessor}. Each cited file is resolved ONCE — a corpus * citing one file a thousand times pays for one `stat` — to its real repository spelling, or to * nothing when it is not a regular file inside the repository. A graph node deleted from disk after * analysis resolves to nothing too, so its absent symbol is `stale`, not excused. */ export declare function buildFileView(rootPath: string, graph: DependencyGraphResult): Promise; /** * Resolve the current deterministic link index: cache when it is current, * in-memory derivation otherwise. */ export declare function resolveSpecLinkIndex(options: ResolveLinkIndexOptions): Promise; /** Persist the index as `mapping.json`. */ export declare function writeSpecLinkIndex(rootPath: string, index: SpecLinkIndex): Promise; /** One requirement's proposed implementation symbol, as produced by generation. */ export interface RequirementAnchorProposal { domain: string; requirement: string; /** The proposed symbol name. An empty or absent name is simply not verifiable. */ symbol?: string; } /** * Flatten a pipeline result into one anchor proposal per requirement. * * Only the LLM's explicitly proposed symbol (`functionName`, or a sub-spec's * `callee`) is carried forward. Operation descriptions and names are NOT used to * search for a symbol — that search was the semantic/heuristic fallback this * change removes. */ export declare function requirementAnchorProposals(pipeline: PipelineResult): RequirementAnchorProposal[]; /** * Keep only the proposals that resolve to exactly one exported symbol. * * This is the gate between a generator's PROPOSAL and a written spec ANCHOR: a * name that resolves to nothing or to several identities is dropped, so the spec * is written with no anchor for that requirement instead of a probabilistic one. * Standalone generation and agent-hosted skills therefore write anchors under the * same rule the link index later reads them under. */ export declare function verifyRequirementAnchors(proposals: RequirementAnchorProposal[], graph: DependencyGraphResult): Map; /** * Shape a resolution as the requirement→code view served by `get_mapping` and * consumed by Repair. One shaping for both callers, so a Repair that reuses an * already-parsed graph returns exactly what the MCP tool would. */ export declare function mappingViewOf(resolution: LinkIndexResolution, domain?: string, orphansOnly?: boolean): Record; /** * File-qualified keys (`file::name`) of every symbol a requirement links to. * * ONLY qualified. A bare `name` key was previously added alongside, which made * coverage leak across files: an anchor on `foo::src/a.ts` marked an unrelated * `foo` in `src/b.ts` covered too, inflating the audit. Every linked symbol is a * uniquely resolved reference, so its file is always known — including for a * bare-name anchor, which only links when exactly one symbol carries that name. */ export declare function coveredSymbolKeys(index: SpecLinkIndex): Set; /** * Requirements that establish no function coverage. * * `unmapped` and `stale` requirements are orphans: the first cites no exact * symbol, the second cites one that is gone. An `ambiguous` requirement is NOT an * orphan — it names a real symbol that the repository defines more than once — and * neither is a `not-assessed` one, whose citation the analysis simply cannot check. */ export declare function orphanRequirementsOf(index: SpecLinkIndex, domains?: Set): Array<{ requirement: string; domain: string; specFile: string; state: SpecLinkIndex['links'][number]['state']; }>; //# sourceMappingURL=spec-link-service.d.ts.map