typegraph-mcp 0.9.47 → 0.9.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -3
- package/benchmark.ts +12 -3
- package/biome-config.ts +76 -0
- package/check.ts +67 -83
- package/cli.ts +43 -62
- package/commands/check.md +1 -1
- package/dist/benchmark.js +56 -19
- package/dist/check.js +213 -153
- package/dist/cli.js +1310 -1219
- package/dist/module-graph.js +22 -9
- package/dist/server.js +82 -38
- package/dist/smoke-test.js +66 -25
- package/dist/tsserver-client.js +35 -10
- package/module-graph.ts +29 -9
- package/package.json +5 -4
- package/scripts/self-install.ts +78 -0
- package/server.ts +26 -19
- package/smoke-test.ts +15 -6
- package/tsserver-client.ts +49 -11
- package/engine-sync-test.ts +0 -262
- package/export-surface-test.ts +0 -202
- package/install-oxlint-test.ts +0 -116
- package/smoke-test-selection-test.ts +0 -67
package/dist/check.js
CHANGED
|
@@ -21,14 +21,25 @@ __export(module_graph_exports, {
|
|
|
21
21
|
});
|
|
22
22
|
import { parseSync } from "oxc-parser";
|
|
23
23
|
import { ResolverFactory } from "oxc-resolver";
|
|
24
|
-
import * as
|
|
25
|
-
import * as
|
|
26
|
-
function
|
|
24
|
+
import * as fs2 from "fs";
|
|
25
|
+
import * as path3 from "path";
|
|
26
|
+
function normalizeExcludedPaths(rootDir, excludedPaths) {
|
|
27
|
+
return excludedPaths.map(
|
|
28
|
+
(excludedPath) => path3.resolve(rootDir, excludedPath)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
function isExcluded(filePath, excludedPaths) {
|
|
32
|
+
return excludedPaths.some(
|
|
33
|
+
(excludedPath) => filePath === excludedPath || filePath.startsWith(excludedPath + path3.sep)
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
function discoverFiles(rootDir, excludedPaths = []) {
|
|
27
37
|
const files = [];
|
|
38
|
+
const normalizedExclusions = normalizeExcludedPaths(rootDir, excludedPaths);
|
|
28
39
|
function walk(dir) {
|
|
29
40
|
let entries;
|
|
30
41
|
try {
|
|
31
|
-
entries =
|
|
42
|
+
entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
32
43
|
} catch {
|
|
33
44
|
return;
|
|
34
45
|
}
|
|
@@ -36,14 +47,16 @@ function discoverFiles(rootDir) {
|
|
|
36
47
|
if (entry.isDirectory()) {
|
|
37
48
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
38
49
|
if (entry.name.startsWith(".") && dir !== rootDir) continue;
|
|
39
|
-
|
|
50
|
+
const childDir = path3.join(dir, entry.name);
|
|
51
|
+
if (isExcluded(childDir, normalizedExclusions)) continue;
|
|
52
|
+
walk(childDir);
|
|
40
53
|
} else if (entry.isFile()) {
|
|
41
54
|
const name = entry.name;
|
|
42
55
|
if (SKIP_FILES.has(name)) continue;
|
|
43
56
|
if (name.endsWith(".d.ts") || name.endsWith(".d.mts") || name.endsWith(".d.cts")) continue;
|
|
44
|
-
const ext =
|
|
57
|
+
const ext = path3.extname(name);
|
|
45
58
|
if (TS_EXTENSIONS.has(ext)) {
|
|
46
|
-
files.push(
|
|
59
|
+
files.push(path3.join(dir, name));
|
|
47
60
|
}
|
|
48
61
|
}
|
|
49
62
|
}
|
|
@@ -98,25 +111,25 @@ function parseFileImports(filePath, source) {
|
|
|
98
111
|
}
|
|
99
112
|
function distToSource(resolvedPath, projectRoot) {
|
|
100
113
|
if (!resolvedPath.startsWith(projectRoot)) return resolvedPath;
|
|
101
|
-
const rel =
|
|
102
|
-
const distIdx = rel.indexOf("dist" +
|
|
114
|
+
const rel = path3.relative(projectRoot, resolvedPath);
|
|
115
|
+
const distIdx = rel.indexOf("dist" + path3.sep);
|
|
103
116
|
if (distIdx === -1) return resolvedPath;
|
|
104
117
|
const prefix = rel.slice(0, distIdx);
|
|
105
118
|
const afterDist = rel.slice(distIdx + 5);
|
|
106
119
|
const withoutExt = afterDist.replace(/\.(m?j|c)s$/, "");
|
|
107
120
|
for (const ext of SOURCE_EXTS) {
|
|
108
|
-
const candidate =
|
|
109
|
-
if (
|
|
121
|
+
const candidate = path3.resolve(projectRoot, prefix, "src", withoutExt + ext);
|
|
122
|
+
if (fs2.existsSync(candidate)) return candidate;
|
|
110
123
|
}
|
|
111
124
|
for (const ext of SOURCE_EXTS) {
|
|
112
|
-
const candidate =
|
|
113
|
-
if (
|
|
125
|
+
const candidate = path3.resolve(projectRoot, prefix, withoutExt + ext);
|
|
126
|
+
if (fs2.existsSync(candidate)) return candidate;
|
|
114
127
|
}
|
|
115
128
|
if (withoutExt.endsWith("/index")) {
|
|
116
129
|
const dirPath = withoutExt.slice(0, -6);
|
|
117
130
|
for (const ext of SOURCE_EXTS) {
|
|
118
|
-
const candidate =
|
|
119
|
-
if (
|
|
131
|
+
const candidate = path3.resolve(projectRoot, prefix, "src", dirPath + ext);
|
|
132
|
+
if (fs2.existsSync(candidate)) return candidate;
|
|
120
133
|
}
|
|
121
134
|
}
|
|
122
135
|
return resolvedPath;
|
|
@@ -126,9 +139,9 @@ function resolveProjectImport(resolver, fromDir, specifier, projectRoot) {
|
|
|
126
139
|
const result = resolver.sync(fromDir, specifier);
|
|
127
140
|
if (result.path && !result.path.includes("node_modules")) {
|
|
128
141
|
const mapped = distToSource(result.path, projectRoot);
|
|
129
|
-
const ext =
|
|
142
|
+
const ext = path3.extname(mapped);
|
|
130
143
|
if (!TS_EXTENSIONS.has(ext)) return null;
|
|
131
|
-
if (SKIP_FILES.has(
|
|
144
|
+
if (SKIP_FILES.has(path3.basename(mapped))) return null;
|
|
132
145
|
return mapped;
|
|
133
146
|
}
|
|
134
147
|
} catch {
|
|
@@ -136,11 +149,9 @@ function resolveProjectImport(resolver, fromDir, specifier, projectRoot) {
|
|
|
136
149
|
return null;
|
|
137
150
|
}
|
|
138
151
|
function createResolver(projectRoot, tsconfigPath) {
|
|
152
|
+
const configFile = path3.resolve(projectRoot, tsconfigPath);
|
|
139
153
|
return new ResolverFactory({
|
|
140
|
-
tsconfig: {
|
|
141
|
-
configFile: path2.resolve(projectRoot, tsconfigPath),
|
|
142
|
-
references: "auto"
|
|
143
|
-
},
|
|
154
|
+
...fs2.existsSync(configFile) ? { tsconfig: { configFile, references: "auto" } } : {},
|
|
144
155
|
extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
|
|
145
156
|
extensionAlias: {
|
|
146
157
|
".js": [".ts", ".tsx", ".js"],
|
|
@@ -158,7 +169,7 @@ function buildForwardEdges(files, resolver, projectRoot) {
|
|
|
158
169
|
for (const filePath of files) {
|
|
159
170
|
let source;
|
|
160
171
|
try {
|
|
161
|
-
source =
|
|
172
|
+
source = fs2.readFileSync(filePath, "utf-8");
|
|
162
173
|
} catch {
|
|
163
174
|
continue;
|
|
164
175
|
}
|
|
@@ -170,7 +181,7 @@ function buildForwardEdges(files, resolver, projectRoot) {
|
|
|
170
181
|
continue;
|
|
171
182
|
}
|
|
172
183
|
const edges = [];
|
|
173
|
-
const fromDir =
|
|
184
|
+
const fromDir = path3.dirname(filePath);
|
|
174
185
|
for (const raw of rawImports) {
|
|
175
186
|
const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot);
|
|
176
187
|
if (target) {
|
|
@@ -206,10 +217,10 @@ function buildReverseMap(forward) {
|
|
|
206
217
|
}
|
|
207
218
|
return reverse;
|
|
208
219
|
}
|
|
209
|
-
async function buildGraph(projectRoot, tsconfigPath) {
|
|
220
|
+
async function buildGraph(projectRoot, tsconfigPath, excludedPaths = []) {
|
|
210
221
|
const startTime = performance.now();
|
|
211
222
|
const resolver = createResolver(projectRoot, tsconfigPath);
|
|
212
|
-
const fileList = discoverFiles(projectRoot);
|
|
223
|
+
const fileList = discoverFiles(projectRoot, excludedPaths);
|
|
213
224
|
log(`Discovered ${fileList.length} source files`);
|
|
214
225
|
const { forward, parseFailures } = buildForwardEdges(fileList, resolver, projectRoot);
|
|
215
226
|
const reverse = buildReverseMap(forward);
|
|
@@ -237,7 +248,7 @@ function updateFile(graph, filePath, resolver, projectRoot) {
|
|
|
237
248
|
}
|
|
238
249
|
let source;
|
|
239
250
|
try {
|
|
240
|
-
source =
|
|
251
|
+
source = fs2.readFileSync(filePath, "utf-8");
|
|
241
252
|
} catch {
|
|
242
253
|
removeFile(graph, filePath);
|
|
243
254
|
return;
|
|
@@ -250,7 +261,7 @@ function updateFile(graph, filePath, resolver, projectRoot) {
|
|
|
250
261
|
graph.forward.set(filePath, []);
|
|
251
262
|
return;
|
|
252
263
|
}
|
|
253
|
-
const fromDir =
|
|
264
|
+
const fromDir = path3.dirname(filePath);
|
|
254
265
|
const newEdges = [];
|
|
255
266
|
for (const raw of rawImports) {
|
|
256
267
|
const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot);
|
|
@@ -301,22 +312,24 @@ function removeFile(graph, filePath) {
|
|
|
301
312
|
graph.reverse.delete(filePath);
|
|
302
313
|
graph.files.delete(filePath);
|
|
303
314
|
}
|
|
304
|
-
function startWatcher(projectRoot, graph, resolver, hooks) {
|
|
315
|
+
function startWatcher(projectRoot, graph, resolver, hooks, excludedPaths = []) {
|
|
305
316
|
try {
|
|
306
|
-
const
|
|
317
|
+
const normalizedExclusions = normalizeExcludedPaths(projectRoot, excludedPaths);
|
|
318
|
+
const watcher = fs2.watch(
|
|
307
319
|
projectRoot,
|
|
308
320
|
{ recursive: true },
|
|
309
321
|
(_eventType, filename) => {
|
|
310
322
|
if (!filename) return;
|
|
311
|
-
const ext =
|
|
323
|
+
const ext = path3.extname(filename);
|
|
312
324
|
if (!TS_EXTENSIONS.has(ext)) return;
|
|
313
|
-
const parts = filename.split(
|
|
325
|
+
const parts = filename.split(path3.sep);
|
|
314
326
|
if (parts.some((p) => SKIP_DIRS.has(p))) return;
|
|
315
|
-
if (SKIP_FILES.has(
|
|
327
|
+
if (SKIP_FILES.has(path3.basename(filename))) return;
|
|
316
328
|
if (filename.endsWith(".d.ts") || filename.endsWith(".d.mts") || filename.endsWith(".d.cts"))
|
|
317
329
|
return;
|
|
318
|
-
const absPath =
|
|
319
|
-
if (
|
|
330
|
+
const absPath = path3.resolve(projectRoot, filename);
|
|
331
|
+
if (isExcluded(absPath, normalizedExclusions)) return;
|
|
332
|
+
if (fs2.existsSync(absPath)) {
|
|
320
333
|
updateFile(graph, absPath, resolver, projectRoot);
|
|
321
334
|
void hooks?.onFileUpdated?.(absPath);
|
|
322
335
|
} else {
|
|
@@ -355,10 +368,10 @@ var init_module_graph = __esm({
|
|
|
355
368
|
});
|
|
356
369
|
|
|
357
370
|
// check.ts
|
|
358
|
-
import * as
|
|
359
|
-
import * as
|
|
360
|
-
import { createRequire } from "module";
|
|
361
|
-
import { spawn, spawnSync } from "child_process";
|
|
371
|
+
import * as fs3 from "fs";
|
|
372
|
+
import * as path4 from "path";
|
|
373
|
+
import { createRequire as createRequire2 } from "module";
|
|
374
|
+
import { spawn as spawn2, spawnSync } from "child_process";
|
|
362
375
|
|
|
363
376
|
// config.ts
|
|
364
377
|
import * as path from "path";
|
|
@@ -371,18 +384,84 @@ function resolveConfig(toolDir) {
|
|
|
371
384
|
return { projectRoot, tsconfigPath, toolDir, toolIsEmbedded, toolRelPath };
|
|
372
385
|
}
|
|
373
386
|
|
|
387
|
+
// tsserver-client.ts
|
|
388
|
+
import { spawn } from "child_process";
|
|
389
|
+
import * as path2 from "path";
|
|
390
|
+
import * as fs from "fs";
|
|
391
|
+
import { createRequire } from "module";
|
|
392
|
+
function readPackageVersion(packagePath) {
|
|
393
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
|
|
394
|
+
return pkg.version ?? "unknown";
|
|
395
|
+
}
|
|
396
|
+
function resolveTsServer(projectRoot) {
|
|
397
|
+
const projectRequire = createRequire(path2.resolve(projectRoot, "package.json"));
|
|
398
|
+
let projectVersion;
|
|
399
|
+
try {
|
|
400
|
+
const packagePath2 = projectRequire.resolve("typescript/package.json");
|
|
401
|
+
projectVersion = readPackageVersion(packagePath2);
|
|
402
|
+
const serverPath2 = projectRequire.resolve("typescript/lib/tsserver.js");
|
|
403
|
+
return { path: serverPath2, version: projectVersion, source: "project" };
|
|
404
|
+
} catch {
|
|
405
|
+
}
|
|
406
|
+
const toolRequire = createRequire(import.meta.url);
|
|
407
|
+
const packagePath = toolRequire.resolve("typescript/package.json");
|
|
408
|
+
const serverPath = toolRequire.resolve("typescript/lib/tsserver.js");
|
|
409
|
+
return {
|
|
410
|
+
path: serverPath,
|
|
411
|
+
version: readPackageVersion(packagePath),
|
|
412
|
+
source: "typegraph",
|
|
413
|
+
projectVersion
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// biome-config.ts
|
|
418
|
+
var BIOME_CONFIG_NAMES = [
|
|
419
|
+
"biome.json",
|
|
420
|
+
"biome.jsonc",
|
|
421
|
+
".biome.json",
|
|
422
|
+
".biome.jsonc"
|
|
423
|
+
];
|
|
424
|
+
function filesObjectMatch(raw) {
|
|
425
|
+
return raw.match(/(["']files["']\s*:\s*\{)([\s\S]*?)(\n?\s*\})/);
|
|
426
|
+
}
|
|
427
|
+
function filesIncludes(raw) {
|
|
428
|
+
const filesObject = filesObjectMatch(raw);
|
|
429
|
+
if (!filesObject) return null;
|
|
430
|
+
const includes = filesObject[2].match(/["']includes["']\s*:\s*\[([\s\S]*?)\]/);
|
|
431
|
+
if (!includes) return null;
|
|
432
|
+
return [...includes[1].matchAll(/["']([^"']+)["']/g)].map((match) => match[1]);
|
|
433
|
+
}
|
|
434
|
+
function biomeScopeExcludes(raw, parentDir) {
|
|
435
|
+
const escaped = parentDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
436
|
+
const explicitIgnore = new RegExp(
|
|
437
|
+
`["']!{1,2}(?:\\*\\*/)?${escaped}(?:/\\*\\*)?["']`
|
|
438
|
+
);
|
|
439
|
+
if (explicitIgnore.test(raw)) return true;
|
|
440
|
+
const includes = filesIncludes(raw);
|
|
441
|
+
if (!includes) return false;
|
|
442
|
+
const positivePatterns = includes.filter((pattern) => !pattern.startsWith("!"));
|
|
443
|
+
return !positivePatterns.some((pattern) => {
|
|
444
|
+
const normalized = pattern.replace(/^\.\//, "");
|
|
445
|
+
return normalized === "*" || normalized === "**" || normalized.startsWith("**/") || normalized.startsWith("*/") || normalized === parentDir || normalized.startsWith(`${parentDir}/`);
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
374
449
|
// check.ts
|
|
375
|
-
function findFirstTsFile(dir) {
|
|
450
|
+
function findFirstTsFile(dir, excludedPaths = []) {
|
|
376
451
|
const skipDirs = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", ".wrangler", "coverage"]);
|
|
377
452
|
try {
|
|
378
|
-
for (const entry of
|
|
453
|
+
for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
|
|
379
454
|
if (entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) {
|
|
380
|
-
return
|
|
455
|
+
return path4.join(dir, entry.name);
|
|
381
456
|
}
|
|
382
457
|
}
|
|
383
|
-
for (const entry of
|
|
458
|
+
for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
|
|
384
459
|
if (entry.isDirectory() && !skipDirs.has(entry.name) && !entry.name.startsWith(".")) {
|
|
385
|
-
const
|
|
460
|
+
const childDir = path4.join(dir, entry.name);
|
|
461
|
+
if (excludedPaths.some(
|
|
462
|
+
(excludedPath) => childDir === excludedPath || childDir.startsWith(excludedPath + path4.sep)
|
|
463
|
+
)) continue;
|
|
464
|
+
const found = findFirstTsFile(childDir, excludedPaths);
|
|
386
465
|
if (found) return found;
|
|
387
466
|
}
|
|
388
467
|
}
|
|
@@ -390,23 +469,15 @@ function findFirstTsFile(dir) {
|
|
|
390
469
|
}
|
|
391
470
|
return null;
|
|
392
471
|
}
|
|
393
|
-
function testTsserver(projectRoot) {
|
|
394
|
-
return new Promise((
|
|
395
|
-
|
|
396
|
-
try {
|
|
397
|
-
const require2 = createRequire(path3.resolve(projectRoot, "package.json"));
|
|
398
|
-
tsserverPath = require2.resolve("typescript/lib/tsserver.js");
|
|
399
|
-
} catch {
|
|
400
|
-
resolve4(false);
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
const child = spawn("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
|
|
472
|
+
function testTsserver(projectRoot, resolution) {
|
|
473
|
+
return new Promise((resolve5) => {
|
|
474
|
+
const child = spawn2("node", [resolution.path, "--disableAutomaticTypingAcquisition"], {
|
|
404
475
|
cwd: projectRoot,
|
|
405
476
|
stdio: ["pipe", "pipe", "pipe"]
|
|
406
477
|
});
|
|
407
478
|
const timeout = setTimeout(() => {
|
|
408
479
|
child.kill();
|
|
409
|
-
|
|
480
|
+
resolve5(false);
|
|
410
481
|
}, 1e4);
|
|
411
482
|
let buffer = "";
|
|
412
483
|
child.stdout.on("data", (chunk) => {
|
|
@@ -414,12 +485,12 @@ function testTsserver(projectRoot) {
|
|
|
414
485
|
if (buffer.includes('"success":true')) {
|
|
415
486
|
clearTimeout(timeout);
|
|
416
487
|
child.kill();
|
|
417
|
-
|
|
488
|
+
resolve5(true);
|
|
418
489
|
}
|
|
419
490
|
});
|
|
420
491
|
child.on("error", () => {
|
|
421
492
|
clearTimeout(timeout);
|
|
422
|
-
|
|
493
|
+
resolve5(false);
|
|
423
494
|
});
|
|
424
495
|
child.on("exit", () => {
|
|
425
496
|
clearTimeout(timeout);
|
|
@@ -436,9 +507,9 @@ function testTsserver(projectRoot) {
|
|
|
436
507
|
});
|
|
437
508
|
}
|
|
438
509
|
function readProjectCodexConfig(projectRoot) {
|
|
439
|
-
const configPath =
|
|
440
|
-
if (!
|
|
441
|
-
return
|
|
510
|
+
const configPath = path4.resolve(projectRoot, ".codex/config.toml");
|
|
511
|
+
if (!fs3.existsSync(configPath)) return null;
|
|
512
|
+
return fs3.readFileSync(configPath, "utf-8");
|
|
442
513
|
}
|
|
443
514
|
function hasCodexTypegraphRegistration(content) {
|
|
444
515
|
return /\[mcp_servers\.typegraph\]/.test(content);
|
|
@@ -452,17 +523,17 @@ function hasCompleteCodexTypegraphRegistration(content) {
|
|
|
452
523
|
function hasTrustedCodexProject(projectRoot) {
|
|
453
524
|
const home = process.env.HOME;
|
|
454
525
|
if (!home) return null;
|
|
455
|
-
const globalConfigPath =
|
|
456
|
-
if (!
|
|
457
|
-
const lines =
|
|
526
|
+
const globalConfigPath = path4.join(home, ".codex/config.toml");
|
|
527
|
+
if (!fs3.existsSync(globalConfigPath)) return null;
|
|
528
|
+
const lines = fs3.readFileSync(globalConfigPath, "utf-8").split(/\r?\n/);
|
|
458
529
|
let currentProject = null;
|
|
459
530
|
let currentTrusted = false;
|
|
460
|
-
const matchesTrustedProject = () => currentProject !== null && currentTrusted && (projectRoot === currentProject || projectRoot.startsWith(currentProject +
|
|
531
|
+
const matchesTrustedProject = () => currentProject !== null && currentTrusted && (projectRoot === currentProject || projectRoot.startsWith(currentProject + path4.sep));
|
|
461
532
|
for (const line of lines) {
|
|
462
533
|
const sectionMatch = line.match(/^\[projects\."([^"]+)"\]\s*$/);
|
|
463
534
|
if (sectionMatch) {
|
|
464
535
|
if (matchesTrustedProject()) return true;
|
|
465
|
-
currentProject =
|
|
536
|
+
currentProject = path4.resolve(sectionMatch[1]);
|
|
466
537
|
currentTrusted = false;
|
|
467
538
|
continue;
|
|
468
539
|
}
|
|
@@ -492,9 +563,9 @@ function hasCompleteJsonTypegraphRegistration(config, serverName, projectRoot) {
|
|
|
492
563
|
return typeof command === "string" && command.length > 0 && Array.isArray(args) && serializedArgs.includes("server.ts") && serializedArgs.includes("tsx") && typeof env === "object" && env !== null && env["TYPEGRAPH_PROJECT_ROOT"] === projectRoot && typeof env["TYPEGRAPH_TSCONFIG"] === "string";
|
|
493
564
|
}
|
|
494
565
|
function readJsonConfig(configPath) {
|
|
495
|
-
if (!
|
|
566
|
+
if (!fs3.existsSync(configPath)) return null;
|
|
496
567
|
try {
|
|
497
|
-
return JSON.parse(
|
|
568
|
+
return JSON.parse(fs3.readFileSync(configPath, "utf-8"));
|
|
498
569
|
} catch {
|
|
499
570
|
return null;
|
|
500
571
|
}
|
|
@@ -502,8 +573,8 @@ function readJsonConfig(configPath) {
|
|
|
502
573
|
function getAntigravityMcpConfigPaths() {
|
|
503
574
|
const home = process.env.HOME || "";
|
|
504
575
|
return [
|
|
505
|
-
|
|
506
|
-
|
|
576
|
+
path4.join(home, ".gemini/antigravity/mcp_config.json"),
|
|
577
|
+
path4.join(home, ".gemini/antigravity-cli/plugins/typegraph-mcp/mcp_config.json")
|
|
507
578
|
];
|
|
508
579
|
}
|
|
509
580
|
function findAntigravityRegistration(projectRoot) {
|
|
@@ -516,37 +587,6 @@ function findAntigravityRegistration(projectRoot) {
|
|
|
516
587
|
}
|
|
517
588
|
return null;
|
|
518
589
|
}
|
|
519
|
-
function readProjectPackageJson(projectRoot) {
|
|
520
|
-
const packageJsonPath = path3.resolve(projectRoot, "package.json");
|
|
521
|
-
if (!fs2.existsSync(packageJsonPath)) return null;
|
|
522
|
-
try {
|
|
523
|
-
return JSON.parse(fs2.readFileSync(packageJsonPath, "utf-8"));
|
|
524
|
-
} catch {
|
|
525
|
-
return null;
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
function getProjectInstallCommand(projectRoot, packageJson) {
|
|
529
|
-
const packageManager = typeof packageJson?.["packageManager"] === "string" ? packageJson["packageManager"] : "";
|
|
530
|
-
if (packageManager.startsWith("pnpm@") || fs2.existsSync(path3.join(projectRoot, "pnpm-lock.yaml"))) {
|
|
531
|
-
return "pnpm install";
|
|
532
|
-
}
|
|
533
|
-
if (packageManager.startsWith("yarn@") || fs2.existsSync(path3.join(projectRoot, "yarn.lock"))) {
|
|
534
|
-
return "yarn install";
|
|
535
|
-
}
|
|
536
|
-
return "npm install";
|
|
537
|
-
}
|
|
538
|
-
function hasDeclaredDependency(packageJson, packageName) {
|
|
539
|
-
const depKeys = [
|
|
540
|
-
"dependencies",
|
|
541
|
-
"devDependencies",
|
|
542
|
-
"peerDependencies",
|
|
543
|
-
"optionalDependencies"
|
|
544
|
-
];
|
|
545
|
-
return depKeys.some((key) => {
|
|
546
|
-
const deps = packageJson?.[key];
|
|
547
|
-
return typeof deps === "object" && deps !== null && packageName in deps;
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
590
|
var ESLINT_CONFIG_NAMES = [
|
|
551
591
|
"eslint.config.mjs",
|
|
552
592
|
"eslint.config.js",
|
|
@@ -563,26 +603,32 @@ var OXLINT_CONFIG_NAMES = [
|
|
|
563
603
|
function findLintConfigs(projectRoot) {
|
|
564
604
|
const configs = [];
|
|
565
605
|
for (const fileName of ESLINT_CONFIG_NAMES) {
|
|
566
|
-
const fullPath =
|
|
567
|
-
if (
|
|
606
|
+
const fullPath = path4.resolve(projectRoot, fileName);
|
|
607
|
+
if (fs3.existsSync(fullPath)) {
|
|
568
608
|
configs.push({ tool: "ESLint", fileName, fullPath, propertyName: "ignores" });
|
|
569
609
|
}
|
|
570
610
|
}
|
|
571
611
|
for (const fileName of OXLINT_CONFIG_NAMES) {
|
|
572
|
-
const fullPath =
|
|
573
|
-
if (
|
|
612
|
+
const fullPath = path4.resolve(projectRoot, fileName);
|
|
613
|
+
if (fs3.existsSync(fullPath)) {
|
|
574
614
|
configs.push({ tool: "Oxlint", fileName, fullPath, propertyName: "ignorePatterns" });
|
|
575
615
|
}
|
|
576
616
|
}
|
|
617
|
+
for (const fileName of BIOME_CONFIG_NAMES) {
|
|
618
|
+
const fullPath = path4.resolve(projectRoot, fileName);
|
|
619
|
+
if (fs3.existsSync(fullPath)) {
|
|
620
|
+
configs.push({ tool: "Biome", fileName, fullPath, propertyName: "files.includes" });
|
|
621
|
+
break;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
577
624
|
return configs;
|
|
578
625
|
}
|
|
579
626
|
async function main(configOverride) {
|
|
580
627
|
const { projectRoot, tsconfigPath, toolDir, toolIsEmbedded, toolRelPath } = configOverride ?? resolveConfig(import.meta.dirname);
|
|
628
|
+
const excludedPaths = toolIsEmbedded ? [toolDir] : [];
|
|
581
629
|
let passed = 0;
|
|
582
630
|
let failed = 0;
|
|
583
631
|
let warned = 0;
|
|
584
|
-
const projectPackageJson = readProjectPackageJson(projectRoot);
|
|
585
|
-
const installCommand = getProjectInstallCommand(projectRoot, projectPackageJson);
|
|
586
632
|
function pass(msg) {
|
|
587
633
|
console.log(` \u2713 ${msg}`);
|
|
588
634
|
passed++;
|
|
@@ -612,36 +658,39 @@ async function main(configOverride) {
|
|
|
612
658
|
} else {
|
|
613
659
|
fail(`Node.js ${nodeVersion} is too old`, "Upgrade Node.js to >= 22");
|
|
614
660
|
}
|
|
615
|
-
const tsxInRoot =
|
|
616
|
-
const tsxInTool =
|
|
661
|
+
const tsxInRoot = fs3.existsSync(path4.join(projectRoot, "node_modules/.bin/tsx"));
|
|
662
|
+
const tsxInTool = fs3.existsSync(path4.join(toolDir, "node_modules/.bin/tsx"));
|
|
617
663
|
if (tsxInRoot || tsxInTool) {
|
|
618
664
|
pass(`tsx available (in ${tsxInRoot ? "project" : "tool"} node_modules)`);
|
|
619
665
|
} else {
|
|
620
666
|
pass("tsx available (via npx/global)");
|
|
621
667
|
}
|
|
622
|
-
let
|
|
668
|
+
let tsResolution = null;
|
|
623
669
|
try {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
670
|
+
tsResolution = resolveTsServer(projectRoot);
|
|
671
|
+
if (tsResolution.source === "project") {
|
|
672
|
+
pass(`TypeScript found (v${tsResolution.version})`);
|
|
673
|
+
} else if (tsResolution.projectVersion) {
|
|
674
|
+
pass(
|
|
675
|
+
`Compatible tsserver found (TypeScript v${tsResolution.version} tool runtime; project uses v${tsResolution.projectVersion})`
|
|
676
|
+
);
|
|
677
|
+
} else {
|
|
678
|
+
pass(`TypeScript found in typegraph-mcp (v${tsResolution.version})`);
|
|
679
|
+
}
|
|
630
680
|
} catch {
|
|
631
|
-
const hasDeclaredTs = hasDeclaredDependency(projectPackageJson, "typescript");
|
|
632
681
|
fail(
|
|
633
|
-
|
|
634
|
-
|
|
682
|
+
"Compatible tsserver runtime not found",
|
|
683
|
+
`Run \`cd ${toolRelPath} && npm install --include=optional\``
|
|
635
684
|
);
|
|
636
685
|
}
|
|
637
|
-
const tsconfigAbs =
|
|
638
|
-
if (
|
|
686
|
+
const tsconfigAbs = path4.resolve(projectRoot, tsconfigPath);
|
|
687
|
+
if (fs3.existsSync(tsconfigAbs)) {
|
|
639
688
|
pass(`tsconfig.json exists at ${tsconfigPath}`);
|
|
640
689
|
} else {
|
|
641
|
-
|
|
690
|
+
pass("No tsconfig.json; semantic tools will use an inferred TypeScript project");
|
|
642
691
|
}
|
|
643
|
-
const pluginMcpPath =
|
|
644
|
-
const hasPluginMcp =
|
|
692
|
+
const pluginMcpPath = path4.join(toolDir, ".mcp.json");
|
|
693
|
+
const hasPluginMcp = fs3.existsSync(pluginMcpPath) && fs3.existsSync(path4.join(toolDir, ".claude-plugin/plugin.json"));
|
|
645
694
|
const projectCodexConfig = readProjectCodexConfig(projectRoot);
|
|
646
695
|
const hasProjectCodexRegistration = projectCodexConfig !== null && hasCompleteCodexTypegraphRegistration(projectCodexConfig);
|
|
647
696
|
const codexGet = spawnSync("codex", ["mcp", "get", "typegraph"], {
|
|
@@ -655,7 +704,7 @@ async function main(configOverride) {
|
|
|
655
704
|
} else if (hasPluginMcp) {
|
|
656
705
|
pass("MCP registered via plugin (.mcp.json + .claude-plugin/ present)");
|
|
657
706
|
} else if (projectCodexConfig !== null) {
|
|
658
|
-
const codexConfigPath =
|
|
707
|
+
const codexConfigPath = path4.resolve(projectRoot, ".codex/config.toml");
|
|
659
708
|
const hasSection = hasCodexTypegraphRegistration(projectCodexConfig);
|
|
660
709
|
const hasCommand = /command\s*=\s*"[^"]+"/.test(projectCodexConfig);
|
|
661
710
|
const hasArgs = /args\s*=\s*\[[\s\S]*\]/.test(projectCodexConfig);
|
|
@@ -689,11 +738,11 @@ async function main(configOverride) {
|
|
|
689
738
|
} else if (antigravityRegistrationPath !== null) {
|
|
690
739
|
pass(`MCP registered in Antigravity config (${antigravityRegistrationPath})`);
|
|
691
740
|
} else {
|
|
692
|
-
const codexConfigPath =
|
|
693
|
-
const mcpJsonPath =
|
|
694
|
-
if (
|
|
741
|
+
const codexConfigPath = path4.resolve(projectRoot, ".codex/config.toml");
|
|
742
|
+
const mcpJsonPath = path4.resolve(projectRoot, ".claude/mcp.json");
|
|
743
|
+
if (fs3.existsSync(mcpJsonPath)) {
|
|
695
744
|
try {
|
|
696
|
-
const mcpJson = JSON.parse(
|
|
745
|
+
const mcpJson = JSON.parse(fs3.readFileSync(mcpJsonPath, "utf-8"));
|
|
697
746
|
const tsNav = mcpJson?.mcpServers?.["typegraph"];
|
|
698
747
|
if (tsNav) {
|
|
699
748
|
const hasCommand = tsNav.command === "npx";
|
|
@@ -712,7 +761,7 @@ async function main(configOverride) {
|
|
|
712
761
|
);
|
|
713
762
|
}
|
|
714
763
|
} else {
|
|
715
|
-
const serverPath = toolIsEmbedded ? `./${toolRelPath}/server.ts` :
|
|
764
|
+
const serverPath = toolIsEmbedded ? `./${toolRelPath}/server.ts` : path4.resolve(toolDir, "server.ts");
|
|
716
765
|
fail(
|
|
717
766
|
"MCP entry 'typegraph' not found in .claude/mcp.json",
|
|
718
767
|
`Add to .claude/mcp.json:
|
|
@@ -740,11 +789,17 @@ async function main(configOverride) {
|
|
|
740
789
|
);
|
|
741
790
|
}
|
|
742
791
|
}
|
|
743
|
-
const toolNodeModules =
|
|
744
|
-
if (
|
|
745
|
-
const requiredPkgs = [
|
|
792
|
+
const toolNodeModules = path4.join(toolDir, "node_modules");
|
|
793
|
+
if (fs3.existsSync(toolNodeModules)) {
|
|
794
|
+
const requiredPkgs = [
|
|
795
|
+
"@modelcontextprotocol/sdk",
|
|
796
|
+
"oxc-parser",
|
|
797
|
+
"oxc-resolver",
|
|
798
|
+
"typescript",
|
|
799
|
+
"zod"
|
|
800
|
+
];
|
|
746
801
|
const missing = requiredPkgs.filter(
|
|
747
|
-
(pkg) => !
|
|
802
|
+
(pkg) => !fs3.existsSync(path4.join(toolNodeModules, ...pkg.split("/")))
|
|
748
803
|
);
|
|
749
804
|
if (missing.length === 0) {
|
|
750
805
|
pass(`Dependencies installed (${requiredPkgs.length} packages)`);
|
|
@@ -755,7 +810,7 @@ async function main(configOverride) {
|
|
|
755
810
|
fail("typegraph-mcp dependencies not installed", `Run \`cd ${toolRelPath} && npm install\``);
|
|
756
811
|
}
|
|
757
812
|
try {
|
|
758
|
-
const oxcParserReq =
|
|
813
|
+
const oxcParserReq = createRequire2(path4.join(toolDir, "package.json"));
|
|
759
814
|
const { parseSync: parseSync2 } = await import(oxcParserReq.resolve("oxc-parser"));
|
|
760
815
|
const result = parseSync2("test.ts", 'import { x } from "./y";');
|
|
761
816
|
if (result.module?.staticImports?.length === 1) {
|
|
@@ -773,18 +828,18 @@ async function main(configOverride) {
|
|
|
773
828
|
);
|
|
774
829
|
}
|
|
775
830
|
try {
|
|
776
|
-
const oxcResolverReq =
|
|
831
|
+
const oxcResolverReq = createRequire2(path4.join(toolDir, "package.json"));
|
|
777
832
|
const { ResolverFactory: ResolverFactory2 } = await import(oxcResolverReq.resolve("oxc-resolver"));
|
|
778
833
|
const resolver = new ResolverFactory2({
|
|
779
|
-
tsconfig: { configFile: tsconfigAbs, references: "auto" },
|
|
834
|
+
...fs3.existsSync(tsconfigAbs) ? { tsconfig: { configFile: tsconfigAbs, references: "auto" } } : {},
|
|
780
835
|
extensions: [".ts", ".tsx", ".js"],
|
|
781
836
|
extensionAlias: { ".js": [".ts", ".tsx", ".js"] }
|
|
782
837
|
});
|
|
783
838
|
let resolveOk = false;
|
|
784
|
-
const testFile = findFirstTsFile(projectRoot);
|
|
839
|
+
const testFile = findFirstTsFile(projectRoot, excludedPaths);
|
|
785
840
|
if (testFile) {
|
|
786
|
-
const dir =
|
|
787
|
-
const base = "./" +
|
|
841
|
+
const dir = path4.dirname(testFile);
|
|
842
|
+
const base = "./" + path4.basename(testFile);
|
|
788
843
|
const result = resolver.sync(dir, base);
|
|
789
844
|
resolveOk = !!result.path;
|
|
790
845
|
}
|
|
@@ -802,9 +857,9 @@ async function main(configOverride) {
|
|
|
802
857
|
`Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && npm install\``
|
|
803
858
|
);
|
|
804
859
|
}
|
|
805
|
-
if (
|
|
860
|
+
if (tsResolution) {
|
|
806
861
|
try {
|
|
807
|
-
const ok = await testTsserver(projectRoot);
|
|
862
|
+
const ok = await testTsserver(projectRoot, tsResolution);
|
|
808
863
|
if (ok) {
|
|
809
864
|
pass("tsserver responds to configure");
|
|
810
865
|
} else {
|
|
@@ -825,12 +880,16 @@ async function main(configOverride) {
|
|
|
825
880
|
try {
|
|
826
881
|
let buildGraph2;
|
|
827
882
|
try {
|
|
828
|
-
({ buildGraph: buildGraph2 } = await import(
|
|
883
|
+
({ buildGraph: buildGraph2 } = await import(path4.resolve(toolDir, "module-graph.js")));
|
|
829
884
|
} catch {
|
|
830
885
|
({ buildGraph: buildGraph2 } = await Promise.resolve().then(() => (init_module_graph(), module_graph_exports)));
|
|
831
886
|
}
|
|
832
887
|
const start = performance.now();
|
|
833
|
-
const { graph } = await buildGraph2(
|
|
888
|
+
const { graph } = await buildGraph2(
|
|
889
|
+
projectRoot,
|
|
890
|
+
tsconfigPath,
|
|
891
|
+
excludedPaths
|
|
892
|
+
);
|
|
834
893
|
const elapsed = (performance.now() - start).toFixed(0);
|
|
835
894
|
const edgeCount = [...graph.forward.values()].reduce(
|
|
836
895
|
(s, e) => s + e.length,
|
|
@@ -858,35 +917,36 @@ async function main(configOverride) {
|
|
|
858
917
|
if (toolIsEmbedded) {
|
|
859
918
|
const lintConfigs = findLintConfigs(projectRoot);
|
|
860
919
|
if (lintConfigs.length > 0) {
|
|
861
|
-
const parentDir =
|
|
920
|
+
const parentDir = path4.basename(path4.dirname(toolDir));
|
|
862
921
|
const parentIgnorePattern = new RegExp(`["']${parentDir}\\/\\*\\*["']`);
|
|
863
922
|
for (const config of lintConfigs) {
|
|
864
|
-
const content =
|
|
865
|
-
const hasParentIgnore = parentIgnorePattern.test(content);
|
|
923
|
+
const content = fs3.readFileSync(config.fullPath, "utf-8");
|
|
924
|
+
const hasParentIgnore = config.tool === "Biome" ? biomeScopeExcludes(content, parentDir) : parentIgnorePattern.test(content);
|
|
866
925
|
if (hasParentIgnore) {
|
|
867
926
|
pass(`${config.tool} ignores ${parentDir}/ (${config.fileName})`);
|
|
868
927
|
} else {
|
|
928
|
+
const expectedPattern = config.tool === "Biome" ? `!!${parentDir}` : `${parentDir}/**`;
|
|
869
929
|
fail(
|
|
870
|
-
`${config.tool} missing ignore: "${
|
|
930
|
+
`${config.tool} missing ignore: "${expectedPattern}" (${config.fileName})`,
|
|
871
931
|
`Add to ${config.propertyName} in ${config.fileName}:
|
|
872
|
-
"${
|
|
932
|
+
"${expectedPattern}",`
|
|
873
933
|
);
|
|
874
934
|
}
|
|
875
935
|
}
|
|
876
936
|
} else {
|
|
877
|
-
skip("Lint config check (no ESLint or
|
|
937
|
+
skip("Lint config check (no ESLint, Oxlint, or Biome config found)");
|
|
878
938
|
}
|
|
879
939
|
} else {
|
|
880
940
|
skip("Lint config check (typegraph-mcp is external to project)");
|
|
881
941
|
}
|
|
882
|
-
const gitignorePath =
|
|
883
|
-
if (
|
|
884
|
-
const gitignoreContent =
|
|
942
|
+
const gitignorePath = path4.resolve(projectRoot, ".gitignore");
|
|
943
|
+
if (fs3.existsSync(gitignorePath)) {
|
|
944
|
+
const gitignoreContent = fs3.readFileSync(gitignorePath, "utf-8");
|
|
885
945
|
const lines = gitignoreContent.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
886
946
|
const ignoresClaude = lines.some(
|
|
887
947
|
(l) => l === ".claude/" || l === ".claude" || l === "/.claude"
|
|
888
948
|
);
|
|
889
|
-
const parentDir = toolIsEmbedded ?
|
|
949
|
+
const parentDir = toolIsEmbedded ? path4.basename(path4.dirname(toolDir)) : null;
|
|
890
950
|
const ignoresParent = parentDir && lines.some((l) => l === `${parentDir}/` || l === parentDir || l === `/${parentDir}`);
|
|
891
951
|
if (!ignoresParent && !ignoresClaude) {
|
|
892
952
|
pass(".gitignore does not exclude .claude/" + (parentDir ? ` or ${parentDir}/` : ""));
|