codesight 1.1.1 → 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 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. 4 ORM parsers. MCP server. One `npx` call.**
5
+ **Zero dependencies. 25+ framework detectors. 8 ORM parsers. 8 MCP tools. Blast radius analysis. One `npx` call.**
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/codesight?style=for-the-badge&logo=npm&color=CB3837)](https://www.npmjs.com/package/codesight)
8
8
  [![npm downloads](https://img.shields.io/npm/dm/codesight?style=for-the-badge&logo=npm&color=blue&label=Monthly%20Downloads)](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 # Also generate CLAUDE.md, .cursorrules, codex.md, AGENTS.md
46
- npx codesight --open # Also open interactive HTML report in browser
47
- npx codesight --mcp # Start as MCP server for Claude Code / Cursor
48
- npx codesight --benchmark # Show detailed token savings breakdown
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 one tool: `codesight_scan`. Your AI calls it whenever it needs to understand the project.
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
 
@@ -254,34 +310,37 @@ Context stays fresh without thinking about it.
254
310
  ## All Options
255
311
 
256
312
  ```bash
257
- npx codesight # Scan current directory
258
- npx codesight ./my-project # Scan specific directory
259
- npx codesight --init # Generate AI config files
260
- npx codesight --open # Open visual HTML report
261
- npx codesight --html # Generate HTML report without opening
262
- npx codesight --mcp # Start MCP server
263
- npx codesight --watch # Watch mode
264
- npx codesight --hook # Install git pre-commit hook
265
- npx codesight --benchmark # Detailed token savings breakdown
266
- npx codesight --json # Output as JSON
267
- npx codesight -o .ai-context # Custom output directory
268
- npx codesight -d 5 # Limit directory depth
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
269
327
  ```
270
328
 
271
329
  ## How It Compares
272
330
 
273
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.
274
332
 
275
- | | codesight | File concatenation tools |
276
- |---|---|---|
277
- | **Output** | Structured routes, schema, components, deps | Raw file contents |
278
- | **Token cost** | ~3,000-5,000 tokens | 50,000-500,000+ tokens |
279
- | **Route detection** | 25+ frameworks auto-detected | None |
280
- | **Schema parsing** | ORM-aware with relations | None |
281
- | **Dependency graph** | Hot file detection | None |
282
- | **AI config generation** | CLAUDE.md, .cursorrules, etc. | None |
283
- | **MCP server** | Built-in | Varies |
284
- | **Dependencies** | Zero | Varies |
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. |
285
344
 
286
345
  ## Contributing
287
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.1.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);
@@ -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
- async function runScan(directory) {
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
- return outputContent.replace(/Saves ~\d[\d,]* tokens/, `Saves ~${tokenStats.saved.toLocaleString()} tokens`);
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.0.0" },
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; // no response for notifications
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
- name: "codesight_scan",
74
- description: "Scans a codebase and returns a complete AI context map including routes, database schema, components, libraries, config, middleware, and dependency graph. Saves thousands of tokens vs manual exploration.",
75
- inputSchema: {
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
- if (toolName === "codesight_scan") {
355
+ const tool = TOOLS.find((t) => t.name === toolName);
356
+ if (tool) {
95
357
  try {
96
- const result = await runScan(args.directory || process.cwd());
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 scanning: ${err.message}` }],
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.1.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": {