claude-dependency-mapper
Standardizes dependency-graph generation across all of the user's projects with a
single self-contained Node script. Produces two artifacts from the same analysis:
- Human graph — an Obsidian vault in
obsidian-graph/ (one note per file with
[[wikilinks]]). Open the folder as a vault → Graph View (Ctrl/Cmd+G).
- AI graph — a compact single-file
PROJECT_MAP.md (adjacency list), generated
only for larger projects where a precomputed index saves more tokens than it
costs to keep fresh.
Route decision (by code-file count)
- < 40 code files → "directed": small enough that Grep/Glob exploration is cheap;
no
PROJECT_MAP.md is maintained (a stale map would waste more tokens than it saves).
CLAUDE.md tells future sessions to navigate by directed search.
- >= 40 code files → "compact": a precomputed
PROJECT_MAP.md is generated and
CLAUDE.md instructs future sessions to read it first before fanning out.
The Obsidian human vault is generated in both cases.
Procedure
Confirm Node.js is available (node --version). The script (Node, zero-dependency)
parses imports for several languages — detected automatically by file extension:
- JavaScript/TypeScript (
.ts .tsx .js .jsx .mjs .cjs .mts .cts .vue .svelte .astro)
- Python (
.py .pyi)
- Rust (
.rs)
- Dart (
.dart)
Resolution is best-effort and regex-based (no compiler/LSP), so it's great for
visualization but not a perfectly accurate graph — see Notes/limitations. If the
project is in another language, tell the user it isn't parsed yet (adding one means
adding an entry to the LANGS registry in gen-graph.mjs).
Run the generator against the current project (it does everything and is idempotent):
node "<SKILL_DIR>/gen-graph.mjs"
where <SKILL_DIR> is this skill's directory. The script:
- finds the source base (
src/ if present, else the project root, excluding
node_modules, build dirs, .git, etc.),
- generates
obsidian-graph/,
- decides the route and, if "compact", writes
PROJECT_MAP.md,
- inserts/refreshes a managed block in
.gitignore (ignoring obsidian-graph/
and PROJECT_MAP.md),
- inserts/refreshes a managed block in
CLAUDE.md with the route-specific guidance,
- installs/refreshes a
PostToolUse hook in <project>/.claude/settings.json
(matcher Edit|Write|MultiEdit) that re-runs the generator in --hook mode,
- prints a final
RESULT: files=<n> route=<directed|compact> ... line.
Optional args: pass a project path as argv[2]; override the threshold with
--threshold=N; force a route with --route=directed|compact.
Read the script's RESULT: line and report to the user: file count, chosen route,
what was created, and how to open the Obsidian vault.
Tell the user the graphs now auto-refresh: the installed hook re-runs the
generator whenever a source code file under the source base is edited. The hook runs
quietly, only regenerates the graphs (it does not touch .gitignore/CLAUDE.md/the
hook), is gated to source files (other edits are ignored, no loop on obsidian-graph/),
and never fails an edit. Re-run /claude-dependency-mapper manually only to re-apply
config or change the threshold/route.
Auto-refresh hook
installHook() writes a project-scoped PostToolUse hook to
<project>/.claude/settings.json:
{ "hooks": { "PostToolUse": [
{ "matcher": "Edit|Write|MultiEdit",
"hooks": [ { "type": "command", "command": "node \"<skill>/gen-graph.mjs\" --hook" } ] } ] } }
In --hook mode the script reads the PostToolUse JSON from stdin, extracts
tool_input.file_path, and exits 0 immediately unless that file is a code file under the
source base (so non-source edits and writes inside obsidian-graph/ are no-ops). It then
regenerates the graphs with --quiet --no-config semantics, wrapped in try/catch so a
failure can never disrupt editing. Re-installing is idempotent (prior claude-dependency-mapper
hook entries are removed first), and other existing hooks in settings.json are preserved.
Notes / limitations
- The whole project tree is scanned (minus
node_modules, build dirs, .git, virtual
envs, target, etc.); note ids are paths relative to the project root.
- Resolution is regex-based and best-effort per language:
- JS/TS: relative paths +
@/ → source-base alias. Other tsconfig paths aliases
and dynamically string-built imports are not resolved.
- Python: relative (
from . / .. import) and absolute (import a.b.c,
from a.b import x) resolved against the project root and src/. Star/as ignored;
third-party/stdlib modules appear as external nodes.
- Rust:
mod name; declarations give accurate file edges; use crate::/self::/super::
are resolved best-effort to files (the trailing item name is dropped), other use
paths are treated as external crates. Grouped use a::{b, c} edges are not expanded.
- Dart: relative imports and
package:<self>/… (resolved via pubspec.yaml name)
become edges; dart: and other package: imports are external.
- Imports that don't resolve to a file in the project are listed under "Unresolved" in
the Obsidian note (kept out of the graph edges).
- Both graphs are gitignored by design. If the user later wants
PROJECT_MAP.md shared
with a team, remove it from the managed .gitignore block.
1---2name: claude-dependency-mapper3description: Map a project's dependency graph. Generates an Obsidian vault (obsidian-graph/) for human visualization and, for medium/large codebases, a compact PROJECT_MAP.md for token-efficient AI navigation. Decides the route by file count, wires up CLAUDE.md, adds .gitignore entries so the graphs are not committed, and installs a PostToolUse hook that auto-refreshes the graphs on source edits. Use when the user runs /claude-dependency-mapper or asks to set up / refresh the project dependency graph.4---56# claude-dependency-mapper78Standardizes dependency-graph generation across all of the user's projects with a9single self-contained Node script. Produces two artifacts from the same analysis:10111. **Human graph** — an Obsidian vault in `obsidian-graph/` (one note per file with12 `[[wikilinks]]`). Open the folder as a vault → Graph View (Ctrl/Cmd+G).132. **AI graph** — a compact single-file `PROJECT_MAP.md` (adjacency list), generated14 **only for larger projects** where a precomputed index saves more tokens than it15 costs to keep fresh.1617## Route decision (by code-file count)1819- **< 40 code files → "directed"**: small enough that Grep/Glob exploration is cheap;20 no `PROJECT_MAP.md` is maintained (a stale map would waste more tokens than it saves).21 CLAUDE.md tells future sessions to navigate by directed search.22- **>= 40 code files → "compact"**: a precomputed `PROJECT_MAP.md` is generated and23 CLAUDE.md instructs future sessions to read it first before fanning out.2425The Obsidian human vault is generated in **both** cases.2627## Procedure28291. Confirm Node.js is available (`node --version`). The script (Node, zero-dependency)30 parses imports for several languages — detected automatically by file extension:31 - **JavaScript/TypeScript** (`.ts .tsx .js .jsx .mjs .cjs .mts .cts .vue .svelte .astro`)32 - **Python** (`.py .pyi`)33 - **Rust** (`.rs`)34 - **Dart** (`.dart`)3536 Resolution is best-effort and regex-based (no compiler/LSP), so it's great for37 visualization but not a perfectly accurate graph — see Notes/limitations. If the38 project is in another language, tell the user it isn't parsed yet (adding one means39 adding an entry to the `LANGS` registry in `gen-graph.mjs`).40412. Run the generator against the current project (it does everything and is idempotent):4243 ```44 node "<SKILL_DIR>/gen-graph.mjs"45 ```4647 where `<SKILL_DIR>` is this skill's directory. The script:48 - finds the source base (`src/` if present, else the project root, excluding49 `node_modules`, build dirs, `.git`, etc.),50 - generates `obsidian-graph/`,51 - decides the route and, if "compact", writes `PROJECT_MAP.md`,52 - inserts/refreshes a managed block in `.gitignore` (ignoring `obsidian-graph/`53 and `PROJECT_MAP.md`),54 - inserts/refreshes a managed block in `CLAUDE.md` with the route-specific guidance,55 - installs/refreshes a `PostToolUse` hook in `<project>/.claude/settings.json`56 (matcher `Edit|Write|MultiEdit`) that re-runs the generator in `--hook` mode,57 - prints a final `RESULT: files=<n> route=<directed|compact> ...` line.5859 Optional args: pass a project path as `argv[2]`; override the threshold with60 `--threshold=N`; force a route with `--route=directed|compact`.61623. Read the script's `RESULT:` line and report to the user: file count, chosen route,63 what was created, and how to open the Obsidian vault.64654. Tell the user the graphs now **auto-refresh**: the installed hook re-runs the66 generator whenever a source code file under the source base is edited. The hook runs67 quietly, only regenerates the graphs (it does not touch `.gitignore`/`CLAUDE.md`/the68 hook), is gated to source files (other edits are ignored, no loop on `obsidian-graph/`),69 and never fails an edit. Re-run `/claude-dependency-mapper` manually only to re-apply70 config or change the threshold/route.7172## Auto-refresh hook7374`installHook()` writes a project-scoped `PostToolUse` hook to75`<project>/.claude/settings.json`:7677```json78{ "hooks": { "PostToolUse": [79 { "matcher": "Edit|Write|MultiEdit",80 "hooks": [ { "type": "command", "command": "node \"<skill>/gen-graph.mjs\" --hook" } ] } ] } }81```8283In `--hook` mode the script reads the PostToolUse JSON from stdin, extracts84`tool_input.file_path`, and exits 0 immediately unless that file is a code file under the85source base (so non-source edits and writes inside `obsidian-graph/` are no-ops). It then86regenerates the graphs with `--quiet --no-config` semantics, wrapped in try/catch so a87failure can never disrupt editing. Re-installing is idempotent (prior claude-dependency-mapper88hook entries are removed first), and other existing hooks in `settings.json` are preserved.8990## Notes / limitations9192- The whole project tree is scanned (minus `node_modules`, build dirs, `.git`, virtual93 envs, `target`, etc.); note ids are paths relative to the project root.94- Resolution is regex-based and best-effort per language:95 - **JS/TS:** relative paths + `@/` → source-base alias. Other tsconfig `paths` aliases96 and dynamically string-built imports are not resolved.97 - **Python:** relative (`from . / .. import`) and absolute (`import a.b.c`,98 `from a.b import x`) resolved against the project root and `src/`. Star/`as` ignored;99 third-party/stdlib modules appear as external nodes.100 - **Rust:** `mod name;` declarations give accurate file edges; `use crate::/self::/super::`101 are resolved best-effort to files (the trailing item name is dropped), other `use`102 paths are treated as external crates. Grouped `use a::{b, c}` edges are not expanded.103 - **Dart:** relative imports and `package:<self>/…` (resolved via `pubspec.yaml` name)104 become edges; `dart:` and other `package:` imports are external.105- Imports that don't resolve to a file in the project are listed under "Unresolved" in106 the Obsidian note (kept out of the graph edges).107- Both graphs are gitignored by design. If the user later wants `PROJECT_MAP.md` shared108 with a team, remove it from the managed `.gitignore` block.