weavatrix 0.2.12 → 0.2.14

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 (132) hide show
  1. package/README.md +90 -21
  2. package/SECURITY.md +2 -2
  3. package/docs/releases/v0.2.14.md +93 -0
  4. package/package.json +2 -2
  5. package/skill/SKILL.md +108 -43
  6. package/src/analysis/allowed-test-runner.js +20 -9
  7. package/src/analysis/architecture/contract-graph.js +119 -0
  8. package/src/analysis/architecture/contract-schema.js +110 -0
  9. package/src/analysis/architecture/contract-storage.js +35 -0
  10. package/src/analysis/architecture/contract-verification.js +168 -0
  11. package/src/analysis/architecture-contract.js +16 -343
  12. package/src/analysis/change-classification/diff-parser.js +103 -0
  13. package/src/analysis/change-classification/options.js +40 -0
  14. package/src/analysis/change-classification/symbol-classifier.js +177 -0
  15. package/src/analysis/change-classification.js +120 -519
  16. package/src/analysis/dead-code-review/policy.js +23 -0
  17. package/src/analysis/dead-code-review.js +9 -19
  18. package/src/analysis/dep-check.js +10 -57
  19. package/src/analysis/dep-rules.js +10 -303
  20. package/src/analysis/dependency/scoped-dependencies.js +40 -0
  21. package/src/analysis/endpoints/common.js +62 -0
  22. package/src/analysis/endpoints/extract.js +107 -0
  23. package/src/analysis/endpoints/inventory.js +84 -0
  24. package/src/analysis/endpoints/mounts.js +129 -0
  25. package/src/analysis/endpoints-java.js +80 -3
  26. package/src/analysis/endpoints.js +3 -448
  27. package/src/analysis/git-history/analytics.js +177 -0
  28. package/src/analysis/git-history/collector.js +109 -0
  29. package/src/analysis/git-history/options.js +68 -0
  30. package/src/analysis/git-history.js +128 -577
  31. package/src/analysis/health-capabilities.js +82 -0
  32. package/src/analysis/http-contracts/analysis.js +169 -0
  33. package/src/analysis/http-contracts/client-call-detection.js +81 -0
  34. package/src/analysis/http-contracts/client-call-parser.js +255 -0
  35. package/src/analysis/http-contracts/graph-context.js +143 -0
  36. package/src/analysis/http-contracts/matching.js +61 -0
  37. package/src/analysis/http-contracts/shared.js +86 -0
  38. package/src/analysis/http-contracts.js +6 -822
  39. package/src/analysis/internal-audit/dependency-health.js +111 -0
  40. package/src/analysis/internal-audit/python-manifests.js +65 -0
  41. package/src/analysis/internal-audit/repo-files.js +55 -0
  42. package/src/analysis/internal-audit/supply-chain.js +132 -0
  43. package/src/analysis/internal-audit.collect.js +5 -106
  44. package/src/analysis/internal-audit.run.js +113 -200
  45. package/src/analysis/jvm-dependency-evidence.js +69 -0
  46. package/src/analysis/source-correctness/retry-patterns.js +93 -0
  47. package/src/analysis/source-correctness.js +225 -0
  48. package/src/analysis/structure/dependency-graph.js +156 -0
  49. package/src/analysis/structure/findings.js +142 -0
  50. package/src/graph/builder/go-receiver-resolution.js +112 -0
  51. package/src/graph/builder/js/queries.js +48 -0
  52. package/src/graph/builder/lang-go.js +89 -4
  53. package/src/graph/builder/lang-js.js +4 -44
  54. package/src/graph/builder/pass2-resolution.js +68 -0
  55. package/src/graph/freshness-probe.js +2 -1
  56. package/src/graph/incremental-refresh.js +3 -2
  57. package/src/graph/internal-builder.build.js +22 -183
  58. package/src/graph/internal-builder.pass2.js +161 -0
  59. package/src/graph/internal-builder.resolvers.js +2 -105
  60. package/src/graph/resolvers/rust.js +117 -0
  61. package/src/mcp/actions/advisories.mjs +18 -0
  62. package/src/mcp/actions/graph-lifecycle.mjs +148 -0
  63. package/src/mcp/actions/graph-sync.mjs +195 -0
  64. package/src/mcp/actions/hosted-architecture.mjs +57 -0
  65. package/src/mcp/architecture-bootstrap.mjs +168 -0
  66. package/src/mcp/architecture-starter.mjs +234 -0
  67. package/src/mcp/catalog.mjs +22 -6
  68. package/src/mcp/evidence/bun-lock-graph.mjs +209 -0
  69. package/src/mcp/evidence/package-graph-common.mjs +115 -0
  70. package/src/mcp/evidence/package-lock-graph.mjs +92 -0
  71. package/src/mcp/evidence-snapshot.package-graph.mjs +5 -474
  72. package/src/mcp/graph/context-core.mjs +111 -0
  73. package/src/mcp/graph/context-seeds.mjs +208 -0
  74. package/src/mcp/graph/context-state.mjs +86 -0
  75. package/src/mcp/graph/tools-core.mjs +143 -0
  76. package/src/mcp/graph/tools-query.mjs +223 -0
  77. package/src/mcp/graph-context.mjs +4 -496
  78. package/src/mcp/health/audit-format.mjs +172 -0
  79. package/src/mcp/health/audit.mjs +171 -0
  80. package/src/mcp/health/dead-code.mjs +110 -0
  81. package/src/mcp/health/duplicates.mjs +83 -0
  82. package/src/mcp/health/endpoints.mjs +42 -0
  83. package/src/mcp/health/structure.mjs +219 -0
  84. package/src/mcp/runtime-version.mjs +32 -0
  85. package/src/mcp/server/auto-refresh.mjs +66 -0
  86. package/src/mcp/server/runtime-config.mjs +43 -0
  87. package/src/mcp/server/shutdown.mjs +40 -0
  88. package/src/mcp/sync/evidence-architecture.mjs +54 -0
  89. package/src/mcp/sync/evidence-common.mjs +88 -0
  90. package/src/mcp/sync/evidence-duplicates.mjs +100 -0
  91. package/src/mcp/sync/evidence-health.mjs +56 -0
  92. package/src/mcp/sync/evidence-packages.mjs +95 -0
  93. package/src/mcp/sync/payload-common.mjs +97 -0
  94. package/src/mcp/sync/payload-v2.mjs +53 -0
  95. package/src/mcp/sync/payload-v3.mjs +79 -0
  96. package/src/mcp/sync-evidence.mjs +7 -402
  97. package/src/mcp/sync-payload.mjs +10 -244
  98. package/src/mcp/tools-actions.mjs +18 -414
  99. package/src/mcp/tools-architecture.mjs +18 -70
  100. package/src/mcp/tools-context.mjs +56 -15
  101. package/src/mcp/tools-endpoints.mjs +4 -1
  102. package/src/mcp/tools-graph.mjs +17 -330
  103. package/src/mcp/tools-health.mjs +24 -705
  104. package/src/mcp/tools-verified-change.mjs +12 -4
  105. package/src/mcp-server.mjs +49 -146
  106. package/src/precision/lsp-client/constants.js +12 -0
  107. package/src/precision/lsp-client/environment.js +20 -0
  108. package/src/precision/lsp-client/errors.js +15 -0
  109. package/src/precision/lsp-client/lifecycle.js +127 -0
  110. package/src/precision/lsp-client/message-parser.js +81 -0
  111. package/src/precision/lsp-client/protocol.js +212 -0
  112. package/src/precision/lsp-client/registry.js +58 -0
  113. package/src/precision/lsp-client/repo-uri.js +60 -0
  114. package/src/precision/lsp-client/stdio-client.js +151 -0
  115. package/src/precision/lsp-client.js +10 -682
  116. package/src/precision/lsp-overlay/build.js +238 -0
  117. package/src/precision/lsp-overlay/contract.js +61 -0
  118. package/src/precision/lsp-overlay/reference-results.js +108 -0
  119. package/src/precision/lsp-overlay/semantic-inputs.js +62 -0
  120. package/src/precision/lsp-overlay/source-session.js +134 -0
  121. package/src/precision/lsp-overlay/store.js +150 -0
  122. package/src/precision/lsp-overlay/target-index.js +147 -0
  123. package/src/precision/lsp-overlay/target-query.js +55 -0
  124. package/src/precision/lsp-overlay.js +11 -868
  125. package/src/precision/typescript-lsp-provider.js +12 -682
  126. package/src/precision/typescript-provider/client.js +69 -0
  127. package/src/precision/typescript-provider/discovery.js +124 -0
  128. package/src/precision/typescript-provider/project-host.js +249 -0
  129. package/src/precision/typescript-provider/project-safety.js +161 -0
  130. package/src/precision/typescript-provider/reference-usage.js +53 -0
  131. package/src/version.js +7 -0
  132. package/docs/releases/v0.2.12.md +0 -45
@@ -0,0 +1,223 @@
1
+ import {
2
+ isSymbol, degreeOf, labelOf, resolveNode, findSeeds, resolveSeedFiles,
3
+ undirectedNeighbors, requestedPathClasses,
4
+ } from '../graph-context.mjs'
5
+ import {createPathClassifier, hasPathClass} from '../../path-classification.js'
6
+
7
+ const QUERY_NON_PRODUCT = Object.freeze(['test', 'e2e', 'generated', 'mock', 'story', 'docs', 'benchmark', 'temp'])
8
+ const LOW_SIGNAL_SYMBOL_RE = /^(?:const(?:ant)?|variable|property|field|enum_member)$/i
9
+ const querySourceFile = (node) => String(node?.source_file || String(node?.id || '').split('#', 1)[0]).replace(/\\/g, '/').replace(/^\.\//, '').toLowerCase()
10
+ const queryWords = (value) => new Set(String(value || '').replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase().split(/[^a-z0-9_]+/).filter(Boolean))
11
+ const exactSymbolName = (node) => {
12
+ const match = String(node?.id || '').match(/#([^@]+)@/)
13
+ return match ? match[1] : String(node?.label || '').replace(/\(\)$/, '')
14
+ }
15
+
16
+ function resolveExactSeedSymbols(g, requested, limit = 12) {
17
+ const values = Array.isArray(requested) ? requested.slice(0, limit) : []
18
+ const seeds = []
19
+ const missing = []
20
+ const ambiguous = []
21
+ for (const raw of values) {
22
+ const wanted = String(raw || '').trim()
23
+ if (!wanted) continue
24
+ let matches = []
25
+ const byId = g.byId.get(wanted)
26
+ if (byId && isSymbol(byId.id)) matches = [byId]
27
+ else matches = g.nodes.filter((node) => isSymbol(node.id)
28
+ && (String(node.label || '') === wanted || exactSymbolName(node) === wanted))
29
+ if (matches.length === 1) {
30
+ if (!seeds.some((seed) => String(seed.id) === String(matches[0].id))) seeds.push(matches[0])
31
+ } else if (matches.length > 1) ambiguous.push(`${wanted} (${matches.length} exact matches; pass a symbol id)`)
32
+ else missing.push(wanted)
33
+ }
34
+ return {seeds, missing, ambiguous}
35
+ }
36
+
37
+ const relationSet = (relationFilter, legacyFilter) => {
38
+ const raw = relationFilter ?? legacyFilter
39
+ const values = Array.isArray(raw) ? raw : (raw == null ? [] : String(raw).split(','))
40
+ const normalized = values.map((value) => String(value).trim().toLowerCase()).filter(Boolean)
41
+ return normalized.length ? new Set(normalized) : null
42
+ }
43
+
44
+ export function tQueryGraph(g, {
45
+ question, mode = 'bfs', depth = 3, context_filter, relation_filter, seed_files, seed_symbols, augment_seeds = false,
46
+ flow_direction = 'both', include_classified = false, include_low_signal = false, token_budget = 2000,
47
+ } = {}, toolCtx = {}) {
48
+ const pinned = resolveSeedFiles(g, seed_files)
49
+ const exactSymbols = resolveExactSeedSymbols(g, seed_symbols)
50
+ const pinnedSeeds = [...pinned.seeds, ...exactSymbols.seeds.filter((node) => !pinned.seeds.some((seed) => String(seed.id) === String(node.id)))]
51
+ const automatic = pinnedSeeds.length && augment_seeds !== true
52
+ ? []
53
+ : findSeeds(g, question, Math.max(0, 6 - pinnedSeeds.length), {repoRoot: toolCtx.repoRoot || null})
54
+ const seeds = [...pinnedSeeds, ...automatic.filter((node) => !pinnedSeeds.some((seed) => String(seed.id) === String(node.id)))]
55
+ if (!seeds.length) {
56
+ const details = [
57
+ pinned.missing.length ? `Unresolved seed files: ${pinned.missing.join(', ')}` : null,
58
+ exactSymbols.missing.length ? `Unresolved exact symbols: ${exactSymbols.missing.join(', ')}` : null,
59
+ exactSymbols.ambiguous.length ? `Ambiguous exact symbols: ${exactSymbols.ambiguous.join(', ')}` : null,
60
+ ].filter(Boolean)
61
+ return [`No nodes matched "${question || ''}".`, ...details].join('\n')
62
+ }
63
+
64
+ const maxDepth = Math.max(1, Math.min(6, Number(depth) || 3))
65
+ const relations = relationSet(relation_filter, context_filter)
66
+ const relationAllowed = (relation) => !relations || relations.has(String(relation ?? '').toLowerCase())
67
+ const direction = ['forward', 'backward', 'both'].includes(String(flow_direction).toLowerCase())
68
+ ? String(flow_direction).toLowerCase() : 'both'
69
+ const neighbors = (id) => {
70
+ const found = new Map()
71
+ const add = (edges) => {
72
+ for (const edge of edges || []) {
73
+ const neighbor = String(edge.id)
74
+ if (edge.barrelProxy === true || !relationAllowed(edge.relation) || found.has(neighbor)) continue
75
+ found.set(neighbor, edge.relation)
76
+ }
77
+ }
78
+ if (direction !== 'backward') add(g.out.get(id))
79
+ if (direction !== 'forward') add(g.inn.get(id))
80
+ return found
81
+ }
82
+
83
+ const requestedClasses = requestedPathClasses(question)
84
+ const classifier = createPathClassifier(toolCtx.repoRoot || null)
85
+ const classificationCache = new Map()
86
+ const pinnedFiles = new Set(pinnedSeeds.map(querySourceFile))
87
+ const classifiedSuppressed = new Set()
88
+ const pathPolicy = (id) => {
89
+ const node = g.byId.get(String(id))
90
+ const file = querySourceFile(node)
91
+ if (!file || pinnedFiles.has(file) || include_classified === true) return {ok: true}
92
+ if (!classificationCache.has(file)) classificationCache.set(file, classifier.explain(file, {content: ''}))
93
+ const info = classificationCache.get(file)
94
+ const classes = QUERY_NON_PRODUCT.filter((name) => hasPathClass(info, name))
95
+ if (!classes.length && !info?.excluded) return {ok: true}
96
+ if (classes.some((name) => requestedClasses.has(name))) return {ok: true}
97
+ classifiedSuppressed.add(String(id))
98
+ return {ok: false, bucket: 'classified'}
99
+ }
100
+
101
+ const questionTerms = queryWords(question)
102
+ const start = seeds.map((seed) => String(seed.id))
103
+ const isLowSignal = (id) => {
104
+ if (include_low_signal === true || start.includes(String(id))) return false
105
+ const node = g.byId.get(String(id))
106
+ if (!node || !isSymbol(node.id) || !LOW_SIGNAL_SYMBOL_RE.test(String(node.symbol_kind || ''))) return false
107
+ const labelTerms = queryWords(node.label || String(node.id || '').split('#').pop() || '')
108
+ if ([...questionTerms].some((term) => labelTerms.has(term))) return false
109
+ return degreeOf(g, id) === 0
110
+ }
111
+ const charBudget = Math.max(400, (Number(token_budget) || 2000) * 4)
112
+ const nodeBudget = Math.max(20, Math.min(120, Math.round((Number(token_budget) || 2000) / 40)))
113
+ const depthOf = new Map()
114
+
115
+ if (mode === 'dfs') {
116
+ const stack = start.map((id) => ({id, depth: 0}))
117
+ const seen = new Set()
118
+ while (stack.length) {
119
+ const {id, depth: currentDepth} = stack.pop()
120
+ if (!depthOf.has(id) || currentDepth < depthOf.get(id)) depthOf.set(id, currentDepth)
121
+ if (seen.has(id)) continue
122
+ seen.add(id)
123
+ if (currentDepth >= maxDepth) continue
124
+ for (const [neighbor, relation] of neighbors(id)) {
125
+ if (!relationAllowed(relation) || !pathPolicy(neighbor).ok) continue
126
+ if (!seen.has(neighbor)) stack.push({id: neighbor, depth: currentDepth + 1})
127
+ }
128
+ }
129
+ } else {
130
+ let frontier = start.slice()
131
+ start.forEach((id) => depthOf.set(id, 0))
132
+ for (let currentDepth = 0; currentDepth < maxDepth && frontier.length; currentDepth++) {
133
+ const next = []
134
+ for (const id of frontier) {
135
+ for (const [neighbor, relation] of neighbors(id)) {
136
+ if (!relationAllowed(relation) || !pathPolicy(neighbor).ok) continue
137
+ if (!depthOf.has(neighbor)) {
138
+ depthOf.set(neighbor, currentDepth + 1)
139
+ next.push(neighbor)
140
+ }
141
+ }
142
+ }
143
+ frontier = next
144
+ }
145
+ }
146
+
147
+ const reachedBeforeSignalFilter = depthOf.size
148
+ const lowSignalSuppressed = [...depthOf.keys()].filter(isLowSignal).length
149
+ const ranked = [...depthOf.entries()]
150
+ .filter(([id]) => !isLowSignal(id))
151
+ .map(([id, distance]) => ({id, distance, degree: degreeOf(g, id)}))
152
+ .sort((a, b) => a.distance - b.distance || b.degree - a.degree)
153
+ const shown = ranked.slice(0, nodeBudget)
154
+ const shownIds = new Set(shown.map((node) => node.id))
155
+ const edgeSeen = new Set()
156
+ const shownEdges = []
157
+ for (const source of shownIds) {
158
+ for (const edge of g.out.get(source) || []) {
159
+ const target = String(edge.id)
160
+ if (edge.barrelProxy === true || !shownIds.has(target) || !relationAllowed(edge.relation)) continue
161
+ const key = `${source}|${edge.relation}|${target}`
162
+ if (edgeSeen.has(key)) continue
163
+ edgeSeen.add(key)
164
+ shownEdges.push([source, edge.relation, target])
165
+ if (shownEdges.length >= 160) break
166
+ }
167
+ if (shownEdges.length >= 160) break
168
+ }
169
+ const head = [
170
+ `Query: "${question || ''}" (${mode}, depth ${maxDepth}, flow ${direction}${relations ? `, relations ${[...relations].join('/')}` : ''})`,
171
+ `Seeds: ${seeds.map((seed) => seed.label ?? seed.id).join(', ')}`,
172
+ pinned.missing.length ? `Unresolved pinned seed files: ${pinned.missing.join(', ')}` : null,
173
+ exactSymbols.missing.length ? `Unresolved exact seed symbols: ${exactSymbols.missing.join(', ')}` : null,
174
+ exactSymbols.ambiguous.length ? `Ambiguous exact seed symbols: ${exactSymbols.ambiguous.join(', ')}` : null,
175
+ `Reached ${reachedBeforeSignalFilter} policy-eligible nodes; showing ${shown.length} closest by proximity + connectivity, ${shownEdges.length} edges among them.`,
176
+ classifiedSuppressed.size ? `Suppressed ${classifiedSuppressed.size} classified/non-product traversal node(s); ask for that class or pass include_classified:true.` : null,
177
+ lowSignalSuppressed ? `Suppressed ${lowSignalSuppressed} unreferenced constant/field node(s) with no query-term match; pass include_low_signal:true to inspect them.` : null,
178
+ include_classified === true ? 'Path policy: classified/non-product traversal explicitly enabled.' : `Path policy: production-first${requestedClasses.size ? `; explicit question classes enabled: ${[...requestedClasses].join(', ')}` : ''}.`,
179
+ '',
180
+ 'Nodes:',
181
+ ]
182
+ const nodeLines = shown.map((node) => ` [d${node.distance}] ${labelOf(g, node.id)} (deg ${node.degree}) [${node.id}]`)
183
+ const edgeLines = ['', 'Edges:', ...shownEdges.map(([source, relation, target]) => ` ${labelOf(g, source)} --${relation || 'rel'}--> ${labelOf(g, target)}`)]
184
+ let text = [...head.filter(Boolean), ...nodeLines, ...edgeLines].join('\n')
185
+ if (text.length > charBudget) text = text.slice(0, charBudget) + `\n... (truncated to ~${token_budget} tokens)`
186
+ return text
187
+ }
188
+
189
+ export function tShortestPath(g, {source, target, max_hops = 8} = {}) {
190
+ const sourceNode = resolveNode(g, source)
191
+ const targetNode = resolveNode(g, target)
192
+ if (!sourceNode) return `Source "${source}" not found.`
193
+ if (!targetNode) return `Target "${target}" not found.`
194
+ const sourceId = String(sourceNode.id)
195
+ const targetId = String(targetNode.id)
196
+ if (sourceId === targetId) return `Source and target are the same node: ${sourceNode.label ?? sourceId}.`
197
+ const limit = Math.max(1, Math.min(20, Number(max_hops) || 8))
198
+ const previous = new Map([[sourceId, null]])
199
+ const relationTo = new Map()
200
+ let frontier = [sourceId]
201
+ let hops = 0
202
+ let found = false
203
+ while (frontier.length && hops < limit && !found) {
204
+ const next = []
205
+ for (const id of frontier) {
206
+ for (const [neighbor, relation] of undirectedNeighbors(g, id)) {
207
+ if (previous.has(neighbor)) continue
208
+ previous.set(neighbor, id)
209
+ relationTo.set(neighbor, relation)
210
+ if (neighbor === targetId) { found = true; break }
211
+ next.push(neighbor)
212
+ }
213
+ if (found) break
214
+ }
215
+ frontier = next
216
+ hops++
217
+ }
218
+ if (!previous.has(targetId)) return `No path found between "${sourceNode.label ?? sourceId}" and "${targetNode.label ?? targetId}" within ${limit} hops.`
219
+ const path = []
220
+ for (let current = targetId; current != null; current = previous.get(current)) path.unshift(current)
221
+ const lines = path.map((id, index) => index === 0 ? ` ${labelOf(g, id)}` : ` --${relationTo.get(id) || 'rel'}--> ${labelOf(g, id)}`)
222
+ return [`Shortest path (${path.length - 1} hops): ${sourceNode.label ?? sourceId} → ${targetNode.label ?? targetId}`, ...lines].join('\n')
223
+ }