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.
@@ -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
- function queryConcepts(query) {
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 = requestedLanguages(query)
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
- const concepts = queryConcepts(query)
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 = requestedLanguages(query)
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:5 "http://www.apache.org/licenses/" was the hot FP).
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; URL schemes are case-insensitive (HTTP:// is a known evasion)
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
  {