weavatrix 0.2.9 → 0.2.11

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
@@ -1,18 +1,29 @@
1
1
  # Weavatrix
2
2
 
3
- **Code graph & blast-radius MCP server for AI coding agents.**
4
-
5
- Weavatrix builds and compares the living architecture of any local repository: files, symbols,
6
- imports, calls, inheritance, health findings, clone families and Git-history coupling. It serves that
7
- evidence to Claude Code, Codex, or any MCP client for change impact, transitive dependents, health
8
- audit, clone detection and coverage mapping. Text search is included as a convenience, not the product
9
- core. **38 tools available; 34 enabled by the default offline profile. Local-first: with
3
+ **Local repository intelligence for AI coding agents — understand the application quickly, then change it with evidence.**
4
+
5
+ Weavatrix builds a reusable living graph of any local repository: files, symbols, imports, calls,
6
+ inheritance, health findings, clone families and Git-history coupling. It gives Claude Code, Codex,
7
+ or any MCP client a bounded map for fast application understanding and lower repeated context, then
8
+ reuses the same graph for change impact, Health, dead-code review, duplicates, history and intended-
9
+ architecture safeguards. Text search is included as a supporting source check, not the product core.
10
+ **38 tools available; 34 enabled by the default offline profile. Local-first: with
10
11
  the defaults, no repository data leaves your machine.**
11
12
 
12
13
  - Website: [weavatrix.com](https://weavatrix.com)
13
14
  - Source: [github.com/sergii-ziborov/weavatrix](https://github.com/sergii-ziborov/weavatrix)
14
15
  - npm: [`weavatrix`](https://www.npmjs.com/package/weavatrix) — `npx -y weavatrix <repoRoot>`
15
16
 
17
+ ## One graph, many views
18
+
19
+ The 38 MCP methods are not one linear workflow. They project the same reusable graph into the
20
+ smallest view needed for a task: repository and build state; modules, communities, hubs, neighbors
21
+ and paths; exact symbols and bounded source context; endpoint and cross-repository API flow; symbol
22
+ and change-set blast radius; graph revision and behavioral Git history; Health, dependencies,
23
+ vulnerabilities, dead code, clones, coverage and hot paths; proof-carrying change verification;
24
+ intended-architecture contracts and ratchets; local multi-repository work; and explicit,
25
+ preview-confirmed Hosted exchange.
26
+
16
27
  ## Why
17
28
 
18
29
  An AI agent editing code without architecture, health and history evidence is refactoring blind.
@@ -443,9 +454,11 @@ Java graphs:
443
454
  - Java class/interface/enum/record/annotation declarations retain their symbol kind; methods and
444
455
  constructors are linked to their declaring type with visibility metadata. Internal
445
456
  `extends`/`implements` relationships and resolvable type references link to real declarations.
446
- Imports are compile-only; call/reference/heritage edges contribute impact. Maven/Gradle Java
447
- trees use package-aware communities instead of one giant `src` bucket. External or synthetic
448
- placeholder types are not created merely to inflate graph counts.
457
+ Field, parameter, local and static receiver types resolve project-internal cross-file calls;
458
+ overloads are selected by arity and ambiguous/external targets fail closed. Imports are
459
+ compile-only; call/reference/heritage edges contribute impact. Maven/Gradle Java trees use
460
+ package-aware communities instead of one giant `src` bucket. External or synthetic placeholder
461
+ types are not created merely to inflate graph counts.
449
462
  - Dependency checks resolve the nearest workspace manifest and `tsconfig`/`jsconfig` aliases,
450
463
  account for framework-owned runtime peers such as Next.js + `react-dom`, and recognize Next.js
451
464
  App Router route exports as endpoints.
@@ -0,0 +1,23 @@
1
+ # Weavatrix 0.2.11
2
+
3
+ ## MCP Registry publication hotfix
4
+
5
+ 0.2.11 is the immutable follow-up to 0.2.10. The 0.2.10 npm package published successfully, but
6
+ the official MCP Registry rejected its expanded server description because that field exceeded the
7
+ Registry's 100-character limit. This patch shortens only the Registry summary while preserving the
8
+ broad product positioning across the package, MCPB, skill, README and public site. The release
9
+ verifier now enforces the same 1-100 character boundary before a tag can publish.
10
+
11
+ The runtime is otherwise the verified 0.2.10 release: 38 graph-backed methods for fast application
12
+ understanding, Health, dependencies, dead-code and duplicate review, history, endpoint and impact
13
+ tracing, proof-carrying changes, target-architecture ratchets and explicit Hosted governance. Its
14
+ Java receiver-aware call resolver and product-code-only architecture starter are unchanged.
15
+
16
+ ## Compatibility and verification
17
+
18
+ - Tool names, input schemas, result envelopes and Hosted sync payload v2/v3 remain unchanged.
19
+ - Full Node suite: 506 tests, 503 passed, 3 platform skips, 0 failed.
20
+ - Six-language, cross-repository, framework and MCP lifecycle benchmark: `PASS`.
21
+ - Six available real-repository baselines: 6/6 `PASS`.
22
+ - Repo Lens portable analyzer parity: 21/21 methods present in the default offline profile.
23
+ - Release metadata, MCP Registry description length and MCPB manifest validation: `PASS`.
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "weavatrix",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "type": "module",
5
- "description": "Weavatrix architecture graph, health, clone and history MCP server for AI coding agents: blast radius, dependency audit, graph diff and coverage over any local repository.",
5
+ "description": "Local repository intelligence MCP for AI coding agents: reusable architecture graph for fast application understanding, Health, dead code, clones, history, blast radius and architecture safeguards.",
6
6
  "author": "Sergii Ziborov <sergii.ziborov@gmail.com>",
7
7
  "license": "MIT",
8
8
  "homepage": "https://weavatrix.com",
@@ -26,7 +26,7 @@
26
26
  "skill",
27
27
  "scripts/run-agent-task-benchmark.mjs",
28
28
  "docs/agent-task-benchmark.md",
29
- "docs/releases/v0.2.9.md",
29
+ "docs/releases/v0.2.11.md",
30
30
  "README.md",
31
31
  "SECURITY.md",
32
32
  "LICENSE"
package/skill/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: weavatrix
3
- description: Drive the weavatrix MCP code graph, blast-radius (get_dependents/change_impact), dead-code review, health audit, duplicates, coverage, endpoints over any local repo. Use when analyzing code structure, refactor risk, dead code, dependency health, or before opening a PR.
3
+ description: "Use the Weavatrix MCP as a reusable local repository-intelligence layer: understand unfamiliar applications with a bounded code graph, reduce repeated context, review Health, dead code, duplicates and history, trace endpoints and blast radius, enforce target architecture, and verify changes before a PR."
4
4
  ---
5
5
 
6
6
  # weavatrix MCP
@@ -31,31 +31,82 @@ named profiles for new registrations.
31
31
 
32
32
  ## Intent router
33
33
 
34
- Start from the task, not from the complete tool list:
35
-
36
- - **Orient in a repository**: `graph_stats` -> verify `Repo`, `Graph`, and `Build mode`; if the target
37
- is wrong, call `open_repo`, then `graph_stats` again -> `module_map` (production-only by default).
38
- - **Review a branch/diff**: `change_impact` for changed-file blast radius; add
39
- `graph_diff base_ref=<merge-base>` for structural drift, then drill into exact candidates with
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.
45
- - **Trace an API across repositories**: `list_known_repos` -> `trace_api_contract` with an explicit
46
- backend and client list; inspect each `graphReconciliation.buildMode` before using the verdict.
47
- - **Inspect exact symbol references**: start with `graph_stats`, then call `context_bundle` with an
48
- exact node ID (or an unambiguous label) for a compact definition, grouped relations, exact
49
- re-export sites and source workset. Use `inspect_symbol` when the raw bounded point query is needed;
50
- it spends evidence beyond the broad overlay cap. Use `get_dependents` for transitive
51
- graph impact and `find_dead_code` for bounded zero-reference
52
- candidates. Treat `PARTIAL`, `UNAVAILABLE`, `OFF`, or zero exact edges as incomplete evidence, not
53
- a compiler-exact result; `OFF` means the caller explicitly selected static-only mode. Java and Rust
54
- providers are not bundled in 0.2.7, so their edges never become `EXACT_LSP` even when a mixed
55
- repository has a complete TypeScript/JavaScript overlay.
56
- - **Explore an architectural question**: use broad `query_graph` when entry points are unknown; its
57
- bootstrap/tool-execution ranking prefers production executables over docs, sites and fixtures.
58
- Pass exact `seed_files` when the intended entry points are already known.
34
+ Weavatrix is not one report or three fixed workflows. Its 38 methods expose different bounded views
35
+ and analyses over the same reusable graph. Start from the task and choose the smallest sufficient
36
+ projection; expand only when the answer requires it.
37
+
38
+ ### Graph views and application understanding
39
+
40
+ - **Confirm repository, graph freshness and build mode**: `graph_stats`; use `rebuild_graph` only for
41
+ a reported fallback/error or an intentional mode/precision change.
42
+ - **Switch or inventory local repositories**: `list_known_repos` -> `open_repo` -> `graph_stats`.
43
+ - **See production module topology**: `module_map`; use this first for a large unfamiliar application.
44
+ - **See discovered communities and their members**: `list_communities` -> `get_community`.
45
+ - **Find high-coupling hubs**: `god_nodes`; repeated call sites do not inflate unique connectivity.
46
+ - **Inspect one graph entity**: `get_node`; pass an exact node ID when labels are ambiguous.
47
+ - **Inspect direct one-hop relations**: `get_neighbors`; do not confuse it with transitive impact.
48
+ - **Prove a path between two concepts**: `shortest_path`.
49
+ - **Explore an unknown entry point or architectural question**: `query_graph`; pin `seed_files` when
50
+ known, and keep its broad result as orientation evidence.
51
+ - **Inspect one exact symbol deeply**: `context_bundle` for the bounded edit workset;
52
+ `inspect_symbol` for the raw point query and on-demand JS/TS reference evidence.
53
+ - **Confirm graph evidence in source**: `read_source`; use `search_code` only for a narrow regex/glob
54
+ check. Search is supporting evidence, not the repository-intelligence layer.
55
+
56
+ ### Runtime, API and change scenarios
57
+
58
+ - **Inventory HTTP routes**: `list_endpoints` for declared/reachable composed paths and mount proof.
59
+ - **Trace one endpoint through the application**: `trace_endpoint` for route -> handler -> bounded
60
+ downstream call flow with edge-centered excerpts.
61
+ - **Trace an API contract across repositories**: `list_known_repos` -> `trace_api_contract` with an
62
+ explicit backend/client set; inspect each graph reconciliation state before using the verdict.
63
+ - **Measure one symbol's transitive blast radius**: `get_dependents`.
64
+ - **Review the current branch, diff, or external patch**: `change_impact`.
65
+ - **Compare structural graph revisions**: `graph_diff base_ref=<merge-base>` for module edges, cycles,
66
+ orphans and lost callers; without `base_ref`, compare the last rebuild snapshots.
67
+ - **Use behavioral history**: `git_history` for churn x connectivity, hidden co-change and expected
68
+ test/source coupling from bounded local Git numstat evidence.
69
+ - **Plan and verify a serious change or pre-commit gate**: `verified_change phase=plan` -> edit ->
70
+ `verified_change phase=verify base_ref=<same-ref>`. It composes exact context, impact, graph,
71
+ architecture, duplicate, optional API and test proof into one PASS/BLOCKED/UNKNOWN envelope.
72
+
73
+ ### Health, debt and testing scenarios
74
+
75
+ - **Run a whole-repository Health review**: `run_audit debt=all`.
76
+ - **Gate only newly introduced branch debt**: `run_audit base_ref=<merge-base> debt=new`; old debt in
77
+ a changed file remains existing.
78
+ - **Review dependency declarations/imports**: `run_audit category=dependencies`; it includes missing,
79
+ unused, duplicate, unresolved-import and lockfile-drift evidence without relabelling identities.
80
+ - **Refresh vulnerability evidence when explicitly authorized**: `refresh_advisories`, then
81
+ `run_audit category=vulnerability`. `NOT_CHECKED` is unknown, never clean.
82
+ - **Review dead files, functions, methods and symbols**: `find_dead_code`; every result remains a
83
+ review candidate with framework/dynamic/public-API caveats, never an auto-delete verdict.
84
+ - **Review clone families or same-name divergence**: `find_duplicates`; framework router boilerplate
85
+ is suppressed unless explicitly requested.
86
+ - **Map measured coverage or honest static test reachability**: `coverage_map`; unavailable measured
87
+ coverage is not 0%.
88
+ - **Prioritize local performance-review candidates**: `hot_path_review`; confirm with a profiler or
89
+ benchmark before changing runtime behavior.
90
+
91
+ ### Intended architecture and Hosted governance
92
+
93
+ - **Read or establish intended architecture**: `get_architecture_contract`. A returned starter is a
94
+ product-code-only territory proposal (not tests, docs, benchmarks, generated output or repository
95
+ configs), not automatically approved policy.
96
+ - **Select rules before editing**: `prepare_change`; **enforce the ratchet after editing**:
97
+ `verify_architecture`.
98
+ - **Understand or request an exception**: `explain_architecture_violation` ->
99
+ `propose_architecture_exception`; proposals never mutate policy automatically.
100
+ - **Pull an owner-approved Hosted target**: `pull_architecture_contract` only in an explicitly enabled
101
+ Hosted profile.
102
+ - **Review exactly what Hosted sync would send**: `preview_sync`; only an approved
103
+ `sync_graph dry_run=false confirm_token=...` may send that exact bounded payload.
104
+
105
+ Across every scenario, treat `PARTIAL`, `UNAVAILABLE`, `OFF`, `NOT_CHECKED`, `ERROR`, or capped
106
+ evidence as incomplete rather than success. Java and Rust exact language-server providers are not
107
+ bundled, so their edges never become `EXACT_LSP` even when a mixed repository has a complete
108
+ TypeScript/JavaScript overlay. Java receiver-type call edges are parser-resolved and explicitly
109
+ `INFERRED`; they improve cross-file flow without claiming compiler-exact overload or dynamic dispatch.
59
110
 
60
111
  ## Ground rules
61
112
 
@@ -0,0 +1,142 @@
1
+ // Java call resolution beyond bare same-file method names. Tree-sitter gives us the declared type of
2
+ // fields, parameters and locals; imports/same-package resolution then identifies the project type that
3
+ // owns a receiver method. This restores useful cross-file call flow without inventing external nodes or
4
+ // pretending to perform compiler-exact overload/dynamic-dispatch resolution.
5
+ const TYPE_DECLARATIONS = new Set([
6
+ "class_declaration", "interface_declaration", "enum_declaration",
7
+ "record_declaration", "annotation_type_declaration",
8
+ ]);
9
+ const CALLABLE_DECLARATIONS = new Set([
10
+ "method_declaration", "constructor_declaration", "lambda_expression",
11
+ ]);
12
+ const BINDING_DECLARATIONS = new Set([
13
+ "formal_parameter", "spread_parameter", "catch_formal_parameter",
14
+ "enhanced_for_statement", "resource",
15
+ ]);
16
+
17
+ const contains = (scope, node) => scope && node
18
+ && scope.startIndex <= node.startIndex && scope.endIndex >= node.endIndex;
19
+
20
+ function nearest(node, accepted) {
21
+ for (let current = node?.parent, hops = 0; current && hops < 30; current = current.parent, hops++) {
22
+ if (accepted.has(current.type)) return current;
23
+ }
24
+ return null;
25
+ }
26
+
27
+ function baseTypeName(typeNode, field) {
28
+ if (!typeNode) return null;
29
+ if (typeNode.type === "type_identifier") return typeNode.text;
30
+ if (typeNode.type === "scoped_type_identifier") return typeNode.text.split(".").pop() || null;
31
+ const nested = field(typeNode, "type") || field(typeNode, "element")
32
+ || typeNode.namedChildren?.find((child) => [
33
+ "type_identifier", "scoped_type_identifier", "generic_type", "array_type", "annotated_type",
34
+ ].includes(child.type));
35
+ return nested && nested !== typeNode ? baseTypeName(nested, field) : null;
36
+ }
37
+
38
+ function collectBindings(root, field) {
39
+ const bindings = [];
40
+ const add = (nameNode, typeNode, declaration, scope, allowForward = false) => {
41
+ const typeName = baseTypeName(typeNode, field);
42
+ if (!nameNode?.text || !typeName || !scope) return;
43
+ bindings.push({
44
+ name: nameNode.text,
45
+ typeName,
46
+ declaration,
47
+ scope,
48
+ allowForward,
49
+ });
50
+ };
51
+ const stack = [root];
52
+ while (stack.length) {
53
+ const node = stack.pop();
54
+ if (!node) continue;
55
+ if (node.type === "field_declaration" || node.type === "local_variable_declaration") {
56
+ const type = field(node, "type");
57
+ const owner = node.type === "field_declaration"
58
+ ? nearest(node, TYPE_DECLARATIONS)
59
+ : nearest(node, new Set(["block", ...CALLABLE_DECLARATIONS]));
60
+ for (const child of node.namedChildren || []) {
61
+ if (child.type !== "variable_declarator") continue;
62
+ add(field(child, "name"), type, node, owner, node.type === "field_declaration");
63
+ }
64
+ } else if (BINDING_DECLARATIONS.has(node.type)) {
65
+ const scope = node.type === "enhanced_for_statement"
66
+ ? node
67
+ : nearest(node, CALLABLE_DECLARATIONS) || nearest(node, new Set(["catch_clause", "try_with_resources_statement"]));
68
+ add(field(node, "name"), field(node, "type"), node, scope);
69
+ }
70
+ for (const child of node.namedChildren || []) stack.push(child);
71
+ }
72
+ return bindings;
73
+ }
74
+
75
+ function receiverName(node, field) {
76
+ if (!node) return null;
77
+ if (node.type === "identifier" || node.type === "type_identifier") return node.text;
78
+ if (node.type === "scoped_type_identifier") return node.text.split(".").pop() || null;
79
+ if (node.type === "field_access") {
80
+ const object = field(node, "object");
81
+ if (["this", "super"].includes(object?.type)) return field(node, "field")?.text || null;
82
+ }
83
+ return null;
84
+ }
85
+
86
+ function bindingAt(bindings, name, callNode) {
87
+ return bindings
88
+ .filter((item) => item.name === name && contains(item.scope, callNode)
89
+ && (item.allowForward || item.declaration.startIndex <= callNode.startIndex))
90
+ .sort((left, right) => {
91
+ const leftSpan = left.scope.endIndex - left.scope.startIndex;
92
+ const rightSpan = right.scope.endIndex - right.scope.startIndex;
93
+ return leftSpan - rightSpan || right.declaration.startIndex - left.declaration.startIndex;
94
+ })[0] || null;
95
+ }
96
+
97
+ function methodIn(file, name, argumentCount, symIdsByFileName, nodeById) {
98
+ const candidates = (symIdsByFileName.get(file)?.get(name) || [])
99
+ .map((id) => nodeById.get(id))
100
+ .filter((node) => ["method", "constructor"].includes(node?.symbol_kind));
101
+ if (candidates.length === 1) return candidates[0].id;
102
+ const exact = candidates.filter((node) => node.parameter_count === argumentCount);
103
+ return exact.length === 1 ? exact[0].id : null;
104
+ }
105
+
106
+ export function addJavaCalls({
107
+ grammar, tree, fileRel, caps, field, enclosing, links, nodeById,
108
+ symIdsByFileName, resolveCall, resolveJavaType,
109
+ }) {
110
+ const bindings = collectBindings(tree.rootNode, field);
111
+ for (const cap of caps(grammar, `(method_invocation) @call`, tree.rootNode)) {
112
+ const call = cap.node;
113
+ const name = field(call, "name")?.text;
114
+ const argumentCount = field(call, "arguments")?.namedChildren?.length ?? 0;
115
+ const caller = enclosing(fileRel, call.startPosition.row + 1);
116
+ if (!name || !caller) continue;
117
+
118
+ const receiver = field(call, "object");
119
+ let target = null;
120
+ let resolution = "unqualified";
121
+ if (!receiver || ["this", "super"].includes(receiver.type)) {
122
+ target = resolveCall(name, fileRel);
123
+ } else {
124
+ const localName = receiverName(receiver, field);
125
+ const binding = localName && bindingAt(bindings, localName, call);
126
+ const declaredType = binding?.typeName || localName;
127
+ const typeTarget = declaredType && resolveJavaType(declaredType, fileRel);
128
+ const targetFile = typeTarget && nodeById.get(typeTarget)?.source_file;
129
+ target = targetFile && methodIn(targetFile, name, argumentCount, symIdsByFileName, nodeById);
130
+ resolution = binding ? "receiver-declared-type" : "static-type";
131
+ }
132
+ if (!target || target === caller.id) continue;
133
+ links.push({
134
+ source: caller.id,
135
+ target,
136
+ relation: "calls",
137
+ confidence: "INFERRED",
138
+ line: call.startPosition.row + 1,
139
+ javaResolution: resolution,
140
+ });
141
+ }
142
+ }
@@ -1,6 +1,8 @@
1
1
  // Java extractor. Keeps file-level imports/calls while modelling the Java ownership and type system:
2
2
  // classes/interfaces/enums/records own their methods, heritage distinguishes extends from implements,
3
3
  // and project-local type references resolve to the declaration node (never to synthetic type-name nodes).
4
+ import { addJavaCalls } from "./lang-java-calls.js";
5
+
4
6
  const TYPE_CORE = `
5
7
  (class_declaration name: (identifier) @class)
6
8
  (interface_declaration name: (identifier) @interface)
@@ -39,6 +41,13 @@ const visibilityOf = (declaration, owner) => {
39
41
  };
40
42
 
41
43
  const lineOf = (node) => node.startPosition.row + 1;
44
+ const parameterCountOf = (declaration) => {
45
+ const parameters = declaration?.childForFieldName?.("parameters");
46
+ if (!parameters) return null;
47
+ return (parameters.namedChildren || []).filter((node) => [
48
+ "formal_parameter", "spread_parameter", "receiver_parameter",
49
+ ].includes(node.type)).length;
50
+ };
42
51
  const symbolBaseId = (fileRel, nameNode) => `${fileRel}#${nameNode.text}@${lineOf(nameNode)}`;
43
52
  const declarationKey = (node) => `${node?.startIndex ?? -1}:${node?.endIndex ?? -1}`;
44
53
  const exactJavaTarget = (resolveJavaImport, parts) => {
@@ -52,6 +61,7 @@ export default {
52
61
  grammars: ["java"],
53
62
  exts: { ".java": "java" },
54
63
  isWeb: false,
64
+ customCalls: true,
55
65
  calls: `(method_invocation name: (identifier) @callee)`,
56
66
  // Capturing the base type_identifier (rather than the whole generic/scoped type) gives the shared
57
67
  // resolver the imported/local declaration name. Each query is deliberately non-overlapping.
@@ -110,6 +120,7 @@ export default {
110
120
  const memberId = addJavaSym(cap.node, cap.name !== "field", {
111
121
  sourceNode: declaration,
112
122
  symbolKind: memberKind,
123
+ ...(["method", "constructor"].includes(memberKind) ? { parameterCount: parameterCountOf(declaration) } : {}),
113
124
  ...(ownerNameNode ? { memberOf: ownerNameNode.text } : {}),
114
125
  visibility: visibilityOf(declaration, owner),
115
126
  });
@@ -190,4 +201,6 @@ export default {
190
201
  }
191
202
  }
192
203
  },
204
+
205
+ pass2: addJavaCalls,
193
206
  };
@@ -143,7 +143,8 @@ export async function buildInternalGraph(repoDir, opts = {}) {
143
143
  ...(extra && extra.symbolKind ? { symbol_kind: extra.symbolKind } : {}),
144
144
  ...(extra && extra.symbolSpace ? { symbol_space: extra.symbolSpace } : {}),
145
145
  ...(extra && extra.memberOf ? { member_of: extra.memberOf } : {}),
146
- ...(extra && extra.visibility ? { visibility: extra.visibility } : {})
146
+ ...(extra && extra.visibility ? { visibility: extra.visibility } : {}),
147
+ ...(Number.isInteger(extra?.parameterCount) ? { parameter_count: extra.parameterCount } : {})
147
148
  });
148
149
  links.push({ source: fileRel, target: id, relation: "contains", confidence: "EXTRACTED" });
149
150
  syms.push({
@@ -154,6 +155,7 @@ export async function buildInternalGraph(repoDir, opts = {}) {
154
155
  ...(extra?.memberOf ? {memberOf: extra.memberOf} : {}),
155
156
  ...(extra?.symbolKind ? {symbolKind: extra.symbolKind} : {}),
156
157
  ...(extra?.symbolSpace ? {symbolSpace: extra.symbolSpace} : {}),
158
+ ...(Number.isInteger(extra?.parameterCount) ? {parameterCount: extra.parameterCount} : {}),
157
159
  });
158
160
  if (!nameToId.has(name)) nameToId.set(name, id);
159
161
  const ids = nameToIds.get(name) || [];
@@ -290,7 +292,7 @@ export async function buildInternalGraph(repoDir, opts = {}) {
290
292
  try {
291
293
  lang.pass2({
292
294
  grammar, tree, fileRel, code, caps, field, enclosing, links, nodeById,
293
- perFileSymbols, symByFileName, importedLocals, resolveCall,
295
+ perFileSymbols, symByFileName, symIdsByFileName, importedLocals, resolveCall, resolveJavaType,
294
296
  });
295
297
  } catch (e) { /* one language-specific resolver never sinks the graph */ void e; }
296
298
  }
@@ -41,7 +41,7 @@ function buildTools({tg, ti, th, ts, tb, te, ta, tar, thi, tc, tv, caps}) {
41
41
  {cap: 'graph', name: 'git_history', description: 'Behavioral architecture evidence from bounded local git history: churn × connectivity hotspots, hidden co-change coupling, and expected test/source coupling. Reads numstat only — never commit messages, authors, or source bodies.', inputSchema: {type: 'object', properties: {months: {type: 'integer', enum: [3, 6, 12], default: 6}, max_commits: {type: 'integer', minimum: 1, maximum: 5000, default: 1000}, min_pair_count: {type: 'integer', minimum: 2, maximum: 100, default: 3}, max_pairs: {type: 'integer', minimum: 1, maximum: 500, default: 100}, top_n: {type: 'integer', minimum: 1, maximum: 50, default: 10}}}, run: (g, a, ctx) => thi.tGitHistory(g, a, ctx)},
42
42
  {
43
43
  cap: 'graph', refreshGraph: true, name: 'verified_change',
44
- description: 'Proof-carrying change workflow. Given a natural-language task and current diff/files, returns compact edit contexts, bounded call-argument data-flow, blast radius, graph/architecture/duplicate/API ratchets, affected tests, and one PASS/BLOCKED/UNKNOWN verdict. Package tests run only when explicitly requested and WEAVATRIX_ALLOW_TEST_RUNS=1.',
44
+ description: 'Pre-commit, proof-carrying change safeguard. Given a natural-language task and current diff/files, returns compact edit contexts, bounded call-argument data-flow, blast radius, graph/architecture/duplicate/API ratchets, affected tests, and one PASS/BLOCKED/UNKNOWN verdict. Use this high-level workflow before manually composing lower-level checks. Package tests run only when explicitly requested and WEAVATRIX_ALLOW_TEST_RUNS=1.',
45
45
  inputSchema: {type: 'object', additionalProperties: false, properties: {
46
46
  task: {type: 'string', minLength: 1, maxLength: 4000}, phase: {type: 'string', enum: ['plan', 'verify'], default: 'plan'},
47
47
  base_ref: {type: 'string', maxLength: 200, default: 'HEAD'}, diff: {type: 'string', maxLength: 2097152}, files: {type: 'array', items: {type: 'string'}, maxItems: 500},
@@ -108,16 +108,16 @@ function buildTools({tg, ti, th, ts, tb, te, ta, tar, thi, tc, tv, caps}) {
108
108
  {cap: 'source', refreshGraph: true, name: 'context_bundle', description: 'Return one compact, bounded source bundle for an exact symbol: definition, aggregated inbound/outbound containers, exact re-export sites, on-demand TS/JS reference evidence and a few focused source excerpts. Use before an edit when query_graph would be too broad.', inputSchema: {type: 'object', properties: {label: {type: 'string', description: 'Exact node ID or unambiguous symbol label'}, precision: {type: 'string', enum: ['auto', 'graph', 'lsp'], default: 'auto'}, max_references: {type: 'integer', minimum: 1, maximum: 5000, default: 1000}, max_related: {type: 'integer', minimum: 1, maximum: 30, default: 10}, max_reexports: {type: 'integer', minimum: 1, maximum: 100, default: 20}, max_source_files: {type: 'integer', minimum: 1, maximum: 8, default: 4}, context_lines: {type: 'integer', minimum: 0, maximum: 12, default: 4}, timeout_ms: {type: 'integer', minimum: 1000, maximum: 60000, default: 30000}}, required: ['label']}, run: (g, a, ctx) => tb.tContextBundle(g, a, ctx, ts.tInspectSymbol)},
109
109
  {cap: 'health', name: 'find_duplicates', description: "Content-based clone detection over production code (MOSS winnowing over method bodies). Supports high-confidence small clones down to 12 tokens when min_tokens is lowered. Tests, classified non-product paths, and all-router framework boilerplate clone groups are excluded by default; opt them in explicitly.", inputSchema: {type: 'object', properties: {min_similarity: {type: 'integer', description: '50-100, default 80 (ignored in semantic mode)'}, min_tokens: {type: 'integer', minimum: 12, maximum: 400, description: 'min fragment size, 12-400; default 50'}, mode: {type: 'string', enum: ['renamed', 'strict', 'semantic'], default: 'renamed'}, include_tests: {type: 'boolean', default: false}, include_classified: {type: 'boolean', default: false, description: 'Include generated/mock/story/docs/benchmark/temp and paths explicitly classified as excluded; tests still require include_tests'}, include_boilerplate: {type: 'boolean', default: false, description: 'Include clone groups made entirely of conventional *.router.js/ts router symbols'}, include_strings: {type: 'boolean', description: 'Also clone-check large multi-line string literals', default: false}, top_n: {type: 'integer', default: 15}}}, run: (g, a, ctx) => th.tFindDuplicates(g, a, ctx)},
110
110
  {cap: 'health', name: 'find_dead_code', description: 'Conservative review queue for statically unreferenced files, functions, methods and symbols. Returns confidence, reason, bounded evidence and explicit framework/dynamic/reflection/public-API caveats; never an auto-delete verdict. Tests, generated code, mocks, stories, docs, benchmarks and temporary roots are excluded by default.', inputSchema: {type: 'object', properties: {path: {type: 'string', maxLength: 1024, description: 'Optional repo-relative path prefix'}, kinds: {type: 'array', items: {type: 'string', enum: ['file', 'function', 'method', 'symbol']}, maxItems: 4, uniqueItems: true, description: 'Optional candidate kinds; defaults to all'}, min_confidence: {type: 'string', enum: ['high', 'medium', 'low'], default: 'medium', description: 'Minimum confidence to include. low explicitly includes public/framework/dynamic review candidates'}, include_tests: {type: 'boolean', default: false}, include_classified: {type: 'boolean', default: false, description: 'Include generated/mock/story/docs/benchmark/temp and paths explicitly classified as excluded; tests still require include_tests'}, top_n: {type: 'integer', minimum: 1, maximum: 100, default: 30}}}, run: (g, a, ctx) => th.tFindDeadCode(g, a, ctx)},
111
- {cap: 'health', name: 'run_audit', description: 'Production-first internal health audit. Findings whose evidence is entirely test/e2e/generated/mock/story/docs/benchmark/temp or explicitly excluded are suppressed by default; opt them in with include_classified. category=dependencies is a dedicated dependency-health projection across missing, unused and duplicate declarations while preserving each finding\'s native category. With base_ref, builds and audits an immutable Git checkout and compares stable deterministic finding IDs; debt defaults to genuinely new findings. Supply-chain checks remain explicitly uncomparable across the source-only baseline. changed_files without base_ref is only changed-scope, never a new-debt claim.', inputSchema: {type: 'object', properties: {category: {type: 'string', enum: ['dependencies', 'unused', 'structure', 'vulnerability', 'malware'], description: 'Only findings of this category; dependencies selects dependency manifest/import findings across native categories'}, min_severity: {type: 'string', enum: ['critical', 'high', 'medium', 'low', 'info'], description: 'Minimum severity to include'}, max_findings: {type: 'integer', description: 'Max findings to list, default 30'}, include_classified: {type: 'boolean', default: false, description: 'Include findings whose evidence is entirely tests/e2e/generated/mocks/stories/docs/benchmarks/temp or explicitly excluded'}, include_malware_scan: {type: 'boolean', description: 'Also grep installed packages for malware heuristics (slow)', default: false}, base_ref: {type: 'string', maxLength: 200, description: 'Optional immutable Git baseline (for example HEAD~1 or origin/main). Enables honest new/existing/fixed debt comparison'}, changed_files: {type: 'array', items: {type: 'string'}, minItems: 1, maxItems: 500, description: 'Optional explicit repo-relative scope. Without base_ref this is changed-scope only; when omitted with base_ref, files are derived from the Git diff'}, debt: {type: 'string', enum: ['new', 'existing', 'all'], default: 'new', description: 'Baseline comparison view. Defaults to genuinely new deterministic findings when base_ref is present'}}}, run: (g, a, ctx) => th.tRunAudit(g, a, ctx)},
111
+ {cap: 'health', name: 'run_audit', description: 'Core production-first repository Health review: structure, dependency declarations, unused surfaces and explicitly bounded supply-chain evidence. Findings whose evidence is entirely test/e2e/generated/mock/story/docs/benchmark/temp or explicitly excluded are suppressed by default; opt them in with include_classified. category=dependencies is a dedicated dependency-health projection across missing, unused and duplicate declarations while preserving each finding\'s native category. With base_ref, builds and audits an immutable Git checkout and compares stable deterministic finding IDs; debt defaults to genuinely new findings. Supply-chain checks remain explicitly uncomparable across the source-only baseline. changed_files without base_ref is only changed-scope, never a new-debt claim.', inputSchema: {type: 'object', properties: {category: {type: 'string', enum: ['dependencies', 'unused', 'structure', 'vulnerability', 'malware'], description: 'Only findings of this category; dependencies selects dependency manifest/import findings across native categories'}, min_severity: {type: 'string', enum: ['critical', 'high', 'medium', 'low', 'info'], description: 'Minimum severity to include'}, max_findings: {type: 'integer', description: 'Max findings to list, default 30'}, include_classified: {type: 'boolean', default: false, description: 'Include findings whose evidence is entirely tests/e2e/generated/mocks/stories/docs/benchmarks/temp or explicitly excluded'}, include_malware_scan: {type: 'boolean', description: 'Also grep installed packages for malware heuristics (slow)', default: false}, base_ref: {type: 'string', maxLength: 200, description: 'Optional immutable Git baseline (for example HEAD~1 or origin/main). Enables honest new/existing/fixed debt comparison'}, changed_files: {type: 'array', items: {type: 'string'}, minItems: 1, maxItems: 500, description: 'Optional explicit repo-relative scope. Without base_ref this is changed-scope only; when omitted with base_ref, files are derived from the Git diff'}, debt: {type: 'string', enum: ['new', 'existing', 'all'], default: 'new', description: 'Baseline comparison view. Defaults to genuinely new deterministic findings when base_ref is present'}}}, run: (g, a, ctx) => th.tRunAudit(g, a, ctx)},
112
112
  {cap: 'health', name: 'coverage_map', description: 'Map a real existing coverage report onto the graph. If no report exists, return clearly labelled static test reachability (a test imports/reaches a source file) with actualCoverage=NOT_AVAILABLE; reachability is never presented as measured coverage.', inputSchema: {type: 'object', properties: {top_n: {type: 'integer', description: 'Max risk hotspots to list, default 15'}, path: {type: 'string', description: 'Optional repo-relative path prefix filter, e.g. src/query'}}}, run: (g, a, ctx) => th.tCoverageMap(g, a, ctx)},
113
113
  {cap: 'health', name: 'hot_path_review', description: 'Rank a focused production-symbol hot-path queue from parser-derived local complexity, inside-loop allocations/copies/scans/sorts/recursion, graph fan-in/fan-out, and measured coverage or clearly labelled static test reachability. The default score gate is 85 with a narrow strong-local fallback; set min_score=0 for the full diagnostic queue. This is not profiler data or interprocedural Big-O.', inputSchema: {type: 'object', properties: {path: {type: 'string', maxLength: 1024, description: 'Optional repository-relative path prefix'}, top_n: {type: 'integer', minimum: 1, maximum: 100, default: 20}, min_score: {type: 'integer', minimum: 0, maximum: 100, default: 85, description: 'Focused default is 85; lower explicitly to broaden, or use 0 for every threshold-matching diagnostic candidate'}, cyclomatic_threshold: {type: 'integer', minimum: 2, maximum: 1000, default: 8}, call_threshold: {type: 'integer', minimum: 1, maximum: 10000, default: 12}, loop_depth_threshold: {type: 'integer', minimum: 1, maximum: 10, default: 2}, time_rank_threshold: {type: 'integer', minimum: 0, maximum: 5, default: 2}, include_tests: {type: 'boolean', default: false}, include_classified: {type: 'boolean', default: false, description: 'Include generated/mock/story/docs/benchmark/temp and explicitly excluded paths; tests still require include_tests'}}}, run: (g, a, ctx) => th.tHotPathReview(g, a, ctx)},
114
114
  {cap: 'graph', name: 'list_communities', description: 'List graph communities named by their dominant folder (largest first) with sample files — a readable module overview; feed the list position into get_community.', inputSchema: {type: 'object', properties: {top_n: {type: 'integer', description: 'Max communities to list, default 20'}}}, run: (g, a, ctx) => th.tListCommunities(g, a, ctx)},
115
- {cap: 'graph', name: 'module_map', description: 'Production-first folder architecture map with file/symbol counts and strongest module dependencies, separating runtime, TypeScript type-only and language compile-only coupling.', inputSchema: {type: 'object', properties: {top_n: {type: 'integer', description: 'Max modules to list, default 25'}, include_non_product: {type: 'boolean', default: false, description: 'Include tests, fixtures, benchmarks, generated output, docs and other classified non-product files; false by default'}}}, run: (g, a, ctx) => th.tModuleMap(g, a, ctx)},
115
+ {cap: 'graph', name: 'module_map', description: 'First orientation view for understanding an unfamiliar application with little context: a production-first folder architecture map with file/symbol counts and strongest module dependencies, separating runtime, TypeScript type-only and language compile-only coupling.', inputSchema: {type: 'object', properties: {top_n: {type: 'integer', description: 'Max modules to list, default 25'}, include_non_product: {type: 'boolean', default: false, description: 'Include tests, fixtures, benchmarks, generated output, docs and other classified non-product files; false by default'}}}, run: (g, a, ctx) => th.tModuleMap(g, a, ctx)},
116
116
  {cap: 'source', refreshGraph: true, name: 'list_endpoints', description: 'Inventory of HTTP endpoints defined in the repo (Express/Fastify/Nest/Flask/FastAPI/Go mux/Rust axum and actix-web/Spring MVC and WebFlux): declared and reachable composed paths, static mount provenance, confidence, handler, and file:line.', inputSchema: {type: 'object', properties: {method: {type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT', 'ALL', 'ANY']}, path: {type: 'string', maxLength: 2048, description: 'Optional exact composed path or segment-aligned suffix'}, max_results: {type: 'integer', description: 'Max endpoints to list, default 100'}}}, run: (g, a, ctx) => th.tListEndpoints(g, a, ctx)},
117
117
  {cap: 'source', refreshGraph: true, name: 'trace_endpoint', description: 'Resolve one exact reachable HTTP endpoint, prove its router mount chain, bind its handler symbol, and return a bounded production-only multi-hop call graph with call-site excerpts. This is a focused projection of the repository graph, not text-search inference.', inputSchema: {type: 'object', properties: {path: {type: 'string', minLength: 1, maxLength: 2048, description: 'Exact composed path; a suffix is accepted only when unambiguous'}, method: {type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT', 'ALL', 'ANY']}, max_depth: {type: 'integer', minimum: 1, maximum: 4, default: 3}, max_nodes: {type: 'integer', minimum: 2, maximum: 40, default: 20}, max_excerpts: {type: 'integer', minimum: 0, maximum: 12, default: 6}, context_lines: {type: 'integer', minimum: 0, maximum: 6, default: 2}, include_classified: {type: 'boolean', default: false, description: 'Include test/e2e/generated/mock/story/docs/benchmark/temp and explicitly excluded targets'}}, required: ['path']}, run: (g, a, ctx) => te.tTraceEndpoint(g, a, ctx)},
118
118
  {cap: 'build', name: 'rebuild_graph', description: "Rebuild the active full-repository graph and report a structural delta. Omitted mode/precision preserve the active graph; a first build uses full and the startup precision setting (lsp unless WEAVATRIX_PRECISION=off). The local TypeScript/JavaScript LSP overlay validates bounded ambiguous edges; precision:off is an explicit fallback. With scope, build an isolated diagnostic graph without replacing or diffing the full graph.", inputSchema: {type: 'object', properties: {mode: {type: 'string', enum: ['full', 'no-tests', 'tests-only'], description: 'Build mode; omit to preserve the active mode (or use full for a first build)'}, precision: {type: 'string', enum: ['lsp', 'off'], description: 'Semantic precision; omit to preserve the active mode (or use the startup setting for a first build)'}, scope: {type: 'string', description: 'Optional isolated diagnostic path prefix; never replaces the active full graph'}}}, run: (g, a, ctx) => ta.tRebuildGraph(g, a, ctx)},
119
119
  {cap: 'graph', name: 'graph_diff', description: 'Structural graph diff: compare the current graph with an immutable Git-ref baseline (base_ref such as HEAD~1 or main), or with graph.prev.json from the last rebuild when base_ref is omitted. Reports architecture drift, cycle changes and symbols that lost their last caller.', inputSchema: {type: 'object', properties: {base_ref: {type: 'string', maxLength: 256, description: 'Optional immutable Git baseline to build in isolation, e.g. HEAD~1, main or origin/main; never checks out or mutates the working tree'}, path: {type: 'string', description: 'Optional node-id/path prefix to scope the diff, e.g. src/query'}}}, run: (g, a, ctx) => ti.tGraphDiff(g, a, ctx)},
120
- {cap: 'graph', name: 'get_architecture_contract', description: 'Return the executable target-architecture contract, quality budgets and ratchet mode that an agent must follow before editing.', inputSchema: {type: 'object', properties: {}}, run: (g, a, ctx) => tar.tGetArchitectureContract(g, a, ctx)},
120
+ {cap: 'graph', name: 'get_architecture_contract', description: 'Start the intended-architecture workflow: return the executable owner-approved target, quality budgets and ratchet mode that an agent must follow before editing. When absent, returns a starter proposal rather than silently inventing policy.', inputSchema: {type: 'object', properties: {}}, run: (g, a, ctx) => tar.tGetArchitectureContract(g, a, ctx)},
121
121
  {cap: 'graph', name: 'prepare_change', description: 'Select active target-architecture rules for an intended set of changed files. Run before a non-trivial edit.', inputSchema: {type: 'object', properties: {intent: {type: 'string'}, files: {type: 'array', items: {type: 'string'}, maxItems: 200}}, required: ['files']}, run: (g, a, ctx) => tar.tPrepareChange(g, a, ctx)},
122
122
  {cap: 'health', name: 'verify_architecture', description: 'Verify the fresh graph against the active target contract and ratchet; separates new, existing, fixed and excepted debt.', inputSchema: {type: 'object', properties: {}}, run: (g, a, ctx) => tar.tVerifyArchitecture(g, a, ctx)},
123
123
  {cap: 'health', name: 'explain_architecture_violation', description: 'Explain one active architecture violation and the governing rule.', inputSchema: {type: 'object', properties: {fingerprint: {type: 'string'}}, required: ['fingerprint']}, run: (g, a, ctx) => tar.tExplainArchitectureViolation(g, a, ctx)},
@@ -15,6 +15,12 @@ const PROVISIONAL_BUDGETS = Object.freeze({
15
15
  maxModuleBoundaryRatio: .65,
16
16
  })
17
17
 
18
+ const SOURCE_ROOTS = new Set(['src', 'app', 'lib', 'packages', 'services'])
19
+ const PRODUCT_CODE_EXTENSIONS = new Set([
20
+ '.cjs', '.cs', '.css', '.go', '.htm', '.html', '.java', '.js', '.jsx', '.less', '.mjs', '.py', '.pyi',
21
+ '.rs', '.scss', '.ts', '.tsx',
22
+ ])
23
+
18
24
  const remediation = () => ({
19
25
  offlinePath: '.weavatrix/architecture.json',
20
26
  hostedAction: 'Open Architecture -> choose intended style -> Save target & baseline',
@@ -35,24 +41,51 @@ function activeContract(ctx) {
35
41
  return loadArchitectureContract(ctx.repoRoot, ctx.graphPath)
36
42
  }
37
43
 
38
- function starterContract(g) {
44
+ const codeExtension = (file) => {
45
+ const name = String(file || '').split('/').at(-1) || ''
46
+ const dot = name.lastIndexOf('.')
47
+ return dot >= 0 ? name.slice(dot).toLowerCase() : ''
48
+ }
49
+
50
+ function starterContract(g, repoRoot) {
51
+ const classifier = createPathClassifier(repoRoot)
39
52
  const files = [...new Set((g?.nodes || [])
40
53
  .filter((node) => !String(node.id).includes('#'))
41
54
  .map((node) => String(node.source_file || node.id || '').replace(/\\/g, '/'))
42
- .filter(Boolean))]
55
+ .filter((file) => file && PRODUCT_CODE_EXTENSIONS.has(codeExtension(file))))]
56
+ .filter((file) => {
57
+ const explanation = classifier.explain(file)
58
+ return !explanation.excluded && !hasPathClass(
59
+ explanation, 'test', 'e2e', 'generated', 'mock', 'story', 'docs', 'benchmark', 'temp',
60
+ )
61
+ })
43
62
  const groups = new Map()
44
63
  for (const file of files) {
45
64
  const parts = file.split('/').filter(Boolean)
46
- const depth = ['src', 'app', 'lib', 'packages', 'services'].includes(parts[0]) ? 2 : 1
47
- const path = parts.slice(0, Math.max(1, Math.min(depth, parts.length - 1))).join('/') || '(root)'
48
- groups.set(path, (groups.get(path) || 0) + 1)
65
+ let key, name, path
66
+ if (parts.length === 1) {
67
+ key = 'root-code'
68
+ name = 'root code'
69
+ path = file
70
+ } else if (SOURCE_ROOTS.has(parts[0]) && parts.length === 2) {
71
+ key = `${parts[0]}-root`
72
+ name = `${parts[0]} (root files)`
73
+ path = file
74
+ } else {
75
+ path = parts.slice(0, SOURCE_ROOTS.has(parts[0]) ? 2 : 1).join('/')
76
+ key = path
77
+ name = path
78
+ }
79
+ const group = groups.get(key) || {name, paths: new Set(), files: 0}
80
+ group.paths.add(path)
81
+ group.files += 1
82
+ groups.set(key, group)
49
83
  }
50
- const components = [...groups].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 80)
51
- .filter(([path]) => path !== '(root)')
52
- .map(([path], index) => ({
53
- id: path.toLowerCase().replace(/[^a-z0-9._:-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 100) || `component-${index + 1}`,
54
- name: path,
55
- paths: [path],
84
+ const components = [...groups].sort((a, b) => b[1].files - a[1].files || a[0].localeCompare(b[0])).slice(0, 80)
85
+ .map(([key, group], index) => ({
86
+ id: key.toLowerCase().replace(/[^a-z0-9._:-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 100) || `component-${index + 1}`,
87
+ name: group.name,
88
+ paths: [...group.paths].sort((a, b) => a.localeCompare(b)),
56
89
  }))
57
90
  return normalizeArchitectureContract({
58
91
  name: 'Proposed no-regressions baseline', style: 'custom', enforcement: 'ratchet', components,
@@ -62,10 +95,10 @@ function starterContract(g) {
62
95
  })
63
96
  }
64
97
 
65
- function notConfiguredResult(g, action, {includeStarter = false} = {}) {
66
- const starter = includeStarter ? starterContract(g) : null
98
+ function notConfiguredResult(g, action, {includeStarter = false, repoRoot = null} = {}) {
99
+ const starter = includeStarter ? starterContract(g, repoRoot) : null
67
100
  const starterText = starter
68
- ? ` A source-free starter with ${starter.components.length} path territories is available in JSON output from this lookup.`
101
+ ? ` A source-free starter with ${starter.components.length} product-code territories is available in JSON output from this lookup.`
69
102
  : ''
70
103
  return toolResult([
71
104
  `Architecture ${action} is NOT_CONFIGURED — no target contract is active.${starterText}`,
@@ -122,7 +155,7 @@ export function tGetArchitectureContract(g, args, ctx) {
122
155
  const text = loaded.error
123
156
  ? `Architecture contract is invalid (${loaded.source || 'unknown'}): ${loaded.error}`
124
157
  : 'No target architecture contract is active.'
125
- if (!loaded.error) return notConfiguredResult(g, 'lookup', {includeStarter: true})
158
+ if (!loaded.error) return notConfiguredResult(g, 'lookup', {includeStarter: true, repoRoot: ctx?.repoRoot})
126
159
  return toolResult(text, {state: 'ERROR', source: loaded.source, error: loaded.error})
127
160
  }
128
161
  const contract = loaded.contract
@@ -1,123 +0,0 @@
1
- # Weavatrix 0.2.9
2
-
3
- ## Correctness, signal, and consent
4
-
5
- 0.2.9 is a focused MCP correctness release driven by dogfooding on a 10,000-node JavaScript REST
6
- service. It fixes cases where broad repository orientation was useful but precision tools handed an
7
- agent incomplete paths, mismatched call-site provenance, or noisy review queues.
8
-
9
- ## Composed Express routes
10
-
11
- `list_endpoints` now follows relative ESM/CommonJS router imports and nested `router.use(...)`
12
- mounts. A handler declared as `/:attackId/startMitigate` below `/warRoom` and `/attack` is reported as
13
- `/warRoom/attack/:attackId/startMitigate` while retaining its local declaration path. The inventory
14
- separates declaration and reachable-route counts, labels local versus statically composed evidence,
15
- and returns the exact mount files, lines and prefixes.
16
-
17
- Composition is intentionally bounded. Dynamic mount expressions that cannot be resolved statically
18
- remain local-path evidence rather than guessed full routes.
19
-
20
- The new `trace_endpoint` tool resolves one exact composed route, binds its handler without silently
21
- choosing among same-name symbols, and traverses a bounded production-only call graph with excerpts at
22
- the actual edge lines. On the dogfood route it recovered router mount provenance, controller:268,
23
- service, messaging and task calls in one graph-anchored response.
24
-
25
- ## Edge-centered source context
26
-
27
- `context_bundle` now keeps two facts separate:
28
-
29
- - the file and line containing an outbound call;
30
- - the target symbol's definition file.
31
-
32
- The compact source bundle prioritizes the definition and bounded excerpts around decisive inbound and
33
- outbound edge lines. Long Swagger/JSDoc comments at the beginning of a controller therefore no longer
34
- consume the only excerpt before the real service call.
35
-
36
- ## Search and retrieval signal
37
-
38
- - Ripgrep-backed `search_code` runs from the repository root, so documented relative globs such as
39
- `services/attack/**` behave consistently on Windows, macOS, and Linux.
40
- - Broad `query_graph` prompts discard generic REST/instruction terms before fuzzy seeding and use a
41
- smaller automatic seed set. When prose contains a code-shaped identifier such as `startMitigate`,
42
- its exact same-name declarations are exclusive automatic seeds; generic controller/service/flow
43
- words cannot add unrelated modules. Exact files, endpoints, and symbols remain the preferred
44
- precision path.
45
-
46
- ## Production-first review queues
47
-
48
- - `run_audit` suppresses findings whose evidence is entirely in tests, generated output, docs,
49
- benchmarks, fixtures, mocks, or stories. `include_classified:true` restores the full diagnostic
50
- view; mixed production/non-production evidence remains visible by default.
51
- - `run_audit category=dependencies` projects missing, unused and duplicate declarations, unresolved
52
- imports and lockfile drift into one dependency-health review without relabelling their native
53
- deterministic finding identities.
54
- - `find_duplicates` suppresses groups made entirely of router boilerplate by default.
55
- `include_boilerplate:true` restores them for framework-template review.
56
- - Incomplete OSV coverage remains explicit as `NOT_CHECKED`; this release does not turn missing
57
- advisory data into a clean security verdict.
58
-
59
- ## Preview-confirm graph sync
60
-
61
- `preview_sync` and `sync_graph` are now a two-step consent flow:
62
-
63
- 1. `preview_sync` builds and validates the strict payload allowlist locally and has no network path.
64
- 2. Display the destination, repository identity, exact top-level fields, payload
65
- version/sections/size/hash and return a short-lived confirmation token.
66
- 3. `sync_graph` sends only the exact cached payload when it supplies both `dry_run:false` and that token
67
- within five minutes. A token alone remains a no-network dry-run.
68
-
69
- Incorrect or expired tokens never send. Sync also rejects embedded URL credentials/fragments and
70
- requires HTTPS except for loopback development endpoints. URL query values are not echoed in previews
71
- or errors.
72
-
73
- `pull_architecture_contract` applies the same HTTPS-outside-loopback and embedded-credential URL
74
- rules before sending bearer authentication. It now distinguishes authentication, forbidden,
75
- repository-not-registered, endpoint-not-found, repository-not-ready and general HTTP failures.
76
- Hosted's `REPOSITORY_NOT_FOUND` response is therefore no longer confused with a wrong endpoint;
77
- `NOT_CONFIGURED` explicitly means sync/authentication succeeded but no target contract was saved.
78
-
79
- ## Per-call metrics without telemetry
80
-
81
- Every MCP response carries transient local `_meta["weavatrix/metrics"]`: duration, text/structured
82
- bytes, estimated output tokens, graph freshness/revision/update and graph-cache status. These values
83
- are returned to the caller only; Weavatrix does not persist, aggregate or transmit them.
84
-
85
- ## Public contract alignment
86
-
87
- The README, package manifests, public home page, privacy policy, security policy, and new public
88
- license/security pages now describe the same 38-tool surface, 34-tool offline default, preview-confirm
89
- sync behavior, and source/data boundary. The optional Hosted workbench is described as an
90
- independently evolving access-controlled preview. Payload v3 is unchanged, so existing Hosted storage,
91
- history and evidence readers remain wire-compatible; Hosted contract tests validate v1/v2/v3.
92
-
93
- ## Compatibility
94
-
95
- The release adds `trace_endpoint` to the local source surface and `preview_sync` to the explicit
96
- Hosted surface, taking the catalog from 36 to 38 tools and the default offline profile from 33 to 34.
97
- Existing offline, pinned, osv, hosted, full and legacy online capability names remain valid.
98
- `sync_graph` retains its no-network dry-run compatibility form, but `preview_sync` is the canonical
99
- approval step for new clients.
100
-
101
- ## Verification
102
-
103
- - Full Node suite: 502 tests, 499 passed, 3 platform skips, 0 failed.
104
- - Deterministic six-language, cross-repository, framework-convention and MCP lifecycle benchmark:
105
- `PASS`.
106
- - Real pinned corpus: 6 of 6 repositories `PASS` across TypeScript, JavaScript, Python, Go, Java and
107
- Rust.
108
- - Release metadata consistency: `PASS`; production npm audit: 0 vulnerabilities; npm 0.2.9 package
109
- dry-run: 154 files, 413,993 bytes.
110
- - Portable MCPB: manifest validation `PASS`; archive built at 6.7 MiB
111
- (`5a5def22491bc0777c882c314b12be7153d56d29`). The local archive is unsigned.
112
- - Direct `controller-rest-api` dogfood: 1,076 indexed files and 989 reachable composed endpoints;
113
- public and internal composed
114
- `startMitigate` routes found; documented attack glob returned real matches; context preserved
115
- `controller:268 → service` with the call excerpt; production-first audit retained the manually
116
- confirmed missing `mongodb` declaration and explicit `OSV NOT_CHECKED`; 2 router-only clone groups
117
- were separated from 60 default groups; exact identifier seeding returned only controller, service
118
- and messaging declarations.
119
-
120
- Focused regressions cover nested Express mounts, call-site/target provenance and edge excerpts,
121
- Windows path globs, generic REST and exact-identifier seed handling, production-first audit filtering,
122
- router boilerplate suppression, no-network sync previews, confirmation-token mismatch, URL redaction,
123
- and actionable contract 404 state.