codesight 1.1.0 → 1.2.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 +94 -32
- package/dist/detectors/blast-radius.d.ts +11 -0
- package/dist/detectors/blast-radius.js +102 -0
- package/dist/generators/ai-config.d.ts +5 -0
- package/dist/generators/ai-config.js +97 -0
- package/dist/index.js +49 -1
- package/dist/mcp-server.js +288 -30
- package/dist/types.d.ts +16 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
### Your AI assistant wastes thousands of tokens every conversation just figuring out your project. codesight fixes that in one command.
|
|
4
4
|
|
|
5
|
-
**Zero dependencies. 25+ framework detectors.
|
|
5
|
+
**Zero dependencies. 25+ framework detectors. 8 ORM parsers. 8 MCP tools. Blast radius analysis. One `npx` call.**
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/codesight)
|
|
8
8
|
[](https://www.npmjs.com/package/codesight)
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
---
|
|
27
27
|
|
|
28
28
|
```
|
|
29
|
-
0 dependencies · Node.js >= 18 · 27 tests · MIT
|
|
29
|
+
0 dependencies · Node.js >= 18 · 27 tests · 8 MCP tools · MIT
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
## Works With
|
|
@@ -42,10 +42,12 @@ npx codesight
|
|
|
42
42
|
That's it. Run it in any project root. No config, no setup, no API keys.
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
|
-
npx codesight --init
|
|
46
|
-
npx codesight --open
|
|
47
|
-
npx codesight --mcp
|
|
48
|
-
npx codesight --
|
|
45
|
+
npx codesight --init # Generate CLAUDE.md, .cursorrules, codex.md, AGENTS.md
|
|
46
|
+
npx codesight --open # Open interactive HTML report in browser
|
|
47
|
+
npx codesight --mcp # Start as MCP server (8 tools) for Claude Code / Cursor
|
|
48
|
+
npx codesight --blast src/lib/db.ts # Show blast radius for a file
|
|
49
|
+
npx codesight --profile claude-code # Generate optimized config for a specific AI tool
|
|
50
|
+
npx codesight --benchmark # Show detailed token savings breakdown
|
|
49
51
|
```
|
|
50
52
|
|
|
51
53
|
## What It Does
|
|
@@ -117,6 +119,35 @@ The files imported the most are the ones that break the most things when changed
|
|
|
117
119
|
- `apps/api/src/lib/auth.ts` — imported by **7** files
|
|
118
120
|
```
|
|
119
121
|
|
|
122
|
+
## Blast Radius
|
|
123
|
+
|
|
124
|
+
See exactly what breaks if you change a file. BFS through the import graph finds all transitively affected files, routes, models, and middleware.
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npx codesight --blast src/lib/db.ts
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
Blast Radius: src/lib/db.ts
|
|
132
|
+
Depth: 3 hops
|
|
133
|
+
|
|
134
|
+
Affected files (10):
|
|
135
|
+
src/routes/users.ts
|
|
136
|
+
src/routes/projects.ts
|
|
137
|
+
src/routes/billing.ts
|
|
138
|
+
...
|
|
139
|
+
|
|
140
|
+
Affected routes (33):
|
|
141
|
+
POST /auth/login — src/routes/auth.ts
|
|
142
|
+
GET /api/users — src/routes/users.ts
|
|
143
|
+
...
|
|
144
|
+
|
|
145
|
+
Affected models: users, projects, subscriptions
|
|
146
|
+
Affected middleware: authMiddleware
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Your AI can also query blast radius through the MCP server before making changes.
|
|
150
|
+
|
|
120
151
|
## Environment Audit
|
|
121
152
|
|
|
122
153
|
Every env var across your codebase, flagged as required or has default, with the exact file where it is referenced.
|
|
@@ -157,7 +188,7 @@ npx codesight --benchmark
|
|
|
157
188
|
| Category | Supported |
|
|
158
189
|
|---|---|
|
|
159
190
|
| **Routes** | Hono, Express, Fastify, Next.js (App + Pages), Koa, NestJS, tRPC, Elysia, AdonisJS, SvelteKit, Remix, Nuxt, FastAPI, Flask, Django, Go (net/http, Gin, Fiber, Echo, Chi), Rails, Phoenix, Spring Boot, Actix, Axum, raw http.createServer |
|
|
160
|
-
| **Schema** | Drizzle, Prisma, TypeORM, Mongoose, Sequelize, SQLAlchemy, ActiveRecord, Ecto |
|
|
191
|
+
| **Schema** | Drizzle, Prisma, TypeORM, Mongoose, Sequelize, SQLAlchemy, ActiveRecord, Ecto (8 ORMs) |
|
|
161
192
|
| **Components** | React, Vue, Svelte (auto-filters shadcn/ui and Radix primitives) |
|
|
162
193
|
| **Libraries** | TypeScript, JavaScript, Python, Go, Ruby, Elixir, Java, Kotlin, Rust (exports with function signatures) |
|
|
163
194
|
| **Middleware** | Auth, rate limiting, CORS, validation, logging, error handlers |
|
|
@@ -184,7 +215,7 @@ Generates ready-to-use instruction files for every major AI coding tool at once:
|
|
|
184
215
|
|
|
185
216
|
Each file is pre-filled with your project's stack, architecture, high-impact files, and required env vars. Your AI reads it on startup and starts with full context from the first message.
|
|
186
217
|
|
|
187
|
-
## MCP Server
|
|
218
|
+
## MCP Server (8 Tools)
|
|
188
219
|
|
|
189
220
|
```bash
|
|
190
221
|
npx codesight --mcp
|
|
@@ -203,7 +234,32 @@ Runs as a Model Context Protocol server. Claude Code and Cursor call it directly
|
|
|
203
234
|
}
|
|
204
235
|
```
|
|
205
236
|
|
|
206
|
-
Exposes
|
|
237
|
+
Exposes 8 specialized tools, each returning only what your AI needs:
|
|
238
|
+
|
|
239
|
+
| Tool | What it does |
|
|
240
|
+
|---|---|
|
|
241
|
+
| `codesight_scan` | Full project scan (~3K-5K tokens) |
|
|
242
|
+
| `codesight_get_summary` | Compact overview (~500 tokens) |
|
|
243
|
+
| `codesight_get_routes` | Routes filtered by prefix, tag, or method |
|
|
244
|
+
| `codesight_get_schema` | Schema filtered by model name |
|
|
245
|
+
| `codesight_get_blast_radius` | Impact analysis before changing a file |
|
|
246
|
+
| `codesight_get_env` | Environment variables (filter: required only) |
|
|
247
|
+
| `codesight_get_hot_files` | Most imported files with configurable limit |
|
|
248
|
+
| `codesight_refresh` | Force re-scan (results are cached per session) |
|
|
249
|
+
|
|
250
|
+
Your AI asks for exactly what it needs instead of loading the entire context map. Session caching means the first call scans, subsequent calls return instantly.
|
|
251
|
+
|
|
252
|
+
## AI Tool Profiles
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
npx codesight --profile claude-code
|
|
256
|
+
npx codesight --profile cursor
|
|
257
|
+
npx codesight --profile codex
|
|
258
|
+
npx codesight --profile copilot
|
|
259
|
+
npx codesight --profile windsurf
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Generates an optimized config file for a specific AI tool. Each profile includes your project summary, stack info, high-impact files, required env vars, and tool-specific instructions on how to use codesight outputs. For Claude Code, this includes MCP tool usage instructions. For Cursor, it points to the right codesight files. Each profile writes to the correct file for that tool.
|
|
207
263
|
|
|
208
264
|
## Visual Report
|
|
209
265
|
|
|
@@ -225,7 +281,10 @@ jobs:
|
|
|
225
281
|
runs-on: ubuntu-latest
|
|
226
282
|
steps:
|
|
227
283
|
- uses: actions/checkout@v4
|
|
228
|
-
-
|
|
284
|
+
- uses: actions/setup-node@v4
|
|
285
|
+
with:
|
|
286
|
+
node-version: 20
|
|
287
|
+
- run: npm install -g codesight && codesight
|
|
229
288
|
- uses: actions/upload-artifact@v4
|
|
230
289
|
with:
|
|
231
290
|
name: codesight
|
|
@@ -251,34 +310,37 @@ Context stays fresh without thinking about it.
|
|
|
251
310
|
## All Options
|
|
252
311
|
|
|
253
312
|
```bash
|
|
254
|
-
npx codesight
|
|
255
|
-
npx codesight ./my-project
|
|
256
|
-
npx codesight --init
|
|
257
|
-
npx codesight --open
|
|
258
|
-
npx codesight --html
|
|
259
|
-
npx codesight --mcp
|
|
260
|
-
npx codesight --
|
|
261
|
-
npx codesight --
|
|
262
|
-
npx codesight --
|
|
263
|
-
npx codesight --
|
|
264
|
-
npx codesight
|
|
265
|
-
npx codesight
|
|
313
|
+
npx codesight # Scan current directory
|
|
314
|
+
npx codesight ./my-project # Scan specific directory
|
|
315
|
+
npx codesight --init # Generate AI config files
|
|
316
|
+
npx codesight --open # Open visual HTML report
|
|
317
|
+
npx codesight --html # Generate HTML report without opening
|
|
318
|
+
npx codesight --mcp # Start MCP server (8 tools)
|
|
319
|
+
npx codesight --blast src/lib/db.ts # Show blast radius for a file
|
|
320
|
+
npx codesight --profile claude-code # Optimized config for specific tool
|
|
321
|
+
npx codesight --watch # Watch mode
|
|
322
|
+
npx codesight --hook # Install git pre-commit hook
|
|
323
|
+
npx codesight --benchmark # Detailed token savings breakdown
|
|
324
|
+
npx codesight --json # Output as JSON
|
|
325
|
+
npx codesight -o .ai-context # Custom output directory
|
|
326
|
+
npx codesight -d 5 # Limit directory depth
|
|
266
327
|
```
|
|
267
328
|
|
|
268
329
|
## How It Compares
|
|
269
330
|
|
|
270
331
|
Most AI context tools dump your entire codebase into one file. codesight takes a different approach: it **parses** your code to extract structured information.
|
|
271
332
|
|
|
272
|
-
| | codesight | File concatenation tools |
|
|
273
|
-
|
|
274
|
-
| **Output** | Structured routes, schema, components, deps | Raw file contents |
|
|
275
|
-
| **Token cost** | ~3,000-5,000 tokens | 50,000-500,000+ tokens |
|
|
276
|
-
| **Route detection** | 25+ frameworks auto-detected | None |
|
|
277
|
-
| **Schema parsing** |
|
|
278
|
-
| **
|
|
279
|
-
| **AI
|
|
280
|
-
| **MCP server** |
|
|
281
|
-
| **
|
|
333
|
+
| | codesight | File concatenation tools | AST-based tools |
|
|
334
|
+
|---|---|---|---|
|
|
335
|
+
| **Output** | Structured routes, schema, components, deps | Raw file contents | Call graphs, class diagrams |
|
|
336
|
+
| **Token cost** | ~3,000-5,000 tokens | 50,000-500,000+ tokens | Varies |
|
|
337
|
+
| **Route detection** | 25+ frameworks auto-detected | None | Limited |
|
|
338
|
+
| **Schema parsing** | 8 ORMs with relations | None | Varies |
|
|
339
|
+
| **Blast radius** | BFS through import graph | None | Some |
|
|
340
|
+
| **AI tool profiles** | 5 tools (Claude, Cursor, Codex, Copilot, Windsurf) | None | None |
|
|
341
|
+
| **MCP server** | 8 specialized tools with session caching | None | Some |
|
|
342
|
+
| **Setup** | `npx codesight` (zero deps, zero config) | Copy/paste | Install compilers, runtimes |
|
|
343
|
+
| **Dependencies** | Zero | Varies | Tree-sitter, SQLite, etc. |
|
|
282
344
|
|
|
283
345
|
## Contributing
|
|
284
346
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ScanResult, BlastRadiusResult } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Blast radius analysis: given a file, find all transitively affected
|
|
4
|
+
* files, routes, models, and middleware using BFS through the import graph.
|
|
5
|
+
*/
|
|
6
|
+
export declare function analyzeBlastRadius(filePath: string, result: ScanResult, maxDepth?: number): BlastRadiusResult;
|
|
7
|
+
/**
|
|
8
|
+
* Multi-file blast radius: given a list of changed files (e.g., from git diff),
|
|
9
|
+
* find the combined blast radius.
|
|
10
|
+
*/
|
|
11
|
+
export declare function analyzeMultiFileBlastRadius(files: string[], result: ScanResult, maxDepth?: number): BlastRadiusResult;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blast radius analysis: given a file, find all transitively affected
|
|
3
|
+
* files, routes, models, and middleware using BFS through the import graph.
|
|
4
|
+
*/
|
|
5
|
+
export function analyzeBlastRadius(filePath, result, maxDepth = 3) {
|
|
6
|
+
const { graph, routes, schemas, middleware } = result;
|
|
7
|
+
// Build reverse adjacency map: file -> files that import it
|
|
8
|
+
const importedBy = new Map();
|
|
9
|
+
// Build forward adjacency map: file -> files it imports
|
|
10
|
+
const imports = new Map();
|
|
11
|
+
for (const edge of graph.edges) {
|
|
12
|
+
if (!importedBy.has(edge.to))
|
|
13
|
+
importedBy.set(edge.to, new Set());
|
|
14
|
+
importedBy.get(edge.to).add(edge.from);
|
|
15
|
+
if (!imports.has(edge.from))
|
|
16
|
+
imports.set(edge.from, new Set());
|
|
17
|
+
imports.get(edge.from).add(edge.to);
|
|
18
|
+
}
|
|
19
|
+
// BFS: find all files affected by changing this file
|
|
20
|
+
// "affected" = files that directly or transitively import this file
|
|
21
|
+
const affected = new Set();
|
|
22
|
+
const queue = [{ file: filePath, depth: 0 }];
|
|
23
|
+
while (queue.length > 0) {
|
|
24
|
+
const { file, depth } = queue.shift();
|
|
25
|
+
if (depth > maxDepth)
|
|
26
|
+
continue;
|
|
27
|
+
const dependents = importedBy.get(file);
|
|
28
|
+
if (!dependents)
|
|
29
|
+
continue;
|
|
30
|
+
for (const dep of dependents) {
|
|
31
|
+
if (!affected.has(dep)) {
|
|
32
|
+
affected.add(dep);
|
|
33
|
+
queue.push({ file: dep, depth: depth + 1 });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Also include the file itself
|
|
38
|
+
affected.add(filePath);
|
|
39
|
+
// Find affected routes (routes whose handler file is in the affected set)
|
|
40
|
+
const affectedRoutes = routes.filter((r) => affected.has(r.file));
|
|
41
|
+
// Find affected models (schemas referenced in affected files)
|
|
42
|
+
const affectedModels = [];
|
|
43
|
+
for (const schema of schemas) {
|
|
44
|
+
for (const file of affected) {
|
|
45
|
+
// Check if any route/lib in this file touches this model
|
|
46
|
+
const routesInFile = routes.filter((r) => r.file === file);
|
|
47
|
+
if (routesInFile.some((r) => r.tags.includes("db"))) {
|
|
48
|
+
if (!affectedModels.includes(schema.name)) {
|
|
49
|
+
affectedModels.push(schema.name);
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Find affected middleware
|
|
56
|
+
const affectedMiddleware = middleware
|
|
57
|
+
.filter((m) => affected.has(m.file))
|
|
58
|
+
.map((m) => m.name);
|
|
59
|
+
return {
|
|
60
|
+
file: filePath,
|
|
61
|
+
affectedFiles: Array.from(affected).filter((f) => f !== filePath),
|
|
62
|
+
affectedRoutes,
|
|
63
|
+
affectedModels,
|
|
64
|
+
affectedMiddleware,
|
|
65
|
+
depth: maxDepth,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Multi-file blast radius: given a list of changed files (e.g., from git diff),
|
|
70
|
+
* find the combined blast radius.
|
|
71
|
+
*/
|
|
72
|
+
export function analyzeMultiFileBlastRadius(files, result, maxDepth = 3) {
|
|
73
|
+
const combined = new Set();
|
|
74
|
+
const combinedRoutes = [];
|
|
75
|
+
const combinedModels = new Set();
|
|
76
|
+
const combinedMiddleware = new Set();
|
|
77
|
+
for (const file of files) {
|
|
78
|
+
const br = analyzeBlastRadius(file, result, maxDepth);
|
|
79
|
+
for (const f of br.affectedFiles)
|
|
80
|
+
combined.add(f);
|
|
81
|
+
for (const r of br.affectedRoutes) {
|
|
82
|
+
if (!combinedRoutes.some((cr) => cr.path === r.path && cr.method === r.method)) {
|
|
83
|
+
combinedRoutes.push(r);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
for (const m of br.affectedModels)
|
|
87
|
+
combinedModels.add(m);
|
|
88
|
+
for (const mw of br.affectedMiddleware)
|
|
89
|
+
combinedMiddleware.add(mw);
|
|
90
|
+
}
|
|
91
|
+
// Remove the input files from affected
|
|
92
|
+
for (const file of files)
|
|
93
|
+
combined.delete(file);
|
|
94
|
+
return {
|
|
95
|
+
file: files.join(", "),
|
|
96
|
+
affectedFiles: Array.from(combined),
|
|
97
|
+
affectedRoutes: combinedRoutes,
|
|
98
|
+
affectedModels: Array.from(combinedModels),
|
|
99
|
+
affectedMiddleware: Array.from(combinedMiddleware),
|
|
100
|
+
depth: maxDepth,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
import type { ScanResult } from "../types.js";
|
|
2
2
|
export declare function generateAIConfigs(result: ScanResult, root: string): Promise<string[]>;
|
|
3
|
+
/**
|
|
4
|
+
* Generate a profile-specific config file optimized for a particular AI tool.
|
|
5
|
+
* Includes tool-specific instructions on how to use codesight outputs.
|
|
6
|
+
*/
|
|
7
|
+
export declare function generateProfileConfig(result: ScanResult, root: string, profile: string): Promise<string>;
|
|
@@ -135,3 +135,100 @@ ${context}
|
|
|
135
135
|
}
|
|
136
136
|
return generated;
|
|
137
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Generate a profile-specific config file optimized for a particular AI tool.
|
|
140
|
+
* Includes tool-specific instructions on how to use codesight outputs.
|
|
141
|
+
*/
|
|
142
|
+
export async function generateProfileConfig(result, root, profile) {
|
|
143
|
+
const { project, routes, schemas, graph, config } = result;
|
|
144
|
+
// Build a compact always-load summary (~1-2k tokens)
|
|
145
|
+
const summaryLines = [];
|
|
146
|
+
summaryLines.push(`# ${project.name} — Project Context\n`);
|
|
147
|
+
summaryLines.push(`**Stack:** ${project.frameworks.join(", ") || "generic"} | ${project.orms.join(", ") || "none"} | ${project.language}`);
|
|
148
|
+
if (project.isMonorepo) {
|
|
149
|
+
summaryLines.push(`**Monorepo:** ${project.workspaces.map((w) => w.name).join(", ")}`);
|
|
150
|
+
}
|
|
151
|
+
summaryLines.push(`\n${routes.length} routes | ${schemas.length} models | ${config.envVars.length} env vars | ${graph.edges.length} import links\n`);
|
|
152
|
+
// Top entry points
|
|
153
|
+
if (routes.length > 0) {
|
|
154
|
+
const areas = [...new Set(routes.map((r) => r.path.split("/").slice(0, 3).join("/")))].slice(0, 10);
|
|
155
|
+
summaryLines.push(`**API areas:** ${areas.join(", ")}`);
|
|
156
|
+
}
|
|
157
|
+
// High-impact files
|
|
158
|
+
if (graph.hotFiles.length > 0) {
|
|
159
|
+
summaryLines.push(`\n**High-impact files** (change carefully):`);
|
|
160
|
+
for (const hf of graph.hotFiles.slice(0, 5)) {
|
|
161
|
+
summaryLines.push(`- ${hf.file} (imported by ${hf.importedBy} files)`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Required env
|
|
165
|
+
const required = config.envVars.filter((e) => !e.hasDefault);
|
|
166
|
+
if (required.length > 0) {
|
|
167
|
+
summaryLines.push(`\n**Required env vars:** ${required.map((e) => e.name).join(", ")}`);
|
|
168
|
+
}
|
|
169
|
+
summaryLines.push(`\n---\n`);
|
|
170
|
+
// Profile-specific instructions
|
|
171
|
+
switch (profile) {
|
|
172
|
+
case "claude-code": {
|
|
173
|
+
summaryLines.push(`## Instructions for Claude Code\n`);
|
|
174
|
+
summaryLines.push(`Before exploring the repo, read these files in order:`);
|
|
175
|
+
summaryLines.push(`1. \`.codesight/CODESIGHT.md\` — full context map (routes, schema, components, deps)`);
|
|
176
|
+
summaryLines.push(`2. Use the codesight MCP server for targeted queries:\n`);
|
|
177
|
+
summaryLines.push(` - \`codesight_get_summary\` — quick project overview`);
|
|
178
|
+
summaryLines.push(` - \`codesight_get_routes --prefix /api/users\` — filtered routes`);
|
|
179
|
+
summaryLines.push(` - \`codesight_get_blast_radius --file src/lib/db.ts\` — impact analysis before changes`);
|
|
180
|
+
summaryLines.push(` - \`codesight_get_schema --model users\` — specific model details`);
|
|
181
|
+
summaryLines.push(`\nOnly open specific files after consulting codesight context. This saves ~${result.tokenStats.saved.toLocaleString()} tokens per conversation.`);
|
|
182
|
+
const outPath = join(root, "CLAUDE.md");
|
|
183
|
+
const existing = await fileExists(outPath) ? await readFile(outPath, "utf-8") : "";
|
|
184
|
+
if (existing && existing.includes("codesight")) {
|
|
185
|
+
await writeFile(outPath, existing.replace(/# AI Context.*?(?=\n#|\n$|$)/s, summaryLines.join("\n")));
|
|
186
|
+
}
|
|
187
|
+
else if (existing) {
|
|
188
|
+
await writeFile(outPath, existing + "\n\n" + summaryLines.join("\n"));
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
await writeFile(outPath, summaryLines.join("\n"));
|
|
192
|
+
}
|
|
193
|
+
return "CLAUDE.md";
|
|
194
|
+
}
|
|
195
|
+
case "cursor": {
|
|
196
|
+
summaryLines.push(`## Instructions for Cursor\n`);
|
|
197
|
+
summaryLines.push(`When answering questions about this project:`);
|
|
198
|
+
summaryLines.push(`1. Read \`.codesight/CODESIGHT.md\` first for full project structure`);
|
|
199
|
+
summaryLines.push(`2. Use \`.codesight/routes.md\` to find relevant API handlers`);
|
|
200
|
+
summaryLines.push(`3. Use \`.codesight/schema.md\` to understand data models`);
|
|
201
|
+
summaryLines.push(`4. Use \`.codesight/graph.md\` to check blast radius before changes`);
|
|
202
|
+
summaryLines.push(`\nDo not crawl the file tree — the codesight context map already contains the full project structure.`);
|
|
203
|
+
await writeFile(join(root, ".cursorrules"), summaryLines.join("\n"));
|
|
204
|
+
return ".cursorrules";
|
|
205
|
+
}
|
|
206
|
+
case "codex": {
|
|
207
|
+
summaryLines.push(`## Instructions for OpenAI Codex\n`);
|
|
208
|
+
summaryLines.push(`This project uses codesight for structured context. Read .codesight/CODESIGHT.md before exploring files.`);
|
|
209
|
+
summaryLines.push(`Routes: .codesight/routes.md | Schema: .codesight/schema.md | Dependencies: .codesight/graph.md`);
|
|
210
|
+
await writeFile(join(root, "codex.md"), summaryLines.join("\n"));
|
|
211
|
+
return "codex.md";
|
|
212
|
+
}
|
|
213
|
+
case "copilot": {
|
|
214
|
+
summaryLines.push(`## Instructions for GitHub Copilot\n`);
|
|
215
|
+
summaryLines.push(`Project context is pre-generated in .codesight/. Consult CODESIGHT.md for full architecture.`);
|
|
216
|
+
const ghDir = join(root, ".github");
|
|
217
|
+
await fileExists(ghDir) || await (await import("node:fs/promises")).mkdir(ghDir, { recursive: true });
|
|
218
|
+
await writeFile(join(ghDir, "copilot-instructions.md"), summaryLines.join("\n"));
|
|
219
|
+
return ".github/copilot-instructions.md";
|
|
220
|
+
}
|
|
221
|
+
case "windsurf": {
|
|
222
|
+
summaryLines.push(`## Instructions for Windsurf\n`);
|
|
223
|
+
summaryLines.push(`Read .codesight/CODESIGHT.md for full project map before exploring files.`);
|
|
224
|
+
summaryLines.push(`Use .codesight/routes.md for API structure and .codesight/graph.md for dependency analysis.`);
|
|
225
|
+
await writeFile(join(root, ".windsurfrules"), summaryLines.join("\n"));
|
|
226
|
+
return ".windsurfrules";
|
|
227
|
+
}
|
|
228
|
+
default: {
|
|
229
|
+
// Generic profile — write all configs
|
|
230
|
+
const generated = await generateAIConfigs(result, root);
|
|
231
|
+
return generated.join(", ") || "all configs already exist";
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
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.2.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
|
|
|
@@ -220,6 +222,8 @@ async function main() {
|
|
|
220
222
|
let doOpen = false;
|
|
221
223
|
let doMcp = false;
|
|
222
224
|
let doBenchmark = false;
|
|
225
|
+
let doProfile = "";
|
|
226
|
+
let doBlast = "";
|
|
223
227
|
for (let i = 0; i < args.length; i++) {
|
|
224
228
|
const arg = args[i];
|
|
225
229
|
if ((arg === "-o" || arg === "--output") && args[i + 1]) {
|
|
@@ -253,6 +257,12 @@ async function main() {
|
|
|
253
257
|
else if (arg === "--benchmark") {
|
|
254
258
|
doBenchmark = true;
|
|
255
259
|
}
|
|
260
|
+
else if (arg === "--profile" && args[i + 1]) {
|
|
261
|
+
doProfile = args[++i];
|
|
262
|
+
}
|
|
263
|
+
else if (arg === "--blast" && args[i + 1]) {
|
|
264
|
+
doBlast = args[++i];
|
|
265
|
+
}
|
|
256
266
|
else if (!arg.startsWith("-")) {
|
|
257
267
|
targetDir = resolve(arg);
|
|
258
268
|
}
|
|
@@ -329,6 +339,44 @@ async function main() {
|
|
|
329
339
|
- 1.3x multiplier: AI revisits files during multi-turn exploration
|
|
330
340
|
`);
|
|
331
341
|
}
|
|
342
|
+
// Blast radius analysis
|
|
343
|
+
if (doBlast) {
|
|
344
|
+
const { analyzeBlastRadius } = await import("./detectors/blast-radius.js");
|
|
345
|
+
const br = analyzeBlastRadius(doBlast, result);
|
|
346
|
+
console.log(`\n Blast Radius: ${doBlast}`);
|
|
347
|
+
console.log(` Depth: ${br.depth} hops\n`);
|
|
348
|
+
if (br.affectedFiles.length > 0) {
|
|
349
|
+
console.log(` Affected files (${br.affectedFiles.length}):`);
|
|
350
|
+
for (const f of br.affectedFiles.slice(0, 20)) {
|
|
351
|
+
console.log(` ${f}`);
|
|
352
|
+
}
|
|
353
|
+
if (br.affectedFiles.length > 20)
|
|
354
|
+
console.log(` ... +${br.affectedFiles.length - 20} more`);
|
|
355
|
+
}
|
|
356
|
+
if (br.affectedRoutes.length > 0) {
|
|
357
|
+
console.log(`\n Affected routes (${br.affectedRoutes.length}):`);
|
|
358
|
+
for (const r of br.affectedRoutes) {
|
|
359
|
+
console.log(` ${r.method} ${r.path} — ${r.file}`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (br.affectedModels.length > 0) {
|
|
363
|
+
console.log(`\n Affected models: ${br.affectedModels.join(", ")}`);
|
|
364
|
+
}
|
|
365
|
+
if (br.affectedMiddleware.length > 0) {
|
|
366
|
+
console.log(`\n Affected middleware: ${br.affectedMiddleware.join(", ")}`);
|
|
367
|
+
}
|
|
368
|
+
if (br.affectedFiles.length === 0) {
|
|
369
|
+
console.log(" No downstream dependencies. Minimal blast radius.");
|
|
370
|
+
}
|
|
371
|
+
console.log("");
|
|
372
|
+
}
|
|
373
|
+
// Profile-based AI config generation
|
|
374
|
+
if (doProfile) {
|
|
375
|
+
const { generateProfileConfig } = await import("./generators/ai-config.js");
|
|
376
|
+
process.stdout.write(` Generating ${doProfile} profile...`);
|
|
377
|
+
const file = await generateProfileConfig(result, root, doProfile);
|
|
378
|
+
console.log(` ${file}`);
|
|
379
|
+
}
|
|
332
380
|
// Watch mode (blocks)
|
|
333
381
|
if (doWatch) {
|
|
334
382
|
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
|
@@ -81,6 +81,22 @@ export interface DependencyGraph {
|
|
|
81
81
|
importedBy: number;
|
|
82
82
|
}[];
|
|
83
83
|
}
|
|
84
|
+
export interface BlastRadiusResult {
|
|
85
|
+
file: string;
|
|
86
|
+
affectedFiles: string[];
|
|
87
|
+
affectedRoutes: RouteInfo[];
|
|
88
|
+
affectedModels: string[];
|
|
89
|
+
affectedMiddleware: string[];
|
|
90
|
+
depth: number;
|
|
91
|
+
}
|
|
92
|
+
export interface CodesightConfig {
|
|
93
|
+
disableDetectors?: string[];
|
|
94
|
+
customTags?: Record<string, string[]>;
|
|
95
|
+
maxDepth?: number;
|
|
96
|
+
outputDir?: string;
|
|
97
|
+
profile?: "claude-code" | "cursor" | "codex" | "copilot" | "windsurf" | "generic";
|
|
98
|
+
ignorePatterns?: string[];
|
|
99
|
+
}
|
|
84
100
|
export interface ScanResult {
|
|
85
101
|
project: ProjectInfo;
|
|
86
102
|
routes: RouteInfo[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codesight",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.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": {
|