codesight 1.0.1 → 1.1.1
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 +78 -9
- package/dist/detectors/contracts.js +7 -0
- package/dist/detectors/graph.js +87 -23
- package/dist/detectors/routes.js +519 -13
- package/dist/formatter.js +20 -7
- package/dist/index.js +37 -1
- package/dist/scanner.js +125 -6
- package/dist/types.d.ts +3 -3
- package/package.json +2 -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.
|
|
5
|
+
**Zero dependencies. 25+ framework detectors. 4 ORM parsers. MCP server. One `npx` call.**
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/codesight)
|
|
8
8
|
[](https://www.npmjs.com/package/codesight)
|
|
@@ -26,9 +26,13 @@
|
|
|
26
26
|
---
|
|
27
27
|
|
|
28
28
|
```
|
|
29
|
-
0 dependencies · Node.js >= 18 · MIT
|
|
29
|
+
0 dependencies · Node.js >= 18 · 27 tests · MIT
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
## Works With
|
|
33
|
+
|
|
34
|
+
**Claude Code, Cursor, GitHub Copilot, OpenAI Codex, Windsurf, Cline, Aider**, and anything that reads markdown.
|
|
35
|
+
|
|
32
36
|
## Install
|
|
33
37
|
|
|
34
38
|
```bash
|
|
@@ -41,6 +45,7 @@ That's it. Run it in any project root. No config, no setup, no API keys.
|
|
|
41
45
|
npx codesight --init # Also generate CLAUDE.md, .cursorrules, codex.md, AGENTS.md
|
|
42
46
|
npx codesight --open # Also open interactive HTML report in browser
|
|
43
47
|
npx codesight --mcp # Start as MCP server for Claude Code / Cursor
|
|
48
|
+
npx codesight --benchmark # Show detailed token savings breakdown
|
|
44
49
|
```
|
|
45
50
|
|
|
46
51
|
## What It Does
|
|
@@ -55,8 +60,6 @@ Exploration cost: ~52,000 tokens (without codesight)
|
|
|
55
60
|
Saved: ~48,800 tokens per conversation
|
|
56
61
|
```
|
|
57
62
|
|
|
58
|
-
Works with **Claude Code, Cursor, GitHub Copilot, OpenAI Codex, Windsurf, Cline**, and anything that reads markdown.
|
|
59
|
-
|
|
60
63
|
## What It Generates
|
|
61
64
|
|
|
62
65
|
```
|
|
@@ -74,13 +77,14 @@ Works with **Claude Code, Cursor, GitHub Copilot, OpenAI Codex, Windsurf, Cline*
|
|
|
74
77
|
|
|
75
78
|
## Routes
|
|
76
79
|
|
|
77
|
-
Not just paths. Methods, URL parameters, what each route touches (auth, database, cache, payments, AI, email, queues), and where the handler lives. Detects routes across
|
|
80
|
+
Not just paths. Methods, URL parameters, what each route touches (auth, database, cache, payments, AI, email, queues), and where the handler lives. Detects routes across 25+ frameworks automatically.
|
|
78
81
|
|
|
79
82
|
```markdown
|
|
80
83
|
- `POST` `/auth/login` [auth, db, email]
|
|
81
84
|
- `GET` `/api/projects/:id/analytics` params(id) [auth, db, cache]
|
|
82
85
|
- `POST` `/api/billing/checkout` [auth, db, payment]
|
|
83
|
-
- `
|
|
86
|
+
- `QUERY` `getUsers` [db] # tRPC procedures
|
|
87
|
+
- `MUTATION` `createProject` [db, ai] # tRPC mutations
|
|
84
88
|
```
|
|
85
89
|
|
|
86
90
|
## Schema
|
|
@@ -123,18 +127,44 @@ Every env var across your codebase, flagged as required or has default, with the
|
|
|
123
127
|
- `PORT` (has default) — apps/api/src/index.ts
|
|
124
128
|
```
|
|
125
129
|
|
|
130
|
+
## Token Benchmark
|
|
131
|
+
|
|
132
|
+
See exactly where your token savings come from:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npx codesight --benchmark
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
Token Savings Breakdown:
|
|
140
|
+
┌──────────────────────────────────────────────────┐
|
|
141
|
+
│ What codesight found │ Exploration cost │
|
|
142
|
+
├──────────────────────────────┼────────────────────┤
|
|
143
|
+
│ 65 routes │ ~26,000 tokens │
|
|
144
|
+
│ 18 schema models │ ~ 5,400 tokens │
|
|
145
|
+
│ 16 components │ ~ 4,000 tokens │
|
|
146
|
+
│ 36 library files │ ~ 7,200 tokens │
|
|
147
|
+
│ 22 env vars │ ~ 2,200 tokens │
|
|
148
|
+
│ 92 files (search overhead) │ ~ 4,000 tokens │
|
|
149
|
+
├──────────────────────────────┼────────────────────┤
|
|
150
|
+
│ codesight output │ ~ 4,041 tokens │
|
|
151
|
+
│ SAVED PER CONVERSATION │ ~64,599 tokens │
|
|
152
|
+
└──────────────────────────────┴────────────────────┘
|
|
153
|
+
```
|
|
154
|
+
|
|
126
155
|
## Supported Stacks
|
|
127
156
|
|
|
128
157
|
| Category | Supported |
|
|
129
158
|
|---|---|
|
|
130
|
-
| **Routes** | Hono, Express, Fastify, Next.js (App + Pages), Koa, FastAPI, Flask, Django, Go (net/http, Gin, Fiber) |
|
|
131
|
-
| **Schema** | Drizzle, Prisma, TypeORM, SQLAlchemy |
|
|
159
|
+
| **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 |
|
|
132
161
|
| **Components** | React, Vue, Svelte (auto-filters shadcn/ui and Radix primitives) |
|
|
133
|
-
| **Libraries** | TypeScript, JavaScript, Python, Go (exports with function signatures) |
|
|
162
|
+
| **Libraries** | TypeScript, JavaScript, Python, Go, Ruby, Elixir, Java, Kotlin, Rust (exports with function signatures) |
|
|
134
163
|
| **Middleware** | Auth, rate limiting, CORS, validation, logging, error handlers |
|
|
135
164
|
| **Dependencies** | Import graph with hot file detection (most imported = highest blast radius) |
|
|
136
165
|
| **Contracts** | URL params, request types, response types from route handlers |
|
|
137
166
|
| **Monorepos** | pnpm, npm, yarn workspaces (cross-workspace detection) |
|
|
167
|
+
| **Languages** | TypeScript, JavaScript, Python, Go, Ruby, Elixir, Java, Kotlin, Rust, PHP |
|
|
138
168
|
|
|
139
169
|
## AI Config Generation
|
|
140
170
|
|
|
@@ -183,6 +213,28 @@ npx codesight --open
|
|
|
183
213
|
|
|
184
214
|
Opens an interactive HTML dashboard in your browser. Routes table with method badges and tags. Schema cards with fields and relations. Dependency hot files with impact bars. Env var audit. Token savings breakdown. Useful for onboarding or just seeing your project from above.
|
|
185
215
|
|
|
216
|
+
## GitHub Action
|
|
217
|
+
|
|
218
|
+
Add to your CI pipeline to keep context fresh on every push:
|
|
219
|
+
|
|
220
|
+
```yaml
|
|
221
|
+
name: codesight
|
|
222
|
+
on: [push]
|
|
223
|
+
jobs:
|
|
224
|
+
scan:
|
|
225
|
+
runs-on: ubuntu-latest
|
|
226
|
+
steps:
|
|
227
|
+
- uses: actions/checkout@v4
|
|
228
|
+
- uses: actions/setup-node@v4
|
|
229
|
+
with:
|
|
230
|
+
node-version: 20
|
|
231
|
+
- run: npm install -g codesight && codesight
|
|
232
|
+
- uses: actions/upload-artifact@v4
|
|
233
|
+
with:
|
|
234
|
+
name: codesight
|
|
235
|
+
path: .codesight/
|
|
236
|
+
```
|
|
237
|
+
|
|
186
238
|
## Watch Mode and Git Hook
|
|
187
239
|
|
|
188
240
|
**Watch mode** re-scans when files change:
|
|
@@ -210,11 +262,27 @@ npx codesight --html # Generate HTML report without opening
|
|
|
210
262
|
npx codesight --mcp # Start MCP server
|
|
211
263
|
npx codesight --watch # Watch mode
|
|
212
264
|
npx codesight --hook # Install git pre-commit hook
|
|
265
|
+
npx codesight --benchmark # Detailed token savings breakdown
|
|
213
266
|
npx codesight --json # Output as JSON
|
|
214
267
|
npx codesight -o .ai-context # Custom output directory
|
|
215
268
|
npx codesight -d 5 # Limit directory depth
|
|
216
269
|
```
|
|
217
270
|
|
|
271
|
+
## How It Compares
|
|
272
|
+
|
|
273
|
+
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
|
+
|
|
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 |
|
|
285
|
+
|
|
218
286
|
## Contributing
|
|
219
287
|
|
|
220
288
|
```bash
|
|
@@ -223,6 +291,7 @@ cd codesight
|
|
|
223
291
|
pnpm install
|
|
224
292
|
pnpm dev # Run locally
|
|
225
293
|
pnpm build # Compile TypeScript
|
|
294
|
+
pnpm test # Run 27 tests
|
|
226
295
|
```
|
|
227
296
|
|
|
228
297
|
PRs welcome. Open an issue first for large changes.
|
|
@@ -36,9 +36,16 @@ export async function enrichRouteContracts(routes, project) {
|
|
|
36
36
|
case "express":
|
|
37
37
|
case "fastify":
|
|
38
38
|
case "koa":
|
|
39
|
+
case "nestjs":
|
|
40
|
+
case "elysia":
|
|
41
|
+
case "adonis":
|
|
42
|
+
case "raw-http":
|
|
39
43
|
enrichTSRoute(route, content);
|
|
40
44
|
break;
|
|
41
45
|
case "next-app":
|
|
46
|
+
case "sveltekit":
|
|
47
|
+
case "remix":
|
|
48
|
+
case "nuxt":
|
|
42
49
|
enrichNextRoute(route, content);
|
|
43
50
|
break;
|
|
44
51
|
case "fastapi":
|
package/dist/detectors/graph.js
CHANGED
|
@@ -3,7 +3,15 @@ import { readFileSafe } from "../scanner.js";
|
|
|
3
3
|
export async function detectDependencyGraph(files, project) {
|
|
4
4
|
const edges = [];
|
|
5
5
|
const importCount = new Map();
|
|
6
|
-
const codeFiles = files.filter((f) => f.match(/\.(ts|tsx|js|jsx|mjs|py|go)$/));
|
|
6
|
+
const codeFiles = files.filter((f) => f.match(/\.(ts|tsx|js|jsx|mjs|py|go|rb|ex|exs|java|kt|rs|php)$/));
|
|
7
|
+
// Build a lookup map for faster resolution: relative path -> true
|
|
8
|
+
const relPathSet = new Set();
|
|
9
|
+
const relPaths = [];
|
|
10
|
+
for (const file of files) {
|
|
11
|
+
const rel = relative(project.root, file);
|
|
12
|
+
relPathSet.add(rel);
|
|
13
|
+
relPaths.push(rel);
|
|
14
|
+
}
|
|
7
15
|
for (const file of codeFiles) {
|
|
8
16
|
const content = await readFileSafe(file);
|
|
9
17
|
if (!content)
|
|
@@ -16,8 +24,20 @@ export async function detectDependencyGraph(files, project) {
|
|
|
16
24
|
else if (ext === ".go") {
|
|
17
25
|
extractGoImports(content, rel, edges, importCount);
|
|
18
26
|
}
|
|
27
|
+
else if (ext === ".rb") {
|
|
28
|
+
extractRubyImports(content, rel, edges, importCount);
|
|
29
|
+
}
|
|
30
|
+
else if (ext === ".ex" || ext === ".exs") {
|
|
31
|
+
extractElixirImports(content, rel, edges, importCount);
|
|
32
|
+
}
|
|
33
|
+
else if (ext === ".java" || ext === ".kt") {
|
|
34
|
+
extractJavaImports(content, rel, edges, importCount, relPaths);
|
|
35
|
+
}
|
|
36
|
+
else if (ext === ".rs") {
|
|
37
|
+
extractRustImports(content, rel, edges, importCount);
|
|
38
|
+
}
|
|
19
39
|
else {
|
|
20
|
-
extractTSImports(content, rel, file, project,
|
|
40
|
+
extractTSImports(content, rel, file, project, relPathSet, edges, importCount);
|
|
21
41
|
}
|
|
22
42
|
}
|
|
23
43
|
// Sort by most imported
|
|
@@ -27,7 +47,7 @@ export async function detectDependencyGraph(files, project) {
|
|
|
27
47
|
.slice(0, 20);
|
|
28
48
|
return { edges, hotFiles };
|
|
29
49
|
}
|
|
30
|
-
function extractTSImports(content, rel, absPath, project,
|
|
50
|
+
function extractTSImports(content, rel, absPath, project, relPathSet, edges, importCount) {
|
|
31
51
|
// Match: import ... from "./path" or import("./path") or require("./path")
|
|
32
52
|
const patterns = [
|
|
33
53
|
/(?:import|export)\s+.*?from\s+['"]([^'"]+)['"]/g,
|
|
@@ -50,8 +70,10 @@ function extractTSImports(content, rel, absPath, project, allFiles, edges, impor
|
|
|
50
70
|
const dir = dirname(absPath);
|
|
51
71
|
resolvedPath = relative(project.root, resolve(dir, importPath));
|
|
52
72
|
}
|
|
53
|
-
// Strip extension
|
|
54
|
-
|
|
73
|
+
// Strip .js/.mjs extension that TypeScript adds for ESM compatibility
|
|
74
|
+
// e.g., import { foo } from "./bar.js" actually refers to ./bar.ts
|
|
75
|
+
const stripped = resolvedPath.replace(/\.(js|mjs|cjs)$/, "");
|
|
76
|
+
const normalized = normalizeImportPath(stripped, relPathSet);
|
|
55
77
|
if (normalized && normalized !== rel) {
|
|
56
78
|
edges.push({ from: rel, to: normalized });
|
|
57
79
|
importCount.set(normalized, (importCount.get(normalized) || 0) + 1);
|
|
@@ -70,11 +92,9 @@ function extractPythonImports(content, rel, edges, importCount) {
|
|
|
70
92
|
}
|
|
71
93
|
}
|
|
72
94
|
function extractGoImports(content, rel, edges, importCount) {
|
|
73
|
-
// Go doesn't have relative imports in the same way, but we can track internal package imports
|
|
74
95
|
const importBlock = content.match(/import\s*\(([\s\S]*?)\)/);
|
|
75
96
|
if (!importBlock)
|
|
76
97
|
return;
|
|
77
|
-
// Look for internal package paths (not standard library)
|
|
78
98
|
const lines = importBlock[1].split("\n");
|
|
79
99
|
for (const line of lines) {
|
|
80
100
|
const pathMatch = line.match(/["']([^"']+)["']/);
|
|
@@ -85,29 +105,73 @@ function extractGoImports(content, rel, edges, importCount) {
|
|
|
85
105
|
}
|
|
86
106
|
}
|
|
87
107
|
}
|
|
88
|
-
function
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
108
|
+
function extractRubyImports(content, rel, edges, importCount) {
|
|
109
|
+
// require_relative "./path"
|
|
110
|
+
const pattern = /require_relative\s+['"]([^'"]+)['"]/g;
|
|
111
|
+
let match;
|
|
112
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
113
|
+
const target = match[1].replace(/^\.\//, "") + ".rb";
|
|
114
|
+
edges.push({ from: rel, to: target });
|
|
115
|
+
importCount.set(target, (importCount.get(target) || 0) + 1);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function extractElixirImports(content, rel, edges, importCount) {
|
|
119
|
+
// alias MyApp.Accounts.User
|
|
120
|
+
const pattern = /(?:alias|import|use)\s+([\w.]+)/g;
|
|
121
|
+
let match;
|
|
122
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
123
|
+
const mod = match[1];
|
|
124
|
+
// Convert module path to potential file: MyApp.Accounts.User -> lib/my_app/accounts/user.ex
|
|
125
|
+
if (mod.includes(".") && !mod.startsWith("Ecto") && !mod.startsWith("Phoenix") && !mod.startsWith("Plug")) {
|
|
126
|
+
const target = "lib/" + mod.split(".").map(s => s.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "")).join("/") + ".ex";
|
|
127
|
+
edges.push({ from: rel, to: target });
|
|
128
|
+
importCount.set(target, (importCount.get(target) || 0) + 1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function extractJavaImports(content, rel, edges, importCount, relPaths) {
|
|
133
|
+
// import com.myapp.service.UserService;
|
|
134
|
+
const pattern = /^import\s+([\w.]+);/gm;
|
|
135
|
+
let match;
|
|
136
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
137
|
+
const imp = match[1];
|
|
138
|
+
// Skip standard library and common third-party
|
|
139
|
+
if (imp.startsWith("java.") || imp.startsWith("javax.") || imp.startsWith("org.springframework") || imp.startsWith("org.apache"))
|
|
140
|
+
continue;
|
|
141
|
+
// Convert to path pattern: com.myapp.service.UserService -> UserService
|
|
142
|
+
const className = imp.split(".").pop();
|
|
143
|
+
const found = relPaths.find(p => p.endsWith(`/${className}.java`) || p.endsWith(`/${className}.kt`));
|
|
144
|
+
if (found && found !== rel) {
|
|
145
|
+
edges.push({ from: rel, to: found });
|
|
146
|
+
importCount.set(found, (importCount.get(found) || 0) + 1);
|
|
147
|
+
}
|
|
94
148
|
}
|
|
149
|
+
}
|
|
150
|
+
function extractRustImports(content, rel, edges, importCount) {
|
|
151
|
+
// mod my_module; or use crate::my_module::something;
|
|
152
|
+
const modPattern = /^mod\s+(\w+)\s*;/gm;
|
|
153
|
+
let match;
|
|
154
|
+
while ((match = modPattern.exec(content)) !== null) {
|
|
155
|
+
const dir = dirname(rel);
|
|
156
|
+
const target = dir === "." ? `${match[1]}.rs` : `${dir}/${match[1]}.rs`;
|
|
157
|
+
edges.push({ from: rel, to: target });
|
|
158
|
+
importCount.set(target, (importCount.get(target) || 0) + 1);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function normalizeImportPath(importPath, relPathSet) {
|
|
162
|
+
// Try exact match first
|
|
163
|
+
if (relPathSet.has(importPath))
|
|
164
|
+
return importPath;
|
|
95
165
|
// Try with extensions
|
|
96
166
|
const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs"];
|
|
97
167
|
for (const ext of extensions) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (rel === importPath + ext)
|
|
101
|
-
return rel;
|
|
102
|
-
}
|
|
168
|
+
if (relPathSet.has(importPath + ext))
|
|
169
|
+
return importPath + ext;
|
|
103
170
|
}
|
|
104
171
|
// Try index files
|
|
105
172
|
for (const ext of extensions) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (rel === importPath + "/index" + ext)
|
|
109
|
-
return rel;
|
|
110
|
-
}
|
|
173
|
+
if (relPathSet.has(importPath + "/index" + ext))
|
|
174
|
+
return importPath + "/index" + ext;
|
|
111
175
|
}
|
|
112
176
|
return null;
|
|
113
177
|
}
|
package/dist/detectors/routes.js
CHANGED
|
@@ -42,6 +42,27 @@ export async function detectRoutes(files, project) {
|
|
|
42
42
|
case "koa":
|
|
43
43
|
routes.push(...(await detectKoaRoutes(files, project)));
|
|
44
44
|
break;
|
|
45
|
+
case "nestjs":
|
|
46
|
+
routes.push(...(await detectNestJSRoutes(files, project)));
|
|
47
|
+
break;
|
|
48
|
+
case "elysia":
|
|
49
|
+
routes.push(...(await detectElysiaRoutes(files, project)));
|
|
50
|
+
break;
|
|
51
|
+
case "adonis":
|
|
52
|
+
routes.push(...(await detectAdonisRoutes(files, project)));
|
|
53
|
+
break;
|
|
54
|
+
case "trpc":
|
|
55
|
+
routes.push(...(await detectTRPCRoutes(files, project)));
|
|
56
|
+
break;
|
|
57
|
+
case "sveltekit":
|
|
58
|
+
routes.push(...(await detectSvelteKitRoutes(files, project)));
|
|
59
|
+
break;
|
|
60
|
+
case "remix":
|
|
61
|
+
routes.push(...(await detectRemixRoutes(files, project)));
|
|
62
|
+
break;
|
|
63
|
+
case "nuxt":
|
|
64
|
+
routes.push(...(await detectNuxtRoutes(files, project)));
|
|
65
|
+
break;
|
|
45
66
|
case "fastapi":
|
|
46
67
|
routes.push(...(await detectFastAPIRoutes(files, project)));
|
|
47
68
|
break;
|
|
@@ -54,8 +75,26 @@ export async function detectRoutes(files, project) {
|
|
|
54
75
|
case "gin":
|
|
55
76
|
case "go-net-http":
|
|
56
77
|
case "fiber":
|
|
78
|
+
case "echo":
|
|
79
|
+
case "chi":
|
|
57
80
|
routes.push(...(await detectGoRoutes(files, project, fw)));
|
|
58
81
|
break;
|
|
82
|
+
case "rails":
|
|
83
|
+
routes.push(...(await detectRailsRoutes(files, project)));
|
|
84
|
+
break;
|
|
85
|
+
case "phoenix":
|
|
86
|
+
routes.push(...(await detectPhoenixRoutes(files, project)));
|
|
87
|
+
break;
|
|
88
|
+
case "spring":
|
|
89
|
+
routes.push(...(await detectSpringRoutes(files, project)));
|
|
90
|
+
break;
|
|
91
|
+
case "actix":
|
|
92
|
+
case "axum":
|
|
93
|
+
routes.push(...(await detectRustRoutes(files, project, fw)));
|
|
94
|
+
break;
|
|
95
|
+
case "raw-http":
|
|
96
|
+
routes.push(...(await detectRawHttpRoutes(files, project)));
|
|
97
|
+
break;
|
|
59
98
|
}
|
|
60
99
|
}
|
|
61
100
|
return routes;
|
|
@@ -67,7 +106,6 @@ async function detectNextAppRoutes(files, project) {
|
|
|
67
106
|
for (const file of routeFiles) {
|
|
68
107
|
const content = await readFileSafe(file);
|
|
69
108
|
const rel = relative(project.root, file);
|
|
70
|
-
// Extract API path from file path
|
|
71
109
|
const pathMatch = rel.match(/(?:src\/)?app(.*)\/route\./);
|
|
72
110
|
const apiPath = pathMatch ? pathMatch[1] || "/" : "/";
|
|
73
111
|
for (const method of HTTP_METHODS) {
|
|
@@ -95,7 +133,6 @@ async function detectNextPagesApi(files, project) {
|
|
|
95
133
|
const pathMatch = rel.match(/(?:src\/)?pages(\/api\/.*)\.(?:ts|js|tsx|jsx)$/);
|
|
96
134
|
let apiPath = pathMatch ? pathMatch[1] : "/api";
|
|
97
135
|
apiPath = apiPath.replace(/\/index$/, "").replace(/\[([^\]]+)\]/g, ":$1");
|
|
98
|
-
// Detect methods from handler
|
|
99
136
|
const methods = [];
|
|
100
137
|
for (const method of HTTP_METHODS) {
|
|
101
138
|
if (content.includes(`req.method === '${method}'`) || content.includes(`req.method === "${method}"`)) {
|
|
@@ -125,12 +162,10 @@ async function detectHonoRoutes(files, project) {
|
|
|
125
162
|
if (!content.includes("hono") && !content.includes("Hono"))
|
|
126
163
|
continue;
|
|
127
164
|
const rel = relative(project.root, file);
|
|
128
|
-
// Match: app.get("/path", ...), router.post("/path", ...), .route("/base", ...)
|
|
129
165
|
const routePattern = /\.\s*(get|post|put|patch|delete|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
|
|
130
166
|
let match;
|
|
131
167
|
while ((match = routePattern.exec(content)) !== null) {
|
|
132
168
|
const path = match[2];
|
|
133
|
-
// Skip non-path strings (middleware keys like "user", "userId", etc.)
|
|
134
169
|
if (!path.startsWith("/") && !path.startsWith(":"))
|
|
135
170
|
continue;
|
|
136
171
|
routes.push({
|
|
@@ -153,7 +188,6 @@ async function detectExpressRoutes(files, project) {
|
|
|
153
188
|
if (!content.includes("express") && !content.includes("Router"))
|
|
154
189
|
continue;
|
|
155
190
|
const rel = relative(project.root, file);
|
|
156
|
-
// Match: app.get("/path", ...), router.post("/path", ...)
|
|
157
191
|
const routePattern = /(?:app|router|server)\s*\.\s*(get|post|put|patch|delete|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
|
|
158
192
|
let match;
|
|
159
193
|
while ((match = routePattern.exec(content)) !== null) {
|
|
@@ -177,7 +211,6 @@ async function detectFastifyRoutes(files, project) {
|
|
|
177
211
|
if (!content.includes("fastify"))
|
|
178
212
|
continue;
|
|
179
213
|
const rel = relative(project.root, file);
|
|
180
|
-
// Match: fastify.get("/path", ...) or server.route({ method: 'GET', url: '/path' })
|
|
181
214
|
const routePattern = /(?:fastify|server|app)\s*\.\s*(get|post|put|patch|delete|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
|
|
182
215
|
let match;
|
|
183
216
|
while ((match = routePattern.exec(content)) !== null) {
|
|
@@ -226,6 +259,233 @@ async function detectKoaRoutes(files, project) {
|
|
|
226
259
|
}
|
|
227
260
|
return routes;
|
|
228
261
|
}
|
|
262
|
+
// --- NestJS ---
|
|
263
|
+
async function detectNestJSRoutes(files, project) {
|
|
264
|
+
const tsFiles = files.filter((f) => f.match(/\.(ts|js)$/));
|
|
265
|
+
const routes = [];
|
|
266
|
+
for (const file of tsFiles) {
|
|
267
|
+
const content = await readFileSafe(file);
|
|
268
|
+
if (!content.includes("@Controller") && !content.includes("@Get") && !content.includes("@Post"))
|
|
269
|
+
continue;
|
|
270
|
+
const rel = relative(project.root, file);
|
|
271
|
+
// Extract controller base path: @Controller('users') or @Controller('/users')
|
|
272
|
+
const controllerMatch = content.match(/@Controller\s*\(\s*['"`]([^'"`]*)['"`]\s*\)/);
|
|
273
|
+
const basePath = controllerMatch ? "/" + controllerMatch[1].replace(/^\//, "") : "";
|
|
274
|
+
// Match method decorators: @Get(), @Post('/create'), @Put(':id'), etc.
|
|
275
|
+
const decoratorPattern = /@(Get|Post|Put|Patch|Delete|Options|Head|All)\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/gi;
|
|
276
|
+
let match;
|
|
277
|
+
while ((match = decoratorPattern.exec(content)) !== null) {
|
|
278
|
+
const method = match[1].toUpperCase();
|
|
279
|
+
const subPath = match[2] || "";
|
|
280
|
+
const fullPath = basePath + (subPath ? "/" + subPath.replace(/^\//, "") : "") || "/";
|
|
281
|
+
routes.push({
|
|
282
|
+
method,
|
|
283
|
+
path: fullPath,
|
|
284
|
+
file: rel,
|
|
285
|
+
tags: detectTags(content),
|
|
286
|
+
framework: "nestjs",
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return routes;
|
|
291
|
+
}
|
|
292
|
+
// --- Elysia (Bun) ---
|
|
293
|
+
async function detectElysiaRoutes(files, project) {
|
|
294
|
+
const tsFiles = files.filter((f) => f.match(/\.(ts|js|mjs)$/));
|
|
295
|
+
const routes = [];
|
|
296
|
+
for (const file of tsFiles) {
|
|
297
|
+
const content = await readFileSafe(file);
|
|
298
|
+
if (!content.includes("elysia") && !content.includes("Elysia"))
|
|
299
|
+
continue;
|
|
300
|
+
const rel = relative(project.root, file);
|
|
301
|
+
const routePattern = /\.\s*(get|post|put|patch|delete|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
|
|
302
|
+
let match;
|
|
303
|
+
while ((match = routePattern.exec(content)) !== null) {
|
|
304
|
+
const path = match[2];
|
|
305
|
+
if (!path.startsWith("/") && !path.startsWith(":"))
|
|
306
|
+
continue;
|
|
307
|
+
routes.push({
|
|
308
|
+
method: match[1].toUpperCase(),
|
|
309
|
+
path,
|
|
310
|
+
file: rel,
|
|
311
|
+
tags: detectTags(content),
|
|
312
|
+
framework: "elysia",
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return routes;
|
|
317
|
+
}
|
|
318
|
+
// --- AdonisJS ---
|
|
319
|
+
async function detectAdonisRoutes(files, project) {
|
|
320
|
+
// AdonisJS uses start/routes.ts with Route.get(), Route.post(), router.get(), etc.
|
|
321
|
+
const routeFiles = files.filter((f) => f.match(/routes\.(ts|js)$/) || f.match(/\/routes\/.*\.(ts|js)$/));
|
|
322
|
+
const routes = [];
|
|
323
|
+
for (const file of routeFiles) {
|
|
324
|
+
const content = await readFileSafe(file);
|
|
325
|
+
const rel = relative(project.root, file);
|
|
326
|
+
const routePattern = /(?:Route|router)\s*\.\s*(get|post|put|patch|delete|any)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
|
|
327
|
+
let match;
|
|
328
|
+
while ((match = routePattern.exec(content)) !== null) {
|
|
329
|
+
routes.push({
|
|
330
|
+
method: match[1].toUpperCase() === "ANY" ? "ALL" : match[1].toUpperCase(),
|
|
331
|
+
path: match[2],
|
|
332
|
+
file: rel,
|
|
333
|
+
tags: detectTags(content),
|
|
334
|
+
framework: "adonis",
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return routes;
|
|
339
|
+
}
|
|
340
|
+
// --- tRPC ---
|
|
341
|
+
async function detectTRPCRoutes(files, project) {
|
|
342
|
+
const tsFiles = files.filter((f) => f.match(/\.(ts|js)$/));
|
|
343
|
+
const routes = [];
|
|
344
|
+
for (const file of tsFiles) {
|
|
345
|
+
const content = await readFileSafe(file);
|
|
346
|
+
if (!content.includes("Procedure") && !content.includes("procedure") && !content.includes("router"))
|
|
347
|
+
continue;
|
|
348
|
+
if (!content.includes("trpc") && !content.includes("TRPC") && !content.includes("createTRPCRouter") && !content.includes("publicProcedure") && !content.includes("protectedProcedure"))
|
|
349
|
+
continue;
|
|
350
|
+
const rel = relative(project.root, file);
|
|
351
|
+
// Match tRPC procedure definitions like:
|
|
352
|
+
// list: publicProcedure.query(...)
|
|
353
|
+
// create: publicProcedure.input(schema).mutation(...)
|
|
354
|
+
// getById: t.procedure.input(z.object({...})).query(...)
|
|
355
|
+
// Strategy: find lines with "query(" or "mutation(" and extract the procedure name
|
|
356
|
+
const lines = content.split("\n");
|
|
357
|
+
for (const line of lines) {
|
|
358
|
+
const queryMatch = line.match(/^\s*(\w+)\s*:\s*.*\.(query)\s*\(/);
|
|
359
|
+
const mutationMatch = line.match(/^\s*(\w+)\s*:\s*.*\.(mutation)\s*\(/);
|
|
360
|
+
const m = queryMatch || mutationMatch;
|
|
361
|
+
if (m) {
|
|
362
|
+
const procName = m[1];
|
|
363
|
+
const isQuery = m[2] === "query";
|
|
364
|
+
if (!routes.some((r) => r.path === procName && r.file === rel)) {
|
|
365
|
+
routes.push({
|
|
366
|
+
method: isQuery ? "QUERY" : "MUTATION",
|
|
367
|
+
path: procName,
|
|
368
|
+
file: rel,
|
|
369
|
+
tags: detectTags(content),
|
|
370
|
+
framework: "trpc",
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return routes;
|
|
377
|
+
}
|
|
378
|
+
// --- SvelteKit ---
|
|
379
|
+
async function detectSvelteKitRoutes(files, project) {
|
|
380
|
+
// SvelteKit API routes: src/routes/**/+server.ts
|
|
381
|
+
const routeFiles = files.filter((f) => f.match(/\/routes\/.*\+server\.(ts|js)$/));
|
|
382
|
+
const routes = [];
|
|
383
|
+
for (const file of routeFiles) {
|
|
384
|
+
const content = await readFileSafe(file);
|
|
385
|
+
const rel = relative(project.root, file);
|
|
386
|
+
// Extract path from file structure: src/routes/api/users/+server.ts -> /api/users
|
|
387
|
+
const pathMatch = rel.match(/(?:src\/)?routes(.*)\/\+server\./);
|
|
388
|
+
let apiPath = pathMatch ? pathMatch[1] || "/" : "/";
|
|
389
|
+
// Convert [param] to :param
|
|
390
|
+
apiPath = apiPath.replace(/\[([^\]]+)\]/g, ":$1");
|
|
391
|
+
for (const method of HTTP_METHODS) {
|
|
392
|
+
const pattern = new RegExp(`export\\s+(?:async\\s+)?function\\s+${method}\\b`);
|
|
393
|
+
if (pattern.test(content)) {
|
|
394
|
+
routes.push({
|
|
395
|
+
method,
|
|
396
|
+
path: apiPath,
|
|
397
|
+
file: rel,
|
|
398
|
+
tags: detectTags(content),
|
|
399
|
+
framework: "sveltekit",
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
// Also detect: export const GET = ...
|
|
404
|
+
for (const method of HTTP_METHODS) {
|
|
405
|
+
const constPattern = new RegExp(`export\\s+const\\s+${method}\\s*[=:]`);
|
|
406
|
+
if (constPattern.test(content) && !routes.some((r) => r.method === method && r.path === apiPath)) {
|
|
407
|
+
routes.push({
|
|
408
|
+
method,
|
|
409
|
+
path: apiPath,
|
|
410
|
+
file: rel,
|
|
411
|
+
tags: detectTags(content),
|
|
412
|
+
framework: "sveltekit",
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return routes;
|
|
418
|
+
}
|
|
419
|
+
// --- Remix ---
|
|
420
|
+
async function detectRemixRoutes(files, project) {
|
|
421
|
+
// Remix routes: app/routes/*.tsx with loader/action exports
|
|
422
|
+
const routeFiles = files.filter((f) => f.match(/\/routes\/.*\.(ts|tsx|js|jsx)$/));
|
|
423
|
+
const routes = [];
|
|
424
|
+
for (const file of routeFiles) {
|
|
425
|
+
const content = await readFileSafe(file);
|
|
426
|
+
const rel = relative(project.root, file);
|
|
427
|
+
// Convert filename to route path
|
|
428
|
+
const pathMatch = rel.match(/(?:app\/)?routes\/(.+)\.(ts|tsx|js|jsx)$/);
|
|
429
|
+
if (!pathMatch)
|
|
430
|
+
continue;
|
|
431
|
+
let routePath = "/" + pathMatch[1]
|
|
432
|
+
.replace(/\./g, "/") // dots become path segments
|
|
433
|
+
.replace(/_index$/, "") // _index -> root of parent
|
|
434
|
+
.replace(/\$/g, ":") // $param -> :param
|
|
435
|
+
.replace(/\[([^\]]+)\]/g, ":$1");
|
|
436
|
+
if (content.match(/export\s+(?:async\s+)?function\s+loader\b/) || content.match(/export\s+const\s+loader\b/)) {
|
|
437
|
+
routes.push({
|
|
438
|
+
method: "GET",
|
|
439
|
+
path: routePath,
|
|
440
|
+
file: rel,
|
|
441
|
+
tags: detectTags(content),
|
|
442
|
+
framework: "remix",
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
if (content.match(/export\s+(?:async\s+)?function\s+action\b/) || content.match(/export\s+const\s+action\b/)) {
|
|
446
|
+
routes.push({
|
|
447
|
+
method: "POST",
|
|
448
|
+
path: routePath,
|
|
449
|
+
file: rel,
|
|
450
|
+
tags: detectTags(content),
|
|
451
|
+
framework: "remix",
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return routes;
|
|
456
|
+
}
|
|
457
|
+
// --- Nuxt ---
|
|
458
|
+
async function detectNuxtRoutes(files, project) {
|
|
459
|
+
// Nuxt server routes: server/api/**/*.ts
|
|
460
|
+
const routeFiles = files.filter((f) => f.match(/\/server\/(?:api|routes)\/.*\.(ts|js|mjs)$/));
|
|
461
|
+
const routes = [];
|
|
462
|
+
for (const file of routeFiles) {
|
|
463
|
+
const content = await readFileSafe(file);
|
|
464
|
+
const rel = relative(project.root, file);
|
|
465
|
+
// Extract path from file structure
|
|
466
|
+
const pathMatch = rel.match(/server\/((?:api|routes)\/.+)\.(ts|js|mjs)$/);
|
|
467
|
+
if (!pathMatch)
|
|
468
|
+
continue;
|
|
469
|
+
let routePath = "/" + pathMatch[1]
|
|
470
|
+
.replace(/\/index$/, "")
|
|
471
|
+
.replace(/\[([^\]]+)\]/g, ":$1");
|
|
472
|
+
// Detect method from filename (e.g., users.get.ts, users.post.ts)
|
|
473
|
+
const methodFromFile = basename(file).match(/\.(get|post|put|patch|delete)\.(ts|js|mjs)$/);
|
|
474
|
+
const method = methodFromFile ? methodFromFile[1].toUpperCase() : "ALL";
|
|
475
|
+
// Clean path: remove method suffix from path
|
|
476
|
+
if (methodFromFile) {
|
|
477
|
+
routePath = routePath.replace(new RegExp(`\\.${methodFromFile[1]}$`), "");
|
|
478
|
+
}
|
|
479
|
+
routes.push({
|
|
480
|
+
method,
|
|
481
|
+
path: routePath,
|
|
482
|
+
file: rel,
|
|
483
|
+
tags: detectTags(content),
|
|
484
|
+
framework: "nuxt",
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
return routes;
|
|
488
|
+
}
|
|
229
489
|
// --- FastAPI ---
|
|
230
490
|
async function detectFastAPIRoutes(files, project) {
|
|
231
491
|
const pyFiles = files.filter((f) => f.endsWith(".py"));
|
|
@@ -235,7 +495,6 @@ async function detectFastAPIRoutes(files, project) {
|
|
|
235
495
|
if (!content.includes("fastapi") && !content.includes("FastAPI") && !content.includes("APIRouter"))
|
|
236
496
|
continue;
|
|
237
497
|
const rel = relative(project.root, file);
|
|
238
|
-
// Match: @app.get("/path") or @router.post("/path") or @api_router.get("/path")
|
|
239
498
|
const routePattern = /@\w+\s*\.\s*(get|post|put|patch|delete|options)\s*\(\s*['"]([^'"]+)['"]/gi;
|
|
240
499
|
let match;
|
|
241
500
|
while ((match = routePattern.exec(content)) !== null) {
|
|
@@ -259,8 +518,7 @@ async function detectFlaskRoutes(files, project) {
|
|
|
259
518
|
if (!content.includes("flask") && !content.includes("Flask") && !content.includes("Blueprint"))
|
|
260
519
|
continue;
|
|
261
520
|
const rel = relative(project.root, file);
|
|
262
|
-
|
|
263
|
-
const routePattern = /@(?:app|bp|blueprint)\s*\.\s*route\s*\(\s*['"]([^'"]+)['"](?:\s*,\s*methods\s*=\s*\[([^\]]+)\])?\s*\)/gi;
|
|
521
|
+
const routePattern = /@(?:app|bp|blueprint|\w+)\s*\.\s*route\s*\(\s*['"]([^'"]+)['"](?:\s*,\s*methods\s*=\s*\[([^\]]+)\])?\s*\)/gi;
|
|
264
522
|
let match;
|
|
265
523
|
while ((match = routePattern.exec(content)) !== null) {
|
|
266
524
|
const path = match[1];
|
|
@@ -287,7 +545,7 @@ async function detectDjangoRoutes(files, project) {
|
|
|
287
545
|
for (const file of pyFiles) {
|
|
288
546
|
const content = await readFileSafe(file);
|
|
289
547
|
const rel = relative(project.root, file);
|
|
290
|
-
//
|
|
548
|
+
// path("api/v1/users/", views.UserView.as_view())
|
|
291
549
|
const pathPattern = /path\s*\(\s*['"]([^'"]*)['"]\s*,/g;
|
|
292
550
|
let match;
|
|
293
551
|
while ((match = pathPattern.exec(content)) !== null) {
|
|
@@ -302,7 +560,7 @@ async function detectDjangoRoutes(files, project) {
|
|
|
302
560
|
}
|
|
303
561
|
return routes;
|
|
304
562
|
}
|
|
305
|
-
// --- Go ---
|
|
563
|
+
// --- Go (net/http, Gin, Fiber, Echo, Chi) ---
|
|
306
564
|
async function detectGoRoutes(files, project, fw) {
|
|
307
565
|
const goFiles = files.filter((f) => f.endsWith(".go"));
|
|
308
566
|
const routes = [];
|
|
@@ -310,7 +568,6 @@ async function detectGoRoutes(files, project, fw) {
|
|
|
310
568
|
const content = await readFileSafe(file);
|
|
311
569
|
const rel = relative(project.root, file);
|
|
312
570
|
if (fw === "gin") {
|
|
313
|
-
// Match: r.GET("/path", handler)
|
|
314
571
|
const pattern = /\.\s*(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\s*\(\s*["']([^"']+)["']/g;
|
|
315
572
|
let match;
|
|
316
573
|
while ((match = pattern.exec(content)) !== null) {
|
|
@@ -324,7 +581,34 @@ async function detectGoRoutes(files, project, fw) {
|
|
|
324
581
|
}
|
|
325
582
|
}
|
|
326
583
|
else if (fw === "fiber") {
|
|
327
|
-
|
|
584
|
+
const pattern = /\.\s*(Get|Post|Put|Patch|Delete|Options|Head)\s*\(\s*["']([^"']+)["']/g;
|
|
585
|
+
let match;
|
|
586
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
587
|
+
routes.push({
|
|
588
|
+
method: match[1].toUpperCase(),
|
|
589
|
+
path: match[2],
|
|
590
|
+
file: rel,
|
|
591
|
+
tags: detectTags(content),
|
|
592
|
+
framework: fw,
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
else if (fw === "echo") {
|
|
597
|
+
// e.GET("/path", handler) or g.POST("/path", handler)
|
|
598
|
+
const pattern = /\.\s*(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\s*\(\s*["']([^"']+)["']/g;
|
|
599
|
+
let match;
|
|
600
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
601
|
+
routes.push({
|
|
602
|
+
method: match[1],
|
|
603
|
+
path: match[2],
|
|
604
|
+
file: rel,
|
|
605
|
+
tags: detectTags(content),
|
|
606
|
+
framework: fw,
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
else if (fw === "chi") {
|
|
611
|
+
// r.Get("/path", handler), r.Post("/path", handler)
|
|
328
612
|
const pattern = /\.\s*(Get|Post|Put|Patch|Delete|Options|Head)\s*\(\s*["']([^"']+)["']/g;
|
|
329
613
|
let match;
|
|
330
614
|
while ((match = pattern.exec(content)) !== null) {
|
|
@@ -354,3 +638,225 @@ async function detectGoRoutes(files, project, fw) {
|
|
|
354
638
|
}
|
|
355
639
|
return routes;
|
|
356
640
|
}
|
|
641
|
+
// --- Rails ---
|
|
642
|
+
async function detectRailsRoutes(files, project) {
|
|
643
|
+
const routeFiles = files.filter((f) => f.match(/routes\.rb$/));
|
|
644
|
+
const routes = [];
|
|
645
|
+
for (const file of routeFiles) {
|
|
646
|
+
const content = await readFileSafe(file);
|
|
647
|
+
const rel = relative(project.root, file);
|
|
648
|
+
// get '/users', to: 'users#index'
|
|
649
|
+
const routePattern = /\b(get|post|put|patch|delete)\s+['"]([^'"]+)['"]/gi;
|
|
650
|
+
let match;
|
|
651
|
+
while ((match = routePattern.exec(content)) !== null) {
|
|
652
|
+
routes.push({
|
|
653
|
+
method: match[1].toUpperCase(),
|
|
654
|
+
path: match[2],
|
|
655
|
+
file: rel,
|
|
656
|
+
tags: detectTags(content),
|
|
657
|
+
framework: "rails",
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
// resources :users (generates RESTful routes)
|
|
661
|
+
const resourcePattern = /resources?\s+:(\w+)/g;
|
|
662
|
+
while ((match = resourcePattern.exec(content)) !== null) {
|
|
663
|
+
const name = match[1];
|
|
664
|
+
for (const [method, suffix] of [
|
|
665
|
+
["GET", ""], ["GET", "/:id"], ["POST", ""],
|
|
666
|
+
["PUT", "/:id"], ["PATCH", "/:id"], ["DELETE", "/:id"],
|
|
667
|
+
]) {
|
|
668
|
+
routes.push({
|
|
669
|
+
method,
|
|
670
|
+
path: `/${name}${suffix}`,
|
|
671
|
+
file: rel,
|
|
672
|
+
tags: detectTags(content),
|
|
673
|
+
framework: "rails",
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return routes;
|
|
679
|
+
}
|
|
680
|
+
// --- Phoenix (Elixir) ---
|
|
681
|
+
async function detectPhoenixRoutes(files, project) {
|
|
682
|
+
const routeFiles = files.filter((f) => f.match(/router\.ex$/));
|
|
683
|
+
const routes = [];
|
|
684
|
+
for (const file of routeFiles) {
|
|
685
|
+
const content = await readFileSafe(file);
|
|
686
|
+
const rel = relative(project.root, file);
|
|
687
|
+
// get "/users", UserController, :index
|
|
688
|
+
const routePattern = /\b(get|post|put|patch|delete)\s+["']([^"']+)["']/gi;
|
|
689
|
+
let match;
|
|
690
|
+
while ((match = routePattern.exec(content)) !== null) {
|
|
691
|
+
routes.push({
|
|
692
|
+
method: match[1].toUpperCase(),
|
|
693
|
+
path: match[2],
|
|
694
|
+
file: rel,
|
|
695
|
+
tags: detectTags(content),
|
|
696
|
+
framework: "phoenix",
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
// resources "/users", UserController
|
|
700
|
+
const resourcePattern = /resources\s+["']([^"']+)["']/g;
|
|
701
|
+
while ((match = resourcePattern.exec(content)) !== null) {
|
|
702
|
+
const basePath = match[1];
|
|
703
|
+
for (const [method, suffix] of [
|
|
704
|
+
["GET", ""], ["GET", "/:id"], ["POST", ""],
|
|
705
|
+
["PUT", "/:id"], ["PATCH", "/:id"], ["DELETE", "/:id"],
|
|
706
|
+
]) {
|
|
707
|
+
routes.push({
|
|
708
|
+
method,
|
|
709
|
+
path: `${basePath}${suffix}`,
|
|
710
|
+
file: rel,
|
|
711
|
+
tags: detectTags(content),
|
|
712
|
+
framework: "phoenix",
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return routes;
|
|
718
|
+
}
|
|
719
|
+
// --- Spring Boot (Java/Kotlin) ---
|
|
720
|
+
async function detectSpringRoutes(files, project) {
|
|
721
|
+
const javaFiles = files.filter((f) => f.match(/\.(java|kt)$/));
|
|
722
|
+
const routes = [];
|
|
723
|
+
for (const file of javaFiles) {
|
|
724
|
+
const content = await readFileSafe(file);
|
|
725
|
+
if (!content.includes("@RestController") && !content.includes("@Controller") && !content.includes("@RequestMapping"))
|
|
726
|
+
continue;
|
|
727
|
+
const rel = relative(project.root, file);
|
|
728
|
+
// Extract class-level @RequestMapping
|
|
729
|
+
const classMapping = content.match(/@RequestMapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']/);
|
|
730
|
+
const basePath = classMapping ? classMapping[1] : "";
|
|
731
|
+
// @GetMapping("/path"), @PostMapping("/path"), etc.
|
|
732
|
+
const mappingPattern = /@(Get|Post|Put|Patch|Delete)Mapping\s*\(\s*(?:value\s*=\s*)?(?:["']([^"']*)["'])?\s*\)/gi;
|
|
733
|
+
let match;
|
|
734
|
+
while ((match = mappingPattern.exec(content)) !== null) {
|
|
735
|
+
const method = match[1].toUpperCase();
|
|
736
|
+
const subPath = match[2] || "";
|
|
737
|
+
routes.push({
|
|
738
|
+
method,
|
|
739
|
+
path: basePath + subPath || "/",
|
|
740
|
+
file: rel,
|
|
741
|
+
tags: detectTags(content),
|
|
742
|
+
framework: "spring",
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
// @RequestMapping(method = RequestMethod.GET, value = "/path")
|
|
746
|
+
const reqMappingPattern = /@RequestMapping\s*\([^)]*method\s*=\s*RequestMethod\.(\w+)[^)]*value\s*=\s*["']([^"']+)["']/gi;
|
|
747
|
+
while ((match = reqMappingPattern.exec(content)) !== null) {
|
|
748
|
+
routes.push({
|
|
749
|
+
method: match[1].toUpperCase(),
|
|
750
|
+
path: basePath + match[2],
|
|
751
|
+
file: rel,
|
|
752
|
+
tags: detectTags(content),
|
|
753
|
+
framework: "spring",
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return routes;
|
|
758
|
+
}
|
|
759
|
+
// --- Rust (Actix-web, Axum) ---
|
|
760
|
+
async function detectRustRoutes(files, project, fw) {
|
|
761
|
+
const rsFiles = files.filter((f) => f.endsWith(".rs"));
|
|
762
|
+
const routes = [];
|
|
763
|
+
for (const file of rsFiles) {
|
|
764
|
+
const content = await readFileSafe(file);
|
|
765
|
+
const rel = relative(project.root, file);
|
|
766
|
+
if (fw === "actix") {
|
|
767
|
+
// #[get("/path")], #[post("/path")], etc.
|
|
768
|
+
const attrPattern = /#\[(get|post|put|patch|delete)\s*\(\s*"([^"]+)"\s*\)\s*\]/gi;
|
|
769
|
+
let match;
|
|
770
|
+
while ((match = attrPattern.exec(content)) !== null) {
|
|
771
|
+
routes.push({
|
|
772
|
+
method: match[1].toUpperCase(),
|
|
773
|
+
path: match[2],
|
|
774
|
+
file: rel,
|
|
775
|
+
tags: detectTags(content),
|
|
776
|
+
framework: "actix",
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
// .route("/path", web::get().to(handler))
|
|
780
|
+
const routePattern = /\.route\s*\(\s*"([^"]+)"\s*,\s*web::(get|post|put|patch|delete)\s*\(\s*\)/gi;
|
|
781
|
+
while ((match = routePattern.exec(content)) !== null) {
|
|
782
|
+
routes.push({
|
|
783
|
+
method: match[2].toUpperCase(),
|
|
784
|
+
path: match[1],
|
|
785
|
+
file: rel,
|
|
786
|
+
tags: detectTags(content),
|
|
787
|
+
framework: "actix",
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
else if (fw === "axum") {
|
|
792
|
+
// .route("/path", get(handler)) or .route("/path", post(handler).get(handler))
|
|
793
|
+
const routePattern = /\.route\s*\(\s*"([^"]+)"\s*,\s*(get|post|put|patch|delete)\s*\(/gi;
|
|
794
|
+
let match;
|
|
795
|
+
while ((match = routePattern.exec(content)) !== null) {
|
|
796
|
+
routes.push({
|
|
797
|
+
method: match[2].toUpperCase(),
|
|
798
|
+
path: match[1],
|
|
799
|
+
file: rel,
|
|
800
|
+
tags: detectTags(content),
|
|
801
|
+
framework: "axum",
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return routes;
|
|
807
|
+
}
|
|
808
|
+
// --- Raw HTTP (Node.js http.createServer, Deno, Bun.serve) ---
|
|
809
|
+
async function detectRawHttpRoutes(files, project) {
|
|
810
|
+
const tsFiles = files.filter((f) => f.match(/\.(ts|js|mjs|cjs)$/));
|
|
811
|
+
const routes = [];
|
|
812
|
+
const globalSeen = new Set();
|
|
813
|
+
for (const file of tsFiles) {
|
|
814
|
+
const content = await readFileSafe(file);
|
|
815
|
+
// Only scan files that handle HTTP requests
|
|
816
|
+
if (!content.match(/(?:createServer|http\.|req\.|request\.|url|pathname|Bun\.serve|Deno\.serve)/))
|
|
817
|
+
continue;
|
|
818
|
+
const rel = relative(project.root, file);
|
|
819
|
+
const patterns = [
|
|
820
|
+
// Direct comparison: url === "/path" or pathname === "/path"
|
|
821
|
+
/(?:url|pathname|parsedUrl\.pathname)\s*===?\s*['"`](\/[a-zA-Z0-9/_:.\-]+)['"`]/g,
|
|
822
|
+
// startsWith: url.startsWith("/api")
|
|
823
|
+
/(?:url|pathname)\s*\.startsWith\s*\(\s*['"`](\/[a-zA-Z0-9/_:.\-]+)['"`]\s*\)/g,
|
|
824
|
+
// Switch case: case "/path":
|
|
825
|
+
/case\s+['"`](\/[a-zA-Z0-9/_:.\-]+)['"`]\s*:/g,
|
|
826
|
+
];
|
|
827
|
+
const fileTags = detectTags(content);
|
|
828
|
+
for (const pattern of patterns) {
|
|
829
|
+
let match;
|
|
830
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
831
|
+
const path = match[1];
|
|
832
|
+
// Skip paths that are clearly not routes
|
|
833
|
+
if (path.includes("\\") || path.length > 100 || path.includes(".."))
|
|
834
|
+
continue;
|
|
835
|
+
// Skip file extensions
|
|
836
|
+
if (path.match(/\.\w{2,4}$/))
|
|
837
|
+
continue;
|
|
838
|
+
const key = `${rel}:${path}`;
|
|
839
|
+
if (globalSeen.has(key))
|
|
840
|
+
continue;
|
|
841
|
+
globalSeen.add(key);
|
|
842
|
+
// Try to detect method from surrounding context (within 300 chars)
|
|
843
|
+
const surroundingStart = Math.max(0, match.index - 300);
|
|
844
|
+
const surroundingEnd = Math.min(content.length, match.index + 300);
|
|
845
|
+
const surrounding = content.substring(surroundingStart, surroundingEnd);
|
|
846
|
+
let method = "ALL";
|
|
847
|
+
const methodMatch = surrounding.match(/method\s*===?\s*['"`](GET|POST|PUT|PATCH|DELETE)['"`]/i);
|
|
848
|
+
if (methodMatch) {
|
|
849
|
+
method = methodMatch[1].toUpperCase();
|
|
850
|
+
}
|
|
851
|
+
routes.push({
|
|
852
|
+
method,
|
|
853
|
+
path,
|
|
854
|
+
file: rel,
|
|
855
|
+
tags: fileTags,
|
|
856
|
+
framework: "raw-http",
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return routes;
|
|
862
|
+
}
|
package/dist/formatter.js
CHANGED
|
@@ -253,14 +253,27 @@ function formatCombined(result, sections) {
|
|
|
253
253
|
}
|
|
254
254
|
function filterNotableDeps(deps) {
|
|
255
255
|
const notable = new Set([
|
|
256
|
+
// Frameworks
|
|
256
257
|
"next", "react", "vue", "svelte", "hono", "express", "fastify", "koa",
|
|
257
|
-
"
|
|
258
|
-
"
|
|
259
|
-
|
|
260
|
-
"
|
|
261
|
-
"
|
|
262
|
-
|
|
263
|
-
"
|
|
258
|
+
"@nestjs/core", "@nestjs/common", "elysia", "@adonisjs/core",
|
|
259
|
+
"@sveltejs/kit", "@remix-run/node", "@remix-run/react", "nuxt",
|
|
260
|
+
// ORMs & DB
|
|
261
|
+
"drizzle-orm", "prisma", "@prisma/client", "typeorm", "mongoose", "sequelize",
|
|
262
|
+
"pg", "mysql2", "better-sqlite3", "knex",
|
|
263
|
+
// Auth
|
|
264
|
+
"better-auth", "@clerk/nextjs", "next-auth", "lucia", "passport", "@auth/core",
|
|
265
|
+
// Payments
|
|
266
|
+
"stripe", "@polar-sh/sdk", "resend", "@lemonsqueezy/lemonsqueezy.js",
|
|
267
|
+
// Infrastructure
|
|
268
|
+
"bullmq", "redis", "ioredis", "tailwindcss",
|
|
269
|
+
// API
|
|
270
|
+
"zod", "@trpc/server", "graphql", "@apollo/server",
|
|
271
|
+
// AI
|
|
272
|
+
"@anthropic-ai/sdk", "openai", "ai", "langchain", "@google/generative-ai",
|
|
273
|
+
// Services
|
|
274
|
+
"supabase", "@supabase/supabase-js", "firebase", "@firebase/app",
|
|
275
|
+
// Testing/tools
|
|
276
|
+
"playwright", "puppeteer", "socket.io",
|
|
264
277
|
]);
|
|
265
278
|
return Object.entries(deps)
|
|
266
279
|
.filter(([name]) => notable.has(name))
|
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.1.1";
|
|
18
18
|
const BRAND = "codesight";
|
|
19
19
|
function printHelp() {
|
|
20
20
|
console.log(`
|
|
@@ -32,6 +32,7 @@ function printHelp() {
|
|
|
32
32
|
--open Generate HTML report and open in browser
|
|
33
33
|
--mcp Start as MCP server (for Claude Code, Cursor)
|
|
34
34
|
--json Output JSON instead of markdown
|
|
35
|
+
--benchmark Show detailed token savings breakdown
|
|
35
36
|
-v, --version Show version
|
|
36
37
|
-h, --help Show this help
|
|
37
38
|
|
|
@@ -218,6 +219,7 @@ async function main() {
|
|
|
218
219
|
let doHtml = false;
|
|
219
220
|
let doOpen = false;
|
|
220
221
|
let doMcp = false;
|
|
222
|
+
let doBenchmark = false;
|
|
221
223
|
for (let i = 0; i < args.length; i++) {
|
|
222
224
|
const arg = args[i];
|
|
223
225
|
if ((arg === "-o" || arg === "--output") && args[i + 1]) {
|
|
@@ -248,6 +250,9 @@ async function main() {
|
|
|
248
250
|
else if (arg === "--mcp") {
|
|
249
251
|
doMcp = true;
|
|
250
252
|
}
|
|
253
|
+
else if (arg === "--benchmark") {
|
|
254
|
+
doBenchmark = true;
|
|
255
|
+
}
|
|
251
256
|
else if (!arg.startsWith("-")) {
|
|
252
257
|
targetDir = resolve(arg);
|
|
253
258
|
}
|
|
@@ -293,6 +298,37 @@ async function main() {
|
|
|
293
298
|
console.log(" Opening in browser...");
|
|
294
299
|
}
|
|
295
300
|
}
|
|
301
|
+
// Benchmark output
|
|
302
|
+
if (doBenchmark) {
|
|
303
|
+
const ts = result.tokenStats;
|
|
304
|
+
const r = result;
|
|
305
|
+
console.log(`
|
|
306
|
+
Token Savings Breakdown:
|
|
307
|
+
┌──────────────────────────────────────────────────┐
|
|
308
|
+
│ What codesight found │ Exploration cost │
|
|
309
|
+
├──────────────────────────────┼────────────────────┤
|
|
310
|
+
│ ${String(r.routes.length).padStart(3)} routes │ ~${(r.routes.length * 400).toLocaleString().padStart(6)} tokens │
|
|
311
|
+
│ ${String(r.schemas.length).padStart(3)} schema models │ ~${(r.schemas.length * 300).toLocaleString().padStart(6)} tokens │
|
|
312
|
+
│ ${String(r.components.length).padStart(3)} components │ ~${(r.components.length * 250).toLocaleString().padStart(6)} tokens │
|
|
313
|
+
│ ${String(r.libs.length).padStart(3)} library files │ ~${(r.libs.length * 200).toLocaleString().padStart(6)} tokens │
|
|
314
|
+
│ ${String(r.config.envVars.length).padStart(3)} env vars │ ~${(r.config.envVars.length * 100).toLocaleString().padStart(6)} tokens │
|
|
315
|
+
│ ${String(r.middleware.length).padStart(3)} middleware │ ~${(r.middleware.length * 200).toLocaleString().padStart(6)} tokens │
|
|
316
|
+
│ ${String(r.graph.hotFiles.length).padStart(3)} hot files │ ~${(r.graph.hotFiles.length * 150).toLocaleString().padStart(6)} tokens │
|
|
317
|
+
│ ${String(ts.fileCount).padStart(3)} files (search overhead) │ ~${(Math.min(ts.fileCount, 50) * 80).toLocaleString().padStart(6)} tokens │
|
|
318
|
+
├──────────────────────────────┼────────────────────┤
|
|
319
|
+
│ codesight output │ ~${ts.outputTokens.toLocaleString().padStart(6)} tokens │
|
|
320
|
+
│ Manual exploration (1.3x) │ ~${ts.estimatedExplorationTokens.toLocaleString().padStart(6)} tokens │
|
|
321
|
+
│ SAVED PER CONVERSATION │ ~${ts.saved.toLocaleString().padStart(6)} tokens │
|
|
322
|
+
└──────────────────────────────┴────────────────────┘
|
|
323
|
+
|
|
324
|
+
How this is calculated:
|
|
325
|
+
- Each route found saves ~400 tokens of file reading + grep exploration
|
|
326
|
+
- Each schema model saves ~300 tokens of migration/ORM file parsing
|
|
327
|
+
- Each component saves ~250 tokens of prop discovery
|
|
328
|
+
- Search overhead: AI typically runs ${Math.min(ts.fileCount, 50)} glob/grep operations
|
|
329
|
+
- 1.3x multiplier: AI revisits files during multi-turn exploration
|
|
330
|
+
`);
|
|
331
|
+
}
|
|
296
332
|
// Watch mode (blocks)
|
|
297
333
|
if (doWatch) {
|
|
298
334
|
await watchMode(root, outputDirName, maxDepth);
|
package/dist/scanner.js
CHANGED
|
@@ -35,6 +35,13 @@ const CODE_EXTENSIONS = new Set([
|
|
|
35
35
|
".go",
|
|
36
36
|
".vue",
|
|
37
37
|
".svelte",
|
|
38
|
+
".rb",
|
|
39
|
+
".ex",
|
|
40
|
+
".exs",
|
|
41
|
+
".java",
|
|
42
|
+
".kt",
|
|
43
|
+
".rs",
|
|
44
|
+
".php",
|
|
38
45
|
]);
|
|
39
46
|
export async function collectFiles(root, maxDepth = 10) {
|
|
40
47
|
const files = [];
|
|
@@ -174,6 +181,27 @@ async function detectFrameworks(root, pkg) {
|
|
|
174
181
|
// Koa
|
|
175
182
|
if (deps["koa"])
|
|
176
183
|
frameworks.push("koa");
|
|
184
|
+
// NestJS
|
|
185
|
+
if (deps["@nestjs/core"] || deps["@nestjs/common"])
|
|
186
|
+
frameworks.push("nestjs");
|
|
187
|
+
// Elysia (Bun)
|
|
188
|
+
if (deps["elysia"])
|
|
189
|
+
frameworks.push("elysia");
|
|
190
|
+
// AdonisJS
|
|
191
|
+
if (deps["@adonisjs/core"])
|
|
192
|
+
frameworks.push("adonis");
|
|
193
|
+
// tRPC
|
|
194
|
+
if (deps["@trpc/server"])
|
|
195
|
+
frameworks.push("trpc");
|
|
196
|
+
// SvelteKit
|
|
197
|
+
if (deps["@sveltejs/kit"])
|
|
198
|
+
frameworks.push("sveltekit");
|
|
199
|
+
// Remix
|
|
200
|
+
if (deps["@remix-run/node"] || deps["@remix-run/react"])
|
|
201
|
+
frameworks.push("remix");
|
|
202
|
+
// Nuxt
|
|
203
|
+
if (deps["nuxt"])
|
|
204
|
+
frameworks.push("nuxt");
|
|
177
205
|
// Python frameworks - check for requirements.txt or pyproject.toml
|
|
178
206
|
const pyDeps = await getPythonDeps(root);
|
|
179
207
|
if (pyDeps.includes("flask"))
|
|
@@ -190,6 +218,59 @@ async function detectFrameworks(root, pkg) {
|
|
|
190
218
|
frameworks.push("gin");
|
|
191
219
|
if (goDeps.includes("gofiber/fiber"))
|
|
192
220
|
frameworks.push("fiber");
|
|
221
|
+
if (goDeps.some((d) => d.includes("labstack/echo")))
|
|
222
|
+
frameworks.push("echo");
|
|
223
|
+
if (goDeps.some((d) => d.includes("go-chi/chi")))
|
|
224
|
+
frameworks.push("chi");
|
|
225
|
+
// Ruby on Rails
|
|
226
|
+
const hasGemfile = await fileExists(join(root, "Gemfile"));
|
|
227
|
+
if (hasGemfile) {
|
|
228
|
+
try {
|
|
229
|
+
const gemfile = await readFile(join(root, "Gemfile"), "utf-8");
|
|
230
|
+
if (gemfile.includes("rails"))
|
|
231
|
+
frameworks.push("rails");
|
|
232
|
+
}
|
|
233
|
+
catch { }
|
|
234
|
+
}
|
|
235
|
+
// Phoenix (Elixir)
|
|
236
|
+
const hasMixFile = await fileExists(join(root, "mix.exs"));
|
|
237
|
+
if (hasMixFile) {
|
|
238
|
+
try {
|
|
239
|
+
const mix = await readFile(join(root, "mix.exs"), "utf-8");
|
|
240
|
+
if (mix.includes("phoenix"))
|
|
241
|
+
frameworks.push("phoenix");
|
|
242
|
+
}
|
|
243
|
+
catch { }
|
|
244
|
+
}
|
|
245
|
+
// Spring Boot (Java/Kotlin)
|
|
246
|
+
const hasPomXml = await fileExists(join(root, "pom.xml"));
|
|
247
|
+
const hasBuildGradle = await fileExists(join(root, "build.gradle")) || await fileExists(join(root, "build.gradle.kts"));
|
|
248
|
+
if (hasPomXml || hasBuildGradle) {
|
|
249
|
+
try {
|
|
250
|
+
const buildFile = hasPomXml
|
|
251
|
+
? await readFile(join(root, "pom.xml"), "utf-8")
|
|
252
|
+
: await readFile(join(root, hasBuildGradle ? "build.gradle.kts" : "build.gradle"), "utf-8");
|
|
253
|
+
if (buildFile.includes("spring"))
|
|
254
|
+
frameworks.push("spring");
|
|
255
|
+
}
|
|
256
|
+
catch { }
|
|
257
|
+
}
|
|
258
|
+
// Rust web frameworks
|
|
259
|
+
const hasCargoToml = await fileExists(join(root, "Cargo.toml"));
|
|
260
|
+
if (hasCargoToml) {
|
|
261
|
+
try {
|
|
262
|
+
const cargo = await readFile(join(root, "Cargo.toml"), "utf-8");
|
|
263
|
+
if (cargo.includes("actix-web"))
|
|
264
|
+
frameworks.push("actix");
|
|
265
|
+
else if (cargo.includes("axum"))
|
|
266
|
+
frameworks.push("axum");
|
|
267
|
+
}
|
|
268
|
+
catch { }
|
|
269
|
+
}
|
|
270
|
+
// Fallback: detect raw http.createServer if no other frameworks found
|
|
271
|
+
if (frameworks.length === 0) {
|
|
272
|
+
frameworks.push("raw-http");
|
|
273
|
+
}
|
|
193
274
|
return frameworks;
|
|
194
275
|
}
|
|
195
276
|
async function detectORMs(root, pkg) {
|
|
@@ -201,12 +282,36 @@ async function detectORMs(root, pkg) {
|
|
|
201
282
|
orms.push("prisma");
|
|
202
283
|
if (deps["typeorm"])
|
|
203
284
|
orms.push("typeorm");
|
|
285
|
+
if (deps["mongoose"])
|
|
286
|
+
orms.push("mongoose");
|
|
287
|
+
if (deps["sequelize"])
|
|
288
|
+
orms.push("sequelize");
|
|
204
289
|
const pyDeps = await getPythonDeps(root);
|
|
205
290
|
if (pyDeps.includes("sqlalchemy"))
|
|
206
291
|
orms.push("sqlalchemy");
|
|
207
292
|
const goDeps = await getGoDeps(root);
|
|
208
293
|
if (goDeps.some((d) => d.includes("gorm")))
|
|
209
294
|
orms.push("gorm");
|
|
295
|
+
// Rails ActiveRecord
|
|
296
|
+
const hasGemfile = await fileExists(join(root, "Gemfile"));
|
|
297
|
+
if (hasGemfile) {
|
|
298
|
+
try {
|
|
299
|
+
const gemfile = await readFile(join(root, "Gemfile"), "utf-8");
|
|
300
|
+
if (gemfile.includes("activerecord") || gemfile.includes("rails"))
|
|
301
|
+
orms.push("activerecord");
|
|
302
|
+
}
|
|
303
|
+
catch { }
|
|
304
|
+
}
|
|
305
|
+
// Phoenix Ecto
|
|
306
|
+
const hasMixFile = await fileExists(join(root, "mix.exs"));
|
|
307
|
+
if (hasMixFile) {
|
|
308
|
+
try {
|
|
309
|
+
const mix = await readFile(join(root, "mix.exs"), "utf-8");
|
|
310
|
+
if (mix.includes("ecto"))
|
|
311
|
+
orms.push("ecto");
|
|
312
|
+
}
|
|
313
|
+
catch { }
|
|
314
|
+
}
|
|
210
315
|
return orms;
|
|
211
316
|
}
|
|
212
317
|
function detectComponentFramework(deps) {
|
|
@@ -223,6 +328,12 @@ async function detectLanguage(root, deps) {
|
|
|
223
328
|
const hasPyProject = await fileExists(join(root, "pyproject.toml")) || await fileExists(join(root, "backend/pyproject.toml"));
|
|
224
329
|
const hasGoMod = await fileExists(join(root, "go.mod"));
|
|
225
330
|
const hasRequirements = await fileExists(join(root, "requirements.txt")) || await fileExists(join(root, "backend/requirements.txt"));
|
|
331
|
+
const hasGemfile = await fileExists(join(root, "Gemfile"));
|
|
332
|
+
const hasMixExs = await fileExists(join(root, "mix.exs"));
|
|
333
|
+
const hasPomXml = await fileExists(join(root, "pom.xml"));
|
|
334
|
+
const hasBuildGradle = await fileExists(join(root, "build.gradle")) || await fileExists(join(root, "build.gradle.kts"));
|
|
335
|
+
const hasCargoToml = await fileExists(join(root, "Cargo.toml"));
|
|
336
|
+
const hasComposerJson = await fileExists(join(root, "composer.json"));
|
|
226
337
|
const langs = [];
|
|
227
338
|
if (hasTsConfig || deps["typescript"])
|
|
228
339
|
langs.push("typescript");
|
|
@@ -230,14 +341,22 @@ async function detectLanguage(root, deps) {
|
|
|
230
341
|
langs.push("python");
|
|
231
342
|
if (hasGoMod)
|
|
232
343
|
langs.push("go");
|
|
344
|
+
if (hasGemfile)
|
|
345
|
+
langs.push("ruby");
|
|
346
|
+
if (hasMixExs)
|
|
347
|
+
langs.push("elixir");
|
|
348
|
+
if (hasBuildGradle)
|
|
349
|
+
langs.push("kotlin");
|
|
350
|
+
else if (hasPomXml)
|
|
351
|
+
langs.push("java");
|
|
352
|
+
if (hasCargoToml)
|
|
353
|
+
langs.push("rust");
|
|
354
|
+
if (hasComposerJson)
|
|
355
|
+
langs.push("php");
|
|
233
356
|
if (langs.length > 1)
|
|
234
357
|
return "mixed";
|
|
235
|
-
if (langs
|
|
236
|
-
return
|
|
237
|
-
if (langs[0] === "python")
|
|
238
|
-
return "python";
|
|
239
|
-
if (langs[0] === "go")
|
|
240
|
-
return "go";
|
|
358
|
+
if (langs.length === 1)
|
|
359
|
+
return langs[0];
|
|
241
360
|
return "javascript";
|
|
242
361
|
}
|
|
243
362
|
async function getWorkspacePatterns(root, pkg) {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type Framework = "next-app" | "next-pages" | "hono" | "express" | "fastify" | "koa" | "flask" | "fastapi" | "django" | "go-net-http" | "gin" | "fiber" | "unknown";
|
|
2
|
-
export type ORM = "drizzle" | "prisma" | "typeorm" | "sqlalchemy" | "gorm" | "unknown";
|
|
1
|
+
export type Framework = "next-app" | "next-pages" | "hono" | "express" | "fastify" | "koa" | "nestjs" | "elysia" | "adonis" | "trpc" | "sveltekit" | "remix" | "nuxt" | "flask" | "fastapi" | "django" | "go-net-http" | "gin" | "fiber" | "echo" | "chi" | "rails" | "phoenix" | "spring" | "actix" | "axum" | "raw-http" | "unknown";
|
|
2
|
+
export type ORM = "drizzle" | "prisma" | "typeorm" | "sqlalchemy" | "gorm" | "mongoose" | "sequelize" | "activerecord" | "ecto" | "unknown";
|
|
3
3
|
export type ComponentFramework = "react" | "vue" | "svelte" | "unknown";
|
|
4
4
|
export interface ProjectInfo {
|
|
5
5
|
root: string;
|
|
@@ -9,7 +9,7 @@ export interface ProjectInfo {
|
|
|
9
9
|
componentFramework: ComponentFramework;
|
|
10
10
|
isMonorepo: boolean;
|
|
11
11
|
workspaces: WorkspaceInfo[];
|
|
12
|
-
language: "typescript" | "javascript" | "python" | "go" | "mixed";
|
|
12
|
+
language: "typescript" | "javascript" | "python" | "go" | "ruby" | "elixir" | "java" | "kotlin" | "rust" | "php" | "mixed";
|
|
13
13
|
}
|
|
14
14
|
export interface WorkspaceInfo {
|
|
15
15
|
name: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codesight",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
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": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "tsc",
|
|
12
12
|
"dev": "tsx src/index.ts",
|
|
13
|
+
"test": "pnpm build && node --test tests/detectors.test.ts",
|
|
13
14
|
"prepublishOnly": "pnpm build"
|
|
14
15
|
},
|
|
15
16
|
"keywords": [
|