typegraph-mcp 0.9.46 → 0.9.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -87,7 +87,7 @@ First query takes ~2s (tsserver warmup). Subsequent queries: 1-60ms.
87
87
  ## Requirements
88
88
 
89
89
  - **Node.js** >= 22
90
- - **TypeScript** >= 5.0 in the target project
90
+ - A project containing TypeScript source files. `tsconfig.json` is supported but optional; without one, semantic tools use an inferred TypeScript project.
91
91
  - **npm** for dependency installation
92
92
 
93
93
  ## Tools
@@ -146,9 +146,9 @@ npx typegraph-mcp check
146
146
  | Symptom | Fix |
147
147
  |---|---|
148
148
  | Server won't start | `cd plugins/typegraph-mcp && npm install --include=optional` |
149
- | "TypeScript not found" | Run `pnpm install` or `npm install`; if TypeScript is not declared, add it to devDependencies first |
149
+ | "Compatible tsserver not found" | Reinstall the plugin dependencies; TypeGraph uses the project's legacy tsserver when available and its bundled TypeScript 5 runtime for TypeScript 7 projects |
150
150
  | Tools return empty results | Check `TYPEGRAPH_TSCONFIG` points to the right tsconfig |
151
- | Build errors from plugins/ | Add `"plugins/**"` to tsconfig.json `exclude` array |
151
+ | Broad project checks include plugins/ | Narrow the check command's input or ignore `plugins/`; setup does not edit `tsconfig.json` |
152
152
  | `@esbuild/*` or `@rollup/*` package missing | Reinstall with Node 22: `npm install --include=optional` |
153
153
  | "npm warn Unknown project config" | Safe to ignore — caused by pnpm settings in your `.npmrc` that npm doesn't recognize |
154
154
 
@@ -224,6 +224,15 @@ nvm use
224
224
  npm install --include=optional
225
225
  ```
226
226
 
227
+ To install the current checkout into itself for Claude Code and Codex CLI:
228
+
229
+ ```bash
230
+ npm run setup:self
231
+ ```
232
+
233
+ The wrapper invokes the local `cli.ts`. In a Git worktree it first adds every generated
234
+ plugin, skill, MCP configuration, and agent-instruction path to `.gitignore`.
235
+
227
236
  ### Run locally against a project
228
237
 
229
238
  ```bash
package/benchmark.ts CHANGED
@@ -666,8 +666,17 @@ async function benchmarkAccuracy(
666
666
 
667
667
  // ─── Main ────────────────────────────────────────────────────────────────────
668
668
 
669
- export async function main(config?: { projectRoot: string; tsconfigPath: string }) {
670
- ({ projectRoot, tsconfigPath } = config ?? resolveConfig(import.meta.dirname));
669
+ export async function main(config?: {
670
+ projectRoot: string;
671
+ tsconfigPath: string;
672
+ toolDir?: string;
673
+ toolIsEmbedded?: boolean;
674
+ }) {
675
+ const resolvedConfig = config ?? resolveConfig(import.meta.dirname);
676
+ ({ projectRoot, tsconfigPath } = resolvedConfig);
677
+ const excludedPaths = resolvedConfig.toolIsEmbedded && resolvedConfig.toolDir
678
+ ? [resolvedConfig.toolDir]
679
+ : [];
671
680
  console.log("");
672
681
  console.log("typegraph-mcp Benchmark");
673
682
  console.log("=======================");
@@ -676,7 +685,7 @@ export async function main(config?: { projectRoot: string; tsconfigPath: string
676
685
 
677
686
  // Build graph
678
687
  const graphStart = performance.now();
679
- const { graph } = await buildGraph(projectRoot, tsconfigPath);
688
+ const { graph } = await buildGraph(projectRoot, tsconfigPath, excludedPaths);
680
689
  const graphMs = performance.now() - graphStart;
681
690
  const edgeCount = [...graph.forward.values()].reduce((s, e) => s + e.length, 0);
682
691
  console.log(`Module graph: ${graph.files.size} files, ${edgeCount} edges [${graphMs.toFixed(0)}ms]`);
@@ -0,0 +1,76 @@
1
+ export const BIOME_CONFIG_NAMES = [
2
+ "biome.json",
3
+ "biome.jsonc",
4
+ ".biome.json",
5
+ ".biome.jsonc",
6
+ ];
7
+
8
+ function filesObjectMatch(raw: string): RegExpMatchArray | null {
9
+ return raw.match(/(["']files["']\s*:\s*\{)([\s\S]*?)(\n?\s*\})/);
10
+ }
11
+
12
+ function filesIncludes(raw: string): string[] | null {
13
+ const filesObject = filesObjectMatch(raw);
14
+ if (!filesObject) return null;
15
+ const includes = filesObject[2].match(/["']includes["']\s*:\s*\[([\s\S]*?)\]/);
16
+ if (!includes) return null;
17
+ return [...includes[1].matchAll(/["']([^"']+)["']/g)].map((match) => match[1]);
18
+ }
19
+
20
+ export function biomeScopeExcludes(raw: string, parentDir: string): boolean {
21
+ const escaped = parentDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22
+ const explicitIgnore = new RegExp(
23
+ `["']!{1,2}(?:\\*\\*/)?${escaped}(?:/\\*\\*)?["']`
24
+ );
25
+ if (explicitIgnore.test(raw)) return true;
26
+
27
+ const includes = filesIncludes(raw);
28
+ if (!includes) return false;
29
+ const positivePatterns = includes.filter((pattern) => !pattern.startsWith("!"));
30
+
31
+ return !positivePatterns.some((pattern) => {
32
+ const normalized = pattern.replace(/^\.\//, "");
33
+ return (
34
+ normalized === "*" ||
35
+ normalized === "**" ||
36
+ normalized.startsWith("**/") ||
37
+ normalized.startsWith("*/") ||
38
+ normalized === parentDir ||
39
+ normalized.startsWith(`${parentDir}/`)
40
+ );
41
+ });
42
+ }
43
+
44
+ function appendToIncludes(body: string, forceIgnore: string): string | null {
45
+ const pattern = /(["']includes["']\s*:\s*\[)([\s\S]*?)(\])/;
46
+ if (!pattern.test(body)) return null;
47
+ return body.replace(pattern, (_match, open, items, close) => {
48
+ const trimmed = items.trimEnd();
49
+ const needsComma = trimmed.length > 0 && !trimmed.endsWith(",");
50
+ return `${open}${trimmed}${needsComma ? "," : ""} ${forceIgnore}${close}`;
51
+ });
52
+ }
53
+
54
+ export function patchBiomeConfig(raw: string, parentDir: string): string | null {
55
+ const forceIgnore = `"!!${parentDir}"`;
56
+ const filesObjectRe = /(["']files["']\s*:\s*\{)([\s\S]*?)(\n?\s*\})/;
57
+ const filesObject = filesObjectMatch(raw);
58
+
59
+ if (filesObject) {
60
+ const updatedBody = appendToIncludes(filesObject[2], forceIgnore);
61
+ if (updatedBody) {
62
+ return raw.replace(filesObjectRe, `${filesObject[1]}${updatedBody}${filesObject[3]}`);
63
+ }
64
+
65
+ const body = filesObject[2].trimEnd();
66
+ const needsComma = body.trim().length > 0 && !body.trimEnd().endsWith(",");
67
+ const updatedFiles = `${filesObject[1]}${body}${needsComma ? "," : ""}\n "includes": ["**", ${forceIgnore}]${filesObject[3]}`;
68
+ return raw.replace(filesObjectRe, updatedFiles);
69
+ }
70
+
71
+ const lastBrace = raw.lastIndexOf("}");
72
+ if (lastBrace === -1) return null;
73
+ const before = raw.slice(0, lastBrace).trimEnd();
74
+ const needsComma = !before.endsWith(",") && !before.endsWith("{");
75
+ return `${before}${needsComma ? "," : ""}\n "files": { "includes": ["**", ${forceIgnore}] }\n}\n`;
76
+ }
package/check.ts CHANGED
@@ -14,6 +14,8 @@ import * as path from "node:path";
14
14
  import { createRequire } from "node:module";
15
15
  import { spawn, spawnSync } from "node:child_process";
16
16
  import { resolveConfig, type TypegraphConfig } from "./config.js";
17
+ import { resolveTsServer, type TsServerResolution } from "./tsserver-client.js";
18
+ import { BIOME_CONFIG_NAMES, biomeScopeExcludes } from "./biome-config.js";
17
19
 
18
20
  // ─── Result Type ─────────────────────────────────────────────────────────────
19
21
 
@@ -26,7 +28,7 @@ export interface CheckResult {
26
28
  // ─── Helpers ─────────────────────────────────────────────────────────────────
27
29
 
28
30
  /** Find first .ts file in the project (for resolver smoke test) */
29
- function findFirstTsFile(dir: string): string | null {
31
+ function findFirstTsFile(dir: string, excludedPaths: string[] = []): string | null {
30
32
  const skipDirs = new Set(["node_modules", "dist", ".git", ".wrangler", "coverage"]);
31
33
  try {
32
34
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
@@ -36,7 +38,14 @@ function findFirstTsFile(dir: string): string | null {
36
38
  }
37
39
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
38
40
  if (entry.isDirectory() && !skipDirs.has(entry.name) && !entry.name.startsWith(".")) {
39
- const found = findFirstTsFile(path.join(dir, entry.name));
41
+ const childDir = path.join(dir, entry.name);
42
+ if (
43
+ excludedPaths.some(
44
+ (excludedPath) =>
45
+ childDir === excludedPath || childDir.startsWith(excludedPath + path.sep)
46
+ )
47
+ ) continue;
48
+ const found = findFirstTsFile(childDir, excludedPaths);
40
49
  if (found) return found;
41
50
  }
42
51
  }
@@ -47,18 +56,9 @@ function findFirstTsFile(dir: string): string | null {
47
56
  }
48
57
 
49
58
  /** Spawn tsserver, send configure, verify response, shut down */
50
- function testTsserver(projectRoot: string): Promise<boolean> {
59
+ function testTsserver(projectRoot: string, resolution: TsServerResolution): Promise<boolean> {
51
60
  return new Promise((resolve) => {
52
- let tsserverPath: string;
53
- try {
54
- const require = createRequire(path.resolve(projectRoot, "package.json"));
55
- tsserverPath = require.resolve("typescript/lib/tsserver.js");
56
- } catch {
57
- resolve(false);
58
- return;
59
- }
60
-
61
- const child = spawn("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
61
+ const child = spawn("node", [resolution.path, "--disableAutomaticTypingAcquisition"], {
62
62
  cwd: projectRoot,
63
63
  stdio: ["pipe", "pipe", "pipe"],
64
64
  });
@@ -228,45 +228,6 @@ function findAntigravityRegistration(projectRoot: string): string | null {
228
228
  return null;
229
229
  }
230
230
 
231
- function readProjectPackageJson(projectRoot: string): Record<string, unknown> | null {
232
- const packageJsonPath = path.resolve(projectRoot, "package.json");
233
- if (!fs.existsSync(packageJsonPath)) return null;
234
-
235
- try {
236
- return JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")) as Record<string, unknown>;
237
- } catch {
238
- return null;
239
- }
240
- }
241
-
242
- function getProjectInstallCommand(projectRoot: string, packageJson: Record<string, unknown> | null): string {
243
- const packageManager = typeof packageJson?.["packageManager"] === "string"
244
- ? packageJson["packageManager"]
245
- : "";
246
-
247
- if (packageManager.startsWith("pnpm@") || fs.existsSync(path.join(projectRoot, "pnpm-lock.yaml"))) {
248
- return "pnpm install";
249
- }
250
- if (packageManager.startsWith("yarn@") || fs.existsSync(path.join(projectRoot, "yarn.lock"))) {
251
- return "yarn install";
252
- }
253
- return "npm install";
254
- }
255
-
256
- function hasDeclaredDependency(packageJson: Record<string, unknown> | null, packageName: string): boolean {
257
- const depKeys = [
258
- "dependencies",
259
- "devDependencies",
260
- "peerDependencies",
261
- "optionalDependencies",
262
- ] as const;
263
-
264
- return depKeys.some((key) => {
265
- const deps = packageJson?.[key];
266
- return typeof deps === "object" && deps !== null && packageName in deps;
267
- });
268
- }
269
-
270
231
  const ESLINT_CONFIG_NAMES = [
271
232
  "eslint.config.mjs",
272
233
  "eslint.config.js",
@@ -283,7 +244,8 @@ const OXLINT_CONFIG_NAMES = [
283
244
 
284
245
  type LintConfigCheck =
285
246
  | { tool: "ESLint"; fileName: string; fullPath: string; propertyName: "ignores" }
286
- | { tool: "Oxlint"; fileName: string; fullPath: string; propertyName: "ignorePatterns" };
247
+ | { tool: "Oxlint"; fileName: string; fullPath: string; propertyName: "ignorePatterns" }
248
+ | { tool: "Biome"; fileName: string; fullPath: string; propertyName: "files.includes" };
287
249
 
288
250
  function findLintConfigs(projectRoot: string): LintConfigCheck[] {
289
251
  const configs: LintConfigCheck[] = [];
@@ -302,6 +264,14 @@ function findLintConfigs(projectRoot: string): LintConfigCheck[] {
302
264
  }
303
265
  }
304
266
 
267
+ for (const fileName of BIOME_CONFIG_NAMES) {
268
+ const fullPath = path.resolve(projectRoot, fileName);
269
+ if (fs.existsSync(fullPath)) {
270
+ configs.push({ tool: "Biome", fileName, fullPath, propertyName: "files.includes" });
271
+ break;
272
+ }
273
+ }
274
+
305
275
  return configs;
306
276
  }
307
277
 
@@ -310,12 +280,11 @@ function findLintConfigs(projectRoot: string): LintConfigCheck[] {
310
280
  export async function main(configOverride?: TypegraphConfig): Promise<CheckResult> {
311
281
  const { projectRoot, tsconfigPath, toolDir, toolIsEmbedded, toolRelPath } =
312
282
  configOverride ?? resolveConfig(import.meta.dirname);
283
+ const excludedPaths = toolIsEmbedded ? [toolDir] : [];
313
284
 
314
285
  let passed = 0;
315
286
  let failed = 0;
316
287
  let warned = 0;
317
- const projectPackageJson = readProjectPackageJson(projectRoot);
318
- const installCommand = getProjectInstallCommand(projectRoot, projectPackageJson);
319
288
 
320
289
  function pass(msg: string): void {
321
290
  console.log(` \u2713 ${msg}`);
@@ -363,33 +332,32 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
363
332
  pass("tsx available (via npx/global)");
364
333
  }
365
334
 
366
- // 3. TypeScript in project
367
- let tsVersion: string | null = null;
335
+ // 3. Compatible tsserver runtime
336
+ let tsResolution: TsServerResolution | null = null;
368
337
  try {
369
- const require = createRequire(path.resolve(projectRoot, "package.json"));
370
- const tsserverPath = require.resolve("typescript/lib/tsserver.js");
371
- const tsPkgPath = path.resolve(path.dirname(tsserverPath), "..", "package.json");
372
- const tsPkg = JSON.parse(fs.readFileSync(tsPkgPath, "utf-8"));
373
- tsVersion = tsPkg.version;
374
- pass(`TypeScript found (v${tsVersion})`);
338
+ tsResolution = resolveTsServer(projectRoot);
339
+ if (tsResolution.source === "project") {
340
+ pass(`TypeScript found (v${tsResolution.version})`);
341
+ } else if (tsResolution.projectVersion) {
342
+ pass(
343
+ `Compatible tsserver found (TypeScript v${tsResolution.version} tool runtime; project uses v${tsResolution.projectVersion})`
344
+ );
345
+ } else {
346
+ pass(`TypeScript found in typegraph-mcp (v${tsResolution.version})`);
347
+ }
375
348
  } catch {
376
- const hasDeclaredTs = hasDeclaredDependency(projectPackageJson, "typescript");
377
349
  fail(
378
- hasDeclaredTs
379
- ? "TypeScript is declared but not installed in project"
380
- : "TypeScript not found in project",
381
- hasDeclaredTs
382
- ? `Run \`${installCommand}\` to install project dependencies`
383
- : `Add \`typescript\` to devDependencies and run \`${installCommand}\``
350
+ "Compatible tsserver runtime not found",
351
+ `Run \`cd ${toolRelPath} && npm install --include=optional\``
384
352
  );
385
353
  }
386
354
 
387
- // 4. tsconfig.json exists
355
+ // 4. TypeScript project mode
388
356
  const tsconfigAbs = path.resolve(projectRoot, tsconfigPath);
389
357
  if (fs.existsSync(tsconfigAbs)) {
390
358
  pass(`tsconfig.json exists at ${tsconfigPath}`);
391
359
  } else {
392
- fail(`tsconfig.json not found at ${tsconfigPath}`, `Create a tsconfig.json at ${tsconfigPath}`);
360
+ pass("No tsconfig.json; semantic tools will use an inferred TypeScript project");
393
361
  }
394
362
 
395
363
  // 5. MCP registration
@@ -501,7 +469,13 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
501
469
  // 6. typegraph-mcp dependencies installed
502
470
  const toolNodeModules = path.join(toolDir, "node_modules");
503
471
  if (fs.existsSync(toolNodeModules)) {
504
- const requiredPkgs = ["@modelcontextprotocol/sdk", "oxc-parser", "oxc-resolver", "zod"];
472
+ const requiredPkgs = [
473
+ "@modelcontextprotocol/sdk",
474
+ "oxc-parser",
475
+ "oxc-resolver",
476
+ "typescript",
477
+ "zod",
478
+ ];
505
479
  const missing = requiredPkgs.filter(
506
480
  (pkg) => !fs.existsSync(path.join(toolNodeModules, ...pkg.split("/")))
507
481
  );
@@ -539,13 +513,15 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
539
513
  const oxcResolverReq = createRequire(path.join(toolDir, "package.json"));
540
514
  const { ResolverFactory } = await import(oxcResolverReq.resolve("oxc-resolver"));
541
515
  const resolver = new ResolverFactory({
542
- tsconfig: { configFile: tsconfigAbs, references: "auto" },
516
+ ...(fs.existsSync(tsconfigAbs)
517
+ ? { tsconfig: { configFile: tsconfigAbs, references: "auto" as const } }
518
+ : {}),
543
519
  extensions: [".ts", ".tsx", ".js"],
544
520
  extensionAlias: { ".js": [".ts", ".tsx", ".js"] },
545
521
  });
546
522
  // Find any .ts file in the project to test resolution
547
523
  let resolveOk = false;
548
- const testFile = findFirstTsFile(projectRoot);
524
+ const testFile = findFirstTsFile(projectRoot, excludedPaths);
549
525
  if (testFile) {
550
526
  const dir = path.dirname(testFile);
551
527
  const base = "./" + path.basename(testFile);
@@ -569,9 +545,9 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
569
545
  }
570
546
 
571
547
  // 9. tsserver startup test
572
- if (tsVersion) {
548
+ if (tsResolution) {
573
549
  try {
574
- const ok = await testTsserver(projectRoot);
550
+ const ok = await testTsserver(projectRoot, tsResolution);
575
551
  if (ok) {
576
552
  pass("tsserver responds to configure");
577
553
  } else {
@@ -592,7 +568,7 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
592
568
 
593
569
  // 10. Module graph build test
594
570
  try {
595
- let buildGraph: (root: string, tsconfig: string) => Promise<{ graph: { files: Set<string>; forward: Map<string, unknown[]> } }>;
571
+ let buildGraph: (root: string, tsconfig: string, excluded?: string[]) => Promise<{ graph: { files: Set<string>; forward: Map<string, unknown[]> } }>;
596
572
  try {
597
573
  ({ buildGraph } = await import(path.resolve(toolDir, "module-graph.js")));
598
574
  } catch {
@@ -600,7 +576,11 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
600
576
  ({ buildGraph } = await import("./module-graph.js"));
601
577
  }
602
578
  const start = performance.now();
603
- const { graph } = await buildGraph(projectRoot, tsconfigPath);
579
+ const { graph } = await buildGraph(
580
+ projectRoot,
581
+ tsconfigPath,
582
+ excludedPaths
583
+ );
604
584
  const elapsed = (performance.now() - start).toFixed(0);
605
585
  const edgeCount = [...graph.forward.values()].reduce(
606
586
  (s: number, e: unknown[]) => s + e.length,
@@ -636,19 +616,23 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
636
616
 
637
617
  for (const config of lintConfigs) {
638
618
  const content = fs.readFileSync(config.fullPath, "utf-8");
639
- const hasParentIgnore = parentIgnorePattern.test(content);
619
+ const hasParentIgnore =
620
+ config.tool === "Biome"
621
+ ? biomeScopeExcludes(content, parentDir)
622
+ : parentIgnorePattern.test(content);
640
623
 
641
624
  if (hasParentIgnore) {
642
625
  pass(`${config.tool} ignores ${parentDir}/ (${config.fileName})`);
643
626
  } else {
627
+ const expectedPattern = config.tool === "Biome" ? `!!${parentDir}` : `${parentDir}/**`;
644
628
  fail(
645
- `${config.tool} missing ignore: "${parentDir}/**" (${config.fileName})`,
646
- `Add to ${config.propertyName} in ${config.fileName}:\n "${parentDir}/**",`
629
+ `${config.tool} missing ignore: "${expectedPattern}" (${config.fileName})`,
630
+ `Add to ${config.propertyName} in ${config.fileName}:\n "${expectedPattern}",`
647
631
  );
648
632
  }
649
633
  }
650
634
  } else {
651
- skip("Lint config check (no ESLint or Oxlint config found)");
635
+ skip("Lint config check (no ESLint, Oxlint, or Biome config found)");
652
636
  }
653
637
  } else {
654
638
  skip("Lint config check (typegraph-mcp is external to project)");
package/cli.ts CHANGED
@@ -18,6 +18,7 @@ import * as fs from "node:fs";
18
18
  import * as path from "node:path";
19
19
  import { execSync } from "node:child_process";
20
20
  import * as p from "@clack/prompts";
21
+ import { BIOME_CONFIG_NAMES, biomeScopeExcludes, patchBiomeConfig } from "./biome-config.js";
21
22
  import { resolveConfig } from "./config.js";
22
23
 
23
24
  // ─── Types ───────────────────────────────────────────────────────────────────
@@ -141,6 +142,7 @@ const AGENTS: Record<AgentId, AgentDef> = {
141
142
  /** Core files always installed (server, modules, config, package manifest) */
142
143
  const CORE_FILES = [
143
144
  "server.ts",
145
+ "biome-config.ts",
144
146
  "module-graph.ts",
145
147
  "tsserver-client.ts",
146
148
  "graph-queries.ts",
@@ -700,49 +702,6 @@ function deregisterAntigravityMcp(projectRoot: string): void {
700
702
  }
701
703
  }
702
704
 
703
- // ─── TSConfig Exclude ─────────────────────────────────────────────────────────
704
-
705
- function ensureTsconfigExclude(projectRoot: string): void {
706
- const tsconfigPath = path.resolve(projectRoot, "tsconfig.json");
707
- if (!fs.existsSync(tsconfigPath)) return;
708
-
709
- try {
710
- const raw = fs.readFileSync(tsconfigPath, "utf-8");
711
- const excludeArrayMatch = raw.match(/("exclude"\s*:\s*\[)([\s\S]*?)(\])/);
712
- if (excludeArrayMatch && /["']plugins(?:\/\*\*|\/\*|)["']/.test(excludeArrayMatch[2])) {
713
- return;
714
- }
715
-
716
- // Insert "plugins/**" into the exclude array in the original file
717
- if (excludeArrayMatch) {
718
- // Existing exclude array — append to it
719
- const updated = raw.replace(
720
- /("exclude"\s*:\s*\[)([\s\S]*?)(\])/,
721
- (_match, open, items, close) => {
722
- const trimmed = items.trimEnd();
723
- const needsComma = trimmed.length > 0 && !trimmed.endsWith(",");
724
- return `${open}${items.trimEnd()}${needsComma ? "," : ""}\n "plugins/**"${close}`;
725
- }
726
- );
727
- fs.writeFileSync(tsconfigPath, updated);
728
- } else {
729
- // No exclude field — add one before the closing brace
730
- const lastBrace = raw.lastIndexOf("}");
731
- if (lastBrace !== -1) {
732
- const before = raw.slice(0, lastBrace).trimEnd();
733
- const needsComma = !before.endsWith(",") && !before.endsWith("{");
734
- const patched = `${before}${needsComma ? "," : ""}\n "exclude": ["plugins/**"]\n}\n`;
735
- fs.writeFileSync(tsconfigPath, patched);
736
- }
737
- }
738
-
739
- p.log.success('Added "plugins/**" to tsconfig.json exclude (prevents build errors)');
740
- } catch {
741
- // Don't fail setup over tsconfig parsing issues
742
- p.log.warn('Could not update tsconfig.json — manually add "plugins/**" to the exclude array to prevent build errors');
743
- }
744
- }
745
-
746
705
  // ─── Lint Ignore ─────────────────────────────────────────────────────────────
747
706
 
748
707
  const ESLINT_CONFIG_NAMES = [
@@ -761,7 +720,8 @@ const OXLINT_CONFIG_NAMES = [
761
720
 
762
721
  type LintConfig =
763
722
  | { tool: "ESLint"; fileName: string; fullPath: string; format: "flat" }
764
- | { tool: "Oxlint"; fileName: string; fullPath: string; format: "json" | "module" };
723
+ | { tool: "Oxlint"; fileName: string; fullPath: string; format: "json" | "module" }
724
+ | { tool: "Biome"; fileName: string; fullPath: string; format: "json" };
765
725
 
766
726
  function findLintConfigs(projectRoot: string): LintConfig[] {
767
727
  const configs: LintConfig[] = [];
@@ -785,6 +745,14 @@ function findLintConfigs(projectRoot: string): LintConfig[] {
785
745
  }
786
746
  }
787
747
 
748
+ for (const fileName of BIOME_CONFIG_NAMES) {
749
+ const fullPath = path.resolve(projectRoot, fileName);
750
+ if (fs.existsSync(fullPath)) {
751
+ configs.push({ tool: "Biome", fileName, fullPath, format: "json" });
752
+ break;
753
+ }
754
+ }
755
+
788
756
  return configs;
789
757
  }
790
758
 
@@ -862,34 +830,51 @@ function patchOxlintModuleConfig(raw: string): string | null {
862
830
  return null;
863
831
  }
864
832
 
833
+ function lintPropertyName(config: LintConfig): string {
834
+ if (config.tool === "ESLint") return "ignores";
835
+ if (config.tool === "Oxlint") return "ignorePatterns";
836
+ return "files.includes";
837
+ }
838
+
865
839
  function ensureLintIgnores(projectRoot: string): void {
866
840
  const configs = findLintConfigs(projectRoot);
867
841
  for (const config of configs) {
868
842
  try {
869
843
  const raw = fs.readFileSync(config.fullPath, "utf-8");
870
- if (/["']plugins\/\*\*["']/.test(raw)) continue;
844
+ if (
845
+ config.tool === "Biome"
846
+ ? biomeScopeExcludes(raw, "plugins")
847
+ : /["']plugins\/\*\*["']/.test(raw)
848
+ ) {
849
+ continue;
850
+ }
871
851
 
872
852
  const updated =
873
853
  config.tool === "ESLint"
874
854
  ? patchEslintConfig(raw)
855
+ : config.tool === "Biome"
856
+ ? patchBiomeConfig(raw, "plugins")
875
857
  : config.format === "json"
876
858
  ? patchOxlintJsonConfig(raw)
877
859
  : patchOxlintModuleConfig(raw);
878
860
 
879
861
  if (updated) {
880
862
  fs.writeFileSync(config.fullPath, updated);
881
- const propertyName = config.tool === "ESLint" ? "ignores" : "ignorePatterns";
882
- p.log.success(`Added "plugins/**" to ${config.fileName} ${propertyName}`);
863
+ const propertyName = lintPropertyName(config);
864
+ const ignorePattern = config.tool === "Biome" ? "!!plugins" : "plugins/**";
865
+ p.log.success(`Added "${ignorePattern}" to ${config.fileName} ${propertyName}`);
883
866
  } else {
884
- const propertyName = config.tool === "ESLint" ? "ignores" : "ignorePatterns";
867
+ const propertyName = lintPropertyName(config);
868
+ const ignorePattern = config.tool === "Biome" ? "!!plugins" : "plugins/**";
885
869
  p.log.warn(
886
- `Could not patch ${config.fileName} — manually add "plugins/**" to ${propertyName}`
870
+ `Could not patch ${config.fileName} — manually add "${ignorePattern}" to ${propertyName}`
887
871
  );
888
872
  }
889
873
  } catch {
890
- const propertyName = config.tool === "ESLint" ? "ignores" : "ignorePatterns";
874
+ const propertyName = lintPropertyName(config);
875
+ const ignorePattern = config.tool === "Biome" ? "!!plugins" : "plugins/**";
891
876
  p.log.warn(
892
- `Could not update ${config.fileName} — manually add "plugins/**" to ${propertyName}`
877
+ `Could not update ${config.fileName} — manually add "${ignorePattern}" to ${propertyName}`
893
878
  );
894
879
  }
895
880
  }
@@ -957,13 +942,13 @@ async function setup(yes: boolean): Promise<void> {
957
942
  process.exit(1);
958
943
  }
959
944
 
960
- if (!fs.existsSync(tsconfigPath)) {
961
- p.cancel("No tsconfig.json found. typegraph-mcp requires a TypeScript project.");
962
- process.exit(1);
945
+ if (fs.existsSync(tsconfigPath)) {
946
+ p.log.success("Found package.json and tsconfig.json");
947
+ } else {
948
+ p.log.success("Found package.json");
949
+ p.log.info("No tsconfig.json found; semantic tools will use an inferred TypeScript project");
963
950
  }
964
951
 
965
- p.log.success("Found package.json and tsconfig.json");
966
-
967
952
  // 2. Check for existing installation
968
953
  const targetDir = path.resolve(projectRoot, PLUGIN_DIR_NAME);
969
954
  const isUpdate = fs.existsSync(targetDir);
@@ -1125,13 +1110,10 @@ async function setup(yes: boolean): Promise<void> {
1125
1110
  // 7. Register MCP server in agent-specific configs
1126
1111
  registerMcpServers(projectRoot, selectedAgents);
1127
1112
 
1128
- // 8. Ensure plugins/ is excluded from tsconfig
1129
- ensureTsconfigExclude(projectRoot);
1130
-
1131
- // 9. Ensure plugins/ is ignored by supported lint configs
1113
+ // 8. Ensure plugins/ is ignored by supported lint configs
1132
1114
  ensureLintIgnores(projectRoot);
1133
1115
 
1134
- // 10. Verification
1116
+ // 9. Verification
1135
1117
  await runVerification(targetDir, selectedAgents);
1136
1118
  }
1137
1119
 
@@ -1383,7 +1365,6 @@ const args = process.argv.slice(2);
1383
1365
  const command = args.find((a) => !a.startsWith("-"));
1384
1366
  const yes = args.includes("--yes") || args.includes("-y");
1385
1367
  const help = args.includes("--help") || args.includes("-h");
1386
-
1387
1368
  // Clear npx download noise (warnings, "Ok to proceed?" prompt)
1388
1369
  process.stdout.write("\x1Bc");
1389
1370
 
package/commands/check.md CHANGED
@@ -20,4 +20,4 @@ Run health checks to verify typegraph-mcp is correctly set up for this project.
20
20
  - For any failures, highlight the issue and the suggested fix
21
21
  - If all checks pass, confirm typegraph-mcp is ready
22
22
 
23
- 3. The check verifies: Node.js version, tsx availability, TypeScript installation, tsconfig.json, MCP registration, dependencies, oxc-parser, oxc-resolver, tsserver, module graph, ESLint ignores, and .gitignore configuration.
23
+ 3. The check verifies: Node.js version, tsx availability, compatible TypeScript runtime, tsconfig.json, MCP registration, dependencies, oxc-parser, oxc-resolver, tsserver, module graph, ESLint/Oxlint/Biome ignores, and .gitignore configuration.