typegraph-mcp 0.9.26 → 0.9.28
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 +22 -0
- package/check.ts +8 -2
- package/cli.ts +82 -5
- package/commands/deep-survey.md +442 -0
- package/dist/check.js +8 -1
- package/dist/cli.js +81 -9
- package/package.json +2 -1
- package/skills/deep-survey/SKILL.md +408 -0
package/README.md
CHANGED
|
@@ -80,6 +80,8 @@ This gives you 14 MCP tools, 5 workflow skills that teach Claude *when* and *how
|
|
|
80
80
|
|
|
81
81
|
**Other agents** (Cursor, Codex CLI, Gemini CLI, GitHub Copilot) — restart your agent session. The MCP server and skills are already configured.
|
|
82
82
|
|
|
83
|
+
For **Codex CLI**, setup now registers the server with `codex mcp add` using absolute paths so the tools work even when Codex launches from outside your project root.
|
|
84
|
+
|
|
83
85
|
First query takes ~2s (tsserver warmup). Subsequent queries: 1-60ms.
|
|
84
86
|
|
|
85
87
|
## Requirements
|
|
@@ -147,6 +149,26 @@ npx typegraph-mcp check
|
|
|
147
149
|
|
|
148
150
|
## Manual MCP configuration
|
|
149
151
|
|
|
152
|
+
### Codex CLI
|
|
153
|
+
|
|
154
|
+
Register the server with absolute paths:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
codex mcp add typegraph \
|
|
158
|
+
--env TYPEGRAPH_PROJECT_ROOT=/absolute/path/to/your-project \
|
|
159
|
+
--env TYPEGRAPH_TSCONFIG=/absolute/path/to/your-project/tsconfig.json \
|
|
160
|
+
-- npx tsx /absolute/path/to/your-project/plugins/typegraph-mcp/server.ts
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Verify with:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
codex mcp get typegraph
|
|
167
|
+
codex mcp list
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### JSON-based MCP clients
|
|
171
|
+
|
|
150
172
|
Add to `.claude/mcp.json` (or `~/.claude/mcp.json` for global):
|
|
151
173
|
|
|
152
174
|
```json
|
package/check.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import * as fs from "node:fs";
|
|
13
13
|
import * as path from "node:path";
|
|
14
14
|
import { createRequire } from "node:module";
|
|
15
|
-
import { spawn } from "node:child_process";
|
|
15
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
16
16
|
import { resolveConfig, type TypegraphConfig } from "./config.js";
|
|
17
17
|
|
|
18
18
|
// ─── Result Type ─────────────────────────────────────────────────────────────
|
|
@@ -186,10 +186,17 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
|
|
|
186
186
|
// Check for plugin .mcp.json in the tool directory (embedded plugin install)
|
|
187
187
|
const pluginMcpPath = path.join(toolDir, ".mcp.json");
|
|
188
188
|
const hasPluginMcp = fs.existsSync(pluginMcpPath) && fs.existsSync(path.join(toolDir, ".claude-plugin/plugin.json"));
|
|
189
|
+
const codexGet = spawnSync("codex", ["mcp", "get", "typegraph"], {
|
|
190
|
+
stdio: "pipe",
|
|
191
|
+
encoding: "utf-8",
|
|
192
|
+
});
|
|
193
|
+
const hasCodexRegistration = codexGet.status === 0;
|
|
189
194
|
if (process.env.CLAUDE_PLUGIN_ROOT) {
|
|
190
195
|
pass("MCP registered via plugin (CLAUDE_PLUGIN_ROOT set)");
|
|
191
196
|
} else if (hasPluginMcp) {
|
|
192
197
|
pass("MCP registered via plugin (.mcp.json + .claude-plugin/ present)");
|
|
198
|
+
} else if (hasCodexRegistration) {
|
|
199
|
+
pass("MCP registered in Codex CLI");
|
|
193
200
|
} else {
|
|
194
201
|
const mcpJsonPath = path.resolve(projectRoot, ".claude/mcp.json");
|
|
195
202
|
if (fs.existsSync(mcpJsonPath)) {
|
|
@@ -450,4 +457,3 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
|
|
|
450
457
|
|
|
451
458
|
return { passed, failed, warned };
|
|
452
459
|
}
|
|
453
|
-
|
package/cli.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import * as fs from "node:fs";
|
|
17
17
|
import * as path from "node:path";
|
|
18
|
-
import { execSync } from "node:child_process";
|
|
18
|
+
import { execSync, spawnSync } from "node:child_process";
|
|
19
19
|
import * as p from "@clack/prompts";
|
|
20
20
|
import { resolveConfig } from "./config.js";
|
|
21
21
|
|
|
@@ -62,6 +62,7 @@ const AGENTS: Record<AgentId, AgentDef> = {
|
|
|
62
62
|
"commands/check.md",
|
|
63
63
|
"commands/test.md",
|
|
64
64
|
"commands/bench.md",
|
|
65
|
+
"commands/deep-survey.md",
|
|
65
66
|
],
|
|
66
67
|
agentFile: "CLAUDE.md",
|
|
67
68
|
needsAgentsSkills: false,
|
|
@@ -120,6 +121,7 @@ const SKILL_FILES = [
|
|
|
120
121
|
"skills/refactor-safety/SKILL.md",
|
|
121
122
|
"skills/dependency-audit/SKILL.md",
|
|
122
123
|
"skills/code-exploration/SKILL.md",
|
|
124
|
+
"skills/deep-survey/SKILL.md",
|
|
123
125
|
];
|
|
124
126
|
|
|
125
127
|
|
|
@@ -129,6 +131,7 @@ const SKILL_NAMES = [
|
|
|
129
131
|
"refactor-safety",
|
|
130
132
|
"dependency-audit",
|
|
131
133
|
"code-exploration",
|
|
134
|
+
"deep-survey",
|
|
132
135
|
];
|
|
133
136
|
|
|
134
137
|
const HELP = `
|
|
@@ -174,6 +177,21 @@ const MCP_SERVER_ENTRY = {
|
|
|
174
177
|
},
|
|
175
178
|
};
|
|
176
179
|
|
|
180
|
+
function getAbsoluteMcpServerEntry(projectRoot: string): {
|
|
181
|
+
command: string;
|
|
182
|
+
args: string[];
|
|
183
|
+
env: Record<string, string>;
|
|
184
|
+
} {
|
|
185
|
+
return {
|
|
186
|
+
command: "npx",
|
|
187
|
+
args: ["tsx", path.resolve(projectRoot, PLUGIN_DIR_NAME, "server.ts")],
|
|
188
|
+
env: {
|
|
189
|
+
TYPEGRAPH_PROJECT_ROOT: projectRoot,
|
|
190
|
+
TYPEGRAPH_TSCONFIG: path.resolve(projectRoot, "tsconfig.json"),
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
177
195
|
/** Register the typegraph MCP server in agent-specific config files */
|
|
178
196
|
function registerMcpServers(projectRoot: string, selectedAgents: AgentId[]): void {
|
|
179
197
|
if (selectedAgents.includes("cursor")) {
|
|
@@ -256,6 +274,57 @@ function deregisterJsonMcp(projectRoot: string, configPath: string, rootKey: str
|
|
|
256
274
|
|
|
257
275
|
/** Register MCP server in Codex CLI's TOML config */
|
|
258
276
|
function registerCodexMcp(projectRoot: string): void {
|
|
277
|
+
const absoluteEntry = getAbsoluteMcpServerEntry(projectRoot);
|
|
278
|
+
|
|
279
|
+
const codexGet = spawnSync("codex", ["mcp", "get", "typegraph"], {
|
|
280
|
+
stdio: "pipe",
|
|
281
|
+
encoding: "utf-8",
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
if (codexGet.status === 0) {
|
|
285
|
+
const output = `${codexGet.stdout ?? ""}${codexGet.stderr ?? ""}`;
|
|
286
|
+
const hasServerPath = output.includes(absoluteEntry.args[1]!);
|
|
287
|
+
const hasProjectRoot = output.includes("TYPEGRAPH_PROJECT_ROOT=*****") || output.includes(projectRoot);
|
|
288
|
+
const hasTsconfig = output.includes("TYPEGRAPH_TSCONFIG=*****") || output.includes(path.resolve(projectRoot, "tsconfig.json"));
|
|
289
|
+
if (hasServerPath && hasProjectRoot && hasTsconfig) {
|
|
290
|
+
p.log.info("Codex CLI: typegraph MCP server already registered");
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
spawnSync("codex", ["mcp", "remove", "typegraph"], {
|
|
294
|
+
stdio: "pipe",
|
|
295
|
+
encoding: "utf-8",
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const codexAdd = spawnSync(
|
|
300
|
+
"codex",
|
|
301
|
+
[
|
|
302
|
+
"mcp",
|
|
303
|
+
"add",
|
|
304
|
+
"typegraph",
|
|
305
|
+
"--env",
|
|
306
|
+
`TYPEGRAPH_PROJECT_ROOT=${absoluteEntry.env.TYPEGRAPH_PROJECT_ROOT}`,
|
|
307
|
+
"--env",
|
|
308
|
+
`TYPEGRAPH_TSCONFIG=${absoluteEntry.env.TYPEGRAPH_TSCONFIG}`,
|
|
309
|
+
"--",
|
|
310
|
+
absoluteEntry.command,
|
|
311
|
+
...absoluteEntry.args,
|
|
312
|
+
],
|
|
313
|
+
{
|
|
314
|
+
stdio: "pipe",
|
|
315
|
+
encoding: "utf-8",
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
if (codexAdd.status === 0) {
|
|
320
|
+
p.log.success("Codex CLI: registered typegraph MCP server");
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
p.log.warn(
|
|
325
|
+
`Codex CLI registration failed — falling back to ${".codex/config.toml"}`
|
|
326
|
+
);
|
|
327
|
+
|
|
259
328
|
const configPath = ".codex/config.toml";
|
|
260
329
|
const fullPath = path.resolve(projectRoot, configPath);
|
|
261
330
|
let content = "";
|
|
@@ -272,9 +341,9 @@ function registerCodexMcp(projectRoot: string): void {
|
|
|
272
341
|
const block = [
|
|
273
342
|
"",
|
|
274
343
|
"[mcp_servers.typegraph]",
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
344
|
+
`command = "${absoluteEntry.command}"`,
|
|
345
|
+
`args = ["${absoluteEntry.args[0]}", "${absoluteEntry.args[1]}"]`,
|
|
346
|
+
`env = { TYPEGRAPH_PROJECT_ROOT = "${absoluteEntry.env.TYPEGRAPH_PROJECT_ROOT}", TYPEGRAPH_TSCONFIG = "${absoluteEntry.env.TYPEGRAPH_TSCONFIG}" }`,
|
|
278
347
|
"",
|
|
279
348
|
].join("\n");
|
|
280
349
|
|
|
@@ -289,6 +358,14 @@ function registerCodexMcp(projectRoot: string): void {
|
|
|
289
358
|
|
|
290
359
|
/** Deregister MCP server from Codex CLI's TOML config */
|
|
291
360
|
function deregisterCodexMcp(projectRoot: string): void {
|
|
361
|
+
const codexRemove = spawnSync("codex", ["mcp", "remove", "typegraph"], {
|
|
362
|
+
stdio: "pipe",
|
|
363
|
+
encoding: "utf-8",
|
|
364
|
+
});
|
|
365
|
+
if (codexRemove.status === 0) {
|
|
366
|
+
p.log.info("Codex CLI: removed typegraph MCP server");
|
|
367
|
+
}
|
|
368
|
+
|
|
292
369
|
const configPath = ".codex/config.toml";
|
|
293
370
|
const fullPath = path.resolve(projectRoot, configPath);
|
|
294
371
|
if (!fs.existsSync(fullPath)) return;
|
|
@@ -792,7 +869,7 @@ async function runVerification(pluginDir: string, selectedAgents: AgentId[]): Pr
|
|
|
792
869
|
|
|
793
870
|
if (checkResult.failed === 0 && testResult.failed === 0) {
|
|
794
871
|
if (selectedAgents.includes("claude-code")) {
|
|
795
|
-
p.outro("Setup complete! Run: claude --plugin-dir ./plugins/typegraph-mcp\n Slash commands: /typegraph:check, /typegraph:test, /typegraph:bench");
|
|
872
|
+
p.outro("Setup complete! Run: claude --plugin-dir ./plugins/typegraph-mcp\n Slash commands: /typegraph:check, /typegraph:test, /typegraph:bench, /typegraph:deep-survey");
|
|
796
873
|
} else {
|
|
797
874
|
p.outro("Setup complete! typegraph-mcp tools are now available to your agents.\n CLI: npx typegraph-mcp check | test | bench");
|
|
798
875
|
}
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Run a comprehensive 7-phase codebase analysis producing a detailed report
|
|
3
|
+
argument-hint: [--skip-phases 4,6]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TypeGraph Deep Survey
|
|
7
|
+
|
|
8
|
+
Run a comprehensive 7-phase codebase exploration using all typegraph-mcp tools. Produces a detailed architectural report at `typegraph-exploration-report.md` in the project root.
|
|
9
|
+
|
|
10
|
+
## Execution Instructions
|
|
11
|
+
|
|
12
|
+
Follow the phases below in order. Each phase produces findings.
|
|
13
|
+
|
|
14
|
+
**Report output:** Write a markdown report to `<project_root>/typegraph-exploration-report.md` as you go. After each phase checkpoint, append that phase's findings to the report. Structure the report with the same phase headings used below. Include raw data (file counts, edge counts, cycle lists, blast radius numbers) alongside your interpretive analysis.
|
|
15
|
+
|
|
16
|
+
**Tool usage:** Call typegraph-mcp tools via the MCP tool interface (e.g., `ts_dependency_tree`, `ts_import_cycles`, etc.). Use `Glob` and `Grep` for file discovery steps. Use `Read` sparingly — only when a phase explicitly requires reading source to verify a hypothesis. The point is to learn as much as possible *from the graph* before reading code.
|
|
17
|
+
|
|
18
|
+
**Parallelism:** Within each phase, make independent tool calls in parallel. Between phases, respect the sequencing — later phases depend on earlier findings.
|
|
19
|
+
|
|
20
|
+
**Adaptiveness:** The procedure below uses placeholders like `<entry_point>` and `<service_file>`. Substitute actual files discovered during execution. If a phase yields surprising results, investigate before moving on — add a "Notable Finding" subsection to the report.
|
|
21
|
+
|
|
22
|
+
## Prerequisites
|
|
23
|
+
|
|
24
|
+
Run the health check first:
|
|
25
|
+
```bash
|
|
26
|
+
npx tsx ${CLAUDE_PLUGIN_ROOT}/cli.ts check
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Record three numbers from the output:
|
|
30
|
+
- **File count** — calibrates expectations (200 files = afternoon, 2000 = days)
|
|
31
|
+
- **Edge count** — total import relationships
|
|
32
|
+
- **Edge density** (edges / files) — coupling indicator (<2 = loosely coupled, 2-3 = moderate, >4 = tightly coupled)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Phase 1: Structural Skeleton
|
|
37
|
+
|
|
38
|
+
**Goal:** Map the architecture before reading any source code.
|
|
39
|
+
|
|
40
|
+
### 1a. Find the entry points
|
|
41
|
+
|
|
42
|
+
Use `Glob` to find likely entry points:
|
|
43
|
+
```
|
|
44
|
+
Glob: **/index.ts, **/main.ts, **/entry*.ts, **/worker*.ts, **/server.ts, **/app.ts
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Exclude `node_modules/` hits. The results are your starting nodes.
|
|
48
|
+
|
|
49
|
+
### 1b. Dependency tree from every entry point (depth 2)
|
|
50
|
+
|
|
51
|
+
For each entry point, run:
|
|
52
|
+
```
|
|
53
|
+
ts_dependency_tree(file: "<entry_point>", depth: 2)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Compare the results:
|
|
57
|
+
- **File counts** reveal which entry points are heavy orchestrators vs lean workers
|
|
58
|
+
- **Overlap** between trees reveals shared infrastructure (files that appear in multiple trees)
|
|
59
|
+
- **Disjoint trees** reveal isolated subsystems that don't talk to each other
|
|
60
|
+
|
|
61
|
+
Record: Which entry point is the "main" one (highest file count)? Which are satellites?
|
|
62
|
+
|
|
63
|
+
### 1c. Import cycles — find the tangles
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
ts_import_cycles()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This is the single most important diagnostic call. Record:
|
|
70
|
+
- **Cycle count** — 0 is pristine, 1-3 is normal, 10+ indicates structural problems
|
|
71
|
+
- **Cycle locations** — which directories/modules participate in cycles?
|
|
72
|
+
- **Cycle sizes** — 2-file cycles are usually intentional; 5+ file cycles are usually accidental
|
|
73
|
+
|
|
74
|
+
Cycles are places where you cannot reason about files independently. They're the first thing to understand and the last thing to refactor.
|
|
75
|
+
|
|
76
|
+
### 1d. Cross-boundary isolation — do the intended boundaries hold?
|
|
77
|
+
|
|
78
|
+
For each pair of entry points/apps that seem like they *should* be independent, run:
|
|
79
|
+
```
|
|
80
|
+
ts_shortest_path(from: "<app_a_entry>", to: "<app_b_entry>")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
A `null` result proves compile-time isolation. A non-null result reveals the chain of imports that violates the intended boundary. This is architectural assertion testing.
|
|
84
|
+
|
|
85
|
+
**Checkpoint:** You now know the shape of the architecture — how many subsystems, how coupled they are, where the tangles are, and whether boundaries hold. You haven't read a single line of source code.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Phase 2: Module Anatomy
|
|
90
|
+
|
|
91
|
+
**Goal:** Understand what each major module provides and how it connects to others.
|
|
92
|
+
|
|
93
|
+
### 2a. Identify high-fanout infrastructure files
|
|
94
|
+
|
|
95
|
+
From Phase 1, note files that appeared in multiple dependency trees. These are shared infrastructure. Common examples: schema files, type definitions, barrel exports, utility modules.
|
|
96
|
+
|
|
97
|
+
For each, run:
|
|
98
|
+
```
|
|
99
|
+
ts_dependents(file: "<shared_file>", depth: 1)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Files with 20+ direct dependents are **foundational types** — changing them affects everything. Files with 2-3 dependents are **focused utilities**. This ranking tells you which files to understand first and which to treat with extreme care.
|
|
103
|
+
|
|
104
|
+
### 2b. Module exports — what does each key file provide?
|
|
105
|
+
|
|
106
|
+
For every file identified as important (entry points, high-fanout files, files in cycle groups), run:
|
|
107
|
+
```
|
|
108
|
+
ts_module_exports(file: "<important_file>")
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Record for each:
|
|
112
|
+
- **Export count** — files with 15+ exports may be doing too much
|
|
113
|
+
- **Export types** — classes, interfaces, types, constants, functions. A file that exports 8 interfaces is a contract definition. A file that exports 8 constants is a configuration. A file that exports a mix of class + error + test layer is a service module.
|
|
114
|
+
- **Naming patterns** — do exports follow consistent naming (`*Service`, `*Error`, `*Test`, `*Live`)? Consistency across files is evidence of intentional patterns.
|
|
115
|
+
|
|
116
|
+
### 2c. Module boundaries — how coupled are directories?
|
|
117
|
+
|
|
118
|
+
Identify 3-5 directories that look like they should be self-contained modules (e.g., `services/billing/`, `providers/email/`, `middleware/`).
|
|
119
|
+
|
|
120
|
+
For each, list the files in the directory and run:
|
|
121
|
+
```
|
|
122
|
+
ts_module_boundary(files: ["<dir>/file1.ts", "<dir>/file2.ts", ...])
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Record:
|
|
126
|
+
- **Isolation score** — 0.0 = zero isolation (everything flows through), 1.0 = perfectly encapsulated
|
|
127
|
+
- **Incoming edges** — who depends on this module? (consumers)
|
|
128
|
+
- **Outgoing edges** — what does this module depend on? (dependencies)
|
|
129
|
+
- **Shared dependencies** — what do files in this module have in common?
|
|
130
|
+
|
|
131
|
+
A module with many incoming edges and few outgoing edges is a **provider** (depended upon, depends on little).
|
|
132
|
+
A module with few incoming edges and many outgoing edges is a **consumer/orchestrator** (depends on everything, nothing depends on it).
|
|
133
|
+
A module with many in both directions is a **coupling hotspot**.
|
|
134
|
+
|
|
135
|
+
**Checkpoint:** You now know what each module provides, how they connect, and which are providers vs consumers vs hotspots.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Phase 3: Pattern Discovery
|
|
140
|
+
|
|
141
|
+
**Goal:** Determine which patterns are intentional conventions vs one-off occurrences.
|
|
142
|
+
|
|
143
|
+
### 3a. Consistency analysis across files in the same role
|
|
144
|
+
|
|
145
|
+
Pick a "role" — e.g., all files named `*Service.ts`, or all files in a `services/` directory. Run `ts_module_exports` on 4-5 of them.
|
|
146
|
+
|
|
147
|
+
Compare the export shapes:
|
|
148
|
+
- Do they all export `[Name]`, `[Name]Error`, `[Name]Test`? → Intentional service pattern
|
|
149
|
+
- Do they all export a `Layer.Layer<...>` factory? → Intentional DI pattern
|
|
150
|
+
- Does one file export 5 symbols while others export 15? → The outlier is either newer, older, or doing something different
|
|
151
|
+
|
|
152
|
+
### 3b. Pattern prevalence via navigate_to
|
|
153
|
+
|
|
154
|
+
For suspected patterns, use `ts_navigate_to` to measure prevalence:
|
|
155
|
+
```
|
|
156
|
+
ts_navigate_to(symbol: "Layer") // How pervasive is DI?
|
|
157
|
+
ts_navigate_to(symbol: "Error") // How many typed errors exist?
|
|
158
|
+
ts_navigate_to(symbol: "Test") // How many test doubles exist?
|
|
159
|
+
ts_navigate_to(symbol: "Live") // How many live implementations?
|
|
160
|
+
ts_navigate_to(symbol: "Repository") // Is there a repository pattern?
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
High counts (20+) across many files = intentional, project-wide convention.
|
|
164
|
+
Low counts (2-3) in one directory = localized experiment or one-off.
|
|
165
|
+
|
|
166
|
+
### 3c. Test layer coverage — which services are testable?
|
|
167
|
+
|
|
168
|
+
If the project uses a DI pattern (Effect Layers, classes with interfaces, etc.), check whether test implementations exist alongside production ones.
|
|
169
|
+
|
|
170
|
+
For each service file found in 3a:
|
|
171
|
+
```
|
|
172
|
+
ts_module_exports(file: "<service_file>")
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Look for paired exports: `ServiceLive` + `ServiceTest`, or `Service` + `Service.Test`. Services with test layers are intentionally designed for testability. Services without them may be legacy, trivial, or undertested.
|
|
176
|
+
|
|
177
|
+
**Checkpoint:** You can now distinguish intentional patterns from accidental ones, and you know which conventions are project-wide vs localized.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Phase 4: Dead Code Detection
|
|
182
|
+
|
|
183
|
+
**Goal:** Identify exports that nothing uses and files that nothing imports.
|
|
184
|
+
|
|
185
|
+
### 4a. Orphan file detection
|
|
186
|
+
|
|
187
|
+
For every file in directories that seem to have accumulated code over time, run:
|
|
188
|
+
```
|
|
189
|
+
ts_dependents(file: "<suspect_file>", depth: 0)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Files with 0 dependents that are NOT entry points are **orphan files** — nothing imports them. They're either:
|
|
193
|
+
- Dead code (most likely)
|
|
194
|
+
- Dynamically imported (check for `import()` expressions)
|
|
195
|
+
- Entry points not recognized by the build system
|
|
196
|
+
- Test files (which typically have 0 dependents by nature — filter these out)
|
|
197
|
+
|
|
198
|
+
### 4b. Dead export detection
|
|
199
|
+
|
|
200
|
+
For files with 0 dependents, you're done — the whole file is dead. For files that ARE imported, check for partially dead exports.
|
|
201
|
+
|
|
202
|
+
Take high-export files from Phase 2b (those with 10+ exports) and run:
|
|
203
|
+
```
|
|
204
|
+
ts_references(file: "<file>", symbol: "<exported_symbol>")
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
...for each export. Exports with 0-1 references (only the export itself) are dead exports. This is tedious for large files, so prioritize:
|
|
208
|
+
- Files that feel overstuffed (15+ exports)
|
|
209
|
+
- Barrel/index files (which may re-export symbols nothing actually uses)
|
|
210
|
+
- Files in directories flagged as potentially stale
|
|
211
|
+
|
|
212
|
+
### 4c. Barrel file audit
|
|
213
|
+
|
|
214
|
+
Barrel files (`index.ts` that re-export from submodules) often accumulate dead re-exports. Run `ts_module_exports` on each barrel, then spot-check with `ts_references` on exports that seem obscure or oddly named.
|
|
215
|
+
|
|
216
|
+
**Checkpoint:** You have a list of confirmed dead files and dead exports that can be safely removed.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Phase 5: Domain Topology
|
|
221
|
+
|
|
222
|
+
**Goal:** Understand the business domain from the code structure.
|
|
223
|
+
|
|
224
|
+
### 5a. Entity identification from schema/type files
|
|
225
|
+
|
|
226
|
+
Find schema or type definition files:
|
|
227
|
+
```
|
|
228
|
+
Glob: **/schemas/*.ts, **/types/*.ts, **/models/*.ts, **/domain/*.ts
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Run `ts_module_exports` on each. The exported type/interface names *are* the domain vocabulary: `User`, `Tenant`, `Todo`, `Invoice`, `Subscription`, etc.
|
|
232
|
+
|
|
233
|
+
### 5b. Entity relationship mapping via dependency_tree
|
|
234
|
+
|
|
235
|
+
For each domain entity's primary service file, run:
|
|
236
|
+
```
|
|
237
|
+
ts_dependency_tree(file: "<entity_service>", depth: 1)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
The direct dependencies reveal domain relationships:
|
|
241
|
+
- `TodoShareService` depends on `AddressService` and `ClaimTokenService` → sharing requires addresses and tokens
|
|
242
|
+
- `BillingService` depends on `CoreApiClient` and `StripeCheckoutClient` → billing bridges internal data with an external API
|
|
243
|
+
- `NotificationService` depends on `EmailProvider` and `SmsProvider` → notifications are multi-channel
|
|
244
|
+
|
|
245
|
+
Draw a mental graph: entities are nodes, service-to-service imports are edges. This is the domain topology.
|
|
246
|
+
|
|
247
|
+
### 5c. Domain boundary verification
|
|
248
|
+
|
|
249
|
+
For domain areas that seem like they should be independent (e.g., billing vs notifications, auth vs todo), run:
|
|
250
|
+
```
|
|
251
|
+
ts_shortest_path(from: "<domain_a_service>", to: "<domain_b_service>")
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Null = truly independent domains. A path = one domain depends on the other (and the path shows exactly how).
|
|
255
|
+
|
|
256
|
+
### 5d. Entity access patterns via blast_radius
|
|
257
|
+
|
|
258
|
+
For the central domain entity (usually a `User`, `Account`, or core API client), run:
|
|
259
|
+
```
|
|
260
|
+
ts_blast_radius(file: "<entity_file>", symbol: "<EntityName>")
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
The caller list, grouped by file, shows which parts of the system access the entity and how. Middleware files accessing it = access control. Handler files = API surface. Service files = business logic. This reveals the entity's role in the system better than reading its definition.
|
|
264
|
+
|
|
265
|
+
**Checkpoint:** You understand the domain model, entity relationships, and which domains are coupled vs independent — derived entirely from code structure.
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Phase 6: Runtime Behavior Approximation
|
|
270
|
+
|
|
271
|
+
**Goal:** Trace likely execution paths from external inputs to internal effects.
|
|
272
|
+
|
|
273
|
+
### 6a. Request flow tracing
|
|
274
|
+
|
|
275
|
+
For each RPC/HTTP handler or API route, run:
|
|
276
|
+
```
|
|
277
|
+
ts_trace_chain(file: "<handler_file>", symbol: "<HandlerOrRouteGroup>")
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
This follows go-to-definition hops from the handler to its dependencies, building an approximation of the call chain. Each hop = one layer of indirection.
|
|
281
|
+
|
|
282
|
+
Shallow chains (1-2 hops) = thin handlers that delegate directly to services.
|
|
283
|
+
Deep chains (4-5 hops) = layered architecture with middleware, decorators, or adapters.
|
|
284
|
+
|
|
285
|
+
### 6b. Layer composition analysis
|
|
286
|
+
|
|
287
|
+
For the main entry point / composition root (typically the file with the most Phase 1 dependencies), run:
|
|
288
|
+
```
|
|
289
|
+
ts_module_exports(file: "<composition_root>")
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Then for each exported Layer or provider, use `ts_trace_chain` to see what it wires together. In DI-heavy codebases, the Layer/provider composition *is* the runtime wiring — following the chain tells you exactly what services are live.
|
|
293
|
+
|
|
294
|
+
### 6c. Service implementation mapping
|
|
295
|
+
|
|
296
|
+
For every service *interface* file, use `ts_navigate_to` to find its implementations:
|
|
297
|
+
```
|
|
298
|
+
ts_navigate_to(symbol: "<ServiceName>Live")
|
|
299
|
+
ts_navigate_to(symbol: "<ServiceName>Test")
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
This reveals:
|
|
303
|
+
- How many implementations exist per interface (1 = standard, 2+ = strategy pattern or platform-specific)
|
|
304
|
+
- Which services have test doubles (intentionally testable) vs which don't (may rely on integration tests)
|
|
305
|
+
- Where implementations live relative to interfaces (same package = co-located, different app = platform-specific)
|
|
306
|
+
|
|
307
|
+
### 6d. Queue/event handler discovery
|
|
308
|
+
|
|
309
|
+
For async or event-driven systems, find queue consumers, event handlers, or scheduled jobs:
|
|
310
|
+
```
|
|
311
|
+
Glob: **/jobs/*.ts, **/handlers/*.ts, **/consumers/*.ts, **/workers/*.ts
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Run `ts_dependency_tree(depth: 1)` on each. Their dependencies reveal what domain logic is triggered asynchronously vs synchronously. Services that appear in both HTTP handler trees AND job handler trees participate in both synchronous and async paths.
|
|
315
|
+
|
|
316
|
+
### 6e. Feature flag / conditional path detection
|
|
317
|
+
|
|
318
|
+
This is the hardest to detect statically. Use `Grep` for common patterns:
|
|
319
|
+
```
|
|
320
|
+
Grep: "feature", "flag", "toggle", "experiment", "enabled", "FF_", "FEATURE_"
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Then for any files found, use `ts_dependents` to see how far the conditional reaches. A feature flag in a leaf file affects one path. A feature flag in a service depended on by 15 files affects many paths.
|
|
324
|
+
|
|
325
|
+
**Checkpoint:** You have traced the likely runtime paths from external inputs through handlers to services to data, identified async vs sync processing, and flagged feature-gated code.
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Phase 7: Risk Assessment
|
|
330
|
+
|
|
331
|
+
**Goal:** Before making any changes, know where the risk concentrates.
|
|
332
|
+
|
|
333
|
+
### 7a. Blast radius ranking
|
|
334
|
+
|
|
335
|
+
For the top 5-10 most-referenced symbols discovered in earlier phases, run:
|
|
336
|
+
```
|
|
337
|
+
ts_blast_radius(file: "<file>", symbol: "<symbol>")
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Sort by `filesAffected`. The top 5 are your highest-risk changes. Plan these carefully, test extensively, and consider backwards-compatible migration strategies.
|
|
341
|
+
|
|
342
|
+
### 7b. Dependency inversion check
|
|
343
|
+
|
|
344
|
+
For high-risk symbols, check if the coupling goes through an abstraction:
|
|
345
|
+
```
|
|
346
|
+
ts_dependents(file: "<interface_file>", depth: 1)
|
|
347
|
+
ts_dependents(file: "<implementation_file>", depth: 1)
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
If dependents point to the *interface* file (not the implementation), the system is properly inverted — you can change implementations without affecting consumers. If dependents point to the *implementation* directly, changing it will break callers.
|
|
351
|
+
|
|
352
|
+
### 7c. Change propagation preview
|
|
353
|
+
|
|
354
|
+
For a planned change, combine tools:
|
|
355
|
+
1. `ts_blast_radius` — who references this symbol?
|
|
356
|
+
2. `ts_dependents` on the file — who imports this file?
|
|
357
|
+
3. `ts_module_boundary` on the affected directory — how does this change propagate to the module boundary?
|
|
358
|
+
|
|
359
|
+
This three-tool sequence gives you a complete picture: direct references (blast_radius), file-level impact (dependents), and module-level impact (boundary).
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Report Format
|
|
364
|
+
|
|
365
|
+
Write the report to `<project_root>/typegraph-exploration-report.md` using this structure:
|
|
366
|
+
|
|
367
|
+
```markdown
|
|
368
|
+
# Codebase Exploration Report
|
|
369
|
+
> Generated: <date>
|
|
370
|
+
> Project: <project_root>
|
|
371
|
+
> Files: <count> | Edges: <count> | Density: <ratio>
|
|
372
|
+
|
|
373
|
+
## Executive Summary
|
|
374
|
+
<!-- 3-5 bullet points: the most important findings across all phases -->
|
|
375
|
+
|
|
376
|
+
## Phase 1: Structural Skeleton
|
|
377
|
+
### Entry Points
|
|
378
|
+
### Import Cycles
|
|
379
|
+
### Boundary Verification
|
|
380
|
+
### Checkpoint
|
|
381
|
+
|
|
382
|
+
## Phase 2: Module Anatomy
|
|
383
|
+
### High-Fanout Files
|
|
384
|
+
### Module Export Profiles
|
|
385
|
+
### Module Boundaries
|
|
386
|
+
### Checkpoint
|
|
387
|
+
|
|
388
|
+
## Phase 3: Pattern Discovery
|
|
389
|
+
### Consistency Analysis
|
|
390
|
+
### Pattern Prevalence
|
|
391
|
+
### Test Layer Coverage
|
|
392
|
+
### Checkpoint
|
|
393
|
+
|
|
394
|
+
## Phase 4: Dead Code Detection
|
|
395
|
+
### Orphan Files
|
|
396
|
+
### Dead Exports
|
|
397
|
+
### Barrel File Audit
|
|
398
|
+
### Checkpoint
|
|
399
|
+
|
|
400
|
+
## Phase 5: Domain Topology
|
|
401
|
+
### Domain Vocabulary
|
|
402
|
+
### Entity Relationships
|
|
403
|
+
### Domain Independence
|
|
404
|
+
### Entity Access Patterns
|
|
405
|
+
### Checkpoint
|
|
406
|
+
|
|
407
|
+
## Phase 6: Runtime Behavior Approximation
|
|
408
|
+
### Request Flow Traces
|
|
409
|
+
### Layer Composition
|
|
410
|
+
### Implementation Map
|
|
411
|
+
### Async Paths
|
|
412
|
+
### Feature Flags
|
|
413
|
+
### Checkpoint
|
|
414
|
+
|
|
415
|
+
## Phase 7: Risk Assessment
|
|
416
|
+
### Blast Radius Ranking
|
|
417
|
+
### Dependency Inversion Health
|
|
418
|
+
### Change Propagation Hotspots
|
|
419
|
+
### Checkpoint
|
|
420
|
+
|
|
421
|
+
## Appendix: Raw Data
|
|
422
|
+
<!-- Full tool outputs under <details> tags -->
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Guidelines:
|
|
426
|
+
- Include actual numbers, file paths, and tool outputs — not vague summaries
|
|
427
|
+
- Use tables for structured data (blast radius rankings, module boundaries, etc.)
|
|
428
|
+
- Add a "Notable Findings" subsection after any checkpoint where something unexpected appeared
|
|
429
|
+
- Put verbose raw tool outputs in the Appendix under `<details>` tags to keep the main report scannable
|
|
430
|
+
- Write the Executive Summary last, after all phases complete
|
|
431
|
+
|
|
432
|
+
## Quick Reference
|
|
433
|
+
|
|
434
|
+
| Phase | Tools | Answers |
|
|
435
|
+
|-------|-------|---------|
|
|
436
|
+
| 1. Skeleton | `dependency_tree`, `import_cycles`, `shortest_path` | Architecture shape, boundaries, tangles |
|
|
437
|
+
| 2. Anatomy | `dependents`, `module_exports`, `module_boundary` | What modules provide, how they connect |
|
|
438
|
+
| 3. Patterns | `module_exports` (comparative), `navigate_to` | Intentional vs accidental conventions |
|
|
439
|
+
| 4. Dead Code | `dependents` (0 check), `references` (per export) | Orphan files, dead exports |
|
|
440
|
+
| 5. Domain | `dependency_tree`, `shortest_path`, `blast_radius` | Entity relationships, domain topology |
|
|
441
|
+
| 6. Runtime | `trace_chain`, `navigate_to`, `dependency_tree` | Execution paths, wiring, async flows |
|
|
442
|
+
| 7. Risk | `blast_radius`, `dependents`, `module_boundary` | Change impact, coupling direction |
|
package/dist/check.js
CHANGED
|
@@ -355,7 +355,7 @@ var init_module_graph = __esm({
|
|
|
355
355
|
import * as fs2 from "fs";
|
|
356
356
|
import * as path3 from "path";
|
|
357
357
|
import { createRequire } from "module";
|
|
358
|
-
import { spawn } from "child_process";
|
|
358
|
+
import { spawn, spawnSync } from "child_process";
|
|
359
359
|
|
|
360
360
|
// config.ts
|
|
361
361
|
import * as path from "path";
|
|
@@ -495,10 +495,17 @@ async function main(configOverride) {
|
|
|
495
495
|
}
|
|
496
496
|
const pluginMcpPath = path3.join(toolDir, ".mcp.json");
|
|
497
497
|
const hasPluginMcp = fs2.existsSync(pluginMcpPath) && fs2.existsSync(path3.join(toolDir, ".claude-plugin/plugin.json"));
|
|
498
|
+
const codexGet = spawnSync("codex", ["mcp", "get", "typegraph"], {
|
|
499
|
+
stdio: "pipe",
|
|
500
|
+
encoding: "utf-8"
|
|
501
|
+
});
|
|
502
|
+
const hasCodexRegistration = codexGet.status === 0;
|
|
498
503
|
if (process.env.CLAUDE_PLUGIN_ROOT) {
|
|
499
504
|
pass("MCP registered via plugin (CLAUDE_PLUGIN_ROOT set)");
|
|
500
505
|
} else if (hasPluginMcp) {
|
|
501
506
|
pass("MCP registered via plugin (.mcp.json + .claude-plugin/ present)");
|
|
507
|
+
} else if (hasCodexRegistration) {
|
|
508
|
+
pass("MCP registered in Codex CLI");
|
|
502
509
|
} else {
|
|
503
510
|
const mcpJsonPath = path3.resolve(projectRoot, ".claude/mcp.json");
|
|
504
511
|
if (fs2.existsSync(mcpJsonPath)) {
|
package/dist/cli.js
CHANGED
|
@@ -375,7 +375,7 @@ __export(check_exports, {
|
|
|
375
375
|
import * as fs2 from "fs";
|
|
376
376
|
import * as path3 from "path";
|
|
377
377
|
import { createRequire } from "module";
|
|
378
|
-
import { spawn } from "child_process";
|
|
378
|
+
import { spawn, spawnSync } from "child_process";
|
|
379
379
|
function findFirstTsFile(dir) {
|
|
380
380
|
const skipDirs = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", ".wrangler", "coverage"]);
|
|
381
381
|
try {
|
|
@@ -502,10 +502,17 @@ async function main(configOverride) {
|
|
|
502
502
|
}
|
|
503
503
|
const pluginMcpPath = path3.join(toolDir, ".mcp.json");
|
|
504
504
|
const hasPluginMcp = fs2.existsSync(pluginMcpPath) && fs2.existsSync(path3.join(toolDir, ".claude-plugin/plugin.json"));
|
|
505
|
+
const codexGet = spawnSync("codex", ["mcp", "get", "typegraph"], {
|
|
506
|
+
stdio: "pipe",
|
|
507
|
+
encoding: "utf-8"
|
|
508
|
+
});
|
|
509
|
+
const hasCodexRegistration = codexGet.status === 0;
|
|
505
510
|
if (process.env.CLAUDE_PLUGIN_ROOT) {
|
|
506
511
|
pass("MCP registered via plugin (CLAUDE_PLUGIN_ROOT set)");
|
|
507
512
|
} else if (hasPluginMcp) {
|
|
508
513
|
pass("MCP registered via plugin (.mcp.json + .claude-plugin/ present)");
|
|
514
|
+
} else if (hasCodexRegistration) {
|
|
515
|
+
pass("MCP registered in Codex CLI");
|
|
509
516
|
} else {
|
|
510
517
|
const mcpJsonPath = path3.resolve(projectRoot3, ".claude/mcp.json");
|
|
511
518
|
if (fs2.existsSync(mcpJsonPath)) {
|
|
@@ -2795,7 +2802,7 @@ var init_server = __esm({
|
|
|
2795
2802
|
init_config();
|
|
2796
2803
|
import * as fs8 from "fs";
|
|
2797
2804
|
import * as path9 from "path";
|
|
2798
|
-
import { execSync as execSync2 } from "child_process";
|
|
2805
|
+
import { execSync as execSync2, spawnSync as spawnSync2 } from "child_process";
|
|
2799
2806
|
import * as p from "@clack/prompts";
|
|
2800
2807
|
var AGENT_SNIPPET = `
|
|
2801
2808
|
## TypeScript Navigation (typegraph-mcp)
|
|
@@ -2817,7 +2824,8 @@ var AGENTS = {
|
|
|
2817
2824
|
"scripts/ensure-deps.sh",
|
|
2818
2825
|
"commands/check.md",
|
|
2819
2826
|
"commands/test.md",
|
|
2820
|
-
"commands/bench.md"
|
|
2827
|
+
"commands/bench.md",
|
|
2828
|
+
"commands/deep-survey.md"
|
|
2821
2829
|
],
|
|
2822
2830
|
agentFile: "CLAUDE.md",
|
|
2823
2831
|
needsAgentsSkills: false,
|
|
@@ -2868,14 +2876,16 @@ var SKILL_FILES = [
|
|
|
2868
2876
|
"skills/impact-analysis/SKILL.md",
|
|
2869
2877
|
"skills/refactor-safety/SKILL.md",
|
|
2870
2878
|
"skills/dependency-audit/SKILL.md",
|
|
2871
|
-
"skills/code-exploration/SKILL.md"
|
|
2879
|
+
"skills/code-exploration/SKILL.md",
|
|
2880
|
+
"skills/deep-survey/SKILL.md"
|
|
2872
2881
|
];
|
|
2873
2882
|
var SKILL_NAMES = [
|
|
2874
2883
|
"tool-selection",
|
|
2875
2884
|
"impact-analysis",
|
|
2876
2885
|
"refactor-safety",
|
|
2877
2886
|
"dependency-audit",
|
|
2878
|
-
"code-exploration"
|
|
2887
|
+
"code-exploration",
|
|
2888
|
+
"deep-survey"
|
|
2879
2889
|
];
|
|
2880
2890
|
var HELP = `
|
|
2881
2891
|
typegraph-mcp \u2014 Type-aware codebase navigation for AI coding agents.
|
|
@@ -2912,6 +2922,16 @@ var MCP_SERVER_ENTRY = {
|
|
|
2912
2922
|
TYPEGRAPH_TSCONFIG: "./tsconfig.json"
|
|
2913
2923
|
}
|
|
2914
2924
|
};
|
|
2925
|
+
function getAbsoluteMcpServerEntry(projectRoot3) {
|
|
2926
|
+
return {
|
|
2927
|
+
command: "npx",
|
|
2928
|
+
args: ["tsx", path9.resolve(projectRoot3, PLUGIN_DIR_NAME, "server.ts")],
|
|
2929
|
+
env: {
|
|
2930
|
+
TYPEGRAPH_PROJECT_ROOT: projectRoot3,
|
|
2931
|
+
TYPEGRAPH_TSCONFIG: path9.resolve(projectRoot3, "tsconfig.json")
|
|
2932
|
+
}
|
|
2933
|
+
};
|
|
2934
|
+
}
|
|
2915
2935
|
function registerMcpServers(projectRoot3, selectedAgents) {
|
|
2916
2936
|
if (selectedAgents.includes("cursor")) {
|
|
2917
2937
|
registerJsonMcp(projectRoot3, ".cursor/mcp.json", "mcpServers");
|
|
@@ -2974,6 +2994,51 @@ function deregisterJsonMcp(projectRoot3, configPath, rootKey) {
|
|
|
2974
2994
|
}
|
|
2975
2995
|
}
|
|
2976
2996
|
function registerCodexMcp(projectRoot3) {
|
|
2997
|
+
const absoluteEntry = getAbsoluteMcpServerEntry(projectRoot3);
|
|
2998
|
+
const codexGet = spawnSync2("codex", ["mcp", "get", "typegraph"], {
|
|
2999
|
+
stdio: "pipe",
|
|
3000
|
+
encoding: "utf-8"
|
|
3001
|
+
});
|
|
3002
|
+
if (codexGet.status === 0) {
|
|
3003
|
+
const output = `${codexGet.stdout ?? ""}${codexGet.stderr ?? ""}`;
|
|
3004
|
+
const hasServerPath = output.includes(absoluteEntry.args[1]);
|
|
3005
|
+
const hasProjectRoot = output.includes("TYPEGRAPH_PROJECT_ROOT=*****") || output.includes(projectRoot3);
|
|
3006
|
+
const hasTsconfig = output.includes("TYPEGRAPH_TSCONFIG=*****") || output.includes(path9.resolve(projectRoot3, "tsconfig.json"));
|
|
3007
|
+
if (hasServerPath && hasProjectRoot && hasTsconfig) {
|
|
3008
|
+
p.log.info("Codex CLI: typegraph MCP server already registered");
|
|
3009
|
+
return;
|
|
3010
|
+
}
|
|
3011
|
+
spawnSync2("codex", ["mcp", "remove", "typegraph"], {
|
|
3012
|
+
stdio: "pipe",
|
|
3013
|
+
encoding: "utf-8"
|
|
3014
|
+
});
|
|
3015
|
+
}
|
|
3016
|
+
const codexAdd = spawnSync2(
|
|
3017
|
+
"codex",
|
|
3018
|
+
[
|
|
3019
|
+
"mcp",
|
|
3020
|
+
"add",
|
|
3021
|
+
"typegraph",
|
|
3022
|
+
"--env",
|
|
3023
|
+
`TYPEGRAPH_PROJECT_ROOT=${absoluteEntry.env.TYPEGRAPH_PROJECT_ROOT}`,
|
|
3024
|
+
"--env",
|
|
3025
|
+
`TYPEGRAPH_TSCONFIG=${absoluteEntry.env.TYPEGRAPH_TSCONFIG}`,
|
|
3026
|
+
"--",
|
|
3027
|
+
absoluteEntry.command,
|
|
3028
|
+
...absoluteEntry.args
|
|
3029
|
+
],
|
|
3030
|
+
{
|
|
3031
|
+
stdio: "pipe",
|
|
3032
|
+
encoding: "utf-8"
|
|
3033
|
+
}
|
|
3034
|
+
);
|
|
3035
|
+
if (codexAdd.status === 0) {
|
|
3036
|
+
p.log.success("Codex CLI: registered typegraph MCP server");
|
|
3037
|
+
return;
|
|
3038
|
+
}
|
|
3039
|
+
p.log.warn(
|
|
3040
|
+
`Codex CLI registration failed \u2014 falling back to ${".codex/config.toml"}`
|
|
3041
|
+
);
|
|
2977
3042
|
const configPath = ".codex/config.toml";
|
|
2978
3043
|
const fullPath = path9.resolve(projectRoot3, configPath);
|
|
2979
3044
|
let content = "";
|
|
@@ -2987,9 +3052,9 @@ function registerCodexMcp(projectRoot3) {
|
|
|
2987
3052
|
const block = [
|
|
2988
3053
|
"",
|
|
2989
3054
|
"[mcp_servers.typegraph]",
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
3055
|
+
`command = "${absoluteEntry.command}"`,
|
|
3056
|
+
`args = ["${absoluteEntry.args[0]}", "${absoluteEntry.args[1]}"]`,
|
|
3057
|
+
`env = { TYPEGRAPH_PROJECT_ROOT = "${absoluteEntry.env.TYPEGRAPH_PROJECT_ROOT}", TYPEGRAPH_TSCONFIG = "${absoluteEntry.env.TYPEGRAPH_TSCONFIG}" }`,
|
|
2993
3058
|
""
|
|
2994
3059
|
].join("\n");
|
|
2995
3060
|
const dir = path9.dirname(fullPath);
|
|
@@ -3001,6 +3066,13 @@ function registerCodexMcp(projectRoot3) {
|
|
|
3001
3066
|
p.log.success(`${configPath}: registered typegraph MCP server`);
|
|
3002
3067
|
}
|
|
3003
3068
|
function deregisterCodexMcp(projectRoot3) {
|
|
3069
|
+
const codexRemove = spawnSync2("codex", ["mcp", "remove", "typegraph"], {
|
|
3070
|
+
stdio: "pipe",
|
|
3071
|
+
encoding: "utf-8"
|
|
3072
|
+
});
|
|
3073
|
+
if (codexRemove.status === 0) {
|
|
3074
|
+
p.log.info("Codex CLI: removed typegraph MCP server");
|
|
3075
|
+
}
|
|
3004
3076
|
const configPath = ".codex/config.toml";
|
|
3005
3077
|
const fullPath = path9.resolve(projectRoot3, configPath);
|
|
3006
3078
|
if (!fs8.existsSync(fullPath)) return;
|
|
@@ -3374,7 +3446,7 @@ async function runVerification(pluginDir, selectedAgents) {
|
|
|
3374
3446
|
console.log("");
|
|
3375
3447
|
if (checkResult.failed === 0 && testResult.failed === 0) {
|
|
3376
3448
|
if (selectedAgents.includes("claude-code")) {
|
|
3377
|
-
p.outro("Setup complete! Run: claude --plugin-dir ./plugins/typegraph-mcp\n Slash commands: /typegraph:check, /typegraph:test, /typegraph:bench");
|
|
3449
|
+
p.outro("Setup complete! Run: claude --plugin-dir ./plugins/typegraph-mcp\n Slash commands: /typegraph:check, /typegraph:test, /typegraph:bench, /typegraph:deep-survey");
|
|
3378
3450
|
} else {
|
|
3379
3451
|
p.outro("Setup complete! typegraph-mcp tools are now available to your agents.\n CLI: npx typegraph-mcp check | test | bench");
|
|
3380
3452
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typegraph-mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.28",
|
|
4
4
|
"description": "Type-aware codebase navigation for AI coding agents — 14 MCP tools powered by tsserver + oxc",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/node": "^25.3.0",
|
|
45
45
|
"tsup": "^8.5.0",
|
|
46
|
+
"tsx": "^4.21.0",
|
|
46
47
|
"typescript": "^5.8.0"
|
|
47
48
|
}
|
|
48
49
|
}
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deep-survey
|
|
3
|
+
description: Run a comprehensive 7-phase codebase analysis using all typegraph-mcp tools, producing a detailed architectural report. Trigger when onboarding to an unfamiliar codebase, requesting a full architectural overview, asking for a codebase health report, or needing to understand the overall structure before making significant changes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TypeGraph Deep Survey
|
|
7
|
+
|
|
8
|
+
Run a comprehensive 7-phase codebase exploration using all typegraph-mcp tools. Produces a detailed architectural report at `typegraph-exploration-report.md` in the project root.
|
|
9
|
+
|
|
10
|
+
## When to Activate
|
|
11
|
+
|
|
12
|
+
- User is onboarding to an unfamiliar or brownfield codebase
|
|
13
|
+
- User requests a full architectural overview or codebase survey
|
|
14
|
+
- User asks for a codebase health report or structural analysis
|
|
15
|
+
- User needs to understand overall structure before significant changes
|
|
16
|
+
- User asks "what does this codebase look like?" or "give me an overview"
|
|
17
|
+
|
|
18
|
+
## Execution Instructions
|
|
19
|
+
|
|
20
|
+
Follow the phases below in order. Each phase produces findings.
|
|
21
|
+
|
|
22
|
+
**Report output:** Write a markdown report to `<project_root>/typegraph-exploration-report.md` as you go. After each phase checkpoint, append that phase's findings to the report. Structure the report with the same phase headings used below. Include raw data (file counts, edge counts, cycle lists, blast radius numbers) alongside your interpretive analysis.
|
|
23
|
+
|
|
24
|
+
**Tool usage:** Call typegraph-mcp tools via the MCP tool interface (e.g., `ts_dependency_tree`, `ts_import_cycles`, etc.). Use `Glob` and `Grep` for file discovery steps. Use `Read` sparingly — only when a phase explicitly requires reading source to verify a hypothesis. The point is to learn as much as possible *from the graph* before reading code.
|
|
25
|
+
|
|
26
|
+
**Parallelism:** Within each phase, make independent tool calls in parallel. Between phases, respect the sequencing — later phases depend on earlier findings.
|
|
27
|
+
|
|
28
|
+
**Adaptiveness:** The procedure below uses placeholders like `<entry_point>` and `<service_file>`. Substitute actual files discovered during execution. If a phase yields surprising results, investigate before moving on — add a "Notable Finding" subsection to the report.
|
|
29
|
+
|
|
30
|
+
## Prerequisites
|
|
31
|
+
|
|
32
|
+
Run the health check first:
|
|
33
|
+
```bash
|
|
34
|
+
npx tsx ${CLAUDE_PLUGIN_ROOT}/cli.ts check
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Record three numbers from the output:
|
|
38
|
+
- **File count** — calibrates expectations (200 files = afternoon, 2000 = days)
|
|
39
|
+
- **Edge count** — total import relationships
|
|
40
|
+
- **Edge density** (edges / files) — coupling indicator (<2 = loosely coupled, 2-3 = moderate, >4 = tightly coupled)
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Phase 1: Structural Skeleton
|
|
45
|
+
|
|
46
|
+
**Goal:** Map the architecture before reading any source code.
|
|
47
|
+
|
|
48
|
+
### 1a. Find the entry points
|
|
49
|
+
|
|
50
|
+
Use `Glob` to find likely entry points:
|
|
51
|
+
```
|
|
52
|
+
Glob: **/index.ts, **/main.ts, **/entry*.ts, **/worker*.ts, **/server.ts, **/app.ts
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Exclude `node_modules/` hits. The results are your starting nodes.
|
|
56
|
+
|
|
57
|
+
### 1b. Dependency tree from every entry point (depth 2)
|
|
58
|
+
|
|
59
|
+
For each entry point, run:
|
|
60
|
+
```
|
|
61
|
+
ts_dependency_tree(file: "<entry_point>", depth: 2)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Compare the results:
|
|
65
|
+
- **File counts** reveal which entry points are heavy orchestrators vs lean workers
|
|
66
|
+
- **Overlap** between trees reveals shared infrastructure (files that appear in multiple trees)
|
|
67
|
+
- **Disjoint trees** reveal isolated subsystems that don't talk to each other
|
|
68
|
+
|
|
69
|
+
Record: Which entry point is the "main" one (highest file count)? Which are satellites?
|
|
70
|
+
|
|
71
|
+
### 1c. Import cycles — find the tangles
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
ts_import_cycles()
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
This is the single most important diagnostic call. Record:
|
|
78
|
+
- **Cycle count** — 0 is pristine, 1-3 is normal, 10+ indicates structural problems
|
|
79
|
+
- **Cycle locations** — which directories/modules participate in cycles?
|
|
80
|
+
- **Cycle sizes** — 2-file cycles are usually intentional; 5+ file cycles are usually accidental
|
|
81
|
+
|
|
82
|
+
Cycles are places where you cannot reason about files independently. They're the first thing to understand and the last thing to refactor.
|
|
83
|
+
|
|
84
|
+
### 1d. Cross-boundary isolation — do the intended boundaries hold?
|
|
85
|
+
|
|
86
|
+
For each pair of entry points/apps that seem like they *should* be independent, run:
|
|
87
|
+
```
|
|
88
|
+
ts_shortest_path(from: "<app_a_entry>", to: "<app_b_entry>")
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
A `null` result proves compile-time isolation. A non-null result reveals the chain of imports that violates the intended boundary. This is architectural assertion testing.
|
|
92
|
+
|
|
93
|
+
**Checkpoint:** You now know the shape of the architecture — how many subsystems, how coupled they are, where the tangles are, and whether boundaries hold. You haven't read a single line of source code.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Phase 2: Module Anatomy
|
|
98
|
+
|
|
99
|
+
**Goal:** Understand what each major module provides and how it connects to others.
|
|
100
|
+
|
|
101
|
+
### 2a. Identify high-fanout infrastructure files
|
|
102
|
+
|
|
103
|
+
From Phase 1, note files that appeared in multiple dependency trees. These are shared infrastructure. Common examples: schema files, type definitions, barrel exports, utility modules.
|
|
104
|
+
|
|
105
|
+
For each, run:
|
|
106
|
+
```
|
|
107
|
+
ts_dependents(file: "<shared_file>", depth: 1)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Files with 20+ direct dependents are **foundational types** — changing them affects everything. Files with 2-3 dependents are **focused utilities**. This ranking tells you which files to understand first and which to treat with extreme care.
|
|
111
|
+
|
|
112
|
+
### 2b. Module exports — what does each key file provide?
|
|
113
|
+
|
|
114
|
+
For every file identified as important (entry points, high-fanout files, files in cycle groups), run:
|
|
115
|
+
```
|
|
116
|
+
ts_module_exports(file: "<important_file>")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Record for each:
|
|
120
|
+
- **Export count** — files with 15+ exports may be doing too much
|
|
121
|
+
- **Export types** — classes, interfaces, types, constants, functions. A file that exports 8 interfaces is a contract definition. A file that exports 8 constants is a configuration. A file that exports a mix of class + error + test layer is a service module.
|
|
122
|
+
- **Naming patterns** — do exports follow consistent naming (`*Service`, `*Error`, `*Test`, `*Live`)? Consistency across files is evidence of intentional patterns.
|
|
123
|
+
|
|
124
|
+
### 2c. Module boundaries — how coupled are directories?
|
|
125
|
+
|
|
126
|
+
Identify 3-5 directories that look like they should be self-contained modules (e.g., `services/billing/`, `providers/email/`, `middleware/`).
|
|
127
|
+
|
|
128
|
+
For each, list the files in the directory and run:
|
|
129
|
+
```
|
|
130
|
+
ts_module_boundary(files: ["<dir>/file1.ts", "<dir>/file2.ts", ...])
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Record:
|
|
134
|
+
- **Isolation score** — 0.0 = zero isolation (everything flows through), 1.0 = perfectly encapsulated
|
|
135
|
+
- **Incoming edges** — who depends on this module? (consumers)
|
|
136
|
+
- **Outgoing edges** — what does this module depend on? (dependencies)
|
|
137
|
+
- **Shared dependencies** — what do files in this module have in common?
|
|
138
|
+
|
|
139
|
+
A module with many incoming edges and few outgoing edges is a **provider** (depended upon, depends on little).
|
|
140
|
+
A module with few incoming edges and many outgoing edges is a **consumer/orchestrator** (depends on everything, nothing depends on it).
|
|
141
|
+
A module with many in both directions is a **coupling hotspot**.
|
|
142
|
+
|
|
143
|
+
**Checkpoint:** You now know what each module provides, how they connect, and which are providers vs consumers vs hotspots.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Phase 3: Pattern Discovery
|
|
148
|
+
|
|
149
|
+
**Goal:** Determine which patterns are intentional conventions vs one-off occurrences.
|
|
150
|
+
|
|
151
|
+
### 3a. Consistency analysis across files in the same role
|
|
152
|
+
|
|
153
|
+
Pick a "role" — e.g., all files named `*Service.ts`, or all files in a `services/` directory. Run `ts_module_exports` on 4-5 of them.
|
|
154
|
+
|
|
155
|
+
Compare the export shapes:
|
|
156
|
+
- Do they all export `[Name]`, `[Name]Error`, `[Name]Test`? → Intentional service pattern
|
|
157
|
+
- Do they all export a `Layer.Layer<...>` factory? → Intentional DI pattern
|
|
158
|
+
- Does one file export 5 symbols while others export 15? → The outlier is either newer, older, or doing something different
|
|
159
|
+
|
|
160
|
+
### 3b. Pattern prevalence via navigate_to
|
|
161
|
+
|
|
162
|
+
For suspected patterns, use `ts_navigate_to` to measure prevalence:
|
|
163
|
+
```
|
|
164
|
+
ts_navigate_to(symbol: "Layer") // How pervasive is DI?
|
|
165
|
+
ts_navigate_to(symbol: "Error") // How many typed errors exist?
|
|
166
|
+
ts_navigate_to(symbol: "Test") // How many test doubles exist?
|
|
167
|
+
ts_navigate_to(symbol: "Live") // How many live implementations?
|
|
168
|
+
ts_navigate_to(symbol: "Repository") // Is there a repository pattern?
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
High counts (20+) across many files = intentional, project-wide convention.
|
|
172
|
+
Low counts (2-3) in one directory = localized experiment or one-off.
|
|
173
|
+
|
|
174
|
+
### 3c. Test layer coverage — which services are testable?
|
|
175
|
+
|
|
176
|
+
If the project uses a DI pattern (Effect Layers, classes with interfaces, etc.), check whether test implementations exist alongside production ones.
|
|
177
|
+
|
|
178
|
+
For each service file found in 3a:
|
|
179
|
+
```
|
|
180
|
+
ts_module_exports(file: "<service_file>")
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Look for paired exports: `ServiceLive` + `ServiceTest`, or `Service` + `Service.Test`. Services with test layers are intentionally designed for testability. Services without them may be legacy, trivial, or undertested.
|
|
184
|
+
|
|
185
|
+
**Checkpoint:** You can now distinguish intentional patterns from accidental ones, and you know which conventions are project-wide vs localized.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Phase 4: Dead Code Detection
|
|
190
|
+
|
|
191
|
+
**Goal:** Identify exports that nothing uses and files that nothing imports.
|
|
192
|
+
|
|
193
|
+
### 4a. Orphan file detection
|
|
194
|
+
|
|
195
|
+
For every file in directories that seem to have accumulated code over time, run:
|
|
196
|
+
```
|
|
197
|
+
ts_dependents(file: "<suspect_file>", depth: 0)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Files with 0 dependents that are NOT entry points are **orphan files** — nothing imports them. They're either:
|
|
201
|
+
- Dead code (most likely)
|
|
202
|
+
- Dynamically imported (check for `import()` expressions)
|
|
203
|
+
- Entry points not recognized by the build system
|
|
204
|
+
- Test files (which typically have 0 dependents by nature — filter these out)
|
|
205
|
+
|
|
206
|
+
### 4b. Dead export detection
|
|
207
|
+
|
|
208
|
+
For files with 0 dependents, you're done — the whole file is dead. For files that ARE imported, check for partially dead exports.
|
|
209
|
+
|
|
210
|
+
Take high-export files from Phase 2b (those with 10+ exports) and run:
|
|
211
|
+
```
|
|
212
|
+
ts_references(file: "<file>", symbol: "<exported_symbol>")
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
...for each export. Exports with 0-1 references (only the export itself) are dead exports. This is tedious for large files, so prioritize:
|
|
216
|
+
- Files that feel overstuffed (15+ exports)
|
|
217
|
+
- Barrel/index files (which may re-export symbols nothing actually uses)
|
|
218
|
+
- Files in directories flagged as potentially stale
|
|
219
|
+
|
|
220
|
+
### 4c. Barrel file audit
|
|
221
|
+
|
|
222
|
+
Barrel files (`index.ts` that re-export from submodules) often accumulate dead re-exports. Run `ts_module_exports` on each barrel, then spot-check with `ts_references` on exports that seem obscure or oddly named.
|
|
223
|
+
|
|
224
|
+
**Checkpoint:** You have a list of confirmed dead files and dead exports that can be safely removed.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Phase 5: Domain Topology
|
|
229
|
+
|
|
230
|
+
**Goal:** Understand the business domain from the code structure.
|
|
231
|
+
|
|
232
|
+
### 5a. Entity identification from schema/type files
|
|
233
|
+
|
|
234
|
+
Find schema or type definition files:
|
|
235
|
+
```
|
|
236
|
+
Glob: **/schemas/*.ts, **/types/*.ts, **/models/*.ts, **/domain/*.ts
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Run `ts_module_exports` on each. The exported type/interface names *are* the domain vocabulary: `User`, `Tenant`, `Todo`, `Invoice`, `Subscription`, etc.
|
|
240
|
+
|
|
241
|
+
### 5b. Entity relationship mapping via dependency_tree
|
|
242
|
+
|
|
243
|
+
For each domain entity's primary service file, run:
|
|
244
|
+
```
|
|
245
|
+
ts_dependency_tree(file: "<entity_service>", depth: 1)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The direct dependencies reveal domain relationships:
|
|
249
|
+
- `TodoShareService` depends on `AddressService` and `ClaimTokenService` → sharing requires addresses and tokens
|
|
250
|
+
- `BillingService` depends on `CoreApiClient` and `StripeCheckoutClient` → billing bridges internal data with an external API
|
|
251
|
+
- `NotificationService` depends on `EmailProvider` and `SmsProvider` → notifications are multi-channel
|
|
252
|
+
|
|
253
|
+
Draw a mental graph: entities are nodes, service-to-service imports are edges. This is the domain topology.
|
|
254
|
+
|
|
255
|
+
### 5c. Domain boundary verification
|
|
256
|
+
|
|
257
|
+
For domain areas that seem like they should be independent (e.g., billing vs notifications, auth vs todo), run:
|
|
258
|
+
```
|
|
259
|
+
ts_shortest_path(from: "<domain_a_service>", to: "<domain_b_service>")
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Null = truly independent domains. A path = one domain depends on the other (and the path shows exactly how).
|
|
263
|
+
|
|
264
|
+
### 5d. Entity access patterns via blast_radius
|
|
265
|
+
|
|
266
|
+
For the central domain entity (usually a `User`, `Account`, or core API client), run:
|
|
267
|
+
```
|
|
268
|
+
ts_blast_radius(file: "<entity_file>", symbol: "<EntityName>")
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The caller list, grouped by file, shows which parts of the system access the entity and how. Middleware files accessing it = access control. Handler files = API surface. Service files = business logic. This reveals the entity's role in the system better than reading its definition.
|
|
272
|
+
|
|
273
|
+
**Checkpoint:** You understand the domain model, entity relationships, and which domains are coupled vs independent — derived entirely from code structure.
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Phase 6: Runtime Behavior Approximation
|
|
278
|
+
|
|
279
|
+
**Goal:** Trace likely execution paths from external inputs to internal effects.
|
|
280
|
+
|
|
281
|
+
### 6a. Request flow tracing
|
|
282
|
+
|
|
283
|
+
For each RPC/HTTP handler or API route, run:
|
|
284
|
+
```
|
|
285
|
+
ts_trace_chain(file: "<handler_file>", symbol: "<HandlerOrRouteGroup>")
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
This follows go-to-definition hops from the handler to its dependencies, building an approximation of the call chain. Each hop = one layer of indirection.
|
|
289
|
+
|
|
290
|
+
Shallow chains (1-2 hops) = thin handlers that delegate directly to services.
|
|
291
|
+
Deep chains (4-5 hops) = layered architecture with middleware, decorators, or adapters.
|
|
292
|
+
|
|
293
|
+
### 6b. Layer composition analysis
|
|
294
|
+
|
|
295
|
+
For the main entry point / composition root (typically the file with the most Phase 1 dependencies), run:
|
|
296
|
+
```
|
|
297
|
+
ts_module_exports(file: "<composition_root>")
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Then for each exported Layer or provider, use `ts_trace_chain` to see what it wires together. In DI-heavy codebases, the Layer/provider composition *is* the runtime wiring — following the chain tells you exactly what services are live.
|
|
301
|
+
|
|
302
|
+
### 6c. Service implementation mapping
|
|
303
|
+
|
|
304
|
+
For every service *interface* file, use `ts_navigate_to` to find its implementations:
|
|
305
|
+
```
|
|
306
|
+
ts_navigate_to(symbol: "<ServiceName>Live")
|
|
307
|
+
ts_navigate_to(symbol: "<ServiceName>Test")
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
This reveals:
|
|
311
|
+
- How many implementations exist per interface (1 = standard, 2+ = strategy pattern or platform-specific)
|
|
312
|
+
- Which services have test doubles (intentionally testable) vs which don't (may rely on integration tests)
|
|
313
|
+
- Where implementations live relative to interfaces (same package = co-located, different app = platform-specific)
|
|
314
|
+
|
|
315
|
+
### 6d. Queue/event handler discovery
|
|
316
|
+
|
|
317
|
+
For async or event-driven systems, find queue consumers, event handlers, or scheduled jobs:
|
|
318
|
+
```
|
|
319
|
+
Glob: **/jobs/*.ts, **/handlers/*.ts, **/consumers/*.ts, **/workers/*.ts
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Run `ts_dependency_tree(depth: 1)` on each. Their dependencies reveal what domain logic is triggered asynchronously vs synchronously. Services that appear in both HTTP handler trees AND job handler trees participate in both synchronous and async paths.
|
|
323
|
+
|
|
324
|
+
### 6e. Feature flag / conditional path detection
|
|
325
|
+
|
|
326
|
+
This is the hardest to detect statically. Use `Grep` for common patterns:
|
|
327
|
+
```
|
|
328
|
+
Grep: "feature", "flag", "toggle", "experiment", "enabled", "FF_", "FEATURE_"
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Then for any files found, use `ts_dependents` to see how far the conditional reaches. A feature flag in a leaf file affects one path. A feature flag in a service depended on by 15 files affects many paths.
|
|
332
|
+
|
|
333
|
+
**Checkpoint:** You have traced the likely runtime paths from external inputs through handlers to services to data, identified async vs sync processing, and flagged feature-gated code.
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Phase 7: Risk Assessment
|
|
338
|
+
|
|
339
|
+
**Goal:** Before making any changes, know where the risk concentrates.
|
|
340
|
+
|
|
341
|
+
### 7a. Blast radius ranking
|
|
342
|
+
|
|
343
|
+
For the top 5-10 most-referenced symbols discovered in earlier phases, run:
|
|
344
|
+
```
|
|
345
|
+
ts_blast_radius(file: "<file>", symbol: "<symbol>")
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Sort by `filesAffected`. The top 5 are your highest-risk changes. Plan these carefully, test extensively, and consider backwards-compatible migration strategies.
|
|
349
|
+
|
|
350
|
+
### 7b. Dependency inversion check
|
|
351
|
+
|
|
352
|
+
For high-risk symbols, check if the coupling goes through an abstraction:
|
|
353
|
+
```
|
|
354
|
+
ts_dependents(file: "<interface_file>", depth: 1)
|
|
355
|
+
ts_dependents(file: "<implementation_file>", depth: 1)
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
If dependents point to the *interface* file (not the implementation), the system is properly inverted — you can change implementations without affecting consumers. If dependents point to the *implementation* directly, changing it will break callers.
|
|
359
|
+
|
|
360
|
+
### 7c. Change propagation preview
|
|
361
|
+
|
|
362
|
+
For a planned change, combine tools:
|
|
363
|
+
1. `ts_blast_radius` — who references this symbol?
|
|
364
|
+
2. `ts_dependents` on the file — who imports this file?
|
|
365
|
+
3. `ts_module_boundary` on the affected directory — how does this change propagate to the module boundary?
|
|
366
|
+
|
|
367
|
+
This three-tool sequence gives you a complete picture: direct references (blast_radius), file-level impact (dependents), and module-level impact (boundary).
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Report Format
|
|
372
|
+
|
|
373
|
+
Write the report to `<project_root>/typegraph-exploration-report.md` using this structure:
|
|
374
|
+
|
|
375
|
+
```markdown
|
|
376
|
+
# Codebase Exploration Report
|
|
377
|
+
> Generated: <date>
|
|
378
|
+
> Project: <project_root>
|
|
379
|
+
> Files: <count> | Edges: <count> | Density: <ratio>
|
|
380
|
+
|
|
381
|
+
## Executive Summary
|
|
382
|
+
<!-- 3-5 bullet points: the most important findings across all phases -->
|
|
383
|
+
|
|
384
|
+
## Phase 1–7
|
|
385
|
+
<!-- One section per phase with subsections as listed above -->
|
|
386
|
+
|
|
387
|
+
## Appendix: Raw Data
|
|
388
|
+
<!-- Full tool outputs under <details> tags -->
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Guidelines:
|
|
392
|
+
- Include actual numbers, file paths, and tool outputs — not vague summaries
|
|
393
|
+
- Use tables for structured data (blast radius rankings, module boundaries, etc.)
|
|
394
|
+
- Add a "Notable Findings" subsection after any checkpoint where something unexpected appeared
|
|
395
|
+
- Put verbose raw tool outputs in the Appendix under `<details>` tags to keep the main report scannable
|
|
396
|
+
- Write the Executive Summary last, after all phases complete
|
|
397
|
+
|
|
398
|
+
## Quick Reference
|
|
399
|
+
|
|
400
|
+
| Phase | Tools | Answers |
|
|
401
|
+
|-------|-------|---------|
|
|
402
|
+
| 1. Skeleton | `dependency_tree`, `import_cycles`, `shortest_path` | Architecture shape, boundaries, tangles |
|
|
403
|
+
| 2. Anatomy | `dependents`, `module_exports`, `module_boundary` | What modules provide, how they connect |
|
|
404
|
+
| 3. Patterns | `module_exports` (comparative), `navigate_to` | Intentional vs accidental conventions |
|
|
405
|
+
| 4. Dead Code | `dependents` (0 check), `references` (per export) | Orphan files, dead exports |
|
|
406
|
+
| 5. Domain | `dependency_tree`, `shortest_path`, `blast_radius` | Entity relationships, domain topology |
|
|
407
|
+
| 6. Runtime | `trace_chain`, `navigate_to`, `dependency_tree` | Execution paths, wiring, async flows |
|
|
408
|
+
| 7. Risk | `blast_radius`, `dependents`, `module_boundary` | Change impact, coupling direction |
|