weavatrix 0.1.1 → 0.1.2

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.
Files changed (35) hide show
  1. package/README.md +57 -16
  2. package/SECURITY.md +31 -0
  3. package/package.json +16 -4
  4. package/skill/SKILL.md +16 -10
  5. package/src/analysis/coverage-reports.js +14 -11
  6. package/src/analysis/dead-check.js +7 -2
  7. package/src/analysis/duplicates.compute.js +221 -215
  8. package/src/analysis/duplicates.js +15 -15
  9. package/src/analysis/duplicates.run.js +52 -51
  10. package/src/analysis/duplicates.tokenize.js +182 -182
  11. package/src/analysis/endpoints.js +5 -3
  12. package/src/analysis/graph-analysis.aggregate.js +5 -1
  13. package/src/analysis/internal-audit.collect.js +37 -11
  14. package/src/analysis/internal-audit.run.js +8 -5
  15. package/src/build-graph.js +2 -1
  16. package/src/child-env.js +7 -0
  17. package/src/graph/internal-builder.build.js +1 -1
  18. package/src/graph/internal-builder.langs.js +8 -3
  19. package/src/graph/internal-builder.resolvers.js +10 -3
  20. package/src/mcp/catalog.mjs +9 -7
  21. package/src/mcp/graph-context.mjs +8 -5
  22. package/src/mcp/sync-payload.mjs +102 -0
  23. package/src/mcp/tools-actions.mjs +22 -13
  24. package/src/mcp/tools-impact.mjs +3 -2
  25. package/src/mcp-rg.mjs +2 -1
  26. package/src/mcp-server.mjs +22 -16
  27. package/src/mcp-source-tools.mjs +16 -5
  28. package/src/process.js +4 -3
  29. package/src/repo-path.js +53 -0
  30. package/src/security/installed.js +44 -31
  31. package/src/security/malware-heuristics.exclusions.js +134 -134
  32. package/src/security/malware-heuristics.js +15 -15
  33. package/src/security/malware-heuristics.roots.js +142 -142
  34. package/src/security/malware-heuristics.scan.js +85 -85
  35. 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. One server for every repo on your machine. Local-first: your code
9
- never leaves it.**
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 — one user-level registration serves every repo on the machine:
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,10 @@ 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? Just ask the agent to call `rebuild_graph` (or `open_repo`) it builds missing
67
- graphs itself. From then on, `open_repo` retargets the same running server at **any** other local
68
- repository; never register a second copy.
80
+ No graph yet? Ask the agent to call `rebuild_graph`; it builds the missing graph locally. When the
81
+ `open_repo` can change the active repository and build its graph. Retargeting is offline but
82
+ intentionally changes the filesystem boundary for subsequent tools; omit `retarget` from an explicit
83
+ capability list when a registration must stay pinned to one repository.
69
84
 
70
85
  An agent skill with recipes ships in [skill/SKILL.md](skill/SKILL.md) — install as
71
86
  `~/.claude/skills/weavatrix/SKILL.md`.
@@ -74,7 +89,7 @@ An agent skill with recipes ships in [skill/SKILL.md](skill/SKILL.md) — instal
74
89
 
75
90
  **graph** — `graph_stats`, `get_node`, `get_neighbors`, `query_graph`, `god_nodes`,
76
91
  `shortest_path`, `get_community`, `list_communities`, `module_map`, `get_dependents`,
77
- `change_impact`, `graph_diff`, `list_known_repos`
92
+ `change_impact`, `graph_diff`
78
93
 
79
94
  **search / source** — `search_code` (ripgrep-backed, pure-Node fallback), `read_source` (a
80
95
  symbol's actual code in one hop), `list_endpoints` (HTTP route inventory:
@@ -87,7 +102,10 @@ cycles, orphans, boundary rules, offline OSV vulnerabilities + typosquat + lockf
87
102
  connectivity — tests are never executed)
88
103
 
89
104
  **build** — `rebuild_graph` (reports the structural delta, keeps the prior state as
90
- `graph.prev.json`), `open_repo`
105
+ `graph.prev.json`)
106
+
107
+ **retarget** *(enabled by default, offline, explicit tool call)* — `open_repo`, `list_known_repos`;
108
+ changes the active repository boundary
91
109
 
92
110
  **online** *(explicit opt-in — see Privacy)* — `refresh_advisories`, `sync_graph`
93
111
 
@@ -97,18 +115,41 @@ files under `src/mcp/` change — no reconnect needed.
97
115
 
98
116
  ## Privacy: local-first, offline by design
99
117
 
100
- Graph queries, audits and clone scans run in-process against local files. Exactly two tools can
101
- touch the network, both in the `online` capability group and both inert until explicitly invoked:
118
+ Graph queries, audits, clone scans and repository switching run locally. The default capability set
119
+ is `graph,search,source,health,build,retarget`: no Weavatrix HTTP requests. `open_repo` changes the
120
+ active local boundary only when called. Weavatrix itself initiates outbound HTTP only from two
121
+ tools; both require the explicit `online` group and a tool call:
102
122
 
103
123
  - `refresh_advisories` — queries [OSV.dev](https://osv.dev) with your lockfile's package
104
124
  **names + versions** (that is what an OSV query is; never source code) and caches the advisories
105
125
  in `~/.weavatrix/advisories.json`. `run_audit` then matches against that store fully offline.
106
- - `sync_graph` — pushes `graph.json` (file paths, symbol names, edges — never file contents) to an
107
- endpoint **you** configure via `WEAVATRIX_SYNC_URL` / `WEAVATRIX_SYNC_TOKEN`. Off by default.
108
-
109
- Capability groups (`graph`, `search`, `source`, `health`, `build`, `online`) are selectable per
110
- registration via a fifth argument — a comma-separated list. Omit `online` from it and those tools
111
- don't exist for that client.
126
+ - `sync_graph` — constructs a versioned, allowlisted payload from `graph.json`: relative paths,
127
+ symbol names and line ranges, import/dependency identifiers, edges and numeric metrics. Unknown
128
+ fields are discarded; source file bodies are never read for sync or included in the payload. The
129
+ endpoint is **yours**, configured via `WEAVATRIX_SYNC_URL` / `WEAVATRIX_SYNC_TOKEN`. Off by default.
130
+ Graphs built before `0.1.2` must be rebuilt once before syncing.
131
+
132
+ Capability groups (`graph`, `search`, `source`, `health`, `build`, `retarget`, `online`) are
133
+ selectable through the final positional argument. Omitted caps use the safe default above; an
134
+ explicit list exposes exactly the named groups.
135
+
136
+ ## Security model
137
+
138
+ Socket capability alerts describe expected powers of a local code-analysis tool; they are not
139
+ vulnerability findings. This is where each capability comes from and how it is controlled:
140
+
141
+ | Capability alert | Why it exists | Activation and boundary |
142
+ |---|---|---|
143
+ | 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` |
144
+ | 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 |
145
+ | 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` |
146
+ | 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` |
147
+ | 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 |
148
+ | 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 |
149
+
150
+ `read_source` accepts repo-relative regular files only, caps a read at 2 MB, and refuses lexical or
151
+ realpath escapes. Graph-derived paths pass through the same boundary before analysis tools read
152
+ them. Report suspected vulnerabilities privately as described in [SECURITY.md](SECURITY.md).
112
153
 
113
154
  ## Languages
114
155
 
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.1",
3
+ "version": "0.1.2",
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": "^0.1.13",
48
- "web-tree-sitter": "^0.25.3"
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. One running
9
- server handles ANY local repository via `open_repo` never register a second weavatrix server for a
10
- sibling repo.
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
 
@@ -24,13 +24,18 @@ Tools are named `mcp__weavatrix__…`. If none are available, ask the user to re
24
24
  - **Offline by design**: scans and graph queries run in-process against local files; coverage tools
25
25
  read existing reports and never run tests. The ONLY network-touching tools live in the `online`
26
26
  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.json to a user-configured endpoint; disabled until `WEAVATRIX_SYNC_URL` is set). Registering
29
- the server with a caps list that omits `online` removes both.
27
+ package names + versions so `run_audit` has fresh vulnerability data) and `sync_graph` (pushes a
28
+ versioned allowlist of graph metadata, discarding unknown fields and never reading source file
29
+ bodies for sync, to a user-configured endpoint; disabled until
30
+ `WEAVATRIX_SYNC_URL` is set). `online` is
31
+ absent from the default capability set and must be enabled explicitly.
32
+ - **Repository boundary**: source reads and graph-derived paths are realpath-contained. `open_repo`
33
+ intentionally changes the active boundary through an explicit offline tool call. It is available
34
+ by default; omit `retarget` from a custom capability list to hide it.
30
35
 
31
36
  ## Recipes
32
37
 
33
- - **Orient in an unfamiliar repo**: `open_repo` → `module_map` → `list_communities` → `god_nodes`.
38
+ - **Orient in the configured repo**: `module_map` → `list_communities` → `god_nodes`.
34
39
  - **Refactor safety for one symbol**: `get_dependents` → `coverage_map` (low coverage × many
35
40
  dependents ⇒ write tests first) → `read_source`.
36
41
  - **Pre-PR review of your current changes**: `change_impact` (auto merge-base; includes uncommitted
@@ -44,12 +49,13 @@ Tools are named `mcp__weavatrix__…`. If none are available, ask the user to re
44
49
  `coverage_map`.
45
50
  - **API inventory**: `list_endpoints`.
46
51
  - **Find code**: `search_code` (regex + glob) → `get_node` → `read_source`.
47
- - **Another repo**: `list_known_repos` → `open_repo <path>` (builds the graph when missing — minutes
48
- on large repos; `build:false` probes without building).
52
+ - **Another repo**: `list_known_repos` → `open_repo <path>`
53
+ (builds the graph when missing; `build:false` probes without building).
49
54
 
50
55
  ## Troubleshooting
51
56
 
52
- - `Graph unavailable` → `open_repo` with a valid repo path.
57
+ - `Graph unavailable` → `rebuild_graph`; `open_repo` can select another valid repository path unless
58
+ the registration deliberately omitted `retarget`.
53
59
  - `No coverage report` → run the repo's own tests with coverage (`vitest run --coverage`,
54
60
  `jest --coverage`, `pytest --cov --cov-report=json`, `go test -coverprofile=coverage.out`),
55
61
  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
- [join(repoRoot, "coverage", "coverage-summary.json"), parseIstanbulSummary],
215
- [join(repoRoot, "coverage", "coverage-final.json"), parseIstanbulFinal],
216
- [join(repoRoot, "coverage", "lcov.info"), parseLcov],
217
- [join(repoRoot, "lcov.info"), parseLcov],
218
- [join(repoRoot, "coverage", "coverage.json"), parseCoveragePyJson],
219
- [join(repoRoot, "coverage.json"), parseCoveragePyJson],
220
- [join(repoRoot, "coverage.out"), parseGoCoverage],
221
- [join(repoRoot, "cover.out"), parseGoCoverage]
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 [path, parser] of candidates) {
224
- if (!existsSync(path)) continue;
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,7 @@
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 { join } from "node:path";
9
+ import { createRepoBoundary } from "../repo-path.js";
10
10
 
11
11
  const IDENT_RE = /[A-Za-z_$][\w$]*/g;
12
12
  const bareName = (label) => String(label || "").replace(/\s*\(.*$/, "").replace(/[()]/g, "").trim();
@@ -130,7 +130,12 @@ export function computeUnusedExports(graph, sources, { dynamicTargets = new Set(
130
130
  export function analyzeDeadCode(graph, repoRoot) {
131
131
  const sources = new Map();
132
132
  const files = new Set();
133
+ const boundary = createRepoBoundary(repoRoot);
133
134
  for (const n of graph.nodes || []) if (n.source_file) files.add(n.source_file);
134
- for (const f of files) { try { sources.set(f, readFileSync(join(repoRoot, f), "utf8")); } catch { /* file gone */ } }
135
+ for (const f of files) {
136
+ const resolved = boundary.resolve(f);
137
+ if (!resolved.ok) continue;
138
+ try { sources.set(f, readFileSync(resolved.path, "utf8")); } catch { /* file gone */ }
139
+ }
135
140
  return computeDead(graph, sources);
136
141
  }