weavatrix 0.2.9 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -10
- package/docs/releases/v0.2.10.md +53 -0
- package/package.json +3 -3
- package/skill/SKILL.md +77 -26
- package/src/graph/builder/lang-java-calls.js +142 -0
- package/src/graph/builder/lang-java.js +13 -0
- package/src/graph/internal-builder.build.js +4 -2
- package/src/mcp/catalog.mjs +4 -4
- package/src/mcp/tools-architecture.mjs +48 -15
- package/docs/releases/v0.2.9.md +0 -123
package/README.md
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
# Weavatrix
|
|
2
2
|
|
|
3
|
-
**
|
|
4
|
-
|
|
5
|
-
Weavatrix builds
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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,53 @@
|
|
|
1
|
+
# Weavatrix 0.2.10
|
|
2
|
+
|
|
3
|
+
## Broad graph workflows without losing graph depth
|
|
4
|
+
|
|
5
|
+
0.2.10 is a graph-quality, positioning and agent-routing patch. It does not narrow Weavatrix to text search, a
|
|
6
|
+
single blast-radius tool, or three fixed workflows. The package, MCP registry metadata, portable
|
|
7
|
+
MCPB, skill, README and public site now describe one reusable graph exposed through 38 methods and
|
|
8
|
+
many task-specific projections: module/community/hub navigation, exact symbols and paths, local and
|
|
9
|
+
cross-repository API flow, symbol/diff impact, structural and behavioral history, Health,
|
|
10
|
+
dependencies, vulnerability evidence, dead code, clones, coverage, hot paths, proof-carrying change
|
|
11
|
+
verification, intended-architecture ratchets, local multi-repository work and explicit Hosted
|
|
12
|
+
governance.
|
|
13
|
+
|
|
14
|
+
The skill's intent router now maps each independent scenario to the smallest sufficient graph view or
|
|
15
|
+
analysis before lower-level source search. `module_map` is explicitly the first unfamiliar-repository
|
|
16
|
+
orientation view, `run_audit` is the core production-first Health review, and `verified_change` is
|
|
17
|
+
explicitly the pre-commit proof-carrying safeguard. Text search remains a useful source check, not the
|
|
18
|
+
product center.
|
|
19
|
+
|
|
20
|
+
## Java call graph and Repo Lens baseline
|
|
21
|
+
|
|
22
|
+
- Java field, parameter, local and static receiver types now resolve project-internal cross-file
|
|
23
|
+
method calls. Overloads are selected by argument arity; ambiguous methods and external or
|
|
24
|
+
same-basename impostor types fail closed. Call-site lines remain available to bounded context and
|
|
25
|
+
endpoint traces, while hub ranking continues to count unique neighbors rather than occurrences.
|
|
26
|
+
- A release regression test now asserts that the default offline catalog retains all 21 portable
|
|
27
|
+
repository-analysis methods inherited from Repo Lens. Its Electron-only `app_action` and
|
|
28
|
+
`app_job_status` process-control bridge is not misrepresented as an analyzer method: rebuild,
|
|
29
|
+
audit, advisory refresh, duplicate review and coverage generation/mapping retain direct bounded
|
|
30
|
+
MCP paths, while the persistent Electron job journal remains app-specific orchestration.
|
|
31
|
+
- The skill routes every one of the 38 current methods into a concrete scenario, so graph, history,
|
|
32
|
+
Health, duplicates, dead code and architecture governance remain first-class product surfaces.
|
|
33
|
+
- The unconfigured-architecture starter now proposes product-code territories only: tests, docs,
|
|
34
|
+
benchmarks, generated files and repository configs are filtered out, while direct source-root
|
|
35
|
+
files are grouped without overlapping deeper modules. It remains a source-free owner-review
|
|
36
|
+
proposal and is never saved or enforced automatically.
|
|
37
|
+
|
|
38
|
+
## Compatibility and data boundary
|
|
39
|
+
|
|
40
|
+
Tool names, MCP schemas, result envelopes and Hosted sync payloads are unchanged. Java graph nodes
|
|
41
|
+
and edges gain additive local `parameter_count` and `javaResolution` evidence; existing readers can
|
|
42
|
+
ignore those fields, and the Hosted payload allowlist remains unchanged. The default profile still
|
|
43
|
+
exposes 34 local tools and no HTTP tools. Hosted remains separately opt-in, preview-confirmed and
|
|
44
|
+
wire-compatible with payload v3.
|
|
45
|
+
|
|
46
|
+
## Verification
|
|
47
|
+
|
|
48
|
+
- Full Node suite: 506 tests, 503 passed, 3 platform skips, 0 failed.
|
|
49
|
+
- Six-language, cross-repository, framework and MCP lifecycle benchmark: `PASS`.
|
|
50
|
+
- Six available real-repository baselines, including the Java call-graph case: 6/6 `PASS`.
|
|
51
|
+
- Release metadata consistency and JSON syntax: `PASS`.
|
|
52
|
+
- Repo Lens portable analyzer parity: 21/21 methods present in the default offline profile.
|
|
53
|
+
- Hosted API and sync-payload schemas are unchanged.
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "weavatrix",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
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.
|
|
29
|
+
"docs/releases/v0.2.10.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:
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
- **
|
|
46
|
-
|
|
47
|
-
- **Inspect
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
}
|
package/src/mcp/catalog.mjs
CHANGED
|
@@ -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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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}
|
|
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
|
package/docs/releases/v0.2.9.md
DELETED
|
@@ -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.
|