typegraph-mcp 0.9.0
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/.claude-plugin/plugin.json +17 -0
- package/.cursor-plugin/plugin.json +17 -0
- package/.mcp.json +10 -0
- package/LICENSE +21 -0
- package/README.md +451 -0
- package/benchmark.ts +735 -0
- package/check.ts +459 -0
- package/cli.ts +778 -0
- package/commands/check.md +23 -0
- package/commands/test.md +23 -0
- package/config.ts +50 -0
- package/gemini-extension.json +16 -0
- package/graph-queries.ts +462 -0
- package/hooks/hooks.json +15 -0
- package/module-graph.ts +507 -0
- package/package.json +39 -0
- package/scripts/ensure-deps.sh +34 -0
- package/server.ts +837 -0
- package/skills/code-exploration/SKILL.md +55 -0
- package/skills/dependency-audit/SKILL.md +50 -0
- package/skills/impact-analysis/SKILL.md +52 -0
- package/skills/refactor-safety/SKILL.md +50 -0
- package/skills/tool-selection/SKILL.md +79 -0
- package/smoke-test.ts +500 -0
- package/tsserver-client.ts +413 -0
package/check.ts
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* typegraph-mcp Health Check — Verifies all setup requirements are met.
|
|
4
|
+
*
|
|
5
|
+
* Run from project root:
|
|
6
|
+
* npx tsx plugins/typegraph-mcp/check.ts
|
|
7
|
+
*
|
|
8
|
+
* Or from plugins/typegraph-mcp/:
|
|
9
|
+
* pnpm check
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as fs from "node:fs";
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
import { createRequire } from "node:module";
|
|
15
|
+
import { spawn } from "node:child_process";
|
|
16
|
+
import { resolveConfig, type TypegraphConfig } from "./config.js";
|
|
17
|
+
|
|
18
|
+
// ─── Result Type ─────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
export interface CheckResult {
|
|
21
|
+
passed: number;
|
|
22
|
+
failed: number;
|
|
23
|
+
warned: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
/** Find first .ts file in the project (for resolver smoke test) */
|
|
29
|
+
function findFirstTsFile(dir: string): string | null {
|
|
30
|
+
const skipDirs = new Set(["node_modules", "dist", ".git", ".wrangler", "coverage"]);
|
|
31
|
+
try {
|
|
32
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
33
|
+
if (entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) {
|
|
34
|
+
return path.join(dir, entry.name);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
38
|
+
if (entry.isDirectory() && !skipDirs.has(entry.name) && !entry.name.startsWith(".")) {
|
|
39
|
+
const found = findFirstTsFile(path.join(dir, entry.name));
|
|
40
|
+
if (found) return found;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
// Permission error or similar
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Spawn tsserver, send configure, verify response, shut down */
|
|
50
|
+
function testTsserver(projectRoot: string): Promise<boolean> {
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
let tsserverPath: string;
|
|
53
|
+
try {
|
|
54
|
+
const require = createRequire(path.resolve(projectRoot, "package.json"));
|
|
55
|
+
tsserverPath = require.resolve("typescript/lib/tsserver.js");
|
|
56
|
+
} catch {
|
|
57
|
+
resolve(false);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const child = spawn("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
|
|
62
|
+
cwd: projectRoot,
|
|
63
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const timeout = setTimeout(() => {
|
|
67
|
+
child.kill();
|
|
68
|
+
resolve(false);
|
|
69
|
+
}, 10000);
|
|
70
|
+
|
|
71
|
+
let buffer = "";
|
|
72
|
+
child.stdout.on("data", (chunk: { toString(): string }) => {
|
|
73
|
+
buffer += chunk.toString();
|
|
74
|
+
// tsserver sends Content-Length framed JSON — look for success response
|
|
75
|
+
if (buffer.includes('"success":true')) {
|
|
76
|
+
clearTimeout(timeout);
|
|
77
|
+
child.kill();
|
|
78
|
+
resolve(true);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
child.on("error", () => {
|
|
83
|
+
clearTimeout(timeout);
|
|
84
|
+
resolve(false);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
child.on("exit", () => {
|
|
88
|
+
clearTimeout(timeout);
|
|
89
|
+
// If we haven't resolved yet, it failed
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Send configure request (newline-delimited JSON, not Content-Length framed)
|
|
93
|
+
const request = JSON.stringify({
|
|
94
|
+
seq: 1,
|
|
95
|
+
type: "request",
|
|
96
|
+
command: "configure",
|
|
97
|
+
arguments: {
|
|
98
|
+
preferences: { disableSuggestions: true },
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
child.stdin.write(request + "\n");
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
export async function main(configOverride?: TypegraphConfig): Promise<CheckResult> {
|
|
108
|
+
const { projectRoot, tsconfigPath, toolDir, toolIsEmbedded, toolRelPath } =
|
|
109
|
+
configOverride ?? resolveConfig(import.meta.dirname);
|
|
110
|
+
|
|
111
|
+
let passed = 0;
|
|
112
|
+
let failed = 0;
|
|
113
|
+
let warned = 0;
|
|
114
|
+
|
|
115
|
+
function pass(msg: string): void {
|
|
116
|
+
console.log(` \u2713 ${msg}`);
|
|
117
|
+
passed++;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function fail(msg: string, fix: string): void {
|
|
121
|
+
console.log(` \u2717 ${msg}`);
|
|
122
|
+
console.log(` Fix: ${fix}`);
|
|
123
|
+
failed++;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function warn(msg: string, note: string): void {
|
|
127
|
+
console.log(` ! ${msg}`);
|
|
128
|
+
console.log(` ${note}`);
|
|
129
|
+
warned++;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function skip(msg: string): void {
|
|
133
|
+
console.log(` - ${msg} (skipped)`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
console.log("");
|
|
137
|
+
console.log("typegraph-mcp Health Check");
|
|
138
|
+
console.log("=======================");
|
|
139
|
+
console.log(`Project root: ${projectRoot}`);
|
|
140
|
+
console.log("");
|
|
141
|
+
|
|
142
|
+
// 1. Node.js version
|
|
143
|
+
const nodeVersion = process.version;
|
|
144
|
+
const nodeMajor = parseInt(nodeVersion.slice(1).split(".")[0]!, 10);
|
|
145
|
+
if (nodeMajor >= 18) {
|
|
146
|
+
pass(`Node.js ${nodeVersion} (>= 18 required)`);
|
|
147
|
+
} else {
|
|
148
|
+
fail(`Node.js ${nodeVersion} is too old`, "Upgrade Node.js to >= 18");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 2. tsx availability (if we're running, tsx works — but check it's in the project)
|
|
152
|
+
const tsxInRoot = fs.existsSync(path.join(projectRoot, "node_modules/.bin/tsx"));
|
|
153
|
+
const tsxInTool = fs.existsSync(path.join(toolDir, "node_modules/.bin/tsx"));
|
|
154
|
+
if (tsxInRoot || tsxInTool) {
|
|
155
|
+
pass(`tsx available (in ${tsxInRoot ? "project" : "tool"} node_modules)`);
|
|
156
|
+
} else {
|
|
157
|
+
// We're running via tsx, so it must be available somehow (global or npx)
|
|
158
|
+
pass("tsx available (via npx/global)");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 3. TypeScript in project
|
|
162
|
+
let tsVersion: string | null = null;
|
|
163
|
+
try {
|
|
164
|
+
const require = createRequire(path.resolve(projectRoot, "package.json"));
|
|
165
|
+
const tsserverPath = require.resolve("typescript/lib/tsserver.js");
|
|
166
|
+
const tsPkgPath = path.resolve(path.dirname(tsserverPath), "..", "package.json");
|
|
167
|
+
const tsPkg = JSON.parse(fs.readFileSync(tsPkgPath, "utf-8"));
|
|
168
|
+
tsVersion = tsPkg.version;
|
|
169
|
+
pass(`TypeScript found (v${tsVersion})`);
|
|
170
|
+
} catch {
|
|
171
|
+
fail(
|
|
172
|
+
"TypeScript not found in project",
|
|
173
|
+
"Add `typescript` to devDependencies and run `pnpm install`"
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 4. tsconfig.json exists
|
|
178
|
+
const tsconfigAbs = path.resolve(projectRoot, tsconfigPath);
|
|
179
|
+
if (fs.existsSync(tsconfigAbs)) {
|
|
180
|
+
pass(`tsconfig.json exists at ${tsconfigPath}`);
|
|
181
|
+
} else {
|
|
182
|
+
fail(`tsconfig.json not found at ${tsconfigPath}`, `Create a tsconfig.json at ${tsconfigPath}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 5. MCP registration
|
|
186
|
+
// Check for plugin .mcp.json in the tool directory (embedded plugin install)
|
|
187
|
+
const pluginMcpPath = path.join(toolDir, ".mcp.json");
|
|
188
|
+
const hasPluginMcp = fs.existsSync(pluginMcpPath) && fs.existsSync(path.join(toolDir, ".claude-plugin/plugin.json"));
|
|
189
|
+
if (process.env.CLAUDE_PLUGIN_ROOT) {
|
|
190
|
+
pass("MCP registered via plugin (CLAUDE_PLUGIN_ROOT set)");
|
|
191
|
+
} else if (hasPluginMcp) {
|
|
192
|
+
pass("MCP registered via plugin (.mcp.json + .claude-plugin/ present)");
|
|
193
|
+
} else {
|
|
194
|
+
const mcpJsonPath = path.resolve(projectRoot, ".claude/mcp.json");
|
|
195
|
+
if (fs.existsSync(mcpJsonPath)) {
|
|
196
|
+
try {
|
|
197
|
+
const mcpJson = JSON.parse(fs.readFileSync(mcpJsonPath, "utf-8"));
|
|
198
|
+
const tsNav = mcpJson?.mcpServers?.["typegraph"];
|
|
199
|
+
if (tsNav) {
|
|
200
|
+
const hasCommand = tsNav.command === "npx";
|
|
201
|
+
const hasArgs = Array.isArray(tsNav.args) && tsNav.args.includes("tsx");
|
|
202
|
+
const hasEnv = tsNav.env?.["TYPEGRAPH_PROJECT_ROOT"] && tsNav.env?.["TYPEGRAPH_TSCONFIG"];
|
|
203
|
+
if (hasCommand && hasArgs && hasEnv) {
|
|
204
|
+
pass("MCP registered in .claude/mcp.json");
|
|
205
|
+
} else {
|
|
206
|
+
const issues: string[] = [];
|
|
207
|
+
if (!hasCommand) issues.push("command should be 'npx'");
|
|
208
|
+
if (!hasArgs) issues.push("args should include 'tsx'");
|
|
209
|
+
if (!hasEnv) issues.push("env should set TYPEGRAPH_PROJECT_ROOT and TYPEGRAPH_TSCONFIG");
|
|
210
|
+
fail(
|
|
211
|
+
`MCP registration incomplete: ${issues.join(", ")}`,
|
|
212
|
+
"See README for correct .claude/mcp.json format"
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
const serverPath = toolIsEmbedded
|
|
217
|
+
? `./${toolRelPath}/server.ts`
|
|
218
|
+
: path.resolve(toolDir, "server.ts");
|
|
219
|
+
fail(
|
|
220
|
+
"MCP entry 'typegraph' not found in .claude/mcp.json",
|
|
221
|
+
`Add to .claude/mcp.json:\n` +
|
|
222
|
+
` {\n` +
|
|
223
|
+
` "mcpServers": {\n` +
|
|
224
|
+
` "typegraph": {\n` +
|
|
225
|
+
` "command": "npx",\n` +
|
|
226
|
+
` "args": ["tsx", "${serverPath}"],\n` +
|
|
227
|
+
` "env": { "TYPEGRAPH_PROJECT_ROOT": ".", "TYPEGRAPH_TSCONFIG": "./tsconfig.json" }\n` +
|
|
228
|
+
` }\n` +
|
|
229
|
+
` }\n` +
|
|
230
|
+
` }`
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
} catch (err) {
|
|
234
|
+
fail(
|
|
235
|
+
"Failed to parse .claude/mcp.json",
|
|
236
|
+
`Check JSON syntax: ${err instanceof Error ? err.message : String(err)}`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
fail(".claude/mcp.json not found", `Create .claude/mcp.json with typegraph server registration`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// 6. typegraph-mcp dependencies installed
|
|
245
|
+
const toolNodeModules = path.join(toolDir, "node_modules");
|
|
246
|
+
if (fs.existsSync(toolNodeModules)) {
|
|
247
|
+
const requiredPkgs = ["@modelcontextprotocol/sdk", "oxc-parser", "oxc-resolver", "zod"];
|
|
248
|
+
const missing = requiredPkgs.filter(
|
|
249
|
+
(pkg) => !fs.existsSync(path.join(toolNodeModules, ...pkg.split("/")))
|
|
250
|
+
);
|
|
251
|
+
if (missing.length === 0) {
|
|
252
|
+
pass(`Dependencies installed (${requiredPkgs.length} packages)`);
|
|
253
|
+
} else {
|
|
254
|
+
fail(`Missing packages: ${missing.join(", ")}`, `Run \`cd ${toolRelPath} && pnpm install\``);
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
fail("typegraph-mcp dependencies not installed", `Run \`cd ${toolRelPath} && pnpm install\``);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// 7. oxc-parser smoke test
|
|
261
|
+
try {
|
|
262
|
+
const oxcParserReq = createRequire(path.join(toolDir, "package.json"));
|
|
263
|
+
const { parseSync } = await import(oxcParserReq.resolve("oxc-parser"));
|
|
264
|
+
const result = parseSync("test.ts", 'import { x } from "./y";');
|
|
265
|
+
if (result.module?.staticImports?.length === 1) {
|
|
266
|
+
pass("oxc-parser working");
|
|
267
|
+
} else {
|
|
268
|
+
fail(
|
|
269
|
+
"oxc-parser parseSync returned unexpected result",
|
|
270
|
+
`Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && pnpm install\``
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
} catch (err) {
|
|
274
|
+
fail(
|
|
275
|
+
`oxc-parser failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
276
|
+
`Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && pnpm install\``
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 8. oxc-resolver smoke test
|
|
281
|
+
try {
|
|
282
|
+
const oxcResolverReq = createRequire(path.join(toolDir, "package.json"));
|
|
283
|
+
const { ResolverFactory } = await import(oxcResolverReq.resolve("oxc-resolver"));
|
|
284
|
+
const resolver = new ResolverFactory({
|
|
285
|
+
tsconfig: { configFile: tsconfigAbs, references: "auto" },
|
|
286
|
+
extensions: [".ts", ".tsx", ".js"],
|
|
287
|
+
extensionAlias: { ".js": [".ts", ".tsx", ".js"] },
|
|
288
|
+
});
|
|
289
|
+
// Find any .ts file in the project to test resolution
|
|
290
|
+
let resolveOk = false;
|
|
291
|
+
const testFile = findFirstTsFile(projectRoot);
|
|
292
|
+
if (testFile) {
|
|
293
|
+
const dir = path.dirname(testFile);
|
|
294
|
+
const base = "./" + path.basename(testFile);
|
|
295
|
+
const result = resolver.sync(dir, base);
|
|
296
|
+
resolveOk = !!result.path;
|
|
297
|
+
}
|
|
298
|
+
if (resolveOk) {
|
|
299
|
+
pass("oxc-resolver working");
|
|
300
|
+
} else {
|
|
301
|
+
// Resolver loaded but couldn't resolve — still partially working
|
|
302
|
+
warn(
|
|
303
|
+
"oxc-resolver loaded but couldn't resolve a test import",
|
|
304
|
+
"Check tsconfig.json is valid and has correct `references`"
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
} catch (err) {
|
|
308
|
+
fail(
|
|
309
|
+
`oxc-resolver failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
310
|
+
`Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && pnpm install\``
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// 9. tsserver startup test
|
|
315
|
+
if (tsVersion) {
|
|
316
|
+
try {
|
|
317
|
+
const ok = await testTsserver(projectRoot);
|
|
318
|
+
if (ok) {
|
|
319
|
+
pass("tsserver responds to configure");
|
|
320
|
+
} else {
|
|
321
|
+
fail(
|
|
322
|
+
"tsserver did not respond",
|
|
323
|
+
"Verify `typescript` is installed and tsconfig.json is valid"
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
} catch (err) {
|
|
327
|
+
fail(
|
|
328
|
+
`tsserver failed to start: ${err instanceof Error ? err.message : String(err)}`,
|
|
329
|
+
"Verify `typescript` is installed and tsconfig.json is valid"
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
} else {
|
|
333
|
+
skip("tsserver test (TypeScript not found)");
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// 10. Module graph build test
|
|
337
|
+
try {
|
|
338
|
+
const { buildGraph } = await import(path.resolve(toolDir, "module-graph.js"));
|
|
339
|
+
const start = performance.now();
|
|
340
|
+
const { graph } = await buildGraph(projectRoot, tsconfigPath);
|
|
341
|
+
const elapsed = (performance.now() - start).toFixed(0);
|
|
342
|
+
const edgeCount = [...graph.forward.values()].reduce(
|
|
343
|
+
(s: number, e: unknown[]) => s + e.length,
|
|
344
|
+
0
|
|
345
|
+
);
|
|
346
|
+
if (graph.files.size > 0 && edgeCount > 0) {
|
|
347
|
+
pass(`Module graph: ${graph.files.size} files, ${edgeCount} edges [${elapsed}ms]`);
|
|
348
|
+
} else if (graph.files.size > 0) {
|
|
349
|
+
warn(
|
|
350
|
+
`Module graph: ${graph.files.size} files but 0 edges`,
|
|
351
|
+
"Files found but no internal imports resolved. Check tsconfig references."
|
|
352
|
+
);
|
|
353
|
+
} else {
|
|
354
|
+
fail(
|
|
355
|
+
"Module graph: 0 files discovered",
|
|
356
|
+
"Check tsconfig.json includes source files and project root is correct"
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
} catch (err) {
|
|
360
|
+
fail(
|
|
361
|
+
`Module graph build failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
362
|
+
"Check that oxc-parser and oxc-resolver are installed correctly"
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// 11. ESLint ignores (only when typegraph-mcp is embedded inside the project)
|
|
367
|
+
if (toolIsEmbedded) {
|
|
368
|
+
const eslintConfigPath = path.resolve(projectRoot, "eslint.config.mjs");
|
|
369
|
+
if (fs.existsSync(eslintConfigPath)) {
|
|
370
|
+
const eslintContent = fs.readFileSync(eslintConfigPath, "utf-8");
|
|
371
|
+
// Determine the parent directory (e.g. "plugins") for the ignore pattern
|
|
372
|
+
const parentDir = path.basename(path.dirname(toolDir));
|
|
373
|
+
const parentIgnorePattern = new RegExp(`["']${parentDir}\\/\\*\\*["']`);
|
|
374
|
+
const hasParentIgnore = parentIgnorePattern.test(eslintContent);
|
|
375
|
+
|
|
376
|
+
if (hasParentIgnore) {
|
|
377
|
+
pass(`ESLint ignores ${parentDir}/`);
|
|
378
|
+
} else {
|
|
379
|
+
fail(
|
|
380
|
+
`ESLint missing ignore: "${parentDir}/**"`,
|
|
381
|
+
`Add to the ignores array in eslint.config.mjs:\n "${parentDir}/**",`
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
skip("ESLint config check (no eslint.config.mjs)");
|
|
386
|
+
}
|
|
387
|
+
} else {
|
|
388
|
+
skip("ESLint config check (typegraph-mcp is external to project)");
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// 12. .gitignore check (optional)
|
|
392
|
+
const gitignorePath = path.resolve(projectRoot, ".gitignore");
|
|
393
|
+
if (fs.existsSync(gitignorePath)) {
|
|
394
|
+
const gitignoreContent = fs.readFileSync(gitignorePath, "utf-8");
|
|
395
|
+
const lines = gitignoreContent
|
|
396
|
+
.split("\n")
|
|
397
|
+
.map((l: string) => l.trim())
|
|
398
|
+
.filter((l: string) => l && !l.startsWith("#"));
|
|
399
|
+
const ignoresClaude = lines.some(
|
|
400
|
+
(l: string) => l === ".claude/" || l === ".claude" || l === "/.claude"
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
// Check parent dir exclusion when typegraph-mcp is embedded
|
|
404
|
+
const parentDir = toolIsEmbedded ? path.basename(path.dirname(toolDir)) : null;
|
|
405
|
+
const ignoresParent =
|
|
406
|
+
parentDir &&
|
|
407
|
+
lines.some((l: string) => l === `${parentDir}/` || l === parentDir || l === `/${parentDir}`);
|
|
408
|
+
|
|
409
|
+
if (!ignoresParent && !ignoresClaude) {
|
|
410
|
+
pass(".gitignore does not exclude .claude/" + (parentDir ? ` or ${parentDir}/` : ""));
|
|
411
|
+
} else {
|
|
412
|
+
const excluded: string[] = [];
|
|
413
|
+
if (ignoresParent) excluded.push(`${parentDir}/`);
|
|
414
|
+
if (ignoresClaude) excluded.push(".claude/");
|
|
415
|
+
warn(
|
|
416
|
+
`.gitignore excludes ${excluded.join(" and ")}`,
|
|
417
|
+
"Remove these entries so MCP config and tool source are tracked in git"
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
} else {
|
|
421
|
+
skip(".gitignore check (no .gitignore)");
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ─── Summary ─────────────────────────────────────────────────────────────
|
|
425
|
+
|
|
426
|
+
console.log("");
|
|
427
|
+
const total = passed + failed;
|
|
428
|
+
if (failed === 0) {
|
|
429
|
+
console.log(
|
|
430
|
+
`${passed}/${total} checks passed` +
|
|
431
|
+
(warned > 0 ? ` (${warned} warning${warned > 1 ? "s" : ""})` : "") +
|
|
432
|
+
" -- typegraph-mcp is ready"
|
|
433
|
+
);
|
|
434
|
+
} else {
|
|
435
|
+
console.log(
|
|
436
|
+
`${passed}/${total} checks passed, ${failed} failed` +
|
|
437
|
+
(warned > 0 ? `, ${warned} warning${warned > 1 ? "s" : ""}` : "") +
|
|
438
|
+
" -- fix issues above"
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
console.log("");
|
|
442
|
+
|
|
443
|
+
return { passed, failed, warned };
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// ─── Self-run guard ──────────────────────────────────────────────────────────
|
|
447
|
+
|
|
448
|
+
const isDirectRun =
|
|
449
|
+
process.argv[1] &&
|
|
450
|
+
fs.realpathSync(process.argv[1]) === fs.realpathSync(new URL(import.meta.url).pathname);
|
|
451
|
+
|
|
452
|
+
if (isDirectRun) {
|
|
453
|
+
main()
|
|
454
|
+
.then((result) => process.exit(result.failed > 0 ? 1 : 0))
|
|
455
|
+
.catch((err) => {
|
|
456
|
+
console.error("Fatal error:", err);
|
|
457
|
+
process.exit(1);
|
|
458
|
+
});
|
|
459
|
+
}
|