typegraph-mcp 0.9.38 → 0.9.40

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
@@ -31,6 +31,7 @@ __export(module_graph_exports, {
31
31
  createResolver: () => createResolver,
32
32
  discoverFiles: () => discoverFiles,
33
33
  removeFile: () => removeFile,
34
+ resolveProjectImport: () => resolveProjectImport,
34
35
  startWatcher: () => startWatcher,
35
36
  updateFile: () => updateFile
36
37
  });
@@ -136,7 +137,7 @@ function distToSource(resolvedPath, projectRoot3) {
136
137
  }
137
138
  return resolvedPath;
138
139
  }
139
- function resolveImport(resolver, fromDir, specifier, projectRoot3) {
140
+ function resolveProjectImport(resolver, fromDir, specifier, projectRoot3) {
140
141
  try {
141
142
  const result = resolver.sync(fromDir, specifier);
142
143
  if (result.path && !result.path.includes("node_modules")) {
@@ -187,7 +188,7 @@ function buildForwardEdges(files, resolver, projectRoot3) {
187
188
  const edges = [];
188
189
  const fromDir = path2.dirname(filePath);
189
190
  for (const raw of rawImports) {
190
- const target = resolveImport(resolver, fromDir, raw.specifier, projectRoot3);
191
+ const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot3);
191
192
  if (target) {
192
193
  edges.push({
193
194
  target,
@@ -268,7 +269,7 @@ function updateFile(graph, filePath, resolver, projectRoot3) {
268
269
  const fromDir = path2.dirname(filePath);
269
270
  const newEdges = [];
270
271
  for (const raw of rawImports) {
271
- const target = resolveImport(resolver, fromDir, raw.specifier, projectRoot3);
272
+ const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot3);
272
273
  if (target) {
273
274
  newEdges.push({
274
275
  target,
@@ -513,6 +514,22 @@ function hasDeclaredDependency(packageJson, packageName) {
513
514
  return typeof deps === "object" && deps !== null && packageName in deps;
514
515
  });
515
516
  }
517
+ function findLintConfigs(projectRoot3) {
518
+ const configs = [];
519
+ for (const fileName of ESLINT_CONFIG_NAMES) {
520
+ const fullPath = path3.resolve(projectRoot3, fileName);
521
+ if (fs2.existsSync(fullPath)) {
522
+ configs.push({ tool: "ESLint", fileName, fullPath, propertyName: "ignores" });
523
+ }
524
+ }
525
+ for (const fileName of OXLINT_CONFIG_NAMES) {
526
+ const fullPath = path3.resolve(projectRoot3, fileName);
527
+ if (fs2.existsSync(fullPath)) {
528
+ configs.push({ tool: "Oxlint", fileName, fullPath, propertyName: "ignorePatterns" });
529
+ }
530
+ }
531
+ return configs;
532
+ }
516
533
  async function main(configOverride) {
517
534
  const { projectRoot: projectRoot3, tsconfigPath: tsconfigPath3, toolDir, toolIsEmbedded, toolRelPath } = configOverride ?? resolveConfig(import.meta.dirname);
518
535
  let passed = 0;
@@ -690,8 +707,8 @@ async function main(configOverride) {
690
707
  }
691
708
  try {
692
709
  const oxcParserReq = createRequire(path3.join(toolDir, "package.json"));
693
- const { parseSync: parseSync2 } = await import(oxcParserReq.resolve("oxc-parser"));
694
- const result = parseSync2("test.ts", 'import { x } from "./y";');
710
+ const { parseSync: parseSync3 } = await import(oxcParserReq.resolve("oxc-parser"));
711
+ const result = parseSync3("test.ts", 'import { x } from "./y";');
695
712
  if (result.module?.staticImports?.length === 1) {
696
713
  pass("oxc-parser working");
697
714
  } else {
@@ -790,28 +807,28 @@ async function main(configOverride) {
790
807
  );
791
808
  }
792
809
  if (toolIsEmbedded) {
793
- const eslintConfigNames = ["eslint.config.mjs", "eslint.config.js", "eslint.config.ts", "eslint.config.cjs"];
794
- const eslintConfigFile = eslintConfigNames.find((name) => fs2.existsSync(path3.resolve(projectRoot3, name)));
795
- if (eslintConfigFile) {
796
- const eslintConfigPath = path3.resolve(projectRoot3, eslintConfigFile);
797
- const eslintContent = fs2.readFileSync(eslintConfigPath, "utf-8");
810
+ const lintConfigs = findLintConfigs(projectRoot3);
811
+ if (lintConfigs.length > 0) {
798
812
  const parentDir = path3.basename(path3.dirname(toolDir));
799
813
  const parentIgnorePattern = new RegExp(`["']${parentDir}\\/\\*\\*["']`);
800
- const hasParentIgnore = parentIgnorePattern.test(eslintContent);
801
- if (hasParentIgnore) {
802
- pass(`ESLint ignores ${parentDir}/`);
803
- } else {
804
- fail(
805
- `ESLint missing ignore: "${parentDir}/**"`,
806
- `Add to the ignores array in ${eslintConfigFile}:
814
+ for (const config of lintConfigs) {
815
+ const content = fs2.readFileSync(config.fullPath, "utf-8");
816
+ const hasParentIgnore = parentIgnorePattern.test(content);
817
+ if (hasParentIgnore) {
818
+ pass(`${config.tool} ignores ${parentDir}/ (${config.fileName})`);
819
+ } else {
820
+ fail(
821
+ `${config.tool} missing ignore: "${parentDir}/**" (${config.fileName})`,
822
+ `Add to ${config.propertyName} in ${config.fileName}:
807
823
  "${parentDir}/**",`
808
- );
824
+ );
825
+ }
809
826
  }
810
827
  } else {
811
- skip("ESLint config check (no eslint flat config found)");
828
+ skip("Lint config check (no ESLint or Oxlint config found)");
812
829
  }
813
830
  } else {
814
- skip("ESLint config check (typegraph-mcp is external to project)");
831
+ skip("Lint config check (typegraph-mcp is external to project)");
815
832
  }
816
833
  const gitignorePath = path3.resolve(projectRoot3, ".gitignore");
817
834
  if (fs2.existsSync(gitignorePath)) {
@@ -850,9 +867,23 @@ async function main(configOverride) {
850
867
  console.log("");
851
868
  return { passed, failed, warned };
852
869
  }
870
+ var ESLINT_CONFIG_NAMES, OXLINT_CONFIG_NAMES;
853
871
  var init_check = __esm({
854
872
  "check.ts"() {
855
873
  init_config();
874
+ ESLINT_CONFIG_NAMES = [
875
+ "eslint.config.mjs",
876
+ "eslint.config.js",
877
+ "eslint.config.ts",
878
+ "eslint.config.cjs"
879
+ ];
880
+ OXLINT_CONFIG_NAMES = [
881
+ ".oxlintrc.json",
882
+ "oxlint.config.ts",
883
+ "oxlint.config.js",
884
+ "oxlint.config.mjs",
885
+ "oxlint.config.cjs"
886
+ ];
856
887
  }
857
888
  });
858
889
 
@@ -2307,6 +2338,7 @@ var init_benchmark = __esm({
2307
2338
  var server_exports = {};
2308
2339
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2309
2340
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2341
+ import { parseSync as parseSync2 } from "oxc-parser";
2310
2342
  import { z } from "zod";
2311
2343
  import * as fs7 from "fs";
2312
2344
  import * as path8 from "path";
@@ -2371,8 +2403,227 @@ async function resolveParams(params) {
2371
2403
  }
2372
2404
  return { error: "Either line+column or symbol must be provided" };
2373
2405
  }
2406
+ function exportPriority(source) {
2407
+ switch (source) {
2408
+ case "local":
2409
+ return 3;
2410
+ case "re-export":
2411
+ return 2;
2412
+ case "star-re-export":
2413
+ return 1;
2414
+ }
2415
+ }
2416
+ function exportKey(item) {
2417
+ return `${item.symbol}:${item.exportKind}`;
2418
+ }
2419
+ function sameExportOrigin(a, b) {
2420
+ return a.symbol === b.symbol && a.exportKind === b.exportKind && a.from === b.from && a.definedIn === b.definedIn && a.definedLine === b.definedLine;
2421
+ }
2422
+ function kindImpliesTypeOnly(kind) {
2423
+ return kind === "type" || kind === "interface";
2424
+ }
2425
+ function normalizeExportKindLabel(kind, exportKind) {
2426
+ if (exportKind === "type" && !kindImpliesTypeOnly(kind)) {
2427
+ return "type";
2428
+ }
2429
+ return kind;
2430
+ }
2431
+ function upsertExport(map, conflicts, nextExport) {
2432
+ const key = exportKey(nextExport);
2433
+ if (conflicts.has(key)) {
2434
+ if (nextExport.source === "star-re-export") return;
2435
+ conflicts.delete(key);
2436
+ map.set(key, nextExport);
2437
+ return;
2438
+ }
2439
+ const existing = map.get(key);
2440
+ if (existing && existing.source === "star-re-export" && nextExport.source === "star-re-export" && !sameExportOrigin(existing, nextExport)) {
2441
+ map.delete(key);
2442
+ conflicts.add(key);
2443
+ return;
2444
+ }
2445
+ if (!existing || exportPriority(nextExport.source) > exportPriority(existing.source)) {
2446
+ map.set(key, nextExport);
2447
+ }
2448
+ }
2449
+ function offsetToLineColumn(source, offset) {
2450
+ const safeOffset = Math.max(0, Math.min(offset ?? 0, source.length));
2451
+ const prefix = source.slice(0, safeOffset);
2452
+ const lines = prefix.split("\n");
2453
+ return {
2454
+ line: lines.length,
2455
+ column: (lines.at(-1)?.length ?? 0) + 1
2456
+ };
2457
+ }
2458
+ function normalizeExistingPath(filePath) {
2459
+ const resolved = path8.resolve(filePath);
2460
+ try {
2461
+ return fs7.realpathSync.native(resolved);
2462
+ } catch {
2463
+ return resolved;
2464
+ }
2465
+ }
2466
+ function projectPath(file) {
2467
+ return path8.isAbsolute(file) ? relPath2(file) : file;
2468
+ }
2469
+ function exportSymbol(entry) {
2470
+ if (entry.exportName.kind === "Default") return "default";
2471
+ return entry.exportName.name ?? entry.localName.name ?? entry.importName.name;
2472
+ }
2473
+ function exportLookupOffset(entry) {
2474
+ if (entry.moduleRequest) {
2475
+ return entry.importName.start ?? entry.exportName.start ?? entry.start;
2476
+ }
2477
+ if (entry.exportName.kind === "Default") {
2478
+ return entry.localName.start ?? entry.exportName.start ?? entry.start;
2479
+ }
2480
+ return entry.exportName.start ?? entry.localName.start ?? entry.start;
2481
+ }
2482
+ async function resolveExportMetadata(file, line, column, fallbackKind) {
2483
+ const defs = await client.definition(file, line, column);
2484
+ const def = defs[0] ?? null;
2485
+ let info = await client.quickinfo(file, line, column);
2486
+ if ((!info || info.kind === "alias") && def) {
2487
+ info = await client.quickinfo(def.file, def.start.line, def.start.offset) ?? info;
2488
+ }
2489
+ return {
2490
+ kind: info?.kind ?? fallbackKind,
2491
+ type: info?.displayString ?? null,
2492
+ definedIn: projectPath(def?.file ?? file),
2493
+ definedLine: def?.start.line ?? null
2494
+ };
2495
+ }
2496
+ async function getModuleExports(file, visited = /* @__PURE__ */ new Set()) {
2497
+ const relFile = path8.isAbsolute(file) ? relPath2(file) : file;
2498
+ const absFile = normalizeExistingPath(client.resolvePath(relFile));
2499
+ if (visited.has(absFile)) return [];
2500
+ const nextVisited = new Set(visited);
2501
+ nextVisited.add(absFile);
2502
+ const exportMap = /* @__PURE__ */ new Map();
2503
+ const conflictingStarExports = /* @__PURE__ */ new Set();
2504
+ let source;
2505
+ try {
2506
+ source = fs7.readFileSync(absFile, "utf-8");
2507
+ } catch {
2508
+ return [...exportMap.values()];
2509
+ }
2510
+ let parsed;
2511
+ try {
2512
+ parsed = parseSync2(absFile, source);
2513
+ } catch {
2514
+ return [...exportMap.values()];
2515
+ }
2516
+ for (const exp of parsed.module.staticExports) {
2517
+ for (const entry of exp.entries) {
2518
+ const moduleRequest = entry.moduleRequest;
2519
+ if (!moduleRequest) continue;
2520
+ const targetFile = resolveProjectImport(
2521
+ moduleResolver,
2522
+ path8.dirname(absFile),
2523
+ moduleRequest.value,
2524
+ projectRoot2
2525
+ );
2526
+ const exportLoc = offsetToLineColumn(
2527
+ source,
2528
+ entry.exportName.start ?? entry.localName.start ?? entry.importName.start ?? entry.start
2529
+ );
2530
+ const importKind = entry.importName.kind;
2531
+ const exportKind = entry.exportName.kind;
2532
+ if (importKind === "AllButDefault" && exportKind === "None") {
2533
+ if (!targetFile) continue;
2534
+ const nestedExports = await getModuleExports(targetFile, nextVisited);
2535
+ for (const nested of nestedExports) {
2536
+ if (nested.symbol === "default") continue;
2537
+ const starExportKind = entry.isType ? "type" : nested.exportKind;
2538
+ upsertExport(exportMap, conflictingStarExports, {
2539
+ ...nested,
2540
+ line: exportLoc.line,
2541
+ exportKind: starExportKind,
2542
+ isTypeOnly: starExportKind === "type",
2543
+ source: "star-re-export",
2544
+ from: relPath2(targetFile)
2545
+ });
2546
+ }
2547
+ continue;
2548
+ }
2549
+ const symbol = exportSymbol(entry);
2550
+ if (!symbol) continue;
2551
+ const importedSymbol = importKind === "Default" ? "default" : importKind === "Name" ? entry.importName.name : null;
2552
+ const nestedMatch = targetFile && importedSymbol ? (await getModuleExports(targetFile, nextVisited)).find(
2553
+ (item) => item.symbol === importedSymbol
2554
+ ) ?? null : null;
2555
+ const lookupLoc = offsetToLineColumn(
2556
+ source,
2557
+ exportLookupOffset(entry)
2558
+ );
2559
+ const metadata = await resolveExportMetadata(
2560
+ relFile,
2561
+ lookupLoc.line,
2562
+ lookupLoc.column,
2563
+ importKind === "All" ? "namespace" : "alias"
2564
+ );
2565
+ const resolvedExportKind = entry.isType || nestedMatch?.exportKind === "type" || kindImpliesTypeOnly(nestedMatch?.kind ?? metadata.kind) ? "type" : "value";
2566
+ const resolvedKind = normalizeExportKindLabel(
2567
+ nestedMatch?.kind ?? metadata.kind,
2568
+ resolvedExportKind
2569
+ );
2570
+ upsertExport(exportMap, conflictingStarExports, {
2571
+ symbol,
2572
+ kind: resolvedKind,
2573
+ line: exportLoc.line,
2574
+ type: nestedMatch?.type ?? metadata.type,
2575
+ exportKind: resolvedExportKind,
2576
+ isTypeOnly: resolvedExportKind === "type",
2577
+ isNamespace: importKind === "All",
2578
+ source: "re-export",
2579
+ from: targetFile ? relPath2(targetFile) : moduleRequest.value,
2580
+ definedIn: nestedMatch?.definedIn ?? metadata.definedIn,
2581
+ definedLine: nestedMatch?.definedLine ?? metadata.definedLine
2582
+ });
2583
+ continue;
2584
+ }
2585
+ for (const entry of exp.entries) {
2586
+ const moduleRequest = entry.moduleRequest;
2587
+ if (moduleRequest) continue;
2588
+ const symbol = exportSymbol(entry);
2589
+ if (!symbol) continue;
2590
+ const exportLoc = offsetToLineColumn(
2591
+ source,
2592
+ entry.exportName.start ?? entry.localName.start ?? entry.start
2593
+ );
2594
+ const lookupLoc = offsetToLineColumn(source, exportLookupOffset(entry));
2595
+ const metadata = await resolveExportMetadata(
2596
+ relFile,
2597
+ lookupLoc.line,
2598
+ lookupLoc.column,
2599
+ entry.isType ? "type" : "value"
2600
+ );
2601
+ const resolvedExportKind = entry.isType || kindImpliesTypeOnly(metadata.kind) ? "type" : "value";
2602
+ const resolvedKind = normalizeExportKindLabel(metadata.kind, resolvedExportKind);
2603
+ if (resolvedExportKind === "value" && symbol !== "default" && !exportKinds.has(resolvedKind) && resolvedKind !== "namespace" && resolvedKind !== "class") {
2604
+ continue;
2605
+ }
2606
+ upsertExport(exportMap, conflictingStarExports, {
2607
+ symbol,
2608
+ kind: resolvedKind,
2609
+ line: exportLoc.line,
2610
+ type: metadata.type,
2611
+ exportKind: resolvedExportKind,
2612
+ isTypeOnly: resolvedExportKind === "type",
2613
+ isNamespace: false,
2614
+ source: "local",
2615
+ from: null,
2616
+ definedIn: relFile,
2617
+ definedLine: resolvedExportKind === "type" ? exportLoc.line : metadata.definedLine
2618
+ });
2619
+ }
2620
+ }
2621
+ return [...exportMap.values()].sort(
2622
+ (a, b) => a.line - b.line || a.symbol.localeCompare(b.symbol)
2623
+ );
2624
+ }
2374
2625
  function relPath2(absPath2) {
2375
- return path8.relative(projectRoot2, absPath2);
2626
+ return path8.relative(normalizedProjectRoot, normalizeExistingPath(absPath2));
2376
2627
  }
2377
2628
  function absPath(file) {
2378
2629
  return path8.isAbsolute(file) ? file : path8.resolve(projectRoot2, file);
@@ -2386,12 +2637,13 @@ async function main4() {
2386
2637
  buildGraph(projectRoot2, tsconfigPath2)
2387
2638
  ]);
2388
2639
  moduleGraph = graphResult.graph;
2640
+ moduleResolver = graphResult.resolver;
2389
2641
  startWatcher(projectRoot2, moduleGraph, graphResult.resolver);
2390
2642
  const transport = new StdioServerTransport();
2391
2643
  await mcpServer.connect(transport);
2392
2644
  log3("MCP server connected and ready");
2393
2645
  }
2394
- var projectRoot2, tsconfigPath2, log3, client, moduleGraph, mcpServer, locationOrSymbol;
2646
+ var projectRoot2, tsconfigPath2, log3, client, moduleGraph, moduleResolver, mcpServer, locationOrSymbol, exportKinds, normalizedProjectRoot;
2395
2647
  var init_server = __esm({
2396
2648
  "server.ts"() {
2397
2649
  init_tsserver_client();
@@ -2411,6 +2663,18 @@ var init_server = __esm({
2411
2663
  column: z.number().int().positive().optional().describe("Column/offset (1-based). Required if symbol is not provided."),
2412
2664
  symbol: z.string().optional().describe("Symbol name to find. Alternative to line+column.")
2413
2665
  };
2666
+ exportKinds = /* @__PURE__ */ new Set([
2667
+ "function",
2668
+ "const",
2669
+ "class",
2670
+ "interface",
2671
+ "type",
2672
+ "enum",
2673
+ "var",
2674
+ "let",
2675
+ "method"
2676
+ ]);
2677
+ normalizedProjectRoot = normalizeExistingPath(projectRoot2);
2414
2678
  mcpServer.tool(
2415
2679
  "ts_find_symbol",
2416
2680
  "Find a symbol's location in a file by name. Entry point for navigating without exact coordinates.",
@@ -2676,53 +2940,37 @@ var init_server = __esm({
2676
2940
  );
2677
2941
  mcpServer.tool(
2678
2942
  "ts_module_exports",
2679
- "List all exported symbols from a module with their resolved types. Gives an at-a-glance understanding of what a file provides.",
2943
+ "List all exported symbols from a module with their resolved types, including re-exports when possible. Gives an at-a-glance understanding of what a file provides.",
2680
2944
  {
2681
2945
  file: z.string().describe("File to inspect")
2682
2946
  },
2683
2947
  async ({ file }) => {
2684
- const bar = await client.navbar(file);
2685
- if (bar.length === 0) {
2686
- return {
2687
- content: [
2688
- {
2689
- type: "text",
2690
- text: JSON.stringify({ error: `No symbols found in ${file}` })
2691
- }
2692
- ]
2693
- };
2694
- }
2695
- const moduleItem = bar.find((item) => item.kind === "module");
2696
- const topItems = moduleItem?.childItems ?? bar;
2697
- const exportKinds = /* @__PURE__ */ new Set([
2698
- "function",
2699
- "const",
2700
- "class",
2701
- "interface",
2702
- "type",
2703
- "enum",
2704
- "var",
2705
- "let",
2706
- "method"
2707
- ]);
2708
- const candidates = topItems.filter((item) => exportKinds.has(item.kind));
2709
- const exports = [];
2710
- for (const item of candidates) {
2711
- if (!item.spans[0]) continue;
2712
- const span = item.spans[0];
2713
- const info = await client.quickinfo(file, span.start.line, span.start.offset);
2714
- exports.push({
2715
- symbol: item.text,
2716
- kind: item.kind,
2717
- line: span.start.line,
2718
- type: info?.displayString ?? null
2719
- });
2720
- }
2948
+ const exports = await getModuleExports(file);
2949
+ const localCount = exports.filter((item) => item.source === "local").length;
2950
+ const reExportCount = exports.length - localCount;
2951
+ const typeOnlyCount = exports.filter((item) => item.isTypeOnly).length;
2952
+ const valueCount = exports.length - typeOnlyCount;
2953
+ const namespaceExportCount = exports.filter((item) => item.isNamespace).length;
2954
+ const hasLocalRuntimeExports = exports.some(
2955
+ (item) => item.source === "local" && !item.isTypeOnly
2956
+ );
2957
+ const isPrimarilyBarrel = exports.length > 0 && localCount < reExportCount;
2721
2958
  return {
2722
2959
  content: [
2723
2960
  {
2724
2961
  type: "text",
2725
- text: JSON.stringify({ file, exports, count: exports.length })
2962
+ text: JSON.stringify({
2963
+ file,
2964
+ exports,
2965
+ count: exports.length,
2966
+ localCount,
2967
+ reExportCount,
2968
+ typeOnlyCount,
2969
+ valueCount,
2970
+ namespaceExportCount,
2971
+ hasLocalRuntimeExports,
2972
+ isPrimarilyBarrel
2973
+ })
2726
2974
  }
2727
2975
  ]
2728
2976
  };
@@ -3342,13 +3590,11 @@ function ensureTsconfigExclude(projectRoot3) {
3342
3590
  if (!fs8.existsSync(tsconfigPath3)) return;
3343
3591
  try {
3344
3592
  const raw = fs8.readFileSync(tsconfigPath3, "utf-8");
3345
- const stripped = raw.replace(/\/\/.*$/gm, "").replace(/,(\s*[}\]])/g, "$1");
3346
- const tsconfig = JSON.parse(stripped);
3347
- const exclude = tsconfig.exclude || [];
3348
- if (exclude.some((e) => e === "plugins" || e === "plugins/**" || e === "plugins/*")) {
3593
+ const excludeArrayMatch = raw.match(/("exclude"\s*:\s*\[)([\s\S]*?)(\])/);
3594
+ if (excludeArrayMatch && /["']plugins(?:\/\*\*|\/\*|)["']/.test(excludeArrayMatch[2])) {
3349
3595
  return;
3350
3596
  }
3351
- if (raw.includes('"exclude"')) {
3597
+ if (excludeArrayMatch) {
3352
3598
  const updated = raw.replace(
3353
3599
  /("exclude"\s*:\s*\[)([\s\S]*?)(\])/,
3354
3600
  (_match, open, items, close) => {
@@ -3376,40 +3622,114 @@ function ensureTsconfigExclude(projectRoot3) {
3376
3622
  p.log.warn('Could not update tsconfig.json \u2014 manually add "plugins/**" to the exclude array to prevent build errors');
3377
3623
  }
3378
3624
  }
3379
- function ensureEslintIgnore(projectRoot3) {
3380
- const eslintConfigNames = ["eslint.config.mjs", "eslint.config.js", "eslint.config.ts", "eslint.config.cjs"];
3381
- const eslintConfigFile = eslintConfigNames.find((name) => fs8.existsSync(path9.resolve(projectRoot3, name)));
3382
- if (!eslintConfigFile) return;
3383
- const eslintConfigPath = path9.resolve(projectRoot3, eslintConfigFile);
3384
- try {
3385
- const raw = fs8.readFileSync(eslintConfigPath, "utf-8");
3386
- const pattern = /["']plugins\/\*\*["']/;
3387
- if (pattern.test(raw)) return;
3388
- const ignoresArrayRe = /(ignores\s*:\s*\[)([\s\S]*?)(\])/;
3389
- const match = raw.match(ignoresArrayRe);
3390
- if (match) {
3391
- const updated = raw.replace(ignoresArrayRe, (_m, open, items, close) => {
3392
- const trimmed = items.trimEnd();
3393
- const needsComma = trimmed.length > 0 && !trimmed.endsWith(",");
3394
- return `${open}${items.trimEnd()}${needsComma ? "," : ""} "plugins/**"${close}`;
3625
+ var ESLINT_CONFIG_NAMES2 = [
3626
+ "eslint.config.mjs",
3627
+ "eslint.config.js",
3628
+ "eslint.config.ts",
3629
+ "eslint.config.cjs"
3630
+ ];
3631
+ var OXLINT_CONFIG_NAMES2 = [
3632
+ ".oxlintrc.json",
3633
+ "oxlint.config.ts",
3634
+ "oxlint.config.js",
3635
+ "oxlint.config.mjs",
3636
+ "oxlint.config.cjs"
3637
+ ];
3638
+ function findLintConfigs2(projectRoot3) {
3639
+ const configs = [];
3640
+ for (const fileName of ESLINT_CONFIG_NAMES2) {
3641
+ const fullPath = path9.resolve(projectRoot3, fileName);
3642
+ if (fs8.existsSync(fullPath)) {
3643
+ configs.push({ tool: "ESLint", fileName, fullPath, format: "flat" });
3644
+ }
3645
+ }
3646
+ for (const fileName of OXLINT_CONFIG_NAMES2) {
3647
+ const fullPath = path9.resolve(projectRoot3, fileName);
3648
+ if (fs8.existsSync(fullPath)) {
3649
+ configs.push({
3650
+ tool: "Oxlint",
3651
+ fileName,
3652
+ fullPath,
3653
+ format: fileName.endsWith(".json") ? "json" : "module"
3395
3654
  });
3396
- fs8.writeFileSync(eslintConfigPath, updated);
3397
- p.log.success(`Added "plugins/**" to ${eslintConfigFile} ignores`);
3398
- return;
3399
3655
  }
3400
- const exportArrayRe = /(export\s+default\s+(?:\w+\.config\(|\[))\s*\n?/;
3401
- if (exportArrayRe.test(raw)) {
3402
- const updated = raw.replace(exportArrayRe, (m) => {
3403
- return `${m} { ignores: ["plugins/**"] },
3656
+ }
3657
+ return configs;
3658
+ }
3659
+ function appendToArrayLiteral(raw, propertyPattern, valueLiteral) {
3660
+ if (!propertyPattern.test(raw)) return null;
3661
+ return raw.replace(propertyPattern, (_match, open, items, close) => {
3662
+ const trimmed = items.trimEnd();
3663
+ const needsComma = trimmed.length > 0 && !trimmed.endsWith(",");
3664
+ return `${open}${items.trimEnd()}${needsComma ? "," : ""} ${valueLiteral}${close}`;
3665
+ });
3666
+ }
3667
+ function insertTopLevelJsonArrayProperty(raw, propertyName, valueLiteral) {
3668
+ const lastBrace = raw.lastIndexOf("}");
3669
+ if (lastBrace === -1) return null;
3670
+ const before = raw.slice(0, lastBrace).trimEnd();
3671
+ const needsComma = !before.endsWith(",") && !before.endsWith("{");
3672
+ return `${before}${needsComma ? "," : ""}
3673
+ "${propertyName}": [${valueLiteral}]
3674
+ }
3404
3675
  `;
3405
- });
3406
- fs8.writeFileSync(eslintConfigPath, updated);
3407
- p.log.success(`Added "plugins/**" to ${eslintConfigFile} ignores`);
3408
- return;
3676
+ }
3677
+ function patchEslintConfig(raw) {
3678
+ const updatedIgnores = appendToArrayLiteral(raw, /(ignores\s*:\s*\[)([\s\S]*?)(\])/, '"plugins/**"');
3679
+ if (updatedIgnores) return updatedIgnores;
3680
+ const exportArrayRe = /(export\s+default\s+(?:\w+\.config\(|\[))\s*\n?/;
3681
+ if (exportArrayRe.test(raw)) {
3682
+ return raw.replace(exportArrayRe, (match) => `${match} { ignores: ["plugins/**"] },
3683
+ `);
3684
+ }
3685
+ return null;
3686
+ }
3687
+ function patchOxlintJsonConfig(raw) {
3688
+ const updatedIgnores = appendToArrayLiteral(
3689
+ raw,
3690
+ /("ignorePatterns"\s*:\s*\[)([\s\S]*?)(\])/,
3691
+ '"plugins/**"'
3692
+ );
3693
+ if (updatedIgnores) return updatedIgnores;
3694
+ return insertTopLevelJsonArrayProperty(raw, "ignorePatterns", '"plugins/**"');
3695
+ }
3696
+ function patchOxlintModuleConfig(raw) {
3697
+ const updatedIgnores = appendToArrayLiteral(
3698
+ raw,
3699
+ /(ignorePatterns\s*:\s*\[)([\s\S]*?)(\])/,
3700
+ '"plugins/**"'
3701
+ );
3702
+ if (updatedIgnores) return updatedIgnores;
3703
+ const exportObjectRe = /(export\s+default\s*\{)\s*\n?/;
3704
+ if (exportObjectRe.test(raw)) {
3705
+ return raw.replace(exportObjectRe, (match) => `${match}
3706
+ ignorePatterns: ["plugins/**"],`);
3707
+ }
3708
+ return null;
3709
+ }
3710
+ function ensureLintIgnores(projectRoot3) {
3711
+ const configs = findLintConfigs2(projectRoot3);
3712
+ for (const config of configs) {
3713
+ try {
3714
+ const raw = fs8.readFileSync(config.fullPath, "utf-8");
3715
+ if (/["']plugins\/\*\*["']/.test(raw)) continue;
3716
+ const updated = config.tool === "ESLint" ? patchEslintConfig(raw) : config.format === "json" ? patchOxlintJsonConfig(raw) : patchOxlintModuleConfig(raw);
3717
+ if (updated) {
3718
+ fs8.writeFileSync(config.fullPath, updated);
3719
+ const propertyName = config.tool === "ESLint" ? "ignores" : "ignorePatterns";
3720
+ p.log.success(`Added "plugins/**" to ${config.fileName} ${propertyName}`);
3721
+ } else {
3722
+ const propertyName = config.tool === "ESLint" ? "ignores" : "ignorePatterns";
3723
+ p.log.warn(
3724
+ `Could not patch ${config.fileName} \u2014 manually add "plugins/**" to ${propertyName}`
3725
+ );
3726
+ }
3727
+ } catch {
3728
+ const propertyName = config.tool === "ESLint" ? "ignores" : "ignorePatterns";
3729
+ p.log.warn(
3730
+ `Could not update ${config.fileName} \u2014 manually add "plugins/**" to ${propertyName}`
3731
+ );
3409
3732
  }
3410
- p.log.warn(`Could not patch ${eslintConfigFile} \u2014 manually add "plugins/**" to the ignores array`);
3411
- } catch {
3412
- p.log.warn(`Could not update ${eslintConfigFile} \u2014 manually add "plugins/**" to the ignores array`);
3413
3733
  }
3414
3734
  }
3415
3735
  function detectAgents(projectRoot3) {
@@ -3584,7 +3904,7 @@ async function setup(yes2) {
3584
3904
  await setupAgentInstructions(projectRoot3, selectedAgents);
3585
3905
  registerMcpServers(projectRoot3, selectedAgents);
3586
3906
  ensureTsconfigExclude(projectRoot3);
3587
- ensureEslintIgnore(projectRoot3);
3907
+ ensureLintIgnores(projectRoot3);
3588
3908
  await runVerification(targetDir, selectedAgents);
3589
3909
  }
3590
3910
  async function removePlugin(projectRoot3, pluginDir, options) {
@@ -117,7 +117,7 @@ function distToSource(resolvedPath, projectRoot) {
117
117
  }
118
118
  return resolvedPath;
119
119
  }
120
- function resolveImport(resolver, fromDir, specifier, projectRoot) {
120
+ function resolveProjectImport(resolver, fromDir, specifier, projectRoot) {
121
121
  try {
122
122
  const result = resolver.sync(fromDir, specifier);
123
123
  if (result.path && !result.path.includes("node_modules")) {
@@ -168,7 +168,7 @@ function buildForwardEdges(files, resolver, projectRoot) {
168
168
  const edges = [];
169
169
  const fromDir = path.dirname(filePath);
170
170
  for (const raw of rawImports) {
171
- const target = resolveImport(resolver, fromDir, raw.specifier, projectRoot);
171
+ const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot);
172
172
  if (target) {
173
173
  edges.push({
174
174
  target,
@@ -249,7 +249,7 @@ function updateFile(graph, filePath, resolver, projectRoot) {
249
249
  const fromDir = path.dirname(filePath);
250
250
  const newEdges = [];
251
251
  for (const raw of rawImports) {
252
- const target = resolveImport(resolver, fromDir, raw.specifier, projectRoot);
252
+ const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot);
253
253
  if (target) {
254
254
  newEdges.push({
255
255
  target,
@@ -331,6 +331,7 @@ export {
331
331
  createResolver,
332
332
  discoverFiles,
333
333
  removeFile,
334
+ resolveProjectImport,
334
335
  startWatcher,
335
336
  updateFile
336
337
  };