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,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
+ }