typegraph-mcp 0.9.38 → 0.9.39

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/check.ts CHANGED
@@ -209,6 +209,44 @@ function hasDeclaredDependency(packageJson: Record<string, unknown> | null, pack
209
209
  });
210
210
  }
211
211
 
212
+ const ESLINT_CONFIG_NAMES = [
213
+ "eslint.config.mjs",
214
+ "eslint.config.js",
215
+ "eslint.config.ts",
216
+ "eslint.config.cjs",
217
+ ];
218
+ const OXLINT_CONFIG_NAMES = [
219
+ ".oxlintrc.json",
220
+ "oxlint.config.ts",
221
+ "oxlint.config.js",
222
+ "oxlint.config.mjs",
223
+ "oxlint.config.cjs",
224
+ ];
225
+
226
+ type LintConfigCheck =
227
+ | { tool: "ESLint"; fileName: string; fullPath: string; propertyName: "ignores" }
228
+ | { tool: "Oxlint"; fileName: string; fullPath: string; propertyName: "ignorePatterns" };
229
+
230
+ function findLintConfigs(projectRoot: string): LintConfigCheck[] {
231
+ const configs: LintConfigCheck[] = [];
232
+
233
+ for (const fileName of ESLINT_CONFIG_NAMES) {
234
+ const fullPath = path.resolve(projectRoot, fileName);
235
+ if (fs.existsSync(fullPath)) {
236
+ configs.push({ tool: "ESLint", fileName, fullPath, propertyName: "ignores" });
237
+ }
238
+ }
239
+
240
+ for (const fileName of OXLINT_CONFIG_NAMES) {
241
+ const fullPath = path.resolve(projectRoot, fileName);
242
+ if (fs.existsSync(fullPath)) {
243
+ configs.push({ tool: "Oxlint", fileName, fullPath, propertyName: "ignorePatterns" });
244
+ }
245
+ }
246
+
247
+ return configs;
248
+ }
249
+
212
250
  // ─── Main ────────────────────────────────────────────────────────────────────
213
251
 
214
252
  export async function main(configOverride?: TypegraphConfig): Promise<CheckResult> {
@@ -527,31 +565,32 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
527
565
  );
528
566
  }
529
567
 
530
- // 11. ESLint ignores (only when typegraph-mcp is embedded inside the project)
568
+ // 11. Lint ignores (only when typegraph-mcp is embedded inside the project)
531
569
  if (toolIsEmbedded) {
532
- const eslintConfigNames = ["eslint.config.mjs", "eslint.config.js", "eslint.config.ts", "eslint.config.cjs"];
533
- const eslintConfigFile = eslintConfigNames.find((name) => fs.existsSync(path.resolve(projectRoot, name)));
534
- if (eslintConfigFile) {
535
- const eslintConfigPath = path.resolve(projectRoot, eslintConfigFile);
536
- const eslintContent = fs.readFileSync(eslintConfigPath, "utf-8");
570
+ const lintConfigs = findLintConfigs(projectRoot);
571
+ if (lintConfigs.length > 0) {
537
572
  // Determine the parent directory (e.g. "plugins") for the ignore pattern
538
573
  const parentDir = path.basename(path.dirname(toolDir));
539
574
  const parentIgnorePattern = new RegExp(`["']${parentDir}\\/\\*\\*["']`);
540
- const hasParentIgnore = parentIgnorePattern.test(eslintContent);
541
575
 
542
- if (hasParentIgnore) {
543
- pass(`ESLint ignores ${parentDir}/`);
544
- } else {
545
- fail(
546
- `ESLint missing ignore: "${parentDir}/**"`,
547
- `Add to the ignores array in ${eslintConfigFile}:\n "${parentDir}/**",`
548
- );
576
+ for (const config of lintConfigs) {
577
+ const content = fs.readFileSync(config.fullPath, "utf-8");
578
+ const hasParentIgnore = parentIgnorePattern.test(content);
579
+
580
+ if (hasParentIgnore) {
581
+ pass(`${config.tool} ignores ${parentDir}/ (${config.fileName})`);
582
+ } else {
583
+ fail(
584
+ `${config.tool} missing ignore: "${parentDir}/**" (${config.fileName})`,
585
+ `Add to ${config.propertyName} in ${config.fileName}:\n "${parentDir}/**",`
586
+ );
587
+ }
549
588
  }
550
589
  } else {
551
- skip("ESLint config check (no eslint flat config found)");
590
+ skip("Lint config check (no ESLint or Oxlint config found)");
552
591
  }
553
592
  } else {
554
- skip("ESLint config check (typegraph-mcp is external to project)");
593
+ skip("Lint config check (typegraph-mcp is external to project)");
555
594
  }
556
595
 
557
596
  // 12. .gitignore check (optional)
package/cli.ts CHANGED
@@ -601,19 +601,13 @@ function ensureTsconfigExclude(projectRoot: string): void {
601
601
 
602
602
  try {
603
603
  const raw = fs.readFileSync(tsconfigPath, "utf-8");
604
- // Strip single-line comments (// ...) and trailing commas for JSON.parse
605
- const stripped = raw
606
- .replace(/\/\/.*$/gm, "")
607
- .replace(/,(\s*[}\]])/g, "$1");
608
- const tsconfig = JSON.parse(stripped);
609
-
610
- const exclude: string[] = tsconfig.exclude || [];
611
- if (exclude.some((e: string) => e === "plugins" || e === "plugins/**" || e === "plugins/*")) {
612
- return; // Already excluded
604
+ const excludeArrayMatch = raw.match(/("exclude"\s*:\s*\[)([\s\S]*?)(\])/);
605
+ if (excludeArrayMatch && /["']plugins(?:\/\*\*|\/\*|)["']/.test(excludeArrayMatch[2])) {
606
+ return;
613
607
  }
614
608
 
615
609
  // Insert "plugins/**" into the exclude array in the original file
616
- if (raw.includes('"exclude"')) {
610
+ if (excludeArrayMatch) {
617
611
  // Existing exclude array — append to it
618
612
  const updated = raw.replace(
619
613
  /("exclude"\s*:\s*\[)([\s\S]*?)(\])/,
@@ -642,48 +636,137 @@ function ensureTsconfigExclude(projectRoot: string): void {
642
636
  }
643
637
  }
644
638
 
645
- // ─── ESLint Ignore ───────────────────────────────────────────────────────────
639
+ // ─── Lint Ignore ─────────────────────────────────────────────────────────────
646
640
 
647
- function ensureEslintIgnore(projectRoot: string): void {
648
- const eslintConfigNames = ["eslint.config.mjs", "eslint.config.js", "eslint.config.ts", "eslint.config.cjs"];
649
- const eslintConfigFile = eslintConfigNames.find((name) => fs.existsSync(path.resolve(projectRoot, name)));
650
- if (!eslintConfigFile) return;
651
- const eslintConfigPath = path.resolve(projectRoot, eslintConfigFile);
641
+ const ESLINT_CONFIG_NAMES = [
642
+ "eslint.config.mjs",
643
+ "eslint.config.js",
644
+ "eslint.config.ts",
645
+ "eslint.config.cjs",
646
+ ];
647
+ const OXLINT_CONFIG_NAMES = [
648
+ ".oxlintrc.json",
649
+ "oxlint.config.ts",
650
+ "oxlint.config.js",
651
+ "oxlint.config.mjs",
652
+ "oxlint.config.cjs",
653
+ ];
652
654
 
653
- try {
654
- const raw = fs.readFileSync(eslintConfigPath, "utf-8");
655
- const pattern = /["']plugins\/\*\*["']/;
656
- if (pattern.test(raw)) return; // Already ignored
655
+ type LintConfig =
656
+ | { tool: "ESLint"; fileName: string; fullPath: string; format: "flat" }
657
+ | { tool: "Oxlint"; fileName: string; fullPath: string; format: "json" | "module" };
657
658
 
658
- // Strategy 1: Append to an existing ignores array
659
- const ignoresArrayRe = /(ignores\s*:\s*\[)([\s\S]*?)(\])/;
660
- const match = raw.match(ignoresArrayRe);
661
- if (match) {
662
- const updated = raw.replace(ignoresArrayRe, (_m, open, items, close) => {
663
- const trimmed = items.trimEnd();
664
- const needsComma = trimmed.length > 0 && !trimmed.endsWith(",");
665
- return `${open}${items.trimEnd()}${needsComma ? "," : ""} "plugins/**"${close}`;
666
- });
667
- fs.writeFileSync(eslintConfigPath, updated);
668
- p.log.success(`Added "plugins/**" to ${eslintConfigFile} ignores`);
669
- return;
659
+ function findLintConfigs(projectRoot: string): LintConfig[] {
660
+ const configs: LintConfig[] = [];
661
+
662
+ for (const fileName of ESLINT_CONFIG_NAMES) {
663
+ const fullPath = path.resolve(projectRoot, fileName);
664
+ if (fs.existsSync(fullPath)) {
665
+ configs.push({ tool: "ESLint", fileName, fullPath, format: "flat" });
670
666
  }
667
+ }
671
668
 
672
- // Strategy 2: Insert a new ignores object at the start of the exported array
673
- // Matches: export default [ or export default tseslint.config(
674
- const exportArrayRe = /(export\s+default\s+(?:\w+\.config\(|\[))\s*\n?/;
675
- if (exportArrayRe.test(raw)) {
676
- const updated = raw.replace(exportArrayRe, (m) => {
677
- return `${m} { ignores: ["plugins/**"] },\n`;
669
+ for (const fileName of OXLINT_CONFIG_NAMES) {
670
+ const fullPath = path.resolve(projectRoot, fileName);
671
+ if (fs.existsSync(fullPath)) {
672
+ configs.push({
673
+ tool: "Oxlint",
674
+ fileName,
675
+ fullPath,
676
+ format: fileName.endsWith(".json") ? "json" : "module",
678
677
  });
679
- fs.writeFileSync(eslintConfigPath, updated);
680
- p.log.success(`Added "plugins/**" to ${eslintConfigFile} ignores`);
681
- return;
682
678
  }
679
+ }
683
680
 
684
- p.log.warn(`Could not patch ${eslintConfigFile} — manually add "plugins/**" to the ignores array`);
685
- } catch {
686
- p.log.warn(`Could not update ${eslintConfigFile} — manually add "plugins/**" to the ignores array`);
681
+ return configs;
682
+ }
683
+
684
+ function appendToArrayLiteral(raw: string, propertyPattern: RegExp, valueLiteral: string): string | null {
685
+ if (!propertyPattern.test(raw)) return null;
686
+ return raw.replace(propertyPattern, (_match, open, items, close) => {
687
+ const trimmed = items.trimEnd();
688
+ const needsComma = trimmed.length > 0 && !trimmed.endsWith(",");
689
+ return `${open}${items.trimEnd()}${needsComma ? "," : ""} ${valueLiteral}${close}`;
690
+ });
691
+ }
692
+
693
+ function insertTopLevelJsonArrayProperty(raw: string, propertyName: string, valueLiteral: string): string | null {
694
+ const lastBrace = raw.lastIndexOf("}");
695
+ if (lastBrace === -1) return null;
696
+ const before = raw.slice(0, lastBrace).trimEnd();
697
+ const needsComma = !before.endsWith(",") && !before.endsWith("{");
698
+ return `${before}${needsComma ? "," : ""}\n "${propertyName}": [${valueLiteral}]\n}\n`;
699
+ }
700
+
701
+ function patchEslintConfig(raw: string): string | null {
702
+ const updatedIgnores = appendToArrayLiteral(raw, /(ignores\s*:\s*\[)([\s\S]*?)(\])/, '"plugins/**"');
703
+ if (updatedIgnores) return updatedIgnores;
704
+
705
+ // Matches: export default [ or export default tseslint.config(
706
+ const exportArrayRe = /(export\s+default\s+(?:\w+\.config\(|\[))\s*\n?/;
707
+ if (exportArrayRe.test(raw)) {
708
+ return raw.replace(exportArrayRe, (match) => `${match} { ignores: ["plugins/**"] },\n`);
709
+ }
710
+
711
+ return null;
712
+ }
713
+
714
+ function patchOxlintJsonConfig(raw: string): string | null {
715
+ const updatedIgnores = appendToArrayLiteral(
716
+ raw,
717
+ /("ignorePatterns"\s*:\s*\[)([\s\S]*?)(\])/,
718
+ '"plugins/**"'
719
+ );
720
+ if (updatedIgnores) return updatedIgnores;
721
+ return insertTopLevelJsonArrayProperty(raw, "ignorePatterns", '"plugins/**"');
722
+ }
723
+
724
+ function patchOxlintModuleConfig(raw: string): string | null {
725
+ const updatedIgnores = appendToArrayLiteral(
726
+ raw,
727
+ /(ignorePatterns\s*:\s*\[)([\s\S]*?)(\])/,
728
+ '"plugins/**"'
729
+ );
730
+ if (updatedIgnores) return updatedIgnores;
731
+
732
+ const exportObjectRe = /(export\s+default\s*\{)\s*\n?/;
733
+ if (exportObjectRe.test(raw)) {
734
+ return raw.replace(exportObjectRe, (match) => `${match}\n ignorePatterns: ["plugins/**"],`);
735
+ }
736
+
737
+ return null;
738
+ }
739
+
740
+ function ensureLintIgnores(projectRoot: string): void {
741
+ const configs = findLintConfigs(projectRoot);
742
+ for (const config of configs) {
743
+ try {
744
+ const raw = fs.readFileSync(config.fullPath, "utf-8");
745
+ if (/["']plugins\/\*\*["']/.test(raw)) continue;
746
+
747
+ const updated =
748
+ config.tool === "ESLint"
749
+ ? patchEslintConfig(raw)
750
+ : config.format === "json"
751
+ ? patchOxlintJsonConfig(raw)
752
+ : patchOxlintModuleConfig(raw);
753
+
754
+ if (updated) {
755
+ fs.writeFileSync(config.fullPath, updated);
756
+ const propertyName = config.tool === "ESLint" ? "ignores" : "ignorePatterns";
757
+ p.log.success(`Added "plugins/**" to ${config.fileName} ${propertyName}`);
758
+ } else {
759
+ const propertyName = config.tool === "ESLint" ? "ignores" : "ignorePatterns";
760
+ p.log.warn(
761
+ `Could not patch ${config.fileName} — manually add "plugins/**" to ${propertyName}`
762
+ );
763
+ }
764
+ } catch {
765
+ const propertyName = config.tool === "ESLint" ? "ignores" : "ignorePatterns";
766
+ p.log.warn(
767
+ `Could not update ${config.fileName} — manually add "plugins/**" to ${propertyName}`
768
+ );
769
+ }
687
770
  }
688
771
  }
689
772
 
@@ -920,8 +1003,8 @@ async function setup(yes: boolean): Promise<void> {
920
1003
  // 8. Ensure plugins/ is excluded from tsconfig
921
1004
  ensureTsconfigExclude(projectRoot);
922
1005
 
923
- // 9. Ensure plugins/ is ignored by ESLint
924
- ensureEslintIgnore(projectRoot);
1006
+ // 9. Ensure plugins/ is ignored by supported lint configs
1007
+ ensureLintIgnores(projectRoot);
925
1008
 
926
1009
  // 10. Verification
927
1010
  await runVerification(targetDir, selectedAgents);
package/dist/benchmark.js CHANGED
@@ -387,7 +387,7 @@ function distToSource(resolvedPath, projectRoot2) {
387
387
  }
388
388
  return resolvedPath;
389
389
  }
390
- function resolveImport(resolver, fromDir, specifier, projectRoot2) {
390
+ function resolveProjectImport(resolver, fromDir, specifier, projectRoot2) {
391
391
  try {
392
392
  const result = resolver.sync(fromDir, specifier);
393
393
  if (result.path && !result.path.includes("node_modules")) {
@@ -438,7 +438,7 @@ function buildForwardEdges(files, resolver, projectRoot2) {
438
438
  const edges = [];
439
439
  const fromDir = path2.dirname(filePath);
440
440
  for (const raw of rawImports) {
441
- const target = resolveImport(resolver, fromDir, raw.specifier, projectRoot2);
441
+ const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot2);
442
442
  if (target) {
443
443
  edges.push({
444
444
  target,
package/dist/check.js CHANGED
@@ -15,6 +15,7 @@ __export(module_graph_exports, {
15
15
  createResolver: () => createResolver,
16
16
  discoverFiles: () => discoverFiles,
17
17
  removeFile: () => removeFile,
18
+ resolveProjectImport: () => resolveProjectImport,
18
19
  startWatcher: () => startWatcher,
19
20
  updateFile: () => updateFile
20
21
  });
@@ -120,7 +121,7 @@ function distToSource(resolvedPath, projectRoot) {
120
121
  }
121
122
  return resolvedPath;
122
123
  }
123
- function resolveImport(resolver, fromDir, specifier, projectRoot) {
124
+ function resolveProjectImport(resolver, fromDir, specifier, projectRoot) {
124
125
  try {
125
126
  const result = resolver.sync(fromDir, specifier);
126
127
  if (result.path && !result.path.includes("node_modules")) {
@@ -171,7 +172,7 @@ function buildForwardEdges(files, resolver, projectRoot) {
171
172
  const edges = [];
172
173
  const fromDir = path2.dirname(filePath);
173
174
  for (const raw of rawImports) {
174
- const target = resolveImport(resolver, fromDir, raw.specifier, projectRoot);
175
+ const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot);
175
176
  if (target) {
176
177
  edges.push({
177
178
  target,
@@ -252,7 +253,7 @@ function updateFile(graph, filePath, resolver, projectRoot) {
252
253
  const fromDir = path2.dirname(filePath);
253
254
  const newEdges = [];
254
255
  for (const raw of rawImports) {
255
- const target = resolveImport(resolver, fromDir, raw.specifier, projectRoot);
256
+ const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot);
256
257
  if (target) {
257
258
  newEdges.push({
258
259
  target,
@@ -506,6 +507,35 @@ function hasDeclaredDependency(packageJson, packageName) {
506
507
  return typeof deps === "object" && deps !== null && packageName in deps;
507
508
  });
508
509
  }
510
+ var ESLINT_CONFIG_NAMES = [
511
+ "eslint.config.mjs",
512
+ "eslint.config.js",
513
+ "eslint.config.ts",
514
+ "eslint.config.cjs"
515
+ ];
516
+ var OXLINT_CONFIG_NAMES = [
517
+ ".oxlintrc.json",
518
+ "oxlint.config.ts",
519
+ "oxlint.config.js",
520
+ "oxlint.config.mjs",
521
+ "oxlint.config.cjs"
522
+ ];
523
+ function findLintConfigs(projectRoot) {
524
+ const configs = [];
525
+ for (const fileName of ESLINT_CONFIG_NAMES) {
526
+ const fullPath = path3.resolve(projectRoot, fileName);
527
+ if (fs2.existsSync(fullPath)) {
528
+ configs.push({ tool: "ESLint", fileName, fullPath, propertyName: "ignores" });
529
+ }
530
+ }
531
+ for (const fileName of OXLINT_CONFIG_NAMES) {
532
+ const fullPath = path3.resolve(projectRoot, fileName);
533
+ if (fs2.existsSync(fullPath)) {
534
+ configs.push({ tool: "Oxlint", fileName, fullPath, propertyName: "ignorePatterns" });
535
+ }
536
+ }
537
+ return configs;
538
+ }
509
539
  async function main(configOverride) {
510
540
  const { projectRoot, tsconfigPath, toolDir, toolIsEmbedded, toolRelPath } = configOverride ?? resolveConfig(import.meta.dirname);
511
541
  let passed = 0;
@@ -783,28 +813,28 @@ async function main(configOverride) {
783
813
  );
784
814
  }
785
815
  if (toolIsEmbedded) {
786
- const eslintConfigNames = ["eslint.config.mjs", "eslint.config.js", "eslint.config.ts", "eslint.config.cjs"];
787
- const eslintConfigFile = eslintConfigNames.find((name) => fs2.existsSync(path3.resolve(projectRoot, name)));
788
- if (eslintConfigFile) {
789
- const eslintConfigPath = path3.resolve(projectRoot, eslintConfigFile);
790
- const eslintContent = fs2.readFileSync(eslintConfigPath, "utf-8");
816
+ const lintConfigs = findLintConfigs(projectRoot);
817
+ if (lintConfigs.length > 0) {
791
818
  const parentDir = path3.basename(path3.dirname(toolDir));
792
819
  const parentIgnorePattern = new RegExp(`["']${parentDir}\\/\\*\\*["']`);
793
- const hasParentIgnore = parentIgnorePattern.test(eslintContent);
794
- if (hasParentIgnore) {
795
- pass(`ESLint ignores ${parentDir}/`);
796
- } else {
797
- fail(
798
- `ESLint missing ignore: "${parentDir}/**"`,
799
- `Add to the ignores array in ${eslintConfigFile}:
820
+ for (const config of lintConfigs) {
821
+ const content = fs2.readFileSync(config.fullPath, "utf-8");
822
+ const hasParentIgnore = parentIgnorePattern.test(content);
823
+ if (hasParentIgnore) {
824
+ pass(`${config.tool} ignores ${parentDir}/ (${config.fileName})`);
825
+ } else {
826
+ fail(
827
+ `${config.tool} missing ignore: "${parentDir}/**" (${config.fileName})`,
828
+ `Add to ${config.propertyName} in ${config.fileName}:
800
829
  "${parentDir}/**",`
801
- );
830
+ );
831
+ }
802
832
  }
803
833
  } else {
804
- skip("ESLint config check (no eslint flat config found)");
834
+ skip("Lint config check (no ESLint or Oxlint config found)");
805
835
  }
806
836
  } else {
807
- skip("ESLint config check (typegraph-mcp is external to project)");
837
+ skip("Lint config check (typegraph-mcp is external to project)");
808
838
  }
809
839
  const gitignorePath = path3.resolve(projectRoot, ".gitignore");
810
840
  if (fs2.existsSync(gitignorePath)) {