typegraph-mcp 0.9.3 → 0.9.5

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typegraph",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "Type-aware TypeScript navigation — 14 MCP tools for go-to-definition, find-references, dependency graphs, cycle detection, and impact analysis",
5
5
  "author": {
6
6
  "name": "Owen Jones"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typegraph",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "Type-aware TypeScript navigation — 14 MCP tools for go-to-definition, find-references, dependency graphs, cycle detection, and impact analysis",
5
5
  "author": {
6
6
  "name": "Owen Jones"
package/check.ts CHANGED
@@ -335,7 +335,13 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
335
335
 
336
336
  // 10. Module graph build test
337
337
  try {
338
- const { buildGraph } = await import(path.resolve(toolDir, "module-graph.js"));
338
+ let buildGraph: (root: string, tsconfig: string) => Promise<{ graph: { files: Set<string>; forward: Map<string, unknown[]> } }>;
339
+ try {
340
+ ({ buildGraph } = await import(path.resolve(toolDir, "module-graph.js")));
341
+ } catch {
342
+ // Fallback: plugin dir has .ts files only (no tsx at runtime), use the co-bundled version
343
+ ({ buildGraph } = await import("./module-graph.js"));
344
+ }
339
345
  const start = performance.now();
340
346
  const { graph } = await buildGraph(projectRoot, tsconfigPath);
341
347
  const elapsed = (performance.now() - start).toFixed(0);
@@ -443,17 +449,3 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
443
449
  return { passed, failed, warned };
444
450
  }
445
451
 
446
- // ─── Self-run guard ──────────────────────────────────────────────────────────
447
-
448
- const isDirectRun =
449
- process.argv[1] &&
450
- fs.realpathSync(process.argv[1]) === fs.realpathSync(new URL(import.meta.url).pathname);
451
-
452
- if (isDirectRun) {
453
- main()
454
- .then((result) => process.exit(result.failed > 0 ? 1 : 0))
455
- .catch((err) => {
456
- console.error("Fatal error:", err);
457
- process.exit(1);
458
- });
459
- }
package/cli.ts CHANGED
@@ -709,17 +709,29 @@ async function remove(yes: boolean): Promise<void> {
709
709
 
710
710
  // ─── Check Command ───────────────────────────────────────────────────────────
711
711
 
712
+ function resolvePluginDir(): string {
713
+ // Prefer the installed plugin in the user's project over the npx cache
714
+ const installed = path.resolve(process.cwd(), PLUGIN_DIR_NAME);
715
+ if (fs.existsSync(installed)) return installed;
716
+ // Fall back to the source directory (running from the repo itself)
717
+ return path.basename(import.meta.dirname) === "dist"
718
+ ? path.resolve(import.meta.dirname, "..")
719
+ : import.meta.dirname;
720
+ }
721
+
712
722
  async function check(): Promise<void> {
723
+ const config = resolveConfig(resolvePluginDir());
713
724
  const { main: checkMain } = await import("./check.js");
714
- const result = await checkMain();
725
+ const result = await checkMain(config);
715
726
  process.exit(result.failed > 0 ? 1 : 0);
716
727
  }
717
728
 
718
729
  // ─── Test Command ────────────────────────────────────────────────────────────
719
730
 
720
731
  async function test(): Promise<void> {
732
+ const config = resolveConfig(resolvePluginDir());
721
733
  const { main: testMain } = await import("./smoke-test.js");
722
- const result = await testMain();
734
+ const result = await testMain(config);
723
735
  process.exit(result.failed > 0 ? 1 : 0);
724
736
  }
725
737
 
package/dist/check.js CHANGED
@@ -1,7 +1,359 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
1
10
 
2
- // check.ts
11
+ // module-graph.ts
12
+ var module_graph_exports = {};
13
+ __export(module_graph_exports, {
14
+ buildGraph: () => buildGraph,
15
+ createResolver: () => createResolver,
16
+ discoverFiles: () => discoverFiles,
17
+ removeFile: () => removeFile,
18
+ startWatcher: () => startWatcher,
19
+ updateFile: () => updateFile
20
+ });
21
+ import { parseSync } from "oxc-parser";
22
+ import { ResolverFactory } from "oxc-resolver";
3
23
  import * as fs from "fs";
4
24
  import * as path2 from "path";
25
+ function discoverFiles(rootDir) {
26
+ const files = [];
27
+ function walk(dir) {
28
+ let entries;
29
+ try {
30
+ entries = fs.readdirSync(dir, { withFileTypes: true });
31
+ } catch {
32
+ return;
33
+ }
34
+ for (const entry of entries) {
35
+ if (entry.isDirectory()) {
36
+ if (SKIP_DIRS.has(entry.name)) continue;
37
+ if (entry.name.startsWith(".") && dir !== rootDir) continue;
38
+ walk(path2.join(dir, entry.name));
39
+ } else if (entry.isFile()) {
40
+ const name = entry.name;
41
+ if (SKIP_FILES.has(name)) continue;
42
+ if (name.endsWith(".d.ts") || name.endsWith(".d.mts") || name.endsWith(".d.cts")) continue;
43
+ const ext = path2.extname(name);
44
+ if (TS_EXTENSIONS.has(ext)) {
45
+ files.push(path2.join(dir, name));
46
+ }
47
+ }
48
+ }
49
+ }
50
+ walk(rootDir);
51
+ return files;
52
+ }
53
+ function parseFileImports(filePath, source) {
54
+ const result = parseSync(filePath, source);
55
+ const imports = [];
56
+ for (const imp of result.module.staticImports) {
57
+ const specifier = imp.moduleRequest.value;
58
+ const names = [];
59
+ let allTypeOnly = true;
60
+ for (const entry of imp.entries) {
61
+ const kind = entry.importName.kind;
62
+ const name = kind === "Default" ? "default" : kind === "All" || kind === "AllButDefault" || kind === "NamespaceObject" ? "*" : entry.importName.name ?? entry.localName.value;
63
+ names.push(name);
64
+ if (!entry.isType) allTypeOnly = false;
65
+ }
66
+ if (names.length === 0) {
67
+ imports.push({ specifier, names: ["*"], isTypeOnly: false, isDynamic: false });
68
+ } else {
69
+ imports.push({ specifier, names, isTypeOnly: allTypeOnly, isDynamic: false });
70
+ }
71
+ }
72
+ for (const exp of result.module.staticExports) {
73
+ for (const entry of exp.entries) {
74
+ const moduleRequest = entry.moduleRequest;
75
+ if (!moduleRequest) continue;
76
+ const specifier = moduleRequest.value;
77
+ const entryKind = entry.importName.kind;
78
+ const name = entryKind === "AllButDefault" || entryKind === "All" || entryKind === "NamespaceObject" ? "*" : entry.importName.name ?? "*";
79
+ const existing = imports.find((i) => i.specifier === specifier && !i.isDynamic);
80
+ if (existing) {
81
+ if (!existing.names.includes(name)) existing.names.push(name);
82
+ } else {
83
+ imports.push({ specifier, names: [name], isTypeOnly: false, isDynamic: false });
84
+ }
85
+ }
86
+ }
87
+ for (const di of result.module.dynamicImports) {
88
+ if (di.moduleRequest) {
89
+ const sliced = source.slice(di.moduleRequest.start, di.moduleRequest.end);
90
+ if (sliced.startsWith("'") || sliced.startsWith('"')) {
91
+ const specifier = sliced.slice(1, -1);
92
+ imports.push({ specifier, names: ["*"], isTypeOnly: false, isDynamic: true });
93
+ }
94
+ }
95
+ }
96
+ return imports;
97
+ }
98
+ function distToSource(resolvedPath, projectRoot) {
99
+ if (!resolvedPath.startsWith(projectRoot)) return resolvedPath;
100
+ const rel = path2.relative(projectRoot, resolvedPath);
101
+ const distIdx = rel.indexOf("dist" + path2.sep);
102
+ if (distIdx === -1) return resolvedPath;
103
+ const prefix = rel.slice(0, distIdx);
104
+ const afterDist = rel.slice(distIdx + 5);
105
+ const withoutExt = afterDist.replace(/\.(m?j|c)s$/, "");
106
+ for (const ext of SOURCE_EXTS) {
107
+ const candidate = path2.resolve(projectRoot, prefix, "src", withoutExt + ext);
108
+ if (fs.existsSync(candidate)) return candidate;
109
+ }
110
+ for (const ext of SOURCE_EXTS) {
111
+ const candidate = path2.resolve(projectRoot, prefix, withoutExt + ext);
112
+ if (fs.existsSync(candidate)) return candidate;
113
+ }
114
+ if (withoutExt.endsWith("/index")) {
115
+ const dirPath = withoutExt.slice(0, -6);
116
+ for (const ext of SOURCE_EXTS) {
117
+ const candidate = path2.resolve(projectRoot, prefix, "src", dirPath + ext);
118
+ if (fs.existsSync(candidate)) return candidate;
119
+ }
120
+ }
121
+ return resolvedPath;
122
+ }
123
+ function resolveImport(resolver, fromDir, specifier, projectRoot) {
124
+ try {
125
+ const result = resolver.sync(fromDir, specifier);
126
+ if (result.path && !result.path.includes("node_modules")) {
127
+ const mapped = distToSource(result.path, projectRoot);
128
+ const ext = path2.extname(mapped);
129
+ if (!TS_EXTENSIONS.has(ext)) return null;
130
+ if (SKIP_FILES.has(path2.basename(mapped))) return null;
131
+ return mapped;
132
+ }
133
+ } catch {
134
+ }
135
+ return null;
136
+ }
137
+ function createResolver(projectRoot, tsconfigPath) {
138
+ return new ResolverFactory({
139
+ tsconfig: {
140
+ configFile: path2.resolve(projectRoot, tsconfigPath),
141
+ references: "auto"
142
+ },
143
+ extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
144
+ extensionAlias: {
145
+ ".js": [".ts", ".tsx", ".js"],
146
+ ".jsx": [".tsx", ".jsx"],
147
+ ".mjs": [".mts", ".mjs"],
148
+ ".cjs": [".cts", ".cjs"]
149
+ },
150
+ conditionNames: ["import", "require"],
151
+ mainFields: ["module", "main"]
152
+ });
153
+ }
154
+ function buildForwardEdges(files, resolver, projectRoot) {
155
+ const forward = /* @__PURE__ */ new Map();
156
+ const parseFailures = [];
157
+ for (const filePath of files) {
158
+ let source;
159
+ try {
160
+ source = fs.readFileSync(filePath, "utf-8");
161
+ } catch {
162
+ continue;
163
+ }
164
+ let rawImports;
165
+ try {
166
+ rawImports = parseFileImports(filePath, source);
167
+ } catch (err) {
168
+ parseFailures.push(filePath);
169
+ continue;
170
+ }
171
+ const edges = [];
172
+ const fromDir = path2.dirname(filePath);
173
+ for (const raw of rawImports) {
174
+ const target = resolveImport(resolver, fromDir, raw.specifier, projectRoot);
175
+ if (target) {
176
+ edges.push({
177
+ target,
178
+ specifiers: raw.names,
179
+ isTypeOnly: raw.isTypeOnly,
180
+ isDynamic: raw.isDynamic
181
+ });
182
+ }
183
+ }
184
+ forward.set(filePath, edges);
185
+ }
186
+ return { forward, parseFailures };
187
+ }
188
+ function buildReverseMap(forward) {
189
+ const reverse = /* @__PURE__ */ new Map();
190
+ for (const [source, edges] of forward) {
191
+ for (const edge of edges) {
192
+ let revEdges = reverse.get(edge.target);
193
+ if (!revEdges) {
194
+ revEdges = [];
195
+ reverse.set(edge.target, revEdges);
196
+ }
197
+ revEdges.push({
198
+ target: source,
199
+ // reverse: the "target" is the file that imports
200
+ specifiers: edge.specifiers,
201
+ isTypeOnly: edge.isTypeOnly,
202
+ isDynamic: edge.isDynamic
203
+ });
204
+ }
205
+ }
206
+ return reverse;
207
+ }
208
+ async function buildGraph(projectRoot, tsconfigPath) {
209
+ const startTime = performance.now();
210
+ const resolver = createResolver(projectRoot, tsconfigPath);
211
+ const fileList = discoverFiles(projectRoot);
212
+ log(`Discovered ${fileList.length} source files`);
213
+ const { forward, parseFailures } = buildForwardEdges(fileList, resolver, projectRoot);
214
+ const reverse = buildReverseMap(forward);
215
+ const files = new Set(fileList);
216
+ const edgeCount = [...forward.values()].reduce((sum, edges) => sum + edges.length, 0);
217
+ const elapsed = (performance.now() - startTime).toFixed(0);
218
+ log(`Graph built: ${files.size} files, ${edgeCount} edges [${elapsed}ms]`);
219
+ if (parseFailures.length > 0) {
220
+ log(`Parse failures: ${parseFailures.length} files`);
221
+ }
222
+ return {
223
+ graph: { forward, reverse, files },
224
+ resolver
225
+ };
226
+ }
227
+ function updateFile(graph, filePath, resolver, projectRoot) {
228
+ const oldEdges = graph.forward.get(filePath) ?? [];
229
+ for (const edge of oldEdges) {
230
+ const revEdges = graph.reverse.get(edge.target);
231
+ if (revEdges) {
232
+ const idx = revEdges.findIndex((r) => r.target === filePath);
233
+ if (idx !== -1) revEdges.splice(idx, 1);
234
+ if (revEdges.length === 0) graph.reverse.delete(edge.target);
235
+ }
236
+ }
237
+ let source;
238
+ try {
239
+ source = fs.readFileSync(filePath, "utf-8");
240
+ } catch {
241
+ removeFile(graph, filePath);
242
+ return;
243
+ }
244
+ let rawImports;
245
+ try {
246
+ rawImports = parseFileImports(filePath, source);
247
+ } catch {
248
+ log(`Parse error on update: ${filePath}`);
249
+ graph.forward.set(filePath, []);
250
+ return;
251
+ }
252
+ const fromDir = path2.dirname(filePath);
253
+ const newEdges = [];
254
+ for (const raw of rawImports) {
255
+ const target = resolveImport(resolver, fromDir, raw.specifier, projectRoot);
256
+ if (target) {
257
+ newEdges.push({
258
+ target,
259
+ specifiers: raw.names,
260
+ isTypeOnly: raw.isTypeOnly,
261
+ isDynamic: raw.isDynamic
262
+ });
263
+ }
264
+ }
265
+ graph.forward.set(filePath, newEdges);
266
+ graph.files.add(filePath);
267
+ for (const edge of newEdges) {
268
+ let revEdges = graph.reverse.get(edge.target);
269
+ if (!revEdges) {
270
+ revEdges = [];
271
+ graph.reverse.set(edge.target, revEdges);
272
+ }
273
+ revEdges.push({
274
+ target: filePath,
275
+ specifiers: edge.specifiers,
276
+ isTypeOnly: edge.isTypeOnly,
277
+ isDynamic: edge.isDynamic
278
+ });
279
+ }
280
+ }
281
+ function removeFile(graph, filePath) {
282
+ const edges = graph.forward.get(filePath) ?? [];
283
+ for (const edge of edges) {
284
+ const revEdges2 = graph.reverse.get(edge.target);
285
+ if (revEdges2) {
286
+ const idx = revEdges2.findIndex((r) => r.target === filePath);
287
+ if (idx !== -1) revEdges2.splice(idx, 1);
288
+ if (revEdges2.length === 0) graph.reverse.delete(edge.target);
289
+ }
290
+ }
291
+ const revEdges = graph.reverse.get(filePath) ?? [];
292
+ for (const revEdge of revEdges) {
293
+ const fwdEdges = graph.forward.get(revEdge.target);
294
+ if (fwdEdges) {
295
+ const idx = fwdEdges.findIndex((e) => e.target === filePath);
296
+ if (idx !== -1) fwdEdges.splice(idx, 1);
297
+ }
298
+ }
299
+ graph.forward.delete(filePath);
300
+ graph.reverse.delete(filePath);
301
+ graph.files.delete(filePath);
302
+ }
303
+ function startWatcher(projectRoot, graph, resolver) {
304
+ try {
305
+ const watcher = fs.watch(
306
+ projectRoot,
307
+ { recursive: true },
308
+ (_eventType, filename) => {
309
+ if (!filename) return;
310
+ const ext = path2.extname(filename);
311
+ if (!TS_EXTENSIONS.has(ext)) return;
312
+ const parts = filename.split(path2.sep);
313
+ if (parts.some((p) => SKIP_DIRS.has(p))) return;
314
+ if (SKIP_FILES.has(path2.basename(filename))) return;
315
+ if (filename.endsWith(".d.ts") || filename.endsWith(".d.mts") || filename.endsWith(".d.cts"))
316
+ return;
317
+ const absPath = path2.resolve(projectRoot, filename);
318
+ if (fs.existsSync(absPath)) {
319
+ updateFile(graph, absPath, resolver, projectRoot);
320
+ } else {
321
+ removeFile(graph, absPath);
322
+ }
323
+ }
324
+ );
325
+ process.on("SIGINT", () => watcher.close());
326
+ process.on("SIGTERM", () => watcher.close());
327
+ log("File watcher started");
328
+ } catch (err) {
329
+ log("Failed to start file watcher:", err);
330
+ }
331
+ }
332
+ var log, TS_EXTENSIONS, SKIP_DIRS, SKIP_FILES, SOURCE_EXTS;
333
+ var init_module_graph = __esm({
334
+ "module-graph.ts"() {
335
+ log = (...args) => console.error("[typegraph/graph]", ...args);
336
+ TS_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts"]);
337
+ SKIP_DIRS = /* @__PURE__ */ new Set([
338
+ "node_modules",
339
+ "dist",
340
+ "build",
341
+ "out",
342
+ ".wrangler",
343
+ ".mf",
344
+ ".git",
345
+ ".next",
346
+ ".turbo",
347
+ "coverage"
348
+ ]);
349
+ SKIP_FILES = /* @__PURE__ */ new Set(["routeTree.gen.ts"]);
350
+ SOURCE_EXTS = [".ts", ".tsx", ".mts", ".cts"];
351
+ }
352
+ });
353
+
354
+ // check.ts
355
+ import * as fs2 from "fs";
356
+ import * as path3 from "path";
5
357
  import { createRequire } from "module";
6
358
  import { spawn } from "child_process";
7
359
 
@@ -20,14 +372,14 @@ function resolveConfig(toolDir) {
20
372
  function findFirstTsFile(dir) {
21
373
  const skipDirs = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", ".wrangler", "coverage"]);
22
374
  try {
23
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
375
+ for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
24
376
  if (entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) {
25
- return path2.join(dir, entry.name);
377
+ return path3.join(dir, entry.name);
26
378
  }
27
379
  }
28
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
380
+ for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
29
381
  if (entry.isDirectory() && !skipDirs.has(entry.name) && !entry.name.startsWith(".")) {
30
- const found = findFirstTsFile(path2.join(dir, entry.name));
382
+ const found = findFirstTsFile(path3.join(dir, entry.name));
31
383
  if (found) return found;
32
384
  }
33
385
  }
@@ -36,13 +388,13 @@ function findFirstTsFile(dir) {
36
388
  return null;
37
389
  }
38
390
  function testTsserver(projectRoot) {
39
- return new Promise((resolve3) => {
391
+ return new Promise((resolve4) => {
40
392
  let tsserverPath;
41
393
  try {
42
- const require2 = createRequire(path2.resolve(projectRoot, "package.json"));
394
+ const require2 = createRequire(path3.resolve(projectRoot, "package.json"));
43
395
  tsserverPath = require2.resolve("typescript/lib/tsserver.js");
44
396
  } catch {
45
- resolve3(false);
397
+ resolve4(false);
46
398
  return;
47
399
  }
48
400
  const child = spawn("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
@@ -51,7 +403,7 @@ function testTsserver(projectRoot) {
51
403
  });
52
404
  const timeout = setTimeout(() => {
53
405
  child.kill();
54
- resolve3(false);
406
+ resolve4(false);
55
407
  }, 1e4);
56
408
  let buffer = "";
57
409
  child.stdout.on("data", (chunk) => {
@@ -59,12 +411,12 @@ function testTsserver(projectRoot) {
59
411
  if (buffer.includes('"success":true')) {
60
412
  clearTimeout(timeout);
61
413
  child.kill();
62
- resolve3(true);
414
+ resolve4(true);
63
415
  }
64
416
  });
65
417
  child.on("error", () => {
66
418
  clearTimeout(timeout);
67
- resolve3(false);
419
+ resolve4(false);
68
420
  });
69
421
  child.on("exit", () => {
70
422
  clearTimeout(timeout);
@@ -114,8 +466,8 @@ async function main(configOverride) {
114
466
  } else {
115
467
  fail(`Node.js ${nodeVersion} is too old`, "Upgrade Node.js to >= 18");
116
468
  }
117
- const tsxInRoot = fs.existsSync(path2.join(projectRoot, "node_modules/.bin/tsx"));
118
- const tsxInTool = fs.existsSync(path2.join(toolDir, "node_modules/.bin/tsx"));
469
+ const tsxInRoot = fs2.existsSync(path3.join(projectRoot, "node_modules/.bin/tsx"));
470
+ const tsxInTool = fs2.existsSync(path3.join(toolDir, "node_modules/.bin/tsx"));
119
471
  if (tsxInRoot || tsxInTool) {
120
472
  pass(`tsx available (in ${tsxInRoot ? "project" : "tool"} node_modules)`);
121
473
  } else {
@@ -123,10 +475,10 @@ async function main(configOverride) {
123
475
  }
124
476
  let tsVersion = null;
125
477
  try {
126
- const require2 = createRequire(path2.resolve(projectRoot, "package.json"));
478
+ const require2 = createRequire(path3.resolve(projectRoot, "package.json"));
127
479
  const tsserverPath = require2.resolve("typescript/lib/tsserver.js");
128
- const tsPkgPath = path2.resolve(path2.dirname(tsserverPath), "..", "package.json");
129
- const tsPkg = JSON.parse(fs.readFileSync(tsPkgPath, "utf-8"));
480
+ const tsPkgPath = path3.resolve(path3.dirname(tsserverPath), "..", "package.json");
481
+ const tsPkg = JSON.parse(fs2.readFileSync(tsPkgPath, "utf-8"));
130
482
  tsVersion = tsPkg.version;
131
483
  pass(`TypeScript found (v${tsVersion})`);
132
484
  } catch {
@@ -135,23 +487,23 @@ async function main(configOverride) {
135
487
  "Add `typescript` to devDependencies and run `npm install`"
136
488
  );
137
489
  }
138
- const tsconfigAbs = path2.resolve(projectRoot, tsconfigPath);
139
- if (fs.existsSync(tsconfigAbs)) {
490
+ const tsconfigAbs = path3.resolve(projectRoot, tsconfigPath);
491
+ if (fs2.existsSync(tsconfigAbs)) {
140
492
  pass(`tsconfig.json exists at ${tsconfigPath}`);
141
493
  } else {
142
494
  fail(`tsconfig.json not found at ${tsconfigPath}`, `Create a tsconfig.json at ${tsconfigPath}`);
143
495
  }
144
- const pluginMcpPath = path2.join(toolDir, ".mcp.json");
145
- const hasPluginMcp = fs.existsSync(pluginMcpPath) && fs.existsSync(path2.join(toolDir, ".claude-plugin/plugin.json"));
496
+ const pluginMcpPath = path3.join(toolDir, ".mcp.json");
497
+ const hasPluginMcp = fs2.existsSync(pluginMcpPath) && fs2.existsSync(path3.join(toolDir, ".claude-plugin/plugin.json"));
146
498
  if (process.env.CLAUDE_PLUGIN_ROOT) {
147
499
  pass("MCP registered via plugin (CLAUDE_PLUGIN_ROOT set)");
148
500
  } else if (hasPluginMcp) {
149
501
  pass("MCP registered via plugin (.mcp.json + .claude-plugin/ present)");
150
502
  } else {
151
- const mcpJsonPath = path2.resolve(projectRoot, ".claude/mcp.json");
152
- if (fs.existsSync(mcpJsonPath)) {
503
+ const mcpJsonPath = path3.resolve(projectRoot, ".claude/mcp.json");
504
+ if (fs2.existsSync(mcpJsonPath)) {
153
505
  try {
154
- const mcpJson = JSON.parse(fs.readFileSync(mcpJsonPath, "utf-8"));
506
+ const mcpJson = JSON.parse(fs2.readFileSync(mcpJsonPath, "utf-8"));
155
507
  const tsNav = mcpJson?.mcpServers?.["typegraph"];
156
508
  if (tsNav) {
157
509
  const hasCommand = tsNav.command === "npx";
@@ -170,7 +522,7 @@ async function main(configOverride) {
170
522
  );
171
523
  }
172
524
  } else {
173
- const serverPath = toolIsEmbedded ? `./${toolRelPath}/server.ts` : path2.resolve(toolDir, "server.ts");
525
+ const serverPath = toolIsEmbedded ? `./${toolRelPath}/server.ts` : path3.resolve(toolDir, "server.ts");
174
526
  fail(
175
527
  "MCP entry 'typegraph' not found in .claude/mcp.json",
176
528
  `Add to .claude/mcp.json:
@@ -195,11 +547,11 @@ async function main(configOverride) {
195
547
  fail(".claude/mcp.json not found", `Create .claude/mcp.json with typegraph server registration`);
196
548
  }
197
549
  }
198
- const toolNodeModules = path2.join(toolDir, "node_modules");
199
- if (fs.existsSync(toolNodeModules)) {
550
+ const toolNodeModules = path3.join(toolDir, "node_modules");
551
+ if (fs2.existsSync(toolNodeModules)) {
200
552
  const requiredPkgs = ["@modelcontextprotocol/sdk", "oxc-parser", "oxc-resolver", "zod"];
201
553
  const missing = requiredPkgs.filter(
202
- (pkg) => !fs.existsSync(path2.join(toolNodeModules, ...pkg.split("/")))
554
+ (pkg) => !fs2.existsSync(path3.join(toolNodeModules, ...pkg.split("/")))
203
555
  );
204
556
  if (missing.length === 0) {
205
557
  pass(`Dependencies installed (${requiredPkgs.length} packages)`);
@@ -210,9 +562,9 @@ async function main(configOverride) {
210
562
  fail("typegraph-mcp dependencies not installed", `Run \`cd ${toolRelPath} && npm install\``);
211
563
  }
212
564
  try {
213
- const oxcParserReq = createRequire(path2.join(toolDir, "package.json"));
214
- const { parseSync } = await import(oxcParserReq.resolve("oxc-parser"));
215
- const result = parseSync("test.ts", 'import { x } from "./y";');
565
+ const oxcParserReq = createRequire(path3.join(toolDir, "package.json"));
566
+ const { parseSync: parseSync2 } = await import(oxcParserReq.resolve("oxc-parser"));
567
+ const result = parseSync2("test.ts", 'import { x } from "./y";');
216
568
  if (result.module?.staticImports?.length === 1) {
217
569
  pass("oxc-parser working");
218
570
  } else {
@@ -228,9 +580,9 @@ async function main(configOverride) {
228
580
  );
229
581
  }
230
582
  try {
231
- const oxcResolverReq = createRequire(path2.join(toolDir, "package.json"));
232
- const { ResolverFactory } = await import(oxcResolverReq.resolve("oxc-resolver"));
233
- const resolver = new ResolverFactory({
583
+ const oxcResolverReq = createRequire(path3.join(toolDir, "package.json"));
584
+ const { ResolverFactory: ResolverFactory2 } = await import(oxcResolverReq.resolve("oxc-resolver"));
585
+ const resolver = new ResolverFactory2({
234
586
  tsconfig: { configFile: tsconfigAbs, references: "auto" },
235
587
  extensions: [".ts", ".tsx", ".js"],
236
588
  extensionAlias: { ".js": [".ts", ".tsx", ".js"] }
@@ -238,8 +590,8 @@ async function main(configOverride) {
238
590
  let resolveOk = false;
239
591
  const testFile = findFirstTsFile(projectRoot);
240
592
  if (testFile) {
241
- const dir = path2.dirname(testFile);
242
- const base = "./" + path2.basename(testFile);
593
+ const dir = path3.dirname(testFile);
594
+ const base = "./" + path3.basename(testFile);
243
595
  const result = resolver.sync(dir, base);
244
596
  resolveOk = !!result.path;
245
597
  }
@@ -278,9 +630,14 @@ async function main(configOverride) {
278
630
  skip("tsserver test (TypeScript not found)");
279
631
  }
280
632
  try {
281
- const { buildGraph } = await import(path2.resolve(toolDir, "module-graph.js"));
633
+ let buildGraph2;
634
+ try {
635
+ ({ buildGraph: buildGraph2 } = await import(path3.resolve(toolDir, "module-graph.js")));
636
+ } catch {
637
+ ({ buildGraph: buildGraph2 } = await Promise.resolve().then(() => (init_module_graph(), module_graph_exports)));
638
+ }
282
639
  const start = performance.now();
283
- const { graph } = await buildGraph(projectRoot, tsconfigPath);
640
+ const { graph } = await buildGraph2(projectRoot, tsconfigPath);
284
641
  const elapsed = (performance.now() - start).toFixed(0);
285
642
  const edgeCount = [...graph.forward.values()].reduce(
286
643
  (s, e) => s + e.length,
@@ -306,10 +663,10 @@ async function main(configOverride) {
306
663
  );
307
664
  }
308
665
  if (toolIsEmbedded) {
309
- const eslintConfigPath = path2.resolve(projectRoot, "eslint.config.mjs");
310
- if (fs.existsSync(eslintConfigPath)) {
311
- const eslintContent = fs.readFileSync(eslintConfigPath, "utf-8");
312
- const parentDir = path2.basename(path2.dirname(toolDir));
666
+ const eslintConfigPath = path3.resolve(projectRoot, "eslint.config.mjs");
667
+ if (fs2.existsSync(eslintConfigPath)) {
668
+ const eslintContent = fs2.readFileSync(eslintConfigPath, "utf-8");
669
+ const parentDir = path3.basename(path3.dirname(toolDir));
313
670
  const parentIgnorePattern = new RegExp(`["']${parentDir}\\/\\*\\*["']`);
314
671
  const hasParentIgnore = parentIgnorePattern.test(eslintContent);
315
672
  if (hasParentIgnore) {
@@ -327,14 +684,14 @@ async function main(configOverride) {
327
684
  } else {
328
685
  skip("ESLint config check (typegraph-mcp is external to project)");
329
686
  }
330
- const gitignorePath = path2.resolve(projectRoot, ".gitignore");
331
- if (fs.existsSync(gitignorePath)) {
332
- const gitignoreContent = fs.readFileSync(gitignorePath, "utf-8");
687
+ const gitignorePath = path3.resolve(projectRoot, ".gitignore");
688
+ if (fs2.existsSync(gitignorePath)) {
689
+ const gitignoreContent = fs2.readFileSync(gitignorePath, "utf-8");
333
690
  const lines = gitignoreContent.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
334
691
  const ignoresClaude = lines.some(
335
692
  (l) => l === ".claude/" || l === ".claude" || l === "/.claude"
336
693
  );
337
- const parentDir = toolIsEmbedded ? path2.basename(path2.dirname(toolDir)) : null;
694
+ const parentDir = toolIsEmbedded ? path3.basename(path3.dirname(toolDir)) : null;
338
695
  const ignoresParent = parentDir && lines.some((l) => l === `${parentDir}/` || l === parentDir || l === `/${parentDir}`);
339
696
  if (!ignoresParent && !ignoresClaude) {
340
697
  pass(".gitignore does not exclude .claude/" + (parentDir ? ` or ${parentDir}/` : ""));
@@ -364,13 +721,6 @@ async function main(configOverride) {
364
721
  console.log("");
365
722
  return { passed, failed, warned };
366
723
  }
367
- var isDirectRun = process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(new URL(import.meta.url).pathname);
368
- if (isDirectRun) {
369
- main().then((result) => process.exit(result.failed > 0 ? 1 : 0)).catch((err) => {
370
- console.error("Fatal error:", err);
371
- process.exit(1);
372
- });
373
- }
374
724
  export {
375
725
  main
376
726
  };