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/check.ts +55 -16
- package/cli.ts +129 -46
- package/dist/benchmark.js +2 -2
- package/dist/check.js +48 -18
- package/dist/cli.js +417 -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/module-graph.ts
CHANGED
|
@@ -216,7 +216,7 @@ function distToSource(resolvedPath: string, projectRoot: string): string {
|
|
|
216
216
|
return resolvedPath;
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
function
|
|
219
|
+
export function resolveProjectImport(
|
|
220
220
|
resolver: ResolverFactory,
|
|
221
221
|
fromDir: string,
|
|
222
222
|
specifier: string,
|
|
@@ -287,7 +287,7 @@ function buildForwardEdges(
|
|
|
287
287
|
const fromDir = path.dirname(filePath);
|
|
288
288
|
|
|
289
289
|
for (const raw of rawImports) {
|
|
290
|
-
const target =
|
|
290
|
+
const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot);
|
|
291
291
|
if (target) {
|
|
292
292
|
edges.push({
|
|
293
293
|
target,
|
|
@@ -397,7 +397,7 @@ export function updateFile(
|
|
|
397
397
|
const fromDir = path.dirname(filePath);
|
|
398
398
|
const newEdges: ImportEdge[] = [];
|
|
399
399
|
for (const raw of rawImports) {
|
|
400
|
-
const target =
|
|
400
|
+
const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot);
|
|
401
401
|
if (target) {
|
|
402
402
|
newEdges.push({
|
|
403
403
|
target,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typegraph-mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.40",
|
|
4
4
|
"description": "Type-aware codebase navigation for AI coding agents — 14 MCP tools powered by tsserver + oxc",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "tsup",
|
|
36
36
|
"start": "tsx server.ts",
|
|
37
|
-
"test": "tsx smoke-test.ts",
|
|
37
|
+
"test": "tsx smoke-test.ts && tsx export-surface-test.ts && tsx install-oxlint-test.ts",
|
|
38
38
|
"check": "tsx check.ts"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
package/server.ts
CHANGED
|
@@ -17,9 +17,16 @@
|
|
|
17
17
|
|
|
18
18
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
19
19
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
20
|
+
import { parseSync } from "oxc-parser";
|
|
21
|
+
import type { ResolverFactory } from "oxc-resolver";
|
|
20
22
|
import { z } from "zod";
|
|
21
23
|
import { TsServerClient, type NavBarItem } from "./tsserver-client.js";
|
|
22
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
buildGraph,
|
|
26
|
+
resolveProjectImport,
|
|
27
|
+
startWatcher,
|
|
28
|
+
type ModuleGraph,
|
|
29
|
+
} from "./module-graph.js";
|
|
23
30
|
import {
|
|
24
31
|
dependencyTree,
|
|
25
32
|
dependents,
|
|
@@ -44,6 +51,7 @@ const client = new TsServerClient(projectRoot, tsconfigPath);
|
|
|
44
51
|
|
|
45
52
|
// Module graph — initialized in main(), used by graph tools
|
|
46
53
|
let moduleGraph: ModuleGraph;
|
|
54
|
+
let moduleResolver: ResolverFactory;
|
|
47
55
|
|
|
48
56
|
const mcpServer = new McpServer({
|
|
49
57
|
name: "typegraph",
|
|
@@ -167,6 +175,347 @@ async function resolveParams(params: {
|
|
|
167
175
|
return { error: "Either line+column or symbol must be provided" };
|
|
168
176
|
}
|
|
169
177
|
|
|
178
|
+
type ModuleExportRecord = {
|
|
179
|
+
symbol: string;
|
|
180
|
+
kind: string;
|
|
181
|
+
line: number;
|
|
182
|
+
type: string | null;
|
|
183
|
+
exportKind: "value" | "type";
|
|
184
|
+
isTypeOnly: boolean;
|
|
185
|
+
isNamespace: boolean;
|
|
186
|
+
source: "local" | "re-export" | "star-re-export";
|
|
187
|
+
from: string | null;
|
|
188
|
+
definedIn: string;
|
|
189
|
+
definedLine: number | null;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
type StaticExportEntry = ReturnType<typeof parseSync>["module"]["staticExports"][number]["entries"][number];
|
|
193
|
+
|
|
194
|
+
const exportKinds = new Set([
|
|
195
|
+
"function",
|
|
196
|
+
"const",
|
|
197
|
+
"class",
|
|
198
|
+
"interface",
|
|
199
|
+
"type",
|
|
200
|
+
"enum",
|
|
201
|
+
"var",
|
|
202
|
+
"let",
|
|
203
|
+
"method",
|
|
204
|
+
]);
|
|
205
|
+
|
|
206
|
+
function exportPriority(source: ModuleExportRecord["source"]): number {
|
|
207
|
+
switch (source) {
|
|
208
|
+
case "local":
|
|
209
|
+
return 3;
|
|
210
|
+
case "re-export":
|
|
211
|
+
return 2;
|
|
212
|
+
case "star-re-export":
|
|
213
|
+
return 1;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function exportKey(item: Pick<ModuleExportRecord, "symbol" | "exportKind">): string {
|
|
218
|
+
return `${item.symbol}:${item.exportKind}`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function sameExportOrigin(a: ModuleExportRecord, b: ModuleExportRecord): boolean {
|
|
222
|
+
return (
|
|
223
|
+
a.symbol === b.symbol &&
|
|
224
|
+
a.exportKind === b.exportKind &&
|
|
225
|
+
a.from === b.from &&
|
|
226
|
+
a.definedIn === b.definedIn &&
|
|
227
|
+
a.definedLine === b.definedLine
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function kindImpliesTypeOnly(kind: string): boolean {
|
|
232
|
+
return kind === "type" || kind === "interface";
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function normalizeExportKindLabel(
|
|
236
|
+
kind: string,
|
|
237
|
+
exportKind: ModuleExportRecord["exportKind"]
|
|
238
|
+
): string {
|
|
239
|
+
if (exportKind === "type" && !kindImpliesTypeOnly(kind)) {
|
|
240
|
+
return "type";
|
|
241
|
+
}
|
|
242
|
+
return kind;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function upsertExport(
|
|
246
|
+
map: Map<string, ModuleExportRecord>,
|
|
247
|
+
conflicts: Set<string>,
|
|
248
|
+
nextExport: ModuleExportRecord
|
|
249
|
+
): void {
|
|
250
|
+
const key = exportKey(nextExport);
|
|
251
|
+
if (conflicts.has(key)) {
|
|
252
|
+
if (nextExport.source === "star-re-export") return;
|
|
253
|
+
conflicts.delete(key);
|
|
254
|
+
map.set(key, nextExport);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const existing = map.get(key);
|
|
259
|
+
if (
|
|
260
|
+
existing &&
|
|
261
|
+
existing.source === "star-re-export" &&
|
|
262
|
+
nextExport.source === "star-re-export" &&
|
|
263
|
+
!sameExportOrigin(existing, nextExport)
|
|
264
|
+
) {
|
|
265
|
+
map.delete(key);
|
|
266
|
+
conflicts.add(key);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (!existing || exportPriority(nextExport.source) > exportPriority(existing.source)) {
|
|
271
|
+
map.set(key, nextExport);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function offsetToLineColumn(source: string, offset: number | null | undefined): {
|
|
276
|
+
line: number;
|
|
277
|
+
column: number;
|
|
278
|
+
} {
|
|
279
|
+
const safeOffset = Math.max(0, Math.min(offset ?? 0, source.length));
|
|
280
|
+
const prefix = source.slice(0, safeOffset);
|
|
281
|
+
const lines = prefix.split("\n");
|
|
282
|
+
return {
|
|
283
|
+
line: lines.length,
|
|
284
|
+
column: (lines.at(-1)?.length ?? 0) + 1,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function normalizeExistingPath(filePath: string): string {
|
|
289
|
+
const resolved = path.resolve(filePath);
|
|
290
|
+
try {
|
|
291
|
+
return fs.realpathSync.native(resolved);
|
|
292
|
+
} catch {
|
|
293
|
+
return resolved;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const normalizedProjectRoot = normalizeExistingPath(projectRoot);
|
|
298
|
+
|
|
299
|
+
function projectPath(file: string): string {
|
|
300
|
+
return path.isAbsolute(file) ? relPath(file) : file;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function exportSymbol(entry: StaticExportEntry): string | null {
|
|
304
|
+
if (entry.exportName.kind === "Default") return "default";
|
|
305
|
+
return entry.exportName.name ?? entry.localName.name ?? entry.importName.name;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function exportLookupOffset(entry: StaticExportEntry): number | null | undefined {
|
|
309
|
+
if ((entry as { moduleRequest?: { value: string } }).moduleRequest) {
|
|
310
|
+
return entry.importName.start ?? entry.exportName.start ?? entry.start;
|
|
311
|
+
}
|
|
312
|
+
if (entry.exportName.kind === "Default") {
|
|
313
|
+
return entry.localName.start ?? entry.exportName.start ?? entry.start;
|
|
314
|
+
}
|
|
315
|
+
return entry.exportName.start ?? entry.localName.start ?? entry.start;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async function resolveExportMetadata(
|
|
319
|
+
file: string,
|
|
320
|
+
line: number,
|
|
321
|
+
column: number,
|
|
322
|
+
fallbackKind: string
|
|
323
|
+
): Promise<{
|
|
324
|
+
kind: string;
|
|
325
|
+
type: string | null;
|
|
326
|
+
definedIn: string;
|
|
327
|
+
definedLine: number | null;
|
|
328
|
+
}> {
|
|
329
|
+
const defs = await client.definition(file, line, column);
|
|
330
|
+
const def = defs[0] ?? null;
|
|
331
|
+
|
|
332
|
+
let info = await client.quickinfo(file, line, column);
|
|
333
|
+
if ((!info || info.kind === "alias") && def) {
|
|
334
|
+
info = (await client.quickinfo(def.file, def.start.line, def.start.offset)) ?? info;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
kind: info?.kind ?? fallbackKind,
|
|
339
|
+
type: info?.displayString ?? null,
|
|
340
|
+
definedIn: projectPath(def?.file ?? file),
|
|
341
|
+
definedLine: def?.start.line ?? null,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function getModuleExports(
|
|
346
|
+
file: string,
|
|
347
|
+
visited = new Set<string>()
|
|
348
|
+
): Promise<ModuleExportRecord[]> {
|
|
349
|
+
const relFile = path.isAbsolute(file) ? relPath(file) : file;
|
|
350
|
+
const absFile = normalizeExistingPath(client.resolvePath(relFile));
|
|
351
|
+
if (visited.has(absFile)) return [];
|
|
352
|
+
|
|
353
|
+
const nextVisited = new Set(visited);
|
|
354
|
+
nextVisited.add(absFile);
|
|
355
|
+
|
|
356
|
+
const exportMap = new Map<string, ModuleExportRecord>();
|
|
357
|
+
const conflictingStarExports = new Set<string>();
|
|
358
|
+
|
|
359
|
+
let source: string;
|
|
360
|
+
try {
|
|
361
|
+
source = fs.readFileSync(absFile, "utf-8");
|
|
362
|
+
} catch {
|
|
363
|
+
return [...exportMap.values()];
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
let parsed: ReturnType<typeof parseSync>;
|
|
367
|
+
try {
|
|
368
|
+
parsed = parseSync(absFile, source);
|
|
369
|
+
} catch {
|
|
370
|
+
return [...exportMap.values()];
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
for (const exp of parsed.module.staticExports) {
|
|
374
|
+
for (const entry of exp.entries) {
|
|
375
|
+
const moduleRequest = (entry as { moduleRequest?: { value: string } }).moduleRequest;
|
|
376
|
+
if (!moduleRequest) continue;
|
|
377
|
+
|
|
378
|
+
const targetFile = resolveProjectImport(
|
|
379
|
+
moduleResolver,
|
|
380
|
+
path.dirname(absFile),
|
|
381
|
+
moduleRequest.value,
|
|
382
|
+
projectRoot
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
const exportLoc = offsetToLineColumn(
|
|
386
|
+
source,
|
|
387
|
+
entry.exportName.start ?? entry.localName.start ?? entry.importName.start ?? entry.start
|
|
388
|
+
);
|
|
389
|
+
const importKind = entry.importName.kind as string;
|
|
390
|
+
const exportKind = entry.exportName.kind as string;
|
|
391
|
+
|
|
392
|
+
if (importKind === "AllButDefault" && exportKind === "None") {
|
|
393
|
+
if (!targetFile) continue;
|
|
394
|
+
const nestedExports = await getModuleExports(targetFile, nextVisited);
|
|
395
|
+
for (const nested of nestedExports) {
|
|
396
|
+
if (nested.symbol === "default") continue;
|
|
397
|
+
const starExportKind: ModuleExportRecord["exportKind"] = entry.isType
|
|
398
|
+
? "type"
|
|
399
|
+
: nested.exportKind;
|
|
400
|
+
upsertExport(exportMap, conflictingStarExports, {
|
|
401
|
+
...nested,
|
|
402
|
+
line: exportLoc.line,
|
|
403
|
+
exportKind: starExportKind,
|
|
404
|
+
isTypeOnly: starExportKind === "type",
|
|
405
|
+
source: "star-re-export",
|
|
406
|
+
from: relPath(targetFile),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const symbol = exportSymbol(entry);
|
|
413
|
+
if (!symbol) continue;
|
|
414
|
+
|
|
415
|
+
const importedSymbol =
|
|
416
|
+
importKind === "Default"
|
|
417
|
+
? "default"
|
|
418
|
+
: importKind === "Name"
|
|
419
|
+
? entry.importName.name
|
|
420
|
+
: null;
|
|
421
|
+
const nestedMatch =
|
|
422
|
+
targetFile && importedSymbol
|
|
423
|
+
? (await getModuleExports(targetFile, nextVisited)).find(
|
|
424
|
+
(item) => item.symbol === importedSymbol
|
|
425
|
+
) ?? null
|
|
426
|
+
: null;
|
|
427
|
+
|
|
428
|
+
const lookupLoc = offsetToLineColumn(
|
|
429
|
+
source,
|
|
430
|
+
exportLookupOffset(entry)
|
|
431
|
+
);
|
|
432
|
+
const metadata = await resolveExportMetadata(
|
|
433
|
+
relFile,
|
|
434
|
+
lookupLoc.line,
|
|
435
|
+
lookupLoc.column,
|
|
436
|
+
importKind === "All" ? "namespace" : "alias"
|
|
437
|
+
);
|
|
438
|
+
const resolvedExportKind: ModuleExportRecord["exportKind"] =
|
|
439
|
+
entry.isType ||
|
|
440
|
+
nestedMatch?.exportKind === "type" ||
|
|
441
|
+
kindImpliesTypeOnly(nestedMatch?.kind ?? metadata.kind)
|
|
442
|
+
? "type"
|
|
443
|
+
: "value";
|
|
444
|
+
const resolvedKind = normalizeExportKindLabel(
|
|
445
|
+
nestedMatch?.kind ?? metadata.kind,
|
|
446
|
+
resolvedExportKind
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
upsertExport(exportMap, conflictingStarExports, {
|
|
450
|
+
symbol,
|
|
451
|
+
kind: resolvedKind,
|
|
452
|
+
line: exportLoc.line,
|
|
453
|
+
type: nestedMatch?.type ?? metadata.type,
|
|
454
|
+
exportKind: resolvedExportKind,
|
|
455
|
+
isTypeOnly: resolvedExportKind === "type",
|
|
456
|
+
isNamespace: importKind === "All",
|
|
457
|
+
source: "re-export",
|
|
458
|
+
from: targetFile ? relPath(targetFile) : moduleRequest.value,
|
|
459
|
+
definedIn: nestedMatch?.definedIn ?? metadata.definedIn,
|
|
460
|
+
definedLine: nestedMatch?.definedLine ?? metadata.definedLine,
|
|
461
|
+
});
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
for (const entry of exp.entries) {
|
|
466
|
+
const moduleRequest = (entry as { moduleRequest?: { value: string } }).moduleRequest;
|
|
467
|
+
if (moduleRequest) continue;
|
|
468
|
+
|
|
469
|
+
const symbol = exportSymbol(entry);
|
|
470
|
+
if (!symbol) continue;
|
|
471
|
+
|
|
472
|
+
const exportLoc = offsetToLineColumn(
|
|
473
|
+
source,
|
|
474
|
+
entry.exportName.start ?? entry.localName.start ?? entry.start
|
|
475
|
+
);
|
|
476
|
+
const lookupLoc = offsetToLineColumn(source, exportLookupOffset(entry));
|
|
477
|
+
const metadata = await resolveExportMetadata(
|
|
478
|
+
relFile,
|
|
479
|
+
lookupLoc.line,
|
|
480
|
+
lookupLoc.column,
|
|
481
|
+
entry.isType ? "type" : "value"
|
|
482
|
+
);
|
|
483
|
+
const resolvedExportKind: ModuleExportRecord["exportKind"] =
|
|
484
|
+
entry.isType || kindImpliesTypeOnly(metadata.kind) ? "type" : "value";
|
|
485
|
+
const resolvedKind = normalizeExportKindLabel(metadata.kind, resolvedExportKind);
|
|
486
|
+
|
|
487
|
+
// Skip navbar/import alias noise — only keep actual exported declaration kinds.
|
|
488
|
+
if (
|
|
489
|
+
resolvedExportKind === "value" &&
|
|
490
|
+
symbol !== "default" &&
|
|
491
|
+
!exportKinds.has(resolvedKind) &&
|
|
492
|
+
resolvedKind !== "namespace" &&
|
|
493
|
+
resolvedKind !== "class"
|
|
494
|
+
) {
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
upsertExport(exportMap, conflictingStarExports, {
|
|
499
|
+
symbol,
|
|
500
|
+
kind: resolvedKind,
|
|
501
|
+
line: exportLoc.line,
|
|
502
|
+
type: metadata.type,
|
|
503
|
+
exportKind: resolvedExportKind,
|
|
504
|
+
isTypeOnly: resolvedExportKind === "type",
|
|
505
|
+
isNamespace: false,
|
|
506
|
+
source: "local",
|
|
507
|
+
from: null,
|
|
508
|
+
definedIn: relFile,
|
|
509
|
+
definedLine: resolvedExportKind === "type" ? exportLoc.line : metadata.definedLine,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return [...exportMap.values()].sort(
|
|
515
|
+
(a, b) => a.line - b.line || a.symbol.localeCompare(b.symbol)
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
|
|
170
519
|
// ─── Tool 1: ts_find_symbol ─────────────────────────────────────────────────
|
|
171
520
|
|
|
172
521
|
mcpServer.tool(
|
|
@@ -499,68 +848,38 @@ mcpServer.tool(
|
|
|
499
848
|
|
|
500
849
|
mcpServer.tool(
|
|
501
850
|
"ts_module_exports",
|
|
502
|
-
"List all exported symbols from a module with their resolved types. Gives an at-a-glance understanding of what a file provides.",
|
|
851
|
+
"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.",
|
|
503
852
|
{
|
|
504
853
|
file: z.string().describe("File to inspect"),
|
|
505
854
|
},
|
|
506
855
|
async ({ file }) => {
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// The top-level navbar item is the module itself — its children are exports
|
|
520
|
-
const moduleItem = bar.find((item) => item.kind === "module");
|
|
521
|
-
const topItems = moduleItem?.childItems ?? bar;
|
|
522
|
-
|
|
523
|
-
// Filter to meaningful declarations (skip imports, local vars, etc.)
|
|
524
|
-
const exportKinds = new Set([
|
|
525
|
-
"function",
|
|
526
|
-
"const",
|
|
527
|
-
"class",
|
|
528
|
-
"interface",
|
|
529
|
-
"type",
|
|
530
|
-
"enum",
|
|
531
|
-
"var",
|
|
532
|
-
"let",
|
|
533
|
-
"method",
|
|
534
|
-
]);
|
|
535
|
-
const candidates = topItems.filter((item) => exportKinds.has(item.kind));
|
|
536
|
-
|
|
537
|
-
const exports: Array<{
|
|
538
|
-
symbol: string;
|
|
539
|
-
kind: string;
|
|
540
|
-
line: number;
|
|
541
|
-
type: string | null;
|
|
542
|
-
}> = [];
|
|
543
|
-
|
|
544
|
-
for (const item of candidates) {
|
|
545
|
-
if (!item.spans[0]) continue;
|
|
546
|
-
const span = item.spans[0];
|
|
547
|
-
|
|
548
|
-
// Get type info for this symbol
|
|
549
|
-
const info = await client.quickinfo(file, span.start.line, span.start.offset);
|
|
550
|
-
|
|
551
|
-
exports.push({
|
|
552
|
-
symbol: item.text,
|
|
553
|
-
kind: item.kind,
|
|
554
|
-
line: span.start.line,
|
|
555
|
-
type: info?.displayString ?? null,
|
|
556
|
-
});
|
|
557
|
-
}
|
|
856
|
+
const exports = await getModuleExports(file);
|
|
857
|
+
const localCount = exports.filter((item) => item.source === "local").length;
|
|
858
|
+
const reExportCount = exports.length - localCount;
|
|
859
|
+
const typeOnlyCount = exports.filter((item) => item.isTypeOnly).length;
|
|
860
|
+
const valueCount = exports.length - typeOnlyCount;
|
|
861
|
+
const namespaceExportCount = exports.filter((item) => item.isNamespace).length;
|
|
862
|
+
const hasLocalRuntimeExports = exports.some(
|
|
863
|
+
(item) => item.source === "local" && !item.isTypeOnly
|
|
864
|
+
);
|
|
865
|
+
const isPrimarilyBarrel = exports.length > 0 && localCount < reExportCount;
|
|
558
866
|
|
|
559
867
|
return {
|
|
560
868
|
content: [
|
|
561
869
|
{
|
|
562
870
|
type: "text" as const,
|
|
563
|
-
text: JSON.stringify({
|
|
871
|
+
text: JSON.stringify({
|
|
872
|
+
file,
|
|
873
|
+
exports,
|
|
874
|
+
count: exports.length,
|
|
875
|
+
localCount,
|
|
876
|
+
reExportCount,
|
|
877
|
+
typeOnlyCount,
|
|
878
|
+
valueCount,
|
|
879
|
+
namespaceExportCount,
|
|
880
|
+
hasLocalRuntimeExports,
|
|
881
|
+
isPrimarilyBarrel,
|
|
882
|
+
}),
|
|
564
883
|
},
|
|
565
884
|
],
|
|
566
885
|
};
|
|
@@ -571,7 +890,7 @@ mcpServer.tool(
|
|
|
571
890
|
|
|
572
891
|
/** Convert an absolute path to project-relative */
|
|
573
892
|
function relPath(absPath: string): string {
|
|
574
|
-
return path.relative(
|
|
893
|
+
return path.relative(normalizedProjectRoot, normalizeExistingPath(absPath));
|
|
575
894
|
}
|
|
576
895
|
|
|
577
896
|
/** Convert a relative or absolute path to absolute */
|
|
@@ -812,6 +1131,7 @@ async function main() {
|
|
|
812
1131
|
]);
|
|
813
1132
|
|
|
814
1133
|
moduleGraph = graphResult.graph;
|
|
1134
|
+
moduleResolver = graphResult.resolver;
|
|
815
1135
|
startWatcher(projectRoot, moduleGraph, graphResult.resolver);
|
|
816
1136
|
|
|
817
1137
|
const transport = new StdioServerTransport();
|