codesight 1.2.0 → 1.3.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.
@@ -1,5 +1,7 @@
1
1
  import { relative, basename } from "node:path";
2
2
  import { readFileSafe } from "../scanner.js";
3
+ import { loadTypeScript } from "../ast/loader.js";
4
+ import { extractRoutesAST } from "../ast/extract-routes.js";
3
5
  const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"];
4
6
  const TAG_PATTERNS = [
5
7
  ["auth", [/auth/i, /jwt/i, /token/i, /session/i, /bearer/i, /passport/i, /clerk/i, /betterAuth/i, /better-auth/i]],
@@ -97,7 +99,17 @@ export async function detectRoutes(files, project) {
97
99
  break;
98
100
  }
99
101
  }
100
- return routes;
102
+ // Deduplicate: same method + path from different files/frameworks
103
+ const seen = new Set();
104
+ const deduped = [];
105
+ for (const route of routes) {
106
+ const key = `${route.method}:${route.path}`;
107
+ if (!seen.has(key)) {
108
+ seen.add(key);
109
+ deduped.push(route);
110
+ }
111
+ }
112
+ return deduped;
101
113
  }
102
114
  // --- Next.js App Router ---
103
115
  async function detectNextAppRoutes(files, project) {
@@ -157,11 +169,22 @@ async function detectNextPagesApi(files, project) {
157
169
  async function detectHonoRoutes(files, project) {
158
170
  const tsFiles = files.filter((f) => f.match(/\.(ts|js|tsx|jsx|mjs)$/));
159
171
  const routes = [];
172
+ const ts = loadTypeScript(project.root);
160
173
  for (const file of tsFiles) {
161
174
  const content = await readFileSafe(file);
162
175
  if (!content.includes("hono") && !content.includes("Hono"))
163
176
  continue;
164
177
  const rel = relative(project.root, file);
178
+ const tags = detectTags(content);
179
+ // Try AST first
180
+ if (ts) {
181
+ const astRoutes = extractRoutesAST(ts, rel, content, "hono", tags);
182
+ if (astRoutes.length > 0) {
183
+ routes.push(...astRoutes);
184
+ continue;
185
+ }
186
+ }
187
+ // Regex fallback
165
188
  const routePattern = /\.\s*(get|post|put|patch|delete|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
166
189
  let match;
167
190
  while ((match = routePattern.exec(content)) !== null) {
@@ -172,8 +195,9 @@ async function detectHonoRoutes(files, project) {
172
195
  method: match[1].toUpperCase(),
173
196
  path,
174
197
  file: rel,
175
- tags: detectTags(content),
198
+ tags,
176
199
  framework: "hono",
200
+ confidence: "regex",
177
201
  });
178
202
  }
179
203
  }
@@ -183,11 +207,22 @@ async function detectHonoRoutes(files, project) {
183
207
  async function detectExpressRoutes(files, project) {
184
208
  const tsFiles = files.filter((f) => f.match(/\.(ts|js|mjs|cjs)$/));
185
209
  const routes = [];
210
+ const ts = loadTypeScript(project.root);
186
211
  for (const file of tsFiles) {
187
212
  const content = await readFileSafe(file);
188
213
  if (!content.includes("express") && !content.includes("Router"))
189
214
  continue;
190
215
  const rel = relative(project.root, file);
216
+ const tags = detectTags(content);
217
+ // Try AST first
218
+ if (ts) {
219
+ const astRoutes = extractRoutesAST(ts, rel, content, "express", tags);
220
+ if (astRoutes.length > 0) {
221
+ routes.push(...astRoutes);
222
+ continue;
223
+ }
224
+ }
225
+ // Regex fallback
191
226
  const routePattern = /(?:app|router|server)\s*\.\s*(get|post|put|patch|delete|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
192
227
  let match;
193
228
  while ((match = routePattern.exec(content)) !== null) {
@@ -195,8 +230,9 @@ async function detectExpressRoutes(files, project) {
195
230
  method: match[1].toUpperCase(),
196
231
  path: match[2],
197
232
  file: rel,
198
- tags: detectTags(content),
233
+ tags,
199
234
  framework: "express",
235
+ confidence: "regex",
200
236
  });
201
237
  }
202
238
  }
@@ -206,11 +242,22 @@ async function detectExpressRoutes(files, project) {
206
242
  async function detectFastifyRoutes(files, project) {
207
243
  const tsFiles = files.filter((f) => f.match(/\.(ts|js|mjs|cjs)$/));
208
244
  const routes = [];
245
+ const ts = loadTypeScript(project.root);
209
246
  for (const file of tsFiles) {
210
247
  const content = await readFileSafe(file);
211
248
  if (!content.includes("fastify"))
212
249
  continue;
213
250
  const rel = relative(project.root, file);
251
+ const tags = detectTags(content);
252
+ // Try AST first
253
+ if (ts) {
254
+ const astRoutes = extractRoutesAST(ts, rel, content, "fastify", tags);
255
+ if (astRoutes.length > 0) {
256
+ routes.push(...astRoutes);
257
+ continue;
258
+ }
259
+ }
260
+ // Regex fallback
214
261
  const routePattern = /(?:fastify|server|app)\s*\.\s*(get|post|put|patch|delete|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
215
262
  let match;
216
263
  while ((match = routePattern.exec(content)) !== null) {
@@ -218,8 +265,9 @@ async function detectFastifyRoutes(files, project) {
218
265
  method: match[1].toUpperCase(),
219
266
  path: match[2],
220
267
  file: rel,
221
- tags: detectTags(content),
268
+ tags,
222
269
  framework: "fastify",
270
+ confidence: "regex",
223
271
  });
224
272
  }
225
273
  // Object-style route registration
@@ -229,8 +277,9 @@ async function detectFastifyRoutes(files, project) {
229
277
  method: match[1].toUpperCase(),
230
278
  path: match[2],
231
279
  file: rel,
232
- tags: detectTags(content),
280
+ tags,
233
281
  framework: "fastify",
282
+ confidence: "regex",
234
283
  });
235
284
  }
236
285
  }
@@ -240,11 +289,20 @@ async function detectFastifyRoutes(files, project) {
240
289
  async function detectKoaRoutes(files, project) {
241
290
  const tsFiles = files.filter((f) => f.match(/\.(ts|js|mjs|cjs)$/));
242
291
  const routes = [];
292
+ const ts = loadTypeScript(project.root);
243
293
  for (const file of tsFiles) {
244
294
  const content = await readFileSafe(file);
245
295
  if (!content.includes("koa") && !content.includes("Router"))
246
296
  continue;
247
297
  const rel = relative(project.root, file);
298
+ const tags = detectTags(content);
299
+ if (ts) {
300
+ const astRoutes = extractRoutesAST(ts, rel, content, "koa", tags);
301
+ if (astRoutes.length > 0) {
302
+ routes.push(...astRoutes);
303
+ continue;
304
+ }
305
+ }
248
306
  const routePattern = /router\s*\.\s*(get|post|put|patch|delete|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
249
307
  let match;
250
308
  while ((match = routePattern.exec(content)) !== null) {
@@ -252,8 +310,9 @@ async function detectKoaRoutes(files, project) {
252
310
  method: match[1].toUpperCase(),
253
311
  path: match[2],
254
312
  file: rel,
255
- tags: detectTags(content),
313
+ tags,
256
314
  framework: "koa",
315
+ confidence: "regex",
257
316
  });
258
317
  }
259
318
  }
@@ -263,15 +322,24 @@ async function detectKoaRoutes(files, project) {
263
322
  async function detectNestJSRoutes(files, project) {
264
323
  const tsFiles = files.filter((f) => f.match(/\.(ts|js)$/));
265
324
  const routes = [];
325
+ const ts = loadTypeScript(project.root);
266
326
  for (const file of tsFiles) {
267
327
  const content = await readFileSafe(file);
268
328
  if (!content.includes("@Controller") && !content.includes("@Get") && !content.includes("@Post"))
269
329
  continue;
270
330
  const rel = relative(project.root, file);
271
- // Extract controller base path: @Controller('users') or @Controller('/users')
331
+ const tags = detectTags(content);
332
+ // Try AST — NestJS benefits most from AST (decorator + controller prefix combining)
333
+ if (ts) {
334
+ const astRoutes = extractRoutesAST(ts, rel, content, "nestjs", tags);
335
+ if (astRoutes.length > 0) {
336
+ routes.push(...astRoutes);
337
+ continue;
338
+ }
339
+ }
340
+ // Regex fallback
272
341
  const controllerMatch = content.match(/@Controller\s*\(\s*['"`]([^'"`]*)['"`]\s*\)/);
273
342
  const basePath = controllerMatch ? "/" + controllerMatch[1].replace(/^\//, "") : "";
274
- // Match method decorators: @Get(), @Post('/create'), @Put(':id'), etc.
275
343
  const decoratorPattern = /@(Get|Post|Put|Patch|Delete|Options|Head|All)\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/gi;
276
344
  let match;
277
345
  while ((match = decoratorPattern.exec(content)) !== null) {
@@ -282,8 +350,9 @@ async function detectNestJSRoutes(files, project) {
282
350
  method,
283
351
  path: fullPath,
284
352
  file: rel,
285
- tags: detectTags(content),
353
+ tags,
286
354
  framework: "nestjs",
355
+ confidence: "regex",
287
356
  });
288
357
  }
289
358
  }
@@ -293,11 +362,20 @@ async function detectNestJSRoutes(files, project) {
293
362
  async function detectElysiaRoutes(files, project) {
294
363
  const tsFiles = files.filter((f) => f.match(/\.(ts|js|mjs)$/));
295
364
  const routes = [];
365
+ const ts = loadTypeScript(project.root);
296
366
  for (const file of tsFiles) {
297
367
  const content = await readFileSafe(file);
298
368
  if (!content.includes("elysia") && !content.includes("Elysia"))
299
369
  continue;
300
370
  const rel = relative(project.root, file);
371
+ const tags = detectTags(content);
372
+ if (ts) {
373
+ const astRoutes = extractRoutesAST(ts, rel, content, "elysia", tags);
374
+ if (astRoutes.length > 0) {
375
+ routes.push(...astRoutes);
376
+ continue;
377
+ }
378
+ }
301
379
  const routePattern = /\.\s*(get|post|put|patch|delete|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
302
380
  let match;
303
381
  while ((match = routePattern.exec(content)) !== null) {
@@ -308,8 +386,9 @@ async function detectElysiaRoutes(files, project) {
308
386
  method: match[1].toUpperCase(),
309
387
  path,
310
388
  file: rel,
311
- tags: detectTags(content),
389
+ tags,
312
390
  framework: "elysia",
391
+ confidence: "regex",
313
392
  });
314
393
  }
315
394
  }
@@ -341,6 +420,7 @@ async function detectAdonisRoutes(files, project) {
341
420
  async function detectTRPCRoutes(files, project) {
342
421
  const tsFiles = files.filter((f) => f.match(/\.(ts|js)$/));
343
422
  const routes = [];
423
+ const ts = loadTypeScript(project.root);
344
424
  for (const file of tsFiles) {
345
425
  const content = await readFileSafe(file);
346
426
  if (!content.includes("Procedure") && !content.includes("procedure") && !content.includes("router"))
@@ -348,11 +428,16 @@ async function detectTRPCRoutes(files, project) {
348
428
  if (!content.includes("trpc") && !content.includes("TRPC") && !content.includes("createTRPCRouter") && !content.includes("publicProcedure") && !content.includes("protectedProcedure"))
349
429
  continue;
350
430
  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
431
+ const tags = detectTags(content);
432
+ // AST handles tRPC much better — properly parses router nesting and procedure chains
433
+ if (ts) {
434
+ const astRoutes = extractRoutesAST(ts, rel, content, "trpc", tags);
435
+ if (astRoutes.length > 0) {
436
+ routes.push(...astRoutes);
437
+ continue;
438
+ }
439
+ }
440
+ // Regex fallback
356
441
  const lines = content.split("\n");
357
442
  for (const line of lines) {
358
443
  const queryMatch = line.match(/^\s*(\w+)\s*:\s*.*\.(query)\s*\(/);
@@ -366,8 +451,9 @@ async function detectTRPCRoutes(files, project) {
366
451
  method: isQuery ? "QUERY" : "MUTATION",
367
452
  path: procName,
368
453
  file: rel,
369
- tags: detectTags(content),
454
+ tags,
370
455
  framework: "trpc",
456
+ confidence: "regex",
371
457
  });
372
458
  }
373
459
  }
@@ -809,7 +895,6 @@ async function detectRustRoutes(files, project, fw) {
809
895
  async function detectRawHttpRoutes(files, project) {
810
896
  const tsFiles = files.filter((f) => f.match(/\.(ts|js|mjs|cjs)$/));
811
897
  const routes = [];
812
- const globalSeen = new Set();
813
898
  for (const file of tsFiles) {
814
899
  const content = await readFileSafe(file);
815
900
  // Only scan files that handle HTTP requests
@@ -835,16 +920,12 @@ async function detectRawHttpRoutes(files, project) {
835
920
  // Skip file extensions
836
921
  if (path.match(/\.\w{2,4}$/))
837
922
  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);
923
+ // Detect method from the same line or immediately adjacent lines (within 100 chars)
924
+ const lineStart = content.lastIndexOf("\n", match.index) + 1;
925
+ const lineEnd = content.indexOf("\n", match.index + match[0].length);
926
+ const lineContext = content.substring(Math.max(0, lineStart - 50), Math.min(content.length, (lineEnd === -1 ? content.length : lineEnd) + 50));
846
927
  let method = "ALL";
847
- const methodMatch = surrounding.match(/method\s*===?\s*['"`](GET|POST|PUT|PATCH|DELETE)['"`]/i);
928
+ const methodMatch = lineContext.match(/method\s*===?\s*['"`](GET|POST|PUT|PATCH|DELETE)['"`]/i);
848
929
  if (methodMatch) {
849
930
  method = methodMatch[1].toUpperCase();
850
931
  }
@@ -1,5 +1,7 @@
1
1
  import { join } from "node:path";
2
2
  import { readFileSafe } from "../scanner.js";
3
+ import { loadTypeScript } from "../ast/loader.js";
4
+ import { extractDrizzleSchemaAST, extractTypeORMSchemaAST } from "../ast/extract-schema.js";
3
5
  const AUDIT_FIELDS = new Set([
4
6
  "createdAt",
5
7
  "updatedAt",
@@ -35,10 +37,19 @@ async function detectDrizzleSchemas(files, project) {
35
37
  f.match(/\.schema\.(ts|js)$/) ||
36
38
  f.match(/\/db\/.*\.(ts|js)$/));
37
39
  const models = [];
40
+ const ts = loadTypeScript(project.root);
38
41
  for (const file of schemaFiles) {
39
42
  const content = await readFileSafe(file);
40
43
  if (!content.includes("pgTable") && !content.includes("mysqlTable") && !content.includes("sqliteTable"))
41
44
  continue;
45
+ // Try AST first — much more accurate for Drizzle field chains
46
+ if (ts) {
47
+ const astModels = extractDrizzleSchemaAST(ts, file, content);
48
+ if (astModels.length > 0) {
49
+ models.push(...astModels);
50
+ continue;
51
+ }
52
+ }
42
53
  // Match: export const users = pgTable("users", { ... })
43
54
  const tablePattern = /(?:export\s+)?const\s+(\w+)\s*=\s*(?:pgTable|mysqlTable|sqliteTable)\s*\(\s*['"`](\w+)['"`]\s*,\s*(?:\(\s*\)\s*=>\s*\(?\s*)?\{([\s\S]*?)\}\s*\)?\s*(?:,|\))/g;
44
55
  let match;
@@ -185,10 +196,19 @@ async function detectPrismaSchemas(project) {
185
196
  async function detectTypeORMSchemas(files, project) {
186
197
  const entityFiles = files.filter((f) => f.match(/\.entity\.(ts|js)$/) || f.match(/entities\/.*\.(ts|js)$/));
187
198
  const models = [];
199
+ const ts = loadTypeScript(project.root);
188
200
  for (const file of entityFiles) {
189
201
  const content = await readFileSafe(file);
190
202
  if (!content.includes("@Entity") && !content.includes("@Column"))
191
203
  continue;
204
+ // Try AST first — handles TypeORM decorators accurately
205
+ if (ts) {
206
+ const astModels = extractTypeORMSchemaAST(ts, file, content);
207
+ if (astModels.length > 0) {
208
+ models.push(...astModels);
209
+ continue;
210
+ }
211
+ }
192
212
  // Extract entity name
193
213
  const entityMatch = content.match(/@Entity\s*\(\s*(?:['"`](\w+)['"`])?\s*\)/);
194
214
  const classMatch = content.match(/class\s+(\w+)/);
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.2.0";
17
+ const VERSION = "1.3.1";
18
18
  const BRAND = "codesight";
19
19
  function printHelp() {
20
20
  console.log(`
@@ -86,7 +86,17 @@ async function scan(root, outputDirName, maxDepth) {
86
86
  ]);
87
87
  // Step 4: Enrich routes with contract info
88
88
  const routes = await enrichRouteContracts(rawRoutes, project);
89
- console.log(" done");
89
+ // Report AST vs regex detection
90
+ const astRoutes = routes.filter((r) => r.confidence === "ast").length;
91
+ const astSchemas = schemas.filter((s) => s.confidence === "ast").length;
92
+ const astComponents = components.filter((c) => c.confidence === "ast").length;
93
+ const totalAST = astRoutes + astSchemas + astComponents;
94
+ if (totalAST > 0) {
95
+ console.log(` done (AST: ${astRoutes} routes, ${astSchemas} models, ${astComponents} components)`);
96
+ }
97
+ else {
98
+ console.log(" done");
99
+ }
90
100
  // Step 5: Write output
91
101
  process.stdout.write(" Writing output...");
92
102
  // Temporary result without token stats to generate output
package/dist/types.d.ts CHANGED
@@ -17,6 +17,7 @@ export interface WorkspaceInfo {
17
17
  frameworks: Framework[];
18
18
  orms: ORM[];
19
19
  }
20
+ export type DetectionMethod = "ast" | "regex";
20
21
  export interface RouteInfo {
21
22
  method: string;
22
23
  path: string;
@@ -26,12 +27,15 @@ export interface RouteInfo {
26
27
  requestType?: string;
27
28
  responseType?: string;
28
29
  params?: string[];
30
+ confidence?: DetectionMethod;
31
+ middleware?: string[];
29
32
  }
30
33
  export interface SchemaModel {
31
34
  name: string;
32
35
  fields: SchemaField[];
33
36
  relations: string[];
34
37
  orm: ORM;
38
+ confidence?: DetectionMethod;
35
39
  }
36
40
  export interface SchemaField {
37
41
  name: string;
@@ -41,6 +45,7 @@ export interface SchemaField {
41
45
  export interface ComponentInfo {
42
46
  name: string;
43
47
  file: string;
48
+ confidence?: DetectionMethod;
44
49
  props: string[];
45
50
  isClient: boolean;
46
51
  isServer: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codesight",
3
- "version": "1.2.0",
3
+ "version": "1.3.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": {