universal-ast-mapper 1.27.0 → 2.0.0
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/BLUEPRINT.md +230 -230
- package/CHANGELOG.md +466 -321
- package/README.md +878 -877
- package/package.json +48 -47
- package/scripts/install-skill.mjs +187 -187
- package/dist/analysis.js +0 -134
- package/dist/callgraph.js +0 -467
- package/dist/check.js +0 -112
- package/dist/cli.js +0 -1275
- package/dist/complexity.js +0 -98
- package/dist/config.js +0 -53
- package/dist/contextpack.js +0 -79
- package/dist/coupling.js +0 -35
- package/dist/crosslang.js +0 -425
- package/dist/diskcache.js +0 -97
- package/dist/explorer.js +0 -123
- package/dist/extractors/c.js +0 -204
- package/dist/extractors/common.js +0 -56
- package/dist/extractors/cpp.js +0 -272
- package/dist/extractors/csharp.js +0 -209
- package/dist/extractors/go.js +0 -212
- package/dist/extractors/java.js +0 -152
- package/dist/extractors/kotlin.js +0 -159
- package/dist/extractors/php.js +0 -208
- package/dist/extractors/python.js +0 -153
- package/dist/extractors/ruby.js +0 -146
- package/dist/extractors/rust.js +0 -249
- package/dist/extractors/swift.js +0 -192
- package/dist/extractors/typescript.js +0 -577
- package/dist/gitdiff.js +0 -178
- package/dist/graph-analysis.js +0 -279
- package/dist/graph.js +0 -165
- package/dist/html.js +0 -326
- package/dist/index.js +0 -1407
- package/dist/layers.js +0 -36
- package/dist/modulecoupling.js +0 -0
- package/dist/parser.js +0 -84
- package/dist/pool.js +0 -114
- package/dist/prompts.js +0 -67
- package/dist/registry.js +0 -87
- package/dist/report.js +0 -187
- package/dist/resolver.js +0 -222
- package/dist/roots.js +0 -47
- package/dist/search.js +0 -68
- package/dist/semantic.js +0 -365
- package/dist/sfc.js +0 -27
- package/dist/skeleton.js +0 -132
- package/dist/sourcemap.js +0 -60
- package/dist/testmap.js +0 -167
- package/dist/tsconfig.js +0 -212
- package/dist/typeflow.js +0 -124
- package/dist/types.js +0 -5
- package/dist/unused-params.js +0 -127
- package/dist/worker.js +0 -27
- package/dist/workspace.js +0 -330
package/package.json
CHANGED
|
@@ -1,47 +1,48 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "universal-ast-mapper",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "MCP server that maps source files into a normalized code skeleton (JSON + HTML) using tree-sitter.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "dist/index.js",
|
|
7
|
-
"bin": {
|
|
8
|
-
"universal-ast-mapper": "dist/index.js",
|
|
9
|
-
"ast-map": "dist/cli.js"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "universal-ast-mapper",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "MCP server that maps source files into a normalized code skeleton (JSON + HTML) using tree-sitter.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"universal-ast-mapper": "dist/index.js",
|
|
9
|
+
"ast-map": "dist/cli.js",
|
|
10
|
+
"ast-map-lsp": "dist/lsp.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"scripts",
|
|
15
|
+
"README.md",
|
|
16
|
+
"CHANGELOG.md",
|
|
17
|
+
"BLUEPRINT.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"start": "node dist/index.js",
|
|
22
|
+
"smoke": "node test/smoke.mjs",
|
|
23
|
+
"test": "node test/smoke.mjs && node test/analysis.mjs && node test/cache-smoke.mjs && node test/check-smoke.mjs && node test/roots-smoke.mjs && node test/tsalias-smoke.mjs",
|
|
24
|
+
"postinstall": "node scripts/install-skill.mjs"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"mcp",
|
|
31
|
+
"ast",
|
|
32
|
+
"tree-sitter",
|
|
33
|
+
"code-map",
|
|
34
|
+
"skeleton"
|
|
35
|
+
],
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
39
|
+
"commander": "^14.0.3",
|
|
40
|
+
"tree-sitter-wasms": "0.1.13",
|
|
41
|
+
"web-tree-sitter": "0.21.0",
|
|
42
|
+
"zod": "^3.23.8"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^20.11.0",
|
|
46
|
+
"typescript": "^5.4.0"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -1,187 +1,187 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Postinstall: copies the /ast-map Claude Code skill to ~/.claude/skills/ast-map/
|
|
4
|
-
* so it appears in the / command palette automatically after install.
|
|
5
|
-
* Skips silently if Claude Code is not installed or if running in CI.
|
|
6
|
-
*/
|
|
7
|
-
import fs from "node:fs";
|
|
8
|
-
import path from "node:path";
|
|
9
|
-
import os from "node:os";
|
|
10
|
-
|
|
11
|
-
// ─── Skip in CI / non-interactive environments ────────────────────────────────
|
|
12
|
-
if (
|
|
13
|
-
process.env.CI ||
|
|
14
|
-
process.env.CONTINUOUS_INTEGRATION ||
|
|
15
|
-
process.env.npm_config_ci ||
|
|
16
|
-
process.env.GITHUB_ACTIONS ||
|
|
17
|
-
process.env.GITLAB_CI
|
|
18
|
-
) {
|
|
19
|
-
process.exit(0);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// ─── Skill content ────────────────────────────────────────────────────────────
|
|
23
|
-
|
|
24
|
-
const SKILL_MD = `---
|
|
25
|
-
name: ast-map
|
|
26
|
-
description: "AST-based code analysis using universal-ast-mapper. Use to understand codebase structure, find dead code, detect circular deps, check blast radius, validate architecture. Works on TypeScript, JavaScript, Python, Go."
|
|
27
|
-
trigger: /ast-map
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
# /ast-map
|
|
31
|
-
|
|
32
|
-
Run AST-based code analysis on the current project using universal-ast-mapper (tree-sitter).
|
|
33
|
-
|
|
34
|
-
## Usage
|
|
35
|
-
|
|
36
|
-
\`\`\`
|
|
37
|
-
/ast-map # architecture overview: dead code + cycles + top symbols
|
|
38
|
-
/ast-map dead [dir] # find unused exports
|
|
39
|
-
/ast-map cycles [dir] # detect circular import chains
|
|
40
|
-
/ast-map validate [path] # architecture + structural violations
|
|
41
|
-
/ast-map skeleton <file> # show a file's symbols and imports
|
|
42
|
-
/ast-map top [dir] # top N most-imported symbols (God Nodes)
|
|
43
|
-
/ast-map impact <file> <symbol> # blast radius of changing a symbol
|
|
44
|
-
/ast-map calls <file> <fn> # call graph for a function
|
|
45
|
-
/ast-map search <name> [dir] # find a symbol across all files
|
|
46
|
-
/ast-map deps <file> # what this file imports / what imports it
|
|
47
|
-
\`\`\`
|
|
48
|
-
|
|
49
|
-
If no path is given, use \`.\` (current working directory). Do not ask the user for a path.
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
## What You Must Do When Invoked
|
|
54
|
-
|
|
55
|
-
### Step 1 — Determine mode
|
|
56
|
-
|
|
57
|
-
Parse the arguments after \`/ast-map\`:
|
|
58
|
-
|
|
59
|
-
| Command | Action |
|
|
60
|
-
|---------|--------|
|
|
61
|
-
| _(no args)_ | Run full overview: dead + cycles + validate |
|
|
62
|
-
| \`dead [dir]\` | Find dead exports |
|
|
63
|
-
| \`cycles [dir]\` | Find circular deps |
|
|
64
|
-
| \`validate [path]\` | Architecture + structural check |
|
|
65
|
-
| \`skeleton <file>\` | File skeleton |
|
|
66
|
-
| \`top [dir]\` | Top imported symbols |
|
|
67
|
-
| \`impact <file> <symbol>\` | Change impact |
|
|
68
|
-
| \`calls <file> <fn>\` | Call graph |
|
|
69
|
-
| \`search <name> [dir]\` | Symbol search |
|
|
70
|
-
| \`deps <file>\` | File dependencies |
|
|
71
|
-
|
|
72
|
-
### Step 2 — Choose execution method
|
|
73
|
-
|
|
74
|
-
**Prefer MCP tools** (faster, no subprocess). If \`mcp__ast-mapper__*\` tools are available in the current session, use them. Otherwise fall back to CLI.
|
|
75
|
-
|
|
76
|
-
**MCP tool → CLI command mapping:**
|
|
77
|
-
|
|
78
|
-
| Command | MCP Tool | CLI fallback |
|
|
79
|
-
|---------|----------|-------------|
|
|
80
|
-
| dead | \`find_dead_code\` | \`ast-map dead <dir>\` |
|
|
81
|
-
| cycles | \`find_circular_deps\` | \`ast-map cycles <dir>\` |
|
|
82
|
-
| validate | \`validate_architecture\` | \`ast-map validate <path>\` |
|
|
83
|
-
| skeleton | \`get_skeleton_json\` | \`ast-map skeleton <file>\` |
|
|
84
|
-
| top | \`get_top_symbols\` | \`ast-map top <dir>\` |
|
|
85
|
-
| impact | \`get_change_impact\` | \`ast-map impact <file> <symbol>\` |
|
|
86
|
-
| calls | \`get_call_graph\` | \`ast-map calls <file> <fn>\` |
|
|
87
|
-
| search | \`search_symbol\` | \`ast-map search <name> <dir>\` |
|
|
88
|
-
| deps | \`get_file_deps\` | \`ast-map deps <file>\` |
|
|
89
|
-
|
|
90
|
-
### Step 3 — Full overview (no args)
|
|
91
|
-
|
|
92
|
-
If no command was given, run a 3-part overview and present a unified report:
|
|
93
|
-
|
|
94
|
-
1. **Dead code** — find high-confidence unused exports
|
|
95
|
-
2. **Circular deps** — detect import cycles
|
|
96
|
-
3. **Top symbols** — list the 5 most-imported symbols
|
|
97
|
-
|
|
98
|
-
Then summarise:
|
|
99
|
-
\`\`\`
|
|
100
|
-
AST-MCP Overview — [directory]
|
|
101
|
-
Scanned: N files
|
|
102
|
-
|
|
103
|
-
Dead Code (high confidence): X symbols
|
|
104
|
-
[list, grouped by file]
|
|
105
|
-
|
|
106
|
-
Circular Dependencies: Y cycles
|
|
107
|
-
[list each cycle as A → B → C → A]
|
|
108
|
-
|
|
109
|
-
God Nodes (top 5 most imported):
|
|
110
|
-
1. symbolName (file) — imported by N files
|
|
111
|
-
...
|
|
112
|
-
|
|
113
|
-
Recommendation: [1-2 sentences on what to look at first]
|
|
114
|
-
\`\`\`
|
|
115
|
-
|
|
116
|
-
### Step 4 — Present results clearly
|
|
117
|
-
|
|
118
|
-
- **Dead code**: group by file, show symbol name + kind. Note if 0 ("No dead exports found ✓")
|
|
119
|
-
- **Cycles**: show each cycle as \`A.ts → B.ts → C.ts → A.ts\`. Note length.
|
|
120
|
-
- **validate**: group by severity (errors first, then warnings). Show file + rule + message.
|
|
121
|
-
- **skeleton**: show symbols as a tree with line ranges.
|
|
122
|
-
- **impact**: show direct and transitive file lists + totalFiles count.
|
|
123
|
-
- **calls**: show call list with line numbers + whether external.
|
|
124
|
-
- **search**: show file + symbol + kind + line range.
|
|
125
|
-
|
|
126
|
-
Always end with a relevant follow-up offer:
|
|
127
|
-
- Found dead code? → "Want me to verify each one with \`impact\` before deleting?"
|
|
128
|
-
- Found cycles? → "Want me to show which import to break to resolve the shortest cycle?"
|
|
129
|
-
- Found God Nodes? → "Want me to check the blast radius of the top one?"
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
## Examples
|
|
134
|
-
|
|
135
|
-
### /ast-map
|
|
136
|
-
Runs dead + cycles + top on the current directory — a 30-second architecture health check.
|
|
137
|
-
|
|
138
|
-
### /ast-map dead src/
|
|
139
|
-
Finds all exported symbols in \`src/\` that are never imported by any other file.
|
|
140
|
-
|
|
141
|
-
### /ast-map validate src/ --max-lines 300
|
|
142
|
-
Checks for boundary violations, API routes missing try/catch, files over 300 lines.
|
|
143
|
-
|
|
144
|
-
### /ast-map impact src/lib/auth.ts validateSession
|
|
145
|
-
Shows every file that directly or transitively depends on \`validateSession\`.
|
|
146
|
-
`;
|
|
147
|
-
|
|
148
|
-
// ─── CLAUDE.md entry ──────────────────────────────────────────────────────────
|
|
149
|
-
|
|
150
|
-
const CLAUDE_MD_ENTRY = `
|
|
151
|
-
# ast-map
|
|
152
|
-
- **ast-map** (\`~/.claude/skills/ast-map/SKILL.md\`) - AST-based code analysis: dead code, circular deps, blast radius, architecture validation, symbol search. Trigger: \`/ast-map\`
|
|
153
|
-
When the user types \`/ast-map\`, invoke the Skill tool with \`skill: "ast-map"\` before doing anything else.
|
|
154
|
-
`;
|
|
155
|
-
|
|
156
|
-
// ─── Install ──────────────────────────────────────────────────────────────────
|
|
157
|
-
|
|
158
|
-
function main() {
|
|
159
|
-
const claudeDir = path.join(os.homedir(), ".claude");
|
|
160
|
-
|
|
161
|
-
// Only install if Claude Code config directory exists
|
|
162
|
-
if (!fs.existsSync(claudeDir)) return;
|
|
163
|
-
|
|
164
|
-
try {
|
|
165
|
-
// 1. Write SKILL.md
|
|
166
|
-
const skillDir = path.join(claudeDir, "skills", "ast-map");
|
|
167
|
-
fs.mkdirSync(skillDir, { recursive: true });
|
|
168
|
-
fs.writeFileSync(path.join(skillDir, "SKILL.md"), SKILL_MD, "utf8");
|
|
169
|
-
|
|
170
|
-
// 2. Update CLAUDE.md — idempotent (skip if entry already present)
|
|
171
|
-
const claudeMdPath = path.join(claudeDir, "CLAUDE.md");
|
|
172
|
-
const existing = fs.existsSync(claudeMdPath)
|
|
173
|
-
? fs.readFileSync(claudeMdPath, "utf8")
|
|
174
|
-
: "";
|
|
175
|
-
|
|
176
|
-
if (!existing.includes('skill: "ast-map"')) {
|
|
177
|
-
fs.writeFileSync(claudeMdPath, existing.trimEnd() + "\n" + CLAUDE_MD_ENTRY, "utf8");
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
console.log("✓ /ast-map skill installed to Claude Code (~/.claude/skills/ast-map/)");
|
|
181
|
-
console.log(' Type /ast-map in any Claude Code session to run code analysis.');
|
|
182
|
-
} catch {
|
|
183
|
-
// Non-fatal — skill install is best-effort, don't break npm install
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
main();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Postinstall: copies the /ast-map Claude Code skill to ~/.claude/skills/ast-map/
|
|
4
|
+
* so it appears in the / command palette automatically after install.
|
|
5
|
+
* Skips silently if Claude Code is not installed or if running in CI.
|
|
6
|
+
*/
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import os from "node:os";
|
|
10
|
+
|
|
11
|
+
// ─── Skip in CI / non-interactive environments ────────────────────────────────
|
|
12
|
+
if (
|
|
13
|
+
process.env.CI ||
|
|
14
|
+
process.env.CONTINUOUS_INTEGRATION ||
|
|
15
|
+
process.env.npm_config_ci ||
|
|
16
|
+
process.env.GITHUB_ACTIONS ||
|
|
17
|
+
process.env.GITLAB_CI
|
|
18
|
+
) {
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ─── Skill content ────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
const SKILL_MD = `---
|
|
25
|
+
name: ast-map
|
|
26
|
+
description: "AST-based code analysis using universal-ast-mapper. Use to understand codebase structure, find dead code, detect circular deps, check blast radius, validate architecture. Works on TypeScript, JavaScript, Python, Go."
|
|
27
|
+
trigger: /ast-map
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
# /ast-map
|
|
31
|
+
|
|
32
|
+
Run AST-based code analysis on the current project using universal-ast-mapper (tree-sitter).
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
\`\`\`
|
|
37
|
+
/ast-map # architecture overview: dead code + cycles + top symbols
|
|
38
|
+
/ast-map dead [dir] # find unused exports
|
|
39
|
+
/ast-map cycles [dir] # detect circular import chains
|
|
40
|
+
/ast-map validate [path] # architecture + structural violations
|
|
41
|
+
/ast-map skeleton <file> # show a file's symbols and imports
|
|
42
|
+
/ast-map top [dir] # top N most-imported symbols (God Nodes)
|
|
43
|
+
/ast-map impact <file> <symbol> # blast radius of changing a symbol
|
|
44
|
+
/ast-map calls <file> <fn> # call graph for a function
|
|
45
|
+
/ast-map search <name> [dir] # find a symbol across all files
|
|
46
|
+
/ast-map deps <file> # what this file imports / what imports it
|
|
47
|
+
\`\`\`
|
|
48
|
+
|
|
49
|
+
If no path is given, use \`.\` (current working directory). Do not ask the user for a path.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## What You Must Do When Invoked
|
|
54
|
+
|
|
55
|
+
### Step 1 — Determine mode
|
|
56
|
+
|
|
57
|
+
Parse the arguments after \`/ast-map\`:
|
|
58
|
+
|
|
59
|
+
| Command | Action |
|
|
60
|
+
|---------|--------|
|
|
61
|
+
| _(no args)_ | Run full overview: dead + cycles + validate |
|
|
62
|
+
| \`dead [dir]\` | Find dead exports |
|
|
63
|
+
| \`cycles [dir]\` | Find circular deps |
|
|
64
|
+
| \`validate [path]\` | Architecture + structural check |
|
|
65
|
+
| \`skeleton <file>\` | File skeleton |
|
|
66
|
+
| \`top [dir]\` | Top imported symbols |
|
|
67
|
+
| \`impact <file> <symbol>\` | Change impact |
|
|
68
|
+
| \`calls <file> <fn>\` | Call graph |
|
|
69
|
+
| \`search <name> [dir]\` | Symbol search |
|
|
70
|
+
| \`deps <file>\` | File dependencies |
|
|
71
|
+
|
|
72
|
+
### Step 2 — Choose execution method
|
|
73
|
+
|
|
74
|
+
**Prefer MCP tools** (faster, no subprocess). If \`mcp__ast-mapper__*\` tools are available in the current session, use them. Otherwise fall back to CLI.
|
|
75
|
+
|
|
76
|
+
**MCP tool → CLI command mapping:**
|
|
77
|
+
|
|
78
|
+
| Command | MCP Tool | CLI fallback |
|
|
79
|
+
|---------|----------|-------------|
|
|
80
|
+
| dead | \`find_dead_code\` | \`ast-map dead <dir>\` |
|
|
81
|
+
| cycles | \`find_circular_deps\` | \`ast-map cycles <dir>\` |
|
|
82
|
+
| validate | \`validate_architecture\` | \`ast-map validate <path>\` |
|
|
83
|
+
| skeleton | \`get_skeleton_json\` | \`ast-map skeleton <file>\` |
|
|
84
|
+
| top | \`get_top_symbols\` | \`ast-map top <dir>\` |
|
|
85
|
+
| impact | \`get_change_impact\` | \`ast-map impact <file> <symbol>\` |
|
|
86
|
+
| calls | \`get_call_graph\` | \`ast-map calls <file> <fn>\` |
|
|
87
|
+
| search | \`search_symbol\` | \`ast-map search <name> <dir>\` |
|
|
88
|
+
| deps | \`get_file_deps\` | \`ast-map deps <file>\` |
|
|
89
|
+
|
|
90
|
+
### Step 3 — Full overview (no args)
|
|
91
|
+
|
|
92
|
+
If no command was given, run a 3-part overview and present a unified report:
|
|
93
|
+
|
|
94
|
+
1. **Dead code** — find high-confidence unused exports
|
|
95
|
+
2. **Circular deps** — detect import cycles
|
|
96
|
+
3. **Top symbols** — list the 5 most-imported symbols
|
|
97
|
+
|
|
98
|
+
Then summarise:
|
|
99
|
+
\`\`\`
|
|
100
|
+
AST-MCP Overview — [directory]
|
|
101
|
+
Scanned: N files
|
|
102
|
+
|
|
103
|
+
Dead Code (high confidence): X symbols
|
|
104
|
+
[list, grouped by file]
|
|
105
|
+
|
|
106
|
+
Circular Dependencies: Y cycles
|
|
107
|
+
[list each cycle as A → B → C → A]
|
|
108
|
+
|
|
109
|
+
God Nodes (top 5 most imported):
|
|
110
|
+
1. symbolName (file) — imported by N files
|
|
111
|
+
...
|
|
112
|
+
|
|
113
|
+
Recommendation: [1-2 sentences on what to look at first]
|
|
114
|
+
\`\`\`
|
|
115
|
+
|
|
116
|
+
### Step 4 — Present results clearly
|
|
117
|
+
|
|
118
|
+
- **Dead code**: group by file, show symbol name + kind. Note if 0 ("No dead exports found ✓")
|
|
119
|
+
- **Cycles**: show each cycle as \`A.ts → B.ts → C.ts → A.ts\`. Note length.
|
|
120
|
+
- **validate**: group by severity (errors first, then warnings). Show file + rule + message.
|
|
121
|
+
- **skeleton**: show symbols as a tree with line ranges.
|
|
122
|
+
- **impact**: show direct and transitive file lists + totalFiles count.
|
|
123
|
+
- **calls**: show call list with line numbers + whether external.
|
|
124
|
+
- **search**: show file + symbol + kind + line range.
|
|
125
|
+
|
|
126
|
+
Always end with a relevant follow-up offer:
|
|
127
|
+
- Found dead code? → "Want me to verify each one with \`impact\` before deleting?"
|
|
128
|
+
- Found cycles? → "Want me to show which import to break to resolve the shortest cycle?"
|
|
129
|
+
- Found God Nodes? → "Want me to check the blast radius of the top one?"
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Examples
|
|
134
|
+
|
|
135
|
+
### /ast-map
|
|
136
|
+
Runs dead + cycles + top on the current directory — a 30-second architecture health check.
|
|
137
|
+
|
|
138
|
+
### /ast-map dead src/
|
|
139
|
+
Finds all exported symbols in \`src/\` that are never imported by any other file.
|
|
140
|
+
|
|
141
|
+
### /ast-map validate src/ --max-lines 300
|
|
142
|
+
Checks for boundary violations, API routes missing try/catch, files over 300 lines.
|
|
143
|
+
|
|
144
|
+
### /ast-map impact src/lib/auth.ts validateSession
|
|
145
|
+
Shows every file that directly or transitively depends on \`validateSession\`.
|
|
146
|
+
`;
|
|
147
|
+
|
|
148
|
+
// ─── CLAUDE.md entry ──────────────────────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
const CLAUDE_MD_ENTRY = `
|
|
151
|
+
# ast-map
|
|
152
|
+
- **ast-map** (\`~/.claude/skills/ast-map/SKILL.md\`) - AST-based code analysis: dead code, circular deps, blast radius, architecture validation, symbol search. Trigger: \`/ast-map\`
|
|
153
|
+
When the user types \`/ast-map\`, invoke the Skill tool with \`skill: "ast-map"\` before doing anything else.
|
|
154
|
+
`;
|
|
155
|
+
|
|
156
|
+
// ─── Install ──────────────────────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
function main() {
|
|
159
|
+
const claudeDir = path.join(os.homedir(), ".claude");
|
|
160
|
+
|
|
161
|
+
// Only install if Claude Code config directory exists
|
|
162
|
+
if (!fs.existsSync(claudeDir)) return;
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
// 1. Write SKILL.md
|
|
166
|
+
const skillDir = path.join(claudeDir, "skills", "ast-map");
|
|
167
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
168
|
+
fs.writeFileSync(path.join(skillDir, "SKILL.md"), SKILL_MD, "utf8");
|
|
169
|
+
|
|
170
|
+
// 2. Update CLAUDE.md — idempotent (skip if entry already present)
|
|
171
|
+
const claudeMdPath = path.join(claudeDir, "CLAUDE.md");
|
|
172
|
+
const existing = fs.existsSync(claudeMdPath)
|
|
173
|
+
? fs.readFileSync(claudeMdPath, "utf8")
|
|
174
|
+
: "";
|
|
175
|
+
|
|
176
|
+
if (!existing.includes('skill: "ast-map"')) {
|
|
177
|
+
fs.writeFileSync(claudeMdPath, existing.trimEnd() + "\n" + CLAUDE_MD_ENTRY, "utf8");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
console.log("✓ /ast-map skill installed to Claude Code (~/.claude/skills/ast-map/)");
|
|
181
|
+
console.log(' Type /ast-map in any Claude Code session to run code analysis.');
|
|
182
|
+
} catch {
|
|
183
|
+
// Non-fatal — skill install is best-effort, don't break npm install
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
main();
|
package/dist/analysis.js
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
// ─── Symbol lookup ────────────────────────────────────────────────────────────
|
|
3
|
-
/** Recursively search for a symbol by name and optional kind. */
|
|
4
|
-
export function findSymbol(symbols, name, kind) {
|
|
5
|
-
for (const sym of symbols) {
|
|
6
|
-
if (sym.name === name && (!kind || sym.kind === kind))
|
|
7
|
-
return sym;
|
|
8
|
-
const found = findSymbol(sym.children, name, kind);
|
|
9
|
-
if (found)
|
|
10
|
-
return found;
|
|
11
|
-
}
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Given a target symbol with a signature, find related type/interface/enum
|
|
16
|
-
* symbols referenced in that signature and return their source code blocks.
|
|
17
|
-
*/
|
|
18
|
-
export function findRelatedSymbols(symbols, target, sourceLines) {
|
|
19
|
-
if (!target.signature)
|
|
20
|
-
return [];
|
|
21
|
-
const seen = new Set([target.name]);
|
|
22
|
-
// PascalCase identifiers in the signature are likely type references
|
|
23
|
-
const typeRefs = [...target.signature.matchAll(/\b([A-Z][a-zA-Z0-9_]*)\b/g)]
|
|
24
|
-
.map((m) => m[1])
|
|
25
|
-
.filter((v) => !seen.has(v) && (seen.add(v), true));
|
|
26
|
-
const related = [];
|
|
27
|
-
for (const typeName of typeRefs) {
|
|
28
|
-
const sym = findSymbol(symbols, typeName);
|
|
29
|
-
if (sym && (sym.kind === "interface" || sym.kind === "type" || sym.kind === "enum")) {
|
|
30
|
-
const code = sourceLines.slice(sym.range.startLine - 1, sym.range.endLine).join("\n");
|
|
31
|
-
related.push({ name: sym.name, kind: sym.kind, range: sym.range, code });
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return related;
|
|
35
|
-
}
|
|
36
|
-
// ─── Architecture validation ──────────────────────────────────────────────────
|
|
37
|
-
/** True if the first 500 chars of source contain the given directive literal. */
|
|
38
|
-
export function hasDirective(source, directive) {
|
|
39
|
-
const head = source.slice(0, 500);
|
|
40
|
-
return head.includes(`"${directive}"`) || head.includes(`'${directive}'`);
|
|
41
|
-
}
|
|
42
|
-
/** Patterns that flag server-only imports in a "use client" file. */
|
|
43
|
-
const SERVER_IMPORT_PATTERNS = [
|
|
44
|
-
{ pattern: /from\s+['"]server-only['"]/, label: "server-only" },
|
|
45
|
-
{ pattern: /from\s+['"][^'"]*\/prisma['"]/, label: "prisma client" },
|
|
46
|
-
{ pattern: /from\s+['"][^'"]*lib\/prisma['"]/, label: "lib/prisma" },
|
|
47
|
-
{ pattern: /from\s+['"]next\/headers['"]/, label: "next/headers" },
|
|
48
|
-
{ pattern: /from\s+['"]next\/cookies['"]/, label: "next/cookies" },
|
|
49
|
-
{ pattern: /from\s+['"][^'"]*lib\/auth['"]/, label: "lib/auth" },
|
|
50
|
-
{ pattern: /from\s+['"][^'"]*lib\/auditLog['"]/, label: "lib/auditLog" },
|
|
51
|
-
{ pattern: /from\s+['"][^'"]*lib\/apiAuth['"]/, label: "lib/apiAuth" },
|
|
52
|
-
];
|
|
53
|
-
/** Scan source lines for server-only imports (called on "use client" files). */
|
|
54
|
-
export function findServerImports(source) {
|
|
55
|
-
const lines = source.split("\n");
|
|
56
|
-
const violations = [];
|
|
57
|
-
for (let i = 0; i < lines.length; i++) {
|
|
58
|
-
const line = lines[i];
|
|
59
|
-
for (const { pattern, label } of SERVER_IMPORT_PATTERNS) {
|
|
60
|
-
if (pattern.test(line)) {
|
|
61
|
-
const match = line.match(/from\s+['"]([^'"]+)['"]/);
|
|
62
|
-
violations.push({ module: match ? match[1] : line.trim(), label, line: i + 1 });
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return violations;
|
|
68
|
-
}
|
|
69
|
-
const HTTP_METHODS = new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]);
|
|
70
|
-
/** True if the relative path looks like a Next.js App Router API route file. */
|
|
71
|
-
export function isApiRoute(relPath) {
|
|
72
|
-
const norm = relPath.split(path.sep).join("/");
|
|
73
|
-
return /app\/api\/.+\/route\.(ts|js|tsx|jsx)$/.test(norm);
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Find exported HTTP handler functions that have no try/catch.
|
|
77
|
-
* A simple but effective heuristic: look for the `try {` keyword in the body.
|
|
78
|
-
*/
|
|
79
|
-
export function findMissingTryCatch(symbols, sourceLines) {
|
|
80
|
-
const missing = [];
|
|
81
|
-
for (const sym of symbols) {
|
|
82
|
-
if (!HTTP_METHODS.has(sym.name) || sym.exported === false)
|
|
83
|
-
continue;
|
|
84
|
-
const bodyText = sourceLines.slice(sym.range.startLine - 1, sym.range.endLine).join("\n");
|
|
85
|
-
if (!/\btry\s*\{/.test(bodyText))
|
|
86
|
-
missing.push(sym);
|
|
87
|
-
}
|
|
88
|
-
return missing;
|
|
89
|
-
}
|
|
90
|
-
export const GENERAL_RULE_DEFAULTS = {
|
|
91
|
-
largeFileLines: 500,
|
|
92
|
-
tooManyImports: 15,
|
|
93
|
-
godExportCount: 10,
|
|
94
|
-
};
|
|
95
|
-
/**
|
|
96
|
-
* Run general-purpose structural rules against a source file.
|
|
97
|
-
* Returns violations for any threshold exceeded.
|
|
98
|
-
*/
|
|
99
|
-
export function checkGeneralRules(fileRel, source, symbols, importCount, thresholds = GENERAL_RULE_DEFAULTS) {
|
|
100
|
-
const violations = [];
|
|
101
|
-
const lineCount = source.split("\n").length;
|
|
102
|
-
if (lineCount > thresholds.largeFileLines) {
|
|
103
|
-
violations.push({
|
|
104
|
-
file: fileRel,
|
|
105
|
-
rule: "large-file",
|
|
106
|
-
severity: "warning",
|
|
107
|
-
message: `File has ${lineCount} lines (threshold: ${thresholds.largeFileLines}) — consider splitting`,
|
|
108
|
-
value: lineCount,
|
|
109
|
-
threshold: thresholds.largeFileLines,
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
if (importCount > thresholds.tooManyImports) {
|
|
113
|
-
violations.push({
|
|
114
|
-
file: fileRel,
|
|
115
|
-
rule: "too-many-imports",
|
|
116
|
-
severity: "warning",
|
|
117
|
-
message: `File has ${importCount} imports (threshold: ${thresholds.tooManyImports}) — high coupling`,
|
|
118
|
-
value: importCount,
|
|
119
|
-
threshold: thresholds.tooManyImports,
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
const exportedCount = symbols.filter((s) => s.exported).length;
|
|
123
|
-
if (exportedCount > thresholds.godExportCount) {
|
|
124
|
-
violations.push({
|
|
125
|
-
file: fileRel,
|
|
126
|
-
rule: "god-export",
|
|
127
|
-
severity: "warning",
|
|
128
|
-
message: `File exports ${exportedCount} symbols (threshold: ${thresholds.godExportCount}) — potential God File`,
|
|
129
|
-
value: exportedCount,
|
|
130
|
-
threshold: thresholds.godExportCount,
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
return violations;
|
|
134
|
-
}
|