@holmes-lab/holmes-kit 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/.build-id +1 -1
  3. package/dist/holmes/assoc/assoc-arm.d.ts +94 -0
  4. package/dist/holmes/assoc/assoc-arm.js +187 -0
  5. package/dist/holmes/assoc/explore.d.ts +21 -0
  6. package/dist/holmes/assoc/explore.js +160 -0
  7. package/dist/holmes/assoc/impact-baseline.d.ts +13 -0
  8. package/dist/holmes/assoc/impact-baseline.js +48 -0
  9. package/dist/holmes/assoc/ppr.d.ts +64 -0
  10. package/dist/holmes/assoc/ppr.js +110 -0
  11. package/dist/holmes/cli/agents.d.ts +24 -11
  12. package/dist/holmes/cli/agents.js +93 -17
  13. package/dist/holmes/cli/codex-toml.d.ts +26 -0
  14. package/dist/holmes/cli/codex-toml.js +282 -0
  15. package/dist/holmes/cli/doctor.d.ts +56 -0
  16. package/dist/holmes/cli/doctor.js +348 -18
  17. package/dist/holmes/cli/index.js +16 -2
  18. package/dist/holmes/cli/init.js +78 -0
  19. package/dist/holmes/cli/interactive-prompt.js +4 -4
  20. package/dist/holmes/cli/mcp-launcher.d.ts +2 -2
  21. package/dist/holmes/cli/semantic-key.d.ts +19 -0
  22. package/dist/holmes/cli/semantic-key.js +93 -0
  23. package/dist/holmes/config/config.d.ts +9 -0
  24. package/dist/holmes/config/config.js +8 -1
  25. package/dist/holmes/cpg/language-capability.d.ts +65 -0
  26. package/dist/holmes/cpg/language-capability.js +145 -0
  27. package/dist/holmes/cpg/language-parser-walk.js +179 -34
  28. package/dist/holmes/cpg/language-parser.d.ts +1 -1
  29. package/dist/holmes/governance/ledger-rechain.d.ts +12 -0
  30. package/dist/holmes/governance/ledger-rechain.js +17 -2
  31. package/dist/holmes/governance/provenance-ledger.js +21 -0
  32. package/dist/holmes/guardrail/blind-spots.js +12 -1
  33. package/dist/holmes/guardrail/impact-gate.d.ts +77 -0
  34. package/dist/holmes/guardrail/impact-gate.js +263 -0
  35. package/dist/holmes/guardrail/write-target.d.ts +38 -1
  36. package/dist/holmes/guardrail/write-target.js +48 -4
  37. package/dist/holmes/hooks/adapters/antigravity.js +12 -1
  38. package/dist/holmes/hooks/corrupt-state-run.d.ts +33 -0
  39. package/dist/holmes/hooks/corrupt-state-run.js +16 -0
  40. package/dist/holmes/hooks/pre-tool-use.d.ts +26 -0
  41. package/dist/holmes/hooks/pre-tool-use.js +203 -11
  42. package/dist/holmes/hooks/stop.d.ts +65 -0
  43. package/dist/holmes/hooks/stop.js +200 -2
  44. package/dist/holmes/mcp/handlers.d.ts +88 -0
  45. package/dist/holmes/mcp/handlers.js +597 -16
  46. package/dist/holmes/mcp/maintenance-analyze.d.ts +435 -0
  47. package/dist/holmes/mcp/maintenance-analyze.js +994 -0
  48. package/dist/holmes/mcp/maintenance-evidence.d.ts +140 -0
  49. package/dist/holmes/mcp/maintenance-evidence.js +253 -0
  50. package/dist/holmes/mcp/tool-schemas.js +71 -0
  51. package/dist/holmes/project/root.js +3 -1
  52. package/dist/holmes/review/baseline-arm.d.ts +37 -0
  53. package/dist/holmes/review/baseline-arm.js +51 -0
  54. package/dist/holmes/review/captured-stdin-guard.d.ts +8 -0
  55. package/dist/holmes/review/captured-stdin-guard.js +48 -0
  56. package/dist/holmes/review/coherence-verify.d.ts +31 -0
  57. package/dist/holmes/review/coherence-verify.js +144 -0
  58. package/dist/holmes/review/commit-text.d.ts +50 -0
  59. package/dist/holmes/review/commit-text.js +76 -0
  60. package/dist/holmes/review/confidence-calibration.d.ts +39 -0
  61. package/dist/holmes/review/confidence-calibration.js +39 -0
  62. package/dist/holmes/review/content-baseline.d.ts +38 -0
  63. package/dist/holmes/review/content-baseline.js +103 -0
  64. package/dist/holmes/review/content-verify.d.ts +20 -0
  65. package/dist/holmes/review/content-verify.js +73 -0
  66. package/dist/holmes/review/dense-retrieval.d.ts +66 -0
  67. package/dist/holmes/review/dense-retrieval.js +97 -0
  68. package/dist/holmes/review/edge-quality.d.ts +44 -0
  69. package/dist/holmes/review/edge-quality.js +117 -0
  70. package/dist/holmes/review/evaluation-metrics.d.ts +138 -0
  71. package/dist/holmes/review/evaluation-metrics.js +175 -0
  72. package/dist/holmes/review/graph-verifier.d.ts +34 -0
  73. package/dist/holmes/review/graph-verifier.js +62 -0
  74. package/dist/holmes/review/hop-ablation.d.ts +100 -0
  75. package/dist/holmes/review/hop-ablation.js +89 -0
  76. package/dist/holmes/review/manual-baseline.d.ts +209 -0
  77. package/dist/holmes/review/manual-baseline.js +2846 -0
  78. package/dist/holmes/review/oracle-gap.d.ts +32 -0
  79. package/dist/holmes/review/oracle-gap.js +102 -0
  80. package/dist/holmes/review/point-in-time-replay.d.ts +41 -0
  81. package/dist/holmes/review/point-in-time-replay.js +161 -0
  82. package/dist/holmes/review/rank-diagnosis.d.ts +43 -0
  83. package/dist/holmes/review/rank-diagnosis.js +163 -0
  84. package/dist/holmes/review/replay-calibration.d.ts +62 -0
  85. package/dist/holmes/review/replay-calibration.js +83 -0
  86. package/dist/holmes/review/replay-corpus.d.ts +135 -0
  87. package/dist/holmes/review/replay-corpus.js +210 -0
  88. package/dist/holmes/review/run-replay.d.ts +260 -0
  89. package/dist/holmes/review/run-replay.js +729 -0
  90. package/dist/holmes/review/semantic-arm.d.ts +271 -0
  91. package/dist/holmes/review/semantic-arm.js +717 -0
  92. package/dist/holmes/review/semantic-retrieval.d.ts +55 -0
  93. package/dist/holmes/review/semantic-retrieval.js +156 -0
  94. package/dist/holmes/review/spec-layer-stats.d.ts +38 -0
  95. package/dist/holmes/review/spec-layer-stats.js +52 -0
  96. package/dist/holmes/review/temporal-prior.d.ts +33 -0
  97. package/dist/holmes/review/temporal-prior.js +53 -0
  98. package/dist/holmes/review/test-runner.d.ts +15 -0
  99. package/dist/holmes/review/test-runner.js +41 -4
  100. package/dist/holmes/review/test-selection-breadth.d.ts +75 -0
  101. package/dist/holmes/review/test-selection-breadth.js +57 -0
  102. package/dist/holmes/review/traceability-benchmark.d.ts +81 -0
  103. package/dist/holmes/review/traceability-benchmark.js +135 -0
  104. package/dist/holmes/review/union-verify.d.ts +12 -0
  105. package/dist/holmes/review/union-verify.js +70 -0
  106. package/dist/holmes/rtm/graph-store.d.ts +51 -0
  107. package/dist/holmes/rtm/graph-store.js +122 -0
  108. package/dist/holmes/rtm/incremental.d.ts +25 -1
  109. package/dist/holmes/rtm/incremental.js +18 -1
  110. package/dist/holmes/rtm/localize.d.ts +28 -0
  111. package/dist/holmes/rtm/localize.js +272 -14
  112. package/dist/holmes/rtm/rtm-builder.d.ts +118 -3
  113. package/dist/holmes/rtm/rtm-builder.js +265 -28
  114. package/dist/holmes/rtm/rtm-graph.d.ts +117 -2
  115. package/dist/holmes/rtm/rtm-graph.js +194 -31
  116. package/dist/holmes/rtm/taint-benchmark.d.ts +97 -0
  117. package/dist/holmes/rtm/taint-benchmark.js +141 -0
  118. package/dist/holmes/rtm/test-scope.js +24 -1
  119. package/dist/holmes/semantic/credentials.d.ts +15 -0
  120. package/dist/holmes/semantic/credentials.js +134 -0
  121. package/dist/holmes/semantic/embedder.d.ts +44 -0
  122. package/dist/holmes/semantic/embedder.js +185 -0
  123. package/dist/holmes/semantic/hit-rerank.d.ts +4 -0
  124. package/dist/holmes/semantic/hit-rerank.js +38 -0
  125. package/dist/holmes/semantic/tier.d.ts +37 -0
  126. package/dist/holmes/semantic/tier.js +54 -0
  127. package/dist/holmes/semantic/vector-cache.d.ts +11 -0
  128. package/dist/holmes/semantic/vector-cache.js +91 -0
  129. package/dist/holmes/spec/acceptance-quality.d.ts +81 -0
  130. package/dist/holmes/spec/acceptance-quality.js +169 -0
  131. package/dist/holmes/spec/validator.js +33 -1
  132. package/dist/holmes/spec/yaml-scalar.d.ts +1 -0
  133. package/dist/holmes/spec/yaml-scalar.js +43 -0
  134. package/package.json +1 -1
@@ -0,0 +1,435 @@
1
+ import type { ScannedFile } from '../cpg/cpg-scanner';
2
+ import type { Spec } from '../spec/spec-parser';
3
+ import type { RtmGraph } from '../rtm/rtm-graph';
4
+ import type { ContextBundle } from '../context/bundler';
5
+ import { type TestScope } from '../rtm/test-scope';
6
+ import type { ResolutionReport } from '../rtm/rtm-builder';
7
+ import { type TestEvidence } from '../review/test-evidence';
8
+ import { type LanguageGap } from '../cpg/language-capability';
9
+ export declare const MAINTENANCE_ANALYSIS_SCHEMA = "maintenance-analysis/1.13";
10
+ export declare const MAINTENANCE_ANALYSIS_EXTRACTOR = "holmes-graph-fusion/1.13";
11
+ /**
12
+ * The evidence kinds an ablation arm can actually produce. Completeness is measured by asking each
13
+ * arm which of these it FILLED — the first cut hardcoded 1 / 2 / 2+n, so "enhanced beats current"
14
+ * was an identity of the code rather than a result of a measurement.
15
+ */
16
+ export declare const EVIDENCE_DIMENSIONS: readonly ["candidates", "specs", "directImpact", "transitiveImpact", "tests", "history", "versionedBasis"];
17
+ export type EvidenceDimension = typeof EVIDENCE_DIMENSIONS[number];
18
+ /**
19
+ * Externally supplied truth for a replayed change — normally the files, symbols, and test files a
20
+ * historical commit actually touched. The arms cannot see it; it only scores them.
21
+ */
22
+ export interface MaintenanceGroundTruth {
23
+ files?: readonly string[];
24
+ symbols?: readonly string[];
25
+ tests?: readonly string[];
26
+ }
27
+ /**
28
+ * @implements A-SPEC-271
29
+ * How many ranked candidates form the `primary` tier. A fixed rank cut, not a score threshold:
30
+ * measured over the 12-commit replay corpus at matched set sizes, a `score >= ratio * top` cut was
31
+ * never better than the plain rank cut on both axes at once (top-3 beat ratio>=0.5 on recall AND
32
+ * precision), and tuning a threshold on 12 samples would be fitting noise.
33
+ */
34
+ export declare const PRIMARY_TIER_WIDTH = 3;
35
+ export interface AblationArm {
36
+ localizationCandidates: number;
37
+ localizationRecall: number | null;
38
+ localizationPrecision: number | null;
39
+ impactRecall: number | null;
40
+ impactPrecision: number | null;
41
+ testRecall: number | null;
42
+ testPrecision: number | null;
43
+ evidenceDimensions: EvidenceDimension[];
44
+ evidenceCompleteness: number;
45
+ /**
46
+ * @implements A-SPEC-271
47
+ * Recall/precision of the `primary` tier alone. Reported only by the arm that HAS tiers; null
48
+ * everywhere ground truth is absent, exactly like the flat metrics.
49
+ */
50
+ primaryRecall: number | null;
51
+ primaryPrecision: number | null;
52
+ /**
53
+ * @implements A-SPEC-274
54
+ * The closure-only tier, scored separately so the decision to drop it from the prediction stays
55
+ * open to review: if another corpus shows it carrying true positives, the number says so.
56
+ */
57
+ impactedFilesRecall: number | null;
58
+ impactedFilesPrecision: number | null;
59
+ /** @implements A-SPEC-289 — the import tier, scored on its own so its worth stays visible. */
60
+ importedByRecall: number | null;
61
+ importedByPrecision: number | null;
62
+ }
63
+ export interface MaintenanceHistoryRecord {
64
+ commit: string;
65
+ date: string;
66
+ subject: string;
67
+ files: readonly string[];
68
+ }
69
+ export interface MaintenanceAnalysisInput {
70
+ /**
71
+ * @implements A-SPEC-478 — the INJECTED semantic runtime for the uncited head rerank. The
72
+ * handler resolves the tier, awaits the query embedding, and injects; the replay harness
73
+ * injects nothing, which is what keeps every pinned benchmark constant untouched. queryVector
74
+ * null (embedding failed) disables the rerank — the hot path never breaks on this layer.
75
+ */
76
+ semantic?: {
77
+ label: string;
78
+ queryVector: number[] | null;
79
+ cachedDocVector: (text: string) => number[] | null;
80
+ };
81
+ request: string;
82
+ scanned: readonly ScannedFile[];
83
+ specs: readonly Spec[];
84
+ graph: RtmGraph;
85
+ testAnchors: Readonly<Record<string, readonly string[]>>;
86
+ history: readonly MaintenanceHistoryRecord[];
87
+ /**
88
+ * @implements A-SPEC-378
89
+ * Per-file multipliers from the temporal graph: how recently each file changed, as of the analysis
90
+ * basis. Absent files score 1 — unknown history is not evidence against a file.
91
+ *
92
+ * The first signal measured to beat plain word matching on requests that cite no spec, which is
93
+ * the condition every incoming bug report is in and where five spec-graph mechanisms had all come
94
+ * back negative. Measured at a 10-commit half-life over 60 point-in-time cases: uncited Top-10
95
+ * recall 0.349 -> 0.434 against a 0.357 baseline, cited 0.770 -> 0.843, second corpus 0.684 ->
96
+ * 0.724. Change FREQUENCY was measured alongside recency and made the uncited case worse, so it is
97
+ * deliberately not here.
98
+ *
99
+ * Supplied by the caller rather than read here, because this function performs no I/O — the same
100
+ * property that lets the replay benchmark run it against a materialised tree.
101
+ */
102
+ changePrior?: Record<string, number>;
103
+ /**
104
+ * @implements A-SPEC-388
105
+ * Per-file multipliers from how much each file's COMMIT PROSE talks like the request, normalised
106
+ * to 0..1. Absent files are unaffected.
107
+ *
108
+ * The path is query -> Commit -> `MODIFIED_IN` -> File: the request's words are matched against
109
+ * what the graph records about a file rather than against the file's own identifiers, which is
110
+ * what lets it reach code named nothing like the report that describes it.
111
+ *
112
+ * Measured on history sampled away from the benchmarker's own recent commits (A-SPEC-387), on both
113
+ * corpora: Top-1 0.244 -> 0.333 and 0.400 -> 0.500, Top-5 recall 0.539 -> 0.624 and 0.376 -> 0.464,
114
+ * Top-10 recall 0.668 -> 0.674 and 0.393 -> 0.601. Precision falls, 0.156 -> 0.117 and 0.244 ->
115
+ * 0.145 — the answer is found more often and the list of ten is less pure, and neither half of
116
+ * that is the whole result.
117
+ */
118
+ commitTextBoost?: Record<string, number>;
119
+ contextBundle?: ContextBundle | null;
120
+ /**
121
+ * @implements A-SPEC-290
122
+ * The stored test-run record, if any. Used ONLY when it was produced at this analysis's HEAD:
123
+ * a stale execution record read as current evidence is exactly the failure this codebase keeps
124
+ * closing.
125
+ */
126
+ runtimeEvidence?: TestEvidence;
127
+ /**
128
+ * @implements A-SPEC-292
129
+ * What graph resolution discarded, produced by the resolution loop itself. Absent means nobody
130
+ * measured it — reported as null, never as zeros, because zeros would claim nothing was discarded.
131
+ */
132
+ resolution?: ResolutionReport;
133
+ /** Optional replay ground truth. Absent means recall/precision are UNKNOWN, reported as null. */
134
+ groundTruth?: MaintenanceGroundTruth;
135
+ /**
136
+ * @implements A-SPEC-269
137
+ * Optional changed-file basis (e.g. a base..head diff). The architecture axis reads
138
+ * `.ax/decisions/*.md` and the C-SPEC folder — documents the CPG scanner never scans — so it
139
+ * cannot be derived from the localized code files. Without this the axis is reported as NOT
140
+ * assessed rather than as empty.
141
+ */
142
+ changedFiles?: readonly string[];
143
+ basis: {
144
+ head: string;
145
+ loadedBuild: string;
146
+ diskBuild: string;
147
+ specFingerprint: string;
148
+ };
149
+ coverage: {
150
+ scannedFiles: number;
151
+ skippedFiles: readonly string[];
152
+ unsupportedLanguages: readonly string[];
153
+ historyStatus?: 'available' | 'unavailable' | 'not-requested';
154
+ };
155
+ }
156
+ export interface MaintenanceAnalysis {
157
+ schemaVersion: string;
158
+ extractorVersion: string;
159
+ classification: 'existing-requirement-change' | 'new-requirement' | 'ambiguous';
160
+ /**
161
+ * @implements A-SPEC-436
162
+ * MEASURED ANTI-PREDICTIVE. On both corpora's 30-case clean slices this field correlates
163
+ * NEGATIVELY with how much of the change the analysis actually found — holmes r = −0.309, the
164
+ * second corpus r = −0.212 — and on both the highest band is the worst one. On the second corpus
165
+ * the two cases scored 0.85 returned nothing correct at all. Do not read it as a quality signal;
166
+ * a caller who weights answers by it weights the wrong ones up. The numbers and their sample
167
+ * sizes are pinned in review/confidence-calibration.ts, and the formula is unchanged because
168
+ * nobody has a hypothesis for why it inverts.
169
+ */
170
+ confidence: number;
171
+ /**
172
+ * @implements A-SPEC-375
173
+ * A-SPECs worth citing, when the request cited nothing. Empty when it already did.
174
+ *
175
+ * Measured over 60 point-in-time cases: a request naming its spec reaches Top-10 recall 0.770
176
+ * against 0.305 for matching its words alone; a request naming nothing reaches 0.349 against
177
+ * 0.357 — level, with five graph mechanisms tried in that condition and every one negative. So
178
+ * this is not a nag, it is the single input that changes what the analysis can do.
179
+ *
180
+ * Few, deliberately: 26.6 A-SPECs match lexically per case on this repository, and offering all of
181
+ * them would be a haystack rather than a suggestion.
182
+ */
183
+ citationCandidates: string[];
184
+ /**
185
+ * @implements A-SPEC-390
186
+ * What kind of call edges this graph actually holds.
187
+ *
188
+ * Measured on this repository: 7,917 `calls` edges, 1,078 binding to exactly one other file and
189
+ * 6,050 naming a target the scan does not contain — and those 6,050 are `join`, `push`, `map`,
190
+ * `filter`, `existsSync`. Builtins, correctly unresolvable, stored under the same relation as
191
+ * internal calls. Reporting "7,917 call edges" without this split describes a graph seven times
192
+ * denser than the one that exists.
193
+ *
194
+ * Reporting only: the impact walk already skips targets it cannot resolve, so no ranking, impact
195
+ * set or candidate order depends on these counts.
196
+ */
197
+ callEdges: {
198
+ total: number;
199
+ /** Binds to exactly one OTHER file in the scan — the edges a walk can actually use. */
200
+ internalResolved: number;
201
+ /** The name is declared in several files, so binding it would be a guess. */
202
+ ambiguous: number;
203
+ /** Declared nowhere in the scan: a library, a builtin, or a method on an unknown type. */
204
+ outOfScan: number;
205
+ /** Lands in the file it came from. */
206
+ withinFile: number;
207
+ /** null when there are no call edges — unmeasured, never a perfect score. */
208
+ internalResolvedShare: number | null;
209
+ };
210
+ candidates: Array<{
211
+ file: string;
212
+ score: number;
213
+ symbols: string[];
214
+ evidence: string[];
215
+ }>;
216
+ /**
217
+ * @implements A-SPEC-494 — the semantic ALTERNATES: top-3 cached-vector cosines among files
218
+ * the emission did NOT reach. Additive evidence (cloud tier only) — S-511 measured the ceiling
219
+ * gain at ×1.25–×2.25 in weak windows with precision rising too; selection stays with the
220
+ * consumer. Absent when the gate is shut or nothing off-emission is covered.
221
+ */
222
+ semanticAlternates?: Array<{
223
+ file: string;
224
+ cos: number;
225
+ }>;
226
+ /**
227
+ * @implements A-SPEC-409
228
+ * Ranks 11-20 of the same fused ordering, in the same shape as `candidates`.
229
+ *
230
+ * Two blind sets agree the harness picks the single best file better than reading-and-grepping
231
+ * does (Top-1 0.375 vs 0.125, then 0.750 vs 0.625) and recovers the whole change worse (Top-10
232
+ * recall 0.404 vs 0.554, then 0.833 vs 0.938). The gap was traced to judgement, not retrieval:
233
+ * reranking the fused top-20 by hand moved Top-1 to 0.500 and Top-5 recall to 0.575 on eight
234
+ * cases whose truth was unread at the time. The reachable recall of the wider list is measured —
235
+ * holmes 0.634 at ten to 0.691 at twenty, jarvis 0.612 to 0.739, and jarvis is flat by thirty.
236
+ *
237
+ * This field buys none of that on its own. It hands the calling agent the material; whether recall
238
+ * actually improves depends on how well that agent reranks, and that is not measured here.
239
+ */
240
+ rerankPool: Array<{
241
+ file: string;
242
+ score: number;
243
+ symbols: string[];
244
+ evidence: string[];
245
+ }>;
246
+ candidateSpecs: string[];
247
+ impacts: {
248
+ /**
249
+ * Every file this analysis names as a modification candidate: the ranked localization hits PLUS
250
+ * the files that own transitively impacted symbols. This is the ONE prediction that gets
251
+ * persisted and scored — the enhanced ablation arm takes credit for caller-reachable files, so
252
+ * the result has to name them rather than leave them visible only as a count.
253
+ */
254
+ files: string[];
255
+ /**
256
+ * @implements A-SPEC-271
257
+ * The same files, GRADED. `primary` is the head of the ranking, `secondary` the rest of the
258
+ * ranked candidates, `reachable` the files reached only by a call edge. This does not narrow
259
+ * the prediction — the union is exactly `files` — it says where the density is. Measured
260
+ * 2026-08-28 over 12 real commits: top-3 precision 0.167 against 0.061 for the flat set.
261
+ */
262
+ tiers: {
263
+ primary: string[];
264
+ secondary: string[];
265
+ };
266
+ /**
267
+ * @implements A-SPEC-274
268
+ * Files reached ONLY through a call edge. These are not modification candidates — measured
269
+ * 2026-08-28 over 12 real commits, this list contributed 59 files and **zero** true positives,
270
+ * about a third of the whole prediction. They are what the change may AFFECT, so they are still
271
+ * reported (and still drive spec ancestry and test selection) but they are no longer folded
272
+ * into `files`. Nothing is lost; the name now matches what the list actually is.
273
+ */
274
+ impactedFiles: string[];
275
+ /**
276
+ * @implements A-SPEC-469
277
+ * The GRADED impact prediction: PPR mass over the same graph, top-K, the changed set excluded.
278
+ * The closure above stays the gate-facing reach set (mass never enters a gate decision); this
279
+ * list is the consumer-facing answer to "what will change WITH this". Adopted on S-484's
280
+ * three-corpus measurement — the closure's own numbers were recall 0.0347 / precision 0.0413
281
+ * (HOLMES), the graded top-10's 0.5713 / 0.1611, and both metrics won on all three corpora,
282
+ * which is the bar that A-SPEC-309/313 set and their expansions failed.
283
+ */
284
+ rankedImpact: Array<{
285
+ file: string;
286
+ score: number;
287
+ semCos?: number;
288
+ }>;
289
+ /**
290
+ * @implements A-SPEC-289
291
+ * Files that IMPORT a predicted file. A coarser dependency than a call, and the only axis that
292
+ * reaches a file depending on another without calling a symbol the resolver could pin down.
293
+ *
294
+ * Reported, never folded into `files`. Measured over 12 real commits: folding it in lifts recall
295
+ * 0.826 -> 0.913 but adds 129 files carrying 2 truth files (precision 0.0155) and roughly halves
296
+ * the prediction's density. Those 2 are reachable by nothing else, so the information is worth
297
+ * reporting — and, by A-SPEC-274's rule, being affected is still not being a candidate.
298
+ */
299
+ importedByFiles: string[];
300
+ /**
301
+ * @implements A-SPEC-290
302
+ * Which impacted contracts were actually OBSERVED executing, and whether that observation can
303
+ * be trusted at all. `fresh` means the record was produced at this very commit; `stale` means a
304
+ * record exists for a different one and is therefore not evidence about this tree; `absent`
305
+ * means none exists. Only `fresh` ever populates `specs`.
306
+ *
307
+ * This NEVER removes a test from the selection. Absence of observation is not absence of
308
+ * relevance — the test may simply not have run — so runtime evidence adds a quality label and
309
+ * takes nothing away.
310
+ */
311
+ runtimeObserved: {
312
+ status: 'fresh' | 'stale' | 'absent';
313
+ specs: string[];
314
+ };
315
+ /**
316
+ * @implements A-SPEC-292
317
+ * Relations the graph declined to link, by reason. `null` when unmeasured.
318
+ */
319
+ unresolvedRelations: ResolutionReport | null;
320
+ direct: string[];
321
+ transitive: string[];
322
+ contract: string[];
323
+ test: string[];
324
+ };
325
+ impactBounds: {
326
+ maxCallDepth: number;
327
+ hubInDegree: number;
328
+ stoppedAt: Array<{
329
+ id: string;
330
+ reason: 'hub' | 'depth';
331
+ inDegree?: number;
332
+ }>;
333
+ seedIsHub?: {
334
+ callers: number;
335
+ threshold: number;
336
+ };
337
+ };
338
+ /**
339
+ * @implements A-SPEC-279
340
+ * The two constraint axes Phase 2 asks for beyond architecture. Both are ALWAYS assessed when
341
+ * their input exists — taint reachability costs ~5ms on an already-built graph, measured — and
342
+ * `assessed: false` means the input was missing, never that the axis came back clean.
343
+ */
344
+ constraints: {
345
+ security: {
346
+ assessed: boolean;
347
+ limits: string[];
348
+ paths: Array<{
349
+ source: string;
350
+ sink: string;
351
+ files: string[];
352
+ }>;
353
+ };
354
+ compatibility: {
355
+ assessed: boolean;
356
+ breakingChanges: Array<{
357
+ spec: string;
358
+ kind: string;
359
+ }>;
360
+ };
361
+ /**
362
+ * @implements A-SPEC-293
363
+ * Architecture decisions that constrain the predicted files or candidate specs — what the
364
+ * change must not break, as opposed to what it touches.
365
+ */
366
+ decisions: string[];
367
+ };
368
+ /**
369
+ * @implements A-SPEC-285
370
+ * For the languages ACTUALLY in this scan, the relation kinds their extractor does not recover.
371
+ * An empty list means every language present gave up everything it could — not that no language
372
+ * was involved.
373
+ */
374
+ languageCoverage: LanguageGap[];
375
+ /**
376
+ * @implements A-SPEC-294
377
+ * Why the candidate requirements exist, read from each REQ's `source` frontmatter. The analysis
378
+ * used to report WHICH specs and never WHY — and the why is the one thing the code cannot be read
379
+ * back from. An `adr` source links to the decision it came from.
380
+ */
381
+ /**
382
+ * @implements A-SPEC-295
383
+ * Candidate requirements whose Success Criteria cannot answer "did we satisfy this?" — the
384
+ * section is missing, or it says only that the implementation was completed and tested.
385
+ */
386
+ unactionableCriteria: Array<{
387
+ req: string;
388
+ quality: 'absent' | 'boilerplate';
389
+ }>;
390
+ intent: Array<{
391
+ req: string;
392
+ kind: string;
393
+ ref: string | null;
394
+ retrieved: string | null;
395
+ note: string | null;
396
+ decision: string | null;
397
+ }>;
398
+ testScope: TestScope;
399
+ relevantTests: string[];
400
+ relevantTestSpecs: string[];
401
+ historicalRationale: MaintenanceHistoryRecord[];
402
+ contextBundle: ContextBundle | null;
403
+ assumptions: string[];
404
+ contradictions: string[];
405
+ unknowns: string[];
406
+ coverage: {
407
+ scannedFiles: number;
408
+ skippedFiles: string[];
409
+ unsupportedLanguages: string[];
410
+ historyStatus?: 'available' | 'unavailable' | 'not-requested';
411
+ };
412
+ graphBasis: MaintenanceAnalysisInput['basis'] & {
413
+ stale: boolean;
414
+ };
415
+ ablation: {
416
+ graphOff: AblationArm;
417
+ current: AblationArm;
418
+ enhanced: AblationArm;
419
+ };
420
+ evidenceDigest: string;
421
+ }
422
+ /**
423
+ * Decode a pathname as git prints it in `--name-only`/`--name-status` output. With the default
424
+ * `core.quotepath=true`, any non-ASCII or control byte makes git wrap the name in double quotes and
425
+ * emit C-style escapes (`"src/\354\227\260.ts"`). Comparing that raw form against the scanner's
426
+ * real relative path never matches, which silently emptied the historical rationale while the
427
+ * provenance still claimed the history was `available`.
428
+ */
429
+ export declare function unquoteGitPath(line: string): string;
430
+ /**
431
+ * Fuse lexical localization, RTM ancestry, call-graph blast radius, test anchors, and history into a
432
+ * single immutable evidence contract. This core intentionally performs no I/O, so handlers can
433
+ * reproduce and compare an analysis from the exact same captured inputs.
434
+ */
435
+ export declare function analyzeMaintenance(input: MaintenanceAnalysisInput): MaintenanceAnalysis;