weavatrix 0.2.6 → 0.2.8

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 CHANGED
@@ -5,7 +5,7 @@
5
5
  Grep sees text. Weavatrix sees structure. It builds a dependency graph of any local repository —
6
6
  files, symbols, and the imports/calls/inheritance connecting them — and serves it to Claude Code,
7
7
  Codex, or any MCP client: change impact, transitive dependents, health audit, clone detection,
8
- coverage mapping. **35 tools available; 32 enabled by the default offline profile. Local-first: with
8
+ coverage mapping. **36 tools available; 33 enabled by the default offline profile. Local-first: with
9
9
  the defaults, no repository data leaves your machine.**
10
10
 
11
11
  - Website: [weavatrix.com](https://weavatrix.com)
@@ -29,6 +29,11 @@ answers grep can't produce:
29
29
  baseline graph without checking it out, then reports the structural delta: new module
30
30
  dependencies, broken or introduced import cycles, and symbols that lost their last caller.
31
31
 
32
+ - *"Is this change actually safe?"* -> `verified_change` accepts the task plus current diff/files and
33
+ returns one proof-carrying `PASS`, `BLOCKED`, or `UNKNOWN`: compact edit contexts, exact-symbol
34
+ impact, bounded call-argument flow, Git graph drift, architecture/duplicate/API ratchets, and
35
+ affected-test evidence.
36
+
32
37
  ## Quick start
33
38
 
34
39
  Requires Node ≥ 18. One command:
@@ -108,7 +113,7 @@ An agent skill with recipes ships in [skill/SKILL.md](skill/SKILL.md) — instal
108
113
 
109
114
  **graph** — `graph_stats`, `get_node`, `get_neighbors`, `query_graph`, `god_nodes`,
110
115
  `shortest_path`, `get_community`, `list_communities`, `module_map`, `get_dependents`,
111
- `change_impact`, `git_history`, `graph_diff`, `get_architecture_contract`,
116
+ `change_impact`, `verified_change`, `git_history`, `graph_diff`, `get_architecture_contract`,
112
117
  `prepare_change`. Runtime dependencies, TypeScript type-only coupling and language
113
118
  compile-only edges (Rust module/use, Java imports) are reported separately where that distinction
114
119
  changes the result. TypeScript type-space and value-space declarations keep distinct identities;
@@ -120,7 +125,7 @@ confirmed by its bundled `typescript-language-server` + TypeScript runtime to `E
120
125
  `CONFLICT` means evidence disagrees. `graph_stats` reports the provenance breakdown and the semantic
121
126
  provider's `COMPLETE`, `PARTIAL`, `UNAVAILABLE`, or `OFF` state; `OFF` means precision was explicitly
122
127
  disabled and only static evidence is active. Java and Rust language-server providers are not bundled
123
- in 0.2.6: their edges never become `EXACT_LSP`, even when a mixed repository reports a complete
128
+ in 0.2.7: their edges never become `EXACT_LSP`, even when a mixed repository reports a complete
124
129
  TypeScript/JavaScript overlay.
125
130
 
126
131
  The bounded JS/TS provider is enabled by default for new graphs. Set `WEAVATRIX_PRECISION=off`
@@ -135,8 +140,9 @@ bounded TS/JS reference query, logical containers, impact and source excerpts),
135
140
  Express/Fastify/Nest/Flask/FastAPI/Go mux/Rust axum and actix-web …)
136
141
 
137
142
  **health** — `find_dead_code` (bounded review queue for statically unreferenced and test-only files,
138
- functions, methods, and symbols, with confidence/evidence and explicit public/framework/dynamic caveats),
139
- `run_audit` (unused files/exports/dependencies with an explicit manifest-check summary,
143
+ functions, methods, and symbols, with evidence tiers, completed/remaining verification and explicit
144
+ public/framework/dynamic caveats),
145
+ `run_audit` (unused files/exports/dependencies with per-finding manifest/indexed-source/script/config verification,
140
146
  missing npm/Go/Python deps, runtime
141
147
  cycles, type-only/compile-only coupling, orphans, boundary rules, offline OSV vulnerabilities + typosquat +
142
148
  lockfile drift; accepts an immutable `base_ref`, `changed_files`, and `debt: new|existing|all` for
@@ -149,7 +155,9 @@ with separate graph/test risk), `verify_architecture`,
149
155
  `hot_path_review` ranks production symbols using parser-derived local time, memory, cyclomatic and
150
156
  call-site facts plus exact inside-loop allocation, copy, scan, sort and recursion evidence. Graph
151
157
  fan-in/fan-out and measured coverage (or explicitly labelled static test reachability) remain
152
- separate from local syntax cost. The ranking is not profiler data or interprocedural Big-O.
158
+ separate from local syntax cost. Its focused default uses `min_score:85` plus a narrow strong-local
159
+ fallback; pass `min_score:0` only when the full diagnostic queue is wanted. The ranking is not
160
+ profiler data or interprocedural Big-O.
153
161
 
154
162
  **build** — `rebuild_graph` (reports the structural delta, keeps the prior state as
155
163
  `graph.prev.json`)
@@ -160,7 +168,8 @@ use `kinds:["method"]` or a `path` prefix to narrow it. `min_confidence:"low"` e
160
168
  public APIs and framework/dynamic/reflection-sensitive candidates with their warnings. For repository
161
169
  maintenance or branch debt, pair it with `run_audit category=unused debt=all` (or add an immutable
162
170
  `base_ref` with `debt=new`) to cover unused files, exports and dependencies. Neither tool authorizes
163
- deletion: inspect `read_source`, `get_dependents`, exact search, manifests/configuration and tests first.
171
+ deletion: `STRONG_STATIC_EVIDENCE` means an exact zero-reference result, not permission to remove;
172
+ follow each candidate's `remainingChecks`, then inspect source/dependents/configuration and run tests.
164
173
 
165
174
  `graph_diff` accepts `base_ref` (`HEAD~1`, `main`, `origin/main`, or another local Git ref) for a
166
175
  fresh baseline comparison. Without it, the tool compares against `graph.prev.json` saved by the last
@@ -172,6 +181,14 @@ manifest, alias, ignore/config, non-JS/TS, or unsafe merge cases deliberately fa
172
181
  rebuild. The result says whether freshness was `none`, `incremental`, or `full`. Graph artifacts stay
173
182
  in the per-user cache and never need to be committed to Git.
174
183
 
184
+ `verified_change` is read-only by default. It may run only explicitly requested `package.json`
185
+ scripts whose names are allowlisted as test/check/verify scripts, and only when both
186
+ `run_tests:true` and `WEAVATRIX_ALLOW_TEST_RUNS=1` are present. It never accepts an arbitrary shell
187
+ command. Cross-repository API proof is used only when the active profile includes `crossrepo`;
188
+ without a configured architecture contract or complete evidence, the result is `UNKNOWN`, not a
189
+ cosmetic pass. Its data-flow section maps bounded JS/TS call arguments to callee parameters; it is
190
+ not CFG, value-propagation, or taint analysis.
191
+
175
192
  **retarget** *(included in `offline`; absent from `pinned`; every switch is an explicit local call)* —
176
193
  `open_repo`, `list_known_repos`; changes the active repository boundary
177
194
 
@@ -182,8 +199,11 @@ in the per-user cache and never need to be committed to Git.
182
199
  entry points. Resolved explicit seeds are exclusive by default; set `augment_seeds:true` only when
183
200
  question-derived seeds are also wanted. Broad bootstrap/tool-execution/routing questions now rank
184
201
  conventional executables and graph-declared production entry points ahead of site, documentation, benchmark
185
- and fixture matches. Broad ranking remains orientation evidence; use `seed_files` when the intended
186
- entry point is already known.
202
+ and fixture matches. Production-first classification also applies during traversal, so unrelated
203
+ tests/generated/docs/benchmarks do not leak back from a production seed; name a class in the
204
+ question or set `include_classified:true` to include it. Unreferenced unmatched constant/field leaves
205
+ are hidden unless `include_low_signal:true`. Broad ranking remains orientation evidence; use
206
+ `seed_files` when the intended entry point is already known.
187
207
 
188
208
  **advisories** *(network, explicit opt-in)* — `refresh_advisories`
189
209
 
@@ -196,6 +216,52 @@ disclosed instead of silently guessed; and the server **hot-reloads its watched
196
216
  modules and catalog** when those files change — other MCP helpers and analysis engines require a
197
217
  reconnect.
198
218
 
219
+ ### 0.2.8 trust and precision patch
220
+
221
+ - Dead-code review no longer labels a declaration `test-only` when it has a same-file production
222
+ use. Revision-bound positive evidence from an exact `inspect_symbol` point query also removes that
223
+ declaration from later dead-code candidates instead of remaining isolated in its cache.
224
+ - Python dependency checks discover nested `requirements*.txt`, `requirements/*.txt`,
225
+ `pyproject.toml`, and `Pipfile` manifests and assign imports to their nearest manifest scope.
226
+ A root `test.py` is classified consistently as test code.
227
+ - Endpoint extraction masks commented-out routes without shifting source offsets and rejects
228
+ primitive path-to-name maps such as `{'/.codex': 'claude'}` while retaining real route tables.
229
+ - Rust symbols now expose concrete kinds, owner types, visibility/export state, selection ranges and
230
+ structural owner/member edges. This is parser evidence; Rust semantic/LSP precision remains
231
+ explicitly unavailable.
232
+ - Natural-language retrieval honors explicit Rust, Python, TypeScript, JavaScript, Go, Java, and C#
233
+ intent in mixed repositories. Compact `verified_change` text now includes decisive exact reference
234
+ counts and bounded inbound caller names for every selected edit symbol.
235
+
236
+ Full patch notes: [docs/releases/v0.2.8.md](docs/releases/v0.2.8.md).
237
+
238
+ ### 0.2.7 verified-change workflow
239
+
240
+ - New `verified_change` composes task retrieval, exact symbol context, change impact, immutable Git
241
+ graph comparison, architecture/duplicate/API ratchets, and targeted-test evidence behind one
242
+ `PASS` / `BLOCKED` / `UNKNOWN` contract.
243
+ - Intent-expanded graph retrieval is combined with exact changed-symbol seeds. A bounded JS/TS
244
+ interprocedural layer records call arguments and their callee parameters without claiming CFG or
245
+ taint completeness.
246
+ - Test execution is a double opt-in and limited to existing test/check/verify package scripts;
247
+ default operation remains read-only.
248
+ - Malware scanning now sweeps installed npm package roots instead of package-manager caches or
249
+ hosted release snapshots. Static heuristic findings are capped at `high`, expose unverified
250
+ execution/origin/lockfile/exposure state, and no longer prescribe secret rotation unless execution
251
+ or credential exposure has actually been confirmed.
252
+ - `query_graph` and `verified_change` task retrieval now enforce production-first classification
253
+ through traversal/selection while retaining exact changed or pinned non-product symbols; query
254
+ output also suppresses unmatched constant/field leaves. `hot_path_review` defaults to a focused
255
+ 85-point queue, while `min_score:0` remains the explicit full-diagnostic mode.
256
+ - Dead-code candidates now carry evidence tiers and remaining checks; dependency findings carry
257
+ per-finding manifest plus indexed-source/script/config verification without authorizing removal.
258
+ - `npm run benchmark:agent` reports routing success, false positives, token estimate and latency.
259
+ Codebase Memory and Serena results stay explicitly `MISSING` until independently collected data is
260
+ supplied under the [blind agent-change protocol](docs/agent-task-benchmark.md);
261
+ `benchmark:agent:release` fails closed without same-task change success, FP, token, and time results.
262
+
263
+ Full patch notes: [docs/releases/v0.2.7.md](docs/releases/v0.2.7.md).
264
+
199
265
  ### 0.2.6 compact-context and TypeScript identity patch
200
266
 
201
267
  - New `context_bundle` turns a symbol into a bounded workset: definition, grouped inbound/outbound
@@ -466,7 +532,7 @@ vulnerability findings. This is where each capability comes from and how it is c
466
532
  | Capability alert | Why it exists | Activation and boundary |
467
533
  |---|---|---|
468
534
  | Network access | `refresh_advisories` sends pinned package names and versions to OSV; `pull_architecture_contract` sends an opaque repository UUID and receives an owner-approved contract; `sync_graph` sends a normalized repository label plus an allowlisted graph/evidence payload. Evidence is derived locally from source and manifests, but source bodies, snippets, absolute paths, environment values, credentials and Git remotes are excluded | `offline` and `pinned` expose no network tools. `osv` exposes only advisory refresh. `hosted`/`full` expose all three; every request still requires an explicit tool call, and hosted calls require `WEAVATRIX_SYNC_URL` |
469
- | Shell access | Local `git` powers staleness/change impact; `rg` accelerates search; the bundled TLS/tsserver process supplies bounded JS/TS semantic evidence; timed-out Windows child trees may be terminated | Used only by the corresponding local operation. The semantic provider is package-pinned, disables automatic type acquisition, and never invokes a repository binary, script, installer or `npx` |
535
+ | Shell access | Local `git` powers staleness/change impact; `rg` accelerates search; the bundled TLS/tsserver process supplies bounded JS/TS semantic evidence; timed-out Windows child trees may be terminated; `verified_change` can optionally run an existing package test/check/verify script | The semantic provider never invokes repository code. Test execution separately requires `run_tests:true` plus `WEAVATRIX_ALLOW_TEST_RUNS=1`, rejects arbitrary commands and shell-sensitive arguments, and should be enabled only for trusted repository scripts |
470
536
  | Debug / dynamic loading | Cache-busted `import()` hot-reloads watched MCP tool entry modules; `createRequire` loads package metadata and parser dependencies | Loads files from the installed package; no `eval` |
471
537
  | Environment access | Reads `WEAVATRIX_*` configuration; ordinary local helpers inherit a credential-stripped environment, while TLS/tsserver receives only allowlisted OS/temp/locale values and a constrained executable path | `WEAVATRIX_SYNC_TOKEN` is removed from every child-process and worker environment. TLS/tsserver also receives no registry/proxy/cloud credentials or `NODE_OPTIONS` |
472
538
  | Filesystem access | Reads the active repository, graph, lockfiles and coverage reports; writes derived graphs and advisory/architecture caches | Realpath containment blocks traversal and symlink/junction escapes. The `pinned` profile removes `open_repo`; the default `offline` profile permits only explicit local switching. The optional malware dependency scan may inspect installed dependency caches such as GOPATH |
package/SECURITY.md CHANGED
@@ -28,6 +28,11 @@ dependency caches such as GOPATH. `offline` permits repository switching only th
28
28
  local `open_repo` call; select `pinned` to remove that tool, the global repository listing, and
29
29
  cross-repository API tracing, holding a hard startup-repository boundary.
30
30
 
31
+ Installed-package malware pattern matching is static heuristic evidence. It cannot confirm execution,
32
+ package compromise, or credential exposure and therefore cannot emit `critical`; heuristic severity
33
+ is capped at `high` with explicit `NOT_VERIFIED` runtime/origin/lockfile/exposure fields. Independently
34
+ confirmed malicious-package advisories remain separate vulnerability evidence and may be critical.
35
+
31
36
  The JS/TS precision overlay is enabled by default for new graphs and runs the package-pinned `typescript-language-server` and
32
37
  TypeScript runtime as local child processes. It never resolves a repository executable, invokes
33
38
  `npx`, runs package scripts, or installs dependencies. Automatic type acquisition is disabled, and
@@ -46,6 +51,15 @@ bounded graph-work drain, and closes or tree-terminates TLS/tsserver before exit
46
51
  Set `WEAVATRIX_PRECISION=off` before server startup (or select `off` in the MCPB semantic-precision
47
52
  setting) to keep new graphs parser-only from their first build.
48
53
 
54
+ `verified_change` is read-only by default. Its optional targeted-test step can execute only an
55
+ existing `package.json` script whose name matches the bounded test/check/verify allowlist. The call
56
+ must set `run_tests:true` and the server operator must separately set
57
+ `WEAVATRIX_ALLOW_TEST_RUNS=1`; otherwise no repository script runs. The tool accepts no command or
58
+ shell string, caps scripts/arguments/time, rejects shell-sensitive argument characters, and launches
59
+ the selected package-manager script with the credential-stripped child environment. Repository test
60
+ scripts are repository-controlled code and may have arbitrary side effects, so enable this flag only
61
+ for repositories and scripts you trust.
62
+
49
63
  Network capabilities are split by purpose. `osv` adds only `refresh_advisories`, which sends pinned
50
64
  package names and versions to OSV.dev when called. `hosted` / `full` also expose `sync_graph` and
51
65
  `pull_architecture_contract`; both require a user-configured endpoint, and contract pull requires
@@ -0,0 +1,58 @@
1
+ # Independent agent-change benchmark
2
+
3
+ The 0.2.7 release includes two deliberately separate measurements.
4
+
5
+ `npm run benchmark:agent` is a deterministic local routing microbenchmark. It measures whether the
6
+ task retriever selects the expected symbol, plus false-positive selections, estimated output tokens,
7
+ and local latency. It is a regression test, not evidence that an autonomous code change succeeded.
8
+
9
+ The release comparison is an independently executed, blind agent-change benchmark. The evaluator
10
+ must run the same pinned tasks and repository commits three times: with Weavatrix, with Codebase
11
+ Memory, and with Serena. The product author must not adjudicate or repair a run while it is active.
12
+
13
+ ## Required task protocol
14
+
15
+ Each task must provide a repository commit, natural-language request, allowed commands, timeout, and
16
+ machine-verifiable acceptance tests. Keep task prompts and acceptance tests identical across systems;
17
+ randomize system order. Record the entire agent input/output token count and wall-clock duration.
18
+
19
+ A run is successful only when the requested behavior is implemented, all acceptance tests pass, and
20
+ the final diff stays inside the task scope. A false positive is an evidenced warning, impacted symbol,
21
+ or required edit that an evaluator confirms is unrelated to the task. Record a count, including zero;
22
+ do not silently discard noisy output.
23
+
24
+ Use a mix of at least:
25
+
26
+ - behavior changes with indirect callers;
27
+ - API route/client changes;
28
+ - refactors with a duplicate or architecture regression trap;
29
+ - changes whose nearest relevant test is not in the edited file;
30
+ - one negative task where the correct action is to refuse or return unknown.
31
+
32
+ ## Result contract
33
+
34
+ Save the blind evaluator output as JSON:
35
+
36
+ ```json
37
+ {
38
+ "schemaVersion": "weavatrix.agent-change-results.v1",
39
+ "evaluator": "independent organization or person",
40
+ "systems": {
41
+ "weavatrix": {"runs": [{"taskId": "change-auth", "success": true, "falsePositives": 0, "tokens": 1234, "durationMs": 4567}]},
42
+ "codebase-memory": {"runs": [{"taskId": "change-auth", "success": true, "falsePositives": 1, "tokens": 1400, "durationMs": 5000}]},
43
+ "serena": {"runs": [{"taskId": "change-auth", "success": false, "falsePositives": 2, "tokens": 1600, "durationMs": 7000}]}
44
+ }
45
+ }
46
+ ```
47
+
48
+ Every system must contain the same unique task IDs. Every run must report success, false positives,
49
+ tokens, and duration. Then run:
50
+
51
+ ```sh
52
+ npm run benchmark:agent -- --independent-results path/to/results.json
53
+ npm run benchmark:agent:release -- --independent-results path/to/results.json
54
+ ```
55
+
56
+ The first command reports change-success rate, false positives per task, median tokens, and median
57
+ duration for all three systems. The release command fails unless the independent comparison is
58
+ complete and valid. Missing or malformed competitor data is `INCOMPLETE`, never a green comparison.
@@ -0,0 +1,82 @@
1
+ # Weavatrix 0.2.8
2
+
3
+ ## Trust before surface area
4
+
5
+ 0.2.8 is a correctness release. It does not add another MCP tool. It fixes false positives and
6
+ evidence hand-off failures found while running 0.2.7 against current JavaScript, TypeScript, Python,
7
+ and Rust repositories.
8
+
9
+ ## Dead-code liveness
10
+
11
+ - A production declaration is no longer classified as `test-only` merely because the same identifier
12
+ appears in tests. Same-file occurrences beyond the indexed declaration count as conservative
13
+ production-use evidence.
14
+ - Exact positive point-query results are now revision/config fingerprint checked and consumed by
15
+ `find_dead_code`. If `inspect_symbol` found a reference, the symbol cannot remain in the dead queue.
16
+ - Complete exact zero-reference point results can still raise internal candidates to strong static
17
+ evidence. Partial, stale, malformed, or configuration-drifted cache entries fail closed.
18
+
19
+ These remain review candidates. Static absence never authorizes automatic deletion.
20
+
21
+ ## Python manifest ownership
22
+
23
+ Python dependency collection now discovers nested `requirements*.txt`, `requirements/*.txt`,
24
+ `pyproject.toml`, and `Pipfile` manifests. Each import is judged against its nearest ancestor Python
25
+ manifest. A nested service therefore sees its own declared dependencies, while unrelated sibling code
26
+ cannot inherit them accidentally.
27
+
28
+ The conventional root filename `test.py` is now classified as test code by both path classification
29
+ and dependency severity logic.
30
+
31
+ ## Endpoint false-positive guards
32
+
33
+ Endpoint extraction masks line and block comments while preserving source offsets. Commented-out
34
+ Express/Hono/Go/Rust/Java-style routes no longer enter the inventory.
35
+
36
+ Direct object routes also require an executable handler value. Primitive path maps such as
37
+ `{'/.codex': 'claude'}` are configuration data, not `ANY /.codex` endpoints.
38
+
39
+ ## Rust graph metadata
40
+
41
+ Rust functions, methods, structs, enums, traits, types, modules, constants, statics, macros, and unions
42
+ now carry concrete `symbol_kind` values. Impl/trait functions retain `member_of`, visibility, selection
43
+ ranges and structural owner/member edges; public module declarations retain exported state.
44
+
45
+ This improves graph navigation and dead-code classification but remains tree-sitter parser evidence.
46
+ 0.2.8 does not bundle rust-analyzer and does not claim exact Rust references.
47
+
48
+ ## Mixed-language retrieval and compact proof
49
+
50
+ `query_graph` and workflows that reuse its seed ranking constrain explicit Rust, Python, TypeScript,
51
+ JavaScript, Go, Java, or C# questions to matching source extensions. Multiple explicitly named
52
+ languages form a union; exact `seed_files` continue to be the strongest pinning mechanism.
53
+
54
+ Compact `verified_change` output now lists each selected symbol's exact/bounded reference occurrence
55
+ count, referenced-file count, inbound-container count, and up to three inbound names. The short form
56
+ therefore cannot hide that a supposedly unused symbol has a known caller.
57
+
58
+ ## Scope and remaining competitor gaps
59
+
60
+ The local engine still has no vector embedding search, runtime trace ingestion, clone-to-ADR layer,
61
+ Python/Rust/Java LSP integration, whole-program CFG, value propagation, or taint engine. Those are
62
+ separate precision projects, not claims attached to this patch release. Independent end-to-end
63
+ competitor scores also remain missing until collected under the blind benchmark protocol.
64
+
65
+ ## Verification
66
+
67
+ - Full Node test suite: 487 tests, 484 passed, 3 platform skips, 0 failed.
68
+ - Deterministic language/cross-repository/lifecycle benchmark: `PASS`.
69
+ - Real pinned corpus: 6 of 6 repositories `PASS` across TypeScript, JavaScript, Python, Go, Java,
70
+ and Rust.
71
+ - Agent routing microbenchmark: 3 of 3 expected symbols, 0 false positives. Independent competitor
72
+ comparison remains `INCOMPLETE` because no blind same-task result was supplied.
73
+ - npm package dry-run succeeds for `weavatrix@0.2.8`; the portable MCPB validates, initializes its
74
+ staged TypeScript precision provider, passes the repository-plugin execution guard, and packs.
75
+ - Direct dogfood checks confirm the original regressions: local `USER_HEADER` is not dead/test-only;
76
+ nested `autobot/requirements.txt` covers `slack_bolt`; commented `DELETE /:id` and primitive
77
+ `ANY /.codex` are absent; explicit Rust retrieval returns only `.rs` seeds.
78
+
79
+ ## Tool catalog
80
+
81
+ 0.2.8 exposes the same 36 MCP tools as 0.2.7. The default offline profile exposes 33; pinned exposes
82
+ 30. Network tools remain absent from offline and pinned profiles.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weavatrix",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "type": "module",
5
5
  "description": "Weavatrix — code graph & blast-radius MCP server for AI coding agents: change impact, transitive dependents, health audit, duplicate detection, and coverage mapping over any local repository.",
6
6
  "author": "Sergii Ziborov <sergii.ziborov@gmail.com>",
@@ -24,6 +24,9 @@
24
24
  "bin",
25
25
  "src",
26
26
  "skill",
27
+ "scripts/run-agent-task-benchmark.mjs",
28
+ "docs/agent-task-benchmark.md",
29
+ "docs/releases/v0.2.8.md",
27
30
  "README.md",
28
31
  "SECURITY.md",
29
32
  "LICENSE"
@@ -34,6 +37,8 @@
34
37
  "benchmark:quick": "node scripts/run-benchmark.mjs --quick",
35
38
  "benchmark:real": "node scripts/run-real-benchmark.mjs",
36
39
  "benchmark:real:release": "node scripts/run-real-benchmark.mjs --require-all",
40
+ "benchmark:agent": "node scripts/run-agent-task-benchmark.mjs",
41
+ "benchmark:agent:release": "node scripts/run-agent-task-benchmark.mjs --require-independent",
37
42
  "build:mcpb": "node scripts/build-mcpb.mjs",
38
43
  "verify:release": "node scripts/verify-release.mjs"
39
44
  },
@@ -0,0 +1,119 @@
1
+ import {readFileSync} from 'node:fs'
2
+ import {pathToFileURL} from 'node:url'
3
+ import {performance} from 'node:perf_hooks'
4
+ import {retrieveTaskContext} from '../src/analysis/task-retrieval.js'
5
+
6
+ function graphFixture() {
7
+ const nodes = [
8
+ {id: 'src/auth/session.js#validateSession@10', label: 'validateSession()', source_file: 'src/auth/session.js', symbol_kind: 'function'},
9
+ {id: 'src/http/router.js#registerRoutes@20', label: 'registerRoutes()', source_file: 'src/http/router.js', symbol_kind: 'function'},
10
+ {id: 'src/cache/store.js#readCache@5', label: 'readCache()', source_file: 'src/cache/store.js', symbol_kind: 'function'},
11
+ {id: 'src/auth/session.test.js#sessionTest@3', label: 'sessionTest()', source_file: 'src/auth/session.test.js', symbol_kind: 'function'},
12
+ ]
13
+ return {nodes, byId: new Map(nodes.map((node) => [node.id, node])), out: new Map(), inn: new Map()}
14
+ }
15
+
16
+ const CASES = [
17
+ {task: 'validate authentication session', expected: 'src/auth/session.js#validateSession@10'},
18
+ {task: 'register HTTP API routes', expected: 'src/http/router.js#registerRoutes@20'},
19
+ {task: 'read values from cache store', expected: 'src/cache/store.js#readCache@5'},
20
+ ]
21
+
22
+ function competitorTemplate(name) {
23
+ return {name, status: 'MISSING', reason: 'supply same-task blind evaluator output with --independent-results <json>'}
24
+ }
25
+
26
+ function readCompetitors(path) {
27
+ if (!path) return {}
28
+ const parsed = JSON.parse(readFileSync(path, 'utf8'))
29
+ return parsed?.competitors && typeof parsed.competitors === 'object' ? parsed.competitors : {}
30
+ }
31
+
32
+ const SYSTEMS = ['weavatrix', 'codebase-memory', 'serena']
33
+ const median = (values) => {
34
+ const sorted = values.slice().sort((left, right) => left - right)
35
+ const middle = Math.floor(sorted.length / 2)
36
+ return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2
37
+ }
38
+
39
+ function independentComparison(path) {
40
+ if (!path) return {status: 'MISSING', reason: 'supply blind evaluator output with --independent-results <json>'}
41
+ try {
42
+ const input = JSON.parse(readFileSync(path, 'utf8'))
43
+ if (input?.schemaVersion !== 'weavatrix.agent-change-results.v1') throw new Error('unsupported schemaVersion')
44
+ const taskSets = []
45
+ const metrics = {}
46
+ for (const name of SYSTEMS) {
47
+ const runs = input.systems?.[name]?.runs
48
+ if (!Array.isArray(runs) || !runs.length) throw new Error(`${name} has no runs`)
49
+ const ids = runs.map((run) => String(run.taskId || '')).sort()
50
+ if (ids.some((id) => !id) || new Set(ids).size !== ids.length) throw new Error(`${name} task IDs are missing or duplicated`)
51
+ for (const run of runs) {
52
+ if (typeof run.success !== 'boolean' || !Number.isFinite(run.falsePositives) || !Number.isFinite(run.tokens) || !Number.isFinite(run.durationMs)) {
53
+ throw new Error(`${name}/${run.taskId || '?'} has incomplete metrics`)
54
+ }
55
+ }
56
+ taskSets.push(ids.join('\0'))
57
+ metrics[name] = {
58
+ tasks: runs.length, changeSuccessRate: runs.filter((run) => run.success).length / runs.length,
59
+ falsePositiveRate: runs.reduce((sum, run) => sum + Math.max(0, run.falsePositives), 0) / runs.length,
60
+ medianTokens: median(runs.map((run) => run.tokens)), medianDurationMs: median(runs.map((run) => run.durationMs)),
61
+ }
62
+ }
63
+ if (new Set(taskSets).size !== 1) throw new Error('systems were not evaluated on the same task IDs')
64
+ return {status: 'COMPLETE', evaluator: String(input.evaluator || 'unspecified'), metrics}
65
+ } catch (error) {
66
+ return {status: 'INVALID', reason: error instanceof Error ? error.message : String(error)}
67
+ }
68
+ }
69
+
70
+ export function runAgentTaskBenchmark({competitorResults, independentResults} = {}) {
71
+ const graph = graphFixture()
72
+ const started = performance.now()
73
+ const cases = CASES.map((item) => {
74
+ const semanticSeeds = graph.nodes.filter((node) => item.task.toLowerCase().split(/\W+/).some((word) => word.length > 3 && String(node.id).toLowerCase().includes(word)))
75
+ const result = retrieveTaskContext(graph, {task: item.task, semanticSeeds, maxSymbols: 2})
76
+ const selected = result.selected.map((entry) => entry.id)
77
+ return {task: item.task, expected: item.expected, selected, success: selected[0] === item.expected, falsePositives: selected.filter((id) => id !== item.expected).length}
78
+ })
79
+ const durationMs = performance.now() - started
80
+ const output = JSON.stringify(cases)
81
+ const imported = readCompetitors(competitorResults)
82
+ const independent = independentComparison(independentResults)
83
+ const competitors = independent.status === 'COMPLETE' ? {
84
+ 'codebase-memory': {name: 'codebase-memory', status: 'COMPLETE', metrics: independent.metrics['codebase-memory']},
85
+ serena: {name: 'serena', status: 'COMPLETE', metrics: independent.metrics.serena},
86
+ } : {
87
+ 'codebase-memory': imported['codebase-memory'] || competitorTemplate('codebase-memory'),
88
+ serena: imported.serena || competitorTemplate('serena'),
89
+ }
90
+ return {
91
+ schemaVersion: 'weavatrix.agent-task-benchmark.v1',
92
+ scope: 'deterministic task-to-symbol routing microbenchmark; not an end-to-end autonomous change benchmark',
93
+ weavatrix: {
94
+ status: cases.every((item) => item.success) ? 'PASS' : 'FAIL', cases,
95
+ metrics: {
96
+ taskSuccessRate: cases.filter((item) => item.success).length / cases.length,
97
+ falsePositiveRate: cases.reduce((sum, item) => sum + item.falsePositives, 0) / Math.max(1, cases.reduce((sum, item) => sum + item.selected.length, 0)),
98
+ estimatedOutputTokens: Math.ceil(Buffer.byteLength(output) / 4), durationMs: Number(durationMs.toFixed(3)),
99
+ },
100
+ },
101
+ competitors, independentComparison: independent,
102
+ comparisonStatus: independent.status === 'COMPLETE' ? 'COMPLETE' : 'INCOMPLETE',
103
+ }
104
+ }
105
+
106
+ function argument(name) {
107
+ const index = process.argv.indexOf(name)
108
+ return index >= 0 ? process.argv[index + 1] : null
109
+ }
110
+
111
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
112
+ const report = runAgentTaskBenchmark({
113
+ competitorResults: argument('--competitor-results'),
114
+ independentResults: argument('--independent-results'),
115
+ })
116
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`)
117
+ if ((process.argv.includes('--require-independent') || process.argv.includes('--require-competitors')) && report.comparisonStatus !== 'COMPLETE') process.exitCode = 1
118
+ if (report.weavatrix.status !== 'PASS') process.exitCode = 1
119
+ }
package/skill/SKILL.md CHANGED
@@ -38,6 +38,10 @@ Start from the task, not from the complete tool list:
38
38
  - **Review a branch/diff**: `change_impact` for changed-file blast radius; add
39
39
  `graph_diff base_ref=<merge-base>` for structural drift, then drill into exact candidates with
40
40
  `get_dependents` and `read_source`.
41
+ - **Plan or verify a serious change**: call `verified_change phase=plan` with the natural-language
42
+ task and current diff/files. After editing, call it again with `phase=verify` and an immutable
43
+ `base_ref`. Treat `UNKNOWN` as missing proof, not success; inspect its blockers and unknowns instead
44
+ of manually orchestrating the lower-level graph, context, impact, ratchet, and test tools.
41
45
  - **Trace an API across repositories**: `list_known_repos` -> `trace_api_contract` with an explicit
42
46
  backend and client list; inspect each `graphReconciliation.buildMode` before using the verdict.
43
47
  - **Inspect exact symbol references**: start with `graph_stats`, then call `context_bundle` with an
@@ -47,7 +51,7 @@ Start from the task, not from the complete tool list:
47
51
  graph impact and `find_dead_code` for bounded zero-reference
48
52
  candidates. Treat `PARTIAL`, `UNAVAILABLE`, `OFF`, or zero exact edges as incomplete evidence, not
49
53
  a compiler-exact result; `OFF` means the caller explicitly selected static-only mode. Java and Rust
50
- providers are not bundled in 0.2.6, so their edges never become `EXACT_LSP` even when a mixed
54
+ providers are not bundled in 0.2.7, so their edges never become `EXACT_LSP` even when a mixed
51
55
  repository has a complete TypeScript/JavaScript overlay.
52
56
  - **Explore an architectural question**: use broad `query_graph` when entry points are unknown; its
53
57
  bootstrap/tool-execution ranking prefers production executables over docs, sites and fixtures.
@@ -87,16 +91,20 @@ Start from the task, not from the complete tool list:
87
91
  `.weavatrix.json` `classify.product`, for example `{"classify":{"product":["benchmarks/core/**"]}}`.
88
92
  - **Architectural queries**: broad bootstrap/tool-execution/routing questions rank conventional
89
93
  production and graph-declared entry points ahead of classified docs, sites, benchmarks and
90
- fixtures. Inspect the returned seeds before trusting the traversal. Pass exact repository-relative
91
- `seed_files` to `query_graph` when the intended entry points are already known.
94
+ fixtures. Production-first classification also applies during traversal. A class term in the
95
+ question enables only that class; `include_classified=true` enables all classified paths. Exact
96
+ non-product `seed_files` remain pinned. Unmatched unreferenced constant/field leaves are suppressed
97
+ unless `include_low_signal=true`. Inspect the returned seeds/policy before trusting the traversal.
92
98
  - **Coverage**: `coverage_map` reads an existing report. `unavailable` means no supported report was
93
99
  found, not 0% coverage; do not rank testing risk from that state.
94
100
  - **Local hot paths**: `hot_path_review` ranks parser-derived local syntax cost and reports graph
95
101
  fan-in/fan-out plus test evidence separately. `actualCoverage: NOT_AVAILABLE` is not zero coverage,
96
- and the score is not profiler data or an interprocedural Big-O proof. Inspect its line evidence and
97
- measure runtime before scheduling a performance rewrite.
102
+ and the score is not profiler data or an interprocedural Big-O proof. The default queue uses
103
+ `min_score=85` plus a narrow strong-local fallback; use `min_score=0` only for full diagnostics.
104
+ Inspect its line evidence and measure runtime before scheduling a performance rewrite.
98
105
  - **Audit completeness**: read `dependencyReport.status` plus its unused/missing counts, then
99
- `checks.osv.status` and `checks.malware.status`. A dependency result from a partial graph is not a
106
+ each npm finding's `verification` (manifest, indexed source, scripts/config, unresolved dynamic
107
+ usage), then `checks.osv.status` and `checks.malware.status`. A dependency result from a partial graph is not a
100
108
  repository-wide clean zero. For OSV, `OK` is
101
109
  complete for the recorded dependency fingerprint; `PARTIAL` is incomplete or stale,
102
110
  `NOT_CHECKED` has no repository-specific result, and `ERROR` means the local check failed. Treat
@@ -121,8 +129,18 @@ Start from the task, not from the complete tool list:
121
129
  `offline`, absent from `pinned`, and available in a custom registration only when `retarget` is
122
130
  named. Concurrent non-mutating calls retain the graph/root snapshot with which they started.
123
131
 
132
+ - **Test execution is separately authorized**: `verified_change` only executes named
133
+ `package.json` test/check/verify scripts when the call has `run_tests:true` and the server was
134
+ started with `WEAVATRIX_ALLOW_TEST_RUNS=1`. It never accepts arbitrary shell commands. Without
135
+ both gates it returns a plan or `UNKNOWN` evidence state.
136
+
124
137
  ## Recipes
125
138
 
139
+ - **Proof-carrying refactor**: `verified_change task="..." phase=plan base_ref=<merge-base>` -> edit ->
140
+ `verified_change task="..." phase=verify base_ref=<same-ref> tests=[{"script":"test","args":[...]}]`.
141
+ Add `run_tests:true` only when test execution was authorized. `BLOCKED` means an evidenced ratchet
142
+ or test failure; `UNKNOWN` means at least one required proof is incomplete.
143
+
126
144
  - **Orient in the configured repo**: `module_map` → `list_communities` → `god_nodes`. Hub ranking
127
145
  is production-only by default; use `include_classified:true` only when tests/generated/build
128
146
  surfaces are deliberately part of the question.
@@ -152,7 +170,9 @@ Start from the task, not from the complete tool list:
152
170
  `run_audit category=unused debt=all` (or `base_ref=<merge-base> debt=new`) for unused exports and
153
171
  dependencies. Before deletion, run `read_source`, `get_dependents`, exact `search_code`, inspect
154
172
  framework discovery/annotations, package scripts, CLI/Docker/manifests, generated consumers and
155
- test-only use, then run the repository tests. `REVIEW_REQUIRED` and `autoDelete:false` are hard
173
+ test-only use, then run the repository tests. Use `evidenceTier` and `remainingChecks` to order the
174
+ review; even `STRONG_STATIC_EVIDENCE` is exact zero-reference evidence, not deletion permission.
175
+ `REVIEW_REQUIRED` and `autoDelete:false` are hard
156
176
  safety semantics, not boilerplate. Use `.weavatrix-deps.json` entrypoints/nonRuntimeRoots only for
157
177
  verified conventions.
158
178
  - **API inventory**: `list_endpoints` (including Next.js App Router, Rust axum and actix-web).
@@ -0,0 +1,59 @@
1
+ import {existsSync, readFileSync} from 'node:fs'
2
+ import {join} from 'node:path'
3
+ import {createRepoBoundary} from '../repo-path.js'
4
+ import {runCommand} from '../process.js'
5
+
6
+ const SAFE_SCRIPT = /^(?:test(?::|$)|(?:check|verify)(?::|$)|[^:]+:(?:test|check|verify)(?::|$))/i
7
+ const UNSAFE_SHELL_ARG = /[\0\r\n&|<>^%!`\"]/
8
+ const tail = (value, limit = 8000) => String(value || '').slice(-limit)
9
+
10
+ function manifestAt(repoRoot) {
11
+ const resolved = createRepoBoundary(repoRoot).resolve('package.json')
12
+ if (!resolved.ok) return null
13
+ try { return JSON.parse(readFileSync(resolved.path, 'utf8')) } catch { return null }
14
+ }
15
+
16
+ function packageManager(repoRoot) {
17
+ if (existsSync(join(repoRoot, 'pnpm-lock.yaml'))) return 'pnpm'
18
+ if (existsSync(join(repoRoot, 'yarn.lock'))) return 'yarn'
19
+ return 'npm'
20
+ }
21
+
22
+ export function validateTestRequests(repoRoot, requests = []) {
23
+ const manifest = manifestAt(repoRoot)
24
+ if (!manifest) return {ok: false, reason: 'package.json is missing or unreadable', tests: []}
25
+ const tests = []
26
+ for (const request of requests.slice(0, 5)) {
27
+ const script = String(request?.script || '')
28
+ const args = Array.isArray(request?.args) ? request.args.slice(0, 40).map(String) : []
29
+ if (!SAFE_SCRIPT.test(script)) return {ok: false, reason: `script ${script || '(missing)'} is outside the test/check/verify allowlist`, tests}
30
+ if (!Object.hasOwn(manifest.scripts || {}, script)) return {ok: false, reason: `package.json has no script named ${script}`, tests}
31
+ if (args.some((arg) => arg.length > 300 || UNSAFE_SHELL_ARG.test(arg))) return {ok: false, reason: `script ${script} has an invalid or shell-sensitive argument`, tests}
32
+ tests.push({script, args})
33
+ }
34
+ return {ok: true, tests, packageManager: packageManager(repoRoot)}
35
+ }
36
+
37
+ export async function runAllowedTests(repoRoot, requests = [], {enabled = false, timeoutMs = 60_000} = {}) {
38
+ const checked = validateTestRequests(repoRoot, requests)
39
+ if (!checked.ok) return {state: 'BLOCKED', reason: checked.reason, results: []}
40
+ if (!checked.tests.length) return {state: 'NOT_REQUESTED', reason: 'no package scripts were requested', results: []}
41
+ if (!enabled || process.env.WEAVATRIX_ALLOW_TEST_RUNS !== '1') return {
42
+ state: 'DISABLED', reason: 'set WEAVATRIX_ALLOW_TEST_RUNS=1 and pass run_tests:true to execute allowlisted package scripts',
43
+ plan: checked.tests, results: [],
44
+ }
45
+ const results = []
46
+ const timeout = Math.max(1000, Math.min(300_000, Number(timeoutMs) || 60_000))
47
+ for (const test of checked.tests) {
48
+ const start = Date.now()
49
+ const separator = checked.packageManager === 'yarn' ? [] : ['--']
50
+ try {
51
+ const run = await runCommand(checked.packageManager, ['run', test.script, ...separator, ...test.args], {cwd: repoRoot, timeoutMs: timeout})
52
+ results.push({script: test.script, status: run.exitCode === 0 ? 'PASS' : 'FAIL', exitCode: run.exitCode, durationMs: Date.now() - start, stdoutTail: tail(run.stdout), stderrTail: tail(run.stderr)})
53
+ } catch (error) {
54
+ const message = error instanceof Error ? error.message : String(error)
55
+ results.push({script: test.script, status: /timed out/i.test(message) ? 'TIMEOUT' : 'FAIL', exitCode: null, durationMs: Date.now() - start, stdoutTail: '', stderrTail: tail(message)})
56
+ }
57
+ }
58
+ return {state: results.every((result) => result.status === 'PASS') ? 'PASS' : 'FAIL', packageManager: checked.packageManager, results}
59
+ }