typegraph-mcp 0.9.47 → 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
+ }
198
199
  });
200
+ const warmStart = performance.now();
201
+ await this.sendRequest("compilerOptionsForInferredProjects", {
202
+ options: { allowJs: true, checkJs: false }
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
  }
1096
- }
1097
- }
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
- }));
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" }
1108
+ }
1197
1109
  }
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;
1110
+ }`
1111
+ );
1211
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
+ );
1212
1118
  }
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
- }));
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
+ );
1227
1204
  }
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 ?? [];
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
+ }
1236
1269
  }
1237
- };
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
 
@@ -1577,8 +1671,9 @@ async function selectQuickInfoSymbol(client2, file, symbols, sourceLines) {
1577
1671
  }
1578
1672
  return null;
1579
1673
  }
1580
- function findTestFile(rootDir) {
1674
+ function findTestFile(rootDir, excludedPaths2 = []) {
1581
1675
  const candidates = [];
1676
+ const normalizedExclusions = excludedPaths2.map((excludedPath) => path6.resolve(excludedPath));
1582
1677
  function walk(dir, depth) {
1583
1678
  if (depth > 5 || candidates.length >= 30) return;
1584
1679
  let entries;
@@ -1590,7 +1685,11 @@ function findTestFile(rootDir) {
1590
1685
  for (const entry of entries) {
1591
1686
  if (entry.isDirectory()) {
1592
1687
  if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
1593
- 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);
1594
1693
  } else if (entry.isFile()) {
1595
1694
  const name = entry.name;
1596
1695
  if (name.endsWith(".d.ts") || name.endsWith(".test.ts") || name.endsWith(".spec.ts"))
@@ -1598,7 +1697,7 @@ function findTestFile(rootDir) {
1598
1697
  if (!name.endsWith(".ts") && !name.endsWith(".tsx")) continue;
1599
1698
  try {
1600
1699
  const stat = fs5.statSync(path6.join(dir, name));
1601
- if (stat.size > 200 && stat.size < 5e4) {
1700
+ if (stat.size > 0 && stat.size < 5e4) {
1602
1701
  candidates.push({ file: path6.join(dir, name), size: stat.size });
1603
1702
  }
1604
1703
  } catch {
@@ -1622,7 +1721,8 @@ function findImporter(graph, file) {
1622
1721
  return (preferred ?? revEdges[0]).target;
1623
1722
  }
1624
1723
  async function main2(configOverride) {
1625
- 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] : [];
1626
1726
  let passed = 0;
1627
1727
  let failed = 0;
1628
1728
  let skipped = 0;
@@ -1645,7 +1745,7 @@ async function main2(configOverride) {
1645
1745
  console.log("=====================");
1646
1746
  console.log(`Project root: ${projectRoot3}`);
1647
1747
  console.log("");
1648
- const testFile = findTestFile(projectRoot3);
1748
+ const testFile = findTestFile(projectRoot3, excludedPaths2);
1649
1749
  if (!testFile) {
1650
1750
  console.log(" No suitable .ts file found in project. Cannot run smoke tests.");
1651
1751
  return { passed, failed: failed + 1, skipped };
@@ -1658,7 +1758,7 @@ async function main2(configOverride) {
1658
1758
  let t0;
1659
1759
  t0 = performance.now();
1660
1760
  try {
1661
- const result = await buildGraph(projectRoot3, tsconfigPath3);
1761
+ const result = await buildGraph(projectRoot3, tsconfigPath3, excludedPaths2);
1662
1762
  graph = result.graph;
1663
1763
  const ms = performance.now() - t0;
1664
1764
  const edgeCount = [...graph.forward.values()].reduce((s, e) => s + e.length, 0);
@@ -2409,14 +2509,16 @@ async function benchmarkAccuracy(client2, graph) {
2409
2509
  return scenarios;
2410
2510
  }
2411
2511
  async function main3(config) {
2412
- ({ 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] : [];
2413
2515
  console.log("");
2414
2516
  console.log("typegraph-mcp Benchmark");
2415
2517
  console.log("=======================");
2416
2518
  console.log(`Project: ${projectRoot}`);
2417
2519
  console.log("");
2418
2520
  const graphStart = performance.now();
2419
- const { graph } = await buildGraph(projectRoot, tsconfigPath);
2521
+ const { graph } = await buildGraph(projectRoot, tsconfigPath, excludedPaths2);
2420
2522
  const graphMs = performance.now() - graphStart;
2421
2523
  const edgeCount = [...graph.forward.values()].reduce((s, e) => s + e.length, 0);
2422
2524
  console.log(`Module graph: ${graph.files.size} files, ${edgeCount} edges [${graphMs.toFixed(0)}ms]`);
@@ -2770,37 +2872,44 @@ async function main4() {
2770
2872
  log3(`tsconfig: ${tsconfigPath2}`);
2771
2873
  const [, graphResult] = await Promise.all([
2772
2874
  client.start(),
2773
- buildGraph(projectRoot2, tsconfigPath2)
2875
+ buildGraph(projectRoot2, tsconfigPath2, excludedPaths)
2774
2876
  ]);
2775
2877
  moduleGraph = graphResult.graph;
2776
2878
  moduleResolver = graphResult.resolver;
2777
- startWatcher(projectRoot2, moduleGraph, graphResult.resolver, {
2778
- onFileUpdated: (filePath) => client.reloadOpenFile(filePath).then(
2779
- (wasOpen) => {
2780
- if (!wasOpen) client.markProjectsDirty();
2781
- },
2782
- (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);
2783
2895
  client.markProjectsDirty();
2784
- log3(`Failed to reload open file ${relPath2(filePath)}:`, err);
2785
2896
  }
2786
- ),
2787
- onFileDeleted: (filePath) => {
2788
- client.closeFile(filePath);
2789
- client.markProjectsDirty();
2790
- }
2791
- });
2897
+ },
2898
+ excludedPaths
2899
+ );
2792
2900
  const transport = new StdioServerTransport();
2793
2901
  await mcpServer.connect(transport);
2794
2902
  log3("MCP server connected and ready");
2795
2903
  }
2796
- 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;
2797
2905
  var init_server = __esm({
2798
2906
  "server.ts"() {
2799
2907
  init_tsserver_client();
2800
2908
  init_module_graph();
2801
2909
  init_graph_queries();
2802
2910
  init_config();
2803
- ({ projectRoot: projectRoot2, tsconfigPath: tsconfigPath2 } = resolveConfig(import.meta.dirname));
2911
+ ({ projectRoot: projectRoot2, tsconfigPath: tsconfigPath2, toolDir, toolIsEmbedded } = resolveConfig(import.meta.dirname));
2912
+ excludedPaths = toolIsEmbedded ? [toolDir] : [];
2804
2913
  log3 = (...args2) => console.error("[typegraph]", ...args2);
2805
2914
  client = new TsServerClient(projectRoot2, tsconfigPath2);
2806
2915
  mcpServer = new McpServer({
@@ -3310,6 +3419,7 @@ var init_server = __esm({
3310
3419
  });
3311
3420
 
3312
3421
  // cli.ts
3422
+ init_biome_config();
3313
3423
  init_config();
3314
3424
  import * as fs8 from "fs";
3315
3425
  import * as path9 from "path";
@@ -3398,6 +3508,7 @@ var AGENTS = {
3398
3508
  };
3399
3509
  var CORE_FILES = [
3400
3510
  "server.ts",
3511
+ "biome-config.ts",
3401
3512
  "module-graph.ts",
3402
3513
  "tsserver-client.ts",
3403
3514
  "graph-queries.ts",
@@ -3831,43 +3942,6 @@ function deregisterAntigravityMcp(projectRoot3) {
3831
3942
  }
3832
3943
  }
3833
3944
  }
3834
- function ensureTsconfigExclude(projectRoot3) {
3835
- const tsconfigPath3 = path9.resolve(projectRoot3, "tsconfig.json");
3836
- if (!fs8.existsSync(tsconfigPath3)) return;
3837
- try {
3838
- const raw = fs8.readFileSync(tsconfigPath3, "utf-8");
3839
- const excludeArrayMatch = raw.match(/("exclude"\s*:\s*\[)([\s\S]*?)(\])/);
3840
- if (excludeArrayMatch && /["']plugins(?:\/\*\*|\/\*|)["']/.test(excludeArrayMatch[2])) {
3841
- return;
3842
- }
3843
- if (excludeArrayMatch) {
3844
- const updated = raw.replace(
3845
- /("exclude"\s*:\s*\[)([\s\S]*?)(\])/,
3846
- (_match, open, items, close) => {
3847
- const trimmed = items.trimEnd();
3848
- const needsComma = trimmed.length > 0 && !trimmed.endsWith(",");
3849
- return `${open}${items.trimEnd()}${needsComma ? "," : ""}
3850
- "plugins/**"${close}`;
3851
- }
3852
- );
3853
- fs8.writeFileSync(tsconfigPath3, updated);
3854
- } else {
3855
- const lastBrace = raw.lastIndexOf("}");
3856
- if (lastBrace !== -1) {
3857
- const before = raw.slice(0, lastBrace).trimEnd();
3858
- const needsComma = !before.endsWith(",") && !before.endsWith("{");
3859
- const patched = `${before}${needsComma ? "," : ""}
3860
- "exclude": ["plugins/**"]
3861
- }
3862
- `;
3863
- fs8.writeFileSync(tsconfigPath3, patched);
3864
- }
3865
- }
3866
- p.log.success('Added "plugins/**" to tsconfig.json exclude (prevents build errors)');
3867
- } catch {
3868
- p.log.warn('Could not update tsconfig.json \u2014 manually add "plugins/**" to the exclude array to prevent build errors');
3869
- }
3870
- }
3871
3945
  var ESLINT_CONFIG_NAMES2 = [
3872
3946
  "eslint.config.mjs",
3873
3947
  "eslint.config.js",
@@ -3900,6 +3974,13 @@ function findLintConfigs2(projectRoot3) {
3900
3974
  });
3901
3975
  }
3902
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
+ }
3903
3984
  return configs;
3904
3985
  }
3905
3986
  function appendToArrayLiteral(raw, propertyPattern, valueLiteral) {
@@ -3966,27 +4047,37 @@ function patchOxlintModuleConfig(raw) {
3966
4047
  }
3967
4048
  return null;
3968
4049
  }
4050
+ function lintPropertyName(config) {
4051
+ if (config.tool === "ESLint") return "ignores";
4052
+ if (config.tool === "Oxlint") return "ignorePatterns";
4053
+ return "files.includes";
4054
+ }
3969
4055
  function ensureLintIgnores(projectRoot3) {
3970
4056
  const configs = findLintConfigs2(projectRoot3);
3971
4057
  for (const config of configs) {
3972
4058
  try {
3973
4059
  const raw = fs8.readFileSync(config.fullPath, "utf-8");
3974
- if (/["']plugins\/\*\*["']/.test(raw)) continue;
3975
- 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);
3976
4064
  if (updated) {
3977
4065
  fs8.writeFileSync(config.fullPath, updated);
3978
- const propertyName = config.tool === "ESLint" ? "ignores" : "ignorePatterns";
3979
- 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}`);
3980
4069
  } else {
3981
- const propertyName = config.tool === "ESLint" ? "ignores" : "ignorePatterns";
4070
+ const propertyName = lintPropertyName(config);
4071
+ const ignorePattern = config.tool === "Biome" ? "!!plugins" : "plugins/**";
3982
4072
  p.log.warn(
3983
- `Could not patch ${config.fileName} \u2014 manually add "plugins/**" to ${propertyName}`
4073
+ `Could not patch ${config.fileName} \u2014 manually add "${ignorePattern}" to ${propertyName}`
3984
4074
  );
3985
4075
  }
3986
4076
  } catch {
3987
- const propertyName = config.tool === "ESLint" ? "ignores" : "ignorePatterns";
4077
+ const propertyName = lintPropertyName(config);
4078
+ const ignorePattern = config.tool === "Biome" ? "!!plugins" : "plugins/**";
3988
4079
  p.log.warn(
3989
- `Could not update ${config.fileName} \u2014 manually add "plugins/**" to ${propertyName}`
4080
+ `Could not update ${config.fileName} \u2014 manually add "${ignorePattern}" to ${propertyName}`
3990
4081
  );
3991
4082
  }
3992
4083
  }
@@ -4032,11 +4123,12 @@ async function setup(yes2) {
4032
4123
  p.cancel("No package.json found. Run this from the root of your TypeScript project.");
4033
4124
  process.exit(1);
4034
4125
  }
4035
- if (!fs8.existsSync(tsconfigPath3)) {
4036
- p.cancel("No tsconfig.json found. typegraph-mcp requires a TypeScript project.");
4037
- 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");
4038
4131
  }
4039
- p.log.success("Found package.json and tsconfig.json");
4040
4132
  const targetDir = path9.resolve(projectRoot3, PLUGIN_DIR_NAME);
4041
4133
  const isUpdate = fs8.existsSync(targetDir);
4042
4134
  if (isUpdate && !yes2) {
@@ -4162,7 +4254,6 @@ async function setup(yes2) {
4162
4254
  }
4163
4255
  await setupAgentInstructions(projectRoot3, selectedAgents);
4164
4256
  registerMcpServers(projectRoot3, selectedAgents);
4165
- ensureTsconfigExclude(projectRoot3);
4166
4257
  ensureLintIgnores(projectRoot3);
4167
4258
  await runVerification(targetDir, selectedAgents);
4168
4259
  }