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
@@ -0,0 +1,150 @@
1
+ import {existsSync, readFileSync} from 'node:fs'
2
+ import {dirname, resolve} from 'node:path'
3
+ import {atomicWriteFileSync} from '../../graph/file-lock.js'
4
+ import {edgeProvenance} from '../../graph/edge-provenance.js'
5
+ import {
6
+ PRECISION_FILE,
7
+ PRECISION_OVERLAY_V,
8
+ baseOverlay,
9
+ endpoint,
10
+ precisionOverlayMatches,
11
+ } from './contract.js'
12
+
13
+ export function precisionPathForGraph(graphPath) {
14
+ return resolve(dirname(graphPath), PRECISION_FILE)
15
+ }
16
+
17
+ export function readPrecisionOverlay(graphPath, graph) {
18
+ const path = precisionPathForGraph(graphPath)
19
+ if (!existsSync(path)) return null
20
+ try {
21
+ const overlay = JSON.parse(readFileSync(path, 'utf8'))
22
+ return precisionOverlayMatches(overlay, graph) ? overlay : null
23
+ } catch { return null }
24
+ }
25
+
26
+ export function precisionSummary(overlay) {
27
+ if (!overlay) return {
28
+ state: 'UNAVAILABLE',
29
+ provider: null,
30
+ verifiedEdges: 0,
31
+ candidates: 0,
32
+ queried: 0,
33
+ reason: 'no revision-matched precision overlay',
34
+ }
35
+ const engine = Array.isArray(overlay.engines) ? overlay.engines[0] : null
36
+ return {
37
+ state: String(overlay.state || 'UNAVAILABLE'),
38
+ provider: engine?.provider || null,
39
+ providerVersion: engine?.version || null,
40
+ typescriptVersion: engine?.typescriptVersion || null,
41
+ verifiedEdges: Number(overlay.coverage?.verifiedEdges) || 0,
42
+ candidates: Number(overlay.coverage?.candidates) || 0,
43
+ selected: Number(overlay.coverage?.selected) || 0,
44
+ queried: Number(overlay.coverage?.queried) || 0,
45
+ references: Number(overlay.coverage?.references) || 0,
46
+ unclassifiedReferences: Number(overlay.coverage?.unclassifiedReferences) || 0,
47
+ referenceEvidence: Array.isArray(overlay.referenceEvidence) ? overlay.referenceEvidence.length : 0,
48
+ truncated: overlay.coverage?.truncated === true,
49
+ reason: overlay.reason || engine?.reason || null,
50
+ noReferenceSymbols: Array.isArray(overlay.noReferenceSymbols) ? overlay.noReferenceSymbols.length : 0,
51
+ }
52
+ }
53
+
54
+ export function mergePrecisionOverlay(graph, overlay) {
55
+ if (!precisionOverlayMatches(overlay, graph)) return {...graph, precision: precisionSummary(null)}
56
+ const nodes = Array.isArray(graph.nodes) ? graph.nodes : []
57
+ const ids = new Set(nodes.map((node) => String(node.id)))
58
+ const links = (Array.isArray(graph.links) ? graph.links : []).map((link) => ({...link}))
59
+ for (const exact of Array.isArray(overlay.links) ? overlay.links : []) {
60
+ const source = endpoint(exact.source)
61
+ const target = endpoint(exact.target)
62
+ if (!ids.has(source) || !ids.has(target) || source === target) continue
63
+ const relation = String(exact.relation || 'references')
64
+ const exactLine = Number.isInteger(exact.line) ? exact.line : null
65
+ const exactCharacter = Number.isInteger(exact.character) ? exact.character : null
66
+ let matched = false
67
+ for (const link of links) {
68
+ if (edgeProvenance(link) !== 'EXACT_LSP') continue
69
+ if (endpoint(link.source) !== source || endpoint(link.target) !== target
70
+ || String(link.relation || '') !== relation) continue
71
+ if (exactLine != null && (!Number.isInteger(link.line) || link.line !== exactLine)) continue
72
+ if (exactCharacter != null
73
+ && (!Number.isInteger(link.character) || link.character !== exactCharacter)) continue
74
+ link.provenance = 'EXACT_LSP'
75
+ link.confidence = 'EXACT_LSP'
76
+ link.precisionProvider = String(exact.provider || 'typescript-language-server')
77
+ if (exact.typeOnly === true) link.typeOnly = true
78
+ else delete link.typeOnly
79
+ if (exact.compileOnly === true) link.compileOnly = true
80
+ else delete link.compileOnly
81
+ matched = true
82
+ break
83
+ }
84
+ if (!matched) links.push({
85
+ source,
86
+ target,
87
+ relation: relation || 'references',
88
+ provenance: 'EXACT_LSP',
89
+ confidence: 'EXACT_LSP',
90
+ precisionProvider: String(exact.provider || 'typescript-language-server'),
91
+ ...(exact.typeOnly === true ? {typeOnly: true} : {}),
92
+ ...(exact.compileOnly === true ? {compileOnly: true} : {}),
93
+ ...(exactLine != null ? {line: exactLine} : {}),
94
+ ...(exactCharacter != null ? {character: exactCharacter} : {}),
95
+ ...(Number.isInteger(exact.endLine) ? {endLine: exact.endLine} : {}),
96
+ ...(Number.isInteger(exact.endCharacter) ? {endCharacter: exact.endCharacter} : {}),
97
+ })
98
+ }
99
+ return {
100
+ ...graph,
101
+ links,
102
+ precisionOverlayV: PRECISION_OVERLAY_V,
103
+ precision: precisionSummary(overlay),
104
+ precisionNoReferenceSymbols: Array.isArray(overlay.noReferenceSymbols)
105
+ ? overlay.noReferenceSymbols.filter((id) => ids.has(String(id))).map(String) : [],
106
+ precisionReferenceEvidence: Array.isArray(overlay.referenceEvidence)
107
+ ? overlay.referenceEvidence.filter((evidence) => ids.has(endpoint(evidence.source))
108
+ && ids.has(endpoint(evidence.target))).map((evidence) => ({
109
+ source: endpoint(evidence.source),
110
+ target: endpoint(evidence.target),
111
+ ...(Number.isInteger(evidence.line) ? {line: evidence.line} : {}),
112
+ ...(Number.isInteger(evidence.character) ? {character: evidence.character} : {}),
113
+ classification: String(evidence.classification || 'unknown'),
114
+ provider: String(evidence.provider || 'typescript-language-server'),
115
+ })) : [],
116
+ }
117
+ }
118
+
119
+ export function writePrecisionOverlay(graphPath, overlay) {
120
+ atomicWriteFileSync(precisionPathForGraph(graphPath), JSON.stringify(overlay), 'utf8')
121
+ return overlay
122
+ }
123
+
124
+ const SAFE_INVALIDATION_REASON = 'repository changed while semantic precision was running'
125
+
126
+ export function invalidatePrecisionOverlay(graphPath, graph, reason = SAFE_INVALIDATION_REASON) {
127
+ if (!graphPath || !graph) throw new Error('precision invalidation requires graphPath and graph')
128
+ const previous = readPrecisionOverlay(graphPath, graph)
129
+ const safeReason = typeof reason === 'string'
130
+ && reason.length > 0 && reason.length <= 160
131
+ && /^[A-Za-z0-9 _.,()-]+$/.test(reason) ? reason : SAFE_INVALIDATION_REASON
132
+ const engines = (Array.isArray(previous?.engines) && previous.engines.length
133
+ ? previous.engines
134
+ : [{
135
+ provider: 'typescript-language-server', version: null,
136
+ language: 'typescript/javascript', capability: 'textDocument/references',
137
+ }]).map((engine) => ({...engine, status: 'PARTIAL'}))
138
+ return writePrecisionOverlay(graphPath, baseOverlay(graph, 'PARTIAL', {
139
+ ...(previous?.request ? {request: previous.request} : {}),
140
+ reason: safeReason,
141
+ engines,
142
+ coverage: {
143
+ candidates: 0, selected: 0, queried: 0, references: 0,
144
+ unclassifiedReferences: 0, verifiedEdges: 0, truncated: true,
145
+ },
146
+ links: [],
147
+ referenceEvidence: [],
148
+ noReferenceSymbols: [],
149
+ }))
150
+ }
@@ -0,0 +1,147 @@
1
+ import {realpathSync} from 'node:fs'
2
+ import {isAbsolute, relative, resolve, sep} from 'node:path'
3
+ import {fileURLToPath} from 'node:url'
4
+ import {edgeProvenance} from '../../graph/edge-provenance.js'
5
+ import {isStructuralRelation} from '../../graph/relations.js'
6
+ import {isPathInside} from '../../repo-path.js'
7
+ import {JS_TS_FILE, endpoint, norm} from './contract.js'
8
+
9
+ const lineNumber = (value) => {
10
+ const match = /L(\d+)/.exec(String(value || ''))
11
+ return match ? Number(match[1]) : 0
12
+ }
13
+
14
+ export function repoFileFromLocation(repoRoot, location) {
15
+ if (location?.file) {
16
+ try {
17
+ const root = realpathSync.native(repoRoot)
18
+ const path = realpathSync.native(resolve(root, String(location.file)))
19
+ if (!isPathInside(root, path)) return null
20
+ const rel = relative(root, path)
21
+ return rel && !rel.startsWith(`..${sep}`) && !isAbsolute(rel) ? norm(rel) : null
22
+ } catch { return null }
23
+ }
24
+ const uri = typeof location === 'string' ? location : location?.uri || location?.targetUri
25
+ if (!uri || !String(uri).startsWith('file:')) return null
26
+ try {
27
+ const root = realpathSync.native(repoRoot)
28
+ const path = realpathSync.native(fileURLToPath(uri))
29
+ if (!isPathInside(root, path)) return null
30
+ const rel = relative(root, path)
31
+ if (!rel || rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return null
32
+ return norm(rel)
33
+ } catch { return null }
34
+ }
35
+
36
+ export function locationStart(location) {
37
+ return location?.range?.start || location?.targetSelectionRange?.start
38
+ || location?.targetRange?.start || null
39
+ }
40
+
41
+ const comparePosition = (left, right) => left.line - right.line || left.character - right.character
42
+
43
+ export function symbolIndex(graph) {
44
+ const files = new Set()
45
+ const byFile = new Map()
46
+ for (const node of graph.nodes || []) {
47
+ const id = String(node.id)
48
+ const file = norm(node.source_file || (id.includes('#') ? id.slice(0, id.indexOf('#')) : id))
49
+ if (!file) continue
50
+ if (!id.includes('#')) files.add(file)
51
+ else {
52
+ const start = lineNumber(node.source_location) || Number(node.selection_start?.line) + 1 || 0
53
+ const end = lineNumber(node.source_end) || start
54
+ if (!start) continue
55
+ const range = node.source_range
56
+ const hasRange = Number.isInteger(range?.start?.line)
57
+ && Number.isInteger(range?.start?.character)
58
+ && Number.isInteger(range?.end?.line)
59
+ && Number.isInteger(range?.end?.character)
60
+ const rows = byFile.get(file) || []
61
+ rows.push({id, start, end: Math.max(start, end), ...(hasRange ? {range} : {})})
62
+ byFile.set(file, rows)
63
+ }
64
+ }
65
+ for (const rows of byFile.values()) rows.sort((a, b) => {
66
+ if (a.range && b.range) {
67
+ return comparePosition(b.range.start, a.range.start)
68
+ || comparePosition(a.range.end, b.range.end)
69
+ || a.id.localeCompare(b.id)
70
+ }
71
+ return (a.end - a.start) - (b.end - b.start) || b.start - a.start || a.id.localeCompare(b.id)
72
+ })
73
+ return {files, byFile}
74
+ }
75
+
76
+ export function sourceAt(index, file, position) {
77
+ if (!Number.isInteger(position?.line) || !Number.isInteger(position?.character)) {
78
+ return index.files.has(file) ? file : null
79
+ }
80
+ const line = position.line + 1
81
+ const rows = (index.byFile.get(file) || []).filter((row) => row.range
82
+ ? comparePosition(row.range.start, position) <= 0 && comparePosition(position, row.range.end) < 0
83
+ : row.start < line && line < row.end)
84
+ if (rows.length) {
85
+ const first = rows[0]
86
+ const span = first.range ? null : first.end - first.start
87
+ const tied = rows.filter((row) => {
88
+ if (first.range || row.range) return Boolean(first.range && row.range
89
+ && comparePosition(first.range.start, row.range.start) === 0
90
+ && comparePosition(first.range.end, row.range.end) === 0)
91
+ return row.end - row.start === span
92
+ })
93
+ if (tied.length === 1) return tied[0].id
94
+ }
95
+ return index.files.has(file) ? file : null
96
+ }
97
+
98
+ export function eligibleTargets(graph, limit, requestedIds = null) {
99
+ const byId = new Map((graph.nodes || []).map((node) => [String(node.id), node]))
100
+ if (Array.isArray(requestedIds) && requestedIds.length) {
101
+ const ids = [...new Set(requestedIds.map(String))]
102
+ const targets = ids.map((id) => byId.get(id))
103
+ .filter((node) => node?.selection_start && JS_TS_FILE.test(String(node.source_file || '')))
104
+ .slice(0, limit)
105
+ return {targets, total: ids.length, orphanIds: new Set()}
106
+ }
107
+ const ranked = new Map()
108
+ const inbound = new Set()
109
+ for (const link of graph.links || []) {
110
+ const relation = String(link.relation || '')
111
+ if (!isStructuralRelation(relation)) inbound.add(endpoint(link.target))
112
+ if (isStructuralRelation(relation)
113
+ || !['calls', 'references', 'inherits', 'implements'].includes(relation)) continue
114
+ if (edgeProvenance(link) === 'EXACT_LSP') continue
115
+ const target = endpoint(link.target)
116
+ const node = byId.get(target)
117
+ if (!node?.selection_start || !JS_TS_FILE.test(String(node.source_file || ''))) continue
118
+ const score = (relation === 'calls' ? 30 : relation === 'inherits' || relation === 'implements' ? 20 : 10)
119
+ + (edgeProvenance(link) === 'INFERRED' ? 8 : 0)
120
+ ranked.set(target, Math.max(ranked.get(target) || 0, score))
121
+ }
122
+ const orphans = new Set()
123
+ for (const node of byId.values()) {
124
+ const id = String(node.id)
125
+ const visibility = String(node.visibility || '').toLowerCase()
126
+ if (!node.selection_start || !JS_TS_FILE.test(String(node.source_file || '')) || inbound.has(id)) continue
127
+ if (node.exported === true || visibility === 'public' || visibility === 'protected') continue
128
+ if (!/\(\)$/.test(String(node.label || ''))
129
+ && !['function', 'method', 'constructor'].includes(String(node.symbol_kind || '').toLowerCase())) continue
130
+ if (!ranked.has(id)) { ranked.set(id, 4); orphans.add(id) }
131
+ }
132
+ const all = [...ranked.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
133
+ const positive = all.filter(([id]) => !orphans.has(id))
134
+ const orphan = all.filter(([id]) => orphans.has(id))
135
+ const reserve = orphan.length ? Math.min(8, Math.ceil(limit / 4), limit) : 0
136
+ const selected = positive.slice(0, Math.max(0, limit - reserve))
137
+ selected.push(...orphan.slice(0, reserve))
138
+ if (selected.length < limit) {
139
+ const selectedIds = new Set(selected.map(([id]) => id))
140
+ selected.push(...all.filter(([id]) => !selectedIds.has(id)).slice(0, limit - selected.length))
141
+ }
142
+ return {
143
+ targets: selected.map(([id]) => byId.get(id)),
144
+ total: all.length,
145
+ orphanIds: new Set(orphan.map(([id]) => id)),
146
+ }
147
+ }
@@ -0,0 +1,55 @@
1
+ import {isStructuralRelation} from '../../graph/relations.js'
2
+ import {JS_TS_FILE, endpoint, norm} from './contract.js'
3
+ import {
4
+ ensureBudget,
5
+ ensureFullUniverse,
6
+ ensureOpen,
7
+ requestReferences,
8
+ } from './source-session.js'
9
+
10
+ function supportFilesFor(session, target, relPath) {
11
+ const supportFiles = []
12
+ for (const link of session.graph.links || []) {
13
+ if (endpoint(link.target) !== String(target.id) || isStructuralRelation(link.relation)) continue
14
+ const sourceId = endpoint(link.source)
15
+ const sourceFile = norm(session.nodesById.get(sourceId)?.source_file
16
+ || (sourceId.includes('#') ? sourceId.slice(0, sourceId.indexOf('#')) : sourceId))
17
+ if (sourceFile && JS_TS_FILE.test(sourceFile)
18
+ && sourceFile !== relPath && !supportFiles.includes(sourceFile)) supportFiles.push(sourceFile)
19
+ if (supportFiles.length >= 12) break
20
+ }
21
+ return supportFiles
22
+ }
23
+
24
+ export async function queryPrecisionTarget(session, target) {
25
+ const relPath = norm(target.source_file)
26
+ await ensureOpen(session, relPath)
27
+ for (const file of supportFilesFor(session, target, relPath)) await ensureOpen(session, file)
28
+ if (session.universe.complete
29
+ && session.universe.files.every((file) => session.opened.has(file))) {
30
+ session.fullUniverseOpened = true
31
+ }
32
+ let locations = await requestReferences(session, relPath, target.selection_start)
33
+ if (!Array.isArray(locations)) throw new Error('language server returned an invalid references result')
34
+ session.queried++
35
+ if (locations.length !== 0) return locations
36
+ ensureBudget(session)
37
+ const configRel = session.semanticInputs.fileConfigs?.[relPath]
38
+ const project = configRel ? session.semanticInputs.projects?.[configRel] : null
39
+ const configuredFiles = [...new Set((project?.projectFiles || [])
40
+ .map(norm).filter((file) => JS_TS_FILE.test(file)))].sort()
41
+ const projectFiles = new Set(configuredFiles)
42
+ const exactlyCoversUniverse = session.universe.complete
43
+ && configuredFiles.length === session.universe.files.length
44
+ && session.universe.files.every((file) => projectFiles.has(file))
45
+ ensureBudget(session)
46
+ if (configRel && projectFiles.has(relPath) && exactlyCoversUniverse) {
47
+ const alreadyComplete = session.fullUniverseOpened
48
+ if (alreadyComplete || await ensureFullUniverse(session)) {
49
+ if (!alreadyComplete) locations = await requestReferences(session, relPath, target.selection_start)
50
+ if (!Array.isArray(locations)) throw new Error('language server returned an invalid references result')
51
+ if (locations.length === 0) session.noReferenceSymbols.push(String(target.id))
52
+ }
53
+ }
54
+ return locations
55
+ }