weavatrix 0.1.1 → 0.1.3
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 +117 -21
- package/SECURITY.md +31 -0
- package/package.json +16 -4
- package/skill/SKILL.md +69 -12
- package/src/analysis/coverage-reports.js +14 -11
- package/src/analysis/dead-check.js +87 -6
- package/src/analysis/dep-check.js +84 -8
- package/src/analysis/dep-rules.js +74 -8
- package/src/analysis/duplicates.compute.js +215 -215
- package/src/analysis/duplicates.js +15 -15
- package/src/analysis/duplicates.run.js +52 -51
- package/src/analysis/duplicates.tokenize.js +182 -182
- package/src/analysis/endpoints.js +50 -5
- package/src/analysis/graph-analysis.aggregate.js +16 -4
- package/src/analysis/internal-audit.collect.js +154 -31
- package/src/analysis/internal-audit.reach.js +52 -11
- package/src/analysis/internal-audit.run.js +72 -21
- package/src/build-graph.js +2 -1
- package/src/child-env.js +7 -0
- package/src/graph/builder/lang-js.js +66 -23
- package/src/graph/internal-builder.build.js +48 -10
- package/src/graph/internal-builder.langs.js +49 -3
- package/src/graph/internal-builder.resolvers.js +96 -20
- package/src/mcp/catalog.mjs +10 -8
- package/src/mcp/graph-context.mjs +107 -17
- package/src/mcp/sync-payload.mjs +110 -0
- package/src/mcp/tools-actions.mjs +42 -18
- package/src/mcp/tools-graph.mjs +46 -12
- package/src/mcp/tools-health.mjs +42 -18
- package/src/mcp/tools-impact.mjs +55 -43
- package/src/mcp-rg.mjs +2 -1
- package/src/mcp-server.mjs +25 -16
- package/src/mcp-source-tools.mjs +16 -5
- package/src/process.js +4 -3
- package/src/repo-path.js +53 -0
- package/src/security/advisory-store.js +51 -7
- package/src/security/installed.js +44 -31
- package/src/security/malware-heuristics.exclusions.js +134 -134
- package/src/security/malware-heuristics.js +15 -15
- package/src/security/malware-heuristics.roots.js +142 -142
- package/src/security/malware-heuristics.scan.js +85 -85
- package/src/security/malware-heuristics.sweep.js +122 -122
package/README.md
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
Grep sees text. Weavatrix sees structure. It builds a dependency graph of any local repository —
|
|
6
6
|
files, symbols, and the imports/calls/inheritance connecting them — and serves it to Claude Code,
|
|
7
7
|
Codex, or any MCP client: change impact, transitive dependents, health audit, clone detection,
|
|
8
|
-
coverage mapping. **23 tools
|
|
9
|
-
|
|
8
|
+
coverage mapping. **23 tools available; 21 offline tools enabled by default, including one-call
|
|
9
|
+
repository switching. Local-first: with the defaults, no repository data leaves your machine.**
|
|
10
10
|
|
|
11
11
|
- Website: [weavatrix.com](https://weavatrix.com)
|
|
12
12
|
- Source: [github.com/sergii-ziborov/weavatrix](https://github.com/sergii-ziborov/weavatrix)
|
|
@@ -32,7 +32,7 @@ answers grep can't produce:
|
|
|
32
32
|
Requires Node ≥ 18. One command:
|
|
33
33
|
|
|
34
34
|
```sh
|
|
35
|
-
# Claude Code —
|
|
35
|
+
# Claude Code — offline default; open_repo can switch local repositories:
|
|
36
36
|
claude mcp add -s user weavatrix -- npx -y weavatrix <repoRoot>
|
|
37
37
|
```
|
|
38
38
|
|
|
@@ -51,6 +51,20 @@ startup_timeout_sec = 20
|
|
|
51
51
|
tool_timeout_sec = 60
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
The default includes offline repository switching but excludes every network tool. Pass a final
|
|
55
|
+
comma-separated capability list only to restrict or extend it:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
# Pin the registration to one repository (hide open_repo/list_known_repos):
|
|
59
|
+
claude mcp add -s user weavatrix -- npx -y weavatrix <repoRoot> graph,search,source,health,build
|
|
60
|
+
|
|
61
|
+
# Add network tools while pinning one repository:
|
|
62
|
+
claude mcp add -s user weavatrix -- npx -y weavatrix <repoRoot> graph,search,source,health,build,online
|
|
63
|
+
|
|
64
|
+
# Add network tools and keep the default repository switching:
|
|
65
|
+
claude mcp add -s user weavatrix -- npx -y weavatrix <repoRoot> graph,search,source,health,build,retarget,online
|
|
66
|
+
```
|
|
67
|
+
|
|
54
68
|
Or clone it:
|
|
55
69
|
|
|
56
70
|
```sh
|
|
@@ -63,9 +77,12 @@ claude mcp add -s user weavatrix -- node <path-to>/weavatrix/bin/weavatrix-mcp.m
|
|
|
63
77
|
(`<repoRoot-parent>/weavatrix-graphs/<repoName>/graph.json`). Pass an explicit
|
|
64
78
|
`<graph.json> <repoRoot>` pair instead if you keep graphs elsewhere.
|
|
65
79
|
|
|
66
|
-
No graph yet?
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
No graph yet? Ask the agent to call `rebuild_graph`; it builds the missing graph locally.
|
|
81
|
+
`open_repo` can change the active repository and builds a missing graph automatically. A normal
|
|
82
|
+
`open_repo` call also upgrades graphs created before `0.1.3` to typed import edges; `build:false`
|
|
83
|
+
probes without building and refuses a legacy graph. Retargeting is offline but intentionally changes
|
|
84
|
+
the filesystem boundary for subsequent tools; omit `retarget` from an explicit capability list when
|
|
85
|
+
a registration must stay pinned to one repository.
|
|
69
86
|
|
|
70
87
|
An agent skill with recipes ships in [skill/SKILL.md](skill/SKILL.md) — install as
|
|
71
88
|
`~/.claude/skills/weavatrix/SKILL.md`.
|
|
@@ -74,20 +91,24 @@ An agent skill with recipes ships in [skill/SKILL.md](skill/SKILL.md) — instal
|
|
|
74
91
|
|
|
75
92
|
**graph** — `graph_stats`, `get_node`, `get_neighbors`, `query_graph`, `god_nodes`,
|
|
76
93
|
`shortest_path`, `get_community`, `list_communities`, `module_map`, `get_dependents`,
|
|
77
|
-
`change_impact`, `graph_diff
|
|
94
|
+
`change_impact`, `graph_diff`. Runtime dependencies and TypeScript type-only coupling are reported
|
|
95
|
+
separately where that distinction changes the result.
|
|
78
96
|
|
|
79
97
|
**search / source** — `search_code` (ripgrep-backed, pure-Node fallback), `read_source` (a
|
|
80
98
|
symbol's actual code in one hop), `list_endpoints` (HTTP route inventory:
|
|
81
99
|
Express/Fastify/Nest/Flask/FastAPI/Go mux …)
|
|
82
100
|
|
|
83
|
-
**health** — `run_audit` (dead code, unused exports, missing/unused npm/Go/Python deps,
|
|
84
|
-
cycles, orphans, boundary rules, offline OSV vulnerabilities + typosquat +
|
|
85
|
-
`find_duplicates` (MOSS winnowing over method bodies — catches copy-paste even
|
|
86
|
-
`coverage_map` (existing coverage reports mapped onto the graph; untested hotspots
|
|
87
|
-
connectivity — tests are never executed)
|
|
101
|
+
**health** — `run_audit` (dead code, unused exports, missing/unused npm/Go/Python deps, runtime
|
|
102
|
+
cycles, type-only coupling, orphans, boundary rules, offline OSV vulnerabilities + typosquat +
|
|
103
|
+
lockfile drift), `find_duplicates` (MOSS winnowing over method bodies — catches copy-paste even
|
|
104
|
+
after renames), `coverage_map` (existing coverage reports mapped onto the graph; untested hotspots
|
|
105
|
+
ranked by connectivity — tests are never executed)
|
|
88
106
|
|
|
89
107
|
**build** — `rebuild_graph` (reports the structural delta, keeps the prior state as
|
|
90
|
-
`graph.prev.json`)
|
|
108
|
+
`graph.prev.json`)
|
|
109
|
+
|
|
110
|
+
**retarget** *(enabled by default, offline, explicit tool call)* — `open_repo`, `list_known_repos`;
|
|
111
|
+
changes the active repository boundary
|
|
91
112
|
|
|
92
113
|
**online** *(explicit opt-in — see Privacy)* — `refresh_advisories`, `sync_graph`
|
|
93
114
|
|
|
@@ -95,20 +116,95 @@ Quality of life: graph tools self-report staleness vs the repo HEAD; ambiguous n
|
|
|
95
116
|
disclosed instead of silently guessed; and the server **hot-reloads its own tool code** when the
|
|
96
117
|
files under `src/mcp/` change — no reconnect needed.
|
|
97
118
|
|
|
119
|
+
## Signal quality and repository configuration
|
|
120
|
+
|
|
121
|
+
Weavatrix `0.1.3` reduces the most common sources of static-analysis noise:
|
|
122
|
+
|
|
123
|
+
- In Git repositories, graph and clone scans use tracked plus non-ignored untracked files, so
|
|
124
|
+
`.gitignore`-excluded build outputs such as packaged applications do not dominate findings.
|
|
125
|
+
- TypeScript `import type` and type-only re-exports remain visible as compile-time coupling but do
|
|
126
|
+
not inflate runtime-cycle severity. `module_map`, `change_impact` and structural diffs preserve
|
|
127
|
+
that distinction. `god_nodes` ranks unique neighbors with runtime connectivity first and reports
|
|
128
|
+
repeated occurrences separately.
|
|
129
|
+
- Dependency checks resolve the nearest workspace manifest and `tsconfig`/`jsconfig` aliases,
|
|
130
|
+
account for framework-owned runtime peers such as Next.js + `react-dom`, and recognize Next.js
|
|
131
|
+
App Router route exports as endpoints.
|
|
132
|
+
- `coverage_map` reports coverage as **unavailable** when no supported report exists. That means
|
|
133
|
+
“no data”, not zero coverage.
|
|
134
|
+
- Duplicate output is a review queue, not a verdict: near-identical bodies are clone candidates;
|
|
135
|
+
same-name/different-body pairs are divergence candidates. Read both sources and confirm the
|
|
136
|
+
shared contract before consolidating code.
|
|
137
|
+
|
|
138
|
+
`run_audit` makes incomplete security coverage explicit. OSV state is `OK` only after every
|
|
139
|
+
supported pinned package/version for this repository was queried successfully. `PARTIAL` means
|
|
140
|
+
some queries failed, the response was incomplete, the dependency fingerprint changed, or the cache
|
|
141
|
+
uses a legacy stamp; `NOT_CHECKED` means there is no per-repository refresh; `ERROR` means the local
|
|
142
|
+
check itself failed. None of the latter three states is a clean vulnerability result. The cache
|
|
143
|
+
stores a fingerprint of the supported dependency set so a lockfile change cannot silently reuse a
|
|
144
|
+
stale `OK`.
|
|
145
|
+
|
|
146
|
+
For conventions that cannot be inferred safely, add an optional `.weavatrix-deps.json` at the
|
|
147
|
+
repository root:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"entrypoints": ["scripts/publish-release.mjs"],
|
|
152
|
+
"python": {
|
|
153
|
+
"managedDependencies": ["numpy", "openvino-genai"],
|
|
154
|
+
"ignoreDependencies": ["vendor-sdk"]
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`entrypoints` protects framework/script entry files from dead-code classification.
|
|
160
|
+
`managedDependencies` declares Python modules supplied by an external runtime;
|
|
161
|
+
`ignoreDependencies` suppresses intentionally unresolved Python packages. Keep the lists narrow:
|
|
162
|
+
they change audit interpretation, not the repository or its dependency installation.
|
|
163
|
+
|
|
98
164
|
## Privacy: local-first, offline by design
|
|
99
165
|
|
|
100
|
-
Graph queries, audits
|
|
101
|
-
|
|
166
|
+
Graph queries, audits, clone scans and repository switching run locally. The default capability set
|
|
167
|
+
is `graph,search,source,health,build,retarget`: no Weavatrix HTTP requests. `open_repo` changes the
|
|
168
|
+
active local boundary only when called. Weavatrix itself initiates outbound HTTP only from two
|
|
169
|
+
tools; both require the explicit `online` group and a tool call:
|
|
102
170
|
|
|
103
171
|
- `refresh_advisories` — queries [OSV.dev](https://osv.dev) with your lockfile's package
|
|
104
172
|
**names + versions** (that is what an OSV query is; never source code) and caches the advisories
|
|
105
173
|
in `~/.weavatrix/advisories.json`. `run_audit` then matches against that store fully offline.
|
|
106
|
-
- `sync_graph` —
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
174
|
+
- `sync_graph` — constructs a versioned, allowlisted payload from `graph.json`: relative paths,
|
|
175
|
+
symbol names and line ranges, import/dependency identifiers, edges and numeric metrics. Unknown
|
|
176
|
+
fields are discarded; source file bodies are never read for sync or included in the payload. The
|
|
177
|
+
endpoint is **yours**, configured via `WEAVATRIX_SYNC_URL` / `WEAVATRIX_SYNC_TOKEN`. Off by default.
|
|
178
|
+
Sync payload v2 preserves type-only edge metadata. Graphs built before `0.1.3` must be rebuilt
|
|
179
|
+
once before syncing; a normal `open_repo` call performs that upgrade automatically.
|
|
180
|
+
|
|
181
|
+
If `refresh_advisories` is not listed by the MCP client, that is the expected default: the
|
|
182
|
+
registration does not include `online`. Only with the user's approval, add `online` to the final
|
|
183
|
+
capability list (for example `graph,search,source,health,build,retarget,online`), restart/reconnect
|
|
184
|
+
the MCP server, and then invoke `refresh_advisories`. Enabling the group does not trigger a request
|
|
185
|
+
by itself.
|
|
186
|
+
|
|
187
|
+
Capability groups (`graph`, `search`, `source`, `health`, `build`, `retarget`, `online`) are
|
|
188
|
+
selectable through the final positional argument. Omitted caps use the safe default above; an
|
|
189
|
+
explicit list exposes exactly the named groups.
|
|
190
|
+
|
|
191
|
+
## Security model
|
|
192
|
+
|
|
193
|
+
Socket capability alerts describe expected powers of a local code-analysis tool; they are not
|
|
194
|
+
vulnerability findings. This is where each capability comes from and how it is controlled:
|
|
195
|
+
|
|
196
|
+
| Capability alert | Why it exists | Activation and boundary |
|
|
197
|
+
|---|---|---|
|
|
198
|
+
| Network access | `refresh_advisories` sends pinned package names and versions to OSV; `sync_graph` sends a versioned allowlist of graph metadata (relative paths, symbols and line ranges, import/dependency identifiers, edges and numeric metrics). It discards unknown graph fields and does not read source file bodies | `online` is disabled by default; each request requires a tool call, and sync additionally requires `WEAVATRIX_SYNC_URL` |
|
|
199
|
+
| Shell access | Local `git` powers staleness/change impact; `rg` accelerates search; timed-out Windows child processes may be terminated | Used only by the corresponding local operation; it does not imply network access |
|
|
200
|
+
| Debug / dynamic loading | Cache-busted `import()` hot-reloads local MCP tool modules; `createRequire` loads package metadata and parser dependencies | Loads files from the installed package; no `eval` |
|
|
201
|
+
| Environment access | Reads `WEAVATRIX_*` configuration; local child processes inherit the normal host environment | `WEAVATRIX_SYNC_TOKEN` is removed from every child-process and worker environment and read only by `sync_graph` |
|
|
202
|
+
| Filesystem access | Reads the active repository, graph, lockfiles and coverage reports; writes derived graphs and advisory cache | Realpath containment blocks traversal and symlink/junction escapes. `open_repo` is an explicit offline call that changes the active boundary; omit `retarget` in a custom capability list to pin one repository. The optional malware dependency scan may inspect installed dependency caches such as GOPATH |
|
|
203
|
+
| URL strings | Fixed OSV/documentation URLs plus a user-configured sync URL | A URL string causes no request by itself; only the two `online` tools perform requests |
|
|
204
|
+
|
|
205
|
+
`read_source` accepts repo-relative regular files only, caps a read at 2 MB, and refuses lexical or
|
|
206
|
+
realpath escapes. Graph-derived paths pass through the same boundary before analysis tools read
|
|
207
|
+
them. Report suspected vulnerabilities privately as described in [SECURITY.md](SECURITY.md).
|
|
112
208
|
|
|
113
209
|
## Languages
|
|
114
210
|
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
Security fixes are provided for the latest published version of Weavatrix. Upgrade to the newest
|
|
6
|
+
release before reporting an issue that may already be resolved.
|
|
7
|
+
|
|
8
|
+
## Reporting a vulnerability
|
|
9
|
+
|
|
10
|
+
Please do not open a public issue for a suspected vulnerability. Use GitHub's private vulnerability
|
|
11
|
+
reporting flow:
|
|
12
|
+
|
|
13
|
+
https://github.com/sergii-ziborov/weavatrix/security/advisories/new
|
|
14
|
+
|
|
15
|
+
Include the affected Weavatrix version, operating system, MCP client, enabled capability groups,
|
|
16
|
+
reproduction steps, expected boundary, observed behavior, and potential impact. Avoid including real
|
|
17
|
+
credentials, private source code, or other sensitive data in the report.
|
|
18
|
+
|
|
19
|
+
Reports will be investigated privately. Public disclosure should be coordinated after a fix or
|
|
20
|
+
mitigation is available so users have a reasonable opportunity to update.
|
|
21
|
+
|
|
22
|
+
## Security boundaries
|
|
23
|
+
|
|
24
|
+
The default MCP registration is offline. Every standard source, manifest, configuration and coverage
|
|
25
|
+
read is canonical-path-contained within the active repository and rejects traversal plus symlink or
|
|
26
|
+
junction escapes. The optional malware dependency scan may inspect installed dependency caches such
|
|
27
|
+
as GOPATH. The default `retarget` capability exposes `open_repo`, an explicit offline tool call that
|
|
28
|
+
intentionally changes the active repository; omit that group in a custom capability list to pin one
|
|
29
|
+
boundary. The optional `online` capability contains Weavatrix's HTTP tools; `sync_graph`
|
|
30
|
+
additionally requires a user-configured endpoint and sends only a versioned metadata allowlist,
|
|
31
|
+
discarding unknown fields from the local graph cache.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "weavatrix",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Weavatrix — code graph & blast-radius MCP server for AI coding agents: change impact, transitive dependents, health audit, duplicate detection, and coverage mapping over any local repository.",
|
|
6
6
|
"author": "Sergii Ziborov <sergii@edgehawk.io>",
|
|
@@ -25,10 +25,16 @@
|
|
|
25
25
|
"src",
|
|
26
26
|
"skill",
|
|
27
27
|
"README.md",
|
|
28
|
+
"SECURITY.md",
|
|
28
29
|
"LICENSE"
|
|
29
30
|
],
|
|
30
31
|
"scripts": {
|
|
31
|
-
"test": "node --test"
|
|
32
|
+
"test": "node --test",
|
|
33
|
+
"build:mcpb": "node scripts/build-mcpb.mjs",
|
|
34
|
+
"verify:release": "node scripts/verify-release.mjs"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
32
38
|
},
|
|
33
39
|
"keywords": [
|
|
34
40
|
"mcp",
|
|
@@ -44,7 +50,13 @@
|
|
|
44
50
|
"ai-agents"
|
|
45
51
|
],
|
|
46
52
|
"dependencies": {
|
|
47
|
-
"tree-sitter-wasms": "
|
|
48
|
-
"web-tree-sitter": "
|
|
53
|
+
"tree-sitter-wasms": "0.1.13",
|
|
54
|
+
"web-tree-sitter": "0.25.10"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@anthropic-ai/mcpb": "2.1.2"
|
|
58
|
+
},
|
|
59
|
+
"overrides": {
|
|
60
|
+
"tmp": "0.2.7"
|
|
49
61
|
}
|
|
50
62
|
}
|
package/skill/SKILL.md
CHANGED
|
@@ -5,9 +5,9 @@ description: Drive the weavatrix MCP — code graph, blast-radius (get_dependent
|
|
|
5
5
|
|
|
6
6
|
# weavatrix MCP
|
|
7
7
|
|
|
8
|
-
Structure-analysis tools over a prebuilt code graph plus the weavatrix analysis engines.
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
Structure-analysis tools over a prebuilt code graph plus the weavatrix analysis engines. The default
|
|
9
|
+
is offline and includes `open_repo` for one-call switching between local Git repositories. A custom
|
|
10
|
+
capability list that omits `retarget` pins the registration to its initial repository.
|
|
11
11
|
|
|
12
12
|
## Step 0 — if the tools are missing
|
|
13
13
|
|
|
@@ -15,22 +15,49 @@ Tools are named `mcp__weavatrix__…`. If none are available, ask the user to re
|
|
|
15
15
|
(`claude mcp add -s user weavatrix -- npx -y weavatrix <repoRoot>`; Codex:
|
|
16
16
|
`codex mcp add weavatrix -- npx -y weavatrix <repoRoot>`), then retry.
|
|
17
17
|
|
|
18
|
+
If only `refresh_advisories` or `sync_graph` is missing, do not diagnose a broken installation:
|
|
19
|
+
`online` is intentionally absent from the default capability list.
|
|
20
|
+
|
|
18
21
|
## Ground rules
|
|
19
22
|
|
|
23
|
+
- **Evidence, not verdicts**: treat audit, hub, orphan and duplicate output as hypotheses. Confirm a
|
|
24
|
+
finding in source and check framework/runtime conventions before deleting, merging or redesigning
|
|
25
|
+
code. A same-name/different-body pair is a divergence candidate, not proof of duplication.
|
|
20
26
|
- **Freshness**: every graph tool appends a staleness warning when the repo has commits newer than
|
|
21
|
-
the graph. Act on it: `rebuild_graph`.
|
|
27
|
+
the graph. Act on it: `rebuild_graph`. A normal `open_repo` builds missing graphs and upgrades
|
|
28
|
+
pre-`0.1.3` graphs to typed import edges; `build:false` deliberately refuses that upgrade.
|
|
22
29
|
- **Ambiguity**: `get_node`/`get_neighbors`/`get_dependents` disclose `matched N nodes; using the
|
|
23
30
|
best-connected` — read that note before trusting the answer; pass an exact node id to pin it.
|
|
31
|
+
- **Runtime versus compile time**: keep runtime cycles and TypeScript type-only coupling separate.
|
|
32
|
+
`module_map` and `change_impact` label the distinction; `god_nodes` ranks unique connectivity and
|
|
33
|
+
reports repeated references separately. Do not schedule a runtime-cycle refactor from type-only
|
|
34
|
+
edges alone.
|
|
35
|
+
- **Repository universe**: in Git repositories, graph and duplicate scans include tracked plus
|
|
36
|
+
non-ignored untracked files. If an old graph still contains packaged/generated output, rebuild it
|
|
37
|
+
before interpreting the result.
|
|
38
|
+
- **Coverage**: `coverage_map` reads an existing report. `unavailable` means no supported report was
|
|
39
|
+
found, not 0% coverage; do not rank testing risk from that state.
|
|
40
|
+
- **Audit completeness**: read `checks.osv.status` and `checks.malware.status`. For OSV, `OK` is
|
|
41
|
+
complete for the recorded dependency fingerprint; `PARTIAL` is incomplete or stale,
|
|
42
|
+
`NOT_CHECKED` has no repository-specific result, and `ERROR` means the local check failed. Treat
|
|
43
|
+
the same non-`OK` states as incomplete for malware scanning. Refresh OSV only when the user has
|
|
44
|
+
authorized adding the optional `online` capability group to the MCP registration and then
|
|
45
|
+
invoking `refresh_advisories`; enabling the group alone sends nothing.
|
|
24
46
|
- **Offline by design**: scans and graph queries run in-process against local files; coverage tools
|
|
25
47
|
read existing reports and never run tests. The ONLY network-touching tools live in the `online`
|
|
26
48
|
capability group and run solely when explicitly called: `refresh_advisories` (queries OSV.dev with
|
|
27
|
-
package names + versions so `run_audit` has fresh vulnerability data) and `sync_graph` (pushes
|
|
28
|
-
graph
|
|
29
|
-
|
|
49
|
+
package names + versions so `run_audit` has fresh vulnerability data) and `sync_graph` (pushes a
|
|
50
|
+
versioned allowlist of graph metadata, discarding unknown fields and never reading source file
|
|
51
|
+
bodies for sync, to a user-configured endpoint; disabled until
|
|
52
|
+
`WEAVATRIX_SYNC_URL` is set). `online` is
|
|
53
|
+
absent from the default capability set and must be enabled explicitly.
|
|
54
|
+
- **Repository boundary**: source reads and graph-derived paths are realpath-contained. `open_repo`
|
|
55
|
+
intentionally changes the active boundary through an explicit offline tool call. It is available
|
|
56
|
+
by default; omit `retarget` from a custom capability list to hide it.
|
|
30
57
|
|
|
31
58
|
## Recipes
|
|
32
59
|
|
|
33
|
-
- **Orient in
|
|
60
|
+
- **Orient in the configured repo**: `module_map` → `list_communities` → `god_nodes`.
|
|
34
61
|
- **Refactor safety for one symbol**: `get_dependents` → `coverage_map` (low coverage × many
|
|
35
62
|
dependents ⇒ write tests first) → `read_source`.
|
|
36
63
|
- **Pre-PR review of your current changes**: `change_impact` (auto merge-base; includes uncommitted
|
|
@@ -41,15 +68,45 @@ Tools are named `mcp__weavatrix__…`. If none are available, ask the user to re
|
|
|
41
68
|
(cycle broken? new module dependency introduced? symbols orphaned?); re-query later with
|
|
42
69
|
`graph_diff` (optionally scoped by `path`). The semantic complement to the textual git diff.
|
|
43
70
|
- **Health sweep**: `run_audit` (filter by `category`/`min_severity`) → `find_duplicates` →
|
|
44
|
-
`coverage_map`.
|
|
71
|
+
`coverage_map`. Inspect the source behind shortlisted findings before proposing edits.
|
|
45
72
|
- **API inventory**: `list_endpoints`.
|
|
46
73
|
- **Find code**: `search_code` (regex + glob) → `get_node` → `read_source`.
|
|
47
|
-
- **Another repo**: `list_known_repos` → `open_repo <path>`
|
|
48
|
-
|
|
74
|
+
- **Another repo**: `list_known_repos` → `open_repo <path>`
|
|
75
|
+
(builds or upgrades the graph when needed; `build:false` probes without building).
|
|
76
|
+
|
|
77
|
+
## Repository-specific conventions
|
|
78
|
+
|
|
79
|
+
Weavatrix understands nearest workspace manifests, nested `tsconfig`/`jsconfig` aliases,
|
|
80
|
+
framework-owned runtime peers such as Next.js + `react-dom`, and Next.js App Router route exports.
|
|
81
|
+
For project-specific entry points or Python dependencies supplied by an external runtime, add
|
|
82
|
+
`.weavatrix-deps.json` at the repository root:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"entrypoints": ["scripts/publish-release.mjs"],
|
|
87
|
+
"python": {
|
|
88
|
+
"managedDependencies": ["numpy", "openvino-genai"],
|
|
89
|
+
"ignoreDependencies": ["vendor-sdk"]
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Keep exceptions narrow. `managedDependencies` documents modules provided outside the repo's Python
|
|
95
|
+
manifest; `ignoreDependencies` suppresses intentionally unresolved imports.
|
|
96
|
+
|
|
97
|
+
## Sync
|
|
98
|
+
|
|
99
|
+
`sync_graph` uses allowlisted payload v2, including typed-edge metadata but no source bodies. A graph
|
|
100
|
+
built before `0.1.3` must be rebuilt first; normal `open_repo` does this automatically. Sync remains
|
|
101
|
+
unavailable until the user opts into `online` and configures `WEAVATRIX_SYNC_URL`.
|
|
49
102
|
|
|
50
103
|
## Troubleshooting
|
|
51
104
|
|
|
52
|
-
- `Graph unavailable` → `open_repo`
|
|
105
|
+
- `Graph unavailable` → `rebuild_graph`; `open_repo` can select another valid repository path unless
|
|
106
|
+
the registration deliberately omitted `retarget`.
|
|
107
|
+
- `refresh_advisories` is unavailable → with the user's approval, re-register/reconfigure the MCP
|
|
108
|
+
capability list to include `online`, reconnect it, and then invoke the tool. Do not enable network
|
|
109
|
+
access merely to turn `NOT_CHECKED` into a cosmetic green state.
|
|
53
110
|
- `No coverage report` → run the repo's own tests with coverage (`vitest run --coverage`,
|
|
54
111
|
`jest --coverage`, `pytest --cov --cov-report=json`, `go test -coverprofile=coverage.out`),
|
|
55
112
|
then re-call.
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// graph's known files. Split out of graph-builder-analysis.js (pure except for reading the repo's coverage files).
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import { join, relative, resolve } from "node:path";
|
|
6
|
+
import { createRepoBoundary } from "../repo-path.js";
|
|
6
7
|
|
|
7
8
|
export function normRepoPath(value) {
|
|
8
9
|
return String(value || "").replace(/\\/g, "/").replace(/^\.\//, "");
|
|
@@ -210,20 +211,22 @@ function parseGoCoverage(map, filePath, knownFiles, repoRoot) {
|
|
|
210
211
|
export function readCoverageForRepo(repoRoot, knownFiles) {
|
|
211
212
|
const out = new Map();
|
|
212
213
|
if (!repoRoot) return out;
|
|
214
|
+
const boundary = createRepoBoundary(repoRoot);
|
|
213
215
|
const candidates = [
|
|
214
|
-
[
|
|
215
|
-
[
|
|
216
|
-
[
|
|
217
|
-
[
|
|
218
|
-
[
|
|
219
|
-
[
|
|
220
|
-
[
|
|
221
|
-
[
|
|
216
|
+
["coverage/coverage-summary.json", parseIstanbulSummary],
|
|
217
|
+
["coverage/coverage-final.json", parseIstanbulFinal],
|
|
218
|
+
["coverage/lcov.info", parseLcov],
|
|
219
|
+
["lcov.info", parseLcov],
|
|
220
|
+
["coverage/coverage.json", parseCoveragePyJson],
|
|
221
|
+
["coverage.json", parseCoveragePyJson],
|
|
222
|
+
["coverage.out", parseGoCoverage],
|
|
223
|
+
["cover.out", parseGoCoverage]
|
|
222
224
|
];
|
|
223
|
-
for (const [
|
|
224
|
-
|
|
225
|
+
for (const [candidate, parser] of candidates) {
|
|
226
|
+
const resolved = boundary.resolve(candidate);
|
|
227
|
+
if (!resolved.ok || !existsSync(resolved.path)) continue;
|
|
225
228
|
try {
|
|
226
|
-
parser(out, path, knownFiles, repoRoot);
|
|
229
|
+
parser(out, resolved.path, knownFiles, repoRoot);
|
|
227
230
|
} catch {
|
|
228
231
|
/* malformed/foreign coverage report: ignore */
|
|
229
232
|
}
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
// `analyzeDeadCode(graph, repoRoot)` is the thin fs wrapper. Works on ANY graph-builder-schema graph (built-in OR
|
|
7
7
|
// graph-builder) — it only needs {nodes, links} + the source text. See [[graph-builder-internalization]].
|
|
8
8
|
import { readFileSync } from "node:fs";
|
|
9
|
-
import {
|
|
9
|
+
import { posix } from "node:path";
|
|
10
|
+
import { createRepoBoundary } from "../repo-path.js";
|
|
10
11
|
|
|
11
12
|
const IDENT_RE = /[A-Za-z_$][\w$]*/g;
|
|
12
13
|
const bareName = (label) => String(label || "").replace(/\s*\(.*$/, "").replace(/[()]/g, "").trim();
|
|
@@ -15,7 +16,80 @@ const bareName = (label) => String(label || "").replace(/\s*\(.*$/, "").replace(
|
|
|
15
16
|
export const ENTRY_FILE = /(^|[\\/])(index|main|app|server|cli|cmd|bootstrap|entry|run|__main__|manage|wsgi|asgi|setup|conftest)\.[a-z0-9]+$|(^|[\\/])(bin|cmd)[\\/]|(^|[\\/])main\.go$/i;
|
|
16
17
|
const TEST_FILE = /(^|[\\/])[^\\/]*[._-](test|spec)\.[a-z0-9]+$|(^|[\\/])(test|tests|__tests__|spec)[\\/]/i;
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
// Framework-owned entry modules are invoked by convention rather than a source import. Keep this narrow:
|
|
20
|
+
// these are Next.js App/Pages Router surfaces and framework metadata files, not every file under `app/`.
|
|
21
|
+
export const NEXT_ENTRY_FILE = /(^|\/)(?:src\/)?app\/(?:.*\/)?(?:page|layout|template|loading|error|global-error|not-found|default|route|robots|sitemap|manifest|opengraph-image|twitter-image|icon|apple-icon)\.[cm]?[jt]sx?$|(^|\/)(?:src\/)?pages\/(?!.*\/(?:components?|lib|utils?)\/).+\.[cm]?[jt]sx?$|(^|\/)(?:middleware|instrumentation)\.[cm]?[jt]s$/i;
|
|
22
|
+
export const isFrameworkEntryFile = (file) => NEXT_ENTRY_FILE.test(String(file || "").replace(/\\/g, "/"));
|
|
23
|
+
|
|
24
|
+
const lineOfNode = (n) => {
|
|
25
|
+
const m = /@(\d+)$/.exec(String(n.id || "")) || /L(\d+)/.exec(String(n.source_location || ""));
|
|
26
|
+
return m ? Number(m[1]) : 0;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Older graphs marked every method of an exported class as exported. Verify that an `exported` node is
|
|
30
|
+
// actually a module-surface declaration before reporting it. New graphs expose symbol_kind/member_of and
|
|
31
|
+
// already keep members unexported, but the source check preserves correctness for cached v0.1.2 graphs.
|
|
32
|
+
function hasModuleExport(source, node, name) {
|
|
33
|
+
if (node.member_of || node.symbol_kind === "method") return false;
|
|
34
|
+
const lines = String(source || "").split(/\r?\n/);
|
|
35
|
+
const line = lines[Math.max(0, lineOfNode(node) - 1)] || "";
|
|
36
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
37
|
+
const direct = new RegExp(`\\bexport\\s+(?:default\\s+)?(?:(?:declare|abstract|async)\\s+)*(?:function|class|const|let|var|enum|interface|type|namespace)\\s+${escaped}\\b`);
|
|
38
|
+
if (direct.test(line) || direct.test(String(source || ""))) return true;
|
|
39
|
+
const exportDefault = new RegExp(`\\bexport\\s+default\\s+${escaped}\\b`);
|
|
40
|
+
if (exportDefault.test(String(source || ""))) return true;
|
|
41
|
+
// `export { local }`, `export { local as publicName }` and CommonJS explicit exports.
|
|
42
|
+
const exportList = new RegExp(`\\bexport\\s*\\{[^}]*\\b${escaped}\\b[^}]*\\}`, "s");
|
|
43
|
+
const commonJs = new RegExp(`(?:module\\.exports\\s*=\\s*\\{[^}]*\\b${escaped}\\b|(?:module\\.exports|exports)\\.${escaped}\\s*=)`, "s");
|
|
44
|
+
return exportList.test(String(source || "")) || commonJs.test(String(source || ""));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function namespaceConsumedFiles(sources, graph) {
|
|
48
|
+
const files = new Set(sources.keys());
|
|
49
|
+
const consumed = new Set();
|
|
50
|
+
const extensions = ["", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"];
|
|
51
|
+
const resolve = (from, spec) => {
|
|
52
|
+
if (!spec.startsWith(".")) return "";
|
|
53
|
+
const base = posix.normalize(posix.join(posix.dirname(from.replace(/\\/g, "/")), spec));
|
|
54
|
+
for (const ext of extensions) {
|
|
55
|
+
const p = `${base}${ext}`;
|
|
56
|
+
if (files.has(p)) return p;
|
|
57
|
+
}
|
|
58
|
+
for (const ext of extensions.slice(1)) {
|
|
59
|
+
const p = `${base}/index${ext}`;
|
|
60
|
+
if (files.has(p)) return p;
|
|
61
|
+
}
|
|
62
|
+
return "";
|
|
63
|
+
};
|
|
64
|
+
for (const [file, textValue] of sources) {
|
|
65
|
+
const text = String(textValue || "");
|
|
66
|
+
const patterns = [
|
|
67
|
+
/\bimport\s+\*\s+as\s+[A-Za-z_$][\w$]*\s+from\s*(["'])([^"']+)\1/g,
|
|
68
|
+
/\bexport\s+\*\s+from\s*(["'])([^"']+)\1/g,
|
|
69
|
+
/\b(?:const|let|var)\s+[A-Za-z_$][\w$]*\s*=\s*require\(\s*(["'])([^"']+)\1\s*\)/g,
|
|
70
|
+
];
|
|
71
|
+
for (const re of patterns) {
|
|
72
|
+
let m;
|
|
73
|
+
while ((m = re.exec(text))) {
|
|
74
|
+
const target = resolve(file, m[2]);
|
|
75
|
+
if (target) consumed.add(target);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// New graphs retain the exact specifier on file import edges, which also resolves namespace imports
|
|
80
|
+
// expressed through tsconfig aliases (the relative-path fallback above intentionally cannot guess those).
|
|
81
|
+
for (const link of graph.links || []) {
|
|
82
|
+
if (link.relation !== "imports" && link.relation !== "re_exports") continue;
|
|
83
|
+
const from = String(link.source?.id || link.source || ""), target = String(link.target?.id || link.target || "");
|
|
84
|
+
if (!link.specifier || from.includes("#") || target.includes("#") || !sources.has(from)) continue;
|
|
85
|
+
const spec = String(link.specifier).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
86
|
+
const text = String(sources.get(from) || "");
|
|
87
|
+
if (new RegExp(`(?:import\\s+\\*\\s+as\\s+[A-Za-z_$][\\w$]*\\s+from|export\\s+\\*\\s+from)\\s*["']${spec}["']`).test(text)) consumed.add(target);
|
|
88
|
+
}
|
|
89
|
+
return consumed;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function computeDead(graph, sources, { entrySet = new Set() } = {}) {
|
|
19
93
|
const nodes = graph.nodes || [], links = graph.links || [];
|
|
20
94
|
const ep = (v) => (v && typeof v === "object" ? v.id : v);
|
|
21
95
|
const inbound = new Set();
|
|
@@ -73,7 +147,7 @@ export function computeDead(graph, sources) {
|
|
|
73
147
|
const deadFiles = [];
|
|
74
148
|
for (const n of nodes) {
|
|
75
149
|
if (String(n.id).includes("#")) continue; // file nodes only
|
|
76
|
-
if (inbound.has(n.id) || ENTRY_FILE.test(n.source_file)) continue;
|
|
150
|
+
if (inbound.has(n.id) || ENTRY_FILE.test(n.source_file) || isFrameworkEntryFile(n.source_file) || entrySet.has(n.source_file)) continue;
|
|
77
151
|
const syms = symsByFile.get(n.source_file) || [];
|
|
78
152
|
if (syms.length && syms.every((s) => deadSet.has(s.id))) deadFiles.push({ file: n.source_file, reason: "not imported; all symbols unreferenced" });
|
|
79
153
|
}
|
|
@@ -90,19 +164,21 @@ export function computeDead(graph, sources) {
|
|
|
90
164
|
// with no inbound non-contains edge whose bare name occurs in NO file other than its own. Same-file-only
|
|
91
165
|
// usage means the symbol is alive but the export is not — still worth surfacing. Pure like computeDead.
|
|
92
166
|
// dynamicTargets = files reached via dynamic import() — their exports are consumed at runtime, skip them.
|
|
93
|
-
export function computeUnusedExports(graph, sources, { dynamicTargets = new Set() } = {}) {
|
|
167
|
+
export function computeUnusedExports(graph, sources, { dynamicTargets = new Set(), entrySet = new Set() } = {}) {
|
|
94
168
|
const nodes = graph.nodes || [], links = graph.links || [];
|
|
95
169
|
const ep = (v) => (v && typeof v === "object" ? v.id : v);
|
|
96
170
|
const inbound = new Set();
|
|
97
171
|
for (const l of links) if (l.relation !== "contains") inbound.add(ep(l.target));
|
|
98
172
|
|
|
173
|
+
const namespaceConsumed = namespaceConsumedFiles(sources, graph);
|
|
99
174
|
const candidates = [];
|
|
100
175
|
const wanted = new Set();
|
|
101
176
|
for (const n of nodes) {
|
|
102
177
|
if (!n.exported || !String(n.id).includes("#") || inbound.has(n.id)) continue;
|
|
103
|
-
if (ENTRY_FILE.test(n.source_file) || dynamicTargets.has(n.source_file)) continue;
|
|
178
|
+
if (ENTRY_FILE.test(n.source_file) || isFrameworkEntryFile(n.source_file) || entrySet.has(n.source_file) || dynamicTargets.has(n.source_file) || namespaceConsumed.has(n.source_file)) continue;
|
|
104
179
|
const nm = bareName(n.label);
|
|
105
180
|
if (!nm || !/^[A-Za-z_$]/.test(nm)) continue;
|
|
181
|
+
if (!hasModuleExport(sources.get(n.source_file), n, nm)) continue;
|
|
106
182
|
candidates.push({ n, nm });
|
|
107
183
|
wanted.add(nm);
|
|
108
184
|
}
|
|
@@ -130,7 +206,12 @@ export function computeUnusedExports(graph, sources, { dynamicTargets = new Set(
|
|
|
130
206
|
export function analyzeDeadCode(graph, repoRoot) {
|
|
131
207
|
const sources = new Map();
|
|
132
208
|
const files = new Set();
|
|
209
|
+
const boundary = createRepoBoundary(repoRoot);
|
|
133
210
|
for (const n of graph.nodes || []) if (n.source_file) files.add(n.source_file);
|
|
134
|
-
for (const f of files) {
|
|
211
|
+
for (const f of files) {
|
|
212
|
+
const resolved = boundary.resolve(f);
|
|
213
|
+
if (!resolved.ok) continue;
|
|
214
|
+
try { sources.set(f, readFileSync(resolved.path, "utf8")); } catch { /* file gone */ }
|
|
215
|
+
}
|
|
135
216
|
return computeDead(graph, sources);
|
|
136
217
|
}
|