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,177 @@
1
+ import {hasPathClass} from '../../path-classification.js'
2
+ import {bareGraphLabel, CHANGE_CLASS_RANK, changeLineNumber, normalizeChangePath} from './options.js'
3
+
4
+ export function indexChangeGraph(graph, limits) {
5
+ const byFile = new Map()
6
+ for (const node of graph?.nodes || []) {
7
+ const file = normalizeChangePath(node?.source_file || (!String(node?.id || '').includes('#') ? node?.id : ''))
8
+ if (!file) continue
9
+ if (!byFile.has(file)) byFile.set(file, {path: file, fileNodeId: null, symbols: []})
10
+ const record = byFile.get(file)
11
+ if (!String(node.id).includes('#')) record.fileNodeId = String(node.id)
12
+ else if (record.symbols.length < limits.maxSymbolsPerFile) {
13
+ const start = changeLineNumber(node.source_location) || changeLineNumber(node.id)
14
+ if (!start) continue
15
+ record.symbols.push({
16
+ id: String(node.id), label: String(node.label || node.id), start,
17
+ end: changeLineNumber(node.source_end), exported: node.exported === true,
18
+ symbolKind: node.symbol_kind || null,
19
+ })
20
+ }
21
+ }
22
+ for (const record of byFile.values()) {
23
+ record.symbols.sort((a, b) => a.start - b.start || a.id.localeCompare(b.id))
24
+ for (let index = 0; index < record.symbols.length; index++) {
25
+ const symbol = record.symbols[index]
26
+ if (!symbol.end || symbol.end < symbol.start) {
27
+ const next = record.symbols[index + 1]?.start
28
+ symbol.end = next ? Math.max(symbol.start, next - 1) : symbol.start + 400
29
+ }
30
+ symbol.end = Math.min(symbol.end, symbol.start + 2_000)
31
+ }
32
+ }
33
+ return byFile
34
+ }
35
+
36
+ const isMetadataLine = (text) => {
37
+ const value = String(text || '').trim()
38
+ return !value || /^(?:\/\/|\/\*|\*|\*\/|#(?!include\b)|<!--|-->|"""|''')/.test(value)
39
+ }
40
+
41
+ function signatureText(text, symbol) {
42
+ const value = String(text || '').trim(), name = bareGraphLabel(symbol.label)
43
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
44
+ const hasName = name && new RegExp(`\\b${escaped}\\b`, 'i').test(value)
45
+ return hasName && /\b(?:export|default|declare|abstract|async|function|class|interface|type|enum|const|let|var|def|func|fn|struct|trait|impl|public|private|protected|static)\b/.test(value)
46
+ }
47
+
48
+ function signaturePosition(change, symbol) {
49
+ const candidates = change.kind === 'removed' ? [change.mappedNewLine, change.oldLine] : [change.newLine]
50
+ if (candidates.some((line) => line === symbol.start)) return true
51
+ if (!candidates.some((line) => Number.isFinite(line) && line > symbol.start && line <= symbol.start + 4)) return false
52
+ const value = String(change.text || '').trim()
53
+ if (/^(?:return|throw|yield|if|for|while|switch|match|const|let|var|this\.|self\.|[A-Za-z_$][\w$]*\s*=)/.test(value)) return false
54
+ return /^(?:[A-Za-z_$][\w$]*\??\s*:\s*[^;]+[,)]?|[A-Za-z_$][\w$<>,.?\[\] :*&]+\s+[A-Za-z_$][\w$]*\s*[,)]|[),:<>{}\[\]|&?]+)$/.test(value)
55
+ }
56
+
57
+ const moduleSignatureText = (text) => /^\s*(?:import\b|export\s+(?:\*|\{)|(?:const|let|var)\s+\w+\s*=\s*require\b|using\b|package\b|#include\b|mod\b|pub\s+use\b)/.test(String(text || ''))
58
+
59
+ function chooseSymbol(record, change) {
60
+ if (!record?.symbols?.length) return null
61
+ const lines = change.kind === 'removed'
62
+ ? [...new Set([change.mappedNewLine, change.oldLine].filter((line) => Number.isFinite(line) && line > 0))]
63
+ : [change.newLine]
64
+ const candidates = []
65
+ for (const symbol of record.symbols) {
66
+ if (!lines.some((line) => line >= symbol.start && line <= symbol.end)) continue
67
+ const label = bareGraphLabel(symbol.label)
68
+ const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
69
+ candidates.push({symbol, mentions: label && new RegExp(`\\b${escaped}\\b`).test(change.text), span: symbol.end - symbol.start})
70
+ }
71
+ return candidates.sort((a, b) => Number(b.mentions) - Number(a.mentions) || a.span - b.span || a.symbol.id.localeCompare(b.symbol.id))[0]?.symbol || null
72
+ }
73
+
74
+ const strongest = (classes) => [...classes]
75
+ .sort((a, b) => (CHANGE_CLASS_RANK[b] ?? 5) - (CHANGE_CLASS_RANK[a] ?? 5) || a.localeCompare(b))[0] || 'metadata-only'
76
+
77
+ function symbolChanges(parsed, record, includeAddedSeeds) {
78
+ const grouped = new Map(), unmapped = []
79
+ for (const change of [...parsed.additions, ...parsed.removals]) {
80
+ const symbol = chooseSymbol(record, change)
81
+ if (!symbol) { unmapped.push(change); continue }
82
+ if (!grouped.has(symbol.id)) grouped.set(symbol.id, {symbol, additions: [], removals: []})
83
+ grouped.get(symbol.id)[change.kind === 'added' ? 'additions' : 'removals'].push(change)
84
+ }
85
+ const symbols = [], seedIds = []
86
+ for (const group of [...grouped.values()].sort((a, b) => a.symbol.start - b.symbol.start || a.symbol.id.localeCompare(b.symbol.id))) {
87
+ const addedCode = group.additions.filter((change) => !isMetadataLine(change.text))
88
+ const removedCode = group.removals.filter((change) => !isMetadataLine(change.text))
89
+ const addedDeclaration = addedCode.some((change) => signaturePosition(change, group.symbol) || signatureText(change.text, group.symbol))
90
+ const removedDeclaration = removedCode.some((change) => signaturePosition(change, group.symbol) || signatureText(change.text, group.symbol))
91
+ let classification
92
+ if (parsed.newFile || (addedDeclaration && !removedCode.length)) classification = 'added'
93
+ else if (parsed.deletedFile || (removedDeclaration && !addedCode.length)) classification = 'removed'
94
+ else if (addedDeclaration || removedDeclaration) classification = 'signature-changed'
95
+ else if (addedCode.length || removedCode.length) classification = 'body-changed'
96
+ else classification = 'metadata-only'
97
+ const reasons = {
98
+ added: 'new declaration; existing callers cannot depend on it yet',
99
+ removed: 'declaration removed; existing callers may break',
100
+ 'signature-changed': 'declaration/signature line changed',
101
+ 'body-changed': 'executable lines changed inside the symbol body',
102
+ 'metadata-only': 'only comment/blank lines changed in this symbol',
103
+ }
104
+ const symbolSeeds = []
105
+ if (classification === 'added') { if (includeAddedSeeds) symbolSeeds.push(group.symbol.id) }
106
+ else if (classification !== 'metadata-only') {
107
+ symbolSeeds.push(group.symbol.id)
108
+ if (['removed', 'signature-changed'].includes(classification) && group.symbol.exported && record?.fileNodeId) symbolSeeds.push(record.fileNodeId)
109
+ }
110
+ seedIds.push(...symbolSeeds)
111
+ symbols.push({
112
+ id: group.symbol.id, label: group.symbol.label, start: group.symbol.start, end: group.symbol.end,
113
+ exported: group.symbol.exported, classification, reason: reasons[classification],
114
+ addedLines: group.additions.map((change) => change.newLine).filter(Boolean),
115
+ removedLines: group.removals.map((change) => change.oldLine).filter(Boolean),
116
+ seedIds: [...new Set(symbolSeeds)].sort(),
117
+ })
118
+ }
119
+ return {symbols, seedIds, unmapped}
120
+ }
121
+
122
+ export function analyzeParsedFile(parsed, indexed, {includeAddedSeeds}) {
123
+ const path = parsed.newPath || parsed.oldPath || '(unknown)'
124
+ const record = indexed.get(parsed.newPath) || indexed.get(parsed.oldPath) || null
125
+ const grouped = symbolChanges(parsed, record, includeAddedSeeds)
126
+ const unmappedCode = grouped.unmapped.filter((change) => !isMetadataLine(change.text))
127
+ const unmappedMetadata = grouped.unmapped.length > 0 && !unmappedCode.length
128
+ let classification, reason
129
+ if (parsed.binary) { classification = 'unknown'; reason = 'binary diff has no line-level evidence' }
130
+ else if (parsed.deletedFile) { classification = 'removed'; reason = 'file removed' }
131
+ else if (parsed.renamed) { classification = 'signature-changed'; reason = 'file rename changes module identity' }
132
+ else if (parsed.newFile) { classification = 'added'; reason = 'new file; no existing dependent can target it yet' }
133
+ else if (unmappedCode.some((change) => moduleSignatureText(change.text))) { classification = 'signature-changed'; reason = 'module import/export surface changed outside a mapped symbol' }
134
+ else if (unmappedCode.length) { classification = 'unknown'; reason = 'executable diff lines could not be mapped to a graph symbol' }
135
+ else if (grouped.symbols.length) {
136
+ classification = strongest(grouped.symbols.map((symbol) => symbol.classification))
137
+ reason = grouped.symbols.length === 1 ? grouped.symbols[0].reason : `${grouped.symbols.length} mapped symbols; strongest change is ${classification}`
138
+ } else if (unmappedMetadata || parsed.hunks.length || parsed.additions.length || parsed.removals.length) {
139
+ classification = 'metadata-only'; reason = 'only comment/blank metadata changed outside symbols'
140
+ } else { classification = 'metadata-only'; reason = 'file metadata changed without textual hunks' }
141
+
142
+ const seedIds = grouped.seedIds
143
+ if (parsed.binary || parsed.deletedFile || parsed.renamed || classification === 'unknown') {
144
+ if (record?.fileNodeId) seedIds.push(record.fileNodeId)
145
+ if (parsed.binary || parsed.deletedFile || classification === 'unknown') seedIds.push(...(record?.symbols || []).map((symbol) => symbol.id))
146
+ } else if (classification === 'signature-changed' && !grouped.symbols.length && record?.fileNodeId) seedIds.push(record.fileNodeId)
147
+ else if (classification === 'added' && includeAddedSeeds) {
148
+ if (record?.fileNodeId) seedIds.push(record.fileNodeId)
149
+ seedIds.push(...grouped.symbols.map((symbol) => symbol.id))
150
+ }
151
+ return {
152
+ path, oldPath: parsed.oldPath, newPath: parsed.newPath, classification, reason,
153
+ binary: parsed.binary, renamed: parsed.renamed,
154
+ addedLines: parsed.additions.length, removedLines: parsed.removals.length,
155
+ symbols: grouped.symbols, seedIds: [...new Set(seedIds)].sort(),
156
+ }
157
+ }
158
+
159
+ export function unknownChangedFile(path, indexed, reason) {
160
+ const normalized = normalizeChangePath(path), record = indexed.get(normalized)
161
+ return {
162
+ path: normalized, oldPath: normalized, newPath: normalized, classification: 'unknown', reason,
163
+ binary: false, renamed: false, addedLines: 0, removedLines: 0, symbols: [],
164
+ seedIds: [record?.fileNodeId, ...(record?.symbols || []).map((symbol) => symbol.id)].filter(Boolean).sort(),
165
+ }
166
+ }
167
+
168
+ export function classifyTestSurface(file, pathClassifier) {
169
+ const explanation = pathClassifier.explain(file.path)
170
+ if (!hasPathClass(explanation, 'test', 'e2e')) return file
171
+ const surface = explanation.classes.includes('e2e') ? 'e2e' : 'test'
172
+ return {
173
+ ...file, classification: 'test-only', changeClassification: file.classification,
174
+ reason: `${surface} path; excluded from the product blast-radius seed set`,
175
+ pathClasses: explanation.classes, seedIds: [],
176
+ }
177
+ }