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.
- package/README.md +90 -21
- package/SECURITY.md +2 -2
- package/docs/releases/v0.2.14.md +93 -0
- package/package.json +2 -2
- package/skill/SKILL.md +108 -43
- package/src/analysis/allowed-test-runner.js +20 -9
- package/src/analysis/architecture/contract-graph.js +119 -0
- package/src/analysis/architecture/contract-schema.js +110 -0
- package/src/analysis/architecture/contract-storage.js +35 -0
- package/src/analysis/architecture/contract-verification.js +168 -0
- package/src/analysis/architecture-contract.js +16 -343
- package/src/analysis/change-classification/diff-parser.js +103 -0
- package/src/analysis/change-classification/options.js +40 -0
- package/src/analysis/change-classification/symbol-classifier.js +177 -0
- package/src/analysis/change-classification.js +120 -519
- package/src/analysis/dead-code-review/policy.js +23 -0
- package/src/analysis/dead-code-review.js +9 -19
- package/src/analysis/dep-check.js +10 -57
- package/src/analysis/dep-rules.js +10 -303
- package/src/analysis/dependency/scoped-dependencies.js +40 -0
- package/src/analysis/endpoints/common.js +62 -0
- package/src/analysis/endpoints/extract.js +107 -0
- package/src/analysis/endpoints/inventory.js +84 -0
- package/src/analysis/endpoints/mounts.js +129 -0
- package/src/analysis/endpoints-java.js +80 -3
- package/src/analysis/endpoints.js +3 -448
- package/src/analysis/git-history/analytics.js +177 -0
- package/src/analysis/git-history/collector.js +109 -0
- package/src/analysis/git-history/options.js +68 -0
- package/src/analysis/git-history.js +128 -577
- package/src/analysis/health-capabilities.js +82 -0
- package/src/analysis/http-contracts/analysis.js +169 -0
- package/src/analysis/http-contracts/client-call-detection.js +81 -0
- package/src/analysis/http-contracts/client-call-parser.js +255 -0
- package/src/analysis/http-contracts/graph-context.js +143 -0
- package/src/analysis/http-contracts/matching.js +61 -0
- package/src/analysis/http-contracts/shared.js +86 -0
- package/src/analysis/http-contracts.js +6 -822
- package/src/analysis/internal-audit/dependency-health.js +111 -0
- package/src/analysis/internal-audit/python-manifests.js +65 -0
- package/src/analysis/internal-audit/repo-files.js +55 -0
- package/src/analysis/internal-audit/supply-chain.js +132 -0
- package/src/analysis/internal-audit.collect.js +5 -106
- package/src/analysis/internal-audit.run.js +113 -200
- package/src/analysis/jvm-dependency-evidence.js +69 -0
- package/src/analysis/source-correctness/retry-patterns.js +93 -0
- package/src/analysis/source-correctness.js +225 -0
- package/src/analysis/structure/dependency-graph.js +156 -0
- package/src/analysis/structure/findings.js +142 -0
- package/src/graph/builder/go-receiver-resolution.js +112 -0
- package/src/graph/builder/js/queries.js +48 -0
- package/src/graph/builder/lang-go.js +89 -4
- package/src/graph/builder/lang-js.js +4 -44
- package/src/graph/builder/pass2-resolution.js +68 -0
- package/src/graph/freshness-probe.js +2 -1
- package/src/graph/incremental-refresh.js +3 -2
- package/src/graph/internal-builder.build.js +22 -183
- package/src/graph/internal-builder.pass2.js +161 -0
- package/src/graph/internal-builder.resolvers.js +2 -105
- package/src/graph/resolvers/rust.js +117 -0
- package/src/mcp/actions/advisories.mjs +18 -0
- package/src/mcp/actions/graph-lifecycle.mjs +148 -0
- package/src/mcp/actions/graph-sync.mjs +195 -0
- package/src/mcp/actions/hosted-architecture.mjs +57 -0
- package/src/mcp/architecture-bootstrap.mjs +168 -0
- package/src/mcp/architecture-starter.mjs +234 -0
- package/src/mcp/catalog.mjs +22 -6
- package/src/mcp/evidence/bun-lock-graph.mjs +209 -0
- package/src/mcp/evidence/package-graph-common.mjs +115 -0
- package/src/mcp/evidence/package-lock-graph.mjs +92 -0
- package/src/mcp/evidence-snapshot.package-graph.mjs +5 -474
- package/src/mcp/graph/context-core.mjs +111 -0
- package/src/mcp/graph/context-seeds.mjs +208 -0
- package/src/mcp/graph/context-state.mjs +86 -0
- package/src/mcp/graph/tools-core.mjs +143 -0
- package/src/mcp/graph/tools-query.mjs +223 -0
- package/src/mcp/graph-context.mjs +4 -496
- package/src/mcp/health/audit-format.mjs +172 -0
- package/src/mcp/health/audit.mjs +171 -0
- package/src/mcp/health/dead-code.mjs +110 -0
- package/src/mcp/health/duplicates.mjs +83 -0
- package/src/mcp/health/endpoints.mjs +42 -0
- package/src/mcp/health/structure.mjs +219 -0
- package/src/mcp/runtime-version.mjs +32 -0
- package/src/mcp/server/auto-refresh.mjs +66 -0
- package/src/mcp/server/runtime-config.mjs +43 -0
- package/src/mcp/server/shutdown.mjs +40 -0
- package/src/mcp/sync/evidence-architecture.mjs +54 -0
- package/src/mcp/sync/evidence-common.mjs +88 -0
- package/src/mcp/sync/evidence-duplicates.mjs +100 -0
- package/src/mcp/sync/evidence-health.mjs +56 -0
- package/src/mcp/sync/evidence-packages.mjs +95 -0
- package/src/mcp/sync/payload-common.mjs +97 -0
- package/src/mcp/sync/payload-v2.mjs +53 -0
- package/src/mcp/sync/payload-v3.mjs +79 -0
- package/src/mcp/sync-evidence.mjs +7 -402
- package/src/mcp/sync-payload.mjs +10 -244
- package/src/mcp/tools-actions.mjs +18 -414
- package/src/mcp/tools-architecture.mjs +18 -70
- package/src/mcp/tools-context.mjs +56 -15
- package/src/mcp/tools-endpoints.mjs +4 -1
- package/src/mcp/tools-graph.mjs +17 -330
- package/src/mcp/tools-health.mjs +24 -705
- package/src/mcp/tools-verified-change.mjs +12 -4
- package/src/mcp-server.mjs +49 -146
- package/src/precision/lsp-client/constants.js +12 -0
- package/src/precision/lsp-client/environment.js +20 -0
- package/src/precision/lsp-client/errors.js +15 -0
- package/src/precision/lsp-client/lifecycle.js +127 -0
- package/src/precision/lsp-client/message-parser.js +81 -0
- package/src/precision/lsp-client/protocol.js +212 -0
- package/src/precision/lsp-client/registry.js +58 -0
- package/src/precision/lsp-client/repo-uri.js +60 -0
- package/src/precision/lsp-client/stdio-client.js +151 -0
- package/src/precision/lsp-client.js +10 -682
- package/src/precision/lsp-overlay/build.js +238 -0
- package/src/precision/lsp-overlay/contract.js +61 -0
- package/src/precision/lsp-overlay/reference-results.js +108 -0
- package/src/precision/lsp-overlay/semantic-inputs.js +62 -0
- package/src/precision/lsp-overlay/source-session.js +134 -0
- package/src/precision/lsp-overlay/store.js +150 -0
- package/src/precision/lsp-overlay/target-index.js +147 -0
- package/src/precision/lsp-overlay/target-query.js +55 -0
- package/src/precision/lsp-overlay.js +11 -868
- package/src/precision/typescript-lsp-provider.js +12 -682
- package/src/precision/typescript-provider/client.js +69 -0
- package/src/precision/typescript-provider/discovery.js +124 -0
- package/src/precision/typescript-provider/project-host.js +249 -0
- package/src/precision/typescript-provider/project-safety.js +161 -0
- package/src/precision/typescript-provider/reference-usage.js +53 -0
- package/src/version.js +7 -0
- 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
|
+
}
|