weavatrix 0.2.13 → 0.2.15

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 (145) hide show
  1. package/README.md +99 -39
  2. package/SECURITY.md +2 -2
  3. package/docs/releases/v0.2.15.md +37 -0
  4. package/package.json +2 -2
  5. package/skill/SKILL.md +57 -38
  6. package/src/analysis/architecture/contract-graph.js +119 -0
  7. package/src/analysis/architecture/contract-schema.js +110 -0
  8. package/src/analysis/architecture/contract-storage.js +35 -0
  9. package/src/analysis/architecture/contract-verification.js +168 -0
  10. package/src/analysis/architecture-contract.js +16 -343
  11. package/src/analysis/change-classification/diff-parser.js +103 -0
  12. package/src/analysis/change-classification/options.js +40 -0
  13. package/src/analysis/change-classification/symbol-classifier.js +177 -0
  14. package/src/analysis/change-classification.js +120 -519
  15. package/src/analysis/dead-code-review/policy.js +23 -0
  16. package/src/analysis/dead-code-review.js +9 -19
  17. package/src/analysis/dep-check.js +14 -71
  18. package/src/analysis/dep-rules.js +10 -303
  19. package/src/analysis/dependency/conventions.js +16 -0
  20. package/src/analysis/dependency/scoped-dependencies.js +45 -0
  21. package/src/analysis/dependency/source-references.js +21 -0
  22. package/src/analysis/duplicates.tokenize.js +12 -2
  23. package/src/analysis/endpoints/common.js +62 -0
  24. package/src/analysis/endpoints/extract.js +107 -0
  25. package/src/analysis/endpoints/inventory.js +84 -0
  26. package/src/analysis/endpoints/mounts.js +129 -0
  27. package/src/analysis/endpoints-java.js +80 -3
  28. package/src/analysis/endpoints.js +3 -448
  29. package/src/analysis/git-history/analytics.js +177 -0
  30. package/src/analysis/git-history/collector.js +109 -0
  31. package/src/analysis/git-history/options.js +68 -0
  32. package/src/analysis/git-history.js +128 -577
  33. package/src/analysis/health-capabilities.js +82 -0
  34. package/src/analysis/http-contracts/analysis.js +169 -0
  35. package/src/analysis/http-contracts/client-call-detection.js +81 -0
  36. package/src/analysis/http-contracts/client-call-parser.js +255 -0
  37. package/src/analysis/http-contracts/graph-context.js +143 -0
  38. package/src/analysis/http-contracts/matching.js +61 -0
  39. package/src/analysis/http-contracts/shared.js +86 -0
  40. package/src/analysis/http-contracts.js +6 -822
  41. package/src/analysis/internal-audit/dependency-health.js +111 -0
  42. package/src/analysis/internal-audit/python-manifests.js +65 -0
  43. package/src/analysis/internal-audit/repo-files.js +55 -0
  44. package/src/analysis/internal-audit/supply-chain.js +132 -0
  45. package/src/analysis/internal-audit.collect.js +5 -106
  46. package/src/analysis/internal-audit.reach.js +20 -0
  47. package/src/analysis/internal-audit.run.js +114 -200
  48. package/src/analysis/jvm-dependency-evidence.js +69 -0
  49. package/src/analysis/source-correctness/retry-patterns.js +93 -0
  50. package/src/analysis/source-correctness.js +225 -0
  51. package/src/analysis/structure/dependency-graph.js +156 -0
  52. package/src/analysis/structure/findings.js +142 -0
  53. package/src/analysis/task-retrieval.js +3 -14
  54. package/src/graph/builder/go-receiver-resolution.js +112 -0
  55. package/src/graph/builder/js/queries.js +48 -0
  56. package/src/graph/builder/lang-go.js +89 -4
  57. package/src/graph/builder/lang-js.js +4 -44
  58. package/src/graph/builder/pass2-resolution.js +68 -0
  59. package/src/graph/freshness-probe.js +2 -1
  60. package/src/graph/incremental-refresh.js +3 -2
  61. package/src/graph/internal-builder.build.js +22 -183
  62. package/src/graph/internal-builder.pass2.js +161 -0
  63. package/src/graph/internal-builder.resolvers.js +2 -105
  64. package/src/graph/resolvers/rust.js +117 -0
  65. package/src/mcp/actions/advisories.mjs +18 -0
  66. package/src/mcp/actions/graph-lifecycle.mjs +148 -0
  67. package/src/mcp/actions/graph-sync.mjs +195 -0
  68. package/src/mcp/actions/hosted-architecture.mjs +57 -0
  69. package/src/mcp/architecture-bootstrap.mjs +168 -0
  70. package/src/mcp/architecture-starter.mjs +234 -0
  71. package/src/mcp/catalog.mjs +20 -6
  72. package/src/mcp/evidence/bun-lock-graph.mjs +209 -0
  73. package/src/mcp/evidence/duplicate-member-order.mjs +8 -0
  74. package/src/mcp/evidence/package-graph-common.mjs +115 -0
  75. package/src/mcp/evidence/package-lock-graph.mjs +92 -0
  76. package/src/mcp/evidence-snapshot.duplicates.mjs +3 -7
  77. package/src/mcp/evidence-snapshot.package-graph.mjs +5 -474
  78. package/src/mcp/git-output.mjs +10 -0
  79. package/src/mcp/graph/context-core.mjs +111 -0
  80. package/src/mcp/graph/context-seeds.mjs +197 -0
  81. package/src/mcp/graph/context-state.mjs +86 -0
  82. package/src/mcp/graph/reverse-reach.mjs +42 -0
  83. package/src/mcp/graph/tools-core.mjs +143 -0
  84. package/src/mcp/graph/tools-query.mjs +223 -0
  85. package/src/mcp/graph-context.mjs +4 -496
  86. package/src/mcp/health/audit-format.mjs +172 -0
  87. package/src/mcp/health/audit.mjs +171 -0
  88. package/src/mcp/health/dead-code.mjs +110 -0
  89. package/src/mcp/health/duplicates.mjs +83 -0
  90. package/src/mcp/health/endpoints.mjs +42 -0
  91. package/src/mcp/health/structure.mjs +219 -0
  92. package/src/mcp/runtime-version.mjs +32 -0
  93. package/src/mcp/server/auto-refresh.mjs +66 -0
  94. package/src/mcp/server/runtime-config.mjs +43 -0
  95. package/src/mcp/server/shutdown.mjs +40 -0
  96. package/src/mcp/sync/evidence-architecture.mjs +54 -0
  97. package/src/mcp/sync/evidence-common.mjs +88 -0
  98. package/src/mcp/sync/evidence-duplicates.mjs +97 -0
  99. package/src/mcp/sync/evidence-health.mjs +56 -0
  100. package/src/mcp/sync/evidence-packages.mjs +95 -0
  101. package/src/mcp/sync/payload-common.mjs +97 -0
  102. package/src/mcp/sync/payload-v2.mjs +53 -0
  103. package/src/mcp/sync/payload-v3.mjs +79 -0
  104. package/src/mcp/sync-evidence.mjs +7 -402
  105. package/src/mcp/sync-payload.mjs +10 -244
  106. package/src/mcp/tools-actions.mjs +18 -414
  107. package/src/mcp/tools-architecture.mjs +18 -70
  108. package/src/mcp/tools-context.mjs +56 -15
  109. package/src/mcp/tools-endpoints.mjs +4 -1
  110. package/src/mcp/tools-graph.mjs +17 -331
  111. package/src/mcp/tools-health.mjs +24 -705
  112. package/src/mcp/tools-impact-change.mjs +2 -38
  113. package/src/mcp/tools-impact.mjs +2 -43
  114. package/src/mcp-server.mjs +35 -146
  115. package/src/path-classification.js +14 -0
  116. package/src/precision/lsp-client/constants.js +12 -0
  117. package/src/precision/lsp-client/environment.js +20 -0
  118. package/src/precision/lsp-client/errors.js +15 -0
  119. package/src/precision/lsp-client/lifecycle.js +127 -0
  120. package/src/precision/lsp-client/message-parser.js +81 -0
  121. package/src/precision/lsp-client/protocol.js +212 -0
  122. package/src/precision/lsp-client/registry.js +58 -0
  123. package/src/precision/lsp-client/repo-uri.js +60 -0
  124. package/src/precision/lsp-client/stdio-client.js +151 -0
  125. package/src/precision/lsp-client.js +10 -682
  126. package/src/precision/lsp-overlay/build.js +238 -0
  127. package/src/precision/lsp-overlay/contract.js +61 -0
  128. package/src/precision/lsp-overlay/reference-results.js +108 -0
  129. package/src/precision/lsp-overlay/semantic-inputs.js +62 -0
  130. package/src/precision/lsp-overlay/source-session.js +134 -0
  131. package/src/precision/lsp-overlay/store.js +150 -0
  132. package/src/precision/lsp-overlay/target-index.js +147 -0
  133. package/src/precision/lsp-overlay/target-query.js +55 -0
  134. package/src/precision/lsp-overlay.js +11 -868
  135. package/src/precision/typescript-lsp-provider.js +12 -682
  136. package/src/precision/typescript-provider/client.js +69 -0
  137. package/src/precision/typescript-provider/discovery.js +124 -0
  138. package/src/precision/typescript-provider/project-host.js +249 -0
  139. package/src/precision/typescript-provider/project-safety.js +161 -0
  140. package/src/precision/typescript-provider/reference-usage.js +53 -0
  141. package/src/security/malware-heuristics.sweep.js +1 -1
  142. package/src/security/registry-sig.classify.js +2 -1
  143. package/src/security/registry-sig.rules.js +3 -3
  144. package/src/version.js +7 -0
  145. package/docs/releases/v0.2.13.md +0 -48
@@ -1,868 +1,11 @@
1
- import { createHash } from "node:crypto";
2
- import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
3
- import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- import { boundedInteger } from "../bounds.js";
6
- import { atomicWriteFileSync } from "../graph/file-lock.js";
7
- import { edgeProvenance } from "../graph/edge-provenance.js";
8
- import { isStructuralRelation } from "../graph/relations.js";
9
- import { createRepoBoundary, isPathInside } from "../repo-path.js";
10
- import {
11
- classifyTypeScriptReferenceUsage,
12
- createTypeScriptLspClient,
13
- typeScriptLspContract,
14
- typeScriptProjectSafety,
15
- } from "./typescript-lsp-provider.js";
16
-
17
- export const PRECISION_OVERLAY_V = 3;
18
- export const PRECISION_FILE = "precision.json";
19
-
20
- const JS_TS_FILE = /\.(?:[cm]?[jt]sx?)$/i;
21
- const endpoint = (value) => String(value && typeof value === "object" ? value.id : value);
22
- const norm = (value) => String(value || "").replace(/\\/g, "/").replace(/^\.\//, "");
23
- const graphMode = (graph) => ["full", "no-tests", "tests-only"].includes(graph?.graphBuildMode)
24
- ? graph.graphBuildMode : "full";
25
- const graphScope = (graph) => String(graph?.graphBuildScope || "");
26
- const precisionMode = (graph) => graph?.graphPrecisionMode === "off" ? "off" : "lsp";
27
- const providerContractFor = (graph) => precisionMode(graph) === "off" ? "off" : typeScriptLspContract();
28
- const graphContractFor = (graph) => ({
29
- extractorSchemaV: Number(graph?.extractorSchemaV) || 0,
30
- ...(graph?.repositoryFreshnessBuilderVersion != null
31
- ? {repositoryFreshnessBuilderVersion: String(graph.repositoryFreshnessBuilderVersion)} : {}),
32
- ...(graph?.graphBuilderVersion != null ? {graphBuilderVersion: String(graph.graphBuilderVersion)} : {}),
33
- ...(graph?.internalBuilderVersion != null ? {internalBuilderVersion: String(graph.internalBuilderVersion)} : {}),
34
- });
35
- const lineNumber = (value) => {
36
- const match = /L(\d+)/.exec(String(value || ""));
37
- return match ? Number(match[1]) : 0;
38
- };
39
-
40
- export function precisionPathForGraph(graphPath) {
41
- return resolve(dirname(graphPath), PRECISION_FILE);
42
- }
43
-
44
- function sameRequest(actual, expected) {
45
- if (!expected) return true;
46
- return Number(actual?.maxSymbols) === Number(expected.maxSymbols)
47
- && Number(actual?.maxReferences) === Number(expected.maxReferences)
48
- && Number(actual?.maxLinks) === Number(expected.maxLinks);
49
- }
50
-
51
- function sameGraphContract(actual, graph) {
52
- const expected = graphContractFor(graph);
53
- if (!actual || typeof actual !== "object") return false;
54
- return JSON.stringify(actual) === JSON.stringify(expected);
55
- }
56
-
57
- export function precisionOverlayMatches(overlay, graph, { request } = {}) {
58
- return Number(overlay?.precisionOverlayV) === PRECISION_OVERLAY_V
59
- && String(overlay?.baseGraphRevision || "") === String(graph?.graphRevision || "")
60
- && String(overlay?.graphBuildMode || "full") === graphMode(graph)
61
- && String(overlay?.graphBuildScope || "") === graphScope(graph)
62
- && String(overlay?.precisionMode || "") === precisionMode(graph)
63
- && String(overlay?.providerContract || "") === providerContractFor(graph)
64
- && sameGraphContract(overlay?.graphContract, graph)
65
- && sameRequest(overlay?.request, request);
66
- }
67
-
68
- export function readPrecisionOverlay(graphPath, graph) {
69
- const path = precisionPathForGraph(graphPath);
70
- if (!existsSync(path)) return null;
71
- try {
72
- const overlay = JSON.parse(readFileSync(path, "utf8"));
73
- return precisionOverlayMatches(overlay, graph) ? overlay : null;
74
- } catch {
75
- return null;
76
- }
77
- }
78
-
79
- export function precisionSummary(overlay) {
80
- if (!overlay) return {
81
- state: "UNAVAILABLE",
82
- provider: null,
83
- verifiedEdges: 0,
84
- candidates: 0,
85
- queried: 0,
86
- reason: "no revision-matched precision overlay",
87
- };
88
- const engine = Array.isArray(overlay.engines) ? overlay.engines[0] : null;
89
- return {
90
- state: String(overlay.state || "UNAVAILABLE"),
91
- provider: engine?.provider || null,
92
- providerVersion: engine?.version || null,
93
- typescriptVersion: engine?.typescriptVersion || null,
94
- verifiedEdges: Number(overlay.coverage?.verifiedEdges) || 0,
95
- candidates: Number(overlay.coverage?.candidates) || 0,
96
- selected: Number(overlay.coverage?.selected) || 0,
97
- queried: Number(overlay.coverage?.queried) || 0,
98
- references: Number(overlay.coverage?.references) || 0,
99
- unclassifiedReferences: Number(overlay.coverage?.unclassifiedReferences) || 0,
100
- referenceEvidence: Array.isArray(overlay.referenceEvidence) ? overlay.referenceEvidence.length : 0,
101
- truncated: overlay.coverage?.truncated === true,
102
- reason: overlay.reason || engine?.reason || null,
103
- noReferenceSymbols: Array.isArray(overlay.noReferenceSymbols) ? overlay.noReferenceSymbols.length : 0,
104
- };
105
- }
106
-
107
- // Precision evidence is revision-bound and lives beside graph.json. The static graph stays pristine
108
- // for Git/history diffs; ordinary graph reads merge only an overlay that matches revision + mode.
109
- export function mergePrecisionOverlay(graph, overlay) {
110
- if (!precisionOverlayMatches(overlay, graph)) return {...graph, precision: precisionSummary(null)};
111
- const nodes = Array.isArray(graph.nodes) ? graph.nodes : [];
112
- const ids = new Set(nodes.map((node) => String(node.id)));
113
- const links = (Array.isArray(graph.links) ? graph.links : []).map((link) => ({...link}));
114
- const exactLinks = Array.isArray(overlay.links) ? overlay.links : [];
115
- for (const exact of exactLinks) {
116
- const source = endpoint(exact.source);
117
- const target = endpoint(exact.target);
118
- if (!ids.has(source) || !ids.has(target) || source === target) continue;
119
- const relation = String(exact.relation || "references");
120
- const exactLine = Number.isInteger(exact.line) ? exact.line : null;
121
- const exactCharacter = Number.isInteger(exact.character) ? exact.character : null;
122
- let matched = false;
123
- for (const link of links) {
124
- // Static topology remains static. Deduplicate only an already-materialized semantic
125
- // occurrence; never mutate an EXTRACTED/RESOLVED/INFERRED edge into EXACT_LSP.
126
- if (edgeProvenance(link) !== "EXACT_LSP") continue;
127
- if (endpoint(link.source) !== source || endpoint(link.target) !== target || String(link.relation || "") !== relation) continue;
128
- if (exactLine != null && (!Number.isInteger(link.line) || link.line !== exactLine)) continue;
129
- // Never bless a line-only static edge with an occurrence-specific semantic result. A single
130
- // source line can contain both type-only and runtime uses of the same symbol.
131
- if (exactCharacter != null && (!Number.isInteger(link.character) || link.character !== exactCharacter)) continue;
132
- link.provenance = "EXACT_LSP";
133
- link.confidence = "EXACT_LSP";
134
- link.precisionProvider = String(exact.provider || "typescript-language-server");
135
- if (exact.typeOnly === true) link.typeOnly = true;
136
- else delete link.typeOnly;
137
- if (exact.compileOnly === true) link.compileOnly = true;
138
- else delete link.compileOnly;
139
- matched = true;
140
- break;
141
- }
142
- if (!matched) {
143
- links.push({
144
- source,
145
- target,
146
- relation: relation || "references",
147
- provenance: "EXACT_LSP",
148
- confidence: "EXACT_LSP",
149
- precisionProvider: String(exact.provider || "typescript-language-server"),
150
- ...(exact.typeOnly === true ? {typeOnly: true} : {}),
151
- ...(exact.compileOnly === true ? {compileOnly: true} : {}),
152
- ...(exactLine != null ? {line: exactLine} : {}),
153
- ...(exactCharacter != null ? {character: exactCharacter} : {}),
154
- ...(Number.isInteger(exact.endLine) ? {endLine: exact.endLine} : {}),
155
- ...(Number.isInteger(exact.endCharacter) ? {endCharacter: exact.endCharacter} : {}),
156
- });
157
- }
158
- }
159
- return {
160
- ...graph,
161
- links,
162
- precisionOverlayV: PRECISION_OVERLAY_V,
163
- precision: precisionSummary(overlay),
164
- precisionNoReferenceSymbols: Array.isArray(overlay.noReferenceSymbols)
165
- ? overlay.noReferenceSymbols.filter((id) => ids.has(String(id))).map(String)
166
- : [],
167
- precisionReferenceEvidence: Array.isArray(overlay.referenceEvidence)
168
- ? overlay.referenceEvidence.filter((evidence) => ids.has(endpoint(evidence.source))
169
- && ids.has(endpoint(evidence.target)))
170
- .map((evidence) => ({
171
- source: endpoint(evidence.source),
172
- target: endpoint(evidence.target),
173
- ...(Number.isInteger(evidence.line) ? {line: evidence.line} : {}),
174
- ...(Number.isInteger(evidence.character) ? {character: evidence.character} : {}),
175
- classification: String(evidence.classification || "unknown"),
176
- provider: String(evidence.provider || "typescript-language-server"),
177
- }))
178
- : [],
179
- };
180
- }
181
-
182
- function repoFileFromLocation(repoRoot, location) {
183
- if (location?.file) {
184
- try {
185
- const root = realpathSync.native(repoRoot);
186
- const path = realpathSync.native(resolve(root, String(location.file)));
187
- if (!isPathInside(root, path)) return null;
188
- const rel = relative(root, path);
189
- return rel && !rel.startsWith(`..${sep}`) && !isAbsolute(rel) ? norm(rel) : null;
190
- } catch {
191
- return null;
192
- }
193
- }
194
- const uri = typeof location === "string" ? location : location?.uri || location?.targetUri;
195
- if (!uri || !String(uri).startsWith("file:")) return null;
196
- try {
197
- const root = realpathSync.native(repoRoot);
198
- const path = realpathSync.native(fileURLToPath(uri));
199
- if (!isPathInside(root, path)) return null;
200
- const rel = relative(root, path);
201
- if (!rel || rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return null;
202
- return norm(rel);
203
- } catch {
204
- return null;
205
- }
206
- }
207
-
208
- function locationStart(location) {
209
- return location?.range?.start || location?.targetSelectionRange?.start || location?.targetRange?.start || null;
210
- }
211
-
212
- function symbolIndex(graph) {
213
- const files = new Set();
214
- const byFile = new Map();
215
- for (const node of graph.nodes || []) {
216
- const id = String(node.id);
217
- const file = norm(node.source_file || (id.includes("#") ? id.slice(0, id.indexOf("#")) : id));
218
- if (!file) continue;
219
- if (!id.includes("#")) files.add(file);
220
- else {
221
- const start = lineNumber(node.source_location) || Number(node.selection_start?.line) + 1 || 0;
222
- const end = lineNumber(node.source_end) || start;
223
- if (!start) continue;
224
- const sourceRange = node.source_range;
225
- const hasRange = Number.isInteger(sourceRange?.start?.line)
226
- && Number.isInteger(sourceRange?.start?.character)
227
- && Number.isInteger(sourceRange?.end?.line)
228
- && Number.isInteger(sourceRange?.end?.character);
229
- const rows = byFile.get(file) || [];
230
- rows.push({
231
- id,
232
- start,
233
- end: Math.max(start, end),
234
- ...(hasRange ? {range: sourceRange} : {}),
235
- });
236
- byFile.set(file, rows);
237
- }
238
- }
239
- for (const rows of byFile.values()) rows.sort((a, b) => {
240
- if (a.range && b.range) {
241
- // The innermost containing range starts latest; equal starts end earliest.
242
- return comparePosition(b.range.start, a.range.start)
243
- || comparePosition(a.range.end, b.range.end)
244
- || a.id.localeCompare(b.id);
245
- }
246
- return (a.end - a.start) - (b.end - b.start) || b.start - a.start || a.id.localeCompare(b.id);
247
- });
248
- return {files, byFile};
249
- }
250
-
251
- const comparePosition = (left, right) => left.line - right.line || left.character - right.character;
252
-
253
- function sourceAt(index, file, position) {
254
- if (!Number.isInteger(position?.line) || !Number.isInteger(position?.character)) {
255
- return index.files.has(file) ? file : null;
256
- }
257
- const line = position.line + 1;
258
- const rows = (index.byFile.get(file) || []).filter((row) => row.range
259
- // LSP ranges are start-inclusive and end-exclusive.
260
- ? comparePosition(row.range.start, position) <= 0 && comparePosition(position, row.range.end) < 0
261
- // Legacy graphs have line-only ranges. Refuse boundary lines rather than assigning an import,
262
- // declaration, or trailing token to a coincidentally adjacent symbol.
263
- : row.start < line && line < row.end);
264
- if (rows.length) {
265
- const first = rows[0];
266
- const span = first.range ? null : first.end - first.start;
267
- const tied = rows.filter((row) => {
268
- if (first.range || row.range) return Boolean(first.range && row.range
269
- && comparePosition(first.range.start, row.range.start) === 0
270
- && comparePosition(first.range.end, row.range.end) === 0);
271
- return row.end - row.start === span;
272
- });
273
- if (tied.length === 1) return tied[0].id;
274
- }
275
- return index.files.has(file) ? file : null;
276
- }
277
-
278
- function eligibleTargets(graph, limit, requestedIds = null) {
279
- const byId = new Map((graph.nodes || []).map((node) => [String(node.id), node]));
280
- if (Array.isArray(requestedIds) && requestedIds.length) {
281
- const ids = [...new Set(requestedIds.map(String))];
282
- const targets = ids
283
- .map((id) => byId.get(id))
284
- .filter((node) => node?.selection_start && JS_TS_FILE.test(String(node.source_file || "")))
285
- .slice(0, limit);
286
- return {targets, total: ids.length, orphanIds: new Set()};
287
- }
288
- const ranked = new Map();
289
- const inbound = new Set();
290
- for (const link of graph.links || []) {
291
- const relation = String(link.relation || "");
292
- if (!isStructuralRelation(relation)) inbound.add(endpoint(link.target));
293
- if (isStructuralRelation(relation) || !["calls", "references", "inherits", "implements"].includes(relation)) continue;
294
- if (edgeProvenance(link) === "EXACT_LSP") continue;
295
- const target = endpoint(link.target);
296
- const node = byId.get(target);
297
- if (!node?.selection_start || !JS_TS_FILE.test(String(node.source_file || ""))) continue;
298
- const score = (relation === "calls" ? 30 : relation === "inherits" || relation === "implements" ? 20 : 10)
299
- + (edgeProvenance(link) === "INFERRED" ? 8 : 0);
300
- ranked.set(target, Math.max(ranked.get(target) || 0, score));
301
- }
302
- // After ambiguous positive edges, spend the remaining bounded budget on genuinely orphaned
303
- // internal callables. This is the only route to an exact no-reference dead-code result; public
304
- // exports remain conservative because consumers may live outside this workspace.
305
- const orphans = new Set();
306
- for (const node of byId.values()) {
307
- const id = String(node.id);
308
- const visibility = String(node.visibility || "").toLowerCase();
309
- if (!node.selection_start || !JS_TS_FILE.test(String(node.source_file || "")) || inbound.has(id)) continue;
310
- if (node.exported === true || visibility === "public" || visibility === "protected") continue;
311
- if (!/\(\)$/.test(String(node.label || "")) && !["function", "method", "constructor"].includes(String(node.symbol_kind || "").toLowerCase())) continue;
312
- if (!ranked.has(id)) {
313
- ranked.set(id, 4);
314
- orphans.add(id);
315
- }
316
- }
317
- const all = [...ranked.entries()]
318
- .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
319
- const positive = all.filter(([id]) => !orphans.has(id));
320
- const orphan = all.filter(([id]) => orphans.has(id));
321
- const reserve = orphan.length ? Math.min(8, Math.ceil(limit / 4), limit) : 0;
322
- const selected = positive.slice(0, Math.max(0, limit - reserve));
323
- selected.push(...orphan.slice(0, reserve));
324
- if (selected.length < limit) {
325
- const selectedIds = new Set(selected.map(([id]) => id));
326
- selected.push(...all.filter(([id]) => !selectedIds.has(id)).slice(0, limit - selected.length));
327
- }
328
- return {
329
- targets: selected.map(([id]) => byId.get(id)),
330
- total: all.length,
331
- orphanIds: new Set(orphan.map(([id]) => id)),
332
- };
333
- }
334
-
335
- function baseOverlay(graph, state, extra = {}) {
336
- return {
337
- precisionOverlayV: PRECISION_OVERLAY_V,
338
- baseGraphRevision: String(graph.graphRevision || ""),
339
- graphBuildMode: graphMode(graph),
340
- graphBuildScope: graphScope(graph),
341
- precisionMode: precisionMode(graph),
342
- providerContract: providerContractFor(graph),
343
- graphContract: graphContractFor(graph),
344
- state,
345
- engines: [],
346
- coverage: {candidates: 0, selected: 0, queried: 0, references: 0, unclassifiedReferences: 0, verifiedEdges: 0, truncated: false},
347
- links: [],
348
- referenceEvidence: [],
349
- noReferenceSymbols: [],
350
- ...extra,
351
- };
352
- }
353
-
354
- export function writePrecisionOverlay(graphPath, overlay) {
355
- atomicWriteFileSync(precisionPathForGraph(graphPath), JSON.stringify(overlay), "utf8");
356
- return overlay;
357
- }
358
-
359
- const SAFE_INVALIDATION_REASON = "repository changed while semantic precision was running";
360
-
361
- // The graph builder calls this after its post-LSP repository snapshot check. Never preserve exact
362
- // edges or no-reference proofs from a run that overlapped a source/configuration change.
363
- export function invalidatePrecisionOverlay(graphPath, graph, reason = SAFE_INVALIDATION_REASON) {
364
- if (!graphPath || !graph) throw new Error("precision invalidation requires graphPath and graph");
365
- const previous = readPrecisionOverlay(graphPath, graph);
366
- const safeReason = typeof reason === "string"
367
- && reason.length > 0 && reason.length <= 160
368
- && /^[A-Za-z0-9 _.,()-]+$/.test(reason)
369
- ? reason
370
- : SAFE_INVALIDATION_REASON;
371
- const engines = (Array.isArray(previous?.engines) && previous.engines.length
372
- ? previous.engines
373
- : [{provider: "typescript-language-server", version: null, language: "typescript/javascript", capability: "textDocument/references"}])
374
- .map((engine) => ({...engine, status: "PARTIAL"}));
375
- return writePrecisionOverlay(graphPath, baseOverlay(graph, "PARTIAL", {
376
- ...(previous?.request ? {request: previous.request} : {}),
377
- reason: safeReason,
378
- engines,
379
- coverage: {candidates: 0, selected: 0, queried: 0, references: 0, unclassifiedReferences: 0, verifiedEdges: 0, truncated: true},
380
- links: [],
381
- referenceEvidence: [],
382
- noReferenceSymbols: [],
383
- }));
384
- }
385
-
386
- class PrecisionBudgetError extends Error {
387
- constructor(message = "semantic precision deadline reached") {
388
- super(message);
389
- this.name = "PrecisionBudgetError";
390
- }
391
- }
392
-
393
- class PrecisionLimitError extends Error {
394
- constructor(message) {
395
- super(message);
396
- this.name = "PrecisionLimitError";
397
- }
398
- }
399
-
400
- class PrecisionStaleGraphError extends Error {
401
- constructor() {
402
- super("repository content did not match the graph snapshot");
403
- this.name = "PrecisionStaleGraphError";
404
- }
405
- }
406
-
407
- class PrecisionStaleSemanticInputsError extends Error {
408
- constructor() {
409
- super("TypeScript project inputs changed while semantic precision was running");
410
- this.name = "PrecisionStaleSemanticInputsError";
411
- }
412
- }
413
-
414
- function graphJavaScriptUniverse(graph) {
415
- const files = [...new Set((graph.nodes || [])
416
- .filter((node) => {
417
- const id = String(node?.id || "");
418
- const file = norm(node?.source_file || id);
419
- return id === file && JS_TS_FILE.test(file);
420
- })
421
- .map((node) => norm(node.source_file || node.id)))].sort();
422
- const hashed = Object.keys(graph.fileHashes || {}).map(norm).filter((file) => JS_TS_FILE.test(file)).sort();
423
- const fileSet = new Set(files);
424
- const complete = graphMode(graph) === "full" && !graphScope(graph)
425
- && files.length === hashed.length && hashed.every((file) => fileSet.has(file));
426
- return {files, complete};
427
- }
428
-
429
- // Synchronous and bounded so callers can reject a persisted COMPLETE overlay before taking an
430
- // auto-refresh/probe shortcut. The digest covers applicable repo-contained config chains,
431
- // configured project paths, and the content of configured files omitted from the graph.
432
- export function precisionSemanticInputs(repoRoot, graph, options = {}) {
433
- const universe = graphJavaScriptUniverse(graph);
434
- if (!universe.files.length) {
435
- return {safe: false, reason: "NO_JAVASCRIPT_TYPESCRIPT_INPUTS", fingerprint: null, universe};
436
- }
437
- return {...typeScriptProjectSafety(repoRoot, universe.files, options), universe};
438
- }
439
-
440
- export function precisionSemanticInputsMatch(overlay, repoRoot, graph) {
441
- const current = precisionSemanticInputs(repoRoot, graph);
442
- return current.safe === true
443
- && typeof current.fingerprint === "string"
444
- && current.fingerprint.length > 0
445
- && String(overlay?.semanticInputFingerprint || "") === current.fingerprint;
446
- }
447
-
448
- function publicSemanticSafetyReason(reason) {
449
- return reason === "CONFIGURED_TSSERVER_PLUGINS"
450
- ? "configured TypeScript language-service plugins are not allowed"
451
- : "TypeScript project configuration could not be verified safely";
452
- }
453
-
454
- export async function buildLspPrecisionOverlay({
455
- repoRoot,
456
- graph,
457
- graphPath,
458
- mode = "lsp",
459
- maxSymbols = Number(process.env.WEAVATRIX_PRECISION_MAX_SYMBOLS) || 32,
460
- maxReferences = Number(process.env.WEAVATRIX_PRECISION_MAX_REFERENCES) || 2_048,
461
- maxLinks = Number(process.env.WEAVATRIX_PRECISION_MAX_LINKS) || 2_048,
462
- timeoutMs = Number(process.env.WEAVATRIX_PRECISION_TIMEOUT_MS) || 45_000,
463
- targetIds,
464
- clientFactory,
465
- } = {}) {
466
- if (!graph || !repoRoot) throw new Error("precision overlay requires repoRoot and graph");
467
- const boundedMax = boundedInteger(maxSymbols, 32, 1, 64);
468
- const boundedReferences = boundedInteger(maxReferences, 2_048, 1, 16_384);
469
- const boundedLinks = boundedInteger(maxLinks, 2_048, 1, 16_384);
470
- const boundedTimeout = boundedInteger(timeoutMs, 45_000, 100, 60_000);
471
- const request = {maxSymbols: boundedMax, maxReferences: boundedReferences, maxLinks: boundedLinks};
472
- if (mode === "off") {
473
- const overlay = baseOverlay(graph, "OFF", {request, reason: "precision disabled by request"});
474
- return graphPath ? writePrecisionOverlay(graphPath, overlay) : overlay;
475
- }
476
- // Configuration discovery is part of the same global budget as the provider. Its bounded
477
- // directory host also receives this absolute deadline, so ignored include trees cannot consume
478
- // unlimited synchronous work before the LSP timer starts.
479
- const deadline = Date.now() + boundedTimeout;
480
- const universe = graphJavaScriptUniverse(graph);
481
- if (!universe.files.length) {
482
- const overlay = baseOverlay(graph, "UNAVAILABLE", {
483
- request,
484
- reason: "semantic precision currently supports JavaScript and TypeScript repositories",
485
- });
486
- return graphPath ? writePrecisionOverlay(graphPath, overlay) : overlay;
487
- }
488
- const semanticInputs = precisionSemanticInputs(repoRoot, graph, {deadline});
489
- if (!semanticInputs.safe) {
490
- const overlay = baseOverlay(graph, "UNAVAILABLE", {
491
- request,
492
- reason: publicSemanticSafetyReason(semanticInputs.reason),
493
- engines: [{
494
- provider: "typescript-language-server",
495
- version: null,
496
- language: "typescript/javascript",
497
- capability: "textDocument/references",
498
- status: "UNAVAILABLE",
499
- }],
500
- });
501
- return graphPath ? writePrecisionOverlay(graphPath, overlay) : overlay;
502
- }
503
- if (graphPath) {
504
- const cached = readPrecisionOverlay(graphPath, graph);
505
- if (cached?.state === "COMPLETE"
506
- && precisionOverlayMatches(cached, graph, {request})
507
- && cached.semanticInputFingerprint === semanticInputs.fingerprint) return cached;
508
- }
509
- const eligible = eligibleTargets(graph, boundedMax, targetIds);
510
- const targets = eligible.targets;
511
- if (!targets.length) {
512
- const overlay = baseOverlay(graph, "COMPLETE", {
513
- request,
514
- semanticInputFingerprint: semanticInputs.fingerprint,
515
- reason: "no eligible JavaScript/TypeScript semantic targets",
516
- });
517
- return graphPath ? writePrecisionOverlay(graphPath, overlay) : overlay;
518
- }
519
-
520
- const makeClient = clientFactory || createTypeScriptLspClient;
521
- let client;
522
- const links = [];
523
- const seen = new Set();
524
- const evidenceSeen = new Set();
525
- const index = symbolIndex(graph);
526
- let queried = 0;
527
- let references = 0;
528
- let unclassifiedReferences = 0;
529
- let errors = 0;
530
- let truncated = eligible.total > boundedMax;
531
- const noReferenceSymbols = [];
532
- const referenceEvidence = [];
533
- const exactLocations = [];
534
- const collectLocations = Array.isArray(targetIds) && targetIds.length > 0;
535
- const opened = new Set();
536
- const openedTexts = new Map();
537
- const classificationTexts = new Map();
538
- let classificationBytes = 0;
539
- let openedBytes = 0;
540
- let fullUniverseOpened = false;
541
- let stop = false;
542
- const nodesById = new Map((graph.nodes || []).map((node) => [String(node.id), node]));
543
- const boundary = createRepoBoundary(repoRoot);
544
- const remaining = () => deadline - Date.now();
545
- const ensureBudget = () => {
546
- if (remaining() <= 0) throw new PrecisionBudgetError();
547
- };
548
- const awaitWithBudget = (operation) => {
549
- ensureBudget();
550
- const wait = remaining();
551
- return new Promise((resolvePromise, rejectPromise) => {
552
- const timer = setTimeout(() => rejectPromise(new PrecisionBudgetError()), wait);
553
- Promise.resolve().then(operation).then(
554
- (value) => { clearTimeout(timer); resolvePromise(value); },
555
- (error) => { clearTimeout(timer); rejectPromise(error); },
556
- );
557
- });
558
- };
559
- const coverage = (verifiedEdges = links.length) => ({
560
- candidates: eligible.total,
561
- selected: targets.length,
562
- queried,
563
- references,
564
- unclassifiedReferences,
565
- verifiedEdges,
566
- truncated,
567
- });
568
- try {
569
- client = await awaitWithBudget(() => makeClient({repoRoot, timeoutMs: Math.max(100, remaining())}));
570
- const verifiedSource = (relPath, maxBytes = 4 * 1024 * 1024) => {
571
- const file = norm(relPath);
572
- const expectedHash = graph.fileHashes?.[file];
573
- if (!file || !/^[a-f0-9]{64}$/i.test(String(expectedHash || ""))) throw new PrecisionStaleGraphError();
574
- const resolvedFile = boundary.resolve(file);
575
- if (!resolvedFile.ok) throw new PrecisionStaleGraphError();
576
- let size;
577
- try { size = statSync(resolvedFile.path).size; } catch { throw new PrecisionStaleGraphError(); }
578
- if (size > maxBytes) throw new PrecisionLimitError("precision source-read budget reached");
579
- let body;
580
- try { body = readFileSync(resolvedFile.path); } catch { throw new PrecisionStaleGraphError(); }
581
- if (body.byteLength > maxBytes) throw new PrecisionLimitError("precision source-read budget reached");
582
- if (createHash("sha256").update(body).digest("hex") !== expectedHash) throw new PrecisionStaleGraphError();
583
- return {file, body, bytes: body.byteLength, text: body.toString("utf8")};
584
- };
585
- const ensureOpen = async (relPath) => {
586
- const file = norm(relPath);
587
- if (!file || opened.has(file)) return;
588
- ensureBudget();
589
- if (opened.size >= 96) throw new PrecisionLimitError("precision open-document limit reached");
590
- // Re-read and re-hash here even when classification cached the file earlier: didOpen must
591
- // always be immediately guarded by the graph snapshot hash.
592
- const {bytes, text} = verifiedSource(file, Math.min(4 * 1024 * 1024, 32 * 1024 * 1024 - openedBytes));
593
- if (bytes > 4 * 1024 * 1024) throw new PrecisionLimitError("precision document exceeds 4 MiB limit");
594
- if (openedBytes + bytes > 32 * 1024 * 1024) throw new PrecisionLimitError("precision source-transfer budget reached");
595
- await awaitWithBudget(() => client.openDocument(file, text));
596
- opened.add(file);
597
- openedTexts.set(file, text);
598
- openedBytes += bytes;
599
- };
600
-
601
- const sourceForClassification = (relPath) => {
602
- const file = norm(relPath);
603
- if (openedTexts.has(file)) return openedTexts.get(file);
604
- if (classificationTexts.has(file)) return classificationTexts.get(file);
605
- if (classificationTexts.size >= 96) return null;
606
- let source;
607
- try {
608
- source = verifiedSource(file, Math.min(4 * 1024 * 1024, 32 * 1024 * 1024 - classificationBytes));
609
- } catch (error) {
610
- if (error instanceof PrecisionLimitError) return null;
611
- throw error;
612
- }
613
- classificationTexts.set(file, source.text);
614
- classificationBytes += source.bytes;
615
- return source.text;
616
- };
617
-
618
- const ensureFullUniverse = async () => {
619
- if (fullUniverseOpened) return true;
620
- if (!universe.complete) return false;
621
- const additional = universe.files.filter((file) => !opened.has(file));
622
- if (opened.size + additional.length > 96) {
623
- truncated = true;
624
- return false;
625
- }
626
- let projectedBytes = openedBytes;
627
- for (const file of additional) {
628
- ensureBudget();
629
- if (!/^[a-f0-9]{64}$/i.test(String(graph.fileHashes?.[file] || ""))) throw new PrecisionStaleGraphError();
630
- const resolvedFile = boundary.resolve(file);
631
- if (!resolvedFile.ok) throw new PrecisionStaleGraphError();
632
- let bytes;
633
- try { bytes = statSync(resolvedFile.path).size; } catch { throw new PrecisionStaleGraphError(); }
634
- if (bytes > 4 * 1024 * 1024 || projectedBytes + bytes > 32 * 1024 * 1024) {
635
- truncated = true;
636
- return false;
637
- }
638
- projectedBytes += bytes;
639
- }
640
- for (const file of additional) await ensureOpen(file);
641
- fullUniverseOpened = universe.files.every((file) => opened.has(file));
642
- return fullUniverseOpened;
643
- };
644
-
645
- const requestReferences = (relPath, position) => awaitWithBudget(
646
- () => client.references(relPath, position, false, Math.max(1, remaining())),
647
- );
648
-
649
- for (const target of targets) {
650
- const relPath = norm(target.source_file);
651
- let locations;
652
- try {
653
- await ensureOpen(relPath);
654
- // In inferred JavaScript projects the language server only knows opened roots. Open the
655
- // bounded static callers of this target so exact validation works without executing repo
656
- // configuration or pretending the whole unconfigured workspace was indexed.
657
- const supportFiles = [];
658
- for (const link of graph.links || []) {
659
- if (endpoint(link.target) !== String(target.id) || isStructuralRelation(link.relation)) continue;
660
- const sourceId = endpoint(link.source);
661
- const sourceFile = norm(nodesById.get(sourceId)?.source_file || (sourceId.includes("#") ? sourceId.slice(0, sourceId.indexOf("#")) : sourceId));
662
- if (sourceFile && JS_TS_FILE.test(sourceFile) && sourceFile !== relPath && !supportFiles.includes(sourceFile)) supportFiles.push(sourceFile);
663
- if (supportFiles.length >= 12) break;
664
- }
665
- for (const file of supportFiles) await ensureOpen(file);
666
- if (universe.complete && universe.files.every((file) => opened.has(file))) fullUniverseOpened = true;
667
- locations = await requestReferences(relPath, target.selection_start);
668
- if (!Array.isArray(locations)) throw new Error("language server returned an invalid references result");
669
- queried++;
670
- if (locations.length === 0) {
671
- ensureBudget();
672
- const configRel = semanticInputs.fileConfigs?.[relPath];
673
- const project = configRel ? semanticInputs.projects?.[configRel] : null;
674
- const configuredFiles = [...new Set((project?.projectFiles || [])
675
- .map(norm).filter((file) => JS_TS_FILE.test(file)))].sort();
676
- const projectFiles = new Set(configuredFiles);
677
- const projectExactlyCoversUniverse = universe.complete
678
- && configuredFiles.length === universe.files.length
679
- && universe.files.every((file) => projectFiles.has(file));
680
- ensureBudget();
681
- if (configRel && projectFiles.has(relPath) && projectExactlyCoversUniverse) {
682
- const alreadyComplete = fullUniverseOpened;
683
- if (alreadyComplete || await ensureFullUniverse()) {
684
- // Opening the complete graph universe can move files from an inferred project into
685
- // the configured one. Re-query the first empty response after that transition.
686
- if (!alreadyComplete) locations = await requestReferences(relPath, target.selection_start);
687
- if (!Array.isArray(locations)) throw new Error("language server returned an invalid references result");
688
- if (locations.length === 0) noReferenceSymbols.push(String(target.id));
689
- }
690
- }
691
- }
692
- } catch (error) {
693
- if (error instanceof PrecisionStaleGraphError) throw error;
694
- if (error instanceof PrecisionBudgetError || error instanceof PrecisionLimitError || remaining() <= 0) {
695
- truncated = true;
696
- stop = true;
697
- break;
698
- }
699
- errors++;
700
- continue;
701
- }
702
- for (const location of locations) {
703
- if (remaining() <= 0) {
704
- truncated = true;
705
- stop = true;
706
- break;
707
- }
708
- if (references >= boundedReferences) {
709
- truncated = true;
710
- stop = true;
711
- break;
712
- }
713
- references++;
714
- const file = repoFileFromLocation(repoRoot, location);
715
- const start = locationStart(location);
716
- if (!file || !start || !Number.isInteger(start.line) || !Number.isInteger(start.character)) continue;
717
- const source = sourceAt(index, file, start);
718
- const targetId = String(target.id);
719
- const exactLine = start.line + 1;
720
- const exactCharacter = start.character;
721
- const relation = "references";
722
- const line = exactLine;
723
- const targetFile = norm(target.source_file);
724
- const moduleDependency = source === file && targetFile
725
- ? (graph.links || []).find((link) => endpoint(link.source) === file
726
- && endpoint(link.target) === targetFile
727
- && ["imports", "re_exports"].includes(String(link.relation || ""))
728
- && Number.isInteger(link.line) && link.line === exactLine)
729
- : null;
730
- const sourceText = sourceForClassification(file);
731
- let usage = sourceText == null
732
- ? "unknown"
733
- : classifyTypeScriptReferenceUsage(file, sourceText, start);
734
- if (usage === "unknown" && moduleDependency?.typeOnly === true) usage = "type";
735
- if (usage === "unknown" && moduleDependency?.compileOnly === true) usage = "compile";
736
- if (collectLocations) exactLocations.push({
737
- file,
738
- source: source || file,
739
- target: targetId,
740
- line: exactLine,
741
- character: exactCharacter,
742
- ...(Number.isInteger(location?.range?.end?.line) ? {endLine: location.range.end.line + 1} : {}),
743
- ...(Number.isInteger(location?.range?.end?.character) ? {endCharacter: location.range.end.character} : {}),
744
- classification: usage,
745
- });
746
- if (!source || source === targetId) {
747
- if (usage === "unknown") unclassifiedReferences++;
748
- continue;
749
- }
750
- if (usage === "unknown") {
751
- const evidenceKey = `${source}\0${targetId}\0${line}\0${exactCharacter}`;
752
- if (!evidenceSeen.has(evidenceKey)) {
753
- if (referenceEvidence.length >= boundedLinks) {
754
- truncated = true;
755
- stop = true;
756
- break;
757
- }
758
- evidenceSeen.add(evidenceKey);
759
- unclassifiedReferences++;
760
- referenceEvidence.push({
761
- source,
762
- target: targetId,
763
- line,
764
- character: exactCharacter,
765
- classification: "unknown",
766
- provider: "typescript-language-server",
767
- });
768
- }
769
- continue;
770
- }
771
- const key = `${source}\0${relation}\0${targetId}\0${line}\0${exactCharacter}`;
772
- if (seen.has(key)) continue;
773
- if (links.length >= boundedLinks) {
774
- truncated = true;
775
- stop = true;
776
- break;
777
- }
778
- seen.add(key);
779
- links.push({
780
- source,
781
- target: targetId,
782
- relation,
783
- line,
784
- character: exactCharacter,
785
- ...(Number.isInteger(location?.range?.end?.line) ? {endLine: location.range.end.line + 1} : {}),
786
- ...(Number.isInteger(location?.range?.end?.character) ? {endCharacter: location.range.end.character} : {}),
787
- provenance: "EXACT_LSP",
788
- provider: "typescript-language-server",
789
- ...(usage === "type" || moduleDependency?.typeOnly === true ? {typeOnly: true} : {}),
790
- ...(usage === "compile" || moduleDependency?.compileOnly === true ? {compileOnly: true} : {}),
791
- });
792
- }
793
- if (remaining() <= 0) {
794
- truncated = true;
795
- stop = true;
796
- }
797
- if (stop) break;
798
- }
799
- ensureBudget();
800
- const semanticInputsAfter = precisionSemanticInputs(repoRoot, graph, {deadline});
801
- ensureBudget();
802
- if (!semanticInputsAfter.safe || semanticInputsAfter.fingerprint !== semanticInputs.fingerprint) {
803
- throw new PrecisionStaleSemanticInputsError();
804
- }
805
- const state = errors || truncated || unclassifiedReferences ? "PARTIAL" : "COMPLETE";
806
- const overlay = baseOverlay(graph, state, {
807
- request,
808
- engines: [{
809
- provider: client.provider || "typescript-language-server",
810
- version: client.version || null,
811
- typescriptVersion: client.typescriptVersion || null,
812
- typescriptSource: client.typescriptSource || null,
813
- language: "typescript/javascript",
814
- capability: "textDocument/references",
815
- status: state,
816
- }],
817
- semanticInputFingerprint: semanticInputs.fingerprint,
818
- coverage: coverage(),
819
- links,
820
- referenceEvidence,
821
- ...(collectLocations ? {locations: exactLocations} : {}),
822
- noReferenceSymbols,
823
- ...(errors ? {reason: `${errors} semantic request(s) failed or were refused`}
824
- : truncated ? {reason: "semantic precision stopped at a configured safety limit"}
825
- : unclassifiedReferences ? {reason: "some exact references could not be classified as runtime or type-only"} : {}),
826
- });
827
- return graphPath ? writePrecisionOverlay(graphPath, overlay) : overlay;
828
- } catch (error) {
829
- const stale = error instanceof PrecisionStaleGraphError;
830
- const semanticInputsChanged = error instanceof PrecisionStaleSemanticInputsError;
831
- const deadlineReached = error instanceof PrecisionBudgetError || remaining() <= 0;
832
- const state = stale || semanticInputsChanged || deadlineReached ? "PARTIAL" : "UNAVAILABLE";
833
- const overlay = baseOverlay(graph, state, {
834
- request,
835
- // Provider stderr and discovery errors can contain host paths. Persist a bounded status, never
836
- // raw diagnostics, command lines, environment values, or absolute install/repository paths.
837
- reason: stale
838
- ? "repository content no longer matched the graph snapshot"
839
- : semanticInputsChanged ? "TypeScript project inputs changed while semantic precision was running"
840
- : deadlineReached ? "semantic precision stopped at its global deadline"
841
- : error?.name === "LspTimeoutError" ? "bundled TypeScript language server timed out"
842
- : "bundled TypeScript language server was unavailable",
843
- engines: [{provider: "typescript-language-server", version: null, language: "typescript/javascript", capability: "textDocument/references", status: state}],
844
- coverage: {
845
- candidates: eligible.total,
846
- selected: targets.length,
847
- queried: stale || semanticInputsChanged ? 0 : queried,
848
- references: stale || semanticInputsChanged ? 0 : references,
849
- unclassifiedReferences: stale || semanticInputsChanged ? 0 : unclassifiedReferences,
850
- verifiedEdges: 0,
851
- truncated: truncated || stale || semanticInputsChanged || deadlineReached,
852
- },
853
- links: [],
854
- noReferenceSymbols: [],
855
- });
856
- return graphPath ? writePrecisionOverlay(graphPath, overlay) : overlay;
857
- } finally {
858
- if (client) {
859
- const closeBudget = Math.min(2_000, Math.max(0, remaining()));
860
- if (closeBudget > 0 && client.close) {
861
- try { await awaitWithBudget(() => client.close(closeBudget)); }
862
- catch { client.kill?.(); }
863
- } else {
864
- client.kill?.();
865
- }
866
- }
867
- }
868
- }
1
+ export {PRECISION_FILE, PRECISION_OVERLAY_V, precisionOverlayMatches} from './lsp-overlay/contract.js'
2
+ export {
3
+ invalidatePrecisionOverlay,
4
+ mergePrecisionOverlay,
5
+ precisionPathForGraph,
6
+ precisionSummary,
7
+ readPrecisionOverlay,
8
+ writePrecisionOverlay,
9
+ } from './lsp-overlay/store.js'
10
+ export {precisionSemanticInputs, precisionSemanticInputsMatch} from './lsp-overlay/semantic-inputs.js'
11
+ export {buildLspPrecisionOverlay} from './lsp-overlay/build.js'