weavatrix 0.3.10 → 0.3.12
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 +12 -4
- package/docs/releases/v0.3.11.md +55 -0
- package/docs/releases/v0.3.12.md +44 -0
- package/package.json +4 -1
- package/src/analysis/cargo-manifests.js +7 -0
- package/src/analysis/dead-check.js +4 -4
- package/src/analysis/duplicates.compute.js +5 -1
- package/src/analysis/hot-path-review.js +25 -3
- package/src/analysis/internal-audit/supply-chain.js +1 -1
- package/src/analysis/manifests.js +4 -1
- package/src/analysis/static-test-reachability.js +19 -2
- package/src/analysis-kit.mjs +17 -0
- package/src/graph/builder/lang-rust-calls.js +63 -0
- package/src/graph/builder/lang-rust.js +31 -6
- package/src/graph/builder/lang-solidity.js +126 -0
- package/src/graph/builder/lang-sql.js +263 -0
- package/src/graph/builder/pass2-resolution.js +15 -2
- package/src/graph/freshness-probe.js +1 -1
- package/src/graph/incremental-refresh.js +1 -1
- package/src/graph/internal-builder.build.js +18 -7
- package/src/graph/internal-builder.langs.js +3 -1
- package/src/graph/internal-builder.pass2.js +4 -2
- package/src/graph/internal-builder.resolvers.js +66 -1
- package/src/graph/parser-artifact-boundary.js +1 -0
- package/src/mcp/actions/graph-lifecycle.mjs +1 -1
- package/src/mcp/architecture-starter.mjs +1 -1
- package/src/mcp/graph/context-seeds.mjs +23 -7
- package/src/precision/typescript-provider/client.js +7 -0
- package/src/security/advisory-store.js +0 -1
- package/src/security/malware-heuristics.exclusions.js +1 -1
- package/src/security/registry-sig.rules.js +1 -1
|
@@ -17,11 +17,12 @@ const TOOL_EXECUTION_TERMS = QUERY_INTENTS.find(([id]) => id === 'tool-execution
|
|
|
17
17
|
const TOOL_EXECUTION_TRIGGERS = new Set(['tool', 'tools', 'tooling', 'mcp'])
|
|
18
18
|
const wordsOf = (value) => String(value ?? '').replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase().split(/[^a-z0-9_]+/).filter(Boolean)
|
|
19
19
|
const normPath = (value) => String(value ?? '').replace(/\\/g, '/').replace(/^\.\//, '').toLowerCase()
|
|
20
|
-
const CODE_FILE_RE = /\.(?:[cm]?[jt]sx?|py|go|java|rs|kt|kts|cs|rb|php)$/i
|
|
20
|
+
const CODE_FILE_RE = /\.(?:[cm]?[jt]sx?|py|go|java|rs|kt|kts|cs|rb|php|sol|sql)$/i
|
|
21
21
|
const DATA_OR_PROSE_RE = /\.(?:json|ya?ml|toml|ini|md|mdx|rst|adoc|html?|css|scss|less|svg)$/i
|
|
22
22
|
const LANGUAGE_EXTENSIONS = Object.freeze({
|
|
23
23
|
rust: ['rs'], python: ['py', 'pyi'], typescript: ['ts', 'tsx', 'mts', 'cts'],
|
|
24
24
|
javascript: ['js', 'jsx', 'mjs', 'cjs'], go: ['go'], java: ['java'], csharp: ['cs'],
|
|
25
|
+
solidity: ['sol'], sql: ['sql'],
|
|
25
26
|
})
|
|
26
27
|
|
|
27
28
|
function requestedLanguages(query) {
|
|
@@ -33,6 +34,8 @@ function requestedLanguages(query) {
|
|
|
33
34
|
if (words.has('golang') || /(?:^|[^A-Za-z])Go(?:[^A-Za-z]|$)/.test(raw)) requested.add('go')
|
|
34
35
|
if (words.has('java')) requested.add('java')
|
|
35
36
|
if (words.has('csharp') || words.has('dotnet') || /csharp|(?:^|[^A-Za-z])C#(?:[^A-Za-z]|$)/i.test(raw)) requested.add('csharp')
|
|
37
|
+
if (words.has('solidity') || words.has('contract') || words.has('contracts')) requested.add('solidity')
|
|
38
|
+
if (words.has('sql') || words.has('schema') || words.has('migration') || words.has('migrations')) requested.add('sql')
|
|
36
39
|
return new Set([...requested].flatMap((language) => LANGUAGE_EXTENSIONS[language]))
|
|
37
40
|
}
|
|
38
41
|
|
|
@@ -86,13 +89,16 @@ function toolExecutionSignal(node, source, words, stem) {
|
|
|
86
89
|
return score
|
|
87
90
|
}
|
|
88
91
|
|
|
89
|
-
|
|
92
|
+
// relaxStop is the last-resort fallback: when EVERY token is a stop word (e.g. a bare
|
|
93
|
+
// "architecture" query), keep them as concepts so the query still seeds instead of
|
|
94
|
+
// misreporting "No nodes matched" for a concept the repository clearly contains.
|
|
95
|
+
function queryConcepts(query, {relaxStop = false} = {}) {
|
|
90
96
|
const tokens = wordsOf(query)
|
|
91
97
|
const toolExecution = tokens.some((token) => TOOL_EXECUTION_TRIGGERS.has(token))
|
|
92
98
|
const explanatoryWork = tokens.some((token) => token === 'how' || token === 'explain')
|
|
93
99
|
const seen = new Set(), concepts = []
|
|
94
100
|
for (const raw of tokens) {
|
|
95
|
-
if (raw.length < 2 || QUERY_STOP.has(raw)) continue
|
|
101
|
+
if (raw.length < 2 || (!relaxStop && QUERY_STOP.has(raw))) continue
|
|
96
102
|
if (explanatoryWork && (raw === 'work' || raw === 'works' || raw === 'working')) continue
|
|
97
103
|
if (toolExecution && TOOL_EXECUTION_TERMS.includes(raw)) {
|
|
98
104
|
if (seen.has('tool-execution')) continue
|
|
@@ -113,7 +119,7 @@ function exactIdentifierSeeds(g, query, limit, {repoRoot = null} = {}) {
|
|
|
113
119
|
const identifiers = [...new Set((String(query || '').match(/[A-Za-z_$][A-Za-z0-9_$]*/g) || [])
|
|
114
120
|
.filter((item) => /(?:[a-z0-9][A-Z]|_)/.test(item)).map((item) => item.toLowerCase()))]
|
|
115
121
|
if (!identifiers.length) return []
|
|
116
|
-
const requestedClasses = requestedPathClasses(query), languageExtensions =
|
|
122
|
+
const requestedClasses = requestedPathClasses(query), languageExtensions = effectiveLanguages(g, query)
|
|
117
123
|
const classifier = createPathClassifier(repoRoot), classificationCache = new Map(), matches = []
|
|
118
124
|
for (const identifier of identifiers) {
|
|
119
125
|
const candidates = g.nodes.filter((node) => String(node.label || '').replace(/\(\)$/, '').toLowerCase() === identifier
|
|
@@ -141,7 +147,7 @@ function conceptScore(g, node, concept, queryContext) {
|
|
|
141
147
|
else if (term.length >= 4 && term !== 'tool' && term !== 'tools' && (id.includes(term) || label.includes(term))) match = Math.max(match, primary ? 12 : 7)
|
|
142
148
|
})
|
|
143
149
|
const extension = (/\.([^.\/]+)$/.exec(source) || [])[1] || ''
|
|
144
|
-
const languageConcept = {rust: ['rs'], python: ['py', 'pyi'], py: ['py', 'pyi'], typescript: ['ts', 'tsx', 'mts', 'cts'], ts: ['ts', 'tsx', 'mts', 'cts'], javascript: ['js', 'jsx', 'mjs', 'cjs'], js: ['js', 'jsx', 'mjs', 'cjs'], nodejs: ['js', 'jsx', 'mjs', 'cjs'], golang: ['go'], go: ['go'], java: ['java'], csharp: ['cs'], dotnet: ['cs']}[concept.raw]
|
|
150
|
+
const languageConcept = {rust: ['rs'], python: ['py', 'pyi'], py: ['py', 'pyi'], typescript: ['ts', 'tsx', 'mts', 'cts'], ts: ['ts', 'tsx', 'mts', 'cts'], javascript: ['js', 'jsx', 'mjs', 'cjs'], js: ['js', 'jsx', 'mjs', 'cjs'], nodejs: ['js', 'jsx', 'mjs', 'cjs'], golang: ['go'], go: ['go'], java: ['java'], csharp: ['cs'], dotnet: ['cs'], solidity: ['sol'], sql: ['sql'], schema: ['sql']}[concept.raw]
|
|
145
151
|
if (languageConcept?.includes(extension) && queryContext.languageExtensions.has(extension)) match = Math.max(match, 58)
|
|
146
152
|
const fileNode = !isSymbol(node.id), depth = source ? source.split('/').length : 9
|
|
147
153
|
if (concept.id === 'bootstrap') match = Math.max(match, entrypointSignal(g, node, source, stem))
|
|
@@ -155,12 +161,22 @@ function conceptScore(g, node, concept, queryContext) {
|
|
|
155
161
|
return Math.max(0, score)
|
|
156
162
|
}
|
|
157
163
|
|
|
164
|
+
// A requested language that matches ZERO nodes in this graph is over-eager inference (e.g.
|
|
165
|
+
// "contract" inferring Solidity in a repo with no .sol files); drop the filter rather than
|
|
166
|
+
// eliminate every candidate and return nothing.
|
|
167
|
+
function effectiveLanguages(g, query) {
|
|
168
|
+
const languageExtensions = requestedLanguages(query)
|
|
169
|
+
if (languageExtensions.size && !g.nodes.some((node) => matchesLanguage(node, languageExtensions))) return new Set()
|
|
170
|
+
return languageExtensions
|
|
171
|
+
}
|
|
172
|
+
|
|
158
173
|
export function findSeeds(g, query, limit = 8, {repoRoot = null} = {}) {
|
|
159
174
|
const exact = exactIdentifierSeeds(g, query, limit, {repoRoot})
|
|
160
175
|
if (exact.length) return exact
|
|
161
|
-
|
|
176
|
+
let concepts = queryConcepts(query)
|
|
177
|
+
if (!concepts.length) concepts = queryConcepts(query, {relaxStop: true})
|
|
162
178
|
if (!concepts.length || limit <= 0) return []
|
|
163
|
-
const requestedClasses = requestedPathClasses(query), languageExtensions =
|
|
179
|
+
const requestedClasses = requestedPathClasses(query), languageExtensions = effectiveLanguages(g, query)
|
|
164
180
|
const queryContext = {runtimeIntent: concepts.some((concept) => concept.id === 'bootstrap' || concept.id === 'tool-execution'), maintenanceIntent: wordsOf(query).some((word) => ['script', 'scripts', 'build', 'release', 'publish', 'packaging'].includes(word)), languageExtensions}
|
|
165
181
|
const classifier = createPathClassifier(repoRoot), classificationCache = new Map()
|
|
166
182
|
const rows = g.nodes.filter((node) => matchesLanguage(node, languageExtensions) && isQueryEligible(node, requestedClasses, classificationCache, classifier)).map((node) => {
|
|
@@ -37,6 +37,7 @@ export async function createTypeScriptLspClient({repoRoot, timeoutMs = 10_000} =
|
|
|
37
37
|
textDocument: {
|
|
38
38
|
definition: {linkSupport: true},
|
|
39
39
|
references: {},
|
|
40
|
+
rename: {},
|
|
40
41
|
publishDiagnostics: {relatedInformation: false},
|
|
41
42
|
},
|
|
42
43
|
},
|
|
@@ -68,6 +69,12 @@ export async function createTypeScriptLspClient({repoRoot, timeoutMs = 10_000} =
|
|
|
68
69
|
references(relPath, position, includeDeclaration = true, referenceTimeoutMs = timeoutMs) {
|
|
69
70
|
return client.references({filePath: relPath, position, includeDeclaration, timeoutMs: referenceTimeoutMs})
|
|
70
71
|
},
|
|
72
|
+
// Generic read-only JSON-RPC passthrough plus URI normalization, so consumers outside
|
|
73
|
+
// the core (weavatrix-refactor) can issue their own read-only LSP requests. The client
|
|
74
|
+
// still refuses workspace/applyEdit, so nothing here can apply an edit.
|
|
75
|
+
request(method, params, options) { return client.request(method, params, options) },
|
|
76
|
+
toUri(relPath) { return client.normalizer.toUri(relPath) },
|
|
77
|
+
fromUri(uri) { return client.normalizer.fromUri(uri) },
|
|
71
78
|
definition(relPath, position) { return client.definition({filePath: relPath, position}) },
|
|
72
79
|
closeDocument(relPath) { return client.closeDocument(relPath) },
|
|
73
80
|
async close(shutdownTimeoutMs = timeoutMs) {
|
|
@@ -76,7 +76,6 @@ export function normalizeOsvAdvisory(record, ecosystem, name) {
|
|
|
76
76
|
kind: String(record.id || '').startsWith('MAL-') ? 'malicious' : 'vuln',
|
|
77
77
|
severity: severityOf(record),
|
|
78
78
|
summary: String(record.summary || record.details || '').slice(0, 300),
|
|
79
|
-
url: `https://osv.dev/vulnerability/${record.id}`,
|
|
80
79
|
modified: record.modified || '',
|
|
81
80
|
aliases: (record.aliases || []).slice(0, 6),
|
|
82
81
|
fixedIn: [...new Set(fixed)].slice(0, 4),
|
|
@@ -111,7 +111,7 @@ export function installScriptSnippet(scripts = {}) {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
// weak URL rules only: URL evidence in a license/prose file or in comment/doc-link context is
|
|
114
|
-
// documentation, not behavior (LICENSE
|
|
114
|
+
// documentation, not behavior (a license-header scheme reference on LICENSE line 5 was the hot FP).
|
|
115
115
|
// Strong rules (miner/shell/exfil-url) are untouched and still scan those same files.
|
|
116
116
|
export function isDocUrlNoise(file, signal) {
|
|
117
117
|
if (!NOISY_URL_KEYS.has(signal?.key)) return false;
|
|
@@ -77,7 +77,7 @@ export const CONTENT_RULES = [
|
|
|
77
77
|
severity: "medium", // raw-IP URLs have some legit uses (local tooling) — escalates via co-occurrence
|
|
78
78
|
nearZeroFp: false,
|
|
79
79
|
pattern: "https?://[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}",
|
|
80
|
-
re: /https?:\/\/(?!127\.|0\.0\.0\.0|10\.|192\.168\.|169\.254\.|172\.(1[6-9]|2\d|3[01])\.)[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/i, // /i: rg sweeps -i;
|
|
80
|
+
re: /https?:\/\/(?!127\.|0\.0\.0\.0|10\.|192\.168\.|169\.254\.|172\.(1[6-9]|2\d|3[01])\.)[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/i, // /i: rg sweeps -i; schemes are case-insensitive (an uppercase scheme prefix is a known evasion)
|
|
81
81
|
what: "hardcoded raw-IP URL (loopback/private/link-local ranges excluded)", // 169.254.169.254 = cloud IMDS (AWS/GCP/Azure SDK metadata) — benign, was a hot FP
|
|
82
82
|
},
|
|
83
83
|
{
|