typegraph-mcp 0.9.37 → 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 +55 -16
- package/cli.ts +131 -46
- package/dist/benchmark.js +2 -2
- package/dist/check.js +48 -18
- package/dist/cli.js +419 -97
- package/dist/module-graph.js +4 -3
- package/dist/server.js +261 -43
- package/dist/smoke-test.js +2 -2
- package/export-surface-test.ts +202 -0
- package/install-oxlint-test.ts +95 -0
- package/module-graph.ts +3 -3
- package/package.json +2 -2
- package/server.ts +375 -55
- package/skills/code-exploration/SKILL.md +7 -0
- package/skills/deep-survey/SKILL.md +4 -0
- package/skills/tool-selection/SKILL.md +3 -0
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
|
|
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 =
|
|
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 =
|
|
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:
|
|
694
|
-
const result =
|
|
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
|
|
794
|
-
|
|
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
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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("
|
|
828
|
+
skip("Lint config check (no ESLint or Oxlint config found)");
|
|
812
829
|
}
|
|
813
830
|
} else {
|
|
814
|
-
skip("
|
|
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(
|
|
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
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
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({
|
|
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
|
};
|
|
@@ -2927,6 +3175,8 @@ Where suitable, use the \`ts_*\` MCP tools instead of grep/glob for navigating T
|
|
|
2927
3175
|
|
|
2928
3176
|
Start with the navigation tools before reading entire files. Use direct file reads only after the MCP tools identify the exact symbols or lines that matter.
|
|
2929
3177
|
|
|
3178
|
+
For quick architectural insight, prefer composition modules and entrypoints over top-level barrel files. If \`ts_module_exports\` on an \`index.ts\` or other barrel looks empty or uninformative, pivot to the app entrypoint, router, handler, service composition root, or API module that wires real behavior together.
|
|
3179
|
+
|
|
2930
3180
|
Use \`rg\` or \`grep\` when semantic symbol navigation is not the right tool, especially for:
|
|
2931
3181
|
|
|
2932
3182
|
- docs, config, SQL, migrations, JSON, env vars, route strings, and other non-TypeScript assets
|
|
@@ -3340,13 +3590,11 @@ function ensureTsconfigExclude(projectRoot3) {
|
|
|
3340
3590
|
if (!fs8.existsSync(tsconfigPath3)) return;
|
|
3341
3591
|
try {
|
|
3342
3592
|
const raw = fs8.readFileSync(tsconfigPath3, "utf-8");
|
|
3343
|
-
const
|
|
3344
|
-
|
|
3345
|
-
const exclude = tsconfig.exclude || [];
|
|
3346
|
-
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])) {
|
|
3347
3595
|
return;
|
|
3348
3596
|
}
|
|
3349
|
-
if (
|
|
3597
|
+
if (excludeArrayMatch) {
|
|
3350
3598
|
const updated = raw.replace(
|
|
3351
3599
|
/("exclude"\s*:\s*\[)([\s\S]*?)(\])/,
|
|
3352
3600
|
(_match, open, items, close) => {
|
|
@@ -3374,40 +3622,114 @@ function ensureTsconfigExclude(projectRoot3) {
|
|
|
3374
3622
|
p.log.warn('Could not update tsconfig.json \u2014 manually add "plugins/**" to the exclude array to prevent build errors');
|
|
3375
3623
|
}
|
|
3376
3624
|
}
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
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"
|
|
3393
3654
|
});
|
|
3394
|
-
fs8.writeFileSync(eslintConfigPath, updated);
|
|
3395
|
-
p.log.success(`Added "plugins/**" to ${eslintConfigFile} ignores`);
|
|
3396
|
-
return;
|
|
3397
3655
|
}
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
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
|
+
}
|
|
3402
3675
|
`;
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
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
|
+
);
|
|
3407
3732
|
}
|
|
3408
|
-
p.log.warn(`Could not patch ${eslintConfigFile} \u2014 manually add "plugins/**" to the ignores array`);
|
|
3409
|
-
} catch {
|
|
3410
|
-
p.log.warn(`Could not update ${eslintConfigFile} \u2014 manually add "plugins/**" to the ignores array`);
|
|
3411
3733
|
}
|
|
3412
3734
|
}
|
|
3413
3735
|
function detectAgents(projectRoot3) {
|
|
@@ -3582,7 +3904,7 @@ async function setup(yes2) {
|
|
|
3582
3904
|
await setupAgentInstructions(projectRoot3, selectedAgents);
|
|
3583
3905
|
registerMcpServers(projectRoot3, selectedAgents);
|
|
3584
3906
|
ensureTsconfigExclude(projectRoot3);
|
|
3585
|
-
|
|
3907
|
+
ensureLintIgnores(projectRoot3);
|
|
3586
3908
|
await runVerification(targetDir, selectedAgents);
|
|
3587
3909
|
}
|
|
3588
3910
|
async function removePlugin(projectRoot3, pluginDir, options) {
|