typegraph-mcp 0.9.46 → 0.9.48

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
@@ -9,1232 +9,1326 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // biome-config.ts
13
+ function filesObjectMatch(raw) {
14
+ return raw.match(/(["']files["']\s*:\s*\{)([\s\S]*?)(\n?\s*\})/);
15
+ }
16
+ function filesIncludes(raw) {
17
+ const filesObject = filesObjectMatch(raw);
18
+ if (!filesObject) return null;
19
+ const includes = filesObject[2].match(/["']includes["']\s*:\s*\[([\s\S]*?)\]/);
20
+ if (!includes) return null;
21
+ return [...includes[1].matchAll(/["']([^"']+)["']/g)].map((match) => match[1]);
22
+ }
23
+ function biomeScopeExcludes(raw, parentDir) {
24
+ const escaped = parentDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
25
+ const explicitIgnore = new RegExp(
26
+ `["']!{1,2}(?:\\*\\*/)?${escaped}(?:/\\*\\*)?["']`
27
+ );
28
+ if (explicitIgnore.test(raw)) return true;
29
+ const includes = filesIncludes(raw);
30
+ if (!includes) return false;
31
+ const positivePatterns = includes.filter((pattern) => !pattern.startsWith("!"));
32
+ return !positivePatterns.some((pattern) => {
33
+ const normalized = pattern.replace(/^\.\//, "");
34
+ return normalized === "*" || normalized === "**" || normalized.startsWith("**/") || normalized.startsWith("*/") || normalized === parentDir || normalized.startsWith(`${parentDir}/`);
35
+ });
36
+ }
37
+ function appendToIncludes(body, forceIgnore) {
38
+ const pattern = /(["']includes["']\s*:\s*\[)([\s\S]*?)(\])/;
39
+ if (!pattern.test(body)) return null;
40
+ return body.replace(pattern, (_match, open, items, close) => {
41
+ const trimmed = items.trimEnd();
42
+ const needsComma = trimmed.length > 0 && !trimmed.endsWith(",");
43
+ return `${open}${trimmed}${needsComma ? "," : ""} ${forceIgnore}${close}`;
44
+ });
45
+ }
46
+ function patchBiomeConfig(raw, parentDir) {
47
+ const forceIgnore = `"!!${parentDir}"`;
48
+ const filesObjectRe = /(["']files["']\s*:\s*\{)([\s\S]*?)(\n?\s*\})/;
49
+ const filesObject = filesObjectMatch(raw);
50
+ if (filesObject) {
51
+ const updatedBody = appendToIncludes(filesObject[2], forceIgnore);
52
+ if (updatedBody) {
53
+ return raw.replace(filesObjectRe, `${filesObject[1]}${updatedBody}${filesObject[3]}`);
54
+ }
55
+ const body = filesObject[2].trimEnd();
56
+ const needsComma2 = body.trim().length > 0 && !body.trimEnd().endsWith(",");
57
+ const updatedFiles = `${filesObject[1]}${body}${needsComma2 ? "," : ""}
58
+ "includes": ["**", ${forceIgnore}]${filesObject[3]}`;
59
+ return raw.replace(filesObjectRe, updatedFiles);
60
+ }
61
+ const lastBrace = raw.lastIndexOf("}");
62
+ if (lastBrace === -1) return null;
63
+ const before = raw.slice(0, lastBrace).trimEnd();
64
+ const needsComma = !before.endsWith(",") && !before.endsWith("{");
65
+ return `${before}${needsComma ? "," : ""}
66
+ "files": { "includes": ["**", ${forceIgnore}] }
67
+ }
68
+ `;
69
+ }
70
+ var BIOME_CONFIG_NAMES;
71
+ var init_biome_config = __esm({
72
+ "biome-config.ts"() {
73
+ BIOME_CONFIG_NAMES = [
74
+ "biome.json",
75
+ "biome.jsonc",
76
+ ".biome.json",
77
+ ".biome.jsonc"
78
+ ];
79
+ }
80
+ });
81
+
12
82
  // config.ts
13
83
  import * as path from "path";
14
- function resolveConfig(toolDir) {
84
+ function resolveConfig(toolDir2) {
15
85
  const cwd = process.cwd();
16
- const projectRoot3 = process.env["TYPEGRAPH_PROJECT_ROOT"] ? path.resolve(cwd, process.env["TYPEGRAPH_PROJECT_ROOT"]) : path.basename(path.dirname(toolDir)) === "plugins" ? path.resolve(toolDir, "../..") : cwd;
86
+ const projectRoot3 = process.env["TYPEGRAPH_PROJECT_ROOT"] ? path.resolve(cwd, process.env["TYPEGRAPH_PROJECT_ROOT"]) : path.basename(path.dirname(toolDir2)) === "plugins" ? path.resolve(toolDir2, "../..") : cwd;
17
87
  const tsconfigPath3 = process.env["TYPEGRAPH_TSCONFIG"] || "./tsconfig.json";
18
- const toolIsEmbedded = toolDir.startsWith(projectRoot3 + path.sep);
19
- const toolRelPath = toolIsEmbedded ? path.relative(projectRoot3, toolDir) : toolDir;
20
- return { projectRoot: projectRoot3, tsconfigPath: tsconfigPath3, toolDir, toolIsEmbedded, toolRelPath };
88
+ const toolIsEmbedded2 = toolDir2.startsWith(projectRoot3 + path.sep);
89
+ const toolRelPath = toolIsEmbedded2 ? path.relative(projectRoot3, toolDir2) : toolDir2;
90
+ return { projectRoot: projectRoot3, tsconfigPath: tsconfigPath3, toolDir: toolDir2, toolIsEmbedded: toolIsEmbedded2, toolRelPath };
21
91
  }
22
92
  var init_config = __esm({
23
93
  "config.ts"() {
24
94
  }
25
95
  });
26
96
 
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
- resolveProjectImport: () => resolveProjectImport,
35
- startWatcher: () => startWatcher,
36
- updateFile: () => updateFile
37
- });
38
- import { parseSync } from "oxc-parser";
39
- import { ResolverFactory } from "oxc-resolver";
40
- import * as fs from "fs";
97
+ // tsserver-client.ts
98
+ import { spawn } from "child_process";
41
99
  import * as path2 from "path";
42
- function discoverFiles(rootDir) {
43
- const files = [];
44
- function walk(dir) {
45
- let entries;
46
- try {
47
- entries = fs.readdirSync(dir, { withFileTypes: true });
48
- } catch {
49
- return;
50
- }
51
- for (const entry of entries) {
52
- if (entry.isDirectory()) {
53
- if (SKIP_DIRS.has(entry.name)) continue;
54
- if (entry.name.startsWith(".") && dir !== rootDir) continue;
55
- walk(path2.join(dir, entry.name));
56
- } else if (entry.isFile()) {
57
- const name = entry.name;
58
- if (SKIP_FILES.has(name)) continue;
59
- if (name.endsWith(".d.ts") || name.endsWith(".d.mts") || name.endsWith(".d.cts")) continue;
60
- const ext = path2.extname(name);
61
- if (TS_EXTENSIONS.has(ext)) {
62
- files.push(path2.join(dir, name));
63
- }
64
- }
65
- }
66
- }
67
- walk(rootDir);
68
- return files;
69
- }
70
- function parseFileImports(filePath, source) {
71
- const result = parseSync(filePath, source);
72
- const imports = [];
73
- for (const imp of result.module.staticImports) {
74
- const specifier = imp.moduleRequest.value;
75
- const names = [];
76
- let allTypeOnly = true;
77
- for (const entry of imp.entries) {
78
- const kind = entry.importName.kind;
79
- const name = kind === "Default" ? "default" : kind === "All" || kind === "AllButDefault" || kind === "NamespaceObject" ? "*" : entry.importName.name ?? entry.localName.value;
80
- names.push(name);
81
- if (!entry.isType) allTypeOnly = false;
82
- }
83
- if (names.length === 0) {
84
- imports.push({ specifier, names: ["*"], isTypeOnly: false, isDynamic: false });
85
- } else {
86
- imports.push({ specifier, names, isTypeOnly: allTypeOnly, isDynamic: false });
87
- }
88
- }
89
- for (const exp of result.module.staticExports) {
90
- for (const entry of exp.entries) {
91
- const moduleRequest = entry.moduleRequest;
92
- if (!moduleRequest) continue;
93
- const specifier = moduleRequest.value;
94
- const entryKind = entry.importName.kind;
95
- const name = entryKind === "AllButDefault" || entryKind === "All" || entryKind === "NamespaceObject" ? "*" : entry.importName.name ?? "*";
96
- const existing = imports.find((i) => i.specifier === specifier && !i.isDynamic);
97
- if (existing) {
98
- if (!existing.names.includes(name)) existing.names.push(name);
99
- } else {
100
- imports.push({ specifier, names: [name], isTypeOnly: false, isDynamic: false });
101
- }
102
- }
103
- }
104
- for (const di of result.module.dynamicImports) {
105
- if (di.moduleRequest) {
106
- const sliced = source.slice(di.moduleRequest.start, di.moduleRequest.end);
107
- if (sliced.startsWith("'") || sliced.startsWith('"')) {
108
- const specifier = sliced.slice(1, -1);
109
- imports.push({ specifier, names: ["*"], isTypeOnly: false, isDynamic: true });
110
- }
111
- }
112
- }
113
- return imports;
114
- }
115
- function distToSource(resolvedPath, projectRoot3) {
116
- if (!resolvedPath.startsWith(projectRoot3)) return resolvedPath;
117
- const rel2 = path2.relative(projectRoot3, resolvedPath);
118
- const distIdx = rel2.indexOf("dist" + path2.sep);
119
- if (distIdx === -1) return resolvedPath;
120
- const prefix = rel2.slice(0, distIdx);
121
- const afterDist = rel2.slice(distIdx + 5);
122
- const withoutExt = afterDist.replace(/\.(m?j|c)s$/, "");
123
- for (const ext of SOURCE_EXTS) {
124
- const candidate = path2.resolve(projectRoot3, prefix, "src", withoutExt + ext);
125
- if (fs.existsSync(candidate)) return candidate;
126
- }
127
- for (const ext of SOURCE_EXTS) {
128
- const candidate = path2.resolve(projectRoot3, prefix, withoutExt + ext);
129
- if (fs.existsSync(candidate)) return candidate;
130
- }
131
- if (withoutExt.endsWith("/index")) {
132
- const dirPath = withoutExt.slice(0, -6);
133
- for (const ext of SOURCE_EXTS) {
134
- const candidate = path2.resolve(projectRoot3, prefix, "src", dirPath + ext);
135
- if (fs.existsSync(candidate)) return candidate;
136
- }
137
- }
138
- return resolvedPath;
100
+ import * as fs from "fs";
101
+ import { createRequire } from "module";
102
+ function readPackageVersion(packagePath) {
103
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
104
+ return pkg.version ?? "unknown";
139
105
  }
140
- function resolveProjectImport(resolver, fromDir, specifier, projectRoot3) {
106
+ function resolveTsServer(projectRoot3) {
107
+ const projectRequire = createRequire(path2.resolve(projectRoot3, "package.json"));
108
+ let projectVersion;
141
109
  try {
142
- const result = resolver.sync(fromDir, specifier);
143
- if (result.path && !result.path.includes("node_modules")) {
144
- const mapped = distToSource(result.path, projectRoot3);
145
- const ext = path2.extname(mapped);
146
- if (!TS_EXTENSIONS.has(ext)) return null;
147
- if (SKIP_FILES.has(path2.basename(mapped))) return null;
148
- return mapped;
149
- }
110
+ const packagePath2 = projectRequire.resolve("typescript/package.json");
111
+ projectVersion = readPackageVersion(packagePath2);
112
+ const serverPath2 = projectRequire.resolve("typescript/lib/tsserver.js");
113
+ return { path: serverPath2, version: projectVersion, source: "project" };
150
114
  } catch {
151
115
  }
152
- return null;
153
- }
154
- function createResolver(projectRoot3, tsconfigPath3) {
155
- return new ResolverFactory({
156
- tsconfig: {
157
- configFile: path2.resolve(projectRoot3, tsconfigPath3),
158
- references: "auto"
159
- },
160
- extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
161
- extensionAlias: {
162
- ".js": [".ts", ".tsx", ".js"],
163
- ".jsx": [".tsx", ".jsx"],
164
- ".mjs": [".mts", ".mjs"],
165
- ".cjs": [".cts", ".cjs"]
166
- },
167
- conditionNames: ["import", "require"],
168
- mainFields: ["module", "main"]
169
- });
116
+ const toolRequire = createRequire(import.meta.url);
117
+ const packagePath = toolRequire.resolve("typescript/package.json");
118
+ const serverPath = toolRequire.resolve("typescript/lib/tsserver.js");
119
+ return {
120
+ path: serverPath,
121
+ version: readPackageVersion(packagePath),
122
+ source: "typegraph",
123
+ projectVersion
124
+ };
170
125
  }
171
- function buildForwardEdges(files, resolver, projectRoot3) {
172
- const forward = /* @__PURE__ */ new Map();
173
- const parseFailures = [];
174
- for (const filePath of files) {
175
- let source;
176
- try {
177
- source = fs.readFileSync(filePath, "utf-8");
178
- } catch {
179
- continue;
180
- }
181
- let rawImports;
182
- try {
183
- rawImports = parseFileImports(filePath, source);
184
- } catch (err) {
185
- parseFailures.push(filePath);
186
- continue;
187
- }
188
- const edges = [];
189
- const fromDir = path2.dirname(filePath);
190
- for (const raw of rawImports) {
191
- const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot3);
192
- if (target) {
193
- edges.push({
194
- target,
195
- specifiers: raw.names,
196
- isTypeOnly: raw.isTypeOnly,
197
- isDynamic: raw.isDynamic
126
+ var log, REQUEST_TIMEOUT_MS, TsServerClient;
127
+ var init_tsserver_client = __esm({
128
+ "tsserver-client.ts"() {
129
+ log = (...args2) => console.error("[typegraph/tsserver]", ...args2);
130
+ REQUEST_TIMEOUT_MS = 1e4;
131
+ TsServerClient = class {
132
+ constructor(projectRoot3, tsconfigPath3 = "./tsconfig.json") {
133
+ this.projectRoot = projectRoot3;
134
+ this.tsconfigPath = tsconfigPath3;
135
+ }
136
+ child = null;
137
+ seq = 0;
138
+ pending = /* @__PURE__ */ new Map();
139
+ openFiles = /* @__PURE__ */ new Set();
140
+ buffer = Buffer.alloc(0);
141
+ ready = false;
142
+ shuttingDown = false;
143
+ restartCount = 0;
144
+ maxRestarts = 3;
145
+ projectsDirty = false;
146
+ // ─── Path Resolution ────────────────────────────────────────────────────
147
+ resolvePath(file) {
148
+ return path2.isAbsolute(file) ? file : path2.resolve(this.projectRoot, file);
149
+ }
150
+ relativePath(file) {
151
+ return path2.relative(this.projectRoot, file);
152
+ }
153
+ /** Read a line from a file (1-based line number). Returns trimmed content. */
154
+ readLine(file, line) {
155
+ try {
156
+ const absPath2 = this.resolvePath(file);
157
+ const content = fs.readFileSync(absPath2, "utf-8");
158
+ const lines = content.split("\n");
159
+ return lines[line - 1]?.trim() ?? "";
160
+ } catch {
161
+ return "";
162
+ }
163
+ }
164
+ // ─── Lifecycle ──────────────────────────────────────────────────────────
165
+ async start() {
166
+ if (this.child) return;
167
+ const resolution = resolveTsServer(this.projectRoot);
168
+ const tsserverPath = resolution.path;
169
+ const fallbackNote = resolution.projectVersion ? `; project TypeScript ${resolution.projectVersion} has no legacy tsserver` : "";
170
+ log(
171
+ `Spawning tsserver: ${tsserverPath} (TypeScript ${resolution.version}, ${resolution.source}${fallbackNote})`
172
+ );
173
+ log(`Project root: ${this.projectRoot}`);
174
+ log(`tsconfig: ${this.tsconfigPath}`);
175
+ this.child = spawn("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
176
+ cwd: this.projectRoot,
177
+ stdio: ["pipe", "pipe", "pipe"],
178
+ env: { ...process.env, TSS_LOG: void 0 }
179
+ });
180
+ this.child.stdout.on("data", (chunk) => this.onData(chunk));
181
+ this.child.stderr.on("data", (chunk) => {
182
+ const text = chunk.toString().trim();
183
+ if (text) log(`[stderr] ${text}`);
184
+ });
185
+ this.child.on("close", (code) => {
186
+ log(`tsserver exited with code ${code}`);
187
+ this.child = null;
188
+ this.rejectAllPending(new Error(`tsserver exited with code ${code}`));
189
+ this.tryRestart();
190
+ });
191
+ this.child.on("error", (err) => {
192
+ log(`tsserver error: ${err.message}`);
193
+ this.rejectAllPending(err);
194
+ });
195
+ await this.sendRequest("configure", {
196
+ preferences: {
197
+ disableSuggestions: true
198
+ }
199
+ });
200
+ const warmStart = performance.now();
201
+ await this.sendRequest("compilerOptionsForInferredProjects", {
202
+ options: { allowJs: true, checkJs: false }
198
203
  });
204
+ this.ready = true;
205
+ log(`Ready [${(performance.now() - warmStart).toFixed(0)}ms configure]`);
199
206
  }
200
- }
201
- forward.set(filePath, edges);
202
- }
203
- return { forward, parseFailures };
204
- }
205
- function buildReverseMap(forward) {
206
- const reverse = /* @__PURE__ */ new Map();
207
- for (const [source, edges] of forward) {
208
- for (const edge of edges) {
209
- let revEdges = reverse.get(edge.target);
210
- if (!revEdges) {
211
- revEdges = [];
212
- reverse.set(edge.target, revEdges);
207
+ shutdown() {
208
+ this.shuttingDown = true;
209
+ if (this.child) {
210
+ this.child.kill("SIGTERM");
211
+ this.child = null;
212
+ }
213
+ this.rejectAllPending(new Error("Client shutdown"));
213
214
  }
214
- revEdges.push({
215
- target: source,
216
- // reverse: the "target" is the file that imports
217
- specifiers: edge.specifiers,
218
- isTypeOnly: edge.isTypeOnly,
219
- isDynamic: edge.isDynamic
220
- });
221
- }
222
- }
223
- return reverse;
224
- }
225
- async function buildGraph(projectRoot3, tsconfigPath3) {
226
- const startTime = performance.now();
227
- const resolver = createResolver(projectRoot3, tsconfigPath3);
228
- const fileList = discoverFiles(projectRoot3);
229
- log(`Discovered ${fileList.length} source files`);
230
- const { forward, parseFailures } = buildForwardEdges(fileList, resolver, projectRoot3);
231
- const reverse = buildReverseMap(forward);
232
- const files = new Set(fileList);
233
- const edgeCount = [...forward.values()].reduce((sum, edges) => sum + edges.length, 0);
234
- const elapsed = (performance.now() - startTime).toFixed(0);
235
- log(`Graph built: ${files.size} files, ${edgeCount} edges [${elapsed}ms]`);
236
- if (parseFailures.length > 0) {
237
- log(`Parse failures: ${parseFailures.length} files`);
238
- }
239
- return {
240
- graph: { forward, reverse, files },
241
- resolver
242
- };
243
- }
244
- function updateFile(graph, filePath, resolver, projectRoot3) {
245
- const oldEdges = graph.forward.get(filePath) ?? [];
246
- for (const edge of oldEdges) {
247
- const revEdges = graph.reverse.get(edge.target);
248
- if (revEdges) {
249
- const idx = revEdges.findIndex((r) => r.target === filePath);
250
- if (idx !== -1) revEdges.splice(idx, 1);
251
- if (revEdges.length === 0) graph.reverse.delete(edge.target);
252
- }
253
- }
254
- let source;
255
- try {
256
- source = fs.readFileSync(filePath, "utf-8");
257
- } catch {
258
- removeFile(graph, filePath);
259
- return;
260
- }
261
- let rawImports;
262
- try {
263
- rawImports = parseFileImports(filePath, source);
264
- } catch {
265
- log(`Parse error on update: ${filePath}`);
266
- graph.forward.set(filePath, []);
267
- return;
268
- }
269
- const fromDir = path2.dirname(filePath);
270
- const newEdges = [];
271
- for (const raw of rawImports) {
272
- const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot3);
273
- if (target) {
274
- newEdges.push({
275
- target,
276
- specifiers: raw.names,
277
- isTypeOnly: raw.isTypeOnly,
278
- isDynamic: raw.isDynamic
279
- });
280
- }
281
- }
282
- graph.forward.set(filePath, newEdges);
283
- graph.files.add(filePath);
284
- for (const edge of newEdges) {
285
- let revEdges = graph.reverse.get(edge.target);
286
- if (!revEdges) {
287
- revEdges = [];
288
- graph.reverse.set(edge.target, revEdges);
289
- }
290
- revEdges.push({
291
- target: filePath,
292
- specifiers: edge.specifiers,
293
- isTypeOnly: edge.isTypeOnly,
294
- isDynamic: edge.isDynamic
295
- });
296
- }
297
- }
298
- function removeFile(graph, filePath) {
299
- const edges = graph.forward.get(filePath) ?? [];
300
- for (const edge of edges) {
301
- const revEdges2 = graph.reverse.get(edge.target);
302
- if (revEdges2) {
303
- const idx = revEdges2.findIndex((r) => r.target === filePath);
304
- if (idx !== -1) revEdges2.splice(idx, 1);
305
- if (revEdges2.length === 0) graph.reverse.delete(edge.target);
306
- }
307
- }
308
- const revEdges = graph.reverse.get(filePath) ?? [];
309
- for (const revEdge of revEdges) {
310
- const fwdEdges = graph.forward.get(revEdge.target);
311
- if (fwdEdges) {
312
- const idx = fwdEdges.findIndex((e) => e.target === filePath);
313
- if (idx !== -1) fwdEdges.splice(idx, 1);
314
- }
315
- }
316
- graph.forward.delete(filePath);
317
- graph.reverse.delete(filePath);
318
- graph.files.delete(filePath);
319
- }
320
- function startWatcher(projectRoot3, graph, resolver, hooks) {
321
- try {
322
- const watcher = fs.watch(
323
- projectRoot3,
324
- { recursive: true },
325
- (_eventType, filename) => {
326
- if (!filename) return;
327
- const ext = path2.extname(filename);
328
- if (!TS_EXTENSIONS.has(ext)) return;
329
- const parts = filename.split(path2.sep);
330
- if (parts.some((p2) => SKIP_DIRS.has(p2))) return;
331
- if (SKIP_FILES.has(path2.basename(filename))) return;
332
- if (filename.endsWith(".d.ts") || filename.endsWith(".d.mts") || filename.endsWith(".d.cts"))
215
+ tryRestart() {
216
+ if (this.shuttingDown) return;
217
+ if (this.restartCount >= this.maxRestarts) {
218
+ log(`Max restarts (${this.maxRestarts}) reached, not restarting`);
333
219
  return;
334
- const absPath2 = path2.resolve(projectRoot3, filename);
335
- if (fs.existsSync(absPath2)) {
336
- updateFile(graph, absPath2, resolver, projectRoot3);
337
- void hooks?.onFileUpdated?.(absPath2);
338
- } else {
339
- removeFile(graph, absPath2);
340
- void hooks?.onFileDeleted?.(absPath2);
341
220
  }
221
+ this.restartCount++;
222
+ log(`Restarting tsserver (attempt ${this.restartCount})...`);
223
+ this.buffer = Buffer.alloc(0);
224
+ const filesToReopen = [...this.openFiles];
225
+ this.openFiles.clear();
226
+ this.start().then(async () => {
227
+ for (const file of filesToReopen) {
228
+ await this.ensureOpen(file).catch(() => {
229
+ });
230
+ }
231
+ });
342
232
  }
343
- );
344
- process.on("SIGINT", () => watcher.close());
345
- process.on("SIGTERM", () => watcher.close());
346
- log("File watcher started");
347
- } catch (err) {
348
- log("Failed to start file watcher:", err);
349
- }
350
- }
351
- var log, TS_EXTENSIONS, SKIP_DIRS, SKIP_FILES, SOURCE_EXTS;
352
- var init_module_graph = __esm({
353
- "module-graph.ts"() {
354
- log = (...args2) => console.error("[typegraph/graph]", ...args2);
355
- TS_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts"]);
356
- SKIP_DIRS = /* @__PURE__ */ new Set([
357
- "node_modules",
358
- "dist",
359
- "build",
360
- "out",
361
- ".wrangler",
362
- ".mf",
363
- ".git",
364
- ".next",
365
- ".turbo",
366
- "coverage"
367
- ]);
368
- SKIP_FILES = /* @__PURE__ */ new Set(["routeTree.gen.ts"]);
369
- SOURCE_EXTS = [".ts", ".tsx", ".mts", ".cts"];
370
- }
371
- });
372
-
373
- // check.ts
374
- var check_exports = {};
375
- __export(check_exports, {
376
- main: () => main
377
- });
378
- import * as fs2 from "fs";
379
- import * as path3 from "path";
380
- import { createRequire } from "module";
381
- import { spawn, spawnSync } from "child_process";
382
- function findFirstTsFile(dir) {
383
- const skipDirs = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", ".wrangler", "coverage"]);
384
- try {
385
- for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
386
- if (entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) {
387
- return path3.join(dir, entry.name);
233
+ rejectAllPending(err) {
234
+ for (const [seq, pending] of this.pending) {
235
+ clearTimeout(pending.timer);
236
+ pending.reject(err);
237
+ }
238
+ this.pending.clear();
388
239
  }
389
- }
390
- for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
391
- if (entry.isDirectory() && !skipDirs.has(entry.name) && !entry.name.startsWith(".")) {
392
- const found = findFirstTsFile(path3.join(dir, entry.name));
393
- if (found) return found;
240
+ // ─── Protocol: Parsing ──────────────────────────────────────────────────
241
+ onData(chunk) {
242
+ this.buffer = Buffer.concat([this.buffer, chunk]);
243
+ this.processBuffer();
394
244
  }
395
- }
396
- } catch {
397
- }
398
- return null;
399
- }
400
- function testTsserver(projectRoot3) {
401
- return new Promise((resolve9) => {
402
- let tsserverPath;
403
- try {
404
- const require2 = createRequire(path3.resolve(projectRoot3, "package.json"));
405
- tsserverPath = require2.resolve("typescript/lib/tsserver.js");
406
- } catch {
407
- resolve9(false);
408
- return;
409
- }
410
- const child = spawn("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
411
- cwd: projectRoot3,
412
- stdio: ["pipe", "pipe", "pipe"]
413
- });
414
- const timeout = setTimeout(() => {
415
- child.kill();
416
- resolve9(false);
417
- }, 1e4);
418
- let buffer = "";
419
- child.stdout.on("data", (chunk) => {
420
- buffer += chunk.toString();
421
- if (buffer.includes('"success":true')) {
422
- clearTimeout(timeout);
423
- child.kill();
424
- resolve9(true);
245
+ processBuffer() {
246
+ while (true) {
247
+ const headerEnd = this.buffer.indexOf("\r\n\r\n");
248
+ if (headerEnd === -1) return;
249
+ const header = this.buffer.subarray(0, headerEnd).toString("utf-8");
250
+ const match = header.match(/Content-Length:\s*(\d+)/i);
251
+ if (!match) {
252
+ this.buffer = this.buffer.subarray(headerEnd + 4);
253
+ continue;
254
+ }
255
+ const contentLength = parseInt(match[1], 10);
256
+ const bodyStart = headerEnd + 4;
257
+ if (this.buffer.length < bodyStart + contentLength) {
258
+ return;
259
+ }
260
+ const bodyBytes = this.buffer.subarray(bodyStart, bodyStart + contentLength);
261
+ this.buffer = this.buffer.subarray(bodyStart + contentLength);
262
+ try {
263
+ const message = JSON.parse(bodyBytes.toString("utf-8"));
264
+ this.onMessage(message);
265
+ } catch {
266
+ log("Failed to parse tsserver message");
267
+ }
268
+ }
425
269
  }
426
- });
427
- child.on("error", () => {
428
- clearTimeout(timeout);
429
- resolve9(false);
430
- });
431
- child.on("exit", () => {
432
- clearTimeout(timeout);
433
- });
434
- const request = JSON.stringify({
435
- seq: 1,
436
- type: "request",
437
- command: "configure",
438
- arguments: {
439
- preferences: { disableSuggestions: true }
270
+ onMessage(message) {
271
+ if (message.type === "response" && message.request_seq !== void 0) {
272
+ const pending = this.pending.get(message.request_seq);
273
+ if (pending) {
274
+ clearTimeout(pending.timer);
275
+ this.pending.delete(message.request_seq);
276
+ if (message.success) {
277
+ pending.resolve(message.body);
278
+ } else {
279
+ pending.reject(
280
+ new Error(`tsserver ${pending.command} failed: ${message.message ?? "unknown error"}`)
281
+ );
282
+ }
283
+ }
284
+ }
440
285
  }
441
- });
442
- child.stdin.write(request + "\n");
443
- });
444
- }
445
- function readProjectCodexConfig(projectRoot3) {
446
- const configPath = path3.resolve(projectRoot3, ".codex/config.toml");
447
- if (!fs2.existsSync(configPath)) return null;
448
- return fs2.readFileSync(configPath, "utf-8");
449
- }
450
- function hasCodexTypegraphRegistration(content) {
451
- return /\[mcp_servers\.typegraph\]/.test(content);
452
- }
453
- function hasCodexTsxLauncher(content) {
454
- return /command\s*=\s*"[^"]*tsx(?:\.cmd)?"/.test(content) || /args\s*=\s*\[[\s\S]*"tsx"/.test(content);
455
- }
456
- function hasCompleteCodexTypegraphRegistration(content) {
457
- return hasCodexTypegraphRegistration(content) && /command\s*=\s*"[^"]+"/.test(content) && /args\s*=\s*\[[\s\S]*\]/.test(content) && hasCodexTsxLauncher(content) && /TYPEGRAPH_PROJECT_ROOT\s*=/.test(content) && /TYPEGRAPH_TSCONFIG\s*=/.test(content);
458
- }
459
- function hasTrustedCodexProject(projectRoot3) {
460
- const home = process.env.HOME;
461
- if (!home) return null;
462
- const globalConfigPath = path3.join(home, ".codex/config.toml");
463
- if (!fs2.existsSync(globalConfigPath)) return null;
464
- const lines = fs2.readFileSync(globalConfigPath, "utf-8").split(/\r?\n/);
465
- let currentProject = null;
466
- let currentTrusted = false;
467
- const matchesTrustedProject = () => currentProject !== null && currentTrusted && (projectRoot3 === currentProject || projectRoot3.startsWith(currentProject + path3.sep));
468
- for (const line of lines) {
469
- const sectionMatch = line.match(/^\[projects\."([^"]+)"\]\s*$/);
470
- if (sectionMatch) {
471
- if (matchesTrustedProject()) return true;
472
- currentProject = path3.resolve(sectionMatch[1]);
473
- currentTrusted = false;
474
- continue;
475
- }
476
- if (line.startsWith("[")) {
477
- if (matchesTrustedProject()) return true;
478
- currentProject = null;
479
- currentTrusted = false;
480
- continue;
481
- }
482
- if (currentProject && /\btrust_level\s*=\s*"trusted"/.test(line)) {
483
- currentTrusted = true;
484
- }
485
- }
486
- return matchesTrustedProject();
487
- }
488
- function hasCompleteJsonTypegraphRegistration(config, serverName, projectRoot3) {
489
- if (typeof config !== "object" || config === null) return false;
490
- const servers = config["mcpServers"];
491
- if (typeof servers !== "object" || servers === null) return false;
492
- const entry = servers[serverName];
493
- if (typeof entry !== "object" || entry === null) return false;
494
- const record = entry;
495
- const command2 = record["command"];
496
- const args2 = record["args"];
497
- const env = record["env"];
498
- const serializedArgs = JSON.stringify(args2 ?? []);
499
- return typeof command2 === "string" && command2.length > 0 && Array.isArray(args2) && serializedArgs.includes("server.ts") && serializedArgs.includes("tsx") && typeof env === "object" && env !== null && env["TYPEGRAPH_PROJECT_ROOT"] === projectRoot3 && typeof env["TYPEGRAPH_TSCONFIG"] === "string";
500
- }
501
- function readJsonConfig(configPath) {
502
- if (!fs2.existsSync(configPath)) return null;
503
- try {
504
- return JSON.parse(fs2.readFileSync(configPath, "utf-8"));
505
- } catch {
506
- return null;
507
- }
508
- }
509
- function getAntigravityMcpConfigPaths() {
510
- const home = process.env.HOME || "";
511
- return [
512
- path3.join(home, ".gemini/antigravity/mcp_config.json"),
513
- path3.join(home, ".gemini/antigravity-cli/plugins/typegraph-mcp/mcp_config.json")
514
- ];
515
- }
516
- function findAntigravityRegistration(projectRoot3) {
517
- const home = process.env.HOME || "";
518
- for (const configPath of getAntigravityMcpConfigPaths()) {
519
- const config = readJsonConfig(configPath);
520
- if (hasCompleteJsonTypegraphRegistration(config, "typegraph-mcp", projectRoot3)) {
521
- return configPath.replace(home, "~");
522
- }
523
- }
524
- return null;
525
- }
526
- function readProjectPackageJson(projectRoot3) {
527
- const packageJsonPath = path3.resolve(projectRoot3, "package.json");
528
- if (!fs2.existsSync(packageJsonPath)) return null;
529
- try {
530
- return JSON.parse(fs2.readFileSync(packageJsonPath, "utf-8"));
531
- } catch {
532
- return null;
533
- }
534
- }
535
- function getProjectInstallCommand(projectRoot3, packageJson) {
536
- const packageManager = typeof packageJson?.["packageManager"] === "string" ? packageJson["packageManager"] : "";
537
- if (packageManager.startsWith("pnpm@") || fs2.existsSync(path3.join(projectRoot3, "pnpm-lock.yaml"))) {
538
- return "pnpm install";
539
- }
540
- if (packageManager.startsWith("yarn@") || fs2.existsSync(path3.join(projectRoot3, "yarn.lock"))) {
541
- return "yarn install";
286
+ // ─── Protocol: Sending ──────────────────────────────────────────────────
287
+ sendRequest(command2, args2) {
288
+ if (!this.child?.stdin?.writable) {
289
+ return Promise.reject(new Error("tsserver not running"));
290
+ }
291
+ const seq = ++this.seq;
292
+ const request = {
293
+ seq,
294
+ type: "request",
295
+ command: command2,
296
+ arguments: args2
297
+ };
298
+ return new Promise((resolve10, reject) => {
299
+ const timer = setTimeout(() => {
300
+ this.pending.delete(seq);
301
+ reject(new Error(`tsserver ${command2} timed out after ${REQUEST_TIMEOUT_MS}ms`));
302
+ }, REQUEST_TIMEOUT_MS);
303
+ this.pending.set(seq, { resolve: resolve10, reject, timer, command: command2 });
304
+ this.child.stdin.write(JSON.stringify(request) + "\n");
305
+ });
306
+ }
307
+ // Fire-and-forget for commands like `open` that may not send a response
308
+ sendNotification(command2, args2) {
309
+ if (!this.child?.stdin?.writable) return;
310
+ const seq = ++this.seq;
311
+ const request = { seq, type: "request", command: command2, arguments: args2 };
312
+ this.child.stdin.write(JSON.stringify(request) + "\n");
313
+ }
314
+ // ─── File Management ───────────────────────────────────────────────────
315
+ async ensureOpen(file) {
316
+ const absPath2 = this.resolvePath(file);
317
+ if (this.openFiles.has(absPath2)) return;
318
+ this.openFiles.add(absPath2);
319
+ this.sendNotification("open", { file: absPath2 });
320
+ await new Promise((r) => setTimeout(r, 50));
321
+ }
322
+ /**
323
+ * Mark tsserver's view of the disk as stale (a file changed that is NOT open
324
+ * in tsserver). tsserver's own disk watching silently decays in long-lived
325
+ * instances at monorepo scale, leaving closed-file contents frozen at project
326
+ * load time and new files unassigned (they land in single-file inferred
327
+ * projects, producing definition-only reference results). The next query
328
+ * forces a `reloadProjects`, which re-globs configs and re-reads closed files
329
+ * from disk. Open files are unaffected by design — their content is
330
+ * protocol-owned and refreshed via reloadOpenFile().
331
+ */
332
+ markProjectsDirty() {
333
+ this.projectsDirty = true;
334
+ }
335
+ refreshIfDirty() {
336
+ if (!this.projectsDirty) return;
337
+ this.projectsDirty = false;
338
+ this.sendNotification("reloadProjects");
339
+ }
340
+ async reloadOpenFile(file) {
341
+ const absPath2 = this.resolvePath(file);
342
+ if (!this.openFiles.has(absPath2)) return false;
343
+ await this.sendRequest("reload", {
344
+ file: absPath2,
345
+ tmpfile: absPath2
346
+ });
347
+ return true;
348
+ }
349
+ closeFile(file) {
350
+ const absPath2 = this.resolvePath(file);
351
+ if (!this.openFiles.delete(absPath2)) return false;
352
+ this.sendNotification("close", { file: absPath2 });
353
+ return true;
354
+ }
355
+ // ─── Public API ────────────────────────────────────────────────────────
356
+ async definition(file, line, offset) {
357
+ this.refreshIfDirty();
358
+ const absPath2 = this.resolvePath(file);
359
+ await this.ensureOpen(absPath2);
360
+ const body = await this.sendRequest("definition", {
361
+ file: absPath2,
362
+ line,
363
+ offset
364
+ });
365
+ if (!body || !Array.isArray(body)) return [];
366
+ return body.map((d) => ({
367
+ ...d,
368
+ file: this.relativePath(d.file)
369
+ }));
370
+ }
371
+ async references(file, line, offset) {
372
+ this.refreshIfDirty();
373
+ const absPath2 = this.resolvePath(file);
374
+ await this.ensureOpen(absPath2);
375
+ const body = await this.sendRequest("references", {
376
+ file: absPath2,
377
+ line,
378
+ offset
379
+ });
380
+ if (!body?.refs) return [];
381
+ return body.refs.map((r) => ({
382
+ ...r,
383
+ file: this.relativePath(r.file)
384
+ }));
385
+ }
386
+ async quickinfo(file, line, offset) {
387
+ this.refreshIfDirty();
388
+ const absPath2 = this.resolvePath(file);
389
+ await this.ensureOpen(absPath2);
390
+ try {
391
+ const body = await this.sendRequest("quickinfo", {
392
+ file: absPath2,
393
+ line,
394
+ offset
395
+ });
396
+ return body ?? null;
397
+ } catch {
398
+ return null;
399
+ }
400
+ }
401
+ async navto(searchValue, maxResults = 10, file) {
402
+ this.refreshIfDirty();
403
+ if (file) await this.ensureOpen(file);
404
+ const args2 = {
405
+ searchValue,
406
+ maxResultCount: maxResults
407
+ };
408
+ if (file) args2["file"] = this.resolvePath(file);
409
+ const body = await this.sendRequest("navto", args2);
410
+ if (!body || !Array.isArray(body)) return [];
411
+ return body.map((item) => ({
412
+ ...item,
413
+ file: this.relativePath(item.file)
414
+ }));
415
+ }
416
+ async navbar(file) {
417
+ this.refreshIfDirty();
418
+ const absPath2 = this.resolvePath(file);
419
+ await this.ensureOpen(absPath2);
420
+ const body = await this.sendRequest("navbar", {
421
+ file: absPath2
422
+ });
423
+ return body ?? [];
424
+ }
425
+ };
542
426
  }
543
- return "npm install";
427
+ });
428
+
429
+ // module-graph.ts
430
+ var module_graph_exports = {};
431
+ __export(module_graph_exports, {
432
+ buildGraph: () => buildGraph,
433
+ createResolver: () => createResolver,
434
+ discoverFiles: () => discoverFiles,
435
+ removeFile: () => removeFile,
436
+ resolveProjectImport: () => resolveProjectImport,
437
+ startWatcher: () => startWatcher,
438
+ updateFile: () => updateFile
439
+ });
440
+ import { parseSync } from "oxc-parser";
441
+ import { ResolverFactory } from "oxc-resolver";
442
+ import * as fs2 from "fs";
443
+ import * as path3 from "path";
444
+ function normalizeExcludedPaths(rootDir, excludedPaths2) {
445
+ return excludedPaths2.map(
446
+ (excludedPath) => path3.resolve(rootDir, excludedPath)
447
+ );
544
448
  }
545
- function hasDeclaredDependency(packageJson, packageName) {
546
- const depKeys = [
547
- "dependencies",
548
- "devDependencies",
549
- "peerDependencies",
550
- "optionalDependencies"
551
- ];
552
- return depKeys.some((key) => {
553
- const deps = packageJson?.[key];
554
- return typeof deps === "object" && deps !== null && packageName in deps;
555
- });
449
+ function isExcluded(filePath, excludedPaths2) {
450
+ return excludedPaths2.some(
451
+ (excludedPath) => filePath === excludedPath || filePath.startsWith(excludedPath + path3.sep)
452
+ );
556
453
  }
557
- function findLintConfigs(projectRoot3) {
558
- const configs = [];
559
- for (const fileName of ESLINT_CONFIG_NAMES) {
560
- const fullPath = path3.resolve(projectRoot3, fileName);
561
- if (fs2.existsSync(fullPath)) {
562
- configs.push({ tool: "ESLint", fileName, fullPath, propertyName: "ignores" });
454
+ function discoverFiles(rootDir, excludedPaths2 = []) {
455
+ const files = [];
456
+ const normalizedExclusions = normalizeExcludedPaths(rootDir, excludedPaths2);
457
+ function walk(dir) {
458
+ let entries;
459
+ try {
460
+ entries = fs2.readdirSync(dir, { withFileTypes: true });
461
+ } catch {
462
+ return;
563
463
  }
564
- }
565
- for (const fileName of OXLINT_CONFIG_NAMES) {
566
- const fullPath = path3.resolve(projectRoot3, fileName);
567
- if (fs2.existsSync(fullPath)) {
568
- configs.push({ tool: "Oxlint", fileName, fullPath, propertyName: "ignorePatterns" });
464
+ for (const entry of entries) {
465
+ if (entry.isDirectory()) {
466
+ if (SKIP_DIRS.has(entry.name)) continue;
467
+ if (entry.name.startsWith(".") && dir !== rootDir) continue;
468
+ const childDir = path3.join(dir, entry.name);
469
+ if (isExcluded(childDir, normalizedExclusions)) continue;
470
+ walk(childDir);
471
+ } else if (entry.isFile()) {
472
+ const name = entry.name;
473
+ if (SKIP_FILES.has(name)) continue;
474
+ if (name.endsWith(".d.ts") || name.endsWith(".d.mts") || name.endsWith(".d.cts")) continue;
475
+ const ext = path3.extname(name);
476
+ if (TS_EXTENSIONS.has(ext)) {
477
+ files.push(path3.join(dir, name));
478
+ }
479
+ }
569
480
  }
570
481
  }
571
- return configs;
482
+ walk(rootDir);
483
+ return files;
572
484
  }
573
- async function main(configOverride) {
574
- const { projectRoot: projectRoot3, tsconfigPath: tsconfigPath3, toolDir, toolIsEmbedded, toolRelPath } = configOverride ?? resolveConfig(import.meta.dirname);
575
- let passed = 0;
576
- let failed = 0;
577
- let warned = 0;
578
- const projectPackageJson = readProjectPackageJson(projectRoot3);
579
- const installCommand = getProjectInstallCommand(projectRoot3, projectPackageJson);
580
- function pass(msg) {
581
- console.log(` \u2713 ${msg}`);
582
- passed++;
485
+ function parseFileImports(filePath, source) {
486
+ const result = parseSync(filePath, source);
487
+ const imports = [];
488
+ for (const imp of result.module.staticImports) {
489
+ const specifier = imp.moduleRequest.value;
490
+ const names = [];
491
+ let allTypeOnly = true;
492
+ for (const entry of imp.entries) {
493
+ const kind = entry.importName.kind;
494
+ const name = kind === "Default" ? "default" : kind === "All" || kind === "AllButDefault" || kind === "NamespaceObject" ? "*" : entry.importName.name ?? entry.localName.value;
495
+ names.push(name);
496
+ if (!entry.isType) allTypeOnly = false;
497
+ }
498
+ if (names.length === 0) {
499
+ imports.push({ specifier, names: ["*"], isTypeOnly: false, isDynamic: false });
500
+ } else {
501
+ imports.push({ specifier, names, isTypeOnly: allTypeOnly, isDynamic: false });
502
+ }
583
503
  }
584
- function fail(msg, fix) {
585
- console.log(` \u2717 ${msg}`);
586
- console.log(` Fix: ${fix}`);
587
- failed++;
504
+ for (const exp of result.module.staticExports) {
505
+ for (const entry of exp.entries) {
506
+ const moduleRequest = entry.moduleRequest;
507
+ if (!moduleRequest) continue;
508
+ const specifier = moduleRequest.value;
509
+ const entryKind = entry.importName.kind;
510
+ const name = entryKind === "AllButDefault" || entryKind === "All" || entryKind === "NamespaceObject" ? "*" : entry.importName.name ?? "*";
511
+ const existing = imports.find((i) => i.specifier === specifier && !i.isDynamic);
512
+ if (existing) {
513
+ if (!existing.names.includes(name)) existing.names.push(name);
514
+ } else {
515
+ imports.push({ specifier, names: [name], isTypeOnly: false, isDynamic: false });
516
+ }
517
+ }
588
518
  }
589
- function warn(msg, note) {
590
- console.log(` ! ${msg}`);
591
- console.log(` ${note}`);
592
- warned++;
519
+ for (const di of result.module.dynamicImports) {
520
+ if (di.moduleRequest) {
521
+ const sliced = source.slice(di.moduleRequest.start, di.moduleRequest.end);
522
+ if (sliced.startsWith("'") || sliced.startsWith('"')) {
523
+ const specifier = sliced.slice(1, -1);
524
+ imports.push({ specifier, names: ["*"], isTypeOnly: false, isDynamic: true });
525
+ }
526
+ }
593
527
  }
594
- function skip(msg) {
595
- console.log(` - ${msg} (skipped)`);
528
+ return imports;
529
+ }
530
+ function distToSource(resolvedPath, projectRoot3) {
531
+ if (!resolvedPath.startsWith(projectRoot3)) return resolvedPath;
532
+ const rel2 = path3.relative(projectRoot3, resolvedPath);
533
+ const distIdx = rel2.indexOf("dist" + path3.sep);
534
+ if (distIdx === -1) return resolvedPath;
535
+ const prefix = rel2.slice(0, distIdx);
536
+ const afterDist = rel2.slice(distIdx + 5);
537
+ const withoutExt = afterDist.replace(/\.(m?j|c)s$/, "");
538
+ for (const ext of SOURCE_EXTS) {
539
+ const candidate = path3.resolve(projectRoot3, prefix, "src", withoutExt + ext);
540
+ if (fs2.existsSync(candidate)) return candidate;
596
541
  }
597
- console.log("");
598
- console.log("typegraph-mcp Health Check");
599
- console.log("=======================");
600
- console.log(`Project root: ${projectRoot3}`);
601
- console.log("");
602
- const nodeVersion = process.version;
603
- const nodeMajor = parseInt(nodeVersion.slice(1).split(".")[0], 10);
604
- if (nodeMajor >= 22) {
605
- pass(`Node.js ${nodeVersion} (>= 22 required)`);
606
- } else {
607
- fail(`Node.js ${nodeVersion} is too old`, "Upgrade Node.js to >= 22");
542
+ for (const ext of SOURCE_EXTS) {
543
+ const candidate = path3.resolve(projectRoot3, prefix, withoutExt + ext);
544
+ if (fs2.existsSync(candidate)) return candidate;
608
545
  }
609
- const tsxInRoot = fs2.existsSync(path3.join(projectRoot3, "node_modules/.bin/tsx"));
610
- const tsxInTool = fs2.existsSync(path3.join(toolDir, "node_modules/.bin/tsx"));
611
- if (tsxInRoot || tsxInTool) {
612
- pass(`tsx available (in ${tsxInRoot ? "project" : "tool"} node_modules)`);
613
- } else {
614
- pass("tsx available (via npx/global)");
546
+ if (withoutExt.endsWith("/index")) {
547
+ const dirPath = withoutExt.slice(0, -6);
548
+ for (const ext of SOURCE_EXTS) {
549
+ const candidate = path3.resolve(projectRoot3, prefix, "src", dirPath + ext);
550
+ if (fs2.existsSync(candidate)) return candidate;
551
+ }
615
552
  }
616
- let tsVersion = null;
553
+ return resolvedPath;
554
+ }
555
+ function resolveProjectImport(resolver, fromDir, specifier, projectRoot3) {
617
556
  try {
618
- const require2 = createRequire(path3.resolve(projectRoot3, "package.json"));
619
- const tsserverPath = require2.resolve("typescript/lib/tsserver.js");
620
- const tsPkgPath = path3.resolve(path3.dirname(tsserverPath), "..", "package.json");
621
- const tsPkg = JSON.parse(fs2.readFileSync(tsPkgPath, "utf-8"));
622
- tsVersion = tsPkg.version;
623
- pass(`TypeScript found (v${tsVersion})`);
557
+ const result = resolver.sync(fromDir, specifier);
558
+ if (result.path && !result.path.includes("node_modules")) {
559
+ const mapped = distToSource(result.path, projectRoot3);
560
+ const ext = path3.extname(mapped);
561
+ if (!TS_EXTENSIONS.has(ext)) return null;
562
+ if (SKIP_FILES.has(path3.basename(mapped))) return null;
563
+ return mapped;
564
+ }
624
565
  } catch {
625
- const hasDeclaredTs = hasDeclaredDependency(projectPackageJson, "typescript");
626
- fail(
627
- hasDeclaredTs ? "TypeScript is declared but not installed in project" : "TypeScript not found in project",
628
- hasDeclaredTs ? `Run \`${installCommand}\` to install project dependencies` : `Add \`typescript\` to devDependencies and run \`${installCommand}\``
629
- );
630
- }
631
- const tsconfigAbs = path3.resolve(projectRoot3, tsconfigPath3);
632
- if (fs2.existsSync(tsconfigAbs)) {
633
- pass(`tsconfig.json exists at ${tsconfigPath3}`);
634
- } else {
635
- fail(`tsconfig.json not found at ${tsconfigPath3}`, `Create a tsconfig.json at ${tsconfigPath3}`);
636
566
  }
637
- const pluginMcpPath = path3.join(toolDir, ".mcp.json");
638
- const hasPluginMcp = fs2.existsSync(pluginMcpPath) && fs2.existsSync(path3.join(toolDir, ".claude-plugin/plugin.json"));
639
- const projectCodexConfig = readProjectCodexConfig(projectRoot3);
640
- const hasProjectCodexRegistration = projectCodexConfig !== null && hasCompleteCodexTypegraphRegistration(projectCodexConfig);
641
- const codexGet = spawnSync("codex", ["mcp", "get", "typegraph"], {
642
- stdio: "pipe",
643
- encoding: "utf-8"
567
+ return null;
568
+ }
569
+ function createResolver(projectRoot3, tsconfigPath3) {
570
+ const configFile = path3.resolve(projectRoot3, tsconfigPath3);
571
+ return new ResolverFactory({
572
+ ...fs2.existsSync(configFile) ? { tsconfig: { configFile, references: "auto" } } : {},
573
+ extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
574
+ extensionAlias: {
575
+ ".js": [".ts", ".tsx", ".js"],
576
+ ".jsx": [".tsx", ".jsx"],
577
+ ".mjs": [".mts", ".mjs"],
578
+ ".cjs": [".cts", ".cjs"]
579
+ },
580
+ conditionNames: ["import", "require"],
581
+ mainFields: ["module", "main"]
644
582
  });
645
- const hasGlobalCodexRegistration = codexGet.status === 0;
646
- const antigravityRegistrationPath = findAntigravityRegistration(projectRoot3);
647
- if (process.env.CLAUDE_PLUGIN_ROOT) {
648
- pass("MCP registered via plugin (CLAUDE_PLUGIN_ROOT set)");
649
- } else if (hasPluginMcp) {
650
- pass("MCP registered via plugin (.mcp.json + .claude-plugin/ present)");
651
- } else if (projectCodexConfig !== null) {
652
- const codexConfigPath = path3.resolve(projectRoot3, ".codex/config.toml");
653
- const hasSection = hasCodexTypegraphRegistration(projectCodexConfig);
654
- const hasCommand = /command\s*=\s*"[^"]+"/.test(projectCodexConfig);
655
- const hasArgs = /args\s*=\s*\[[\s\S]*\]/.test(projectCodexConfig);
656
- const hasTsxLauncher = hasCodexTsxLauncher(projectCodexConfig);
657
- const hasEnvRoot = /TYPEGRAPH_PROJECT_ROOT\s*=/.test(projectCodexConfig);
658
- const hasEnvTsconfig = /TYPEGRAPH_TSCONFIG\s*=/.test(projectCodexConfig);
659
- if (hasProjectCodexRegistration) {
660
- pass("MCP registered in project .codex/config.toml");
661
- const trusted = hasTrustedCodexProject(projectRoot3);
662
- if (trusted === false) {
663
- warn(
664
- "Project Codex config may be ignored",
665
- 'Add the project (or a parent directory) to ~/.codex/config.toml with trust_level = "trusted"'
666
- );
667
- }
668
- } else {
669
- const issues = [];
670
- if (!hasSection) issues.push("[mcp_servers.typegraph] section is missing");
671
- if (!hasCommand) issues.push("command is missing");
672
- if (!hasArgs) issues.push("args are missing");
673
- if (!hasTsxLauncher) issues.push("command should point to tsx or args should include 'tsx'");
674
- if (!hasEnvRoot) issues.push("TYPEGRAPH_PROJECT_ROOT is missing");
675
- if (!hasEnvTsconfig) issues.push("TYPEGRAPH_TSCONFIG is missing");
676
- fail(
677
- `Project .codex/config.toml registration incomplete: ${issues.join(", ")}`,
678
- `Update ${codexConfigPath} with a complete [mcp_servers.typegraph] entry`
679
- );
583
+ }
584
+ function buildForwardEdges(files, resolver, projectRoot3) {
585
+ const forward = /* @__PURE__ */ new Map();
586
+ const parseFailures = [];
587
+ for (const filePath of files) {
588
+ let source;
589
+ try {
590
+ source = fs2.readFileSync(filePath, "utf-8");
591
+ } catch {
592
+ continue;
680
593
  }
681
- } else if (hasGlobalCodexRegistration) {
682
- pass("MCP registered in global Codex CLI config");
683
- } else if (antigravityRegistrationPath !== null) {
684
- pass(`MCP registered in Antigravity config (${antigravityRegistrationPath})`);
685
- } else {
686
- const codexConfigPath = path3.resolve(projectRoot3, ".codex/config.toml");
687
- const mcpJsonPath = path3.resolve(projectRoot3, ".claude/mcp.json");
688
- if (fs2.existsSync(mcpJsonPath)) {
689
- try {
690
- const mcpJson = JSON.parse(fs2.readFileSync(mcpJsonPath, "utf-8"));
691
- const tsNav = mcpJson?.mcpServers?.["typegraph"];
692
- if (tsNav) {
693
- const hasCommand = tsNav.command === "npx";
694
- const hasArgs = Array.isArray(tsNav.args) && tsNav.args.includes("tsx");
695
- const hasEnv = tsNav.env?.["TYPEGRAPH_PROJECT_ROOT"] && tsNav.env?.["TYPEGRAPH_TSCONFIG"];
696
- if (hasCommand && hasArgs && hasEnv) {
697
- pass("MCP registered in .claude/mcp.json");
698
- } else {
699
- const issues = [];
700
- if (!hasCommand) issues.push("command should be 'npx'");
701
- if (!hasArgs) issues.push("args should include 'tsx'");
702
- if (!hasEnv) issues.push("env should set TYPEGRAPH_PROJECT_ROOT and TYPEGRAPH_TSCONFIG");
703
- fail(
704
- `MCP registration incomplete: ${issues.join(", ")}`,
705
- "See README for correct .claude/mcp.json format"
706
- );
707
- }
708
- } else {
709
- const serverPath = toolIsEmbedded ? `./${toolRelPath}/server.ts` : path3.resolve(toolDir, "server.ts");
710
- fail(
711
- "MCP entry 'typegraph' not found in .claude/mcp.json",
712
- `Add to .claude/mcp.json:
713
- {
714
- "mcpServers": {
715
- "typegraph": {
716
- "command": "npx",
717
- "args": ["tsx", "${serverPath}"],
718
- "env": { "TYPEGRAPH_PROJECT_ROOT": ".", "TYPEGRAPH_TSCONFIG": "./tsconfig.json" }
719
- }
720
- }
721
- }`
722
- );
723
- }
724
- } catch (err) {
725
- fail(
726
- "Failed to parse .claude/mcp.json",
727
- `Check JSON syntax: ${err instanceof Error ? err.message : String(err)}`
728
- );
594
+ let rawImports;
595
+ try {
596
+ rawImports = parseFileImports(filePath, source);
597
+ } catch (err) {
598
+ parseFailures.push(filePath);
599
+ continue;
600
+ }
601
+ const edges = [];
602
+ const fromDir = path3.dirname(filePath);
603
+ for (const raw of rawImports) {
604
+ const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot3);
605
+ if (target) {
606
+ edges.push({
607
+ target,
608
+ specifiers: raw.names,
609
+ isTypeOnly: raw.isTypeOnly,
610
+ isDynamic: raw.isDynamic
611
+ });
729
612
  }
730
- } else {
731
- fail(
732
- "No MCP registration found",
733
- `Create ${codexConfigPath} with [mcp_servers.typegraph] or create .claude/mcp.json with typegraph server registration`
734
- );
735
613
  }
614
+ forward.set(filePath, edges);
736
615
  }
737
- const toolNodeModules = path3.join(toolDir, "node_modules");
738
- if (fs2.existsSync(toolNodeModules)) {
739
- const requiredPkgs = ["@modelcontextprotocol/sdk", "oxc-parser", "oxc-resolver", "zod"];
740
- const missing = requiredPkgs.filter(
741
- (pkg) => !fs2.existsSync(path3.join(toolNodeModules, ...pkg.split("/")))
742
- );
743
- if (missing.length === 0) {
744
- pass(`Dependencies installed (${requiredPkgs.length} packages)`);
745
- } else {
746
- fail(`Missing packages: ${missing.join(", ")}`, `Run \`cd ${toolRelPath} && npm install\``);
616
+ return { forward, parseFailures };
617
+ }
618
+ function buildReverseMap(forward) {
619
+ const reverse = /* @__PURE__ */ new Map();
620
+ for (const [source, edges] of forward) {
621
+ for (const edge of edges) {
622
+ let revEdges = reverse.get(edge.target);
623
+ if (!revEdges) {
624
+ revEdges = [];
625
+ reverse.set(edge.target, revEdges);
626
+ }
627
+ revEdges.push({
628
+ target: source,
629
+ // reverse: the "target" is the file that imports
630
+ specifiers: edge.specifiers,
631
+ isTypeOnly: edge.isTypeOnly,
632
+ isDynamic: edge.isDynamic
633
+ });
747
634
  }
748
- } else {
749
- fail("typegraph-mcp dependencies not installed", `Run \`cd ${toolRelPath} && npm install\``);
750
635
  }
751
- try {
752
- const oxcParserReq = createRequire(path3.join(toolDir, "package.json"));
753
- const { parseSync: parseSync3 } = await import(oxcParserReq.resolve("oxc-parser"));
754
- const result = parseSync3("test.ts", 'import { x } from "./y";');
755
- if (result.module?.staticImports?.length === 1) {
756
- pass("oxc-parser working");
757
- } else {
758
- fail(
759
- "oxc-parser parseSync returned unexpected result",
760
- `Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && npm install\``
761
- );
636
+ return reverse;
637
+ }
638
+ async function buildGraph(projectRoot3, tsconfigPath3, excludedPaths2 = []) {
639
+ const startTime = performance.now();
640
+ const resolver = createResolver(projectRoot3, tsconfigPath3);
641
+ const fileList = discoverFiles(projectRoot3, excludedPaths2);
642
+ log2(`Discovered ${fileList.length} source files`);
643
+ const { forward, parseFailures } = buildForwardEdges(fileList, resolver, projectRoot3);
644
+ const reverse = buildReverseMap(forward);
645
+ const files = new Set(fileList);
646
+ const edgeCount = [...forward.values()].reduce((sum, edges) => sum + edges.length, 0);
647
+ const elapsed = (performance.now() - startTime).toFixed(0);
648
+ log2(`Graph built: ${files.size} files, ${edgeCount} edges [${elapsed}ms]`);
649
+ if (parseFailures.length > 0) {
650
+ log2(`Parse failures: ${parseFailures.length} files`);
651
+ }
652
+ return {
653
+ graph: { forward, reverse, files },
654
+ resolver
655
+ };
656
+ }
657
+ function updateFile(graph, filePath, resolver, projectRoot3) {
658
+ const oldEdges = graph.forward.get(filePath) ?? [];
659
+ for (const edge of oldEdges) {
660
+ const revEdges = graph.reverse.get(edge.target);
661
+ if (revEdges) {
662
+ const idx = revEdges.findIndex((r) => r.target === filePath);
663
+ if (idx !== -1) revEdges.splice(idx, 1);
664
+ if (revEdges.length === 0) graph.reverse.delete(edge.target);
762
665
  }
763
- } catch (err) {
764
- fail(
765
- `oxc-parser failed: ${err instanceof Error ? err.message : String(err)}`,
766
- `Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && npm install\``
767
- );
768
666
  }
667
+ let source;
769
668
  try {
770
- const oxcResolverReq = createRequire(path3.join(toolDir, "package.json"));
771
- const { ResolverFactory: ResolverFactory2 } = await import(oxcResolverReq.resolve("oxc-resolver"));
772
- const resolver = new ResolverFactory2({
773
- tsconfig: { configFile: tsconfigAbs, references: "auto" },
774
- extensions: [".ts", ".tsx", ".js"],
775
- extensionAlias: { ".js": [".ts", ".tsx", ".js"] }
776
- });
777
- let resolveOk = false;
778
- const testFile = findFirstTsFile(projectRoot3);
779
- if (testFile) {
780
- const dir = path3.dirname(testFile);
781
- const base = "./" + path3.basename(testFile);
782
- const result = resolver.sync(dir, base);
783
- resolveOk = !!result.path;
784
- }
785
- if (resolveOk) {
786
- pass("oxc-resolver working");
787
- } else {
788
- warn(
789
- "oxc-resolver loaded but couldn't resolve a test import",
790
- "Check tsconfig.json is valid and has correct `references`"
791
- );
669
+ source = fs2.readFileSync(filePath, "utf-8");
670
+ } catch {
671
+ removeFile(graph, filePath);
672
+ return;
673
+ }
674
+ let rawImports;
675
+ try {
676
+ rawImports = parseFileImports(filePath, source);
677
+ } catch {
678
+ log2(`Parse error on update: ${filePath}`);
679
+ graph.forward.set(filePath, []);
680
+ return;
681
+ }
682
+ const fromDir = path3.dirname(filePath);
683
+ const newEdges = [];
684
+ for (const raw of rawImports) {
685
+ const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot3);
686
+ if (target) {
687
+ newEdges.push({
688
+ target,
689
+ specifiers: raw.names,
690
+ isTypeOnly: raw.isTypeOnly,
691
+ isDynamic: raw.isDynamic
692
+ });
792
693
  }
793
- } catch (err) {
794
- fail(
795
- `oxc-resolver failed: ${err instanceof Error ? err.message : String(err)}`,
796
- `Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && npm install\``
797
- );
798
694
  }
799
- if (tsVersion) {
800
- try {
801
- const ok = await testTsserver(projectRoot3);
802
- if (ok) {
803
- pass("tsserver responds to configure");
804
- } else {
805
- fail(
806
- "tsserver did not respond",
807
- "Verify `typescript` is installed and tsconfig.json is valid"
808
- );
809
- }
810
- } catch (err) {
811
- fail(
812
- `tsserver failed to start: ${err instanceof Error ? err.message : String(err)}`,
813
- "Verify `typescript` is installed and tsconfig.json is valid"
814
- );
695
+ graph.forward.set(filePath, newEdges);
696
+ graph.files.add(filePath);
697
+ for (const edge of newEdges) {
698
+ let revEdges = graph.reverse.get(edge.target);
699
+ if (!revEdges) {
700
+ revEdges = [];
701
+ graph.reverse.set(edge.target, revEdges);
815
702
  }
816
- } else {
817
- skip("tsserver test (TypeScript not found)");
703
+ revEdges.push({
704
+ target: filePath,
705
+ specifiers: edge.specifiers,
706
+ isTypeOnly: edge.isTypeOnly,
707
+ isDynamic: edge.isDynamic
708
+ });
818
709
  }
819
- try {
820
- let buildGraph2;
821
- try {
822
- ({ buildGraph: buildGraph2 } = await import(path3.resolve(toolDir, "module-graph.js")));
823
- } catch {
824
- ({ buildGraph: buildGraph2 } = await Promise.resolve().then(() => (init_module_graph(), module_graph_exports)));
710
+ }
711
+ function removeFile(graph, filePath) {
712
+ const edges = graph.forward.get(filePath) ?? [];
713
+ for (const edge of edges) {
714
+ const revEdges2 = graph.reverse.get(edge.target);
715
+ if (revEdges2) {
716
+ const idx = revEdges2.findIndex((r) => r.target === filePath);
717
+ if (idx !== -1) revEdges2.splice(idx, 1);
718
+ if (revEdges2.length === 0) graph.reverse.delete(edge.target);
825
719
  }
826
- const start2 = performance.now();
827
- const { graph } = await buildGraph2(projectRoot3, tsconfigPath3);
828
- const elapsed = (performance.now() - start2).toFixed(0);
829
- const edgeCount = [...graph.forward.values()].reduce(
830
- (s, e) => s + e.length,
831
- 0
832
- );
833
- if (graph.files.size > 0 && edgeCount > 0) {
834
- pass(`Module graph: ${graph.files.size} files, ${edgeCount} edges [${elapsed}ms]`);
835
- } else if (graph.files.size > 0) {
836
- warn(
837
- `Module graph: ${graph.files.size} files but 0 edges`,
838
- "Files found but no internal imports resolved. Check tsconfig references."
839
- );
840
- } else {
841
- fail(
842
- "Module graph: 0 files discovered",
843
- "Check tsconfig.json includes source files and project root is correct"
844
- );
720
+ }
721
+ const revEdges = graph.reverse.get(filePath) ?? [];
722
+ for (const revEdge of revEdges) {
723
+ const fwdEdges = graph.forward.get(revEdge.target);
724
+ if (fwdEdges) {
725
+ const idx = fwdEdges.findIndex((e) => e.target === filePath);
726
+ if (idx !== -1) fwdEdges.splice(idx, 1);
845
727
  }
846
- } catch (err) {
847
- fail(
848
- `Module graph build failed: ${err instanceof Error ? err.message : String(err)}`,
849
- "Check that oxc-parser and oxc-resolver are installed correctly"
850
- );
851
728
  }
852
- if (toolIsEmbedded) {
853
- const lintConfigs = findLintConfigs(projectRoot3);
854
- if (lintConfigs.length > 0) {
855
- const parentDir = path3.basename(path3.dirname(toolDir));
856
- const parentIgnorePattern = new RegExp(`["']${parentDir}\\/\\*\\*["']`);
857
- for (const config of lintConfigs) {
858
- const content = fs2.readFileSync(config.fullPath, "utf-8");
859
- const hasParentIgnore = parentIgnorePattern.test(content);
860
- if (hasParentIgnore) {
861
- pass(`${config.tool} ignores ${parentDir}/ (${config.fileName})`);
729
+ graph.forward.delete(filePath);
730
+ graph.reverse.delete(filePath);
731
+ graph.files.delete(filePath);
732
+ }
733
+ function startWatcher(projectRoot3, graph, resolver, hooks, excludedPaths2 = []) {
734
+ try {
735
+ const normalizedExclusions = normalizeExcludedPaths(projectRoot3, excludedPaths2);
736
+ const watcher = fs2.watch(
737
+ projectRoot3,
738
+ { recursive: true },
739
+ (_eventType, filename) => {
740
+ if (!filename) return;
741
+ const ext = path3.extname(filename);
742
+ if (!TS_EXTENSIONS.has(ext)) return;
743
+ const parts = filename.split(path3.sep);
744
+ if (parts.some((p2) => SKIP_DIRS.has(p2))) return;
745
+ if (SKIP_FILES.has(path3.basename(filename))) return;
746
+ if (filename.endsWith(".d.ts") || filename.endsWith(".d.mts") || filename.endsWith(".d.cts"))
747
+ return;
748
+ const absPath2 = path3.resolve(projectRoot3, filename);
749
+ if (isExcluded(absPath2, normalizedExclusions)) return;
750
+ if (fs2.existsSync(absPath2)) {
751
+ updateFile(graph, absPath2, resolver, projectRoot3);
752
+ void hooks?.onFileUpdated?.(absPath2);
862
753
  } else {
863
- fail(
864
- `${config.tool} missing ignore: "${parentDir}/**" (${config.fileName})`,
865
- `Add to ${config.propertyName} in ${config.fileName}:
866
- "${parentDir}/**",`
867
- );
754
+ removeFile(graph, absPath2);
755
+ void hooks?.onFileDeleted?.(absPath2);
868
756
  }
869
757
  }
870
- } else {
871
- skip("Lint config check (no ESLint or Oxlint config found)");
872
- }
873
- } else {
874
- skip("Lint config check (typegraph-mcp is external to project)");
875
- }
876
- const gitignorePath = path3.resolve(projectRoot3, ".gitignore");
877
- if (fs2.existsSync(gitignorePath)) {
878
- const gitignoreContent = fs2.readFileSync(gitignorePath, "utf-8");
879
- const lines = gitignoreContent.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
880
- const ignoresClaude = lines.some(
881
- (l) => l === ".claude/" || l === ".claude" || l === "/.claude"
882
- );
883
- const parentDir = toolIsEmbedded ? path3.basename(path3.dirname(toolDir)) : null;
884
- const ignoresParent = parentDir && lines.some((l) => l === `${parentDir}/` || l === parentDir || l === `/${parentDir}`);
885
- if (!ignoresParent && !ignoresClaude) {
886
- pass(".gitignore does not exclude .claude/" + (parentDir ? ` or ${parentDir}/` : ""));
887
- } else {
888
- const excluded = [];
889
- if (ignoresParent) excluded.push(`${parentDir}/`);
890
- if (ignoresClaude) excluded.push(".claude/");
891
- warn(
892
- `.gitignore excludes ${excluded.join(" and ")}`,
893
- "Remove these entries so MCP config and tool source are tracked in git"
894
- );
895
- }
896
- } else {
897
- skip(".gitignore check (no .gitignore)");
898
- }
899
- console.log("");
900
- const total = passed + failed;
901
- if (failed === 0) {
902
- console.log(
903
- `${passed}/${total} checks passed` + (warned > 0 ? ` (${warned} warning${warned > 1 ? "s" : ""})` : "") + " -- typegraph-mcp is ready"
904
- );
905
- } else {
906
- console.log(
907
- `${passed}/${total} checks passed, ${failed} failed` + (warned > 0 ? `, ${warned} warning${warned > 1 ? "s" : ""}` : "") + " -- fix issues above"
908
758
  );
759
+ process.on("SIGINT", () => watcher.close());
760
+ process.on("SIGTERM", () => watcher.close());
761
+ log2("File watcher started");
762
+ } catch (err) {
763
+ log2("Failed to start file watcher:", err);
909
764
  }
910
- console.log("");
911
- return { passed, failed, warned };
912
765
  }
913
- var ESLINT_CONFIG_NAMES, OXLINT_CONFIG_NAMES;
914
- var init_check = __esm({
915
- "check.ts"() {
916
- init_config();
917
- ESLINT_CONFIG_NAMES = [
918
- "eslint.config.mjs",
919
- "eslint.config.js",
920
- "eslint.config.ts",
921
- "eslint.config.cjs"
922
- ];
923
- OXLINT_CONFIG_NAMES = [
924
- ".oxlintrc.json",
925
- "oxlint.config.ts",
926
- "oxlint.config.js",
927
- "oxlint.config.mjs",
928
- "oxlint.config.cjs"
929
- ];
766
+ var log2, TS_EXTENSIONS, SKIP_DIRS, SKIP_FILES, SOURCE_EXTS;
767
+ var init_module_graph = __esm({
768
+ "module-graph.ts"() {
769
+ log2 = (...args2) => console.error("[typegraph/graph]", ...args2);
770
+ TS_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts"]);
771
+ SKIP_DIRS = /* @__PURE__ */ new Set([
772
+ "node_modules",
773
+ "dist",
774
+ "build",
775
+ "out",
776
+ ".wrangler",
777
+ ".mf",
778
+ ".git",
779
+ ".next",
780
+ ".turbo",
781
+ "coverage"
782
+ ]);
783
+ SKIP_FILES = /* @__PURE__ */ new Set(["routeTree.gen.ts"]);
784
+ SOURCE_EXTS = [".ts", ".tsx", ".mts", ".cts"];
930
785
  }
931
786
  });
932
787
 
933
- // tsserver-client.ts
934
- import { spawn as spawn2 } from "child_process";
935
- import * as path4 from "path";
788
+ // check.ts
789
+ var check_exports = {};
790
+ __export(check_exports, {
791
+ main: () => main
792
+ });
936
793
  import * as fs3 from "fs";
794
+ import * as path4 from "path";
937
795
  import { createRequire as createRequire2 } from "module";
938
- var log2, REQUEST_TIMEOUT_MS, TsServerClient;
939
- var init_tsserver_client = __esm({
940
- "tsserver-client.ts"() {
941
- log2 = (...args2) => console.error("[typegraph/tsserver]", ...args2);
942
- REQUEST_TIMEOUT_MS = 1e4;
943
- TsServerClient = class {
944
- constructor(projectRoot3, tsconfigPath3 = "./tsconfig.json") {
945
- this.projectRoot = projectRoot3;
946
- this.tsconfigPath = tsconfigPath3;
947
- }
948
- child = null;
949
- seq = 0;
950
- pending = /* @__PURE__ */ new Map();
951
- openFiles = /* @__PURE__ */ new Set();
952
- buffer = Buffer.alloc(0);
953
- ready = false;
954
- shuttingDown = false;
955
- restartCount = 0;
956
- maxRestarts = 3;
957
- projectsDirty = false;
958
- // ─── Path Resolution ────────────────────────────────────────────────────
959
- resolvePath(file) {
960
- return path4.isAbsolute(file) ? file : path4.resolve(this.projectRoot, file);
961
- }
962
- relativePath(file) {
963
- return path4.relative(this.projectRoot, file);
964
- }
965
- /** Read a line from a file (1-based line number). Returns trimmed content. */
966
- readLine(file, line) {
967
- try {
968
- const absPath2 = this.resolvePath(file);
969
- const content = fs3.readFileSync(absPath2, "utf-8");
970
- const lines = content.split("\n");
971
- return lines[line - 1]?.trim() ?? "";
972
- } catch {
973
- return "";
974
- }
975
- }
976
- // ─── Lifecycle ──────────────────────────────────────────────────────────
977
- async start() {
978
- if (this.child) return;
979
- const require2 = createRequire2(path4.resolve(this.projectRoot, "package.json"));
980
- const tsserverPath = require2.resolve("typescript/lib/tsserver.js");
981
- log2(`Spawning tsserver: ${tsserverPath}`);
982
- log2(`Project root: ${this.projectRoot}`);
983
- log2(`tsconfig: ${this.tsconfigPath}`);
984
- this.child = spawn2("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
985
- cwd: this.projectRoot,
986
- stdio: ["pipe", "pipe", "pipe"],
987
- env: { ...process.env, TSS_LOG: void 0 }
988
- });
989
- this.child.stdout.on("data", (chunk) => this.onData(chunk));
990
- this.child.stderr.on("data", (chunk) => {
991
- const text = chunk.toString().trim();
992
- if (text) log2(`[stderr] ${text}`);
993
- });
994
- this.child.on("close", (code) => {
995
- log2(`tsserver exited with code ${code}`);
996
- this.child = null;
997
- this.rejectAllPending(new Error(`tsserver exited with code ${code}`));
998
- this.tryRestart();
999
- });
1000
- this.child.on("error", (err) => {
1001
- log2(`tsserver error: ${err.message}`);
1002
- this.rejectAllPending(err);
1003
- });
1004
- await this.sendRequest("configure", {
1005
- preferences: {
1006
- disableSuggestions: true
1007
- }
1008
- });
1009
- const warmStart = performance.now();
1010
- const tsconfigAbs = this.resolvePath(this.tsconfigPath);
1011
- if (fs3.existsSync(tsconfigAbs)) {
1012
- await this.sendRequest("compilerOptionsForInferredProjects", {
1013
- options: { allowJs: true, checkJs: false }
1014
- });
1015
- }
1016
- this.ready = true;
1017
- log2(`Ready [${(performance.now() - warmStart).toFixed(0)}ms configure]`);
1018
- }
1019
- shutdown() {
1020
- this.shuttingDown = true;
1021
- if (this.child) {
1022
- this.child.kill("SIGTERM");
1023
- this.child = null;
1024
- }
1025
- this.rejectAllPending(new Error("Client shutdown"));
796
+ import { spawn as spawn2, spawnSync } from "child_process";
797
+ function findFirstTsFile(dir, excludedPaths2 = []) {
798
+ const skipDirs = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", ".wrangler", "coverage"]);
799
+ try {
800
+ for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
801
+ if (entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) {
802
+ return path4.join(dir, entry.name);
1026
803
  }
1027
- tryRestart() {
1028
- if (this.shuttingDown) return;
1029
- if (this.restartCount >= this.maxRestarts) {
1030
- log2(`Max restarts (${this.maxRestarts}) reached, not restarting`);
1031
- return;
1032
- }
1033
- this.restartCount++;
1034
- log2(`Restarting tsserver (attempt ${this.restartCount})...`);
1035
- this.buffer = Buffer.alloc(0);
1036
- const filesToReopen = [...this.openFiles];
1037
- this.openFiles.clear();
1038
- this.start().then(async () => {
1039
- for (const file of filesToReopen) {
1040
- await this.ensureOpen(file).catch(() => {
1041
- });
1042
- }
1043
- });
804
+ }
805
+ for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
806
+ if (entry.isDirectory() && !skipDirs.has(entry.name) && !entry.name.startsWith(".")) {
807
+ const childDir = path4.join(dir, entry.name);
808
+ if (excludedPaths2.some(
809
+ (excludedPath) => childDir === excludedPath || childDir.startsWith(excludedPath + path4.sep)
810
+ )) continue;
811
+ const found = findFirstTsFile(childDir, excludedPaths2);
812
+ if (found) return found;
1044
813
  }
1045
- rejectAllPending(err) {
1046
- for (const [seq, pending] of this.pending) {
1047
- clearTimeout(pending.timer);
1048
- pending.reject(err);
1049
- }
1050
- this.pending.clear();
814
+ }
815
+ } catch {
816
+ }
817
+ return null;
818
+ }
819
+ function testTsserver(projectRoot3, resolution) {
820
+ return new Promise((resolve10) => {
821
+ const child = spawn2("node", [resolution.path, "--disableAutomaticTypingAcquisition"], {
822
+ cwd: projectRoot3,
823
+ stdio: ["pipe", "pipe", "pipe"]
824
+ });
825
+ const timeout = setTimeout(() => {
826
+ child.kill();
827
+ resolve10(false);
828
+ }, 1e4);
829
+ let buffer = "";
830
+ child.stdout.on("data", (chunk) => {
831
+ buffer += chunk.toString();
832
+ if (buffer.includes('"success":true')) {
833
+ clearTimeout(timeout);
834
+ child.kill();
835
+ resolve10(true);
1051
836
  }
1052
- // ─── Protocol: Parsing ──────────────────────────────────────────────────
1053
- onData(chunk) {
1054
- this.buffer = Buffer.concat([this.buffer, chunk]);
1055
- this.processBuffer();
837
+ });
838
+ child.on("error", () => {
839
+ clearTimeout(timeout);
840
+ resolve10(false);
841
+ });
842
+ child.on("exit", () => {
843
+ clearTimeout(timeout);
844
+ });
845
+ const request = JSON.stringify({
846
+ seq: 1,
847
+ type: "request",
848
+ command: "configure",
849
+ arguments: {
850
+ preferences: { disableSuggestions: true }
1056
851
  }
1057
- processBuffer() {
1058
- while (true) {
1059
- const headerEnd = this.buffer.indexOf("\r\n\r\n");
1060
- if (headerEnd === -1) return;
1061
- const header = this.buffer.subarray(0, headerEnd).toString("utf-8");
1062
- const match = header.match(/Content-Length:\s*(\d+)/i);
1063
- if (!match) {
1064
- this.buffer = this.buffer.subarray(headerEnd + 4);
1065
- continue;
1066
- }
1067
- const contentLength = parseInt(match[1], 10);
1068
- const bodyStart = headerEnd + 4;
1069
- if (this.buffer.length < bodyStart + contentLength) {
1070
- return;
1071
- }
1072
- const bodyBytes = this.buffer.subarray(bodyStart, bodyStart + contentLength);
1073
- this.buffer = this.buffer.subarray(bodyStart + contentLength);
1074
- try {
1075
- const message = JSON.parse(bodyBytes.toString("utf-8"));
1076
- this.onMessage(message);
1077
- } catch {
1078
- log2("Failed to parse tsserver message");
1079
- }
1080
- }
852
+ });
853
+ child.stdin.write(request + "\n");
854
+ });
855
+ }
856
+ function readProjectCodexConfig(projectRoot3) {
857
+ const configPath = path4.resolve(projectRoot3, ".codex/config.toml");
858
+ if (!fs3.existsSync(configPath)) return null;
859
+ return fs3.readFileSync(configPath, "utf-8");
860
+ }
861
+ function hasCodexTypegraphRegistration(content) {
862
+ return /\[mcp_servers\.typegraph\]/.test(content);
863
+ }
864
+ function hasCodexTsxLauncher(content) {
865
+ return /command\s*=\s*"[^"]*tsx(?:\.cmd)?"/.test(content) || /args\s*=\s*\[[\s\S]*"tsx"/.test(content);
866
+ }
867
+ function hasCompleteCodexTypegraphRegistration(content) {
868
+ return hasCodexTypegraphRegistration(content) && /command\s*=\s*"[^"]+"/.test(content) && /args\s*=\s*\[[\s\S]*\]/.test(content) && hasCodexTsxLauncher(content) && /TYPEGRAPH_PROJECT_ROOT\s*=/.test(content) && /TYPEGRAPH_TSCONFIG\s*=/.test(content);
869
+ }
870
+ function hasTrustedCodexProject(projectRoot3) {
871
+ const home = process.env.HOME;
872
+ if (!home) return null;
873
+ const globalConfigPath = path4.join(home, ".codex/config.toml");
874
+ if (!fs3.existsSync(globalConfigPath)) return null;
875
+ const lines = fs3.readFileSync(globalConfigPath, "utf-8").split(/\r?\n/);
876
+ let currentProject = null;
877
+ let currentTrusted = false;
878
+ const matchesTrustedProject = () => currentProject !== null && currentTrusted && (projectRoot3 === currentProject || projectRoot3.startsWith(currentProject + path4.sep));
879
+ for (const line of lines) {
880
+ const sectionMatch = line.match(/^\[projects\."([^"]+)"\]\s*$/);
881
+ if (sectionMatch) {
882
+ if (matchesTrustedProject()) return true;
883
+ currentProject = path4.resolve(sectionMatch[1]);
884
+ currentTrusted = false;
885
+ continue;
886
+ }
887
+ if (line.startsWith("[")) {
888
+ if (matchesTrustedProject()) return true;
889
+ currentProject = null;
890
+ currentTrusted = false;
891
+ continue;
892
+ }
893
+ if (currentProject && /\btrust_level\s*=\s*"trusted"/.test(line)) {
894
+ currentTrusted = true;
895
+ }
896
+ }
897
+ return matchesTrustedProject();
898
+ }
899
+ function hasCompleteJsonTypegraphRegistration(config, serverName, projectRoot3) {
900
+ if (typeof config !== "object" || config === null) return false;
901
+ const servers = config["mcpServers"];
902
+ if (typeof servers !== "object" || servers === null) return false;
903
+ const entry = servers[serverName];
904
+ if (typeof entry !== "object" || entry === null) return false;
905
+ const record = entry;
906
+ const command2 = record["command"];
907
+ const args2 = record["args"];
908
+ const env = record["env"];
909
+ const serializedArgs = JSON.stringify(args2 ?? []);
910
+ return typeof command2 === "string" && command2.length > 0 && Array.isArray(args2) && serializedArgs.includes("server.ts") && serializedArgs.includes("tsx") && typeof env === "object" && env !== null && env["TYPEGRAPH_PROJECT_ROOT"] === projectRoot3 && typeof env["TYPEGRAPH_TSCONFIG"] === "string";
911
+ }
912
+ function readJsonConfig(configPath) {
913
+ if (!fs3.existsSync(configPath)) return null;
914
+ try {
915
+ return JSON.parse(fs3.readFileSync(configPath, "utf-8"));
916
+ } catch {
917
+ return null;
918
+ }
919
+ }
920
+ function getAntigravityMcpConfigPaths() {
921
+ const home = process.env.HOME || "";
922
+ return [
923
+ path4.join(home, ".gemini/antigravity/mcp_config.json"),
924
+ path4.join(home, ".gemini/antigravity-cli/plugins/typegraph-mcp/mcp_config.json")
925
+ ];
926
+ }
927
+ function findAntigravityRegistration(projectRoot3) {
928
+ const home = process.env.HOME || "";
929
+ for (const configPath of getAntigravityMcpConfigPaths()) {
930
+ const config = readJsonConfig(configPath);
931
+ if (hasCompleteJsonTypegraphRegistration(config, "typegraph-mcp", projectRoot3)) {
932
+ return configPath.replace(home, "~");
933
+ }
934
+ }
935
+ return null;
936
+ }
937
+ function findLintConfigs(projectRoot3) {
938
+ const configs = [];
939
+ for (const fileName of ESLINT_CONFIG_NAMES) {
940
+ const fullPath = path4.resolve(projectRoot3, fileName);
941
+ if (fs3.existsSync(fullPath)) {
942
+ configs.push({ tool: "ESLint", fileName, fullPath, propertyName: "ignores" });
943
+ }
944
+ }
945
+ for (const fileName of OXLINT_CONFIG_NAMES) {
946
+ const fullPath = path4.resolve(projectRoot3, fileName);
947
+ if (fs3.existsSync(fullPath)) {
948
+ configs.push({ tool: "Oxlint", fileName, fullPath, propertyName: "ignorePatterns" });
949
+ }
950
+ }
951
+ for (const fileName of BIOME_CONFIG_NAMES) {
952
+ const fullPath = path4.resolve(projectRoot3, fileName);
953
+ if (fs3.existsSync(fullPath)) {
954
+ configs.push({ tool: "Biome", fileName, fullPath, propertyName: "files.includes" });
955
+ break;
956
+ }
957
+ }
958
+ return configs;
959
+ }
960
+ async function main(configOverride) {
961
+ const { projectRoot: projectRoot3, tsconfigPath: tsconfigPath3, toolDir: toolDir2, toolIsEmbedded: toolIsEmbedded2, toolRelPath } = configOverride ?? resolveConfig(import.meta.dirname);
962
+ const excludedPaths2 = toolIsEmbedded2 ? [toolDir2] : [];
963
+ let passed = 0;
964
+ let failed = 0;
965
+ let warned = 0;
966
+ function pass(msg) {
967
+ console.log(` \u2713 ${msg}`);
968
+ passed++;
969
+ }
970
+ function fail(msg, fix) {
971
+ console.log(` \u2717 ${msg}`);
972
+ console.log(` Fix: ${fix}`);
973
+ failed++;
974
+ }
975
+ function warn(msg, note) {
976
+ console.log(` ! ${msg}`);
977
+ console.log(` ${note}`);
978
+ warned++;
979
+ }
980
+ function skip(msg) {
981
+ console.log(` - ${msg} (skipped)`);
982
+ }
983
+ console.log("");
984
+ console.log("typegraph-mcp Health Check");
985
+ console.log("=======================");
986
+ console.log(`Project root: ${projectRoot3}`);
987
+ console.log("");
988
+ const nodeVersion = process.version;
989
+ const nodeMajor = parseInt(nodeVersion.slice(1).split(".")[0], 10);
990
+ if (nodeMajor >= 22) {
991
+ pass(`Node.js ${nodeVersion} (>= 22 required)`);
992
+ } else {
993
+ fail(`Node.js ${nodeVersion} is too old`, "Upgrade Node.js to >= 22");
994
+ }
995
+ const tsxInRoot = fs3.existsSync(path4.join(projectRoot3, "node_modules/.bin/tsx"));
996
+ const tsxInTool = fs3.existsSync(path4.join(toolDir2, "node_modules/.bin/tsx"));
997
+ if (tsxInRoot || tsxInTool) {
998
+ pass(`tsx available (in ${tsxInRoot ? "project" : "tool"} node_modules)`);
999
+ } else {
1000
+ pass("tsx available (via npx/global)");
1001
+ }
1002
+ let tsResolution = null;
1003
+ try {
1004
+ tsResolution = resolveTsServer(projectRoot3);
1005
+ if (tsResolution.source === "project") {
1006
+ pass(`TypeScript found (v${tsResolution.version})`);
1007
+ } else if (tsResolution.projectVersion) {
1008
+ pass(
1009
+ `Compatible tsserver found (TypeScript v${tsResolution.version} tool runtime; project uses v${tsResolution.projectVersion})`
1010
+ );
1011
+ } else {
1012
+ pass(`TypeScript found in typegraph-mcp (v${tsResolution.version})`);
1013
+ }
1014
+ } catch {
1015
+ fail(
1016
+ "Compatible tsserver runtime not found",
1017
+ `Run \`cd ${toolRelPath} && npm install --include=optional\``
1018
+ );
1019
+ }
1020
+ const tsconfigAbs = path4.resolve(projectRoot3, tsconfigPath3);
1021
+ if (fs3.existsSync(tsconfigAbs)) {
1022
+ pass(`tsconfig.json exists at ${tsconfigPath3}`);
1023
+ } else {
1024
+ pass("No tsconfig.json; semantic tools will use an inferred TypeScript project");
1025
+ }
1026
+ const pluginMcpPath = path4.join(toolDir2, ".mcp.json");
1027
+ const hasPluginMcp = fs3.existsSync(pluginMcpPath) && fs3.existsSync(path4.join(toolDir2, ".claude-plugin/plugin.json"));
1028
+ const projectCodexConfig = readProjectCodexConfig(projectRoot3);
1029
+ const hasProjectCodexRegistration = projectCodexConfig !== null && hasCompleteCodexTypegraphRegistration(projectCodexConfig);
1030
+ const codexGet = spawnSync("codex", ["mcp", "get", "typegraph"], {
1031
+ stdio: "pipe",
1032
+ encoding: "utf-8"
1033
+ });
1034
+ const hasGlobalCodexRegistration = codexGet.status === 0;
1035
+ const antigravityRegistrationPath = findAntigravityRegistration(projectRoot3);
1036
+ if (process.env.CLAUDE_PLUGIN_ROOT) {
1037
+ pass("MCP registered via plugin (CLAUDE_PLUGIN_ROOT set)");
1038
+ } else if (hasPluginMcp) {
1039
+ pass("MCP registered via plugin (.mcp.json + .claude-plugin/ present)");
1040
+ } else if (projectCodexConfig !== null) {
1041
+ const codexConfigPath = path4.resolve(projectRoot3, ".codex/config.toml");
1042
+ const hasSection = hasCodexTypegraphRegistration(projectCodexConfig);
1043
+ const hasCommand = /command\s*=\s*"[^"]+"/.test(projectCodexConfig);
1044
+ const hasArgs = /args\s*=\s*\[[\s\S]*\]/.test(projectCodexConfig);
1045
+ const hasTsxLauncher = hasCodexTsxLauncher(projectCodexConfig);
1046
+ const hasEnvRoot = /TYPEGRAPH_PROJECT_ROOT\s*=/.test(projectCodexConfig);
1047
+ const hasEnvTsconfig = /TYPEGRAPH_TSCONFIG\s*=/.test(projectCodexConfig);
1048
+ if (hasProjectCodexRegistration) {
1049
+ pass("MCP registered in project .codex/config.toml");
1050
+ const trusted = hasTrustedCodexProject(projectRoot3);
1051
+ if (trusted === false) {
1052
+ warn(
1053
+ "Project Codex config may be ignored",
1054
+ 'Add the project (or a parent directory) to ~/.codex/config.toml with trust_level = "trusted"'
1055
+ );
1081
1056
  }
1082
- onMessage(message) {
1083
- if (message.type === "response" && message.request_seq !== void 0) {
1084
- const pending = this.pending.get(message.request_seq);
1085
- if (pending) {
1086
- clearTimeout(pending.timer);
1087
- this.pending.delete(message.request_seq);
1088
- if (message.success) {
1089
- pending.resolve(message.body);
1090
- } else {
1091
- pending.reject(
1092
- new Error(`tsserver ${pending.command} failed: ${message.message ?? "unknown error"}`)
1093
- );
1094
- }
1057
+ } else {
1058
+ const issues = [];
1059
+ if (!hasSection) issues.push("[mcp_servers.typegraph] section is missing");
1060
+ if (!hasCommand) issues.push("command is missing");
1061
+ if (!hasArgs) issues.push("args are missing");
1062
+ if (!hasTsxLauncher) issues.push("command should point to tsx or args should include 'tsx'");
1063
+ if (!hasEnvRoot) issues.push("TYPEGRAPH_PROJECT_ROOT is missing");
1064
+ if (!hasEnvTsconfig) issues.push("TYPEGRAPH_TSCONFIG is missing");
1065
+ fail(
1066
+ `Project .codex/config.toml registration incomplete: ${issues.join(", ")}`,
1067
+ `Update ${codexConfigPath} with a complete [mcp_servers.typegraph] entry`
1068
+ );
1069
+ }
1070
+ } else if (hasGlobalCodexRegistration) {
1071
+ pass("MCP registered in global Codex CLI config");
1072
+ } else if (antigravityRegistrationPath !== null) {
1073
+ pass(`MCP registered in Antigravity config (${antigravityRegistrationPath})`);
1074
+ } else {
1075
+ const codexConfigPath = path4.resolve(projectRoot3, ".codex/config.toml");
1076
+ const mcpJsonPath = path4.resolve(projectRoot3, ".claude/mcp.json");
1077
+ if (fs3.existsSync(mcpJsonPath)) {
1078
+ try {
1079
+ const mcpJson = JSON.parse(fs3.readFileSync(mcpJsonPath, "utf-8"));
1080
+ const tsNav = mcpJson?.mcpServers?.["typegraph"];
1081
+ if (tsNav) {
1082
+ const hasCommand = tsNav.command === "npx";
1083
+ const hasArgs = Array.isArray(tsNav.args) && tsNav.args.includes("tsx");
1084
+ const hasEnv = tsNav.env?.["TYPEGRAPH_PROJECT_ROOT"] && tsNav.env?.["TYPEGRAPH_TSCONFIG"];
1085
+ if (hasCommand && hasArgs && hasEnv) {
1086
+ pass("MCP registered in .claude/mcp.json");
1087
+ } else {
1088
+ const issues = [];
1089
+ if (!hasCommand) issues.push("command should be 'npx'");
1090
+ if (!hasArgs) issues.push("args should include 'tsx'");
1091
+ if (!hasEnv) issues.push("env should set TYPEGRAPH_PROJECT_ROOT and TYPEGRAPH_TSCONFIG");
1092
+ fail(
1093
+ `MCP registration incomplete: ${issues.join(", ")}`,
1094
+ "See README for correct .claude/mcp.json format"
1095
+ );
1095
1096
  }
1097
+ } else {
1098
+ const serverPath = toolIsEmbedded2 ? `./${toolRelPath}/server.ts` : path4.resolve(toolDir2, "server.ts");
1099
+ fail(
1100
+ "MCP entry 'typegraph' not found in .claude/mcp.json",
1101
+ `Add to .claude/mcp.json:
1102
+ {
1103
+ "mcpServers": {
1104
+ "typegraph": {
1105
+ "command": "npx",
1106
+ "args": ["tsx", "${serverPath}"],
1107
+ "env": { "TYPEGRAPH_PROJECT_ROOT": ".", "TYPEGRAPH_TSCONFIG": "./tsconfig.json" }
1096
1108
  }
1097
1109
  }
1098
- // ─── Protocol: Sending ──────────────────────────────────────────────────
1099
- sendRequest(command2, args2) {
1100
- if (!this.child?.stdin?.writable) {
1101
- return Promise.reject(new Error("tsserver not running"));
1102
- }
1103
- const seq = ++this.seq;
1104
- const request = {
1105
- seq,
1106
- type: "request",
1107
- command: command2,
1108
- arguments: args2
1109
- };
1110
- return new Promise((resolve9, reject) => {
1111
- const timer = setTimeout(() => {
1112
- this.pending.delete(seq);
1113
- reject(new Error(`tsserver ${command2} timed out after ${REQUEST_TIMEOUT_MS}ms`));
1114
- }, REQUEST_TIMEOUT_MS);
1115
- this.pending.set(seq, { resolve: resolve9, reject, timer, command: command2 });
1116
- this.child.stdin.write(JSON.stringify(request) + "\n");
1117
- });
1118
- }
1119
- // Fire-and-forget — for commands like `open` that may not send a response
1120
- sendNotification(command2, args2) {
1121
- if (!this.child?.stdin?.writable) return;
1122
- const seq = ++this.seq;
1123
- const request = { seq, type: "request", command: command2, arguments: args2 };
1124
- this.child.stdin.write(JSON.stringify(request) + "\n");
1125
- }
1126
- // ─── File Management ───────────────────────────────────────────────────
1127
- async ensureOpen(file) {
1128
- const absPath2 = this.resolvePath(file);
1129
- if (this.openFiles.has(absPath2)) return;
1130
- this.openFiles.add(absPath2);
1131
- this.sendNotification("open", { file: absPath2 });
1132
- await new Promise((r) => setTimeout(r, 50));
1133
- }
1134
- /**
1135
- * Mark tsserver's view of the disk as stale (a file changed that is NOT open
1136
- * in tsserver). tsserver's own disk watching silently decays in long-lived
1137
- * instances at monorepo scale, leaving closed-file contents frozen at project
1138
- * load time and new files unassigned (they land in single-file inferred
1139
- * projects, producing definition-only reference results). The next query
1140
- * forces a `reloadProjects`, which re-globs configs and re-reads closed files
1141
- * from disk. Open files are unaffected by design — their content is
1142
- * protocol-owned and refreshed via reloadOpenFile().
1143
- */
1144
- markProjectsDirty() {
1145
- this.projectsDirty = true;
1146
- }
1147
- refreshIfDirty() {
1148
- if (!this.projectsDirty) return;
1149
- this.projectsDirty = false;
1150
- this.sendNotification("reloadProjects");
1151
- }
1152
- async reloadOpenFile(file) {
1153
- const absPath2 = this.resolvePath(file);
1154
- if (!this.openFiles.has(absPath2)) return false;
1155
- await this.sendRequest("reload", {
1156
- file: absPath2,
1157
- tmpfile: absPath2
1158
- });
1159
- return true;
1160
- }
1161
- closeFile(file) {
1162
- const absPath2 = this.resolvePath(file);
1163
- if (!this.openFiles.delete(absPath2)) return false;
1164
- this.sendNotification("close", { file: absPath2 });
1165
- return true;
1166
- }
1167
- // ─── Public API ────────────────────────────────────────────────────────
1168
- async definition(file, line, offset) {
1169
- this.refreshIfDirty();
1170
- const absPath2 = this.resolvePath(file);
1171
- await this.ensureOpen(absPath2);
1172
- const body = await this.sendRequest("definition", {
1173
- file: absPath2,
1174
- line,
1175
- offset
1176
- });
1177
- if (!body || !Array.isArray(body)) return [];
1178
- return body.map((d) => ({
1179
- ...d,
1180
- file: this.relativePath(d.file)
1181
- }));
1182
- }
1183
- async references(file, line, offset) {
1184
- this.refreshIfDirty();
1185
- const absPath2 = this.resolvePath(file);
1186
- await this.ensureOpen(absPath2);
1187
- const body = await this.sendRequest("references", {
1188
- file: absPath2,
1189
- line,
1190
- offset
1191
- });
1192
- if (!body?.refs) return [];
1193
- return body.refs.map((r) => ({
1194
- ...r,
1195
- file: this.relativePath(r.file)
1196
- }));
1197
- }
1198
- async quickinfo(file, line, offset) {
1199
- this.refreshIfDirty();
1200
- const absPath2 = this.resolvePath(file);
1201
- await this.ensureOpen(absPath2);
1202
- try {
1203
- const body = await this.sendRequest("quickinfo", {
1204
- file: absPath2,
1205
- line,
1206
- offset
1207
- });
1208
- return body ?? null;
1209
- } catch {
1210
- return null;
1211
- }
1212
- }
1213
- async navto(searchValue, maxResults = 10, file) {
1214
- this.refreshIfDirty();
1215
- if (file) await this.ensureOpen(file);
1216
- const args2 = {
1217
- searchValue,
1218
- maxResultCount: maxResults
1219
- };
1220
- if (file) args2["file"] = this.resolvePath(file);
1221
- const body = await this.sendRequest("navto", args2);
1222
- if (!body || !Array.isArray(body)) return [];
1223
- return body.map((item) => ({
1224
- ...item,
1225
- file: this.relativePath(item.file)
1226
- }));
1110
+ }`
1111
+ );
1112
+ }
1113
+ } catch (err) {
1114
+ fail(
1115
+ "Failed to parse .claude/mcp.json",
1116
+ `Check JSON syntax: ${err instanceof Error ? err.message : String(err)}`
1117
+ );
1227
1118
  }
1228
- async navbar(file) {
1229
- this.refreshIfDirty();
1230
- const absPath2 = this.resolvePath(file);
1231
- await this.ensureOpen(absPath2);
1232
- const body = await this.sendRequest("navbar", {
1233
- file: absPath2
1234
- });
1235
- return body ?? [];
1119
+ } else {
1120
+ fail(
1121
+ "No MCP registration found",
1122
+ `Create ${codexConfigPath} with [mcp_servers.typegraph] or create .claude/mcp.json with typegraph server registration`
1123
+ );
1124
+ }
1125
+ }
1126
+ const toolNodeModules = path4.join(toolDir2, "node_modules");
1127
+ if (fs3.existsSync(toolNodeModules)) {
1128
+ const requiredPkgs = [
1129
+ "@modelcontextprotocol/sdk",
1130
+ "oxc-parser",
1131
+ "oxc-resolver",
1132
+ "typescript",
1133
+ "zod"
1134
+ ];
1135
+ const missing = requiredPkgs.filter(
1136
+ (pkg) => !fs3.existsSync(path4.join(toolNodeModules, ...pkg.split("/")))
1137
+ );
1138
+ if (missing.length === 0) {
1139
+ pass(`Dependencies installed (${requiredPkgs.length} packages)`);
1140
+ } else {
1141
+ fail(`Missing packages: ${missing.join(", ")}`, `Run \`cd ${toolRelPath} && npm install\``);
1142
+ }
1143
+ } else {
1144
+ fail("typegraph-mcp dependencies not installed", `Run \`cd ${toolRelPath} && npm install\``);
1145
+ }
1146
+ try {
1147
+ const oxcParserReq = createRequire2(path4.join(toolDir2, "package.json"));
1148
+ const { parseSync: parseSync3 } = await import(oxcParserReq.resolve("oxc-parser"));
1149
+ const result = parseSync3("test.ts", 'import { x } from "./y";');
1150
+ if (result.module?.staticImports?.length === 1) {
1151
+ pass("oxc-parser working");
1152
+ } else {
1153
+ fail(
1154
+ "oxc-parser parseSync returned unexpected result",
1155
+ `Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && npm install\``
1156
+ );
1157
+ }
1158
+ } catch (err) {
1159
+ fail(
1160
+ `oxc-parser failed: ${err instanceof Error ? err.message : String(err)}`,
1161
+ `Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && npm install\``
1162
+ );
1163
+ }
1164
+ try {
1165
+ const oxcResolverReq = createRequire2(path4.join(toolDir2, "package.json"));
1166
+ const { ResolverFactory: ResolverFactory2 } = await import(oxcResolverReq.resolve("oxc-resolver"));
1167
+ const resolver = new ResolverFactory2({
1168
+ ...fs3.existsSync(tsconfigAbs) ? { tsconfig: { configFile: tsconfigAbs, references: "auto" } } : {},
1169
+ extensions: [".ts", ".tsx", ".js"],
1170
+ extensionAlias: { ".js": [".ts", ".tsx", ".js"] }
1171
+ });
1172
+ let resolveOk = false;
1173
+ const testFile = findFirstTsFile(projectRoot3, excludedPaths2);
1174
+ if (testFile) {
1175
+ const dir = path4.dirname(testFile);
1176
+ const base = "./" + path4.basename(testFile);
1177
+ const result = resolver.sync(dir, base);
1178
+ resolveOk = !!result.path;
1179
+ }
1180
+ if (resolveOk) {
1181
+ pass("oxc-resolver working");
1182
+ } else {
1183
+ warn(
1184
+ "oxc-resolver loaded but couldn't resolve a test import",
1185
+ "Check tsconfig.json is valid and has correct `references`"
1186
+ );
1187
+ }
1188
+ } catch (err) {
1189
+ fail(
1190
+ `oxc-resolver failed: ${err instanceof Error ? err.message : String(err)}`,
1191
+ `Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && npm install\``
1192
+ );
1193
+ }
1194
+ if (tsResolution) {
1195
+ try {
1196
+ const ok = await testTsserver(projectRoot3, tsResolution);
1197
+ if (ok) {
1198
+ pass("tsserver responds to configure");
1199
+ } else {
1200
+ fail(
1201
+ "tsserver did not respond",
1202
+ "Verify `typescript` is installed and tsconfig.json is valid"
1203
+ );
1236
1204
  }
1237
- };
1205
+ } catch (err) {
1206
+ fail(
1207
+ `tsserver failed to start: ${err instanceof Error ? err.message : String(err)}`,
1208
+ "Verify `typescript` is installed and tsconfig.json is valid"
1209
+ );
1210
+ }
1211
+ } else {
1212
+ skip("tsserver test (TypeScript not found)");
1213
+ }
1214
+ try {
1215
+ let buildGraph2;
1216
+ try {
1217
+ ({ buildGraph: buildGraph2 } = await import(path4.resolve(toolDir2, "module-graph.js")));
1218
+ } catch {
1219
+ ({ buildGraph: buildGraph2 } = await Promise.resolve().then(() => (init_module_graph(), module_graph_exports)));
1220
+ }
1221
+ const start2 = performance.now();
1222
+ const { graph } = await buildGraph2(
1223
+ projectRoot3,
1224
+ tsconfigPath3,
1225
+ excludedPaths2
1226
+ );
1227
+ const elapsed = (performance.now() - start2).toFixed(0);
1228
+ const edgeCount = [...graph.forward.values()].reduce(
1229
+ (s, e) => s + e.length,
1230
+ 0
1231
+ );
1232
+ if (graph.files.size > 0 && edgeCount > 0) {
1233
+ pass(`Module graph: ${graph.files.size} files, ${edgeCount} edges [${elapsed}ms]`);
1234
+ } else if (graph.files.size > 0) {
1235
+ warn(
1236
+ `Module graph: ${graph.files.size} files but 0 edges`,
1237
+ "Files found but no internal imports resolved. Check tsconfig references."
1238
+ );
1239
+ } else {
1240
+ fail(
1241
+ "Module graph: 0 files discovered",
1242
+ "Check tsconfig.json includes source files and project root is correct"
1243
+ );
1244
+ }
1245
+ } catch (err) {
1246
+ fail(
1247
+ `Module graph build failed: ${err instanceof Error ? err.message : String(err)}`,
1248
+ "Check that oxc-parser and oxc-resolver are installed correctly"
1249
+ );
1250
+ }
1251
+ if (toolIsEmbedded2) {
1252
+ const lintConfigs = findLintConfigs(projectRoot3);
1253
+ if (lintConfigs.length > 0) {
1254
+ const parentDir = path4.basename(path4.dirname(toolDir2));
1255
+ const parentIgnorePattern = new RegExp(`["']${parentDir}\\/\\*\\*["']`);
1256
+ for (const config of lintConfigs) {
1257
+ const content = fs3.readFileSync(config.fullPath, "utf-8");
1258
+ const hasParentIgnore = config.tool === "Biome" ? biomeScopeExcludes(content, parentDir) : parentIgnorePattern.test(content);
1259
+ if (hasParentIgnore) {
1260
+ pass(`${config.tool} ignores ${parentDir}/ (${config.fileName})`);
1261
+ } else {
1262
+ const expectedPattern = config.tool === "Biome" ? `!!${parentDir}` : `${parentDir}/**`;
1263
+ fail(
1264
+ `${config.tool} missing ignore: "${expectedPattern}" (${config.fileName})`,
1265
+ `Add to ${config.propertyName} in ${config.fileName}:
1266
+ "${expectedPattern}",`
1267
+ );
1268
+ }
1269
+ }
1270
+ } else {
1271
+ skip("Lint config check (no ESLint, Oxlint, or Biome config found)");
1272
+ }
1273
+ } else {
1274
+ skip("Lint config check (typegraph-mcp is external to project)");
1275
+ }
1276
+ const gitignorePath = path4.resolve(projectRoot3, ".gitignore");
1277
+ if (fs3.existsSync(gitignorePath)) {
1278
+ const gitignoreContent = fs3.readFileSync(gitignorePath, "utf-8");
1279
+ const lines = gitignoreContent.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
1280
+ const ignoresClaude = lines.some(
1281
+ (l) => l === ".claude/" || l === ".claude" || l === "/.claude"
1282
+ );
1283
+ const parentDir = toolIsEmbedded2 ? path4.basename(path4.dirname(toolDir2)) : null;
1284
+ const ignoresParent = parentDir && lines.some((l) => l === `${parentDir}/` || l === parentDir || l === `/${parentDir}`);
1285
+ if (!ignoresParent && !ignoresClaude) {
1286
+ pass(".gitignore does not exclude .claude/" + (parentDir ? ` or ${parentDir}/` : ""));
1287
+ } else {
1288
+ const excluded = [];
1289
+ if (ignoresParent) excluded.push(`${parentDir}/`);
1290
+ if (ignoresClaude) excluded.push(".claude/");
1291
+ warn(
1292
+ `.gitignore excludes ${excluded.join(" and ")}`,
1293
+ "Remove these entries so MCP config and tool source are tracked in git"
1294
+ );
1295
+ }
1296
+ } else {
1297
+ skip(".gitignore check (no .gitignore)");
1298
+ }
1299
+ console.log("");
1300
+ const total = passed + failed;
1301
+ if (failed === 0) {
1302
+ console.log(
1303
+ `${passed}/${total} checks passed` + (warned > 0 ? ` (${warned} warning${warned > 1 ? "s" : ""})` : "") + " -- typegraph-mcp is ready"
1304
+ );
1305
+ } else {
1306
+ console.log(
1307
+ `${passed}/${total} checks passed, ${failed} failed` + (warned > 0 ? `, ${warned} warning${warned > 1 ? "s" : ""}` : "") + " -- fix issues above"
1308
+ );
1309
+ }
1310
+ console.log("");
1311
+ return { passed, failed, warned };
1312
+ }
1313
+ var ESLINT_CONFIG_NAMES, OXLINT_CONFIG_NAMES;
1314
+ var init_check = __esm({
1315
+ "check.ts"() {
1316
+ init_config();
1317
+ init_tsserver_client();
1318
+ init_biome_config();
1319
+ ESLINT_CONFIG_NAMES = [
1320
+ "eslint.config.mjs",
1321
+ "eslint.config.js",
1322
+ "eslint.config.ts",
1323
+ "eslint.config.cjs"
1324
+ ];
1325
+ OXLINT_CONFIG_NAMES = [
1326
+ ".oxlintrc.json",
1327
+ "oxlint.config.ts",
1328
+ "oxlint.config.js",
1329
+ "oxlint.config.mjs",
1330
+ "oxlint.config.cjs"
1331
+ ];
1238
1332
  }
1239
1333
  });
1240
1334
 
@@ -1518,7 +1612,8 @@ var init_graph_queries = __esm({
1518
1612
  // smoke-test.ts
1519
1613
  var smoke_test_exports = {};
1520
1614
  __export(smoke_test_exports, {
1521
- main: () => main2
1615
+ main: () => main2,
1616
+ selectQuickInfoSymbol: () => selectQuickInfoSymbol
1522
1617
  });
1523
1618
  import * as fs5 from "fs";
1524
1619
  import * as path6 from "path";
@@ -1535,8 +1630,50 @@ function findInNavBar(items, predicate) {
1535
1630
  }
1536
1631
  return null;
1537
1632
  }
1538
- function findTestFile(rootDir) {
1633
+ function symbolPositions(symbol, sourceLines) {
1634
+ const span = symbol.spans[0];
1635
+ const positions = [];
1636
+ const lineText = sourceLines[span.start.line - 1];
1637
+ if (lineText) {
1638
+ const nameIndex = lineText.indexOf(symbol.text, Math.max(0, span.start.offset - 1));
1639
+ if (nameIndex >= 0) {
1640
+ positions.push({ line: span.start.line, offset: nameIndex + 1 });
1641
+ }
1642
+ }
1643
+ if (!positions.some((position) => position.offset === span.start.offset)) {
1644
+ positions.push(span.start);
1645
+ }
1646
+ return positions;
1647
+ }
1648
+ async function probeQuickInfo(client2, file, symbol, sourceLines) {
1649
+ for (const position of symbolPositions(symbol, sourceLines)) {
1650
+ const info = await client2.quickinfo(file, position.line, position.offset);
1651
+ if (info) return { info, position };
1652
+ }
1653
+ return null;
1654
+ }
1655
+ async function selectQuickInfoSymbol(client2, file, symbols, sourceLines) {
1656
+ const kindPriority = /* @__PURE__ */ new Map([
1657
+ ["const", 0],
1658
+ ["let", 1],
1659
+ ["var", 2],
1660
+ ["class", 3],
1661
+ ["enum", 4],
1662
+ ["function", 5],
1663
+ ["method", 6]
1664
+ ]);
1665
+ const concreteSymbols = symbols.map((symbol, index) => ({ symbol, index })).filter(({ symbol }) => kindPriority.has(symbol.kind)).sort(
1666
+ (a, b) => kindPriority.get(a.symbol.kind) - kindPriority.get(b.symbol.kind) || a.index - b.index
1667
+ ).map(({ symbol }) => symbol);
1668
+ for (const symbol of concreteSymbols) {
1669
+ const probe = await probeQuickInfo(client2, file, symbol, sourceLines);
1670
+ if (probe) return { symbol, ...probe };
1671
+ }
1672
+ return null;
1673
+ }
1674
+ function findTestFile(rootDir, excludedPaths2 = []) {
1539
1675
  const candidates = [];
1676
+ const normalizedExclusions = excludedPaths2.map((excludedPath) => path6.resolve(excludedPath));
1540
1677
  function walk(dir, depth) {
1541
1678
  if (depth > 5 || candidates.length >= 30) return;
1542
1679
  let entries;
@@ -1548,7 +1685,11 @@ function findTestFile(rootDir) {
1548
1685
  for (const entry of entries) {
1549
1686
  if (entry.isDirectory()) {
1550
1687
  if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
1551
- walk(path6.join(dir, entry.name), depth + 1);
1688
+ const childDir = path6.join(dir, entry.name);
1689
+ if (normalizedExclusions.some(
1690
+ (excludedPath) => childDir === excludedPath || childDir.startsWith(excludedPath + path6.sep)
1691
+ )) continue;
1692
+ walk(childDir, depth + 1);
1552
1693
  } else if (entry.isFile()) {
1553
1694
  const name = entry.name;
1554
1695
  if (name.endsWith(".d.ts") || name.endsWith(".test.ts") || name.endsWith(".spec.ts"))
@@ -1556,7 +1697,7 @@ function findTestFile(rootDir) {
1556
1697
  if (!name.endsWith(".ts") && !name.endsWith(".tsx")) continue;
1557
1698
  try {
1558
1699
  const stat = fs5.statSync(path6.join(dir, name));
1559
- if (stat.size > 200 && stat.size < 5e4) {
1700
+ if (stat.size > 0 && stat.size < 5e4) {
1560
1701
  candidates.push({ file: path6.join(dir, name), size: stat.size });
1561
1702
  }
1562
1703
  } catch {
@@ -1580,7 +1721,8 @@ function findImporter(graph, file) {
1580
1721
  return (preferred ?? revEdges[0]).target;
1581
1722
  }
1582
1723
  async function main2(configOverride) {
1583
- const { projectRoot: projectRoot3, tsconfigPath: tsconfigPath3 } = configOverride ?? resolveConfig(import.meta.dirname);
1724
+ const { projectRoot: projectRoot3, tsconfigPath: tsconfigPath3, toolDir: toolDir2, toolIsEmbedded: toolIsEmbedded2 } = configOverride ?? resolveConfig(import.meta.dirname);
1725
+ const excludedPaths2 = toolIsEmbedded2 ? [toolDir2] : [];
1584
1726
  let passed = 0;
1585
1727
  let failed = 0;
1586
1728
  let skipped = 0;
@@ -1603,7 +1745,7 @@ async function main2(configOverride) {
1603
1745
  console.log("=====================");
1604
1746
  console.log(`Project root: ${projectRoot3}`);
1605
1747
  console.log("");
1606
- const testFile = findTestFile(projectRoot3);
1748
+ const testFile = findTestFile(projectRoot3, excludedPaths2);
1607
1749
  if (!testFile) {
1608
1750
  console.log(" No suitable .ts file found in project. Cannot run smoke tests.");
1609
1751
  return { passed, failed: failed + 1, skipped };
@@ -1616,7 +1758,7 @@ async function main2(configOverride) {
1616
1758
  let t0;
1617
1759
  t0 = performance.now();
1618
1760
  try {
1619
- const result = await buildGraph(projectRoot3, tsconfigPath3);
1761
+ const result = await buildGraph(projectRoot3, tsconfigPath3, excludedPaths2);
1620
1762
  graph = result.graph;
1621
1763
  const ms = performance.now() - t0;
1622
1764
  const edgeCount = [...graph.forward.values()].reduce((s, e) => s + e.length, 0);
@@ -1733,8 +1875,17 @@ async function main2(configOverride) {
1733
1875
  } else {
1734
1876
  fail("navbar", `No symbols found in ${testFileRel}`, navbarMs);
1735
1877
  }
1736
- const concreteKinds = /* @__PURE__ */ new Set(["const", "function", "class", "var", "let", "enum"]);
1737
- const sym = allSymbols.find((s) => concreteKinds.has(s.kind)) ?? allSymbols[0];
1878
+ const source = fs5.readFileSync(testFile, "utf-8");
1879
+ const sourceLines = source.split(/\r?\n/);
1880
+ const quickInfoSelection = await selectQuickInfoSymbol(
1881
+ client2,
1882
+ testFileRel,
1883
+ allSymbols,
1884
+ sourceLines
1885
+ );
1886
+ const sym = quickInfoSelection?.symbol ?? allSymbols[0];
1887
+ const selectedQuickInfo = quickInfoSelection?.info ?? null;
1888
+ const selectedPosition = quickInfoSelection?.position ?? sym?.spans[0]?.start;
1738
1889
  if (!sym) {
1739
1890
  const toolNames = [
1740
1891
  "find_symbol",
@@ -1749,6 +1900,7 @@ async function main2(configOverride) {
1749
1900
  for (const name of toolNames) skip(name, "No symbol discovered");
1750
1901
  } else {
1751
1902
  const span = sym.spans[0];
1903
+ const point = selectedPosition ?? span.start;
1752
1904
  t0 = performance.now();
1753
1905
  const found = findInNavBar(bar, (item) => item.text === sym.text && item.kind === sym.kind);
1754
1906
  if (found && found.spans.length > 0) {
@@ -1761,7 +1913,7 @@ async function main2(configOverride) {
1761
1913
  fail("find_symbol", `Could not re-find ${sym.text}`, performance.now() - t0);
1762
1914
  }
1763
1915
  t0 = performance.now();
1764
- const defs = await client2.definition(testFileRel, span.start.line, span.start.offset);
1916
+ const defs = await client2.definition(testFileRel, point.line, point.offset);
1765
1917
  if (defs.length > 0) {
1766
1918
  const def = defs[0];
1767
1919
  pass("definition", `${sym.text} -> ${def.file}:${def.start.line}`, performance.now() - t0);
@@ -1769,7 +1921,7 @@ async function main2(configOverride) {
1769
1921
  pass("definition", `${sym.text} is its own definition`, performance.now() - t0);
1770
1922
  }
1771
1923
  t0 = performance.now();
1772
- const refs = await client2.references(testFileRel, span.start.line, span.start.offset);
1924
+ const refs = await client2.references(testFileRel, point.line, point.offset);
1773
1925
  const refFiles = new Set(refs.map((r) => r.file));
1774
1926
  pass(
1775
1927
  "references",
@@ -1777,7 +1929,10 @@ async function main2(configOverride) {
1777
1929
  performance.now() - t0
1778
1930
  );
1779
1931
  t0 = performance.now();
1780
- let info = await client2.quickinfo(testFileRel, span.start.line, span.start.offset);
1932
+ let info = selectedQuickInfo;
1933
+ if (!info) {
1934
+ info = await client2.quickinfo(testFileRel, point.line, point.offset);
1935
+ }
1781
1936
  if (!info && defs.length > 0) {
1782
1937
  const def = defs[0];
1783
1938
  info = await client2.quickinfo(testFileRel, def.start.line, def.start.offset);
@@ -1786,7 +1941,7 @@ async function main2(configOverride) {
1786
1941
  const typeStr = info.displayString.length > 80 ? info.displayString.slice(0, 80) + "..." : info.displayString;
1787
1942
  pass("type_info", typeStr, performance.now() - t0);
1788
1943
  } else {
1789
- fail("type_info", `No type info for ${sym.text}`, performance.now() - t0);
1944
+ skip("type_info", `No discovered symbol returned type info`);
1790
1945
  }
1791
1946
  t0 = performance.now();
1792
1947
  const navItems = await client2.navto(sym.text, 5);
@@ -1818,7 +1973,6 @@ async function main2(configOverride) {
1818
1973
  const exportSymbols = topItems.filter((item) => symbolKinds.has(item.kind));
1819
1974
  pass("module_exports", `${exportSymbols.length} top-level symbol(s)`, performance.now() - t0);
1820
1975
  t0 = performance.now();
1821
- const source = fs5.readFileSync(testFile, "utf-8");
1822
1976
  const importMatch = source.match(/^import\s+\{([^}]+)\}\s+from\s+["']([^"']+)["']/m);
1823
1977
  if (importMatch) {
1824
1978
  const firstName = importMatch[1].split(",")[0].replace(/^type\s+/, "").trim();
@@ -2355,14 +2509,16 @@ async function benchmarkAccuracy(client2, graph) {
2355
2509
  return scenarios;
2356
2510
  }
2357
2511
  async function main3(config) {
2358
- ({ projectRoot, tsconfigPath } = config ?? resolveConfig(import.meta.dirname));
2512
+ const resolvedConfig = config ?? resolveConfig(import.meta.dirname);
2513
+ ({ projectRoot, tsconfigPath } = resolvedConfig);
2514
+ const excludedPaths2 = resolvedConfig.toolIsEmbedded && resolvedConfig.toolDir ? [resolvedConfig.toolDir] : [];
2359
2515
  console.log("");
2360
2516
  console.log("typegraph-mcp Benchmark");
2361
2517
  console.log("=======================");
2362
2518
  console.log(`Project: ${projectRoot}`);
2363
2519
  console.log("");
2364
2520
  const graphStart = performance.now();
2365
- const { graph } = await buildGraph(projectRoot, tsconfigPath);
2521
+ const { graph } = await buildGraph(projectRoot, tsconfigPath, excludedPaths2);
2366
2522
  const graphMs = performance.now() - graphStart;
2367
2523
  const edgeCount = [...graph.forward.values()].reduce((s, e) => s + e.length, 0);
2368
2524
  console.log(`Module graph: ${graph.files.size} files, ${edgeCount} edges [${graphMs.toFixed(0)}ms]`);
@@ -2716,37 +2872,44 @@ async function main4() {
2716
2872
  log3(`tsconfig: ${tsconfigPath2}`);
2717
2873
  const [, graphResult] = await Promise.all([
2718
2874
  client.start(),
2719
- buildGraph(projectRoot2, tsconfigPath2)
2875
+ buildGraph(projectRoot2, tsconfigPath2, excludedPaths)
2720
2876
  ]);
2721
2877
  moduleGraph = graphResult.graph;
2722
2878
  moduleResolver = graphResult.resolver;
2723
- startWatcher(projectRoot2, moduleGraph, graphResult.resolver, {
2724
- onFileUpdated: (filePath) => client.reloadOpenFile(filePath).then(
2725
- (wasOpen) => {
2726
- if (!wasOpen) client.markProjectsDirty();
2727
- },
2728
- (err) => {
2879
+ startWatcher(
2880
+ projectRoot2,
2881
+ moduleGraph,
2882
+ graphResult.resolver,
2883
+ {
2884
+ onFileUpdated: (filePath) => client.reloadOpenFile(filePath).then(
2885
+ (wasOpen) => {
2886
+ if (!wasOpen) client.markProjectsDirty();
2887
+ },
2888
+ (err) => {
2889
+ client.markProjectsDirty();
2890
+ log3(`Failed to reload open file ${relPath2(filePath)}:`, err);
2891
+ }
2892
+ ),
2893
+ onFileDeleted: (filePath) => {
2894
+ client.closeFile(filePath);
2729
2895
  client.markProjectsDirty();
2730
- log3(`Failed to reload open file ${relPath2(filePath)}:`, err);
2731
2896
  }
2732
- ),
2733
- onFileDeleted: (filePath) => {
2734
- client.closeFile(filePath);
2735
- client.markProjectsDirty();
2736
- }
2737
- });
2897
+ },
2898
+ excludedPaths
2899
+ );
2738
2900
  const transport = new StdioServerTransport();
2739
2901
  await mcpServer.connect(transport);
2740
2902
  log3("MCP server connected and ready");
2741
2903
  }
2742
- var projectRoot2, tsconfigPath2, log3, client, moduleGraph, moduleResolver, mcpServer, locationOrSymbol, exportKinds, normalizedProjectRoot;
2904
+ var projectRoot2, tsconfigPath2, toolDir, toolIsEmbedded, excludedPaths, log3, client, moduleGraph, moduleResolver, mcpServer, locationOrSymbol, exportKinds, normalizedProjectRoot;
2743
2905
  var init_server = __esm({
2744
2906
  "server.ts"() {
2745
2907
  init_tsserver_client();
2746
2908
  init_module_graph();
2747
2909
  init_graph_queries();
2748
2910
  init_config();
2749
- ({ projectRoot: projectRoot2, tsconfigPath: tsconfigPath2 } = resolveConfig(import.meta.dirname));
2911
+ ({ projectRoot: projectRoot2, tsconfigPath: tsconfigPath2, toolDir, toolIsEmbedded } = resolveConfig(import.meta.dirname));
2912
+ excludedPaths = toolIsEmbedded ? [toolDir] : [];
2750
2913
  log3 = (...args2) => console.error("[typegraph]", ...args2);
2751
2914
  client = new TsServerClient(projectRoot2, tsconfigPath2);
2752
2915
  mcpServer = new McpServer({
@@ -3256,6 +3419,7 @@ var init_server = __esm({
3256
3419
  });
3257
3420
 
3258
3421
  // cli.ts
3422
+ init_biome_config();
3259
3423
  init_config();
3260
3424
  import * as fs8 from "fs";
3261
3425
  import * as path9 from "path";
@@ -3344,6 +3508,7 @@ var AGENTS = {
3344
3508
  };
3345
3509
  var CORE_FILES = [
3346
3510
  "server.ts",
3511
+ "biome-config.ts",
3347
3512
  "module-graph.ts",
3348
3513
  "tsserver-client.ts",
3349
3514
  "graph-queries.ts",
@@ -3777,43 +3942,6 @@ function deregisterAntigravityMcp(projectRoot3) {
3777
3942
  }
3778
3943
  }
3779
3944
  }
3780
- function ensureTsconfigExclude(projectRoot3) {
3781
- const tsconfigPath3 = path9.resolve(projectRoot3, "tsconfig.json");
3782
- if (!fs8.existsSync(tsconfigPath3)) return;
3783
- try {
3784
- const raw = fs8.readFileSync(tsconfigPath3, "utf-8");
3785
- const excludeArrayMatch = raw.match(/("exclude"\s*:\s*\[)([\s\S]*?)(\])/);
3786
- if (excludeArrayMatch && /["']plugins(?:\/\*\*|\/\*|)["']/.test(excludeArrayMatch[2])) {
3787
- return;
3788
- }
3789
- if (excludeArrayMatch) {
3790
- const updated = raw.replace(
3791
- /("exclude"\s*:\s*\[)([\s\S]*?)(\])/,
3792
- (_match, open, items, close) => {
3793
- const trimmed = items.trimEnd();
3794
- const needsComma = trimmed.length > 0 && !trimmed.endsWith(",");
3795
- return `${open}${items.trimEnd()}${needsComma ? "," : ""}
3796
- "plugins/**"${close}`;
3797
- }
3798
- );
3799
- fs8.writeFileSync(tsconfigPath3, updated);
3800
- } else {
3801
- const lastBrace = raw.lastIndexOf("}");
3802
- if (lastBrace !== -1) {
3803
- const before = raw.slice(0, lastBrace).trimEnd();
3804
- const needsComma = !before.endsWith(",") && !before.endsWith("{");
3805
- const patched = `${before}${needsComma ? "," : ""}
3806
- "exclude": ["plugins/**"]
3807
- }
3808
- `;
3809
- fs8.writeFileSync(tsconfigPath3, patched);
3810
- }
3811
- }
3812
- p.log.success('Added "plugins/**" to tsconfig.json exclude (prevents build errors)');
3813
- } catch {
3814
- p.log.warn('Could not update tsconfig.json \u2014 manually add "plugins/**" to the exclude array to prevent build errors');
3815
- }
3816
- }
3817
3945
  var ESLINT_CONFIG_NAMES2 = [
3818
3946
  "eslint.config.mjs",
3819
3947
  "eslint.config.js",
@@ -3846,6 +3974,13 @@ function findLintConfigs2(projectRoot3) {
3846
3974
  });
3847
3975
  }
3848
3976
  }
3977
+ for (const fileName of BIOME_CONFIG_NAMES) {
3978
+ const fullPath = path9.resolve(projectRoot3, fileName);
3979
+ if (fs8.existsSync(fullPath)) {
3980
+ configs.push({ tool: "Biome", fileName, fullPath, format: "json" });
3981
+ break;
3982
+ }
3983
+ }
3849
3984
  return configs;
3850
3985
  }
3851
3986
  function appendToArrayLiteral(raw, propertyPattern, valueLiteral) {
@@ -3912,27 +4047,37 @@ function patchOxlintModuleConfig(raw) {
3912
4047
  }
3913
4048
  return null;
3914
4049
  }
4050
+ function lintPropertyName(config) {
4051
+ if (config.tool === "ESLint") return "ignores";
4052
+ if (config.tool === "Oxlint") return "ignorePatterns";
4053
+ return "files.includes";
4054
+ }
3915
4055
  function ensureLintIgnores(projectRoot3) {
3916
4056
  const configs = findLintConfigs2(projectRoot3);
3917
4057
  for (const config of configs) {
3918
4058
  try {
3919
4059
  const raw = fs8.readFileSync(config.fullPath, "utf-8");
3920
- if (/["']plugins\/\*\*["']/.test(raw)) continue;
3921
- const updated = config.tool === "ESLint" ? patchEslintConfig(raw) : config.format === "json" ? patchOxlintJsonConfig(raw) : patchOxlintModuleConfig(raw);
4060
+ if (config.tool === "Biome" ? biomeScopeExcludes(raw, "plugins") : /["']plugins\/\*\*["']/.test(raw)) {
4061
+ continue;
4062
+ }
4063
+ const updated = config.tool === "ESLint" ? patchEslintConfig(raw) : config.tool === "Biome" ? patchBiomeConfig(raw, "plugins") : config.format === "json" ? patchOxlintJsonConfig(raw) : patchOxlintModuleConfig(raw);
3922
4064
  if (updated) {
3923
4065
  fs8.writeFileSync(config.fullPath, updated);
3924
- const propertyName = config.tool === "ESLint" ? "ignores" : "ignorePatterns";
3925
- p.log.success(`Added "plugins/**" to ${config.fileName} ${propertyName}`);
4066
+ const propertyName = lintPropertyName(config);
4067
+ const ignorePattern = config.tool === "Biome" ? "!!plugins" : "plugins/**";
4068
+ p.log.success(`Added "${ignorePattern}" to ${config.fileName} ${propertyName}`);
3926
4069
  } else {
3927
- const propertyName = config.tool === "ESLint" ? "ignores" : "ignorePatterns";
4070
+ const propertyName = lintPropertyName(config);
4071
+ const ignorePattern = config.tool === "Biome" ? "!!plugins" : "plugins/**";
3928
4072
  p.log.warn(
3929
- `Could not patch ${config.fileName} \u2014 manually add "plugins/**" to ${propertyName}`
4073
+ `Could not patch ${config.fileName} \u2014 manually add "${ignorePattern}" to ${propertyName}`
3930
4074
  );
3931
4075
  }
3932
4076
  } catch {
3933
- const propertyName = config.tool === "ESLint" ? "ignores" : "ignorePatterns";
4077
+ const propertyName = lintPropertyName(config);
4078
+ const ignorePattern = config.tool === "Biome" ? "!!plugins" : "plugins/**";
3934
4079
  p.log.warn(
3935
- `Could not update ${config.fileName} \u2014 manually add "plugins/**" to ${propertyName}`
4080
+ `Could not update ${config.fileName} \u2014 manually add "${ignorePattern}" to ${propertyName}`
3936
4081
  );
3937
4082
  }
3938
4083
  }
@@ -3978,11 +4123,12 @@ async function setup(yes2) {
3978
4123
  p.cancel("No package.json found. Run this from the root of your TypeScript project.");
3979
4124
  process.exit(1);
3980
4125
  }
3981
- if (!fs8.existsSync(tsconfigPath3)) {
3982
- p.cancel("No tsconfig.json found. typegraph-mcp requires a TypeScript project.");
3983
- process.exit(1);
4126
+ if (fs8.existsSync(tsconfigPath3)) {
4127
+ p.log.success("Found package.json and tsconfig.json");
4128
+ } else {
4129
+ p.log.success("Found package.json");
4130
+ p.log.info("No tsconfig.json found; semantic tools will use an inferred TypeScript project");
3984
4131
  }
3985
- p.log.success("Found package.json and tsconfig.json");
3986
4132
  const targetDir = path9.resolve(projectRoot3, PLUGIN_DIR_NAME);
3987
4133
  const isUpdate = fs8.existsSync(targetDir);
3988
4134
  if (isUpdate && !yes2) {
@@ -4108,7 +4254,6 @@ async function setup(yes2) {
4108
4254
  }
4109
4255
  await setupAgentInstructions(projectRoot3, selectedAgents);
4110
4256
  registerMcpServers(projectRoot3, selectedAgents);
4111
- ensureTsconfigExclude(projectRoot3);
4112
4257
  ensureLintIgnores(projectRoot3);
4113
4258
  await runVerification(targetDir, selectedAgents);
4114
4259
  }