codesight 1.1.1 → 1.3.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/README.md +114 -31
- package/dist/ast/extract-components.d.ts +12 -0
- package/dist/ast/extract-components.js +180 -0
- package/dist/ast/extract-routes.d.ts +13 -0
- package/dist/ast/extract-routes.js +271 -0
- package/dist/ast/extract-schema.d.ts +15 -0
- package/dist/ast/extract-schema.js +302 -0
- package/dist/ast/loader.d.ts +20 -0
- package/dist/ast/loader.js +105 -0
- package/dist/detectors/blast-radius.d.ts +11 -0
- package/dist/detectors/blast-radius.js +102 -0
- package/dist/detectors/components.js +11 -0
- package/dist/detectors/routes.js +91 -15
- package/dist/detectors/schema.js +20 -0
- package/dist/generators/ai-config.d.ts +5 -0
- package/dist/generators/ai-config.js +97 -0
- package/dist/index.js +60 -2
- package/dist/mcp-server.js +288 -30
- package/dist/types.d.ts +21 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import { calculateTokenStats } from "./detectors/tokens.js";
|
|
|
14
14
|
import { writeOutput } from "./formatter.js";
|
|
15
15
|
import { generateAIConfigs } from "./generators/ai-config.js";
|
|
16
16
|
import { generateHtmlReport } from "./generators/html-report.js";
|
|
17
|
-
const VERSION = "1.
|
|
17
|
+
const VERSION = "1.3.0";
|
|
18
18
|
const BRAND = "codesight";
|
|
19
19
|
function printHelp() {
|
|
20
20
|
console.log(`
|
|
@@ -33,6 +33,8 @@ function printHelp() {
|
|
|
33
33
|
--mcp Start as MCP server (for Claude Code, Cursor)
|
|
34
34
|
--json Output JSON instead of markdown
|
|
35
35
|
--benchmark Show detailed token savings breakdown
|
|
36
|
+
--profile <tool> Generate optimized config (claude-code|cursor|codex|copilot|windsurf)
|
|
37
|
+
--blast <file> Show blast radius for a file
|
|
36
38
|
-v, --version Show version
|
|
37
39
|
-h, --help Show this help
|
|
38
40
|
|
|
@@ -84,7 +86,17 @@ async function scan(root, outputDirName, maxDepth) {
|
|
|
84
86
|
]);
|
|
85
87
|
// Step 4: Enrich routes with contract info
|
|
86
88
|
const routes = await enrichRouteContracts(rawRoutes, project);
|
|
87
|
-
|
|
89
|
+
// Report AST vs regex detection
|
|
90
|
+
const astRoutes = routes.filter((r) => r.confidence === "ast").length;
|
|
91
|
+
const astSchemas = schemas.filter((s) => s.confidence === "ast").length;
|
|
92
|
+
const astComponents = components.filter((c) => c.confidence === "ast").length;
|
|
93
|
+
const totalAST = astRoutes + astSchemas + astComponents;
|
|
94
|
+
if (totalAST > 0) {
|
|
95
|
+
console.log(` done (AST: ${astRoutes} routes, ${astSchemas} models, ${astComponents} components)`);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
console.log(" done");
|
|
99
|
+
}
|
|
88
100
|
// Step 5: Write output
|
|
89
101
|
process.stdout.write(" Writing output...");
|
|
90
102
|
// Temporary result without token stats to generate output
|
|
@@ -220,6 +232,8 @@ async function main() {
|
|
|
220
232
|
let doOpen = false;
|
|
221
233
|
let doMcp = false;
|
|
222
234
|
let doBenchmark = false;
|
|
235
|
+
let doProfile = "";
|
|
236
|
+
let doBlast = "";
|
|
223
237
|
for (let i = 0; i < args.length; i++) {
|
|
224
238
|
const arg = args[i];
|
|
225
239
|
if ((arg === "-o" || arg === "--output") && args[i + 1]) {
|
|
@@ -253,6 +267,12 @@ async function main() {
|
|
|
253
267
|
else if (arg === "--benchmark") {
|
|
254
268
|
doBenchmark = true;
|
|
255
269
|
}
|
|
270
|
+
else if (arg === "--profile" && args[i + 1]) {
|
|
271
|
+
doProfile = args[++i];
|
|
272
|
+
}
|
|
273
|
+
else if (arg === "--blast" && args[i + 1]) {
|
|
274
|
+
doBlast = args[++i];
|
|
275
|
+
}
|
|
256
276
|
else if (!arg.startsWith("-")) {
|
|
257
277
|
targetDir = resolve(arg);
|
|
258
278
|
}
|
|
@@ -329,6 +349,44 @@ async function main() {
|
|
|
329
349
|
- 1.3x multiplier: AI revisits files during multi-turn exploration
|
|
330
350
|
`);
|
|
331
351
|
}
|
|
352
|
+
// Blast radius analysis
|
|
353
|
+
if (doBlast) {
|
|
354
|
+
const { analyzeBlastRadius } = await import("./detectors/blast-radius.js");
|
|
355
|
+
const br = analyzeBlastRadius(doBlast, result);
|
|
356
|
+
console.log(`\n Blast Radius: ${doBlast}`);
|
|
357
|
+
console.log(` Depth: ${br.depth} hops\n`);
|
|
358
|
+
if (br.affectedFiles.length > 0) {
|
|
359
|
+
console.log(` Affected files (${br.affectedFiles.length}):`);
|
|
360
|
+
for (const f of br.affectedFiles.slice(0, 20)) {
|
|
361
|
+
console.log(` ${f}`);
|
|
362
|
+
}
|
|
363
|
+
if (br.affectedFiles.length > 20)
|
|
364
|
+
console.log(` ... +${br.affectedFiles.length - 20} more`);
|
|
365
|
+
}
|
|
366
|
+
if (br.affectedRoutes.length > 0) {
|
|
367
|
+
console.log(`\n Affected routes (${br.affectedRoutes.length}):`);
|
|
368
|
+
for (const r of br.affectedRoutes) {
|
|
369
|
+
console.log(` ${r.method} ${r.path} — ${r.file}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (br.affectedModels.length > 0) {
|
|
373
|
+
console.log(`\n Affected models: ${br.affectedModels.join(", ")}`);
|
|
374
|
+
}
|
|
375
|
+
if (br.affectedMiddleware.length > 0) {
|
|
376
|
+
console.log(`\n Affected middleware: ${br.affectedMiddleware.join(", ")}`);
|
|
377
|
+
}
|
|
378
|
+
if (br.affectedFiles.length === 0) {
|
|
379
|
+
console.log(" No downstream dependencies. Minimal blast radius.");
|
|
380
|
+
}
|
|
381
|
+
console.log("");
|
|
382
|
+
}
|
|
383
|
+
// Profile-based AI config generation
|
|
384
|
+
if (doProfile) {
|
|
385
|
+
const { generateProfileConfig } = await import("./generators/ai-config.js");
|
|
386
|
+
process.stdout.write(` Generating ${doProfile} profile...`);
|
|
387
|
+
const file = await generateProfileConfig(result, root, doProfile);
|
|
388
|
+
console.log(` ${file}`);
|
|
389
|
+
}
|
|
332
390
|
// Watch mode (blocks)
|
|
333
391
|
if (doWatch) {
|
|
334
392
|
await watchMode(root, outputDirName, maxDepth);
|
package/dist/mcp-server.js
CHANGED
|
@@ -10,13 +10,20 @@ import { detectDependencyGraph } from "./detectors/graph.js";
|
|
|
10
10
|
import { enrichRouteContracts } from "./detectors/contracts.js";
|
|
11
11
|
import { calculateTokenStats } from "./detectors/tokens.js";
|
|
12
12
|
import { writeOutput } from "./formatter.js";
|
|
13
|
+
import { analyzeBlastRadius, analyzeMultiFileBlastRadius } from "./detectors/blast-radius.js";
|
|
13
14
|
function send(msg) {
|
|
14
15
|
const json = JSON.stringify(msg);
|
|
15
16
|
const header = `Content-Length: ${Buffer.byteLength(json)}\r\n\r\n`;
|
|
16
17
|
process.stdout.write(header + json);
|
|
17
18
|
}
|
|
18
|
-
|
|
19
|
+
// Cache scan results for the session to avoid re-scanning
|
|
20
|
+
let cachedResult = null;
|
|
21
|
+
let cachedRoot = null;
|
|
22
|
+
async function getScanResult(directory) {
|
|
19
23
|
const root = resolve(directory || process.cwd());
|
|
24
|
+
// Return cached if same directory
|
|
25
|
+
if (cachedResult && cachedRoot === root)
|
|
26
|
+
return cachedResult;
|
|
20
27
|
const project = await detectProject(root);
|
|
21
28
|
const files = await collectFiles(root, 10);
|
|
22
29
|
const [rawRoutes, schemas, components, libs, config, middleware, graph] = await Promise.all([
|
|
@@ -42,10 +49,277 @@ async function runScan(directory) {
|
|
|
42
49
|
};
|
|
43
50
|
const outputContent = await writeOutput(tempResult, resolve(root, ".codesight"));
|
|
44
51
|
const tokenStats = calculateTokenStats(tempResult, outputContent, files.length);
|
|
45
|
-
|
|
52
|
+
cachedResult = { ...tempResult, tokenStats };
|
|
53
|
+
cachedRoot = root;
|
|
54
|
+
return cachedResult;
|
|
46
55
|
}
|
|
56
|
+
// =================== TOOL IMPLEMENTATIONS ===================
|
|
57
|
+
async function toolScan(args) {
|
|
58
|
+
const result = await getScanResult(args.directory);
|
|
59
|
+
const outputContent = await writeOutput(result, resolve(cachedRoot, ".codesight"));
|
|
60
|
+
return outputContent.replace(/Saves ~\d[\d,]* tokens/, `Saves ~${result.tokenStats.saved.toLocaleString()} tokens`);
|
|
61
|
+
}
|
|
62
|
+
async function toolGetRoutes(args) {
|
|
63
|
+
const result = await getScanResult(args.directory);
|
|
64
|
+
let routes = result.routes;
|
|
65
|
+
// Filter by prefix
|
|
66
|
+
if (args.prefix) {
|
|
67
|
+
routes = routes.filter((r) => r.path.startsWith(args.prefix));
|
|
68
|
+
}
|
|
69
|
+
// Filter by tag
|
|
70
|
+
if (args.tag) {
|
|
71
|
+
routes = routes.filter((r) => r.tags.includes(args.tag));
|
|
72
|
+
}
|
|
73
|
+
// Filter by method
|
|
74
|
+
if (args.method) {
|
|
75
|
+
routes = routes.filter((r) => r.method === args.method.toUpperCase());
|
|
76
|
+
}
|
|
77
|
+
const lines = routes.map((r) => {
|
|
78
|
+
const tags = r.tags.length > 0 ? ` [${r.tags.join(", ")}]` : "";
|
|
79
|
+
const params = r.params ? ` params(${r.params.join(", ")})` : "";
|
|
80
|
+
return `${r.method} ${r.path}${params}${tags} — ${r.file}`;
|
|
81
|
+
});
|
|
82
|
+
return lines.length > 0
|
|
83
|
+
? `${lines.length} routes:\n${lines.join("\n")}`
|
|
84
|
+
: "No routes found matching filters.";
|
|
85
|
+
}
|
|
86
|
+
async function toolGetSchema(args) {
|
|
87
|
+
const result = await getScanResult(args.directory);
|
|
88
|
+
let models = result.schemas;
|
|
89
|
+
if (args.model) {
|
|
90
|
+
models = models.filter((m) => m.name.toLowerCase().includes(args.model.toLowerCase()));
|
|
91
|
+
}
|
|
92
|
+
const lines = [];
|
|
93
|
+
for (const model of models) {
|
|
94
|
+
lines.push(`### ${model.name} (${model.orm})`);
|
|
95
|
+
for (const field of model.fields) {
|
|
96
|
+
const flags = field.flags.length > 0 ? ` (${field.flags.join(", ")})` : "";
|
|
97
|
+
lines.push(` ${field.name}: ${field.type}${flags}`);
|
|
98
|
+
}
|
|
99
|
+
if (model.relations.length > 0) {
|
|
100
|
+
lines.push(` relations: ${model.relations.join(", ")}`);
|
|
101
|
+
}
|
|
102
|
+
lines.push("");
|
|
103
|
+
}
|
|
104
|
+
return lines.length > 0
|
|
105
|
+
? `${models.length} models:\n${lines.join("\n")}`
|
|
106
|
+
: "No models found.";
|
|
107
|
+
}
|
|
108
|
+
async function toolGetBlastRadius(args) {
|
|
109
|
+
const result = await getScanResult(args.directory);
|
|
110
|
+
const maxDepth = args.depth || 3;
|
|
111
|
+
let br;
|
|
112
|
+
if (args.files && Array.isArray(args.files)) {
|
|
113
|
+
br = analyzeMultiFileBlastRadius(args.files, result, maxDepth);
|
|
114
|
+
}
|
|
115
|
+
else if (args.file) {
|
|
116
|
+
br = analyzeBlastRadius(args.file, result, maxDepth);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
return "Error: provide 'file' (string) or 'files' (array) parameter.";
|
|
120
|
+
}
|
|
121
|
+
const lines = [];
|
|
122
|
+
lines.push(`## Blast Radius for ${br.file}`);
|
|
123
|
+
lines.push(`Depth: ${br.depth} hops\n`);
|
|
124
|
+
if (br.affectedFiles.length > 0) {
|
|
125
|
+
lines.push(`### Affected Files (${br.affectedFiles.length})`);
|
|
126
|
+
for (const f of br.affectedFiles.slice(0, 30)) {
|
|
127
|
+
lines.push(`- ${f}`);
|
|
128
|
+
}
|
|
129
|
+
if (br.affectedFiles.length > 30) {
|
|
130
|
+
lines.push(`- ... +${br.affectedFiles.length - 30} more`);
|
|
131
|
+
}
|
|
132
|
+
lines.push("");
|
|
133
|
+
}
|
|
134
|
+
if (br.affectedRoutes.length > 0) {
|
|
135
|
+
lines.push(`### Affected Routes (${br.affectedRoutes.length})`);
|
|
136
|
+
for (const r of br.affectedRoutes) {
|
|
137
|
+
lines.push(`- ${r.method} ${r.path} — ${r.file}`);
|
|
138
|
+
}
|
|
139
|
+
lines.push("");
|
|
140
|
+
}
|
|
141
|
+
if (br.affectedModels.length > 0) {
|
|
142
|
+
lines.push(`### Potentially Affected Models (${br.affectedModels.length})`);
|
|
143
|
+
for (const m of br.affectedModels) {
|
|
144
|
+
lines.push(`- ${m}`);
|
|
145
|
+
}
|
|
146
|
+
lines.push("");
|
|
147
|
+
}
|
|
148
|
+
if (br.affectedMiddleware.length > 0) {
|
|
149
|
+
lines.push(`### Affected Middleware (${br.affectedMiddleware.length})`);
|
|
150
|
+
for (const m of br.affectedMiddleware) {
|
|
151
|
+
lines.push(`- ${m}`);
|
|
152
|
+
}
|
|
153
|
+
lines.push("");
|
|
154
|
+
}
|
|
155
|
+
if (br.affectedFiles.length === 0 && br.affectedRoutes.length === 0) {
|
|
156
|
+
lines.push("No downstream dependencies found. This file change has minimal blast radius.");
|
|
157
|
+
}
|
|
158
|
+
return lines.join("\n");
|
|
159
|
+
}
|
|
160
|
+
async function toolGetEnv(args) {
|
|
161
|
+
const result = await getScanResult(args.directory);
|
|
162
|
+
const envVars = result.config.envVars;
|
|
163
|
+
if (args.required_only) {
|
|
164
|
+
const required = envVars.filter((e) => !e.hasDefault);
|
|
165
|
+
const lines = required.map((e) => `${e.name} **required** — ${e.source}`);
|
|
166
|
+
return `${required.length} required env vars (no defaults):\n${lines.join("\n")}`;
|
|
167
|
+
}
|
|
168
|
+
const lines = envVars.map((e) => {
|
|
169
|
+
const status = e.hasDefault ? "(has default)" : "**required**";
|
|
170
|
+
return `${e.name} ${status} — ${e.source}`;
|
|
171
|
+
});
|
|
172
|
+
return `${envVars.length} env vars:\n${lines.join("\n")}`;
|
|
173
|
+
}
|
|
174
|
+
async function toolGetHotFiles(args) {
|
|
175
|
+
const result = await getScanResult(args.directory);
|
|
176
|
+
const limit = args.limit || 15;
|
|
177
|
+
const hotFiles = result.graph.hotFiles.slice(0, limit);
|
|
178
|
+
if (hotFiles.length === 0)
|
|
179
|
+
return "No import graph data. Run a full scan first.";
|
|
180
|
+
const lines = hotFiles.map((h) => `${h.file} — imported by ${h.importedBy} files`);
|
|
181
|
+
return `Top ${hotFiles.length} most-imported files (change carefully):\n${lines.join("\n")}`;
|
|
182
|
+
}
|
|
183
|
+
async function toolGetSummary(args) {
|
|
184
|
+
const result = await getScanResult(args.directory);
|
|
185
|
+
const { project, routes, schemas, components, config, middleware, graph, tokenStats } = result;
|
|
186
|
+
const fw = project.frameworks.join(", ") || "generic";
|
|
187
|
+
const orm = project.orms.join(", ") || "none";
|
|
188
|
+
const lines = [];
|
|
189
|
+
lines.push(`# ${project.name}`);
|
|
190
|
+
lines.push(`Stack: ${fw} | ${orm} | ${project.componentFramework} | ${project.language}`);
|
|
191
|
+
if (project.isMonorepo) {
|
|
192
|
+
lines.push(`Monorepo: ${project.workspaces.map((w) => w.name).join(", ")}`);
|
|
193
|
+
}
|
|
194
|
+
lines.push("");
|
|
195
|
+
lines.push(`${routes.length} routes | ${schemas.length} models | ${components.length} components | ${config.envVars.length} env vars | ${middleware.length} middleware | ${graph.edges.length} import links`);
|
|
196
|
+
lines.push(`Token savings: ~${tokenStats.saved.toLocaleString()} per conversation`);
|
|
197
|
+
lines.push("");
|
|
198
|
+
// Top routes summary
|
|
199
|
+
if (routes.length > 0) {
|
|
200
|
+
lines.push(`Key API areas: ${[...new Set(routes.map((r) => r.path.split("/").slice(0, 3).join("/")))].slice(0, 8).join(", ")}`);
|
|
201
|
+
}
|
|
202
|
+
// Hot files
|
|
203
|
+
if (graph.hotFiles.length > 0) {
|
|
204
|
+
lines.push(`High-impact files: ${graph.hotFiles.slice(0, 5).map((h) => h.file).join(", ")}`);
|
|
205
|
+
}
|
|
206
|
+
// Required env
|
|
207
|
+
const required = config.envVars.filter((e) => !e.hasDefault);
|
|
208
|
+
if (required.length > 0) {
|
|
209
|
+
lines.push(`Required env: ${required.slice(0, 8).map((e) => e.name).join(", ")}${required.length > 8 ? ` +${required.length - 8} more` : ""}`);
|
|
210
|
+
}
|
|
211
|
+
lines.push("");
|
|
212
|
+
lines.push("Use codesight_get_routes, codesight_get_schema, codesight_get_blast_radius for details.");
|
|
213
|
+
return lines.join("\n");
|
|
214
|
+
}
|
|
215
|
+
async function toolRefresh(args) {
|
|
216
|
+
cachedResult = null;
|
|
217
|
+
cachedRoot = null;
|
|
218
|
+
const result = await getScanResult(args.directory);
|
|
219
|
+
return `Refreshed. ${result.routes.length} routes, ${result.schemas.length} models, ${result.graph.edges.length} import links, ${result.config.envVars.length} env vars.`;
|
|
220
|
+
}
|
|
221
|
+
// =================== TOOL DEFINITIONS ===================
|
|
222
|
+
const TOOLS = [
|
|
223
|
+
{
|
|
224
|
+
name: "codesight_scan",
|
|
225
|
+
description: "Full codebase scan. Returns complete AI context map with routes, schema, components, libraries, config, middleware, and dependency graph. Use this for initial project understanding.",
|
|
226
|
+
inputSchema: {
|
|
227
|
+
type: "object",
|
|
228
|
+
properties: {
|
|
229
|
+
directory: { type: "string", description: "Directory to scan (defaults to cwd)" },
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
handler: toolScan,
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: "codesight_get_summary",
|
|
236
|
+
description: "Compact project summary (~500 tokens). Stack, key stats, high-impact files, required env vars. Use this first before diving deeper.",
|
|
237
|
+
inputSchema: {
|
|
238
|
+
type: "object",
|
|
239
|
+
properties: {
|
|
240
|
+
directory: { type: "string", description: "Directory (defaults to cwd)" },
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
handler: toolGetSummary,
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: "codesight_get_routes",
|
|
247
|
+
description: "Get API routes with methods, paths, params, tags, and handler files. Supports filtering by prefix, tag, or HTTP method.",
|
|
248
|
+
inputSchema: {
|
|
249
|
+
type: "object",
|
|
250
|
+
properties: {
|
|
251
|
+
directory: { type: "string", description: "Directory (defaults to cwd)" },
|
|
252
|
+
prefix: { type: "string", description: "Filter routes by path prefix (e.g., '/api/users')" },
|
|
253
|
+
tag: { type: "string", description: "Filter routes by tag (e.g., 'auth', 'db', 'payment', 'ai')" },
|
|
254
|
+
method: { type: "string", description: "Filter by HTTP method (e.g., 'GET', 'POST')" },
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
handler: toolGetRoutes,
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: "codesight_get_schema",
|
|
261
|
+
description: "Get database models with fields, types, constraints, and relations. Optionally filter by model name.",
|
|
262
|
+
inputSchema: {
|
|
263
|
+
type: "object",
|
|
264
|
+
properties: {
|
|
265
|
+
directory: { type: "string", description: "Directory (defaults to cwd)" },
|
|
266
|
+
model: { type: "string", description: "Filter by model name (partial match)" },
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
handler: toolGetSchema,
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: "codesight_get_blast_radius",
|
|
273
|
+
description: "Blast radius analysis. Given a file (or list of files), returns all transitively affected files, routes, models, and middleware. Use before making changes to understand impact.",
|
|
274
|
+
inputSchema: {
|
|
275
|
+
type: "object",
|
|
276
|
+
properties: {
|
|
277
|
+
directory: { type: "string", description: "Directory (defaults to cwd)" },
|
|
278
|
+
file: { type: "string", description: "Single file path (relative to project root)" },
|
|
279
|
+
files: { type: "array", items: { type: "string" }, description: "Multiple file paths for combined blast radius" },
|
|
280
|
+
depth: { type: "number", description: "Max traversal depth (default: 3)" },
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
handler: toolGetBlastRadius,
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: "codesight_get_env",
|
|
287
|
+
description: "Get environment variables across the codebase with required/default status and source file.",
|
|
288
|
+
inputSchema: {
|
|
289
|
+
type: "object",
|
|
290
|
+
properties: {
|
|
291
|
+
directory: { type: "string", description: "Directory (defaults to cwd)" },
|
|
292
|
+
required_only: { type: "boolean", description: "Only show required vars (no defaults)" },
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
handler: toolGetEnv,
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: "codesight_get_hot_files",
|
|
299
|
+
description: "Get the most-imported files in the project. These have the highest blast radius — changes here affect the most other files.",
|
|
300
|
+
inputSchema: {
|
|
301
|
+
type: "object",
|
|
302
|
+
properties: {
|
|
303
|
+
directory: { type: "string", description: "Directory (defaults to cwd)" },
|
|
304
|
+
limit: { type: "number", description: "Number of files to return (default: 15)" },
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
handler: toolGetHotFiles,
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
name: "codesight_refresh",
|
|
311
|
+
description: "Force re-scan the project. Use after making significant changes to get updated context.",
|
|
312
|
+
inputSchema: {
|
|
313
|
+
type: "object",
|
|
314
|
+
properties: {
|
|
315
|
+
directory: { type: "string", description: "Directory (defaults to cwd)" },
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
handler: toolRefresh,
|
|
319
|
+
},
|
|
320
|
+
];
|
|
321
|
+
// =================== MCP PROTOCOL ===================
|
|
47
322
|
async function handleRequest(req) {
|
|
48
|
-
// MCP initialize
|
|
49
323
|
if (req.method === "initialize") {
|
|
50
324
|
send({
|
|
51
325
|
jsonrpc: "2.0",
|
|
@@ -53,47 +327,35 @@ async function handleRequest(req) {
|
|
|
53
327
|
result: {
|
|
54
328
|
protocolVersion: "2024-11-05",
|
|
55
329
|
capabilities: { tools: {} },
|
|
56
|
-
serverInfo: { name: "codesight", version: "1.
|
|
330
|
+
serverInfo: { name: "codesight", version: "1.2.0" },
|
|
57
331
|
},
|
|
58
332
|
});
|
|
59
333
|
return;
|
|
60
334
|
}
|
|
61
|
-
// MCP initialized notification
|
|
62
335
|
if (req.method === "notifications/initialized") {
|
|
63
|
-
return;
|
|
336
|
+
return;
|
|
64
337
|
}
|
|
65
|
-
// List tools
|
|
66
338
|
if (req.method === "tools/list") {
|
|
67
339
|
send({
|
|
68
340
|
jsonrpc: "2.0",
|
|
69
341
|
id: req.id ?? null,
|
|
70
342
|
result: {
|
|
71
|
-
tools:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
type: "object",
|
|
77
|
-
properties: {
|
|
78
|
-
directory: {
|
|
79
|
-
type: "string",
|
|
80
|
-
description: "Directory to scan (defaults to current working directory)",
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
],
|
|
343
|
+
tools: TOOLS.map(({ name, description, inputSchema }) => ({
|
|
344
|
+
name,
|
|
345
|
+
description,
|
|
346
|
+
inputSchema,
|
|
347
|
+
})),
|
|
86
348
|
},
|
|
87
349
|
});
|
|
88
350
|
return;
|
|
89
351
|
}
|
|
90
|
-
// Call tool
|
|
91
352
|
if (req.method === "tools/call") {
|
|
92
353
|
const toolName = req.params?.name;
|
|
93
354
|
const args = req.params?.arguments || {};
|
|
94
|
-
|
|
355
|
+
const tool = TOOLS.find((t) => t.name === toolName);
|
|
356
|
+
if (tool) {
|
|
95
357
|
try {
|
|
96
|
-
const result = await
|
|
358
|
+
const result = await tool.handler(args);
|
|
97
359
|
send({
|
|
98
360
|
jsonrpc: "2.0",
|
|
99
361
|
id: req.id ?? null,
|
|
@@ -107,7 +369,7 @@ async function handleRequest(req) {
|
|
|
107
369
|
jsonrpc: "2.0",
|
|
108
370
|
id: req.id ?? null,
|
|
109
371
|
result: {
|
|
110
|
-
content: [{ type: "text", text: `Error
|
|
372
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
111
373
|
isError: true,
|
|
112
374
|
},
|
|
113
375
|
});
|
|
@@ -121,7 +383,6 @@ async function handleRequest(req) {
|
|
|
121
383
|
});
|
|
122
384
|
return;
|
|
123
385
|
}
|
|
124
|
-
// Unknown method
|
|
125
386
|
if (req.id !== undefined) {
|
|
126
387
|
send({
|
|
127
388
|
jsonrpc: "2.0",
|
|
@@ -131,13 +392,11 @@ async function handleRequest(req) {
|
|
|
131
392
|
}
|
|
132
393
|
}
|
|
133
394
|
export async function startMCPServer() {
|
|
134
|
-
// Read Content-Length delimited JSON-RPC messages from stdin
|
|
135
395
|
let buffer = "";
|
|
136
396
|
process.stdin.setEncoding("utf-8");
|
|
137
397
|
process.stdin.on("data", async (chunk) => {
|
|
138
398
|
buffer += chunk;
|
|
139
399
|
while (true) {
|
|
140
|
-
// Parse Content-Length header
|
|
141
400
|
const headerEnd = buffer.indexOf("\r\n\r\n");
|
|
142
401
|
if (headerEnd === -1)
|
|
143
402
|
break;
|
|
@@ -166,6 +425,5 @@ export async function startMCPServer() {
|
|
|
166
425
|
}
|
|
167
426
|
}
|
|
168
427
|
});
|
|
169
|
-
// Keep alive
|
|
170
428
|
await new Promise(() => { });
|
|
171
429
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface WorkspaceInfo {
|
|
|
17
17
|
frameworks: Framework[];
|
|
18
18
|
orms: ORM[];
|
|
19
19
|
}
|
|
20
|
+
export type DetectionMethod = "ast" | "regex";
|
|
20
21
|
export interface RouteInfo {
|
|
21
22
|
method: string;
|
|
22
23
|
path: string;
|
|
@@ -26,12 +27,15 @@ export interface RouteInfo {
|
|
|
26
27
|
requestType?: string;
|
|
27
28
|
responseType?: string;
|
|
28
29
|
params?: string[];
|
|
30
|
+
confidence?: DetectionMethod;
|
|
31
|
+
middleware?: string[];
|
|
29
32
|
}
|
|
30
33
|
export interface SchemaModel {
|
|
31
34
|
name: string;
|
|
32
35
|
fields: SchemaField[];
|
|
33
36
|
relations: string[];
|
|
34
37
|
orm: ORM;
|
|
38
|
+
confidence?: DetectionMethod;
|
|
35
39
|
}
|
|
36
40
|
export interface SchemaField {
|
|
37
41
|
name: string;
|
|
@@ -41,6 +45,7 @@ export interface SchemaField {
|
|
|
41
45
|
export interface ComponentInfo {
|
|
42
46
|
name: string;
|
|
43
47
|
file: string;
|
|
48
|
+
confidence?: DetectionMethod;
|
|
44
49
|
props: string[];
|
|
45
50
|
isClient: boolean;
|
|
46
51
|
isServer: boolean;
|
|
@@ -81,6 +86,22 @@ export interface DependencyGraph {
|
|
|
81
86
|
importedBy: number;
|
|
82
87
|
}[];
|
|
83
88
|
}
|
|
89
|
+
export interface BlastRadiusResult {
|
|
90
|
+
file: string;
|
|
91
|
+
affectedFiles: string[];
|
|
92
|
+
affectedRoutes: RouteInfo[];
|
|
93
|
+
affectedModels: string[];
|
|
94
|
+
affectedMiddleware: string[];
|
|
95
|
+
depth: number;
|
|
96
|
+
}
|
|
97
|
+
export interface CodesightConfig {
|
|
98
|
+
disableDetectors?: string[];
|
|
99
|
+
customTags?: Record<string, string[]>;
|
|
100
|
+
maxDepth?: number;
|
|
101
|
+
outputDir?: string;
|
|
102
|
+
profile?: "claude-code" | "cursor" | "codex" | "copilot" | "windsurf" | "generic";
|
|
103
|
+
ignorePatterns?: string[];
|
|
104
|
+
}
|
|
84
105
|
export interface ScanResult {
|
|
85
106
|
project: ProjectInfo;
|
|
86
107
|
routes: RouteInfo[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codesight",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "See your codebase clearly. Universal AI context generator that maps routes, schema, components, dependencies, and more for Claude Code, Cursor, Copilot, Codex, and any AI coding tool.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|