codesight 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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]],
@@ -157,11 +159,22 @@ async function detectNextPagesApi(files, project) {
157
159
  async function detectHonoRoutes(files, project) {
158
160
  const tsFiles = files.filter((f) => f.match(/\.(ts|js|tsx|jsx|mjs)$/));
159
161
  const routes = [];
162
+ const ts = loadTypeScript(project.root);
160
163
  for (const file of tsFiles) {
161
164
  const content = await readFileSafe(file);
162
165
  if (!content.includes("hono") && !content.includes("Hono"))
163
166
  continue;
164
167
  const rel = relative(project.root, file);
168
+ const tags = detectTags(content);
169
+ // Try AST first
170
+ if (ts) {
171
+ const astRoutes = extractRoutesAST(ts, rel, content, "hono", tags);
172
+ if (astRoutes.length > 0) {
173
+ routes.push(...astRoutes);
174
+ continue;
175
+ }
176
+ }
177
+ // Regex fallback
165
178
  const routePattern = /\.\s*(get|post|put|patch|delete|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
166
179
  let match;
167
180
  while ((match = routePattern.exec(content)) !== null) {
@@ -172,8 +185,9 @@ async function detectHonoRoutes(files, project) {
172
185
  method: match[1].toUpperCase(),
173
186
  path,
174
187
  file: rel,
175
- tags: detectTags(content),
188
+ tags,
176
189
  framework: "hono",
190
+ confidence: "regex",
177
191
  });
178
192
  }
179
193
  }
@@ -183,11 +197,22 @@ async function detectHonoRoutes(files, project) {
183
197
  async function detectExpressRoutes(files, project) {
184
198
  const tsFiles = files.filter((f) => f.match(/\.(ts|js|mjs|cjs)$/));
185
199
  const routes = [];
200
+ const ts = loadTypeScript(project.root);
186
201
  for (const file of tsFiles) {
187
202
  const content = await readFileSafe(file);
188
203
  if (!content.includes("express") && !content.includes("Router"))
189
204
  continue;
190
205
  const rel = relative(project.root, file);
206
+ const tags = detectTags(content);
207
+ // Try AST first
208
+ if (ts) {
209
+ const astRoutes = extractRoutesAST(ts, rel, content, "express", tags);
210
+ if (astRoutes.length > 0) {
211
+ routes.push(...astRoutes);
212
+ continue;
213
+ }
214
+ }
215
+ // Regex fallback
191
216
  const routePattern = /(?:app|router|server)\s*\.\s*(get|post|put|patch|delete|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
192
217
  let match;
193
218
  while ((match = routePattern.exec(content)) !== null) {
@@ -195,8 +220,9 @@ async function detectExpressRoutes(files, project) {
195
220
  method: match[1].toUpperCase(),
196
221
  path: match[2],
197
222
  file: rel,
198
- tags: detectTags(content),
223
+ tags,
199
224
  framework: "express",
225
+ confidence: "regex",
200
226
  });
201
227
  }
202
228
  }
@@ -206,11 +232,22 @@ async function detectExpressRoutes(files, project) {
206
232
  async function detectFastifyRoutes(files, project) {
207
233
  const tsFiles = files.filter((f) => f.match(/\.(ts|js|mjs|cjs)$/));
208
234
  const routes = [];
235
+ const ts = loadTypeScript(project.root);
209
236
  for (const file of tsFiles) {
210
237
  const content = await readFileSafe(file);
211
238
  if (!content.includes("fastify"))
212
239
  continue;
213
240
  const rel = relative(project.root, file);
241
+ const tags = detectTags(content);
242
+ // Try AST first
243
+ if (ts) {
244
+ const astRoutes = extractRoutesAST(ts, rel, content, "fastify", tags);
245
+ if (astRoutes.length > 0) {
246
+ routes.push(...astRoutes);
247
+ continue;
248
+ }
249
+ }
250
+ // Regex fallback
214
251
  const routePattern = /(?:fastify|server|app)\s*\.\s*(get|post|put|patch|delete|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
215
252
  let match;
216
253
  while ((match = routePattern.exec(content)) !== null) {
@@ -218,8 +255,9 @@ async function detectFastifyRoutes(files, project) {
218
255
  method: match[1].toUpperCase(),
219
256
  path: match[2],
220
257
  file: rel,
221
- tags: detectTags(content),
258
+ tags,
222
259
  framework: "fastify",
260
+ confidence: "regex",
223
261
  });
224
262
  }
225
263
  // Object-style route registration
@@ -229,8 +267,9 @@ async function detectFastifyRoutes(files, project) {
229
267
  method: match[1].toUpperCase(),
230
268
  path: match[2],
231
269
  file: rel,
232
- tags: detectTags(content),
270
+ tags,
233
271
  framework: "fastify",
272
+ confidence: "regex",
234
273
  });
235
274
  }
236
275
  }
@@ -240,11 +279,20 @@ async function detectFastifyRoutes(files, project) {
240
279
  async function detectKoaRoutes(files, project) {
241
280
  const tsFiles = files.filter((f) => f.match(/\.(ts|js|mjs|cjs)$/));
242
281
  const routes = [];
282
+ const ts = loadTypeScript(project.root);
243
283
  for (const file of tsFiles) {
244
284
  const content = await readFileSafe(file);
245
285
  if (!content.includes("koa") && !content.includes("Router"))
246
286
  continue;
247
287
  const rel = relative(project.root, file);
288
+ const tags = detectTags(content);
289
+ if (ts) {
290
+ const astRoutes = extractRoutesAST(ts, rel, content, "koa", tags);
291
+ if (astRoutes.length > 0) {
292
+ routes.push(...astRoutes);
293
+ continue;
294
+ }
295
+ }
248
296
  const routePattern = /router\s*\.\s*(get|post|put|patch|delete|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
249
297
  let match;
250
298
  while ((match = routePattern.exec(content)) !== null) {
@@ -252,8 +300,9 @@ async function detectKoaRoutes(files, project) {
252
300
  method: match[1].toUpperCase(),
253
301
  path: match[2],
254
302
  file: rel,
255
- tags: detectTags(content),
303
+ tags,
256
304
  framework: "koa",
305
+ confidence: "regex",
257
306
  });
258
307
  }
259
308
  }
@@ -263,15 +312,24 @@ async function detectKoaRoutes(files, project) {
263
312
  async function detectNestJSRoutes(files, project) {
264
313
  const tsFiles = files.filter((f) => f.match(/\.(ts|js)$/));
265
314
  const routes = [];
315
+ const ts = loadTypeScript(project.root);
266
316
  for (const file of tsFiles) {
267
317
  const content = await readFileSafe(file);
268
318
  if (!content.includes("@Controller") && !content.includes("@Get") && !content.includes("@Post"))
269
319
  continue;
270
320
  const rel = relative(project.root, file);
271
- // Extract controller base path: @Controller('users') or @Controller('/users')
321
+ const tags = detectTags(content);
322
+ // Try AST — NestJS benefits most from AST (decorator + controller prefix combining)
323
+ if (ts) {
324
+ const astRoutes = extractRoutesAST(ts, rel, content, "nestjs", tags);
325
+ if (astRoutes.length > 0) {
326
+ routes.push(...astRoutes);
327
+ continue;
328
+ }
329
+ }
330
+ // Regex fallback
272
331
  const controllerMatch = content.match(/@Controller\s*\(\s*['"`]([^'"`]*)['"`]\s*\)/);
273
332
  const basePath = controllerMatch ? "/" + controllerMatch[1].replace(/^\//, "") : "";
274
- // Match method decorators: @Get(), @Post('/create'), @Put(':id'), etc.
275
333
  const decoratorPattern = /@(Get|Post|Put|Patch|Delete|Options|Head|All)\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/gi;
276
334
  let match;
277
335
  while ((match = decoratorPattern.exec(content)) !== null) {
@@ -282,8 +340,9 @@ async function detectNestJSRoutes(files, project) {
282
340
  method,
283
341
  path: fullPath,
284
342
  file: rel,
285
- tags: detectTags(content),
343
+ tags,
286
344
  framework: "nestjs",
345
+ confidence: "regex",
287
346
  });
288
347
  }
289
348
  }
@@ -293,11 +352,20 @@ async function detectNestJSRoutes(files, project) {
293
352
  async function detectElysiaRoutes(files, project) {
294
353
  const tsFiles = files.filter((f) => f.match(/\.(ts|js|mjs)$/));
295
354
  const routes = [];
355
+ const ts = loadTypeScript(project.root);
296
356
  for (const file of tsFiles) {
297
357
  const content = await readFileSafe(file);
298
358
  if (!content.includes("elysia") && !content.includes("Elysia"))
299
359
  continue;
300
360
  const rel = relative(project.root, file);
361
+ const tags = detectTags(content);
362
+ if (ts) {
363
+ const astRoutes = extractRoutesAST(ts, rel, content, "elysia", tags);
364
+ if (astRoutes.length > 0) {
365
+ routes.push(...astRoutes);
366
+ continue;
367
+ }
368
+ }
301
369
  const routePattern = /\.\s*(get|post|put|patch|delete|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
302
370
  let match;
303
371
  while ((match = routePattern.exec(content)) !== null) {
@@ -308,8 +376,9 @@ async function detectElysiaRoutes(files, project) {
308
376
  method: match[1].toUpperCase(),
309
377
  path,
310
378
  file: rel,
311
- tags: detectTags(content),
379
+ tags,
312
380
  framework: "elysia",
381
+ confidence: "regex",
313
382
  });
314
383
  }
315
384
  }
@@ -341,6 +410,7 @@ async function detectAdonisRoutes(files, project) {
341
410
  async function detectTRPCRoutes(files, project) {
342
411
  const tsFiles = files.filter((f) => f.match(/\.(ts|js)$/));
343
412
  const routes = [];
413
+ const ts = loadTypeScript(project.root);
344
414
  for (const file of tsFiles) {
345
415
  const content = await readFileSafe(file);
346
416
  if (!content.includes("Procedure") && !content.includes("procedure") && !content.includes("router"))
@@ -348,11 +418,16 @@ async function detectTRPCRoutes(files, project) {
348
418
  if (!content.includes("trpc") && !content.includes("TRPC") && !content.includes("createTRPCRouter") && !content.includes("publicProcedure") && !content.includes("protectedProcedure"))
349
419
  continue;
350
420
  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
421
+ const tags = detectTags(content);
422
+ // AST handles tRPC much better — properly parses router nesting and procedure chains
423
+ if (ts) {
424
+ const astRoutes = extractRoutesAST(ts, rel, content, "trpc", tags);
425
+ if (astRoutes.length > 0) {
426
+ routes.push(...astRoutes);
427
+ continue;
428
+ }
429
+ }
430
+ // Regex fallback
356
431
  const lines = content.split("\n");
357
432
  for (const line of lines) {
358
433
  const queryMatch = line.match(/^\s*(\w+)\s*:\s*.*\.(query)\s*\(/);
@@ -366,8 +441,9 @@ async function detectTRPCRoutes(files, project) {
366
441
  method: isQuery ? "QUERY" : "MUTATION",
367
442
  path: procName,
368
443
  file: rel,
369
- tags: detectTags(content),
444
+ tags,
370
445
  framework: "trpc",
446
+ confidence: "regex",
371
447
  });
372
448
  }
373
449
  }
@@ -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.0";
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.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": {