weavatrix 0.3.8 → 0.3.10
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 +17 -0
- package/docs/releases/v0.3.10.md +76 -0
- package/docs/releases/v0.3.9.md +36 -0
- package/package.json +4 -2
- package/scripts/run-agent-task-benchmark.mjs +0 -1
- package/skill/SKILL.md +13 -6
- package/src/analysis/change-classification.js +9 -19
- package/src/analysis/dead-check.js +8 -6
- package/src/analysis/dead-code-review.js +3 -0
- package/src/analysis/dep-rules.js +1 -1
- package/src/analysis/git-history/collector.js +1 -43
- package/src/analysis/git-history.js +2 -1
- package/src/analysis/git-ref-graph.js +4 -10
- package/src/analysis/hot-path-review.js +1 -1
- package/src/analysis/http-contracts/analysis.js +9 -1
- package/src/analysis/internal-audit/repo-files.js +2 -6
- package/src/analysis/structure/findings.js +21 -1
- package/src/analysis/task-retrieval.js +2 -0
- package/src/build-graph.js +9 -1
- package/src/git-exec.js +67 -0
- package/src/graph/builder/lang-rust.js +36 -0
- package/src/graph/freshness-probe.js +3 -6
- package/src/graph/graph-filter.js +5 -3
- package/src/graph/incremental-refresh.js +1 -1
- package/src/graph/internal-builder.build.js +2 -1
- package/src/graph/internal-builder.langs.js +11 -24
- package/src/mcp/actions/graph-lifecycle.mjs +3 -3
- package/src/mcp/catalog.mjs +2 -2
- package/src/mcp/evidence-snapshot.structure.mjs +5 -2
- package/src/mcp/git-output.mjs +2 -5
- package/src/mcp/graph/context-seeds.mjs +1 -0
- package/src/mcp/graph/context-state.mjs +4 -5
- package/src/mcp/graph/tools-query.mjs +28 -2
- package/src/mcp/health/audit-format.mjs +2 -5
- package/src/mcp/health/endpoints.mjs +34 -5
- package/src/mcp/sync/payload-v2.mjs +1 -0
- package/src/mcp/tools-company.mjs +9 -0
- package/src/mcp/tools-endpoints.mjs +54 -12
- package/src/mcp/tools-graph-hubs.mjs +1 -0
- package/src/mcp/tools-impact-change.mjs +2 -3
- package/src/mcp/tools-impact.mjs +9 -1
- package/src/mcp-rg.mjs +2 -4
- package/src/mcp-source-tools.mjs +4 -5
- package/src/precision/lsp-overlay/build.js +9 -4
- package/src/precision/lsp-overlay/contract.js +1 -1
- package/src/precision/lsp-overlay/target-index.js +8 -3
- package/src/precision/symbol-query.js +2 -1
- package/src/process.js +18 -1
|
@@ -12,6 +12,7 @@ export function tGodNodes(g, {top_n = 10, include_classified = false} = {}, ctx
|
|
|
12
12
|
const classificationByFile = new Map()
|
|
13
13
|
const isNonProduct = (node) => {
|
|
14
14
|
if (include_classified === true) return false
|
|
15
|
+
if (node?.test_surface === true) return true
|
|
15
16
|
const file = sourceFileOf(node)
|
|
16
17
|
if (!file) return false
|
|
17
18
|
if (!classificationByFile.has(file)) classificationByFile.set(file, classifier.explain(file))
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Symbol-aware change_impact implementation, isolated from the other hot impact tools so its
|
|
2
2
|
// classifier/evidence contract can be tested without perturbing get_dependents or graph_diff.
|
|
3
|
-
import {
|
|
4
|
-
import {childProcessEnv} from '../child-env.js'
|
|
3
|
+
import {runGit} from '../git-exec.js'
|
|
5
4
|
import {fileOfId} from '../graph/node-id.js'
|
|
6
5
|
import {classifyChangeImpact} from '../analysis/change-classification.js'
|
|
7
6
|
import {readCoverageForRepo} from '../analysis/coverage-reports.js'
|
|
@@ -12,7 +11,7 @@ import {gitLines} from './git-output.mjs'
|
|
|
12
11
|
import {toolResult} from './tool-result.mjs'
|
|
13
12
|
|
|
14
13
|
function gitValue(repoRoot, args) {
|
|
15
|
-
const result =
|
|
14
|
+
const result = runGit(repoRoot, args)
|
|
16
15
|
if (result.status !== 0) return null
|
|
17
16
|
return String(result.stdout || '').trim() || null
|
|
18
17
|
}
|
package/src/mcp/tools-impact.mjs
CHANGED
|
@@ -87,10 +87,18 @@ export function tGetDependents(g, args = {}, ctx = {}) {
|
|
|
87
87
|
`Semantic precision: EXACT_LSP point query${result.cached ? ' (cache hit)' : ''}; ${count} classified direct reference edge(s).`,
|
|
88
88
|
)
|
|
89
89
|
}
|
|
90
|
+
// Never echo COMPLETE here ('COMPLETE; exact absence was not proven' is a contradiction),
|
|
91
|
+
// but keep NONE/UNAVAILABLE truthful — PARTIAL implies probing actually began.
|
|
92
|
+
const fallbackState = result.overlay?.state === 'NONE' ? 'NONE'
|
|
93
|
+
: result.overlay?.state === 'UNAVAILABLE' ? 'UNAVAILABLE' : 'PARTIAL'
|
|
90
94
|
return getDependentsFromGraph(
|
|
91
95
|
g,
|
|
92
96
|
args,
|
|
93
|
-
|
|
97
|
+
fallbackState === 'NONE'
|
|
98
|
+
? `Semantic precision: NONE; exact references are not available for this language/target, so graph edges are retained (${result.overlay?.reason || 'no eligible JavaScript/TypeScript semantic targets'}).`
|
|
99
|
+
: fallbackState === 'UNAVAILABLE'
|
|
100
|
+
? `Semantic precision: UNAVAILABLE; graph evidence retained (${result.overlay?.reason || 'semantic point query unavailable'}).`
|
|
101
|
+
: `Semantic precision: PARTIAL; exact absence was not proven, so graph edges are retained (${result.overlay?.reason || 'incomplete project coverage'}).`,
|
|
94
102
|
)
|
|
95
103
|
} catch (error) {
|
|
96
104
|
return getDependentsFromGraph(
|
package/src/mcp-rg.mjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { readdirSync, existsSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
|
-
import { spawnSync } from 'node:child_process'
|
|
4
|
-
import { childProcessEnv } from './child-env.js'
|
|
5
3
|
import { createRequire } from 'node:module'
|
|
6
|
-
import
|
|
4
|
+
import { runCommandSync } from './process.js'
|
|
7
5
|
|
|
8
6
|
const rgInInstall = (base) => [
|
|
9
7
|
join(base, 'resources', 'app', 'node_modules', '@vscode', 'ripgrep', 'bin', 'rg.exe'),
|
|
@@ -43,7 +41,7 @@ export function createRgResolver(selfDir) {
|
|
|
43
41
|
} catch { /* optional packaged ripgrep path */ }
|
|
44
42
|
for (const c of editorRgCandidates()) if (existsSync(c)) return (rgPath = c)
|
|
45
43
|
try {
|
|
46
|
-
const probe =
|
|
44
|
+
const probe = runCommandSync(process.platform === 'win32' ? 'where' : 'which', ['rg'], {timeout: 3000})
|
|
47
45
|
const p = probe.status === 0 ? probe.stdout.split(/\r?\n/)[0].trim() : ''
|
|
48
46
|
if (p && existsSync(p)) return (rgPath = p)
|
|
49
47
|
} catch { /* optional PATH probe */ }
|
package/src/mcp-source-tools.mjs
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
|
|
2
|
-
import { spawnSync } from 'node:child_process'
|
|
3
2
|
import { extname, isAbsolute, join, relative, resolve } from 'node:path'
|
|
4
3
|
import { resolveRepoPath } from './repo-path.js'
|
|
5
|
-
import {
|
|
4
|
+
import { runCommandSync } from './process.js'
|
|
6
5
|
import { toolResult } from './mcp/tool-result.mjs'
|
|
7
6
|
|
|
8
7
|
const SEARCH_SKIP = new Set(['.git', 'node_modules', 'dist', 'build', 'out', '.next', 'coverage', 'vendor', '.venv', 'venv', 'env', 'target', '__pycache__', '.idea', '.vscode', '.cache', 'bin', 'obj', 'weavatrix-graphs'])
|
|
@@ -11,7 +10,7 @@ const MAX_SEARCH_FILE_BYTES = 1024 * 1024
|
|
|
11
10
|
const MAX_SOURCE_FILE_BYTES = 2 * 1024 * 1024
|
|
12
11
|
const MAX_SOURCE_CONTEXT_LINES = 1000
|
|
13
12
|
|
|
14
|
-
function rgSearch(repoRoot, resolveRg, query, { isRegex, glob, maxResults }, spawnRg =
|
|
13
|
+
function rgSearch(repoRoot, resolveRg, query, { isRegex, glob, maxResults }, spawnRg = runCommandSync) {
|
|
15
14
|
const rg = resolveRg()
|
|
16
15
|
if (!rg) return null
|
|
17
16
|
// Exclude node_modules explicitly: --hidden + the default gitignore stack is the only thing that
|
|
@@ -24,7 +23,7 @@ function rgSearch(repoRoot, resolveRg, query, { isRegex, glob, maxResults }, spa
|
|
|
24
23
|
// against repository-relative paths on Windows too. Passing an absolute search root makes rg
|
|
25
24
|
// compare the glob with a drive-prefixed path and silently return no matches.
|
|
26
25
|
args.push('--', query, '.')
|
|
27
|
-
const res = spawnRg(rg, args, { cwd: repoRoot, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024, timeout: 15000
|
|
26
|
+
const res = spawnRg(rg, args, { cwd: repoRoot, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024, timeout: 15000 })
|
|
28
27
|
if (res.status !== 0 && res.status !== 1) return null
|
|
29
28
|
const out = []
|
|
30
29
|
for (const line of (res.stdout || '').split(/\r?\n/)) {
|
|
@@ -93,7 +92,7 @@ function nodeGrep(repoRoot, query, { isRegex, glob, maxResults }) {
|
|
|
93
92
|
return out
|
|
94
93
|
}
|
|
95
94
|
|
|
96
|
-
export function searchCode({ repoRoot, resolveRg, spawnRg =
|
|
95
|
+
export function searchCode({ repoRoot, resolveRg, spawnRg = runCommandSync }, { query, is_regex = false, max_results = 40, glob } = {}) {
|
|
97
96
|
if (!query) return 'Provide a "query" string.'
|
|
98
97
|
if (!repoRoot || !existsSync(repoRoot)) return 'Source search unavailable: repo root not provided to this MCP server.'
|
|
99
98
|
const max = Math.max(1, Math.min(200, Number(max_results) || 40))
|
|
@@ -39,7 +39,9 @@ function coverage(session, verifiedEdges = session.links.length) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function completedOverlay(session) {
|
|
42
|
-
|
|
42
|
+
// Coherence guard: COMPLETE must mean every selected target was actually probed.
|
|
43
|
+
const underQueried = session.queried < session.targets.length
|
|
44
|
+
const state = session.errors || session.truncated || session.unclassifiedReferences || underQueried
|
|
43
45
|
? 'PARTIAL' : 'COMPLETE'
|
|
44
46
|
return baseOverlay(session.graph, state, {
|
|
45
47
|
request: session.request,
|
|
@@ -67,7 +69,9 @@ function completedOverlay(session) {
|
|
|
67
69
|
...(session.errors ? {reason: `${session.errors} semantic request(s) failed or were refused`}
|
|
68
70
|
: session.truncated ? {reason: 'semantic precision stopped at a configured safety limit'}
|
|
69
71
|
: session.unclassifiedReferences
|
|
70
|
-
? {reason: 'some exact references could not be classified as runtime or type-only'}
|
|
72
|
+
? {reason: 'some exact references could not be classified as runtime or type-only'}
|
|
73
|
+
: underQueried
|
|
74
|
+
? {reason: 'semantic precision stopped before probing every selected target'} : {}),
|
|
71
75
|
})
|
|
72
76
|
}
|
|
73
77
|
|
|
@@ -185,14 +189,15 @@ export async function buildLspPrecisionOverlay({
|
|
|
185
189
|
}
|
|
186
190
|
if (graphPath) {
|
|
187
191
|
const cached = readPrecisionOverlay(graphPath, graph)
|
|
188
|
-
|
|
192
|
+
// NONE (no eligible targets) is as stable as COMPLETE for an unchanged fingerprint.
|
|
193
|
+
if ((cached?.state === 'COMPLETE' || cached?.state === 'NONE')
|
|
189
194
|
&& precisionOverlayMatches(cached, graph, {request: session.request})
|
|
190
195
|
&& cached.semanticInputFingerprint === session.semanticInputs.fingerprint) return cached
|
|
191
196
|
}
|
|
192
197
|
session.eligible = eligibleTargets(graph, session.boundedMax, targetIds)
|
|
193
198
|
session.targets = session.eligible.targets
|
|
194
199
|
if (!session.targets.length) {
|
|
195
|
-
return persist(session, baseOverlay(graph, '
|
|
200
|
+
return persist(session, baseOverlay(graph, 'NONE', {
|
|
196
201
|
request: session.request,
|
|
197
202
|
semanticInputFingerprint: session.semanticInputs.fingerprint,
|
|
198
203
|
pluginPolicy: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {typeScriptLspContract} from '../typescript-lsp-provider.js'
|
|
2
2
|
|
|
3
|
-
export const PRECISION_OVERLAY_V =
|
|
3
|
+
export const PRECISION_OVERLAY_V = 5
|
|
4
4
|
export const PRECISION_FILE = 'precision.json'
|
|
5
5
|
export const JS_TS_FILE = /\.(?:[cm]?[jt]sx?)$/i
|
|
6
6
|
export const endpoint = (value) => String(value && typeof value === 'object' ? value.id : value)
|
|
@@ -99,10 +99,15 @@ export function eligibleTargets(graph, limit, requestedIds = null) {
|
|
|
99
99
|
const byId = new Map((graph.nodes || []).map((node) => [String(node.id), node]))
|
|
100
100
|
if (Array.isArray(requestedIds) && requestedIds.length) {
|
|
101
101
|
const ids = [...new Set(requestedIds.map(String))]
|
|
102
|
-
const
|
|
102
|
+
const eligible = ids.map((id) => byId.get(id))
|
|
103
103
|
.filter((node) => node?.selection_start && JS_TS_FILE.test(String(node.source_file || '')))
|
|
104
|
-
|
|
105
|
-
return {
|
|
104
|
+
// total counts only eligible targets so coverage fractions stay honest when ids were dropped.
|
|
105
|
+
return {
|
|
106
|
+
targets: eligible.slice(0, limit),
|
|
107
|
+
total: eligible.length,
|
|
108
|
+
droppedRequested: ids.length - eligible.length,
|
|
109
|
+
orphanIds: new Set(),
|
|
110
|
+
}
|
|
106
111
|
}
|
|
107
112
|
const ranked = new Map()
|
|
108
113
|
const inbound = new Set()
|
|
@@ -75,7 +75,8 @@ async function storeEntry(path, entry) {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
function cacheable(overlay) {
|
|
78
|
-
|
|
78
|
+
// NONE is stable for the fingerprint, but only COMPLETE ever proves absence (see reader below).
|
|
79
|
+
if (overlay?.state === 'COMPLETE' || overlay?.state === 'NONE') return true
|
|
79
80
|
return overlay?.state === 'PARTIAL'
|
|
80
81
|
&& overlay?.reason === 'semantic precision stopped at a configured safety limit'
|
|
81
82
|
}
|
package/src/process.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Subprocess runner shared by the search engines and security sweeps.
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
2
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
3
3
|
import { childProcessEnv } from "./child-env.js";
|
|
4
4
|
|
|
5
5
|
// Windows: .cmd/.ps1 shims (npx, etc.) can't be spawned directly by Node — they need a
|
|
@@ -76,3 +76,20 @@ export function runCommand(command, args = [], options = {}) {
|
|
|
76
76
|
child.on("close", (code) => finish(() => (timedOut ? reject(new Error("Command timed out")) : resolve({ stdout, stderr, exitCode: code }))));
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
|
+
|
|
80
|
+
// Synchronous run-and-collect variant for callers that resolve a real binary and read its output in
|
|
81
|
+
// one shot (ripgrep resolution/search, tar extraction). These launch a resolved .exe or bare binary
|
|
82
|
+
// directly, so no shell quoting is needed; the child env is stripped of connector secrets and a
|
|
83
|
+
// bounded timeout/buffer applies. Returns the raw spawnSync result ({status, stdout, stderr, error}).
|
|
84
|
+
export function runCommandSync(command, args = [], options = {}) {
|
|
85
|
+
const spawnOptions = {
|
|
86
|
+
cwd: options.cwd || undefined,
|
|
87
|
+
encoding: options.encoding ?? "utf8",
|
|
88
|
+
env: childProcessEnv(options.env),
|
|
89
|
+
windowsHide: true,
|
|
90
|
+
};
|
|
91
|
+
if (options.timeout != null) spawnOptions.timeout = options.timeout;
|
|
92
|
+
if (options.maxBuffer != null) spawnOptions.maxBuffer = options.maxBuffer;
|
|
93
|
+
if (options.stdio != null) spawnOptions.stdio = options.stdio;
|
|
94
|
+
return spawnSync(command, args, spawnOptions);
|
|
95
|
+
}
|