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.
package/dist/cli.js CHANGED
@@ -24,26 +24,369 @@ var init_config = __esm({
24
24
  }
25
25
  });
26
26
 
27
+ // module-graph.ts
28
+ var module_graph_exports = {};
29
+ __export(module_graph_exports, {
30
+ buildGraph: () => buildGraph,
31
+ createResolver: () => createResolver,
32
+ discoverFiles: () => discoverFiles,
33
+ removeFile: () => removeFile,
34
+ startWatcher: () => startWatcher,
35
+ updateFile: () => updateFile
36
+ });
37
+ import { parseSync } from "oxc-parser";
38
+ import { ResolverFactory } from "oxc-resolver";
39
+ import * as fs from "fs";
40
+ import * as path2 from "path";
41
+ function discoverFiles(rootDir) {
42
+ const files = [];
43
+ function walk(dir) {
44
+ let entries;
45
+ try {
46
+ entries = fs.readdirSync(dir, { withFileTypes: true });
47
+ } catch {
48
+ return;
49
+ }
50
+ for (const entry of entries) {
51
+ if (entry.isDirectory()) {
52
+ if (SKIP_DIRS.has(entry.name)) continue;
53
+ if (entry.name.startsWith(".") && dir !== rootDir) continue;
54
+ walk(path2.join(dir, entry.name));
55
+ } else if (entry.isFile()) {
56
+ const name = entry.name;
57
+ if (SKIP_FILES.has(name)) continue;
58
+ if (name.endsWith(".d.ts") || name.endsWith(".d.mts") || name.endsWith(".d.cts")) continue;
59
+ const ext = path2.extname(name);
60
+ if (TS_EXTENSIONS.has(ext)) {
61
+ files.push(path2.join(dir, name));
62
+ }
63
+ }
64
+ }
65
+ }
66
+ walk(rootDir);
67
+ return files;
68
+ }
69
+ function parseFileImports(filePath, source) {
70
+ const result = parseSync(filePath, source);
71
+ const imports = [];
72
+ for (const imp of result.module.staticImports) {
73
+ const specifier = imp.moduleRequest.value;
74
+ const names = [];
75
+ let allTypeOnly = true;
76
+ for (const entry of imp.entries) {
77
+ const kind = entry.importName.kind;
78
+ const name = kind === "Default" ? "default" : kind === "All" || kind === "AllButDefault" || kind === "NamespaceObject" ? "*" : entry.importName.name ?? entry.localName.value;
79
+ names.push(name);
80
+ if (!entry.isType) allTypeOnly = false;
81
+ }
82
+ if (names.length === 0) {
83
+ imports.push({ specifier, names: ["*"], isTypeOnly: false, isDynamic: false });
84
+ } else {
85
+ imports.push({ specifier, names, isTypeOnly: allTypeOnly, isDynamic: false });
86
+ }
87
+ }
88
+ for (const exp of result.module.staticExports) {
89
+ for (const entry of exp.entries) {
90
+ const moduleRequest = entry.moduleRequest;
91
+ if (!moduleRequest) continue;
92
+ const specifier = moduleRequest.value;
93
+ const entryKind = entry.importName.kind;
94
+ const name = entryKind === "AllButDefault" || entryKind === "All" || entryKind === "NamespaceObject" ? "*" : entry.importName.name ?? "*";
95
+ const existing = imports.find((i) => i.specifier === specifier && !i.isDynamic);
96
+ if (existing) {
97
+ if (!existing.names.includes(name)) existing.names.push(name);
98
+ } else {
99
+ imports.push({ specifier, names: [name], isTypeOnly: false, isDynamic: false });
100
+ }
101
+ }
102
+ }
103
+ for (const di of result.module.dynamicImports) {
104
+ if (di.moduleRequest) {
105
+ const sliced = source.slice(di.moduleRequest.start, di.moduleRequest.end);
106
+ if (sliced.startsWith("'") || sliced.startsWith('"')) {
107
+ const specifier = sliced.slice(1, -1);
108
+ imports.push({ specifier, names: ["*"], isTypeOnly: false, isDynamic: true });
109
+ }
110
+ }
111
+ }
112
+ return imports;
113
+ }
114
+ function distToSource(resolvedPath, projectRoot2) {
115
+ if (!resolvedPath.startsWith(projectRoot2)) return resolvedPath;
116
+ const rel2 = path2.relative(projectRoot2, resolvedPath);
117
+ const distIdx = rel2.indexOf("dist" + path2.sep);
118
+ if (distIdx === -1) return resolvedPath;
119
+ const prefix = rel2.slice(0, distIdx);
120
+ const afterDist = rel2.slice(distIdx + 5);
121
+ const withoutExt = afterDist.replace(/\.(m?j|c)s$/, "");
122
+ for (const ext of SOURCE_EXTS) {
123
+ const candidate = path2.resolve(projectRoot2, prefix, "src", withoutExt + ext);
124
+ if (fs.existsSync(candidate)) return candidate;
125
+ }
126
+ for (const ext of SOURCE_EXTS) {
127
+ const candidate = path2.resolve(projectRoot2, prefix, withoutExt + ext);
128
+ if (fs.existsSync(candidate)) return candidate;
129
+ }
130
+ if (withoutExt.endsWith("/index")) {
131
+ const dirPath = withoutExt.slice(0, -6);
132
+ for (const ext of SOURCE_EXTS) {
133
+ const candidate = path2.resolve(projectRoot2, prefix, "src", dirPath + ext);
134
+ if (fs.existsSync(candidate)) return candidate;
135
+ }
136
+ }
137
+ return resolvedPath;
138
+ }
139
+ function resolveImport(resolver, fromDir, specifier, projectRoot2) {
140
+ try {
141
+ const result = resolver.sync(fromDir, specifier);
142
+ if (result.path && !result.path.includes("node_modules")) {
143
+ const mapped = distToSource(result.path, projectRoot2);
144
+ const ext = path2.extname(mapped);
145
+ if (!TS_EXTENSIONS.has(ext)) return null;
146
+ if (SKIP_FILES.has(path2.basename(mapped))) return null;
147
+ return mapped;
148
+ }
149
+ } catch {
150
+ }
151
+ return null;
152
+ }
153
+ function createResolver(projectRoot2, tsconfigPath2) {
154
+ return new ResolverFactory({
155
+ tsconfig: {
156
+ configFile: path2.resolve(projectRoot2, tsconfigPath2),
157
+ references: "auto"
158
+ },
159
+ extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
160
+ extensionAlias: {
161
+ ".js": [".ts", ".tsx", ".js"],
162
+ ".jsx": [".tsx", ".jsx"],
163
+ ".mjs": [".mts", ".mjs"],
164
+ ".cjs": [".cts", ".cjs"]
165
+ },
166
+ conditionNames: ["import", "require"],
167
+ mainFields: ["module", "main"]
168
+ });
169
+ }
170
+ function buildForwardEdges(files, resolver, projectRoot2) {
171
+ const forward = /* @__PURE__ */ new Map();
172
+ const parseFailures = [];
173
+ for (const filePath of files) {
174
+ let source;
175
+ try {
176
+ source = fs.readFileSync(filePath, "utf-8");
177
+ } catch {
178
+ continue;
179
+ }
180
+ let rawImports;
181
+ try {
182
+ rawImports = parseFileImports(filePath, source);
183
+ } catch (err) {
184
+ parseFailures.push(filePath);
185
+ continue;
186
+ }
187
+ const edges = [];
188
+ const fromDir = path2.dirname(filePath);
189
+ for (const raw of rawImports) {
190
+ const target = resolveImport(resolver, fromDir, raw.specifier, projectRoot2);
191
+ if (target) {
192
+ edges.push({
193
+ target,
194
+ specifiers: raw.names,
195
+ isTypeOnly: raw.isTypeOnly,
196
+ isDynamic: raw.isDynamic
197
+ });
198
+ }
199
+ }
200
+ forward.set(filePath, edges);
201
+ }
202
+ return { forward, parseFailures };
203
+ }
204
+ function buildReverseMap(forward) {
205
+ const reverse = /* @__PURE__ */ new Map();
206
+ for (const [source, edges] of forward) {
207
+ for (const edge of edges) {
208
+ let revEdges = reverse.get(edge.target);
209
+ if (!revEdges) {
210
+ revEdges = [];
211
+ reverse.set(edge.target, revEdges);
212
+ }
213
+ revEdges.push({
214
+ target: source,
215
+ // reverse: the "target" is the file that imports
216
+ specifiers: edge.specifiers,
217
+ isTypeOnly: edge.isTypeOnly,
218
+ isDynamic: edge.isDynamic
219
+ });
220
+ }
221
+ }
222
+ return reverse;
223
+ }
224
+ async function buildGraph(projectRoot2, tsconfigPath2) {
225
+ const startTime = performance.now();
226
+ const resolver = createResolver(projectRoot2, tsconfigPath2);
227
+ const fileList = discoverFiles(projectRoot2);
228
+ log(`Discovered ${fileList.length} source files`);
229
+ const { forward, parseFailures } = buildForwardEdges(fileList, resolver, projectRoot2);
230
+ const reverse = buildReverseMap(forward);
231
+ const files = new Set(fileList);
232
+ const edgeCount = [...forward.values()].reduce((sum, edges) => sum + edges.length, 0);
233
+ const elapsed = (performance.now() - startTime).toFixed(0);
234
+ log(`Graph built: ${files.size} files, ${edgeCount} edges [${elapsed}ms]`);
235
+ if (parseFailures.length > 0) {
236
+ log(`Parse failures: ${parseFailures.length} files`);
237
+ }
238
+ return {
239
+ graph: { forward, reverse, files },
240
+ resolver
241
+ };
242
+ }
243
+ function updateFile(graph, filePath, resolver, projectRoot2) {
244
+ const oldEdges = graph.forward.get(filePath) ?? [];
245
+ for (const edge of oldEdges) {
246
+ const revEdges = graph.reverse.get(edge.target);
247
+ if (revEdges) {
248
+ const idx = revEdges.findIndex((r) => r.target === filePath);
249
+ if (idx !== -1) revEdges.splice(idx, 1);
250
+ if (revEdges.length === 0) graph.reverse.delete(edge.target);
251
+ }
252
+ }
253
+ let source;
254
+ try {
255
+ source = fs.readFileSync(filePath, "utf-8");
256
+ } catch {
257
+ removeFile(graph, filePath);
258
+ return;
259
+ }
260
+ let rawImports;
261
+ try {
262
+ rawImports = parseFileImports(filePath, source);
263
+ } catch {
264
+ log(`Parse error on update: ${filePath}`);
265
+ graph.forward.set(filePath, []);
266
+ return;
267
+ }
268
+ const fromDir = path2.dirname(filePath);
269
+ const newEdges = [];
270
+ for (const raw of rawImports) {
271
+ const target = resolveImport(resolver, fromDir, raw.specifier, projectRoot2);
272
+ if (target) {
273
+ newEdges.push({
274
+ target,
275
+ specifiers: raw.names,
276
+ isTypeOnly: raw.isTypeOnly,
277
+ isDynamic: raw.isDynamic
278
+ });
279
+ }
280
+ }
281
+ graph.forward.set(filePath, newEdges);
282
+ graph.files.add(filePath);
283
+ for (const edge of newEdges) {
284
+ let revEdges = graph.reverse.get(edge.target);
285
+ if (!revEdges) {
286
+ revEdges = [];
287
+ graph.reverse.set(edge.target, revEdges);
288
+ }
289
+ revEdges.push({
290
+ target: filePath,
291
+ specifiers: edge.specifiers,
292
+ isTypeOnly: edge.isTypeOnly,
293
+ isDynamic: edge.isDynamic
294
+ });
295
+ }
296
+ }
297
+ function removeFile(graph, filePath) {
298
+ const edges = graph.forward.get(filePath) ?? [];
299
+ for (const edge of edges) {
300
+ const revEdges2 = graph.reverse.get(edge.target);
301
+ if (revEdges2) {
302
+ const idx = revEdges2.findIndex((r) => r.target === filePath);
303
+ if (idx !== -1) revEdges2.splice(idx, 1);
304
+ if (revEdges2.length === 0) graph.reverse.delete(edge.target);
305
+ }
306
+ }
307
+ const revEdges = graph.reverse.get(filePath) ?? [];
308
+ for (const revEdge of revEdges) {
309
+ const fwdEdges = graph.forward.get(revEdge.target);
310
+ if (fwdEdges) {
311
+ const idx = fwdEdges.findIndex((e) => e.target === filePath);
312
+ if (idx !== -1) fwdEdges.splice(idx, 1);
313
+ }
314
+ }
315
+ graph.forward.delete(filePath);
316
+ graph.reverse.delete(filePath);
317
+ graph.files.delete(filePath);
318
+ }
319
+ function startWatcher(projectRoot2, graph, resolver) {
320
+ try {
321
+ const watcher = fs.watch(
322
+ projectRoot2,
323
+ { recursive: true },
324
+ (_eventType, filename) => {
325
+ if (!filename) return;
326
+ const ext = path2.extname(filename);
327
+ if (!TS_EXTENSIONS.has(ext)) return;
328
+ const parts = filename.split(path2.sep);
329
+ if (parts.some((p2) => SKIP_DIRS.has(p2))) return;
330
+ if (SKIP_FILES.has(path2.basename(filename))) return;
331
+ if (filename.endsWith(".d.ts") || filename.endsWith(".d.mts") || filename.endsWith(".d.cts"))
332
+ return;
333
+ const absPath2 = path2.resolve(projectRoot2, filename);
334
+ if (fs.existsSync(absPath2)) {
335
+ updateFile(graph, absPath2, resolver, projectRoot2);
336
+ } else {
337
+ removeFile(graph, absPath2);
338
+ }
339
+ }
340
+ );
341
+ process.on("SIGINT", () => watcher.close());
342
+ process.on("SIGTERM", () => watcher.close());
343
+ log("File watcher started");
344
+ } catch (err) {
345
+ log("Failed to start file watcher:", err);
346
+ }
347
+ }
348
+ var log, TS_EXTENSIONS, SKIP_DIRS, SKIP_FILES, SOURCE_EXTS;
349
+ var init_module_graph = __esm({
350
+ "module-graph.ts"() {
351
+ log = (...args2) => console.error("[typegraph/graph]", ...args2);
352
+ TS_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts"]);
353
+ SKIP_DIRS = /* @__PURE__ */ new Set([
354
+ "node_modules",
355
+ "dist",
356
+ "build",
357
+ "out",
358
+ ".wrangler",
359
+ ".mf",
360
+ ".git",
361
+ ".next",
362
+ ".turbo",
363
+ "coverage"
364
+ ]);
365
+ SKIP_FILES = /* @__PURE__ */ new Set(["routeTree.gen.ts"]);
366
+ SOURCE_EXTS = [".ts", ".tsx", ".mts", ".cts"];
367
+ }
368
+ });
369
+
27
370
  // check.ts
28
371
  var check_exports = {};
29
372
  __export(check_exports, {
30
373
  main: () => main
31
374
  });
32
- import * as fs from "fs";
33
- import * as path2 from "path";
375
+ import * as fs2 from "fs";
376
+ import * as path3 from "path";
34
377
  import { createRequire } from "module";
35
378
  import { spawn } from "child_process";
36
379
  function findFirstTsFile(dir) {
37
380
  const skipDirs = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", ".wrangler", "coverage"]);
38
381
  try {
39
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
382
+ for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
40
383
  if (entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) {
41
- return path2.join(dir, entry.name);
384
+ return path3.join(dir, entry.name);
42
385
  }
43
386
  }
44
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
387
+ for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
45
388
  if (entry.isDirectory() && !skipDirs.has(entry.name) && !entry.name.startsWith(".")) {
46
- const found = findFirstTsFile(path2.join(dir, entry.name));
389
+ const found = findFirstTsFile(path3.join(dir, entry.name));
47
390
  if (found) return found;
48
391
  }
49
392
  }
@@ -55,7 +398,7 @@ function testTsserver(projectRoot2) {
55
398
  return new Promise((resolve8) => {
56
399
  let tsserverPath;
57
400
  try {
58
- const require2 = createRequire(path2.resolve(projectRoot2, "package.json"));
401
+ const require2 = createRequire(path3.resolve(projectRoot2, "package.json"));
59
402
  tsserverPath = require2.resolve("typescript/lib/tsserver.js");
60
403
  } catch {
61
404
  resolve8(false);
@@ -130,8 +473,8 @@ async function main(configOverride) {
130
473
  } else {
131
474
  fail(`Node.js ${nodeVersion} is too old`, "Upgrade Node.js to >= 18");
132
475
  }
133
- const tsxInRoot = fs.existsSync(path2.join(projectRoot2, "node_modules/.bin/tsx"));
134
- const tsxInTool = fs.existsSync(path2.join(toolDir, "node_modules/.bin/tsx"));
476
+ const tsxInRoot = fs2.existsSync(path3.join(projectRoot2, "node_modules/.bin/tsx"));
477
+ const tsxInTool = fs2.existsSync(path3.join(toolDir, "node_modules/.bin/tsx"));
135
478
  if (tsxInRoot || tsxInTool) {
136
479
  pass(`tsx available (in ${tsxInRoot ? "project" : "tool"} node_modules)`);
137
480
  } else {
@@ -139,10 +482,10 @@ async function main(configOverride) {
139
482
  }
140
483
  let tsVersion = null;
141
484
  try {
142
- const require2 = createRequire(path2.resolve(projectRoot2, "package.json"));
485
+ const require2 = createRequire(path3.resolve(projectRoot2, "package.json"));
143
486
  const tsserverPath = require2.resolve("typescript/lib/tsserver.js");
144
- const tsPkgPath = path2.resolve(path2.dirname(tsserverPath), "..", "package.json");
145
- const tsPkg = JSON.parse(fs.readFileSync(tsPkgPath, "utf-8"));
487
+ const tsPkgPath = path3.resolve(path3.dirname(tsserverPath), "..", "package.json");
488
+ const tsPkg = JSON.parse(fs2.readFileSync(tsPkgPath, "utf-8"));
146
489
  tsVersion = tsPkg.version;
147
490
  pass(`TypeScript found (v${tsVersion})`);
148
491
  } catch {
@@ -151,23 +494,23 @@ async function main(configOverride) {
151
494
  "Add `typescript` to devDependencies and run `npm install`"
152
495
  );
153
496
  }
154
- const tsconfigAbs = path2.resolve(projectRoot2, tsconfigPath2);
155
- if (fs.existsSync(tsconfigAbs)) {
497
+ const tsconfigAbs = path3.resolve(projectRoot2, tsconfigPath2);
498
+ if (fs2.existsSync(tsconfigAbs)) {
156
499
  pass(`tsconfig.json exists at ${tsconfigPath2}`);
157
500
  } else {
158
501
  fail(`tsconfig.json not found at ${tsconfigPath2}`, `Create a tsconfig.json at ${tsconfigPath2}`);
159
502
  }
160
- const pluginMcpPath = path2.join(toolDir, ".mcp.json");
161
- const hasPluginMcp = fs.existsSync(pluginMcpPath) && fs.existsSync(path2.join(toolDir, ".claude-plugin/plugin.json"));
503
+ const pluginMcpPath = path3.join(toolDir, ".mcp.json");
504
+ const hasPluginMcp = fs2.existsSync(pluginMcpPath) && fs2.existsSync(path3.join(toolDir, ".claude-plugin/plugin.json"));
162
505
  if (process.env.CLAUDE_PLUGIN_ROOT) {
163
506
  pass("MCP registered via plugin (CLAUDE_PLUGIN_ROOT set)");
164
507
  } else if (hasPluginMcp) {
165
508
  pass("MCP registered via plugin (.mcp.json + .claude-plugin/ present)");
166
509
  } else {
167
- const mcpJsonPath = path2.resolve(projectRoot2, ".claude/mcp.json");
168
- if (fs.existsSync(mcpJsonPath)) {
510
+ const mcpJsonPath = path3.resolve(projectRoot2, ".claude/mcp.json");
511
+ if (fs2.existsSync(mcpJsonPath)) {
169
512
  try {
170
- const mcpJson = JSON.parse(fs.readFileSync(mcpJsonPath, "utf-8"));
513
+ const mcpJson = JSON.parse(fs2.readFileSync(mcpJsonPath, "utf-8"));
171
514
  const tsNav = mcpJson?.mcpServers?.["typegraph"];
172
515
  if (tsNav) {
173
516
  const hasCommand = tsNav.command === "npx";
@@ -186,7 +529,7 @@ async function main(configOverride) {
186
529
  );
187
530
  }
188
531
  } else {
189
- const serverPath = toolIsEmbedded ? `./${toolRelPath}/server.ts` : path2.resolve(toolDir, "server.ts");
532
+ const serverPath = toolIsEmbedded ? `./${toolRelPath}/server.ts` : path3.resolve(toolDir, "server.ts");
190
533
  fail(
191
534
  "MCP entry 'typegraph' not found in .claude/mcp.json",
192
535
  `Add to .claude/mcp.json:
@@ -211,11 +554,11 @@ async function main(configOverride) {
211
554
  fail(".claude/mcp.json not found", `Create .claude/mcp.json with typegraph server registration`);
212
555
  }
213
556
  }
214
- const toolNodeModules = path2.join(toolDir, "node_modules");
215
- if (fs.existsSync(toolNodeModules)) {
557
+ const toolNodeModules = path3.join(toolDir, "node_modules");
558
+ if (fs2.existsSync(toolNodeModules)) {
216
559
  const requiredPkgs = ["@modelcontextprotocol/sdk", "oxc-parser", "oxc-resolver", "zod"];
217
560
  const missing = requiredPkgs.filter(
218
- (pkg) => !fs.existsSync(path2.join(toolNodeModules, ...pkg.split("/")))
561
+ (pkg) => !fs2.existsSync(path3.join(toolNodeModules, ...pkg.split("/")))
219
562
  );
220
563
  if (missing.length === 0) {
221
564
  pass(`Dependencies installed (${requiredPkgs.length} packages)`);
@@ -226,7 +569,7 @@ async function main(configOverride) {
226
569
  fail("typegraph-mcp dependencies not installed", `Run \`cd ${toolRelPath} && npm install\``);
227
570
  }
228
571
  try {
229
- const oxcParserReq = createRequire(path2.join(toolDir, "package.json"));
572
+ const oxcParserReq = createRequire(path3.join(toolDir, "package.json"));
230
573
  const { parseSync: parseSync2 } = await import(oxcParserReq.resolve("oxc-parser"));
231
574
  const result = parseSync2("test.ts", 'import { x } from "./y";');
232
575
  if (result.module?.staticImports?.length === 1) {
@@ -244,7 +587,7 @@ async function main(configOverride) {
244
587
  );
245
588
  }
246
589
  try {
247
- const oxcResolverReq = createRequire(path2.join(toolDir, "package.json"));
590
+ const oxcResolverReq = createRequire(path3.join(toolDir, "package.json"));
248
591
  const { ResolverFactory: ResolverFactory2 } = await import(oxcResolverReq.resolve("oxc-resolver"));
249
592
  const resolver = new ResolverFactory2({
250
593
  tsconfig: { configFile: tsconfigAbs, references: "auto" },
@@ -254,8 +597,8 @@ async function main(configOverride) {
254
597
  let resolveOk = false;
255
598
  const testFile = findFirstTsFile(projectRoot2);
256
599
  if (testFile) {
257
- const dir = path2.dirname(testFile);
258
- const base = "./" + path2.basename(testFile);
600
+ const dir = path3.dirname(testFile);
601
+ const base = "./" + path3.basename(testFile);
259
602
  const result = resolver.sync(dir, base);
260
603
  resolveOk = !!result.path;
261
604
  }
@@ -294,7 +637,12 @@ async function main(configOverride) {
294
637
  skip("tsserver test (TypeScript not found)");
295
638
  }
296
639
  try {
297
- const { buildGraph: buildGraph2 } = await import(path2.resolve(toolDir, "module-graph.js"));
640
+ let buildGraph2;
641
+ try {
642
+ ({ buildGraph: buildGraph2 } = await import(path3.resolve(toolDir, "module-graph.js")));
643
+ } catch {
644
+ ({ buildGraph: buildGraph2 } = await Promise.resolve().then(() => (init_module_graph(), module_graph_exports)));
645
+ }
298
646
  const start2 = performance.now();
299
647
  const { graph } = await buildGraph2(projectRoot2, tsconfigPath2);
300
648
  const elapsed = (performance.now() - start2).toFixed(0);
@@ -322,10 +670,10 @@ async function main(configOverride) {
322
670
  );
323
671
  }
324
672
  if (toolIsEmbedded) {
325
- const eslintConfigPath = path2.resolve(projectRoot2, "eslint.config.mjs");
326
- if (fs.existsSync(eslintConfigPath)) {
327
- const eslintContent = fs.readFileSync(eslintConfigPath, "utf-8");
328
- const parentDir = path2.basename(path2.dirname(toolDir));
673
+ const eslintConfigPath = path3.resolve(projectRoot2, "eslint.config.mjs");
674
+ if (fs2.existsSync(eslintConfigPath)) {
675
+ const eslintContent = fs2.readFileSync(eslintConfigPath, "utf-8");
676
+ const parentDir = path3.basename(path3.dirname(toolDir));
329
677
  const parentIgnorePattern = new RegExp(`["']${parentDir}\\/\\*\\*["']`);
330
678
  const hasParentIgnore = parentIgnorePattern.test(eslintContent);
331
679
  if (hasParentIgnore) {
@@ -343,14 +691,14 @@ async function main(configOverride) {
343
691
  } else {
344
692
  skip("ESLint config check (typegraph-mcp is external to project)");
345
693
  }
346
- const gitignorePath = path2.resolve(projectRoot2, ".gitignore");
347
- if (fs.existsSync(gitignorePath)) {
348
- const gitignoreContent = fs.readFileSync(gitignorePath, "utf-8");
694
+ const gitignorePath = path3.resolve(projectRoot2, ".gitignore");
695
+ if (fs2.existsSync(gitignorePath)) {
696
+ const gitignoreContent = fs2.readFileSync(gitignorePath, "utf-8");
349
697
  const lines = gitignoreContent.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
350
698
  const ignoresClaude = lines.some(
351
699
  (l) => l === ".claude/" || l === ".claude" || l === "/.claude"
352
700
  );
353
- const parentDir = toolIsEmbedded ? path2.basename(path2.dirname(toolDir)) : null;
701
+ const parentDir = toolIsEmbedded ? path3.basename(path3.dirname(toolDir)) : null;
354
702
  const ignoresParent = parentDir && lines.some((l) => l === `${parentDir}/` || l === parentDir || l === `/${parentDir}`);
355
703
  if (!ignoresParent && !ignoresClaude) {
356
704
  pass(".gitignore does not exclude .claude/" + (parentDir ? ` or ${parentDir}/` : ""));
@@ -380,29 +728,21 @@ async function main(configOverride) {
380
728
  console.log("");
381
729
  return { passed, failed, warned };
382
730
  }
383
- var isDirectRun;
384
731
  var init_check = __esm({
385
732
  "check.ts"() {
386
733
  init_config();
387
- isDirectRun = process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(new URL(import.meta.url).pathname);
388
- if (isDirectRun) {
389
- main().then((result) => process.exit(result.failed > 0 ? 1 : 0)).catch((err) => {
390
- console.error("Fatal error:", err);
391
- process.exit(1);
392
- });
393
- }
394
734
  }
395
735
  });
396
736
 
397
737
  // tsserver-client.ts
398
738
  import { spawn as spawn2 } from "child_process";
399
- import * as path3 from "path";
400
- import * as fs2 from "fs";
739
+ import * as path4 from "path";
740
+ import * as fs3 from "fs";
401
741
  import { createRequire as createRequire2 } from "module";
402
- var log, REQUEST_TIMEOUT_MS, TsServerClient;
742
+ var log2, REQUEST_TIMEOUT_MS, TsServerClient;
403
743
  var init_tsserver_client = __esm({
404
744
  "tsserver-client.ts"() {
405
- log = (...args2) => console.error("[typegraph/tsserver]", ...args2);
745
+ log2 = (...args2) => console.error("[typegraph/tsserver]", ...args2);
406
746
  REQUEST_TIMEOUT_MS = 1e4;
407
747
  TsServerClient = class {
408
748
  constructor(projectRoot2, tsconfigPath2 = "./tsconfig.json") {
@@ -420,16 +760,16 @@ var init_tsserver_client = __esm({
420
760
  maxRestarts = 3;
421
761
  // ─── Path Resolution ────────────────────────────────────────────────────
422
762
  resolvePath(file) {
423
- return path3.isAbsolute(file) ? file : path3.resolve(this.projectRoot, file);
763
+ return path4.isAbsolute(file) ? file : path4.resolve(this.projectRoot, file);
424
764
  }
425
765
  relativePath(file) {
426
- return path3.relative(this.projectRoot, file);
766
+ return path4.relative(this.projectRoot, file);
427
767
  }
428
768
  /** Read a line from a file (1-based line number). Returns trimmed content. */
429
769
  readLine(file, line) {
430
770
  try {
431
771
  const absPath2 = this.resolvePath(file);
432
- const content = fs2.readFileSync(absPath2, "utf-8");
772
+ const content = fs3.readFileSync(absPath2, "utf-8");
433
773
  const lines = content.split("\n");
434
774
  return lines[line - 1]?.trim() ?? "";
435
775
  } catch {
@@ -439,11 +779,11 @@ var init_tsserver_client = __esm({
439
779
  // ─── Lifecycle ──────────────────────────────────────────────────────────
440
780
  async start() {
441
781
  if (this.child) return;
442
- const require2 = createRequire2(path3.resolve(this.projectRoot, "package.json"));
782
+ const require2 = createRequire2(path4.resolve(this.projectRoot, "package.json"));
443
783
  const tsserverPath = require2.resolve("typescript/lib/tsserver.js");
444
- log(`Spawning tsserver: ${tsserverPath}`);
445
- log(`Project root: ${this.projectRoot}`);
446
- log(`tsconfig: ${this.tsconfigPath}`);
784
+ log2(`Spawning tsserver: ${tsserverPath}`);
785
+ log2(`Project root: ${this.projectRoot}`);
786
+ log2(`tsconfig: ${this.tsconfigPath}`);
447
787
  this.child = spawn2("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
448
788
  cwd: this.projectRoot,
449
789
  stdio: ["pipe", "pipe", "pipe"],
@@ -452,16 +792,16 @@ var init_tsserver_client = __esm({
452
792
  this.child.stdout.on("data", (chunk) => this.onData(chunk));
453
793
  this.child.stderr.on("data", (chunk) => {
454
794
  const text = chunk.toString().trim();
455
- if (text) log(`[stderr] ${text}`);
795
+ if (text) log2(`[stderr] ${text}`);
456
796
  });
457
797
  this.child.on("close", (code) => {
458
- log(`tsserver exited with code ${code}`);
798
+ log2(`tsserver exited with code ${code}`);
459
799
  this.child = null;
460
800
  this.rejectAllPending(new Error(`tsserver exited with code ${code}`));
461
801
  this.tryRestart();
462
802
  });
463
803
  this.child.on("error", (err) => {
464
- log(`tsserver error: ${err.message}`);
804
+ log2(`tsserver error: ${err.message}`);
465
805
  this.rejectAllPending(err);
466
806
  });
467
807
  await this.sendRequest("configure", {
@@ -471,13 +811,13 @@ var init_tsserver_client = __esm({
471
811
  });
472
812
  const warmStart = performance.now();
473
813
  const tsconfigAbs = this.resolvePath(this.tsconfigPath);
474
- if (fs2.existsSync(tsconfigAbs)) {
814
+ if (fs3.existsSync(tsconfigAbs)) {
475
815
  await this.sendRequest("compilerOptionsForInferredProjects", {
476
816
  options: { allowJs: true, checkJs: false }
477
817
  });
478
818
  }
479
819
  this.ready = true;
480
- log(`Ready [${(performance.now() - warmStart).toFixed(0)}ms configure]`);
820
+ log2(`Ready [${(performance.now() - warmStart).toFixed(0)}ms configure]`);
481
821
  }
482
822
  shutdown() {
483
823
  this.shuttingDown = true;
@@ -490,11 +830,11 @@ var init_tsserver_client = __esm({
490
830
  tryRestart() {
491
831
  if (this.shuttingDown) return;
492
832
  if (this.restartCount >= this.maxRestarts) {
493
- log(`Max restarts (${this.maxRestarts}) reached, not restarting`);
833
+ log2(`Max restarts (${this.maxRestarts}) reached, not restarting`);
494
834
  return;
495
835
  }
496
836
  this.restartCount++;
497
- log(`Restarting tsserver (attempt ${this.restartCount})...`);
837
+ log2(`Restarting tsserver (attempt ${this.restartCount})...`);
498
838
  this.buffer = Buffer.alloc(0);
499
839
  const filesToReopen = [...this.openFiles];
500
840
  this.openFiles.clear();
@@ -538,7 +878,7 @@ var init_tsserver_client = __esm({
538
878
  const message = JSON.parse(bodyBytes.toString("utf-8"));
539
879
  this.onMessage(message);
540
880
  } catch {
541
- log("Failed to parse tsserver message");
881
+ log2("Failed to parse tsserver message");
542
882
  }
543
883
  }
544
884
  }
@@ -551,449 +891,115 @@ var init_tsserver_client = __esm({
551
891
  if (message.success) {
552
892
  pending.resolve(message.body);
553
893
  } else {
554
- pending.reject(
555
- new Error(`tsserver ${pending.command} failed: ${message.message ?? "unknown error"}`)
556
- );
557
- }
558
- }
559
- }
560
- }
561
- // ─── Protocol: Sending ──────────────────────────────────────────────────
562
- sendRequest(command2, args2) {
563
- if (!this.child?.stdin?.writable) {
564
- return Promise.reject(new Error("tsserver not running"));
565
- }
566
- const seq = ++this.seq;
567
- const request = {
568
- seq,
569
- type: "request",
570
- command: command2,
571
- arguments: args2
572
- };
573
- return new Promise((resolve8, reject) => {
574
- const timer = setTimeout(() => {
575
- this.pending.delete(seq);
576
- reject(new Error(`tsserver ${command2} timed out after ${REQUEST_TIMEOUT_MS}ms`));
577
- }, REQUEST_TIMEOUT_MS);
578
- this.pending.set(seq, { resolve: resolve8, reject, timer, command: command2 });
579
- this.child.stdin.write(JSON.stringify(request) + "\n");
580
- });
581
- }
582
- // Fire-and-forget — for commands like `open` that may not send a response
583
- sendNotification(command2, args2) {
584
- if (!this.child?.stdin?.writable) return;
585
- const seq = ++this.seq;
586
- const request = { seq, type: "request", command: command2, arguments: args2 };
587
- this.child.stdin.write(JSON.stringify(request) + "\n");
588
- }
589
- // ─── File Management ───────────────────────────────────────────────────
590
- async ensureOpen(file) {
591
- const absPath2 = this.resolvePath(file);
592
- if (this.openFiles.has(absPath2)) return;
593
- this.openFiles.add(absPath2);
594
- this.sendNotification("open", { file: absPath2 });
595
- await new Promise((r) => setTimeout(r, 50));
596
- }
597
- // ─── Public API ────────────────────────────────────────────────────────
598
- async definition(file, line, offset) {
599
- const absPath2 = this.resolvePath(file);
600
- await this.ensureOpen(absPath2);
601
- const body = await this.sendRequest("definition", {
602
- file: absPath2,
603
- line,
604
- offset
605
- });
606
- if (!body || !Array.isArray(body)) return [];
607
- return body.map((d) => ({
608
- ...d,
609
- file: this.relativePath(d.file)
610
- }));
611
- }
612
- async references(file, line, offset) {
613
- const absPath2 = this.resolvePath(file);
614
- await this.ensureOpen(absPath2);
615
- const body = await this.sendRequest("references", {
616
- file: absPath2,
617
- line,
618
- offset
619
- });
620
- if (!body?.refs) return [];
621
- return body.refs.map((r) => ({
622
- ...r,
623
- file: this.relativePath(r.file)
624
- }));
625
- }
626
- async quickinfo(file, line, offset) {
627
- const absPath2 = this.resolvePath(file);
628
- await this.ensureOpen(absPath2);
629
- try {
630
- const body = await this.sendRequest("quickinfo", {
631
- file: absPath2,
632
- line,
633
- offset
634
- });
635
- return body ?? null;
636
- } catch {
637
- return null;
638
- }
639
- }
640
- async navto(searchValue, maxResults = 10, file) {
641
- if (file) await this.ensureOpen(file);
642
- const args2 = {
643
- searchValue,
644
- maxResultCount: maxResults
645
- };
646
- if (file) args2["file"] = this.resolvePath(file);
647
- const body = await this.sendRequest("navto", args2);
648
- if (!body || !Array.isArray(body)) return [];
649
- return body.map((item) => ({
650
- ...item,
651
- file: this.relativePath(item.file)
652
- }));
653
- }
654
- async navbar(file) {
655
- const absPath2 = this.resolvePath(file);
656
- await this.ensureOpen(absPath2);
657
- const body = await this.sendRequest("navbar", {
658
- file: absPath2
659
- });
660
- return body ?? [];
661
- }
662
- };
663
- }
664
- });
665
-
666
- // module-graph.ts
667
- import { parseSync } from "oxc-parser";
668
- import { ResolverFactory } from "oxc-resolver";
669
- import * as fs3 from "fs";
670
- import * as path4 from "path";
671
- function discoverFiles(rootDir) {
672
- const files = [];
673
- function walk(dir) {
674
- let entries;
675
- try {
676
- entries = fs3.readdirSync(dir, { withFileTypes: true });
677
- } catch {
678
- return;
679
- }
680
- for (const entry of entries) {
681
- if (entry.isDirectory()) {
682
- if (SKIP_DIRS.has(entry.name)) continue;
683
- if (entry.name.startsWith(".") && dir !== rootDir) continue;
684
- walk(path4.join(dir, entry.name));
685
- } else if (entry.isFile()) {
686
- const name = entry.name;
687
- if (SKIP_FILES.has(name)) continue;
688
- if (name.endsWith(".d.ts") || name.endsWith(".d.mts") || name.endsWith(".d.cts")) continue;
689
- const ext = path4.extname(name);
690
- if (TS_EXTENSIONS.has(ext)) {
691
- files.push(path4.join(dir, name));
692
- }
693
- }
694
- }
695
- }
696
- walk(rootDir);
697
- return files;
698
- }
699
- function parseFileImports(filePath, source) {
700
- const result = parseSync(filePath, source);
701
- const imports = [];
702
- for (const imp of result.module.staticImports) {
703
- const specifier = imp.moduleRequest.value;
704
- const names = [];
705
- let allTypeOnly = true;
706
- for (const entry of imp.entries) {
707
- const kind = entry.importName.kind;
708
- const name = kind === "Default" ? "default" : kind === "All" || kind === "AllButDefault" || kind === "NamespaceObject" ? "*" : entry.importName.name ?? entry.localName.value;
709
- names.push(name);
710
- if (!entry.isType) allTypeOnly = false;
711
- }
712
- if (names.length === 0) {
713
- imports.push({ specifier, names: ["*"], isTypeOnly: false, isDynamic: false });
714
- } else {
715
- imports.push({ specifier, names, isTypeOnly: allTypeOnly, isDynamic: false });
716
- }
717
- }
718
- for (const exp of result.module.staticExports) {
719
- for (const entry of exp.entries) {
720
- const moduleRequest = entry.moduleRequest;
721
- if (!moduleRequest) continue;
722
- const specifier = moduleRequest.value;
723
- const entryKind = entry.importName.kind;
724
- const name = entryKind === "AllButDefault" || entryKind === "All" || entryKind === "NamespaceObject" ? "*" : entry.importName.name ?? "*";
725
- const existing = imports.find((i) => i.specifier === specifier && !i.isDynamic);
726
- if (existing) {
727
- if (!existing.names.includes(name)) existing.names.push(name);
728
- } else {
729
- imports.push({ specifier, names: [name], isTypeOnly: false, isDynamic: false });
730
- }
731
- }
732
- }
733
- for (const di of result.module.dynamicImports) {
734
- if (di.moduleRequest) {
735
- const sliced = source.slice(di.moduleRequest.start, di.moduleRequest.end);
736
- if (sliced.startsWith("'") || sliced.startsWith('"')) {
737
- const specifier = sliced.slice(1, -1);
738
- imports.push({ specifier, names: ["*"], isTypeOnly: false, isDynamic: true });
739
- }
740
- }
741
- }
742
- return imports;
743
- }
744
- function distToSource(resolvedPath, projectRoot2) {
745
- if (!resolvedPath.startsWith(projectRoot2)) return resolvedPath;
746
- const rel2 = path4.relative(projectRoot2, resolvedPath);
747
- const distIdx = rel2.indexOf("dist" + path4.sep);
748
- if (distIdx === -1) return resolvedPath;
749
- const prefix = rel2.slice(0, distIdx);
750
- const afterDist = rel2.slice(distIdx + 5);
751
- const withoutExt = afterDist.replace(/\.(m?j|c)s$/, "");
752
- for (const ext of SOURCE_EXTS) {
753
- const candidate = path4.resolve(projectRoot2, prefix, "src", withoutExt + ext);
754
- if (fs3.existsSync(candidate)) return candidate;
755
- }
756
- for (const ext of SOURCE_EXTS) {
757
- const candidate = path4.resolve(projectRoot2, prefix, withoutExt + ext);
758
- if (fs3.existsSync(candidate)) return candidate;
759
- }
760
- if (withoutExt.endsWith("/index")) {
761
- const dirPath = withoutExt.slice(0, -6);
762
- for (const ext of SOURCE_EXTS) {
763
- const candidate = path4.resolve(projectRoot2, prefix, "src", dirPath + ext);
764
- if (fs3.existsSync(candidate)) return candidate;
765
- }
766
- }
767
- return resolvedPath;
768
- }
769
- function resolveImport(resolver, fromDir, specifier, projectRoot2) {
770
- try {
771
- const result = resolver.sync(fromDir, specifier);
772
- if (result.path && !result.path.includes("node_modules")) {
773
- const mapped = distToSource(result.path, projectRoot2);
774
- const ext = path4.extname(mapped);
775
- if (!TS_EXTENSIONS.has(ext)) return null;
776
- if (SKIP_FILES.has(path4.basename(mapped))) return null;
777
- return mapped;
778
- }
779
- } catch {
780
- }
781
- return null;
782
- }
783
- function createResolver(projectRoot2, tsconfigPath2) {
784
- return new ResolverFactory({
785
- tsconfig: {
786
- configFile: path4.resolve(projectRoot2, tsconfigPath2),
787
- references: "auto"
788
- },
789
- extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
790
- extensionAlias: {
791
- ".js": [".ts", ".tsx", ".js"],
792
- ".jsx": [".tsx", ".jsx"],
793
- ".mjs": [".mts", ".mjs"],
794
- ".cjs": [".cts", ".cjs"]
795
- },
796
- conditionNames: ["import", "require"],
797
- mainFields: ["module", "main"]
798
- });
799
- }
800
- function buildForwardEdges(files, resolver, projectRoot2) {
801
- const forward = /* @__PURE__ */ new Map();
802
- const parseFailures = [];
803
- for (const filePath of files) {
804
- let source;
805
- try {
806
- source = fs3.readFileSync(filePath, "utf-8");
807
- } catch {
808
- continue;
809
- }
810
- let rawImports;
811
- try {
812
- rawImports = parseFileImports(filePath, source);
813
- } catch (err) {
814
- parseFailures.push(filePath);
815
- continue;
816
- }
817
- const edges = [];
818
- const fromDir = path4.dirname(filePath);
819
- for (const raw of rawImports) {
820
- const target = resolveImport(resolver, fromDir, raw.specifier, projectRoot2);
821
- if (target) {
822
- edges.push({
823
- target,
824
- specifiers: raw.names,
825
- isTypeOnly: raw.isTypeOnly,
826
- isDynamic: raw.isDynamic
894
+ pending.reject(
895
+ new Error(`tsserver ${pending.command} failed: ${message.message ?? "unknown error"}`)
896
+ );
897
+ }
898
+ }
899
+ }
900
+ }
901
+ // ─── Protocol: Sending ──────────────────────────────────────────────────
902
+ sendRequest(command2, args2) {
903
+ if (!this.child?.stdin?.writable) {
904
+ return Promise.reject(new Error("tsserver not running"));
905
+ }
906
+ const seq = ++this.seq;
907
+ const request = {
908
+ seq,
909
+ type: "request",
910
+ command: command2,
911
+ arguments: args2
912
+ };
913
+ return new Promise((resolve8, reject) => {
914
+ const timer = setTimeout(() => {
915
+ this.pending.delete(seq);
916
+ reject(new Error(`tsserver ${command2} timed out after ${REQUEST_TIMEOUT_MS}ms`));
917
+ }, REQUEST_TIMEOUT_MS);
918
+ this.pending.set(seq, { resolve: resolve8, reject, timer, command: command2 });
919
+ this.child.stdin.write(JSON.stringify(request) + "\n");
827
920
  });
828
921
  }
829
- }
830
- forward.set(filePath, edges);
831
- }
832
- return { forward, parseFailures };
833
- }
834
- function buildReverseMap(forward) {
835
- const reverse = /* @__PURE__ */ new Map();
836
- for (const [source, edges] of forward) {
837
- for (const edge of edges) {
838
- let revEdges = reverse.get(edge.target);
839
- if (!revEdges) {
840
- revEdges = [];
841
- reverse.set(edge.target, revEdges);
922
+ // Fire-and-forget — for commands like `open` that may not send a response
923
+ sendNotification(command2, args2) {
924
+ if (!this.child?.stdin?.writable) return;
925
+ const seq = ++this.seq;
926
+ const request = { seq, type: "request", command: command2, arguments: args2 };
927
+ this.child.stdin.write(JSON.stringify(request) + "\n");
842
928
  }
843
- revEdges.push({
844
- target: source,
845
- // reverse: the "target" is the file that imports
846
- specifiers: edge.specifiers,
847
- isTypeOnly: edge.isTypeOnly,
848
- isDynamic: edge.isDynamic
849
- });
850
- }
851
- }
852
- return reverse;
853
- }
854
- async function buildGraph(projectRoot2, tsconfigPath2) {
855
- const startTime = performance.now();
856
- const resolver = createResolver(projectRoot2, tsconfigPath2);
857
- const fileList = discoverFiles(projectRoot2);
858
- log2(`Discovered ${fileList.length} source files`);
859
- const { forward, parseFailures } = buildForwardEdges(fileList, resolver, projectRoot2);
860
- const reverse = buildReverseMap(forward);
861
- const files = new Set(fileList);
862
- const edgeCount = [...forward.values()].reduce((sum, edges) => sum + edges.length, 0);
863
- const elapsed = (performance.now() - startTime).toFixed(0);
864
- log2(`Graph built: ${files.size} files, ${edgeCount} edges [${elapsed}ms]`);
865
- if (parseFailures.length > 0) {
866
- log2(`Parse failures: ${parseFailures.length} files`);
867
- }
868
- return {
869
- graph: { forward, reverse, files },
870
- resolver
871
- };
872
- }
873
- function updateFile(graph, filePath, resolver, projectRoot2) {
874
- const oldEdges = graph.forward.get(filePath) ?? [];
875
- for (const edge of oldEdges) {
876
- const revEdges = graph.reverse.get(edge.target);
877
- if (revEdges) {
878
- const idx = revEdges.findIndex((r) => r.target === filePath);
879
- if (idx !== -1) revEdges.splice(idx, 1);
880
- if (revEdges.length === 0) graph.reverse.delete(edge.target);
881
- }
882
- }
883
- let source;
884
- try {
885
- source = fs3.readFileSync(filePath, "utf-8");
886
- } catch {
887
- removeFile(graph, filePath);
888
- return;
889
- }
890
- let rawImports;
891
- try {
892
- rawImports = parseFileImports(filePath, source);
893
- } catch {
894
- log2(`Parse error on update: ${filePath}`);
895
- graph.forward.set(filePath, []);
896
- return;
897
- }
898
- const fromDir = path4.dirname(filePath);
899
- const newEdges = [];
900
- for (const raw of rawImports) {
901
- const target = resolveImport(resolver, fromDir, raw.specifier, projectRoot2);
902
- if (target) {
903
- newEdges.push({
904
- target,
905
- specifiers: raw.names,
906
- isTypeOnly: raw.isTypeOnly,
907
- isDynamic: raw.isDynamic
908
- });
909
- }
910
- }
911
- graph.forward.set(filePath, newEdges);
912
- graph.files.add(filePath);
913
- for (const edge of newEdges) {
914
- let revEdges = graph.reverse.get(edge.target);
915
- if (!revEdges) {
916
- revEdges = [];
917
- graph.reverse.set(edge.target, revEdges);
918
- }
919
- revEdges.push({
920
- target: filePath,
921
- specifiers: edge.specifiers,
922
- isTypeOnly: edge.isTypeOnly,
923
- isDynamic: edge.isDynamic
924
- });
925
- }
926
- }
927
- function removeFile(graph, filePath) {
928
- const edges = graph.forward.get(filePath) ?? [];
929
- for (const edge of edges) {
930
- const revEdges2 = graph.reverse.get(edge.target);
931
- if (revEdges2) {
932
- const idx = revEdges2.findIndex((r) => r.target === filePath);
933
- if (idx !== -1) revEdges2.splice(idx, 1);
934
- if (revEdges2.length === 0) graph.reverse.delete(edge.target);
935
- }
936
- }
937
- const revEdges = graph.reverse.get(filePath) ?? [];
938
- for (const revEdge of revEdges) {
939
- const fwdEdges = graph.forward.get(revEdge.target);
940
- if (fwdEdges) {
941
- const idx = fwdEdges.findIndex((e) => e.target === filePath);
942
- if (idx !== -1) fwdEdges.splice(idx, 1);
943
- }
944
- }
945
- graph.forward.delete(filePath);
946
- graph.reverse.delete(filePath);
947
- graph.files.delete(filePath);
948
- }
949
- function startWatcher(projectRoot2, graph, resolver) {
950
- try {
951
- const watcher = fs3.watch(
952
- projectRoot2,
953
- { recursive: true },
954
- (_eventType, filename) => {
955
- if (!filename) return;
956
- const ext = path4.extname(filename);
957
- if (!TS_EXTENSIONS.has(ext)) return;
958
- const parts = filename.split(path4.sep);
959
- if (parts.some((p2) => SKIP_DIRS.has(p2))) return;
960
- if (SKIP_FILES.has(path4.basename(filename))) return;
961
- if (filename.endsWith(".d.ts") || filename.endsWith(".d.mts") || filename.endsWith(".d.cts"))
962
- return;
963
- const absPath2 = path4.resolve(projectRoot2, filename);
964
- if (fs3.existsSync(absPath2)) {
965
- updateFile(graph, absPath2, resolver, projectRoot2);
966
- } else {
967
- removeFile(graph, absPath2);
929
+ // ─── File Management ───────────────────────────────────────────────────
930
+ async ensureOpen(file) {
931
+ const absPath2 = this.resolvePath(file);
932
+ if (this.openFiles.has(absPath2)) return;
933
+ this.openFiles.add(absPath2);
934
+ this.sendNotification("open", { file: absPath2 });
935
+ await new Promise((r) => setTimeout(r, 50));
936
+ }
937
+ // ─── Public API ────────────────────────────────────────────────────────
938
+ async definition(file, line, offset) {
939
+ const absPath2 = this.resolvePath(file);
940
+ await this.ensureOpen(absPath2);
941
+ const body = await this.sendRequest("definition", {
942
+ file: absPath2,
943
+ line,
944
+ offset
945
+ });
946
+ if (!body || !Array.isArray(body)) return [];
947
+ return body.map((d) => ({
948
+ ...d,
949
+ file: this.relativePath(d.file)
950
+ }));
951
+ }
952
+ async references(file, line, offset) {
953
+ const absPath2 = this.resolvePath(file);
954
+ await this.ensureOpen(absPath2);
955
+ const body = await this.sendRequest("references", {
956
+ file: absPath2,
957
+ line,
958
+ offset
959
+ });
960
+ if (!body?.refs) return [];
961
+ return body.refs.map((r) => ({
962
+ ...r,
963
+ file: this.relativePath(r.file)
964
+ }));
965
+ }
966
+ async quickinfo(file, line, offset) {
967
+ const absPath2 = this.resolvePath(file);
968
+ await this.ensureOpen(absPath2);
969
+ try {
970
+ const body = await this.sendRequest("quickinfo", {
971
+ file: absPath2,
972
+ line,
973
+ offset
974
+ });
975
+ return body ?? null;
976
+ } catch {
977
+ return null;
968
978
  }
969
979
  }
970
- );
971
- process.on("SIGINT", () => watcher.close());
972
- process.on("SIGTERM", () => watcher.close());
973
- log2("File watcher started");
974
- } catch (err) {
975
- log2("Failed to start file watcher:", err);
976
- }
977
- }
978
- var log2, TS_EXTENSIONS, SKIP_DIRS, SKIP_FILES, SOURCE_EXTS;
979
- var init_module_graph = __esm({
980
- "module-graph.ts"() {
981
- log2 = (...args2) => console.error("[typegraph/graph]", ...args2);
982
- TS_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts"]);
983
- SKIP_DIRS = /* @__PURE__ */ new Set([
984
- "node_modules",
985
- "dist",
986
- "build",
987
- "out",
988
- ".wrangler",
989
- ".mf",
990
- ".git",
991
- ".next",
992
- ".turbo",
993
- "coverage"
994
- ]);
995
- SKIP_FILES = /* @__PURE__ */ new Set(["routeTree.gen.ts"]);
996
- SOURCE_EXTS = [".ts", ".tsx", ".mts", ".cts"];
980
+ async navto(searchValue, maxResults = 10, file) {
981
+ if (file) await this.ensureOpen(file);
982
+ const args2 = {
983
+ searchValue,
984
+ maxResultCount: maxResults
985
+ };
986
+ if (file) args2["file"] = this.resolvePath(file);
987
+ const body = await this.sendRequest("navto", args2);
988
+ if (!body || !Array.isArray(body)) return [];
989
+ return body.map((item) => ({
990
+ ...item,
991
+ file: this.relativePath(item.file)
992
+ }));
993
+ }
994
+ async navbar(file) {
995
+ const absPath2 = this.resolvePath(file);
996
+ await this.ensureOpen(absPath2);
997
+ const body = await this.sendRequest("navbar", {
998
+ file: absPath2
999
+ });
1000
+ return body ?? [];
1001
+ }
1002
+ };
997
1003
  }
998
1004
  });
999
1005
 
@@ -1633,7 +1639,7 @@ async function main2(configOverride) {
1633
1639
  console.log("");
1634
1640
  return { passed, failed, skipped };
1635
1641
  }
1636
- var SKIP_DIRS2, isDirectRun2;
1642
+ var SKIP_DIRS2;
1637
1643
  var init_smoke_test = __esm({
1638
1644
  "smoke-test.ts"() {
1639
1645
  init_tsserver_client();
@@ -1649,13 +1655,6 @@ var init_smoke_test = __esm({
1649
1655
  "coverage",
1650
1656
  "out"
1651
1657
  ]);
1652
- isDirectRun2 = process.argv[1] && fs5.realpathSync(process.argv[1]) === fs5.realpathSync(new URL(import.meta.url).pathname);
1653
- if (isDirectRun2) {
1654
- main2().then((result) => process.exit(result.failed > 0 ? 1 : 0)).catch((err) => {
1655
- console.error("Fatal:", err);
1656
- process.exit(1);
1657
- });
1658
- }
1659
1658
  }
1660
1659
  });
1661
1660
 
@@ -2782,14 +2781,21 @@ async function remove(yes2) {
2782
2781
  }
2783
2782
  await removePlugin(projectRoot2, pluginDir);
2784
2783
  }
2784
+ function resolvePluginDir() {
2785
+ const installed = path8.resolve(process.cwd(), PLUGIN_DIR_NAME);
2786
+ if (fs7.existsSync(installed)) return installed;
2787
+ return path8.basename(import.meta.dirname) === "dist" ? path8.resolve(import.meta.dirname, "..") : import.meta.dirname;
2788
+ }
2785
2789
  async function check() {
2790
+ const config = resolveConfig(resolvePluginDir());
2786
2791
  const { main: checkMain } = await Promise.resolve().then(() => (init_check(), check_exports));
2787
- const result = await checkMain();
2792
+ const result = await checkMain(config);
2788
2793
  process.exit(result.failed > 0 ? 1 : 0);
2789
2794
  }
2790
2795
  async function test() {
2796
+ const config = resolveConfig(resolvePluginDir());
2791
2797
  const { main: testMain } = await Promise.resolve().then(() => (init_smoke_test(), smoke_test_exports));
2792
- const result = await testMain();
2798
+ const result = await testMain(config);
2793
2799
  process.exit(result.failed > 0 ? 1 : 0);
2794
2800
  }
2795
2801
  async function start() {