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/check.js CHANGED
@@ -21,14 +21,25 @@ __export(module_graph_exports, {
21
21
  });
22
22
  import { parseSync } from "oxc-parser";
23
23
  import { ResolverFactory } from "oxc-resolver";
24
- import * as fs from "fs";
25
- import * as path2 from "path";
26
- function discoverFiles(rootDir) {
24
+ import * as fs2 from "fs";
25
+ import * as path3 from "path";
26
+ function normalizeExcludedPaths(rootDir, excludedPaths) {
27
+ return excludedPaths.map(
28
+ (excludedPath) => path3.resolve(rootDir, excludedPath)
29
+ );
30
+ }
31
+ function isExcluded(filePath, excludedPaths) {
32
+ return excludedPaths.some(
33
+ (excludedPath) => filePath === excludedPath || filePath.startsWith(excludedPath + path3.sep)
34
+ );
35
+ }
36
+ function discoverFiles(rootDir, excludedPaths = []) {
27
37
  const files = [];
38
+ const normalizedExclusions = normalizeExcludedPaths(rootDir, excludedPaths);
28
39
  function walk(dir) {
29
40
  let entries;
30
41
  try {
31
- entries = fs.readdirSync(dir, { withFileTypes: true });
42
+ entries = fs2.readdirSync(dir, { withFileTypes: true });
32
43
  } catch {
33
44
  return;
34
45
  }
@@ -36,14 +47,16 @@ function discoverFiles(rootDir) {
36
47
  if (entry.isDirectory()) {
37
48
  if (SKIP_DIRS.has(entry.name)) continue;
38
49
  if (entry.name.startsWith(".") && dir !== rootDir) continue;
39
- walk(path2.join(dir, entry.name));
50
+ const childDir = path3.join(dir, entry.name);
51
+ if (isExcluded(childDir, normalizedExclusions)) continue;
52
+ walk(childDir);
40
53
  } else if (entry.isFile()) {
41
54
  const name = entry.name;
42
55
  if (SKIP_FILES.has(name)) continue;
43
56
  if (name.endsWith(".d.ts") || name.endsWith(".d.mts") || name.endsWith(".d.cts")) continue;
44
- const ext = path2.extname(name);
57
+ const ext = path3.extname(name);
45
58
  if (TS_EXTENSIONS.has(ext)) {
46
- files.push(path2.join(dir, name));
59
+ files.push(path3.join(dir, name));
47
60
  }
48
61
  }
49
62
  }
@@ -98,25 +111,25 @@ function parseFileImports(filePath, source) {
98
111
  }
99
112
  function distToSource(resolvedPath, projectRoot) {
100
113
  if (!resolvedPath.startsWith(projectRoot)) return resolvedPath;
101
- const rel = path2.relative(projectRoot, resolvedPath);
102
- const distIdx = rel.indexOf("dist" + path2.sep);
114
+ const rel = path3.relative(projectRoot, resolvedPath);
115
+ const distIdx = rel.indexOf("dist" + path3.sep);
103
116
  if (distIdx === -1) return resolvedPath;
104
117
  const prefix = rel.slice(0, distIdx);
105
118
  const afterDist = rel.slice(distIdx + 5);
106
119
  const withoutExt = afterDist.replace(/\.(m?j|c)s$/, "");
107
120
  for (const ext of SOURCE_EXTS) {
108
- const candidate = path2.resolve(projectRoot, prefix, "src", withoutExt + ext);
109
- if (fs.existsSync(candidate)) return candidate;
121
+ const candidate = path3.resolve(projectRoot, prefix, "src", withoutExt + ext);
122
+ if (fs2.existsSync(candidate)) return candidate;
110
123
  }
111
124
  for (const ext of SOURCE_EXTS) {
112
- const candidate = path2.resolve(projectRoot, prefix, withoutExt + ext);
113
- if (fs.existsSync(candidate)) return candidate;
125
+ const candidate = path3.resolve(projectRoot, prefix, withoutExt + ext);
126
+ if (fs2.existsSync(candidate)) return candidate;
114
127
  }
115
128
  if (withoutExt.endsWith("/index")) {
116
129
  const dirPath = withoutExt.slice(0, -6);
117
130
  for (const ext of SOURCE_EXTS) {
118
- const candidate = path2.resolve(projectRoot, prefix, "src", dirPath + ext);
119
- if (fs.existsSync(candidate)) return candidate;
131
+ const candidate = path3.resolve(projectRoot, prefix, "src", dirPath + ext);
132
+ if (fs2.existsSync(candidate)) return candidate;
120
133
  }
121
134
  }
122
135
  return resolvedPath;
@@ -126,9 +139,9 @@ function resolveProjectImport(resolver, fromDir, specifier, projectRoot) {
126
139
  const result = resolver.sync(fromDir, specifier);
127
140
  if (result.path && !result.path.includes("node_modules")) {
128
141
  const mapped = distToSource(result.path, projectRoot);
129
- const ext = path2.extname(mapped);
142
+ const ext = path3.extname(mapped);
130
143
  if (!TS_EXTENSIONS.has(ext)) return null;
131
- if (SKIP_FILES.has(path2.basename(mapped))) return null;
144
+ if (SKIP_FILES.has(path3.basename(mapped))) return null;
132
145
  return mapped;
133
146
  }
134
147
  } catch {
@@ -136,11 +149,9 @@ function resolveProjectImport(resolver, fromDir, specifier, projectRoot) {
136
149
  return null;
137
150
  }
138
151
  function createResolver(projectRoot, tsconfigPath) {
152
+ const configFile = path3.resolve(projectRoot, tsconfigPath);
139
153
  return new ResolverFactory({
140
- tsconfig: {
141
- configFile: path2.resolve(projectRoot, tsconfigPath),
142
- references: "auto"
143
- },
154
+ ...fs2.existsSync(configFile) ? { tsconfig: { configFile, references: "auto" } } : {},
144
155
  extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
145
156
  extensionAlias: {
146
157
  ".js": [".ts", ".tsx", ".js"],
@@ -158,7 +169,7 @@ function buildForwardEdges(files, resolver, projectRoot) {
158
169
  for (const filePath of files) {
159
170
  let source;
160
171
  try {
161
- source = fs.readFileSync(filePath, "utf-8");
172
+ source = fs2.readFileSync(filePath, "utf-8");
162
173
  } catch {
163
174
  continue;
164
175
  }
@@ -170,7 +181,7 @@ function buildForwardEdges(files, resolver, projectRoot) {
170
181
  continue;
171
182
  }
172
183
  const edges = [];
173
- const fromDir = path2.dirname(filePath);
184
+ const fromDir = path3.dirname(filePath);
174
185
  for (const raw of rawImports) {
175
186
  const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot);
176
187
  if (target) {
@@ -206,10 +217,10 @@ function buildReverseMap(forward) {
206
217
  }
207
218
  return reverse;
208
219
  }
209
- async function buildGraph(projectRoot, tsconfigPath) {
220
+ async function buildGraph(projectRoot, tsconfigPath, excludedPaths = []) {
210
221
  const startTime = performance.now();
211
222
  const resolver = createResolver(projectRoot, tsconfigPath);
212
- const fileList = discoverFiles(projectRoot);
223
+ const fileList = discoverFiles(projectRoot, excludedPaths);
213
224
  log(`Discovered ${fileList.length} source files`);
214
225
  const { forward, parseFailures } = buildForwardEdges(fileList, resolver, projectRoot);
215
226
  const reverse = buildReverseMap(forward);
@@ -237,7 +248,7 @@ function updateFile(graph, filePath, resolver, projectRoot) {
237
248
  }
238
249
  let source;
239
250
  try {
240
- source = fs.readFileSync(filePath, "utf-8");
251
+ source = fs2.readFileSync(filePath, "utf-8");
241
252
  } catch {
242
253
  removeFile(graph, filePath);
243
254
  return;
@@ -250,7 +261,7 @@ function updateFile(graph, filePath, resolver, projectRoot) {
250
261
  graph.forward.set(filePath, []);
251
262
  return;
252
263
  }
253
- const fromDir = path2.dirname(filePath);
264
+ const fromDir = path3.dirname(filePath);
254
265
  const newEdges = [];
255
266
  for (const raw of rawImports) {
256
267
  const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot);
@@ -301,22 +312,24 @@ function removeFile(graph, filePath) {
301
312
  graph.reverse.delete(filePath);
302
313
  graph.files.delete(filePath);
303
314
  }
304
- function startWatcher(projectRoot, graph, resolver, hooks) {
315
+ function startWatcher(projectRoot, graph, resolver, hooks, excludedPaths = []) {
305
316
  try {
306
- const watcher = fs.watch(
317
+ const normalizedExclusions = normalizeExcludedPaths(projectRoot, excludedPaths);
318
+ const watcher = fs2.watch(
307
319
  projectRoot,
308
320
  { recursive: true },
309
321
  (_eventType, filename) => {
310
322
  if (!filename) return;
311
- const ext = path2.extname(filename);
323
+ const ext = path3.extname(filename);
312
324
  if (!TS_EXTENSIONS.has(ext)) return;
313
- const parts = filename.split(path2.sep);
325
+ const parts = filename.split(path3.sep);
314
326
  if (parts.some((p) => SKIP_DIRS.has(p))) return;
315
- if (SKIP_FILES.has(path2.basename(filename))) return;
327
+ if (SKIP_FILES.has(path3.basename(filename))) return;
316
328
  if (filename.endsWith(".d.ts") || filename.endsWith(".d.mts") || filename.endsWith(".d.cts"))
317
329
  return;
318
- const absPath = path2.resolve(projectRoot, filename);
319
- if (fs.existsSync(absPath)) {
330
+ const absPath = path3.resolve(projectRoot, filename);
331
+ if (isExcluded(absPath, normalizedExclusions)) return;
332
+ if (fs2.existsSync(absPath)) {
320
333
  updateFile(graph, absPath, resolver, projectRoot);
321
334
  void hooks?.onFileUpdated?.(absPath);
322
335
  } else {
@@ -355,10 +368,10 @@ var init_module_graph = __esm({
355
368
  });
356
369
 
357
370
  // check.ts
358
- import * as fs2 from "fs";
359
- import * as path3 from "path";
360
- import { createRequire } from "module";
361
- import { spawn, spawnSync } from "child_process";
371
+ import * as fs3 from "fs";
372
+ import * as path4 from "path";
373
+ import { createRequire as createRequire2 } from "module";
374
+ import { spawn as spawn2, spawnSync } from "child_process";
362
375
 
363
376
  // config.ts
364
377
  import * as path from "path";
@@ -371,18 +384,84 @@ function resolveConfig(toolDir) {
371
384
  return { projectRoot, tsconfigPath, toolDir, toolIsEmbedded, toolRelPath };
372
385
  }
373
386
 
387
+ // tsserver-client.ts
388
+ import { spawn } from "child_process";
389
+ import * as path2 from "path";
390
+ import * as fs from "fs";
391
+ import { createRequire } from "module";
392
+ function readPackageVersion(packagePath) {
393
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
394
+ return pkg.version ?? "unknown";
395
+ }
396
+ function resolveTsServer(projectRoot) {
397
+ const projectRequire = createRequire(path2.resolve(projectRoot, "package.json"));
398
+ let projectVersion;
399
+ try {
400
+ const packagePath2 = projectRequire.resolve("typescript/package.json");
401
+ projectVersion = readPackageVersion(packagePath2);
402
+ const serverPath2 = projectRequire.resolve("typescript/lib/tsserver.js");
403
+ return { path: serverPath2, version: projectVersion, source: "project" };
404
+ } catch {
405
+ }
406
+ const toolRequire = createRequire(import.meta.url);
407
+ const packagePath = toolRequire.resolve("typescript/package.json");
408
+ const serverPath = toolRequire.resolve("typescript/lib/tsserver.js");
409
+ return {
410
+ path: serverPath,
411
+ version: readPackageVersion(packagePath),
412
+ source: "typegraph",
413
+ projectVersion
414
+ };
415
+ }
416
+
417
+ // biome-config.ts
418
+ var BIOME_CONFIG_NAMES = [
419
+ "biome.json",
420
+ "biome.jsonc",
421
+ ".biome.json",
422
+ ".biome.jsonc"
423
+ ];
424
+ function filesObjectMatch(raw) {
425
+ return raw.match(/(["']files["']\s*:\s*\{)([\s\S]*?)(\n?\s*\})/);
426
+ }
427
+ function filesIncludes(raw) {
428
+ const filesObject = filesObjectMatch(raw);
429
+ if (!filesObject) return null;
430
+ const includes = filesObject[2].match(/["']includes["']\s*:\s*\[([\s\S]*?)\]/);
431
+ if (!includes) return null;
432
+ return [...includes[1].matchAll(/["']([^"']+)["']/g)].map((match) => match[1]);
433
+ }
434
+ function biomeScopeExcludes(raw, parentDir) {
435
+ const escaped = parentDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
436
+ const explicitIgnore = new RegExp(
437
+ `["']!{1,2}(?:\\*\\*/)?${escaped}(?:/\\*\\*)?["']`
438
+ );
439
+ if (explicitIgnore.test(raw)) return true;
440
+ const includes = filesIncludes(raw);
441
+ if (!includes) return false;
442
+ const positivePatterns = includes.filter((pattern) => !pattern.startsWith("!"));
443
+ return !positivePatterns.some((pattern) => {
444
+ const normalized = pattern.replace(/^\.\//, "");
445
+ return normalized === "*" || normalized === "**" || normalized.startsWith("**/") || normalized.startsWith("*/") || normalized === parentDir || normalized.startsWith(`${parentDir}/`);
446
+ });
447
+ }
448
+
374
449
  // check.ts
375
- function findFirstTsFile(dir) {
450
+ function findFirstTsFile(dir, excludedPaths = []) {
376
451
  const skipDirs = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", ".wrangler", "coverage"]);
377
452
  try {
378
- for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
453
+ for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
379
454
  if (entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) {
380
- return path3.join(dir, entry.name);
455
+ return path4.join(dir, entry.name);
381
456
  }
382
457
  }
383
- for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
458
+ for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
384
459
  if (entry.isDirectory() && !skipDirs.has(entry.name) && !entry.name.startsWith(".")) {
385
- const found = findFirstTsFile(path3.join(dir, entry.name));
460
+ const childDir = path4.join(dir, entry.name);
461
+ if (excludedPaths.some(
462
+ (excludedPath) => childDir === excludedPath || childDir.startsWith(excludedPath + path4.sep)
463
+ )) continue;
464
+ const found = findFirstTsFile(childDir, excludedPaths);
386
465
  if (found) return found;
387
466
  }
388
467
  }
@@ -390,23 +469,15 @@ function findFirstTsFile(dir) {
390
469
  }
391
470
  return null;
392
471
  }
393
- function testTsserver(projectRoot) {
394
- return new Promise((resolve4) => {
395
- let tsserverPath;
396
- try {
397
- const require2 = createRequire(path3.resolve(projectRoot, "package.json"));
398
- tsserverPath = require2.resolve("typescript/lib/tsserver.js");
399
- } catch {
400
- resolve4(false);
401
- return;
402
- }
403
- const child = spawn("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
472
+ function testTsserver(projectRoot, resolution) {
473
+ return new Promise((resolve5) => {
474
+ const child = spawn2("node", [resolution.path, "--disableAutomaticTypingAcquisition"], {
404
475
  cwd: projectRoot,
405
476
  stdio: ["pipe", "pipe", "pipe"]
406
477
  });
407
478
  const timeout = setTimeout(() => {
408
479
  child.kill();
409
- resolve4(false);
480
+ resolve5(false);
410
481
  }, 1e4);
411
482
  let buffer = "";
412
483
  child.stdout.on("data", (chunk) => {
@@ -414,12 +485,12 @@ function testTsserver(projectRoot) {
414
485
  if (buffer.includes('"success":true')) {
415
486
  clearTimeout(timeout);
416
487
  child.kill();
417
- resolve4(true);
488
+ resolve5(true);
418
489
  }
419
490
  });
420
491
  child.on("error", () => {
421
492
  clearTimeout(timeout);
422
- resolve4(false);
493
+ resolve5(false);
423
494
  });
424
495
  child.on("exit", () => {
425
496
  clearTimeout(timeout);
@@ -436,9 +507,9 @@ function testTsserver(projectRoot) {
436
507
  });
437
508
  }
438
509
  function readProjectCodexConfig(projectRoot) {
439
- const configPath = path3.resolve(projectRoot, ".codex/config.toml");
440
- if (!fs2.existsSync(configPath)) return null;
441
- return fs2.readFileSync(configPath, "utf-8");
510
+ const configPath = path4.resolve(projectRoot, ".codex/config.toml");
511
+ if (!fs3.existsSync(configPath)) return null;
512
+ return fs3.readFileSync(configPath, "utf-8");
442
513
  }
443
514
  function hasCodexTypegraphRegistration(content) {
444
515
  return /\[mcp_servers\.typegraph\]/.test(content);
@@ -452,17 +523,17 @@ function hasCompleteCodexTypegraphRegistration(content) {
452
523
  function hasTrustedCodexProject(projectRoot) {
453
524
  const home = process.env.HOME;
454
525
  if (!home) return null;
455
- const globalConfigPath = path3.join(home, ".codex/config.toml");
456
- if (!fs2.existsSync(globalConfigPath)) return null;
457
- const lines = fs2.readFileSync(globalConfigPath, "utf-8").split(/\r?\n/);
526
+ const globalConfigPath = path4.join(home, ".codex/config.toml");
527
+ if (!fs3.existsSync(globalConfigPath)) return null;
528
+ const lines = fs3.readFileSync(globalConfigPath, "utf-8").split(/\r?\n/);
458
529
  let currentProject = null;
459
530
  let currentTrusted = false;
460
- const matchesTrustedProject = () => currentProject !== null && currentTrusted && (projectRoot === currentProject || projectRoot.startsWith(currentProject + path3.sep));
531
+ const matchesTrustedProject = () => currentProject !== null && currentTrusted && (projectRoot === currentProject || projectRoot.startsWith(currentProject + path4.sep));
461
532
  for (const line of lines) {
462
533
  const sectionMatch = line.match(/^\[projects\."([^"]+)"\]\s*$/);
463
534
  if (sectionMatch) {
464
535
  if (matchesTrustedProject()) return true;
465
- currentProject = path3.resolve(sectionMatch[1]);
536
+ currentProject = path4.resolve(sectionMatch[1]);
466
537
  currentTrusted = false;
467
538
  continue;
468
539
  }
@@ -492,9 +563,9 @@ function hasCompleteJsonTypegraphRegistration(config, serverName, projectRoot) {
492
563
  return typeof command === "string" && command.length > 0 && Array.isArray(args) && serializedArgs.includes("server.ts") && serializedArgs.includes("tsx") && typeof env === "object" && env !== null && env["TYPEGRAPH_PROJECT_ROOT"] === projectRoot && typeof env["TYPEGRAPH_TSCONFIG"] === "string";
493
564
  }
494
565
  function readJsonConfig(configPath) {
495
- if (!fs2.existsSync(configPath)) return null;
566
+ if (!fs3.existsSync(configPath)) return null;
496
567
  try {
497
- return JSON.parse(fs2.readFileSync(configPath, "utf-8"));
568
+ return JSON.parse(fs3.readFileSync(configPath, "utf-8"));
498
569
  } catch {
499
570
  return null;
500
571
  }
@@ -502,8 +573,8 @@ function readJsonConfig(configPath) {
502
573
  function getAntigravityMcpConfigPaths() {
503
574
  const home = process.env.HOME || "";
504
575
  return [
505
- path3.join(home, ".gemini/antigravity/mcp_config.json"),
506
- path3.join(home, ".gemini/antigravity-cli/plugins/typegraph-mcp/mcp_config.json")
576
+ path4.join(home, ".gemini/antigravity/mcp_config.json"),
577
+ path4.join(home, ".gemini/antigravity-cli/plugins/typegraph-mcp/mcp_config.json")
507
578
  ];
508
579
  }
509
580
  function findAntigravityRegistration(projectRoot) {
@@ -516,37 +587,6 @@ function findAntigravityRegistration(projectRoot) {
516
587
  }
517
588
  return null;
518
589
  }
519
- function readProjectPackageJson(projectRoot) {
520
- const packageJsonPath = path3.resolve(projectRoot, "package.json");
521
- if (!fs2.existsSync(packageJsonPath)) return null;
522
- try {
523
- return JSON.parse(fs2.readFileSync(packageJsonPath, "utf-8"));
524
- } catch {
525
- return null;
526
- }
527
- }
528
- function getProjectInstallCommand(projectRoot, packageJson) {
529
- const packageManager = typeof packageJson?.["packageManager"] === "string" ? packageJson["packageManager"] : "";
530
- if (packageManager.startsWith("pnpm@") || fs2.existsSync(path3.join(projectRoot, "pnpm-lock.yaml"))) {
531
- return "pnpm install";
532
- }
533
- if (packageManager.startsWith("yarn@") || fs2.existsSync(path3.join(projectRoot, "yarn.lock"))) {
534
- return "yarn install";
535
- }
536
- return "npm install";
537
- }
538
- function hasDeclaredDependency(packageJson, packageName) {
539
- const depKeys = [
540
- "dependencies",
541
- "devDependencies",
542
- "peerDependencies",
543
- "optionalDependencies"
544
- ];
545
- return depKeys.some((key) => {
546
- const deps = packageJson?.[key];
547
- return typeof deps === "object" && deps !== null && packageName in deps;
548
- });
549
- }
550
590
  var ESLINT_CONFIG_NAMES = [
551
591
  "eslint.config.mjs",
552
592
  "eslint.config.js",
@@ -563,26 +603,32 @@ var OXLINT_CONFIG_NAMES = [
563
603
  function findLintConfigs(projectRoot) {
564
604
  const configs = [];
565
605
  for (const fileName of ESLINT_CONFIG_NAMES) {
566
- const fullPath = path3.resolve(projectRoot, fileName);
567
- if (fs2.existsSync(fullPath)) {
606
+ const fullPath = path4.resolve(projectRoot, fileName);
607
+ if (fs3.existsSync(fullPath)) {
568
608
  configs.push({ tool: "ESLint", fileName, fullPath, propertyName: "ignores" });
569
609
  }
570
610
  }
571
611
  for (const fileName of OXLINT_CONFIG_NAMES) {
572
- const fullPath = path3.resolve(projectRoot, fileName);
573
- if (fs2.existsSync(fullPath)) {
612
+ const fullPath = path4.resolve(projectRoot, fileName);
613
+ if (fs3.existsSync(fullPath)) {
574
614
  configs.push({ tool: "Oxlint", fileName, fullPath, propertyName: "ignorePatterns" });
575
615
  }
576
616
  }
617
+ for (const fileName of BIOME_CONFIG_NAMES) {
618
+ const fullPath = path4.resolve(projectRoot, fileName);
619
+ if (fs3.existsSync(fullPath)) {
620
+ configs.push({ tool: "Biome", fileName, fullPath, propertyName: "files.includes" });
621
+ break;
622
+ }
623
+ }
577
624
  return configs;
578
625
  }
579
626
  async function main(configOverride) {
580
627
  const { projectRoot, tsconfigPath, toolDir, toolIsEmbedded, toolRelPath } = configOverride ?? resolveConfig(import.meta.dirname);
628
+ const excludedPaths = toolIsEmbedded ? [toolDir] : [];
581
629
  let passed = 0;
582
630
  let failed = 0;
583
631
  let warned = 0;
584
- const projectPackageJson = readProjectPackageJson(projectRoot);
585
- const installCommand = getProjectInstallCommand(projectRoot, projectPackageJson);
586
632
  function pass(msg) {
587
633
  console.log(` \u2713 ${msg}`);
588
634
  passed++;
@@ -612,36 +658,39 @@ async function main(configOverride) {
612
658
  } else {
613
659
  fail(`Node.js ${nodeVersion} is too old`, "Upgrade Node.js to >= 22");
614
660
  }
615
- const tsxInRoot = fs2.existsSync(path3.join(projectRoot, "node_modules/.bin/tsx"));
616
- const tsxInTool = fs2.existsSync(path3.join(toolDir, "node_modules/.bin/tsx"));
661
+ const tsxInRoot = fs3.existsSync(path4.join(projectRoot, "node_modules/.bin/tsx"));
662
+ const tsxInTool = fs3.existsSync(path4.join(toolDir, "node_modules/.bin/tsx"));
617
663
  if (tsxInRoot || tsxInTool) {
618
664
  pass(`tsx available (in ${tsxInRoot ? "project" : "tool"} node_modules)`);
619
665
  } else {
620
666
  pass("tsx available (via npx/global)");
621
667
  }
622
- let tsVersion = null;
668
+ let tsResolution = null;
623
669
  try {
624
- const require2 = createRequire(path3.resolve(projectRoot, "package.json"));
625
- const tsserverPath = require2.resolve("typescript/lib/tsserver.js");
626
- const tsPkgPath = path3.resolve(path3.dirname(tsserverPath), "..", "package.json");
627
- const tsPkg = JSON.parse(fs2.readFileSync(tsPkgPath, "utf-8"));
628
- tsVersion = tsPkg.version;
629
- pass(`TypeScript found (v${tsVersion})`);
670
+ tsResolution = resolveTsServer(projectRoot);
671
+ if (tsResolution.source === "project") {
672
+ pass(`TypeScript found (v${tsResolution.version})`);
673
+ } else if (tsResolution.projectVersion) {
674
+ pass(
675
+ `Compatible tsserver found (TypeScript v${tsResolution.version} tool runtime; project uses v${tsResolution.projectVersion})`
676
+ );
677
+ } else {
678
+ pass(`TypeScript found in typegraph-mcp (v${tsResolution.version})`);
679
+ }
630
680
  } catch {
631
- const hasDeclaredTs = hasDeclaredDependency(projectPackageJson, "typescript");
632
681
  fail(
633
- hasDeclaredTs ? "TypeScript is declared but not installed in project" : "TypeScript not found in project",
634
- hasDeclaredTs ? `Run \`${installCommand}\` to install project dependencies` : `Add \`typescript\` to devDependencies and run \`${installCommand}\``
682
+ "Compatible tsserver runtime not found",
683
+ `Run \`cd ${toolRelPath} && npm install --include=optional\``
635
684
  );
636
685
  }
637
- const tsconfigAbs = path3.resolve(projectRoot, tsconfigPath);
638
- if (fs2.existsSync(tsconfigAbs)) {
686
+ const tsconfigAbs = path4.resolve(projectRoot, tsconfigPath);
687
+ if (fs3.existsSync(tsconfigAbs)) {
639
688
  pass(`tsconfig.json exists at ${tsconfigPath}`);
640
689
  } else {
641
- fail(`tsconfig.json not found at ${tsconfigPath}`, `Create a tsconfig.json at ${tsconfigPath}`);
690
+ pass("No tsconfig.json; semantic tools will use an inferred TypeScript project");
642
691
  }
643
- const pluginMcpPath = path3.join(toolDir, ".mcp.json");
644
- const hasPluginMcp = fs2.existsSync(pluginMcpPath) && fs2.existsSync(path3.join(toolDir, ".claude-plugin/plugin.json"));
692
+ const pluginMcpPath = path4.join(toolDir, ".mcp.json");
693
+ const hasPluginMcp = fs3.existsSync(pluginMcpPath) && fs3.existsSync(path4.join(toolDir, ".claude-plugin/plugin.json"));
645
694
  const projectCodexConfig = readProjectCodexConfig(projectRoot);
646
695
  const hasProjectCodexRegistration = projectCodexConfig !== null && hasCompleteCodexTypegraphRegistration(projectCodexConfig);
647
696
  const codexGet = spawnSync("codex", ["mcp", "get", "typegraph"], {
@@ -655,7 +704,7 @@ async function main(configOverride) {
655
704
  } else if (hasPluginMcp) {
656
705
  pass("MCP registered via plugin (.mcp.json + .claude-plugin/ present)");
657
706
  } else if (projectCodexConfig !== null) {
658
- const codexConfigPath = path3.resolve(projectRoot, ".codex/config.toml");
707
+ const codexConfigPath = path4.resolve(projectRoot, ".codex/config.toml");
659
708
  const hasSection = hasCodexTypegraphRegistration(projectCodexConfig);
660
709
  const hasCommand = /command\s*=\s*"[^"]+"/.test(projectCodexConfig);
661
710
  const hasArgs = /args\s*=\s*\[[\s\S]*\]/.test(projectCodexConfig);
@@ -689,11 +738,11 @@ async function main(configOverride) {
689
738
  } else if (antigravityRegistrationPath !== null) {
690
739
  pass(`MCP registered in Antigravity config (${antigravityRegistrationPath})`);
691
740
  } else {
692
- const codexConfigPath = path3.resolve(projectRoot, ".codex/config.toml");
693
- const mcpJsonPath = path3.resolve(projectRoot, ".claude/mcp.json");
694
- if (fs2.existsSync(mcpJsonPath)) {
741
+ const codexConfigPath = path4.resolve(projectRoot, ".codex/config.toml");
742
+ const mcpJsonPath = path4.resolve(projectRoot, ".claude/mcp.json");
743
+ if (fs3.existsSync(mcpJsonPath)) {
695
744
  try {
696
- const mcpJson = JSON.parse(fs2.readFileSync(mcpJsonPath, "utf-8"));
745
+ const mcpJson = JSON.parse(fs3.readFileSync(mcpJsonPath, "utf-8"));
697
746
  const tsNav = mcpJson?.mcpServers?.["typegraph"];
698
747
  if (tsNav) {
699
748
  const hasCommand = tsNav.command === "npx";
@@ -712,7 +761,7 @@ async function main(configOverride) {
712
761
  );
713
762
  }
714
763
  } else {
715
- const serverPath = toolIsEmbedded ? `./${toolRelPath}/server.ts` : path3.resolve(toolDir, "server.ts");
764
+ const serverPath = toolIsEmbedded ? `./${toolRelPath}/server.ts` : path4.resolve(toolDir, "server.ts");
716
765
  fail(
717
766
  "MCP entry 'typegraph' not found in .claude/mcp.json",
718
767
  `Add to .claude/mcp.json:
@@ -740,11 +789,17 @@ async function main(configOverride) {
740
789
  );
741
790
  }
742
791
  }
743
- const toolNodeModules = path3.join(toolDir, "node_modules");
744
- if (fs2.existsSync(toolNodeModules)) {
745
- const requiredPkgs = ["@modelcontextprotocol/sdk", "oxc-parser", "oxc-resolver", "zod"];
792
+ const toolNodeModules = path4.join(toolDir, "node_modules");
793
+ if (fs3.existsSync(toolNodeModules)) {
794
+ const requiredPkgs = [
795
+ "@modelcontextprotocol/sdk",
796
+ "oxc-parser",
797
+ "oxc-resolver",
798
+ "typescript",
799
+ "zod"
800
+ ];
746
801
  const missing = requiredPkgs.filter(
747
- (pkg) => !fs2.existsSync(path3.join(toolNodeModules, ...pkg.split("/")))
802
+ (pkg) => !fs3.existsSync(path4.join(toolNodeModules, ...pkg.split("/")))
748
803
  );
749
804
  if (missing.length === 0) {
750
805
  pass(`Dependencies installed (${requiredPkgs.length} packages)`);
@@ -755,7 +810,7 @@ async function main(configOverride) {
755
810
  fail("typegraph-mcp dependencies not installed", `Run \`cd ${toolRelPath} && npm install\``);
756
811
  }
757
812
  try {
758
- const oxcParserReq = createRequire(path3.join(toolDir, "package.json"));
813
+ const oxcParserReq = createRequire2(path4.join(toolDir, "package.json"));
759
814
  const { parseSync: parseSync2 } = await import(oxcParserReq.resolve("oxc-parser"));
760
815
  const result = parseSync2("test.ts", 'import { x } from "./y";');
761
816
  if (result.module?.staticImports?.length === 1) {
@@ -773,18 +828,18 @@ async function main(configOverride) {
773
828
  );
774
829
  }
775
830
  try {
776
- const oxcResolverReq = createRequire(path3.join(toolDir, "package.json"));
831
+ const oxcResolverReq = createRequire2(path4.join(toolDir, "package.json"));
777
832
  const { ResolverFactory: ResolverFactory2 } = await import(oxcResolverReq.resolve("oxc-resolver"));
778
833
  const resolver = new ResolverFactory2({
779
- tsconfig: { configFile: tsconfigAbs, references: "auto" },
834
+ ...fs3.existsSync(tsconfigAbs) ? { tsconfig: { configFile: tsconfigAbs, references: "auto" } } : {},
780
835
  extensions: [".ts", ".tsx", ".js"],
781
836
  extensionAlias: { ".js": [".ts", ".tsx", ".js"] }
782
837
  });
783
838
  let resolveOk = false;
784
- const testFile = findFirstTsFile(projectRoot);
839
+ const testFile = findFirstTsFile(projectRoot, excludedPaths);
785
840
  if (testFile) {
786
- const dir = path3.dirname(testFile);
787
- const base = "./" + path3.basename(testFile);
841
+ const dir = path4.dirname(testFile);
842
+ const base = "./" + path4.basename(testFile);
788
843
  const result = resolver.sync(dir, base);
789
844
  resolveOk = !!result.path;
790
845
  }
@@ -802,9 +857,9 @@ async function main(configOverride) {
802
857
  `Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && npm install\``
803
858
  );
804
859
  }
805
- if (tsVersion) {
860
+ if (tsResolution) {
806
861
  try {
807
- const ok = await testTsserver(projectRoot);
862
+ const ok = await testTsserver(projectRoot, tsResolution);
808
863
  if (ok) {
809
864
  pass("tsserver responds to configure");
810
865
  } else {
@@ -825,12 +880,16 @@ async function main(configOverride) {
825
880
  try {
826
881
  let buildGraph2;
827
882
  try {
828
- ({ buildGraph: buildGraph2 } = await import(path3.resolve(toolDir, "module-graph.js")));
883
+ ({ buildGraph: buildGraph2 } = await import(path4.resolve(toolDir, "module-graph.js")));
829
884
  } catch {
830
885
  ({ buildGraph: buildGraph2 } = await Promise.resolve().then(() => (init_module_graph(), module_graph_exports)));
831
886
  }
832
887
  const start = performance.now();
833
- const { graph } = await buildGraph2(projectRoot, tsconfigPath);
888
+ const { graph } = await buildGraph2(
889
+ projectRoot,
890
+ tsconfigPath,
891
+ excludedPaths
892
+ );
834
893
  const elapsed = (performance.now() - start).toFixed(0);
835
894
  const edgeCount = [...graph.forward.values()].reduce(
836
895
  (s, e) => s + e.length,
@@ -858,35 +917,36 @@ async function main(configOverride) {
858
917
  if (toolIsEmbedded) {
859
918
  const lintConfigs = findLintConfigs(projectRoot);
860
919
  if (lintConfigs.length > 0) {
861
- const parentDir = path3.basename(path3.dirname(toolDir));
920
+ const parentDir = path4.basename(path4.dirname(toolDir));
862
921
  const parentIgnorePattern = new RegExp(`["']${parentDir}\\/\\*\\*["']`);
863
922
  for (const config of lintConfigs) {
864
- const content = fs2.readFileSync(config.fullPath, "utf-8");
865
- const hasParentIgnore = parentIgnorePattern.test(content);
923
+ const content = fs3.readFileSync(config.fullPath, "utf-8");
924
+ const hasParentIgnore = config.tool === "Biome" ? biomeScopeExcludes(content, parentDir) : parentIgnorePattern.test(content);
866
925
  if (hasParentIgnore) {
867
926
  pass(`${config.tool} ignores ${parentDir}/ (${config.fileName})`);
868
927
  } else {
928
+ const expectedPattern = config.tool === "Biome" ? `!!${parentDir}` : `${parentDir}/**`;
869
929
  fail(
870
- `${config.tool} missing ignore: "${parentDir}/**" (${config.fileName})`,
930
+ `${config.tool} missing ignore: "${expectedPattern}" (${config.fileName})`,
871
931
  `Add to ${config.propertyName} in ${config.fileName}:
872
- "${parentDir}/**",`
932
+ "${expectedPattern}",`
873
933
  );
874
934
  }
875
935
  }
876
936
  } else {
877
- skip("Lint config check (no ESLint or Oxlint config found)");
937
+ skip("Lint config check (no ESLint, Oxlint, or Biome config found)");
878
938
  }
879
939
  } else {
880
940
  skip("Lint config check (typegraph-mcp is external to project)");
881
941
  }
882
- const gitignorePath = path3.resolve(projectRoot, ".gitignore");
883
- if (fs2.existsSync(gitignorePath)) {
884
- const gitignoreContent = fs2.readFileSync(gitignorePath, "utf-8");
942
+ const gitignorePath = path4.resolve(projectRoot, ".gitignore");
943
+ if (fs3.existsSync(gitignorePath)) {
944
+ const gitignoreContent = fs3.readFileSync(gitignorePath, "utf-8");
885
945
  const lines = gitignoreContent.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
886
946
  const ignoresClaude = lines.some(
887
947
  (l) => l === ".claude/" || l === ".claude" || l === "/.claude"
888
948
  );
889
- const parentDir = toolIsEmbedded ? path3.basename(path3.dirname(toolDir)) : null;
949
+ const parentDir = toolIsEmbedded ? path4.basename(path4.dirname(toolDir)) : null;
890
950
  const ignoresParent = parentDir && lines.some((l) => l === `${parentDir}/` || l === parentDir || l === `/${parentDir}`);
891
951
  if (!ignoresParent && !ignoresClaude) {
892
952
  pass(".gitignore does not exclude .claude/" + (parentDir ? ` or ${parentDir}/` : ""));