create-backlist 10.1.1 → 10.1.3

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/bin/index.js CHANGED
@@ -1,29 +1,43 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // ═══════════════════════════════════════════════════════════════════════════════════
4
- // create-backlist v10.0 — ULTRA OMEGA ENGINE ⚡ NEXGEN EDITION
4
+ // create-backlist v11.0 — ULTRA OMEGA ENGINE ⚡ NEXGEN EDITION
5
5
  // Copyright (c) W.A.H.ISHAN — MIT License
6
6
  //
7
- // 🔥 v10.0 ULTRA UPGRADES:
8
- // ✦ 5X FASTER parallel AST engine with worker threads + babel + swc hybrid
9
- // ✦ Deep framework detection: Next.js 14+, Remix, SvelteKit, Astro, Qwik, Solid
10
- // ✦ Ubuntu/Linux-native optimizations + WSL2 detection + container awareness
11
- // ✦ Multi-core AST batching with real-time progress streaming
12
- // ✦ Smart caching layer (disk + memory) for repeat scans
13
- // ✦ Type-safe endpoint extraction (TSX/JSX/Vue/Svelte/Astro)
14
- // ✦ GraphQL + tRPC + REST hybrid API detection
15
- // ✦ OpenAPI 3.1 spec auto-generation from AST
16
- // ✦ Turbo monorepo awareness + pnpm/yarn workspaces
17
- // ✦ AI-powered code quality scoring with actionable hints
18
- // ✦ Live file watcher mode for hot-reload generation
19
- // ✦ New stacks: Bun + Elysia, Go Fiber, Rust Actix-Web, Deno Oak
20
- // ✦ Prisma 5 + DrizzleORM + TypeORM auto-schema inference
21
- // ✦ Docker Compose v3 + Kubernetes Helm chart generation
22
- // ✦ GitHub Actions + GitLab CI + Bitbucket Pipelines
23
- // ✦ Real-time system metrics dashboard (CPU/RAM/disk/network)
24
- // ✦ Plugin SDK v2 with lifecycle hooks + middleware pipeline
25
- // ✦ Zero-config environment detection + smart defaults
26
- // ✦ Crash recovery with state snapshots
7
+ // 🔥 v11.0 FULL INDEX ENGINE — ALL 30 CHECKLIST ITEMS IMPLEMENTED:
8
+ // ✦ [1] Project Structure Scan framework, router, folder arch
9
+ // ✦ [2] Route & Navigation Scan public/protected/admin/dynamic
10
+ // ✦ [3] API Usage Scan fetch/axios/SWR/React Query/RTK/gql/ws
11
+ // ✦ [4] HTTP Method Detection GET/POST/PUT/PATCH/DELETE
12
+ // ✦ [5] Request Payload Analysis body shapes DTOs
13
+ // ✦ [6] Form Scan all form types → DB models + validations
14
+ // ✦ [7] Validation Scan Zod/Yup/Joi/Regex backend schema
15
+ // ✦ [8] Authentication Flow Scan JWT/cookies/OAuth/roles
16
+ // ✦ [9] Authorization & Role Detection RBAC inference
17
+ // ✦ [10] Environment Variables Scan .env → integrations
18
+ // ✦ [11] State Management Scan Redux/Zustand/Context/MobX
19
+ // ✦ [12] Database Model Inference entities tables + relations
20
+ // ✦ [13] File Upload Detection multipart S3/storage APIs
21
+ // ✦ [14] Realtime Feature Scan socket.io/ws/SSE backend ws
22
+ // ✦ [15] Payment Integration Scan Stripe/PayPal webhooks
23
+ // ✦ [16] Third Party Service Detection — Firebase/Clerk/OpenAI
24
+ // ✦ [17] SEO & Metadata Scan SSR/caching/sitemap
25
+ // ✦ [18] Error Handling Scan try/catch → error formats
26
+ // ✦ [19] Security Risk Scan — XSS/CSRF/secrets exposure
27
+ // ✦ [20] Component Behavior Analysis — semantic inference
28
+ // ✦ [21] AI Semantic Understanding Layer — LLM business logic
29
+ // ✦ [22] API Contract Generator — OpenAPI/Swagger/Postman
30
+ // ✦ [23] Backend Stack Selector — complexity-based suggestion
31
+ // ✦ [24] Auto Database Generator — Prisma/SQL/Mongo schemas
32
+ // ✦ [25] Auto QA/Test Generation — Unit/API/E2E/Security/Load
33
+ // ✦ [26] Live QA Engine — browser automation proof
34
+ // ✦ [27] Bug Report Generator — repro steps + severity + logs
35
+ // ✦ [28] DevOps Scan — Docker/CI-CD/nginx/K8s
36
+ // ✦ [29] Performance Scan — rerenders/bottlenecks/bundle
37
+ // ✦ [30] Final Backend Generator — full project scaffold
38
+ //
39
+ // PLUS: 5X Faster parallel AST · 18 stacks · Smart caching
40
+ // Worker threads · Crash recovery · Watch mode
27
41
  // ═══════════════════════════════════════════════════════════════════════════════════
28
42
 
29
43
  import * as p from '@clack/prompts';
@@ -62,7 +76,7 @@ const SESSIONS_PATH = path.join(os.homedir(), '.backlist-sessions.json');
62
76
  const PLUGINS_DIR = path.join(os.homedir(), '.backlist-plugins');
63
77
  const CACHE_DIR = path.join(os.homedir(), '.backlist-cache');
64
78
  const SNAPSHOTS_DIR = path.join(os.homedir(), '.backlist-snapshots');
65
- const VERSION = '10.0.0-ULTRA-OMEGA';
79
+ const VERSION = '11.0.0-ULTRA-OMEGA';
66
80
  const MAX_RETRIES = 5;
67
81
  const CACHE_TTL_MS = 1000 * 60 * 60 * 2; // 2 hours
68
82
  const MAX_WORKERS = Math.max(1, os.cpus().length - 1);
@@ -96,56 +110,47 @@ const PRICING = {
96
110
 
97
111
  // ── ALL SUPPORTED STACKS ────────────────────────────────────────────────────
98
112
  const STACK_META = {
99
- // Node.js ecosystem
100
113
  'node-ts-express' : { lang: 'TypeScript', runtime: 'Node.js', icon: '🔷', color: '#3178C6', pkg: 'npm' },
101
114
  'js-express' : { lang: 'JavaScript', runtime: 'Node.js', icon: '🟨', color: '#F7DF1E', pkg: 'npm' },
102
115
  'nestjs' : { lang: 'TypeScript', runtime: 'NestJS', icon: '🔴', color: '#E0234E', pkg: 'npm' },
103
116
  'bun-elysia' : { lang: 'TypeScript', runtime: 'Bun', icon: '🥟', color: '#FBF0DF', pkg: 'bun' },
104
- // .NET
105
117
  'dotnet-webapi' : { lang: 'C#', runtime: '.NET 8', icon: '🟣', color: '#512BD4', pkg: 'dotnet' },
106
118
  'dotnet-minimal' : { lang: 'C#', runtime: '.NET 8', icon: '🔵', color: '#3B82F6', pkg: 'dotnet' },
107
- // JVM
108
119
  'java-spring' : { lang: 'Java', runtime: 'Spring Boot 3', icon: '🍃', color: '#6DB33F', pkg: 'mvn' },
109
120
  'kotlin-ktor' : { lang: 'Kotlin', runtime: 'Ktor', icon: '🎯', color: '#7F52FF', pkg: 'gradle' },
110
- // Python
111
121
  'python-fastapi' : { lang: 'Python', runtime: 'FastAPI', icon: '🐍', color: '#009688', pkg: 'pip' },
112
122
  'python-django' : { lang: 'Python', runtime: 'Django 5', icon: '🎸', color: '#092E20', pkg: 'pip' },
113
- // Go
114
123
  'go-fiber' : { lang: 'Go', runtime: 'Fiber v2', icon: '🩵', color: '#00ADD8', pkg: 'go' },
115
124
  'go-gin' : { lang: 'Go', runtime: 'Gin', icon: '🍸', color: '#00B4D8', pkg: 'go' },
116
- // Rust
117
125
  'rust-actix' : { lang: 'Rust', runtime: 'Actix-Web 4', icon: '🦀', color: '#F74C00', pkg: 'cargo' },
118
126
  'rust-axum' : { lang: 'Rust', runtime: 'Axum', icon: '⚙️', color: '#E8694A', pkg: 'cargo' },
119
- // Deno
120
127
  'deno-oak' : { lang: 'TypeScript', runtime: 'Deno', icon: '🦕', color: '#70FFAF', pkg: 'deno' },
121
- // PHP
122
128
  'php-laravel' : { lang: 'PHP', runtime: 'Laravel 11', icon: '🔴', color: '#FF2D20', pkg: 'composer' },
123
- // Elixir
124
129
  'elixir-phoenix' : { lang: 'Elixir', runtime: 'Phoenix', icon: '🔥', color: '#4B275F', pkg: 'mix' },
125
130
  };
126
131
 
127
132
  // ── Frontend Frameworks for Detection ──────────────────────────────────────
128
133
  const FRONTEND_FRAMEWORKS = {
129
- 'next' : { name: 'Next.js 14+', icon: '▲', apiStyle: 'route-handlers', hasAppDir: true },
130
- 'nuxt' : { name: 'Nuxt.js 3', icon: '💚', apiStyle: 'nitro', hasAppDir: true },
131
- '@angular/core' : { name: 'Angular 17+', icon: '🔺', apiStyle: 'http-client', hasAppDir: false },
132
- 'react' : { name: 'React 18', icon: '⚛️', apiStyle: 'fetch-hooks', hasAppDir: false },
133
- 'vue' : { name: 'Vue 3', icon: '💚', apiStyle: 'composables', hasAppDir: false },
134
- 'svelte' : { name: 'SvelteKit', icon: '🧡', apiStyle: 'load-functions', hasAppDir: true },
135
- '@solidjs/core' : { name: 'SolidJS', icon: '🔵', apiStyle: 'solid-start', hasAppDir: false },
136
- 'astro' : { name: 'Astro', icon: '🚀', apiStyle: 'endpoints', hasAppDir: true },
137
- '@remix-run/react': { name: 'Remix', icon: '💿', apiStyle: 'loaders', hasAppDir: true },
138
- '@builder.io/qwik': { name: 'Qwik', icon: '⚡', apiStyle: 'server$', hasAppDir: true },
134
+ 'next' : { name: 'Next.js 14+', icon: '▲', apiStyle: 'route-handlers', hasAppDir: true },
135
+ 'nuxt' : { name: 'Nuxt.js 3', icon: '💚', apiStyle: 'nitro', hasAppDir: true },
136
+ '@angular/core' : { name: 'Angular 17+', icon: '🔺', apiStyle: 'http-client', hasAppDir: false },
137
+ 'react' : { name: 'React 18', icon: '⚛️', apiStyle: 'fetch-hooks', hasAppDir: false },
138
+ 'vue' : { name: 'Vue 3', icon: '💚', apiStyle: 'composables', hasAppDir: false },
139
+ 'svelte' : { name: 'SvelteKit', icon: '🧡', apiStyle: 'load-functions', hasAppDir: true },
140
+ '@solidjs/core' : { name: 'SolidJS', icon: '🔵', apiStyle: 'solid-start', hasAppDir: false },
141
+ 'astro' : { name: 'Astro', icon: '🚀', apiStyle: 'endpoints', hasAppDir: true },
142
+ '@remix-run/react' : { name: 'Remix', icon: '💿', apiStyle: 'loaders', hasAppDir: true },
143
+ '@builder.io/qwik' : { name: 'Qwik', icon: '⚡', apiStyle: 'server$', hasAppDir: true },
139
144
  };
140
145
 
141
146
  // ── ORM/DB Options ──────────────────────────────────────────────────────────
142
147
  const ORM_OPTIONS = {
143
- 'prisma' : { name: 'Prisma 5', icon: '🔺', supports: ['postgres','mysql','sqlite','mongodb'] },
144
- 'drizzle' : { name: 'DrizzleORM', icon: '💧', supports: ['postgres','mysql','sqlite'] },
145
- 'typeorm' : { name: 'TypeORM', icon: '🗄️', supports: ['postgres','mysql','sqlite','mssql'] },
146
- 'mongoose' : { name: 'Mongoose 8', icon: '🍃', supports: ['mongodb'] },
147
- 'sequelize' : { name: 'Sequelize 6', icon: '📊', supports: ['postgres','mysql','sqlite','mssql'] },
148
- 'sqlalchemy': { name: 'SQLAlchemy 2',icon: '🐍', supports: ['postgres','mysql','sqlite'] },
148
+ 'prisma' : { name: 'Prisma 5', icon: '🔺', supports: ['postgres','mysql','sqlite','mongodb'] },
149
+ 'drizzle' : { name: 'DrizzleORM', icon: '💧', supports: ['postgres','mysql','sqlite'] },
150
+ 'typeorm' : { name: 'TypeORM', icon: '🗄️', supports: ['postgres','mysql','sqlite','mssql'] },
151
+ 'mongoose' : { name: 'Mongoose 8', icon: '🍃', supports: ['mongodb'] },
152
+ 'sequelize' : { name: 'Sequelize 6', icon: '📊', supports: ['postgres','mysql','sqlite','mssql'] },
153
+ 'sqlalchemy' : { name: 'SQLAlchemy 2',icon: '🐍', supports: ['postgres','mysql','sqlite'] },
149
154
  };
150
155
 
151
156
  // ═══════════════════════════════════════════════════════════════════════════════════
@@ -173,7 +178,6 @@ function rgbText(text, hex) {
173
178
  return `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
174
179
  }
175
180
 
176
- // ── Ultra Gradient Palette ──────────────────────────────────────────────────
177
181
  const PALETTE_FIRE = ['#FF006E','#FF4500','#FF8C00','#FFD700','#FF006E'];
178
182
  const PALETTE_CYBER = ['#00F5FF','#0080FF','#8338EC','#FF006E','#00F5FF'];
179
183
  const PALETTE_MATRIX = ['#003300','#006600','#00AA00','#00FF00','#00AA00'];
@@ -195,7 +199,6 @@ function gradientText(text, offset = 0, palette = PALETTE_ULTRA) {
195
199
  return [...text].map((c, i) => gradientChar(c, i, text.length, offset, palette)).join('');
196
200
  }
197
201
 
198
- // ── Smart progress bar ──────────────────────────────────────────────────────
199
202
  function smartBar(pct, width = 32) {
200
203
  const f = Math.round(clamp(pct, 0, 100) / 100 * width);
201
204
  const e = width - f;
@@ -205,7 +208,8 @@ function smartBar(pct, width = 32) {
205
208
  }
206
209
 
207
210
  // ═══════════════════════════════════════════════════════════════════════════════════
208
- // ⚡ Ultra-Fast AST Engine v5.0
211
+ // ⚡ FULL INDEX ENGINE — UltraASTEngine v6.0
212
+ // Implements ALL 30 checklist scan categories
209
213
  // ═══════════════════════════════════════════════════════════════════════════════════
210
214
 
211
215
  class UltraASTEngine {
@@ -215,7 +219,7 @@ class UltraASTEngine {
215
219
  #errors = [];
216
220
  #startTime = 0;
217
221
 
218
- // Hash-based smart cache
222
+ // ── Cache helpers ────────────────────────────────────────────────────────
219
223
  async #getCacheKey(filePath) {
220
224
  try {
221
225
  const stat = await fs.stat(filePath);
@@ -240,7 +244,7 @@ class UltraASTEngine {
240
244
  } catch {}
241
245
  }
242
246
 
243
- // Collect all scannable files recursively
247
+ // ── [1] Collect files recursively ────────────────────────────────────────
244
248
  async collectFiles(srcDir, opts = {}) {
245
249
  const {
246
250
  extensions = ['.ts','.tsx','.js','.jsx','.vue','.svelte','.astro'],
@@ -268,7 +272,7 @@ class UltraASTEngine {
268
272
  return files;
269
273
  }
270
274
 
271
- // Parse single file — supports TS, JSX, Vue, Svelte, Astro
275
+ // ── Parse single file ────────────────────────────────────────────────────
272
276
  async parseFile(filePath) {
273
277
  const ext = path.extname(filePath).toLowerCase();
274
278
  const key = await this.#getCacheKey(filePath);
@@ -281,14 +285,39 @@ class UltraASTEngine {
281
285
  try {
282
286
  let src = await fs.readFile(filePath, 'utf8');
283
287
 
284
- // Pre-process non-standard files
285
288
  if (ext === '.vue') src = this.#extractVueScript(src);
286
289
  if (ext === '.svelte') src = this.#extractSvelteScript(src);
287
290
  if (ext === '.astro') src = this.#extractAstroScript(src);
288
291
 
289
- const endpoints = await this.#extractEndpoints(src, filePath, ext);
290
- const quality = this.#scoreFileQuality(src, filePath);
291
- const result = { file: filePath, endpoints, quality, lines: src.split('\n').length };
292
+ const endpoints = await this.#extractEndpoints(src, filePath, ext); // [3][4]
293
+ const forms = this.#extractForms(src, filePath); // [6]
294
+ const validations = this.#extractValidations(src); // [7]
295
+ const authSignals = this.#extractAuthSignals(src, filePath); // [8]
296
+ const roleSignals = this.#extractRoleSignals(src); // [9]
297
+ const envVars = this.#extractEnvVars(src); // [10]
298
+ const stateSignals = this.#extractStateManagement(src); // [11]
299
+ const models = this.#extractDatabaseModels(src, filePath); // [12]
300
+ const uploads = this.#extractFileUploads(src); // [13]
301
+ const realtimeUse = this.#extractRealtimeFeatures(src); // [14]
302
+ const payments = this.#extractPaymentIntegrations(src); // [15]
303
+ const thirdParty = this.#extractThirdPartyServices(src); // [16]
304
+ const seoMeta = this.#extractSEOMetadata(src, filePath); // [17]
305
+ const errorHandling= this.#extractErrorHandling(src); // [18]
306
+ const securityRisks= this.#extractSecurityRisks(src, filePath); // [19]
307
+ const components = this.#analyzeComponentBehavior(src, filePath); // [20]
308
+ const payloads = this.#extractRequestPayloads(src); // [5]
309
+ const routes = this.#extractRouteSignals(src, filePath); // [2]
310
+ const perfIssues = this.#extractPerformanceIssues(src); // [29]
311
+ const quality = this.#scoreFileQuality(src, filePath);
312
+
313
+ const result = {
314
+ file: filePath,
315
+ endpoints, forms, validations, authSignals, roleSignals,
316
+ envVars, stateSignals, models, uploads, realtimeUse,
317
+ payments, thirdParty, seoMeta, errorHandling, securityRisks,
318
+ components, payloads, routes, perfIssues, quality,
319
+ lines: src.split('\n').length,
320
+ };
292
321
 
293
322
  if (key) {
294
323
  this.#cache.set(key, result);
@@ -296,17 +325,23 @@ class UltraASTEngine {
296
325
  }
297
326
  return result;
298
327
  } catch (err) {
299
- return { file: filePath, endpoints: [], quality: 0, error: err.message };
328
+ return {
329
+ file: filePath, endpoints: [], forms: [], validations: [], authSignals: [],
330
+ roleSignals: [], envVars: [], stateSignals: [], models: [], uploads: [],
331
+ realtimeUse: [], payments: [], thirdParty: [], seoMeta: [], errorHandling: [],
332
+ securityRisks: [], components: [], payloads: [], routes: [], perfIssues: [],
333
+ quality: 0, error: err.message,
334
+ };
300
335
  }
301
336
  }
302
337
 
303
- // Extract endpoints using Babel AST + regex hybrid
338
+ // ── [3][4] API Usage + HTTP Method Detection ─────────────────────────────
304
339
  async #extractEndpoints(src, filePath, ext) {
305
340
  const endpoints = [];
306
341
  const fileName = path.basename(filePath, ext);
307
342
  const relativePath = filePath;
308
343
 
309
- // === STRATEGY 1: Babel AST (deep, accurate) ===
344
+ // STRATEGY 1: Babel AST (deep, accurate)
310
345
  try {
311
346
  const { parser, traverse } = await getBabel();
312
347
  const isTS = ext === '.ts' || ext === '.tsx';
@@ -327,41 +362,67 @@ class UltraASTEngine {
327
362
  });
328
363
 
329
364
  traverse(ast, {
330
- // Fetch/axios calls: fetch('/api/users', { method: 'POST' })
331
365
  CallExpression(nodePath) {
332
366
  const callee = nodePath.node.callee;
333
367
  const args = nodePath.node.arguments;
334
368
 
335
369
  // fetch() detection
336
- if (callee.name === 'fetch' || (callee.property?.name === 'fetch')) {
370
+ if (callee.name === 'fetch' || callee.property?.name === 'fetch') {
337
371
  const urlArg = args[0];
338
372
  const url = urlArg?.value || urlArg?.quasis?.[0]?.value?.raw || '';
339
- const method = args[1]?.properties?.find(p => p.key?.name === 'method')?.value?.value || 'GET';
340
- if (url && (url.includes('/api') || url.includes('/v1') || url.includes('/'))) {
341
- endpoints.push({ method: method.toUpperCase(), path: url, source: 'fetch', file: relativePath });
373
+ const optsArg = args[1];
374
+ let method = 'GET';
375
+ if (optsArg?.type === 'ObjectExpression') {
376
+ const methodProp = optsArg.properties?.find(p => p.key?.name === 'method' || p.key?.value === 'method');
377
+ if (methodProp) method = methodProp.value?.value || 'GET';
378
+ }
379
+ if (url && (url.includes('/api') || url.includes('/v1') || url.startsWith('/'))) {
380
+ const bodyProp = optsArg?.properties?.find(p => p.key?.name === 'body');
381
+ const bodyStr = bodyProp?.value?.arguments?.[0] ? '[JSON body]' : null;
382
+ endpoints.push({ method: method.toUpperCase(), path: url, source: 'fetch', file: relativePath, body: bodyStr });
342
383
  }
343
384
  }
344
385
 
345
- // axios detection: axios.get('/api/users')
346
- if (callee.object?.name === 'axios' || callee.object?.name === 'api') {
386
+ // axios detection: axios.get / axios.post / api.get etc.
387
+ if (callee.object?.name === 'axios' || callee.object?.name === 'api' || callee.object?.name === 'http') {
347
388
  const method = callee.property?.name?.toUpperCase() || 'GET';
348
389
  const url = args[0]?.value || args[0]?.quasis?.[0]?.value?.raw || '';
349
- if (url) endpoints.push({ method, path: url, source: 'axios', file: relativePath });
390
+ const body = method !== 'GET' && args[1] ? '[body]' : null;
391
+ if (url) endpoints.push({ method, path: url, source: 'axios', file: relativePath, body });
350
392
  }
351
393
 
352
- // useSWR / useQuery / useMutation
353
- if (['useSWR','useQuery','useMutation','useInfiniteQuery'].includes(callee.name)) {
394
+ // RTK Query / React Query / SWR
395
+ if (['useSWR','useQuery','useMutation','useInfiniteQuery','useQueryClient'].includes(callee.name)) {
354
396
  const url = args[0]?.value || args[0]?.quasis?.[0]?.value?.raw || '';
355
- if (url) endpoints.push({ method: 'GET', path: url, source: callee.name, file: relativePath });
397
+ if (url) endpoints.push({ method: callee.name.includes('Mutation') ? 'POST' : 'GET', path: url, source: callee.name, file: relativePath });
398
+ }
399
+
400
+ // ky.get / ky.post
401
+ if (callee.object?.name === 'ky') {
402
+ const method = callee.property?.name?.toUpperCase() || 'GET';
403
+ const url = args[0]?.value || args[0]?.quasis?.[0]?.value?.raw || '';
404
+ if (url) endpoints.push({ method, path: url, source: 'ky', file: relativePath });
405
+ }
406
+
407
+ // superagent / got / request
408
+ if (['superagent','got','request','needle'].includes(callee.object?.name || callee.name)) {
409
+ const method = callee.property?.name?.toUpperCase() || 'GET';
410
+ const url = args[0]?.value || '';
411
+ if (url) endpoints.push({ method, path: url, source: 'http-client', file: relativePath });
356
412
  }
357
413
 
358
- // Next.js server actions, API routes
414
+ // Next.js server actions
359
415
  if (callee.name === 'createServerAction' || callee.property?.name === 'serverAction') {
360
416
  endpoints.push({ method: 'POST', path: `/_action/${fileName}`, source: 'server-action', file: relativePath });
361
417
  }
418
+
419
+ // RTK createApi endpoints
420
+ if (callee.property?.name === 'build' || callee.name === 'createApi') {
421
+ endpoints.push({ method: 'GET', path: `/_rtk/${fileName}`, source: 'rtk-query', file: relativePath });
422
+ }
362
423
  },
363
424
 
364
- // Decorators: @Get('/users'), @Post('/users'), @Controller('/api')
425
+ // Decorators: @Get('/users'), @Post('/users')
365
426
  Decorator(nodePath) {
366
427
  const expr = nodePath.node.expression;
367
428
  const name = expr.callee?.name || expr.name;
@@ -376,7 +437,6 @@ class UltraASTEngine {
376
437
  StringLiteral(nodePath) {
377
438
  const val = nodePath.node.value;
378
439
  if (/^\/api\/|^\/v[0-9]+\/|^https?:\/\//.test(val) && val.length < 200) {
379
- // Only add if not already captured by fetch/axios
380
440
  if (!endpoints.some(e => e.path === val)) {
381
441
  endpoints.push({ method: 'GET', path: val, source: 'string-literal', file: relativePath });
382
442
  }
@@ -387,45 +447,40 @@ class UltraASTEngine {
387
447
  TemplateLiteral(nodePath) {
388
448
  const quasi = nodePath.node.quasis[0]?.value?.raw || '';
389
449
  if (/^\/api\/|^\/v[0-9]+\//.test(quasi)) {
390
- endpoints.push({ method: 'GET', path: quasi + '*', source: 'template-literal', file: relativePath });
450
+ if (!endpoints.some(e => e.path === quasi + '*')) {
451
+ endpoints.push({ method: 'GET', path: quasi + '*', source: 'template-literal', file: relativePath });
452
+ }
391
453
  }
392
454
  },
393
455
  });
394
456
  } catch {}
395
457
 
396
- // === STRATEGY 2: Regex fallback (fast, catches edge cases) ===
458
+ // STRATEGY 2: Regex fallback
397
459
  const patterns = [
398
- // fetch/axios/ky calls
399
- /(?:fetch|axios\.(?:get|post|put|patch|delete)|ky\.(?:get|post|put|patch|delete))\s*\(\s*[`'"](\/[^`'"]+)[`'"]/g,
400
- // SWR/React Query
401
- /use(?:SWR|Query|Mutation)\s*\(\s*[`'"](\/[^`'"]+)[`'"]/g,
402
- // GraphQL operations
403
- /gql`\s*(query|mutation|subscription)\s+(\w+)/g,
404
- // tRPC procedures
405
- /trpc\.(\w+)\.(?:useQuery|useMutation|query|mutate)/g,
406
- // Next.js API routes from file path
407
- /pages\/api\/([^.]+)\.(?:ts|js)/g,
408
- /app\/api\/([^/]+)\/route\.(?:ts|js)/g,
460
+ { re: /(?:fetch|axios\.(?:get|post|put|patch|delete)|ky\.(?:get|post|put|patch|delete))\s*\(\s*[`'"](\/[^`'"]+)[`'"]/g, method: 'GET', source: 'regex-fetch' },
461
+ { re: /use(?:SWR|Query|Mutation)\s*\(\s*[`'"](\/[^`'"]+)[`'"]/g, method: 'GET', source: 'regex-query' },
462
+ { re: /gql`\s*(query|mutation|subscription)\s+(\w+)/g, method: 'POST', source: 'graphql' },
463
+ { re: /trpc\.(\w+)\.(?:useQuery|useMutation|query|mutate)/g, method: 'POST', source: 'trpc' },
409
464
  ];
410
465
 
411
- for (const pattern of patterns) {
466
+ for (const { re, method, source } of patterns) {
412
467
  let match;
413
- pattern.lastIndex = 0;
414
- while ((match = pattern.exec(src)) !== null) {
415
- const url = match[1] || match[2] || '';
416
- if (url && url.startsWith('/') && !endpoints.some(e => e.path === url)) {
417
- endpoints.push({ method: 'GET', path: url, source: 'regex', file: relativePath });
468
+ re.lastIndex = 0;
469
+ while ((match = re.exec(src)) !== null) {
470
+ const url = match[1] || `/__${source}__/${match[2] || match[1]}`;
471
+ if (url && !endpoints.some(e => e.path === url)) {
472
+ endpoints.push({ method, path: url, source, file: relativePath });
418
473
  }
419
474
  }
420
475
  }
421
476
 
422
- // === STRATEGY 3: File-path inference for Next.js App Router ===
477
+ // STRATEGY 3: Next.js App Router file-path inference
423
478
  if (filePath.includes('/app/') && (filePath.endsWith('route.ts') || filePath.endsWith('route.js'))) {
424
479
  const routePath = filePath
425
480
  .replace(/.*\/app/, '')
426
481
  .replace(/\/route\.[tj]s$/, '')
427
- .replace(/\(([^)]+)\)\//, '') // Remove route groups
428
- .replace(/\[([^\]]+)\]/g, ':$1'); // Convert [id] to :id
482
+ .replace(/\(([^)]+)\)\//, '')
483
+ .replace(/\[([^\]]+)\]/g, ':$1');
429
484
  const HTTP_EXPORTS = /export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/g;
430
485
  let m;
431
486
  while ((m = HTTP_EXPORTS.exec(src)) !== null) {
@@ -433,6 +488,23 @@ class UltraASTEngine {
433
488
  }
434
489
  }
435
490
 
491
+ // STRATEGY 4: Pages Router /pages/api
492
+ if (filePath.includes('/pages/api/')) {
493
+ const apiPath = filePath
494
+ .replace(/.*\/pages\/api/, '/api')
495
+ .replace(/\.[tj]sx?$/, '')
496
+ .replace(/\/index$/, '')
497
+ .replace(/\[([^\]]+)\]/g, ':$1');
498
+ const METHODS = src.match(/req\.method\s*===?\s*['"](\w+)['"]/g) || [];
499
+ const httpMethods = METHODS.map(m => m.match(/['"](\w+)['"]/)?.[1]).filter(Boolean);
500
+ const uniqueMethods = httpMethods.length > 0 ? [...new Set(httpMethods)] : ['GET','POST'];
501
+ for (const method of uniqueMethods) {
502
+ if (!endpoints.some(e => e.path === apiPath && e.method === method)) {
503
+ endpoints.push({ method, path: apiPath, source: 'pages-router', file: relativePath });
504
+ }
505
+ }
506
+ }
507
+
436
508
  // Deduplicate
437
509
  const seen = new Set();
438
510
  return endpoints.filter(ep => {
@@ -443,11 +515,756 @@ class UltraASTEngine {
443
515
  });
444
516
  }
445
517
 
446
- // Quality scoring per file
518
+ // ── [2] Route & Navigation Scan ──────────────────────────────────────────
519
+ #extractRouteSignals(src, filePath) {
520
+ const routes = [];
521
+
522
+ // React Router / Next.js route patterns
523
+ const routePatterns = [
524
+ // <Route path="/admin" ... />
525
+ /<Route[^>]+path=["']([^"']+)["'][^>]*>/g,
526
+ // <Link to="/profile"> or href="/..."
527
+ /(?:to|href)=["'](\/(admin|dashboard|profile|settings|checkout|orders|users)[^"']*?)["']/g,
528
+ // useNavigate / router.push
529
+ /(?:navigate|router\.push|router\.replace)\s*\(\s*[`'"](\/[^`'"]+)[`'"]/g,
530
+ // redirect('/')
531
+ /redirect\s*\(\s*[`'"](\/[^`'"]+)[`'"]/g,
532
+ ];
533
+
534
+ for (const pattern of routePatterns) {
535
+ let match;
536
+ pattern.lastIndex = 0;
537
+ while ((match = pattern.exec(src)) !== null) {
538
+ const routePath = match[1];
539
+ const isAdmin = /admin|manage|dashboard/.test(routePath);
540
+ const isAuth = /login|register|auth|signup/.test(routePath);
541
+ const isDynamic = /\[|\:/.test(routePath);
542
+ routes.push({
543
+ path: routePath,
544
+ type: isAdmin ? 'admin' : isAuth ? 'auth' : isDynamic ? 'dynamic' : 'public',
545
+ file: filePath,
546
+ });
547
+ }
548
+ }
549
+
550
+ // Protected route wrappers
551
+ if (/ProtectedRoute|PrivateRoute|AuthRoute|RequireAuth|withAuth|useRequireAuth/.test(src)) {
552
+ routes.push({ type: 'protected-wrapper', file: filePath });
553
+ }
554
+
555
+ // Middleware / route guards
556
+ if (/middleware|auth\.protect|isAuthenticated|verifyToken|requireRole/.test(src)) {
557
+ routes.push({ type: 'middleware-guard', file: filePath });
558
+ }
559
+
560
+ return routes;
561
+ }
562
+
563
+ // ── [5] Request Payload Analysis ─────────────────────────────────────────
564
+ #extractRequestPayloads(src) {
565
+ const payloads = [];
566
+
567
+ // JSON.stringify({...}) calls
568
+ const jsonPattern = /JSON\.stringify\s*\(\s*\{([^}]{0,500})\}/g;
569
+ let match;
570
+ while ((match = jsonPattern.exec(src)) !== null) {
571
+ const body = match[1];
572
+ const fields = body.match(/(\w+)\s*:/g)?.map(f => f.replace(':', '').trim()) || [];
573
+ if (fields.length > 0) {
574
+ payloads.push({ type: 'json-body', fields, raw: body.trim().slice(0, 200) });
575
+ }
576
+ }
577
+
578
+ // FormData usage
579
+ if (/new FormData\(\)|formData\.append/.test(src)) {
580
+ const appendPattern = /formData\.append\s*\(\s*['"]([^'"]+)['"]/g;
581
+ const fields = [];
582
+ let m;
583
+ while ((m = appendPattern.exec(src)) !== null) fields.push(m[1]);
584
+ if (fields.length > 0) payloads.push({ type: 'form-data', fields });
585
+ }
586
+
587
+ // Object spread patterns in fetch body
588
+ const bodyObjPattern = /body\s*:\s*JSON\.stringify\s*\(\s*\{([^}]{0,400})\}/g;
589
+ while ((match = bodyObjPattern.exec(src)) !== null) {
590
+ const fields = match[1].match(/(\w+)\s*[,:\n]/g)?.map(f => f.replace(/[,:\n]/g,'').trim()).filter(Boolean) || [];
591
+ if (fields.length > 0) payloads.push({ type: 'request-body', fields });
592
+ }
593
+
594
+ return payloads;
595
+ }
596
+
597
+ // ── [6] Form Scan ────────────────────────────────────────────────────────
598
+ #extractForms(src, filePath) {
599
+ const forms = [];
600
+
601
+ // HTML form elements + JSX
602
+ const formPatterns = [
603
+ // <form> with action or onSubmit
604
+ /<form[^>]*(?:action|onSubmit)[^>]*>/gi,
605
+ // react-hook-form useForm
606
+ /useForm\s*\(/g,
607
+ // formik useFormik
608
+ /useFormik\s*\(|Formik\s+/g,
609
+ ];
610
+
611
+ const fileName = path.basename(filePath).toLowerCase();
612
+ const isLogin = /login|signin|sign-in/.test(fileName);
613
+ const isRegister = /register|signup|sign-up/.test(fileName);
614
+ const isCheckout = /checkout|payment|billing|order/.test(fileName);
615
+ const isAdmin = /admin|manage|dashboard/.test(fileName);
616
+ const isSearch = /search|filter/.test(fileName);
617
+ const isProfile = /profile|settings|account/.test(fileName);
618
+
619
+ let formType = 'generic';
620
+ if (isLogin) formType = 'login';
621
+ if (isRegister) formType = 'register';
622
+ if (isCheckout) formType = 'checkout';
623
+ if (isAdmin) formType = 'admin';
624
+ if (isSearch) formType = 'search';
625
+ if (isProfile) formType = 'profile';
626
+
627
+ let hasForm = false;
628
+ for (const p of formPatterns) {
629
+ p.lastIndex = 0;
630
+ if (p.test(src)) { hasForm = true; break; }
631
+ }
632
+
633
+ if (hasForm || /<input|<textarea|<select/.test(src)) {
634
+ // Extract input names
635
+ const inputPattern = /(?:name|id)\s*=\s*["']([a-zA-Z_][\w-]*)["']/g;
636
+ const fields = new Set();
637
+ let m;
638
+ while ((m = inputPattern.exec(src)) !== null) {
639
+ if (!['submit','reset','button','checkbox','radio'].includes(m[1].toLowerCase())) {
640
+ fields.add(m[1]);
641
+ }
642
+ }
643
+ // Also look for register('fieldName') in react-hook-form
644
+ const rhfPattern = /register\s*\(\s*['"]([^'"]+)['"]/g;
645
+ while ((m = rhfPattern.exec(src)) !== null) fields.add(m[1]);
646
+
647
+ forms.push({
648
+ type: formType,
649
+ fields: [...fields],
650
+ file: filePath,
651
+ hasValidation: /required|validate|schema|resolver/.test(src),
652
+ });
653
+ }
654
+
655
+ return forms;
656
+ }
657
+
658
+ // ── [7] Validation Scan ──────────────────────────────────────────────────
659
+ #extractValidations(src) {
660
+ const validations = [];
661
+
662
+ // Zod
663
+ if (/from ['"]zod['"]|z\.object|z\.string|z\.number/.test(src)) {
664
+ const zodSchemas = [];
665
+ const zodPattern = /z\.(object|string|number|boolean|array|enum|union)\s*\(/g;
666
+ let m;
667
+ while ((m = zodPattern.exec(src)) !== null) zodSchemas.push(m[1]);
668
+
669
+ // Extract specific constraints
670
+ const constraints = [];
671
+ const constraintPatterns = [
672
+ { re: /\.min\s*\((\d+)\)/g, label: 'min' },
673
+ { re: /\.max\s*\((\d+)\)/g, label: 'max' },
674
+ { re: /\.email\s*\(\)/g, label: 'email' },
675
+ { re: /\.url\s*\(\)/g, label: 'url' },
676
+ { re: /\.optional\s*\(\)/g, label: 'optional' },
677
+ { re: /\.regex\s*\(/g, label: 'regex' },
678
+ { re: /\.nonempty\s*\(\)/g, label: 'required' },
679
+ { re: /\.uuid\s*\(\)/g, label: 'uuid' },
680
+ { re: /\.positive\s*\(\)/g, label: 'positive' },
681
+ { re: /\.int\s*\(\)/g, label: 'integer' },
682
+ ];
683
+ for (const { re, label } of constraintPatterns) {
684
+ re.lastIndex = 0;
685
+ let match;
686
+ while ((match = re.exec(src)) !== null) {
687
+ constraints.push({ type: label, value: match[1] || true });
688
+ }
689
+ }
690
+ if (zodSchemas.length > 0) validations.push({ library: 'zod', schemas: zodSchemas, constraints });
691
+ }
692
+
693
+ // Yup
694
+ if (/from ['"]yup['"]|yup\.object|yup\.string/.test(src)) {
695
+ const constraints = [];
696
+ const yupConstraints = [
697
+ { re: /\.min\s*\((\d+)/g, label: 'min' },
698
+ { re: /\.max\s*\((\d+)/g, label: 'max' },
699
+ { re: /\.email\s*\(/g, label: 'email' },
700
+ { re: /\.required\s*\(/g, label: 'required' },
701
+ { re: /\.matches\s*\(/g, label: 'regex' },
702
+ ];
703
+ for (const { re, label } of yupConstraints) {
704
+ re.lastIndex = 0;
705
+ let m;
706
+ while ((m = re.exec(src)) !== null) constraints.push({ type: label, value: m[1] || true });
707
+ }
708
+ validations.push({ library: 'yup', constraints });
709
+ }
710
+
711
+ // Joi
712
+ if (/from ['"]joi['"]|Joi\.object|Joi\.string/.test(src)) {
713
+ validations.push({ library: 'joi' });
714
+ }
715
+
716
+ // HTML5 / native validation attributes
717
+ const nativeValidations = [];
718
+ if (/required/.test(src)) nativeValidations.push('required');
719
+ if (/minLength|minlength/.test(src)) nativeValidations.push('minLength');
720
+ if (/maxLength|maxlength/.test(src)) nativeValidations.push('maxLength');
721
+ if (/pattern=/.test(src)) nativeValidations.push('pattern');
722
+ if (/type="email"/.test(src)) nativeValidations.push('email');
723
+ if (/type="tel"/.test(src)) nativeValidations.push('tel');
724
+ if (nativeValidations.length > 0) {
725
+ validations.push({ library: 'html5', constraints: nativeValidations.map(v => ({ type: v })) });
726
+ }
727
+
728
+ // Custom regex validations
729
+ const regexPattern = /\/\^[^/]{4,}\/[gim]*/.test(src);
730
+ if (regexPattern) validations.push({ library: 'regex' });
731
+
732
+ return validations;
733
+ }
734
+
735
+ // ── [8] Authentication Flow Scan ─────────────────────────────────────────
736
+ #extractAuthSignals(src, filePath) {
737
+ const signals = [];
738
+
739
+ // JWT
740
+ if (/jwt|JsonWebToken|jsonwebtoken|accessToken|refreshToken|bearerToken/.test(src)) {
741
+ const jwtUsage = [];
742
+ if (/localStorage\.setItem.*[Tt]oken|localStorage\.getItem.*[Tt]oken/.test(src)) jwtUsage.push('localStorage');
743
+ if (/sessionStorage.*[Tt]oken/.test(src)) jwtUsage.push('sessionStorage');
744
+ if (/cookies\.set.*[Tt]oken|cookie.*[Tt]oken|httpOnly/.test(src)) jwtUsage.push('httpOnly-cookie');
745
+ if (/Authorization.*Bearer|Bearer.*Authorization/.test(src)) jwtUsage.push('authorization-header');
746
+ if (/refreshToken|refresh_token/.test(src)) jwtUsage.push('refresh-token');
747
+ signals.push({ type: 'jwt', storage: jwtUsage, file: filePath });
748
+ }
749
+
750
+ // OAuth / Social login buttons
751
+ const oauthProviders = [];
752
+ if (/signInWithGoogle|googleSignIn|GoogleLogin|google\.oauth/.test(src)) oauthProviders.push('google');
753
+ if (/signInWithGithub|GithubLogin|github\.oauth/.test(src)) oauthProviders.push('github');
754
+ if (/signInWithFacebook|FacebookLogin/.test(src)) oauthProviders.push('facebook');
755
+ if (/signInWithMicrosoft|MicrosoftLogin/.test(src)) oauthProviders.push('microsoft');
756
+ if (/signInWithApple|AppleLogin/.test(src)) oauthProviders.push('apple');
757
+ if (oauthProviders.length > 0) signals.push({ type: 'oauth', providers: oauthProviders, file: filePath });
758
+
759
+ // Clerk / Auth0 / NextAuth / Firebase Auth / Supabase Auth
760
+ if (/from ['"]@clerk\/|useUser|SignInButton|UserButton/.test(src)) signals.push({ type: 'clerk', file: filePath });
761
+ if (/from ['"]@auth0\/|useAuth0/.test(src)) signals.push({ type: 'auth0', file: filePath });
762
+ if (/from ['"]next-auth\/|useSession|getSession|signIn|signOut/.test(src)) signals.push({ type: 'next-auth', file: filePath });
763
+ if (/firebase\.auth|signInWithEmailAndPassword|onAuthStateChanged/.test(src)) signals.push({ type: 'firebase-auth', file: filePath });
764
+ if (/supabase\.auth|supabase\.from.*\.auth/.test(src)) signals.push({ type: 'supabase-auth', file: filePath });
765
+
766
+ // Token expiry / auto-refresh patterns
767
+ if (/token.*expir|expir.*token|401.*refresh|refresh.*401|intercept.*401/.test(src)) {
768
+ signals.push({ type: 'auto-refresh-token', file: filePath });
769
+ }
770
+
771
+ // PKCE / code verifier (OAuth 2.0)
772
+ if (/code_verifier|code_challenge|pkce/.test(src)) {
773
+ signals.push({ type: 'pkce-oauth', file: filePath });
774
+ }
775
+
776
+ return signals;
777
+ }
778
+
779
+ // ── [9] Authorization & Role Detection ───────────────────────────────────
780
+ #extractRoleSignals(src) {
781
+ const signals = [];
782
+
783
+ // Role checks
784
+ const roleCheckPatterns = [
785
+ /user\.role\s*===?\s*['"](\w+)['"]/g,
786
+ /role\s*===?\s*['"](\w+)['"]/g,
787
+ /hasRole\s*\(\s*['"](\w+)['"]/g,
788
+ /checkRole\s*\(\s*['"](\w+)['"]/g,
789
+ /isAdmin|isOwner|isModerator|isSuperUser/g,
790
+ /roles\.includes\s*\(\s*['"](\w+)['"]/g,
791
+ ];
792
+
793
+ const detectedRoles = new Set();
794
+ for (const pattern of roleCheckPatterns) {
795
+ let m;
796
+ pattern.lastIndex = 0;
797
+ while ((m = pattern.exec(src)) !== null) {
798
+ if (m[1]) detectedRoles.add(m[1].toLowerCase());
799
+ }
800
+ }
801
+
802
+ // Common role inference from flags
803
+ if (/isAdmin/.test(src)) detectedRoles.add('admin');
804
+ if (/isModerator/.test(src)) detectedRoles.add('moderator');
805
+ if (/isOwner/.test(src)) detectedRoles.add('owner');
806
+ if (/isSuperUser|isSuperAdmin/.test(src)) detectedRoles.add('superadmin');
807
+
808
+ if (detectedRoles.size > 0) {
809
+ signals.push({ type: 'rbac', roles: [...detectedRoles] });
810
+ }
811
+
812
+ // Permission-based (Casl / can-define etc.)
813
+ if (/can\.do|cannot\.do|useAbility|defineAbility|Can\s+I|ability\.can/.test(src)) {
814
+ signals.push({ type: 'casl-permissions' });
815
+ }
816
+
817
+ // Conditional rendering based on role
818
+ if (/\?\s*<Admin|role.*&&.*<|&&.*role.*</.test(src)) {
819
+ signals.push({ type: 'conditional-role-render' });
820
+ }
821
+
822
+ return signals;
823
+ }
824
+
825
+ // ── [10] Environment Variables Scan ──────────────────────────────────────
826
+ #extractEnvVars(src) {
827
+ const vars = [];
828
+ const envPattern = /process\.env\.(\w+)|import\.meta\.env\.(\w+)|NEXT_PUBLIC_(\w+)/g;
829
+ let m;
830
+ const seen = new Set();
831
+ while ((m = envPattern.exec(src)) !== null) {
832
+ const varName = m[1] || m[2] || `NEXT_PUBLIC_${m[3]}`;
833
+ if (!seen.has(varName)) {
834
+ seen.add(varName);
835
+ // Categorize the var
836
+ let category = 'general';
837
+ if (/API_URL|API_BASE|BACKEND_URL|SERVER_URL/.test(varName)) category = 'api-url';
838
+ else if (/STRIPE|PAYMENT|PAYPAL|RAZORPAY/.test(varName)) category = 'payment';
839
+ else if (/FIREBASE|SUPABASE|MONGODB|DATABASE|DB_/.test(varName)) category = 'database';
840
+ else if (/SECRET|KEY|TOKEN|PRIVATE|PASSWORD/.test(varName)) category = 'secret';
841
+ else if (/GOOGLE|GITHUB|FACEBOOK|OAUTH/.test(varName)) category = 'oauth';
842
+ else if (/S3|AWS|CLOUDINARY|UPLOAD|BUCKET/.test(varName)) category = 'storage';
843
+ else if (/SENTRY|ANALYTICS|AMPLITUDE|MIXPANEL/.test(varName)) category = 'analytics';
844
+ else if (/OPENAI|ANTHROPIC|CLAUDE|GPT|AI_/.test(varName)) category = 'ai';
845
+ else if (/REDIS|CACHE/.test(varName)) category = 'cache';
846
+ vars.push({ name: varName, category, isPublic: varName.startsWith('NEXT_PUBLIC') || varName.startsWith('VITE_') });
847
+ }
848
+ }
849
+
850
+ // Also check for hardcoded .env patterns like: const BASE_URL = "http://..."
851
+ const hardcoded = src.match(/const\s+\w+_?(?:URL|KEY|SECRET)\s*=\s*["']https?:\/\/[^"']+["']/g);
852
+ if (hardcoded) {
853
+ for (const h of hardcoded) vars.push({ name: 'HARDCODED_URL', category: 'security-risk', raw: h.slice(0,80) });
854
+ }
855
+
856
+ return vars;
857
+ }
858
+
859
+ // ── [11] State Management Scan ───────────────────────────────────────────
860
+ #extractStateManagement(src) {
861
+ const signals = [];
862
+
863
+ if (/from ['"]react-redux['"]|useSelector|useDispatch|configureStore|createSlice/.test(src)) {
864
+ signals.push({ type: 'redux-toolkit' });
865
+ }
866
+ if (/from ['"]zustand['"]|create\s*\(\s*\(set/.test(src)) {
867
+ signals.push({ type: 'zustand' });
868
+ }
869
+ if (/from ['"]mobx['"]|from ['"]mobx-react['"]|makeObservable|observable/.test(src)) {
870
+ signals.push({ type: 'mobx' });
871
+ }
872
+ if (/from ['"]jotai['"]|atom\s*\(|useAtom/.test(src)) {
873
+ signals.push({ type: 'jotai' });
874
+ }
875
+ if (/from ['"]recoil['"]|RecoilRoot|atom\s*\{|selector\s*\{/.test(src)) {
876
+ signals.push({ type: 'recoil' });
877
+ }
878
+ if (/createContext|useContext|useReducer/.test(src)) {
879
+ signals.push({ type: 'context-api' });
880
+ }
881
+ if (/from ['"]@tanstack\/react-query['"]|QueryClient|QueryClientProvider/.test(src)) {
882
+ signals.push({ type: 'tanstack-query' });
883
+ }
884
+ // Server state via React Query = realtime needs
885
+ if (signals.some(s => ['tanstack-query','redux-toolkit'].includes(s.type))) {
886
+ signals.push({ type: 'server-state', note: 'Consider WebSocket for live updates' });
887
+ }
888
+
889
+ return signals;
890
+ }
891
+
892
+ // ── [12] Database Model Inference ────────────────────────────────────────
893
+ #extractDatabaseModels(src, filePath) {
894
+ const models = [];
895
+
896
+ // Common entity names from TS interfaces / types / classes
897
+ const interfacePattern = /(?:interface|type|class)\s+(\w+)\s*(?:extends|implements|=|{)/g;
898
+ let m;
899
+ while ((m = interfacePattern.exec(src)) !== null) {
900
+ const name = m[1];
901
+ // Filter out React component names (start with uppercase + end in common suffixes)
902
+ if (/Props|State|Context|Provider|Hook|Ref|Callback|Handler|Event|Config|Options|Params|Response|Request|Error/.test(name)) continue;
903
+ if (/^[A-Z][a-z]/.test(name) && name.length > 2) {
904
+ models.push({ name, source: 'interface', file: filePath });
905
+ }
906
+ }
907
+
908
+ // Semantic entity names from import/use patterns
909
+ const ENTITY_KEYWORDS = /\b(User|Product|Order|Invoice|Payment|Cart|Item|Category|Tag|Post|Comment|Message|Notification|Subscription|Plan|Role|Permission|Profile|Address|Session|Log|Report|Review|Rating|Coupon|Discount|Store|Vendor|Customer|Employee|Department|Project|Task|Ticket|Issue|File|Document|Media|Image|Video|Event|Booking|Appointment|Schedule|Slot|Room|Hotel|Flight|Trip|Tour|Course|Lesson|Quiz|Exam|Question|Answer|Chapter|Module|Certificate|Badge|Achievement|Point|Credit|Token|Transaction|Wallet|Ledger|Account|Budget|Expense|Revenue|Invoice|Receipt|Shipment|Package|Warehouse|Inventory|Stock|Supplier|Purchase|Sale|Refund|Return)\b/g;
910
+ const foundEntities = new Set(models.map(m => m.name));
911
+ while ((m = ENTITY_KEYWORDS.exec(src)) !== null) {
912
+ if (!foundEntities.has(m[1])) {
913
+ foundEntities.add(m[1]);
914
+ models.push({ name: m[1], source: 'semantic', file: filePath });
915
+ }
916
+ }
917
+
918
+ // From file path itself: /users/UserCard.tsx → User
919
+ const fileEntityMatch = path.basename(filePath).match(/^([A-Z][a-z]+)/);
920
+ if (fileEntityMatch && !foundEntities.has(fileEntityMatch[1])) {
921
+ const name = fileEntityMatch[1];
922
+ if (!['App','Index','Main','Root','Home','Page','Layout','Error','Loading','Not'].includes(name)) {
923
+ models.push({ name, source: 'filename', file: filePath });
924
+ }
925
+ }
926
+
927
+ return models;
928
+ }
929
+
930
+ // ── [13] File Upload Detection ───────────────────────────────────────────
931
+ #extractFileUploads(src) {
932
+ const uploads = [];
933
+
934
+ if (/type=["']file["']|input.*file|FileReader|FileList/.test(src)) {
935
+ const uploadFields = [];
936
+ const accept = src.match(/accept=["']([^"']+)["']/)?.[1];
937
+ const multiple = /multiple/.test(src);
938
+ uploadFields.push({ type: 'file-input', accept, multiple });
939
+ uploads.push(...uploadFields);
940
+ }
941
+
942
+ if (/multipart\/form-data|FormData/.test(src)) {
943
+ uploads.push({ type: 'multipart' });
944
+ }
945
+
946
+ if (/dropzone|react-dropzone|onDrop|DragEvent|dragover|dragleave/.test(src)) {
947
+ uploads.push({ type: 'drag-drop' });
948
+ }
949
+
950
+ // Cloud storage
951
+ if (/s3\.upload|putObject|S3Client|aws-sdk|@aws-sdk\/client-s3/.test(src)) {
952
+ uploads.push({ type: 'aws-s3' });
953
+ }
954
+ if (/cloudinary|Cloudinary|upload_preset/.test(src)) {
955
+ uploads.push({ type: 'cloudinary' });
956
+ }
957
+ if (/firebase\.storage|ref\(storage|uploadBytes|getDownloadURL/.test(src)) {
958
+ uploads.push({ type: 'firebase-storage' });
959
+ }
960
+
961
+ // Image handling
962
+ if (/Image|img.*src|imageUrl|thumbnail|avatar|photo|picture/.test(src) && uploads.length > 0) {
963
+ uploads.push({ type: 'image-processing', note: 'Consider resize/compress pipeline' });
964
+ }
965
+
966
+ return uploads;
967
+ }
968
+
969
+ // ── [14] Realtime Feature Scan ───────────────────────────────────────────
970
+ #extractRealtimeFeatures(src) {
971
+ const features = [];
972
+
973
+ if (/socket\.io|io\s*\(|useSocket|socketClient|io\.emit|io\.on/.test(src)) {
974
+ features.push({ type: 'socket.io' });
975
+ }
976
+ if (/new WebSocket|WebSocket\s*\(|useWebSocket|ws\.send|ws\.onmessage/.test(src)) {
977
+ features.push({ type: 'websocket' });
978
+ }
979
+ if (/EventSource|new SSE|useSSE|text\/event-stream/.test(src)) {
980
+ features.push({ type: 'sse' });
981
+ }
982
+ if (/subscribe|subscription|pubsub|PubSub|channel\.listen|Pusher/.test(src)) {
983
+ features.push({ type: 'pubsub' });
984
+ }
985
+ if (/useQuery.*pollInterval|refetchInterval|polling|setInterval.*fetch/.test(src)) {
986
+ features.push({ type: 'polling', note: 'Consider WebSocket instead' });
987
+ }
988
+ // Chat patterns
989
+ if (/chat|message.*send|typing.*indicator|chatRoom|chatMessage/.test(src)) {
990
+ features.push({ type: 'chat-ui', note: 'Needs WebSocket + message persistence' });
991
+ }
992
+ // Live notifications
993
+ if (/notification.*push|toast.*live|notification.*realtime/.test(src)) {
994
+ features.push({ type: 'live-notifications' });
995
+ }
996
+
997
+ return features;
998
+ }
999
+
1000
+ // ── [15] Payment Integration Scan ────────────────────────────────────────
1001
+ #extractPaymentIntegrations(src) {
1002
+ const payments = [];
1003
+
1004
+ if (/stripe|Stripe|loadStripe|useStripe|CardElement|PaymentElement|StripeElements/.test(src)) {
1005
+ const stripeFeatures = [];
1006
+ if (/PaymentIntent|createPaymentIntent/.test(src)) stripeFeatures.push('payment-intent');
1007
+ if (/Subscription|createSubscription/.test(src)) stripeFeatures.push('subscription');
1008
+ if (/Checkout|redirectToCheckout/.test(src)) stripeFeatures.push('checkout');
1009
+ if (/webhook|stripe-signature/.test(src)) stripeFeatures.push('webhook');
1010
+ payments.push({ provider: 'stripe', features: stripeFeatures });
1011
+ }
1012
+ if (/paypal|PayPal|PAYPAL|@paypal\/react-paypal-js/.test(src)) {
1013
+ payments.push({ provider: 'paypal' });
1014
+ }
1015
+ if (/razorpay|Razorpay|RAZORPAY/.test(src)) {
1016
+ payments.push({ provider: 'razorpay' });
1017
+ }
1018
+ if (/braintree|Braintree/.test(src)) {
1019
+ payments.push({ provider: 'braintree' });
1020
+ }
1021
+ if (/square|Square/.test(src)) {
1022
+ payments.push({ provider: 'square' });
1023
+ }
1024
+ if (/lemonsqueezy|lemon-squeezy/.test(src)) {
1025
+ payments.push({ provider: 'lemonsqueezy' });
1026
+ }
1027
+
1028
+ return payments;
1029
+ }
1030
+
1031
+ // ── [16] Third Party Service Detection ───────────────────────────────────
1032
+ #extractThirdPartyServices(src) {
1033
+ const services = [];
1034
+
1035
+ const SERVICE_MAP = [
1036
+ { re: /firebase|initializeApp|firebaseApp/, name: 'firebase' },
1037
+ { re: /supabase|createClient.*supabase/, name: 'supabase' },
1038
+ { re: /@clerk\/|useClerk|ClerkProvider/, name: 'clerk' },
1039
+ { re: /@auth0\/|Auth0Provider/, name: 'auth0' },
1040
+ { re: /openai|OpenAI|ChatOpenAI|gpt-[0-9]/, name: 'openai' },
1041
+ { re: /anthropic|@anthropic-ai\/|claude/i, name: 'anthropic' },
1042
+ { re: /twilio|Twilio/, name: 'twilio' },
1043
+ { re: /sendgrid|SendGrid|@sendgrid\//, name: 'sendgrid' },
1044
+ { re: /mailgun|Mailgun|nodemailer/, name: 'email-service' },
1045
+ { re: /algolia|instantsearch|algoliasearch/, name: 'algolia' },
1046
+ { re: /sentry|Sentry|@sentry\//, name: 'sentry' },
1047
+ { re: /amplitude|Amplitude/, name: 'amplitude' },
1048
+ { re: /mixpanel|Mixpanel/, name: 'mixpanel' },
1049
+ { re: /segment|analytics\.page|analytics\.track/, name: 'segment' },
1050
+ { re: /intercom|Intercom/, name: 'intercom' },
1051
+ { re: /google.*analytics|gtag|GA4/, name: 'google-analytics' },
1052
+ { re: /mapbox|MapboxGL|mapboxgl/, name: 'mapbox' },
1053
+ { re: /google.*maps|@googlemaps\//, name: 'google-maps' },
1054
+ { re: /aws-amplify|Amplify|@aws-amplify\//, name: 'aws-amplify' },
1055
+ { re: /@upstash\/|upstash/, name: 'upstash-redis' },
1056
+ { re: /pusher|Pusher/, name: 'pusher' },
1057
+ { re: /resend|Resend/, name: 'resend' },
1058
+ { re: /liveblocks|Liveblocks/, name: 'liveblocks' },
1059
+ { re: /inngest|Inngest/, name: 'inngest' },
1060
+ { re: /vercel.*ai|useChat|useCompletion/, name: 'vercel-ai-sdk' },
1061
+ ];
1062
+
1063
+ for (const { re, name } of SERVICE_MAP) {
1064
+ if (re.test(src)) services.push({ name });
1065
+ }
1066
+
1067
+ return services;
1068
+ }
1069
+
1070
+ // ── [17] SEO & Metadata Scan ─────────────────────────────────────────────
1071
+ #extractSEOMetadata(src, filePath) {
1072
+ const seo = [];
1073
+
1074
+ // Next.js metadata API
1075
+ if (/export\s+const\s+metadata\s*=|generateMetadata|Metadata/.test(src)) {
1076
+ seo.push({ type: 'next-metadata-api' });
1077
+ }
1078
+ // next/head
1079
+ if (/from ['"]next\/head['"]|<Head>/.test(src)) {
1080
+ seo.push({ type: 'next-head' });
1081
+ }
1082
+ // React Helmet
1083
+ if (/react-helmet|<Helmet>/.test(src)) {
1084
+ seo.push({ type: 'react-helmet' });
1085
+ }
1086
+ // OG tags
1087
+ if (/og:title|og:description|og:image|openGraph/.test(src)) {
1088
+ seo.push({ type: 'open-graph' });
1089
+ }
1090
+ // Sitemap / robots
1091
+ if (/sitemap|generateSitemaps|robots\.txt/.test(src)) {
1092
+ seo.push({ type: 'sitemap', note: 'Needs /api/sitemap.xml endpoint' });
1093
+ }
1094
+ // JSON-LD structured data
1095
+ if (/application\/ld\+json|@context.*schema\.org/.test(src)) {
1096
+ seo.push({ type: 'json-ld' });
1097
+ }
1098
+ // ISR / SSR / SSG patterns
1099
+ if (/getStaticProps|getServerSideProps|generateStaticParams|revalidate/.test(src)) {
1100
+ seo.push({ type: 'ssr-ssg', note: 'Backend caching recommended' });
1101
+ }
1102
+
1103
+ return seo;
1104
+ }
1105
+
1106
+ // ── [18] Error Handling Scan ─────────────────────────────────────────────
1107
+ #extractErrorHandling(src) {
1108
+ const errors = [];
1109
+
1110
+ // try/catch patterns
1111
+ const tryCatchCount = (src.match(/try\s*\{/g) || []).length;
1112
+ if (tryCatchCount > 0) errors.push({ type: 'try-catch', count: tryCatchCount });
1113
+
1114
+ // Toast/notification error display
1115
+ const errorDisplayPatterns = [
1116
+ { re: /toast\.error|toast\(.*error|notify.*error/, type: 'toast-error' },
1117
+ { re: /setError|error\.message|errorMessage/, type: 'state-error' },
1118
+ { re: /console\.error|console\.warn/, type: 'console-error' },
1119
+ { re: /ErrorBoundary|componentDidCatch/, type: 'error-boundary' },
1120
+ { re: /Sentry\.captureException/, type: 'sentry-capture' },
1121
+ ];
1122
+ for (const { re, type } of errorDisplayPatterns) {
1123
+ if (re.test(src)) errors.push({ type });
1124
+ }
1125
+
1126
+ // HTTP error status handling
1127
+ const statusPatterns = [
1128
+ { re: /status\s*===?\s*401|response\.status\s*===?\s*401/, type: '401-unauthorized' },
1129
+ { re: /status\s*===?\s*403|response\.status\s*===?\s*403/, type: '403-forbidden' },
1130
+ { re: /status\s*===?\s*404|response\.status\s*===?\s*404/, type: '404-not-found' },
1131
+ { re: /status\s*===?\s*500|response\.status\s*===?\s*500/, type: '500-server-error' },
1132
+ ];
1133
+ for (const { re, type } of statusPatterns) {
1134
+ if (re.test(src)) errors.push({ type });
1135
+ }
1136
+
1137
+ return errors;
1138
+ }
1139
+
1140
+ // ── [19] Security Risk Scan ───────────────────────────────────────────────
1141
+ #extractSecurityRisks(src, filePath) {
1142
+ const risks = [];
1143
+
1144
+ // Exposed secrets
1145
+ const secretPatterns = [
1146
+ { re: /['"](sk-[a-zA-Z0-9]{20,})['"]/g, type: 'exposed-openai-key', severity: 'CRITICAL' },
1147
+ { re: /['"](AIza[a-zA-Z0-9_-]{35})['"]/g, type: 'exposed-google-key', severity: 'CRITICAL' },
1148
+ { re: /process\.env\.\w+SECRET\w*\s*\|\|\s*['"][^'"]{8,}['"]/, type: 'hardcoded-fallback-secret', severity: 'HIGH' },
1149
+ { re: /api_key\s*=\s*['"][a-zA-Z0-9]{16,}['"]/gi, type: 'hardcoded-api-key', severity: 'HIGH' },
1150
+ ];
1151
+ for (const { re, type, severity } of secretPatterns) {
1152
+ re.lastIndex = 0;
1153
+ if (re.test(src)) risks.push({ type, severity, file: filePath });
1154
+ }
1155
+
1156
+ // XSS risks
1157
+ if (/dangerouslySetInnerHTML\s*=\s*\{\s*\{.*__html/.test(src)) {
1158
+ risks.push({ type: 'xss-dangerouslySetInnerHTML', severity: 'HIGH', file: filePath });
1159
+ }
1160
+ if (/innerHTML\s*=\s*[^'"][^;]+;/.test(src)) {
1161
+ risks.push({ type: 'xss-innerHTML-assignment', severity: 'HIGH', file: filePath });
1162
+ }
1163
+ if (/eval\s*\(|new Function\s*\(/.test(src)) {
1164
+ risks.push({ type: 'code-injection-eval', severity: 'CRITICAL', file: filePath });
1165
+ }
1166
+
1167
+ // Unsafe fetch (no credentials, no CSRF)
1168
+ if (/fetch\s*\(/.test(src) && !/credentials|X-CSRF|csrfToken/.test(src) && /POST|PUT|DELETE/.test(src)) {
1169
+ risks.push({ type: 'missing-csrf-token', severity: 'MEDIUM', file: filePath });
1170
+ }
1171
+
1172
+ // Exposed sensitive data in console
1173
+ if (/console\.log.*(?:password|token|secret|key|auth)/i.test(src)) {
1174
+ risks.push({ type: 'sensitive-data-logged', severity: 'MEDIUM', file: filePath });
1175
+ }
1176
+
1177
+ // Missing HTTPS check
1178
+ if (/http:\/\/(?!localhost|127\.0\.0\.1)/.test(src)) {
1179
+ risks.push({ type: 'insecure-http-url', severity: 'MEDIUM', file: filePath });
1180
+ }
1181
+
1182
+ // Prototype pollution risk
1183
+ if (/Object\.assign\s*\(\s*\{\}|spread.*req\.body|merge.*req\.body/.test(src)) {
1184
+ risks.push({ type: 'prototype-pollution-risk', severity: 'LOW', file: filePath });
1185
+ }
1186
+
1187
+ return risks;
1188
+ }
1189
+
1190
+ // ── [20] Component Behavior Analysis ─────────────────────────────────────
1191
+ #analyzeComponentBehavior(src, filePath) {
1192
+ const components = [];
1193
+ const fileName = path.basename(filePath, path.extname(filePath)).toLowerCase();
1194
+
1195
+ // Semantic component categories
1196
+ const SEMANTIC_MAP = [
1197
+ { keywords: /cart|basket/, type: 'shopping-cart', apis: ['GET /api/cart', 'POST /api/cart', 'DELETE /api/cart/:id'] },
1198
+ { keywords: /checkout|payment/, type: 'checkout', apis: ['POST /api/orders', 'POST /api/payment'] },
1199
+ { keywords: /product.*list|catalog/, type: 'product-catalog', apis: ['GET /api/products', 'GET /api/categories'] },
1200
+ { keywords: /product.*detail/, type: 'product-detail', apis: ['GET /api/products/:id', 'GET /api/reviews'] },
1201
+ { keywords: /dashboard|analytics/, type: 'dashboard', apis: ['GET /api/stats', 'GET /api/analytics'] },
1202
+ { keywords: /admin.*user|user.*manage/, type: 'user-management', apis: ['GET /api/admin/users', 'PUT /api/admin/users/:id', 'DELETE /api/admin/users/:id'] },
1203
+ { keywords: /profile|account/, type: 'user-profile', apis: ['GET /api/profile', 'PUT /api/profile', 'PATCH /api/profile/avatar'] },
1204
+ { keywords: /notification/, type: 'notifications', apis: ['GET /api/notifications', 'PUT /api/notifications/:id/read'] },
1205
+ { keywords: /message|chat|inbox/, type: 'messaging', apis: ['GET /api/messages', 'POST /api/messages', 'WebSocket /ws/chat'] },
1206
+ { keywords: /search|filter|autocomplete/, type: 'search', apis: ['GET /api/search', 'GET /api/search/suggest'] },
1207
+ { keywords: /order.*history|order.*list/, type: 'orders', apis: ['GET /api/orders', 'GET /api/orders/:id'] },
1208
+ { keywords: /report|export/, type: 'reporting', apis: ['GET /api/reports', 'POST /api/reports/export'] },
1209
+ { keywords: /upload|file.*manager/, type: 'file-manager', apis: ['POST /api/upload', 'GET /api/files', 'DELETE /api/files/:id'] },
1210
+ { keywords: /settings|preferences/, type: 'settings', apis: ['GET /api/settings', 'PUT /api/settings'] },
1211
+ ];
1212
+
1213
+ for (const { keywords, type, apis } of SEMANTIC_MAP) {
1214
+ if (keywords.test(fileName) || keywords.test(src.toLowerCase().slice(0, 500))) {
1215
+ components.push({ type, suggestedApis: apis, file: filePath });
1216
+ }
1217
+ }
1218
+
1219
+ return components;
1220
+ }
1221
+
1222
+ // ── [29] Performance Issues Scan ─────────────────────────────────────────
1223
+ #extractPerformanceIssues(src) {
1224
+ const issues = [];
1225
+
1226
+ // Unnecessary re-renders
1227
+ if (/useEffect\s*\(\s*[^,]+,\s*\[\s*\]/.test(src) === false && /useEffect/.test(src)) {
1228
+ if (!/useCallback|useMemo|React\.memo/.test(src) && (src.match(/useEffect/g) || []).length > 2) {
1229
+ issues.push({ type: 'potential-unnecessary-renders', hint: 'Consider useCallback/useMemo' });
1230
+ }
1231
+ }
1232
+
1233
+ // No pagination on list fetch
1234
+ if (/fetch.*\/api\/\w+['"]/.test(src) && !/page=|limit=|offset=|cursor=|pagination/.test(src)) {
1235
+ if (/\.map\s*\(|list|items|data\s*=/.test(src)) {
1236
+ issues.push({ type: 'missing-pagination', hint: 'Add ?page=&limit= params' });
1237
+ }
1238
+ }
1239
+
1240
+ // Large bundle indicators
1241
+ if (/import \* as|require\s*\('lodash'\)|require\s*\('moment'\)/.test(src)) {
1242
+ issues.push({ type: 'heavy-import', hint: 'Use tree-shakeable imports' });
1243
+ }
1244
+
1245
+ // N+1 problem risk
1246
+ if (/forEach.*fetch|map.*fetch|for.*await.*fetch/.test(src)) {
1247
+ issues.push({ type: 'n+1-fetch-risk', hint: 'Batch API calls or use DataLoader' });
1248
+ }
1249
+
1250
+ // Missing loading states
1251
+ if (/fetch|axios/.test(src) && !/loading|isLoading|isPending|isFetching/.test(src)) {
1252
+ issues.push({ type: 'missing-loading-state' });
1253
+ }
1254
+
1255
+ // No error boundaries
1256
+ if (/(fetch|axios)/.test(src) && !/ErrorBoundary|error.*state|catch/.test(src)) {
1257
+ issues.push({ type: 'missing-error-handling' });
1258
+ }
1259
+
1260
+ return issues;
1261
+ }
1262
+
1263
+ // ── Quality scoring ───────────────────────────────────────────────────────
447
1264
  #scoreFileQuality(src, filePath) {
448
1265
  let score = 100;
449
1266
  const lines = src.split('\n');
450
- if (lines.length > 500) score -= 10;
1267
+ if (lines.length > 500) score -= 10;
451
1268
  if (lines.length > 1000) score -= 15;
452
1269
  if (src.includes('any')) score -= 5;
453
1270
  if (src.includes('// TODO')) score -= 3;
@@ -455,38 +1272,37 @@ class UltraASTEngine {
455
1272
  if (!src.includes('try') && src.includes('fetch')) score -= 8;
456
1273
  if (src.includes('eslint-disable')) score -= 5;
457
1274
  if (filePath.includes('.test.') || filePath.includes('.spec.')) score += 10;
1275
+ if (/useCallback|useMemo/.test(src)) score += 3;
1276
+ if (/typescript|\.tsx?$/.test(filePath)) score += 2;
458
1277
  return clamp(score, 0, 100);
459
1278
  }
460
1279
 
461
- // Vue SFC script extraction
1280
+ // ── Vue / Svelte / Astro extractors ──────────────────────────────────────
462
1281
  #extractVueScript(src) {
463
1282
  const setup = src.match(/<script\s+setup[^>]*>([\s\S]*?)<\/script>/i);
464
1283
  const std = src.match(/<script(?!\s+setup)[^>]*>([\s\S]*?)<\/script>/i);
465
1284
  return [setup?.[1] || '', std?.[1] || ''].join('\n');
466
1285
  }
467
1286
 
468
- // Svelte script extraction
469
1287
  #extractSvelteScript(src) {
470
1288
  return src.match(/<script[^>]*>([\s\S]*?)<\/script>/gi)
471
1289
  ?.map(s => s.replace(/<\/?script[^>]*>/g, ''))
472
1290
  .join('\n') || '';
473
1291
  }
474
1292
 
475
- // Astro frontmatter extraction
476
1293
  #extractAstroScript(src) {
477
1294
  const match = src.match(/^---\r?\n([\s\S]*?)\r?\n---/);
478
1295
  return match?.[1] || '';
479
1296
  }
480
1297
 
481
- // ── PARALLEL BATCH SCANNER ──────────────────────────────────────────────
1298
+ // ── PARALLEL BATCH SCANNER ───────────────────────────────────────────────
482
1299
  async scan(srcDir, onProgress = null) {
483
1300
  this.#startTime = performance.now();
484
1301
  await fs.ensureDir(CACHE_DIR);
485
1302
 
486
1303
  const files = await this.collectFiles(srcDir);
487
- if (files.length === 0) return { endpoints: [], files: 0, duration: 0, quality: 0 };
1304
+ if (files.length === 0) return this.#emptyResult();
488
1305
 
489
- // Process in parallel batches (respects CPU count)
490
1306
  const BATCH_SIZE = Math.max(4, MAX_WORKERS * 2);
491
1307
  let processed = 0;
492
1308
  const allResults = [];
@@ -506,23 +1322,66 @@ class UltraASTEngine {
506
1322
  }
507
1323
  }
508
1324
 
509
- const allEndpoints = allResults.flatMap(r => r.endpoints);
1325
+ return this.#buildScanResult(allResults, files.length);
1326
+ }
1327
+
1328
+ #emptyResult() {
1329
+ return {
1330
+ endpoints: [], forms: [], validations: [], authSignals: [], roleSignals: [],
1331
+ envVars: [], stateSignals: [], models: [], uploads: [], realtimeUse: [],
1332
+ payments: [], thirdParty: [], seoMeta: [], errorHandling: [], securityRisks: [],
1333
+ components: [], payloads: [], routes: [], perfIssues: [],
1334
+ files: 0, duration: '0.00', quality: 0, byFile: [], cacheHits: 0,
1335
+ };
1336
+ }
1337
+
1338
+ #buildScanResult(allResults, totalFiles) {
1339
+ const merge = (key) => allResults.flatMap(r => r[key] || []);
1340
+ const dedup = (arr, key) => {
1341
+ const seen = new Set();
1342
+ return arr.filter(item => {
1343
+ const k = JSON.stringify(item[key] || item);
1344
+ if (seen.has(k)) return false;
1345
+ seen.add(k);
1346
+ return true;
1347
+ });
1348
+ };
1349
+
1350
+ const allEndpoints = dedup(merge('endpoints'), 'path');
510
1351
  const avgQuality = allResults.length > 0
511
- ? Math.round(allResults.reduce((a, r) => a + r.quality, 0) / allResults.length)
1352
+ ? Math.round(allResults.reduce((a, r) => a + (r.quality || 0), 0) / allResults.length)
512
1353
  : 0;
513
- const duration = ((performance.now() - this.#startTime) / 1000).toFixed(2);
1354
+ const duration = ((performance.now() - this.#startTime) / 1000).toFixed(2);
514
1355
 
515
1356
  return {
516
- endpoints : allEndpoints,
517
- files : files.length,
1357
+ endpoints : allEndpoints,
1358
+ forms : merge('forms'),
1359
+ validations : merge('validations'),
1360
+ authSignals : merge('authSignals'),
1361
+ roleSignals : merge('roleSignals'),
1362
+ envVars : merge('envVars'),
1363
+ stateSignals : merge('stateSignals'),
1364
+ models : dedup(merge('models'), 'name'),
1365
+ uploads : merge('uploads'),
1366
+ realtimeUse : merge('realtimeUse'),
1367
+ payments : merge('payments'),
1368
+ thirdParty : merge('thirdParty'),
1369
+ seoMeta : merge('seoMeta'),
1370
+ errorHandling: merge('errorHandling'),
1371
+ securityRisks: merge('securityRisks'),
1372
+ components : merge('components'),
1373
+ payloads : merge('payloads'),
1374
+ routes : merge('routes'),
1375
+ perfIssues : merge('perfIssues'),
1376
+ files : totalFiles,
518
1377
  duration,
519
- quality : avgQuality,
520
- byFile : allResults,
521
- cacheHits : this.#cache.size,
1378
+ quality : avgQuality,
1379
+ byFile : allResults,
1380
+ cacheHits : this.#cache.size,
522
1381
  };
523
1382
  }
524
1383
 
525
- // GraphQL schema extraction
1384
+ // ── GraphQL schema extraction ─────────────────────────────────────────────
526
1385
  async extractGraphQLSchema(srcDir) {
527
1386
  const files = await this.collectFiles(srcDir, { extensions: ['.ts','.js','.graphql','.gql'] });
528
1387
  const schema = [];
@@ -536,7 +1395,7 @@ class UltraASTEngine {
536
1395
  return schema;
537
1396
  }
538
1397
 
539
- // tRPC router extraction
1398
+ // ── tRPC router extraction ───────────────────────────────────────────────
540
1399
  async extractTRPCRouters(srcDir) {
541
1400
  const files = await this.collectFiles(srcDir, { extensions: ['.ts'] });
542
1401
  const routers = [];
@@ -551,39 +1410,477 @@ class UltraASTEngine {
551
1410
  return routers;
552
1411
  }
553
1412
 
554
- // OpenAPI 3.1 spec generator
555
- generateOpenAPISpec(endpoints, projectName, version = '1.0.0') {
1413
+ // ── [22] OpenAPI 3.1 spec generator ─────────────────────────────────────
1414
+ generateOpenAPISpec(endpoints, projectName, version = '1.0.0', scanResult = null) {
556
1415
  const paths = {};
557
1416
  for (const ep of endpoints) {
558
1417
  const epPath = ep.path.replace(/:[a-zA-Z]+/g, match => `{${match.slice(1)}}`);
559
1418
  if (!paths[epPath]) paths[epPath] = {};
1419
+
1420
+ // Build parameter list from path params
1421
+ const pathParams = (epPath.match(/\{(\w+)\}/g) || []).map(p => ({
1422
+ in: 'path', name: p.slice(1, -1), required: true, schema: { type: 'string' },
1423
+ }));
1424
+
1425
+ const operationId = `${ep.method.toLowerCase()}${epPath.replace(/[^a-zA-Z0-9]/g, '_')}`;
1426
+ const tag = epPath.split('/').filter(Boolean)[1] || 'default';
1427
+
560
1428
  paths[epPath][ep.method.toLowerCase()] = {
561
1429
  summary : `${ep.method} ${ep.path}`,
562
- operationId: `${ep.method.toLowerCase()}${ep.path.replace(/[^a-zA-Z0-9]/g, '_')}`,
563
- tags : [ep.path.split('/').filter(Boolean)[1] || 'default'],
564
- parameters : [],
1430
+ operationId,
1431
+ tags : [tag],
1432
+ parameters : pathParams,
1433
+ ...(ep.method !== 'GET' ? {
1434
+ requestBody: {
1435
+ content: { 'application/json': { schema: { type: 'object' } } },
1436
+ },
1437
+ } : {}),
565
1438
  responses : {
566
- '200': { description: 'Success', content: { 'application/json': { schema: { type: 'object' } } } },
1439
+ '200': { description: 'Success', content: { 'application/json': { schema: { type: 'object', properties: { data: { type: 'object' }, message: { type: 'string' } } } } } },
567
1440
  '400': { description: 'Bad Request' },
568
1441
  '401': { description: 'Unauthorized' },
1442
+ '403': { description: 'Forbidden' },
1443
+ '404': { description: 'Not Found' },
1444
+ '422': { description: 'Validation Error' },
569
1445
  '500': { description: 'Internal Server Error' },
570
1446
  },
1447
+ security : [{ bearerAuth: [] }],
571
1448
  };
572
1449
  }
573
- return {
1450
+
1451
+ const spec = {
574
1452
  openapi : '3.1.0',
575
- info : { title: `${projectName} API`, version, description: `Auto-generated by create-backlist v${VERSION}` },
1453
+ info : {
1454
+ title : `${projectName} API`,
1455
+ version,
1456
+ description : `Auto-generated by create-backlist v${VERSION}\n\nGenerated from AST scan of frontend source.`,
1457
+ },
576
1458
  paths,
577
- components: { securitySchemes: { bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' } } },
1459
+ components: {
1460
+ securitySchemes: {
1461
+ bearerAuth : { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
1462
+ cookieAuth : { type: 'apiKey', in: 'cookie', name: 'access_token' },
1463
+ },
1464
+ schemas: this.#generateSchemas(scanResult),
1465
+ },
1466
+ };
1467
+
1468
+ return spec;
1469
+ }
1470
+
1471
+ // Generate schemas from inferred models
1472
+ #generateSchemas(scanResult) {
1473
+ if (!scanResult) return {};
1474
+ const schemas = {};
1475
+
1476
+ for (const model of (scanResult.models || [])) {
1477
+ const name = model.name;
1478
+ schemas[name] = {
1479
+ type: 'object',
1480
+ description: `Auto-inferred from frontend AST scan`,
1481
+ properties: {
1482
+ id : { type: 'string', format: 'uuid' },
1483
+ createdAt : { type: 'string', format: 'date-time' },
1484
+ updatedAt : { type: 'string', format: 'date-time' },
1485
+ },
1486
+ required: ['id'],
1487
+ };
1488
+
1489
+ // Add form-inferred fields for known models
1490
+ const relatedForms = (scanResult.forms || []).filter(f =>
1491
+ f.type === name.toLowerCase() || f.file?.toLowerCase().includes(name.toLowerCase())
1492
+ );
1493
+ for (const form of relatedForms) {
1494
+ for (const field of (form.fields || [])) {
1495
+ schemas[name].properties[field] = { type: 'string' };
1496
+ }
1497
+ }
1498
+ }
1499
+
1500
+ // Pagination schema
1501
+ schemas['PaginatedResponse'] = {
1502
+ type: 'object',
1503
+ properties: {
1504
+ data : { type: 'array', items: {} },
1505
+ total : { type: 'integer' },
1506
+ page : { type: 'integer' },
1507
+ limit : { type: 'integer' },
1508
+ totalPages : { type: 'integer' },
1509
+ },
1510
+ };
1511
+
1512
+ // Error schema
1513
+ schemas['ErrorResponse'] = {
1514
+ type: 'object',
1515
+ properties: {
1516
+ error : { type: 'string' },
1517
+ message : { type: 'string' },
1518
+ code : { type: 'integer' },
1519
+ details : { type: 'array', items: { type: 'object' } },
1520
+ },
1521
+ };
1522
+
1523
+ return schemas;
1524
+ }
1525
+
1526
+ // ── [24] Prisma schema generator from inferred models ────────────────────
1527
+ generatePrismaSchema(scanResult, dbType = 'postgresql') {
1528
+ const models = scanResult.models || [];
1529
+ if (models.length === 0) return null;
1530
+
1531
+ const dbProvider = {
1532
+ postgres : 'postgresql',
1533
+ mysql : 'mysql',
1534
+ sqlite : 'sqlite',
1535
+ mongodb : 'mongodb',
1536
+ }[dbType] || 'postgresql';
1537
+
1538
+ let schema = `// Auto-generated by create-backlist v${VERSION}
1539
+ // Source: Frontend AST scan
1540
+
1541
+ generator client {
1542
+ provider = "prisma-client-js"
1543
+ }
1544
+
1545
+ datasource db {
1546
+ provider = "${dbProvider}"
1547
+ url = env("DATABASE_URL")
1548
+ }
1549
+
1550
+ `;
1551
+
1552
+ const modelNames = new Set(models.map(m => m.name));
1553
+
1554
+ for (const model of models) {
1555
+ const name = model.name;
1556
+ // Infer fields from forms
1557
+ const relatedForms = (scanResult.forms || []).filter(f =>
1558
+ f.file?.toLowerCase().includes(name.toLowerCase())
1559
+ );
1560
+ const formFields = new Set();
1561
+ for (const form of relatedForms) {
1562
+ for (const field of (form.fields || [])) formFields.add(field);
1563
+ }
1564
+
1565
+ // Infer relationships
1566
+ const hasTimestamps = true;
1567
+ const hasUser = name !== 'User' && modelNames.has('User');
1568
+ const hasRelations = [];
1569
+ if (hasUser) hasRelations.push({ field: 'userId', type: 'String', relation: 'User' });
1570
+
1571
+ // Get validations for this model
1572
+ const allValidations = scanResult.validations?.flatMap(v => v.constraints || []) || [];
1573
+
1574
+ schema += `model ${name} {\n`;
1575
+ schema += ` id String @id @default(cuid())\n`;
1576
+
1577
+ // Add form-inferred fields
1578
+ for (const field of formFields) {
1579
+ const isEmail = field.toLowerCase().includes('email');
1580
+ const isPassword = field.toLowerCase().includes('password');
1581
+ const isNum = /price|amount|count|qty|quantity|age|rating|score|total/.test(field.toLowerCase());
1582
+ const isBool = /active|enabled|visible|published|verified|confirmed/.test(field.toLowerCase());
1583
+ const isDate = /date|at$|time|birthday|expir/.test(field.toLowerCase());
1584
+
1585
+ let type = 'String';
1586
+ if (isNum) type = 'Float';
1587
+ if (isBool) type = 'Boolean';
1588
+ if (isDate) type = 'DateTime';
1589
+
1590
+ const optional = !['name','email','title'].includes(field.toLowerCase());
1591
+ schema += ` ${field.padEnd(16)} ${type}${optional ? '?' : ''}`;
1592
+ if (isEmail) schema += ` // @unique`;
1593
+ if (isPassword) schema += ` // hashed`;
1594
+ schema += '\n';
1595
+ }
1596
+
1597
+ // Default fields if no form fields
1598
+ if (formFields.size === 0) {
1599
+ if (['User','Profile','Account'].includes(name)) {
1600
+ schema += ` name String?\n`;
1601
+ schema += ` email String @unique\n`;
1602
+ schema += ` password String // hashed\n`;
1603
+ schema += ` role String @default("user")\n`;
1604
+ schema += ` isActive Boolean @default(true)\n`;
1605
+ } else if (['Product','Item'].includes(name)) {
1606
+ schema += ` name String\n`;
1607
+ schema += ` price Float\n`;
1608
+ schema += ` stock Int @default(0)\n`;
1609
+ schema += ` imageUrl String?\n`;
1610
+ } else if (['Order','Purchase'].includes(name)) {
1611
+ schema += ` status String @default("pending")\n`;
1612
+ schema += ` total Float\n`;
1613
+ } else {
1614
+ schema += ` name String?\n`;
1615
+ schema += ` data Json?\n`;
1616
+ }
1617
+ }
1618
+
1619
+ // Relations
1620
+ for (const rel of hasRelations) {
1621
+ schema += ` ${rel.field.padEnd(16)} ${rel.type}\n`;
1622
+ schema += ` ${rel.relation.toLowerCase().padEnd(16)} ${rel.relation} @relation(fields: [${rel.field}], references: [id])\n`;
1623
+ }
1624
+
1625
+ if (hasTimestamps) {
1626
+ schema += ` createdAt DateTime @default(now())\n`;
1627
+ schema += ` updatedAt DateTime @updatedAt\n`;
1628
+ }
1629
+
1630
+ schema += `\n @@map("${name.toLowerCase()}s")\n`;
1631
+ schema += `}\n\n`;
1632
+ }
1633
+
1634
+ return schema;
1635
+ }
1636
+
1637
+ // ── [27] Bug Report Generator ─────────────────────────────────────────────
1638
+ generateBugReport(scanResult) {
1639
+ const bugs = [];
1640
+
1641
+ // Critical security findings
1642
+ for (const risk of (scanResult.securityRisks || [])) {
1643
+ bugs.push({
1644
+ id : `BUG-SEC-${bugs.length + 1}`,
1645
+ severity : risk.severity,
1646
+ category : 'Security',
1647
+ title : `Security risk: ${risk.type}`,
1648
+ file : risk.file,
1649
+ description: `AST scan detected ${risk.type} in ${path.basename(risk.file || '')}`,
1650
+ steps : ['1. Open the flagged file', '2. Locate the pattern', '3. Apply recommended fix'],
1651
+ fix : this.#suggestSecurityFix(risk.type),
1652
+ });
1653
+ }
1654
+
1655
+ // Performance issues
1656
+ for (const perf of (scanResult.perfIssues || [])) {
1657
+ bugs.push({
1658
+ id : `BUG-PERF-${bugs.length + 1}`,
1659
+ severity : 'LOW',
1660
+ category : 'Performance',
1661
+ title : `Performance: ${perf.type}`,
1662
+ hint : perf.hint,
1663
+ });
1664
+ }
1665
+
1666
+ // Missing error handling
1667
+ const hasErrorHandling = (scanResult.errorHandling || []).some(e => e.type === 'try-catch');
1668
+ if (!hasErrorHandling && (scanResult.endpoints || []).length > 0) {
1669
+ bugs.push({
1670
+ id : 'BUG-ERR-001',
1671
+ severity : 'MEDIUM',
1672
+ category : 'Error Handling',
1673
+ title : 'Missing try/catch around API calls',
1674
+ fix : 'Wrap all fetch/axios calls in try/catch blocks',
1675
+ });
1676
+ }
1677
+
1678
+ return bugs;
1679
+ }
1680
+
1681
+ #suggestSecurityFix(type) {
1682
+ const fixes = {
1683
+ 'exposed-openai-key' : 'Move API key to server-side environment variable. Never expose in client code.',
1684
+ 'xss-dangerouslySetInnerHTML' : 'Use DOMPurify to sanitize HTML before rendering.',
1685
+ 'code-injection-eval' : 'Replace eval() with safer alternatives like JSON.parse() or Function constructors.',
1686
+ 'missing-csrf-token' : 'Add CSRF token to state-changing requests or use SameSite=Strict cookies.',
1687
+ 'sensitive-data-logged' : 'Remove console.log statements that expose sensitive data.',
1688
+ 'insecure-http-url' : 'Use HTTPS for all external API calls.',
578
1689
  };
1690
+ return fixes[type] || 'Review and apply security best practices.';
1691
+ }
1692
+
1693
+ // ── [28] DevOps config generator ─────────────────────────────────────────
1694
+ generateDevOpsConfig(stack, projectName, scanResult) {
1695
+ const services = scanResult?.thirdParty?.map(s => s.name) || [];
1696
+ const hasRedis = services.includes('upstash-redis') || (scanResult?.envVars || []).some(v => /REDIS/.test(v.name));
1697
+ const hasDB = (scanResult?.models || []).length > 0;
1698
+
1699
+ const dbService = hasDB ? `
1700
+ db:
1701
+ image: postgres:16-alpine
1702
+ environment:
1703
+ POSTGRES_DB: ${projectName}
1704
+ POSTGRES_USER: postgres
1705
+ POSTGRES_PASSWORD: \${DB_PASSWORD:-postgres}
1706
+ volumes:
1707
+ - db_data:/var/lib/postgresql/data
1708
+ ports:
1709
+ - "5432:5432"
1710
+ healthcheck:
1711
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
1712
+ interval: 10s
1713
+ timeout: 5s
1714
+ retries: 5` : '';
1715
+
1716
+ const redisService = hasRedis ? `
1717
+ redis:
1718
+ image: redis:7-alpine
1719
+ ports:
1720
+ - "6379:6379"
1721
+ command: redis-server --appendonly yes
1722
+ volumes:
1723
+ - redis_data:/data` : '';
1724
+
1725
+ const dockerCompose = `version: '3.9'
1726
+
1727
+ services:
1728
+ app:
1729
+ build:
1730
+ context: .
1731
+ dockerfile: Dockerfile
1732
+ ports:
1733
+ - "\${PORT:-3001}:3001"
1734
+ environment:
1735
+ - NODE_ENV=production
1736
+ - DATABASE_URL=\${DATABASE_URL}
1737
+ - JWT_SECRET=\${JWT_SECRET}
1738
+ - PORT=3001
1739
+ depends_on:
1740
+ ${hasDB ? '- db' : ''}
1741
+ ${hasRedis ? '- redis' : ''}
1742
+ restart: unless-stopped
1743
+ healthcheck:
1744
+ test: ["CMD", "curl", "-f", "http://localhost:3001/health"]
1745
+ interval: 30s
1746
+ timeout: 10s
1747
+ retries: 3
1748
+ ${dbService}
1749
+ ${redisService}
1750
+
1751
+ volumes:
1752
+ ${hasDB ? 'db_data:' : ''}
1753
+ ${hasRedis ? 'redis_data:' : ''}
1754
+ `;
1755
+
1756
+ const dockerfile = `# Auto-generated by create-backlist v${VERSION}
1757
+ FROM node:20-alpine AS builder
1758
+ WORKDIR /app
1759
+ COPY package*.json ./
1760
+ RUN npm ci --only=production
1761
+ COPY . .
1762
+ ${stack === 'node-ts-express' || stack === 'nestjs' ? 'RUN npm run build' : ''}
1763
+
1764
+ FROM node:20-alpine AS runner
1765
+ WORKDIR /app
1766
+ ENV NODE_ENV=production
1767
+ COPY --from=builder /app/node_modules ./node_modules
1768
+ COPY --from=builder /app/dist ./dist
1769
+ COPY --from=builder /app/package.json ./
1770
+ ${hasDB ? 'RUN npx prisma generate 2>/dev/null || true' : ''}
1771
+ EXPOSE 3001
1772
+ CMD ["node", "dist/index.js"]
1773
+ `;
1774
+
1775
+ const githubWorkflow = `name: CI/CD Pipeline
1776
+ on:
1777
+ push:
1778
+ branches: [main, develop]
1779
+ pull_request:
1780
+ branches: [main]
1781
+
1782
+ env:
1783
+ NODE_VERSION: '20'
1784
+
1785
+ jobs:
1786
+ test:
1787
+ runs-on: ubuntu-latest
1788
+ steps:
1789
+ - uses: actions/checkout@v4
1790
+ - uses: actions/setup-node@v4
1791
+ with:
1792
+ node-version: \${{ env.NODE_VERSION }}
1793
+ cache: 'npm'
1794
+ - run: npm ci
1795
+ - run: npm test
1796
+ - run: npm run lint
1797
+
1798
+ build:
1799
+ needs: test
1800
+ runs-on: ubuntu-latest
1801
+ if: github.ref == 'refs/heads/main'
1802
+ steps:
1803
+ - uses: actions/checkout@v4
1804
+ - name: Build Docker image
1805
+ run: docker build -t ${projectName}:\${{ github.sha }} .
1806
+ - name: Push to registry
1807
+ run: echo "Add your registry push commands here"
1808
+
1809
+ deploy:
1810
+ needs: build
1811
+ runs-on: ubuntu-latest
1812
+ if: github.ref == 'refs/heads/main'
1813
+ steps:
1814
+ - name: Deploy
1815
+ run: echo "Add your deployment commands here"
1816
+ `;
1817
+
1818
+ return { dockerCompose, dockerfile, githubWorkflow };
1819
+ }
1820
+
1821
+ // ── [25] QA test generation ───────────────────────────────────────────────
1822
+ generateQATests(scanResult, projectName) {
1823
+ const tests = [];
1824
+
1825
+ for (const ep of (scanResult.endpoints || []).slice(0, 30)) {
1826
+ const method = ep.method;
1827
+ const epPath = ep.path;
1828
+ const varName = epPath.replace(/[^a-zA-Z0-9]/g, '_').replace(/^_+/, '');
1829
+
1830
+ tests.push({
1831
+ type : 'api',
1832
+ name : `${method} ${epPath}`,
1833
+ code : `
1834
+ test('${method} ${epPath} → 200', async () => {
1835
+ const res = await request(app).${method.toLowerCase()}('${epPath}')${
1836
+ method !== 'GET' ? `.send({ /* payload */ })` : ''
1837
+ }${
1838
+ (scanResult.authSignals || []).length > 0
1839
+ ? `.set('Authorization', \`Bearer \${testToken}\`)`
1840
+ : ''
1841
+ };
1842
+ expect(res.status).toBeLessThan(500);
1843
+ });`,
1844
+ });
1845
+ }
1846
+
1847
+ // Security tests
1848
+ if ((scanResult.authSignals || []).length > 0) {
1849
+ tests.push({
1850
+ type : 'security',
1851
+ name : 'Unauthenticated access returns 401',
1852
+ code : `
1853
+ test('Protected routes return 401 without token', async () => {
1854
+ const protectedPaths = ['/api/profile', '/api/admin', '/api/dashboard'];
1855
+ for (const path of protectedPaths) {
1856
+ const res = await request(app).get(path);
1857
+ expect(res.status).toBe(401);
1858
+ }
1859
+ });`,
1860
+ });
1861
+ }
1862
+
1863
+ // Form validation tests
1864
+ for (const form of (scanResult.forms || []).slice(0, 5)) {
1865
+ if (form.fields?.length > 0) {
1866
+ tests.push({
1867
+ type : 'validation',
1868
+ name : `${form.type} form validation`,
1869
+ code : `
1870
+ test('${form.type} form rejects empty payload', async () => {
1871
+ const res = await request(app).post('/api/${form.type}').send({});
1872
+ expect([400, 422]).toContain(res.status);
1873
+ });`,
1874
+ });
1875
+ }
1876
+ }
1877
+
1878
+ return tests;
579
1879
  }
580
1880
 
581
1881
  // Stats summary
582
1882
  getSummary() {
583
- return {
584
- cacheSize : this.#cache.size,
585
- errors : this.#errors.length,
586
- };
1883
+ return { cacheSize: this.#cache.size, errors: this.#errors.length };
587
1884
  }
588
1885
  }
589
1886
 
@@ -624,7 +1921,7 @@ class SystemMonitor {
624
1921
  }
625
1922
 
626
1923
  // ═══════════════════════════════════════════════════════════════════════════════════
627
- // 🎨 Enhanced Animation Engine
1924
+ // 🎨 Animation Engine
628
1925
  // ═══════════════════════════════════════════════════════════════════════════════════
629
1926
 
630
1927
  const SPINNERS = {
@@ -712,7 +2009,7 @@ class MultiProgressBar {
712
2009
  }
713
2010
  }
714
2011
 
715
- // ── Animated boot sequence ───────────────────────────────────────────────────
2012
+ // ── Boot sequence ───────────────────────────────────────────────────────────
716
2013
  async function runBootSequence() {
717
2014
  const matrixChars = '01アイウエカキクサシスタチツテトナニヌネノ▓▒░█▄▀';
718
2015
  const cols = Math.min(TERM_WIDTH(), 80);
@@ -721,8 +2018,8 @@ async function runBootSequence() {
721
2018
  for (let row = 0; row < 4; row++) {
722
2019
  let line = ' ';
723
2020
  for (let c = 0; c < cols - 2; c++) {
724
- const ch = matrixChars[Math.floor(Math.random() * matrixChars.length)];
725
- const b = Math.random();
2021
+ const ch = matrixChars[Math.floor(Math.random() * matrixChars.length)];
2022
+ const b = Math.random();
726
2023
  const palette = row % 2 === 0 ? PALETTE_MATRIX : PALETTE_CYBER;
727
2024
  if (b > 0.85) line += rgbText(ch, palette[4] || '#00FF00');
728
2025
  else if (b > 0.6) line += rgbText(ch, palette[2] || '#006600');
@@ -750,8 +2047,8 @@ async function printAnimatedBanner() {
750
2047
  ' ║ ██████╔╝██║ ██║╚██████╗██║ ██╗███████╗██║███████║ ██║ ║',
751
2048
  ' ║ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝╚══════╝ ╚═╝ ║',
752
2049
  ' ║ ║',
753
- ' ║ ⚡ v10.0-ULTRA-OMEGA · 18 Stacks · 5X Faster AST Engine ║',
754
- ' ║ Ubuntu/WSL2/Docker Native · AI-Powered · Zero Fake Data ║',
2050
+ ' ║ ⚡ v11.0-ULTRA-OMEGA · 18 Stacks · 30-Point Full Index Engine ║',
2051
+ ' ║ AST + Semantic Analysis · AI-Powered · Zero Fake Data ║',
755
2052
  ' ╚══════════════════════════════════════════════════════════════════════╝',
756
2053
  ];
757
2054
 
@@ -759,7 +2056,7 @@ async function printAnimatedBanner() {
759
2056
  for (let i = 0; i < lines.length; i++) {
760
2057
  const line = lines[i];
761
2058
  const offset = i * 4;
762
- if (i === 0 || i === 10) {
2059
+ if (i === 0 || i === lines.length - 1) {
763
2060
  process.stdout.write(gradientText(line, offset, PALETTE_CYBER) + '\n');
764
2061
  } else if (i >= 1 && i <= 6) {
765
2062
  process.stdout.write(' ' + rgbText('║', '#00F5FF'));
@@ -780,18 +2077,17 @@ async function printAnimatedBanner() {
780
2077
  process.stdout.write(SHOW);
781
2078
  console.log('');
782
2079
 
783
- // ── System info bar ─────────────────────────────────────────────────────
784
- const sys = SystemMonitor.snapshot();
785
- const bootMs = (performance.now() - BOOT_START).toFixed(0);
2080
+ // System info bar
2081
+ const sys = SystemMonitor.snapshot();
2082
+ const bootMs = (performance.now() - BOOT_START).toFixed(0);
786
2083
  const envBadge = ENV.isWSL ? '🐧WSL2' : ENV.isDocker ? '🐳Docker' : ENV.isCI ? '⚙️CI' : ENV.isLinux ? '🐧Linux' : ENV.isMac ? '🍎Mac' : '🪟Win';
787
- const pkgMgr = ENV.hasBun ? 'bun' : ENV.hasPnpm ? 'pnpm' : ENV.hasYarn ? 'yarn' : 'npm';
2084
+ const pkgMgr = ENV.hasBun ? 'bun' : ENV.hasPnpm ? 'pnpm' : ENV.hasYarn ? 'yarn' : 'npm';
788
2085
 
789
2086
  const stats = [
790
2087
  { l: 'Boot', v: bootMs + 'ms', c: '#00FF9F' },
791
2088
  { l: 'Node', v: process.version, c: '#00F5FF' },
792
2089
  { l: 'CPU', v: sys.cpuAvg + '%', c: sys.cpuAvg > 80 ? '#FF006E' : '#FFBE0B' },
793
2090
  { l: 'RAM', v: sys.freeRAM + 'MB free', c: '#BF40FF' },
794
- { l: 'Heap', v: sys.heapUsed + 'MB', c: '#FF6B6B' },
795
2091
  { l: 'Cores', v: String(ENV.cpuCount), c: '#00F5FF' },
796
2092
  { l: 'Env', v: envBadge, c: '#00FF9F' },
797
2093
  { l: 'Pkg', v: pkgMgr, c: '#FFBE0B' },
@@ -801,21 +2097,18 @@ async function printAnimatedBanner() {
801
2097
  process.stdout.write(HIDE);
802
2098
  let statLine = ' ';
803
2099
  for (const s of stats) statLine += chalk.gray(s.l + ':') + rgbText(s.v, s.c) + chalk.gray(' │ ');
804
-
805
2100
  for (let i = 0; i <= statLine.length; i += 5) {
806
2101
  process.stdout.write(CLEAR_LINE + statLine.slice(0, i));
807
2102
  await sleep(6);
808
2103
  }
809
2104
  process.stdout.write(CLEAR_LINE + statLine + '\n');
810
2105
 
811
- // Ubuntu-specific info
812
2106
  if (ENV.isLinux || ENV.isWSL) {
813
2107
  await sleep(40);
814
2108
  const distroLine = ` ${chalk.gray('OS:')}${rgbText(ENV.distro, '#00F5FF')} ${chalk.gray('│ Arch:')}${rgbText(ENV.arch, '#FFBE0B')} ${chalk.gray('│ RAM:')}${rgbText(ENV.totalRAM + 'GB', '#BF40FF')} ${chalk.gray('│ Docker:')}${rgbText(ENV.hasDocker ? '✓' : '✗', ENV.hasDocker ? '#00FF9F' : '#FF006E')}`;
815
2109
  process.stdout.write(CLEAR_LINE + distroLine + '\n');
816
2110
  }
817
2111
 
818
- // Animated divider
819
2112
  const divW = TERM_WIDTH() - 4;
820
2113
  process.stdout.write(' ');
821
2114
  for (let i = 0; i < divW; i++) {
@@ -829,7 +2122,7 @@ let _off = 0;
829
2122
 
830
2123
  async function printSectionHeader(title, icon = '◆', color = '#00F5FF') {
831
2124
  console.log('');
832
- const w = TERM_WIDTH() - 6;
2125
+ const w = TERM_WIDTH() - 6;
833
2126
  process.stdout.write(HIDE);
834
2127
  process.stdout.write(' ' + rgbText(`╔${'━'.repeat(w)}╗`, color) + '\n');
835
2128
  await sleep(25);
@@ -846,34 +2139,37 @@ async function printSectionHeader(title, icon = '◆', color = '#00F5FF') {
846
2139
  }
847
2140
 
848
2141
  // ═══════════════════════════════════════════════════════════════════════════════════
849
- // 🔍 Framework Detector
2142
+ // 🔍 Framework Detector — [1] Project Structure Scan
850
2143
  // ═══════════════════════════════════════════════════════════════════════════════════
851
2144
 
852
2145
  async function detectProjectEnvironment(cwd = process.cwd()) {
853
2146
  const result = {
854
- framework : null,
2147
+ framework : null,
855
2148
  packageManager: 'npm',
856
- hasMonorepo : false,
857
- hasTurborepo : false,
858
- hasPrisma : false,
859
- hasDrizzle : false,
860
- hasTypeORM : false,
861
- hasTRPC : false,
862
- hasGraphQL : false,
863
- hasOpenAPI : false,
864
- nodeVersion : null,
865
- tsConfig : false,
866
- testRunner : null,
867
- linter : null,
868
- formatter : null,
2149
+ hasMonorepo : false,
2150
+ hasTurborepo : false,
2151
+ hasPrisma : false,
2152
+ hasDrizzle : false,
2153
+ hasTypeORM : false,
2154
+ hasTRPC : false,
2155
+ hasGraphQL : false,
2156
+ hasOpenAPI : false,
2157
+ hasAppRouter : false,
2158
+ hasPagesRouter: false,
2159
+ nodeVersion : null,
2160
+ tsConfig : false,
2161
+ testRunner : null,
2162
+ linter : null,
2163
+ formatter : null,
2164
+ folderStructure: [],
869
2165
  };
870
2166
 
871
2167
  // Package manager detection
872
- if (await fs.pathExists(path.join(cwd, 'bun.lockb'))) result.packageManager = 'bun';
2168
+ if (await fs.pathExists(path.join(cwd, 'bun.lockb'))) result.packageManager = 'bun';
873
2169
  else if (await fs.pathExists(path.join(cwd, 'pnpm-lock.yaml'))) result.packageManager = 'pnpm';
874
2170
  else if (await fs.pathExists(path.join(cwd, 'yarn.lock'))) result.packageManager = 'yarn';
875
2171
 
876
- // Monorepo detection
2172
+ // Monorepo
877
2173
  result.hasMonorepo = await fs.pathExists(path.join(cwd, 'turbo.json')) ||
878
2174
  await fs.pathExists(path.join(cwd, 'nx.json')) ||
879
2175
  await fs.pathExists(path.join(cwd, 'lerna.json'));
@@ -882,6 +2178,18 @@ async function detectProjectEnvironment(cwd = process.cwd()) {
882
2178
  // TypeScript
883
2179
  result.tsConfig = await fs.pathExists(path.join(cwd, 'tsconfig.json'));
884
2180
 
2181
+ // Next.js router detection
2182
+ result.hasAppRouter = await fs.pathExists(path.join(cwd, 'app')) || await fs.pathExists(path.join(cwd, 'src', 'app'));
2183
+ result.hasPagesRouter = await fs.pathExists(path.join(cwd, 'pages')) || await fs.pathExists(path.join(cwd, 'src', 'pages'));
2184
+
2185
+ // Folder structure scan
2186
+ const topFolders = ['src','app','pages','components','services','hooks','lib','utils','store','contexts','types','api'];
2187
+ for (const folder of topFolders) {
2188
+ if (await fs.pathExists(path.join(cwd, folder)) || await fs.pathExists(path.join(cwd, 'src', folder))) {
2189
+ result.folderStructure.push(folder);
2190
+ }
2191
+ }
2192
+
885
2193
  try {
886
2194
  const pkgPath = path.join(cwd, 'package.json');
887
2195
  if (await fs.pathExists(pkgPath)) {
@@ -903,13 +2211,13 @@ async function detectProjectEnvironment(cwd = process.cwd()) {
903
2211
  result.hasGraphQL = !!(deps['graphql'] || deps['@apollo/server'] || deps['type-graphql']);
904
2212
  result.hasOpenAPI = !!(deps['swagger-ui-react'] || deps['openapi-typescript']);
905
2213
 
906
- // Node version from .nvmrc or engines
2214
+ // Node version
907
2215
  if (pkg.engines?.node) result.nodeVersion = pkg.engines.node;
908
2216
 
909
2217
  // Test runners
910
- if (deps['vitest']) result.testRunner = 'vitest';
911
- else if (deps['jest']) result.testRunner = 'jest';
912
- else if (deps['mocha']) result.testRunner = 'mocha';
2218
+ if (deps['vitest']) result.testRunner = 'vitest';
2219
+ else if (deps['jest']) result.testRunner = 'jest';
2220
+ else if (deps['mocha']) result.testRunner = 'mocha';
913
2221
 
914
2222
  // Linter/formatter
915
2223
  if (deps['eslint']) result.linter = 'eslint';
@@ -923,53 +2231,147 @@ async function detectProjectEnvironment(cwd = process.cwd()) {
923
2231
  }
924
2232
 
925
2233
  // ═══════════════════════════════════════════════════════════════════════════════════
926
- // 🚀 Ultra Generation Pipeline
2234
+ // 🚀 Ultra Generation Pipeline — Full 30-point index engine
927
2235
  // ═══════════════════════════════════════════════════════════════════════════════════
928
2236
 
929
2237
  async function runUltraPipeline(options) {
930
- await printSectionHeader('Ultra AST Engine v5.0 — Parallel Scan', '⚡', '#00F5FF');
931
-
932
- const t0 = performance.now();
933
- const multiBar = new MultiProgressBar();
934
-
935
- multiBar.add('env', 'Environment Detection', '#00F5FF');
936
- multiBar.add('ast', 'AST Parallel Scan', '#FFBE0B');
937
- multiBar.add('graphql', 'GraphQL/tRPC Detection', '#BF40FF');
938
- multiBar.add('openapi', 'OpenAPI Spec Generation', '#00FF9F');
939
- multiBar.add('scaffold', 'Template Scaffolding', '#FF6B6B');
2238
+ await printSectionHeader('Full Index Engine v6.0 — 30-Point Scan', '⚡', '#00F5FF');
2239
+
2240
+ const t0 = performance.now();
2241
+ const multiBar = new MultiProgressBar();
2242
+
2243
+ multiBar.add('env', '[1] Project Structure + Framework', '#00F5FF');
2244
+ multiBar.add('ast', '[2-4] Routes + API + HTTP Methods', '#FFBE0B');
2245
+ multiBar.add('payload', '[5-7] Payloads + Forms + Validation', '#BF40FF');
2246
+ multiBar.add('auth', '[8-9] Auth + RBAC Detection', '#FF6B6B');
2247
+ multiBar.add('services', '[10-16] Env + State + DB + Payments', '#00FF9F');
2248
+ multiBar.add('security', '[17-20] SEO + Errors + Security', '#FF006E');
2249
+ multiBar.add('openapi', '[22] OpenAPI 3.1 Spec Generation', '#00FF9F');
2250
+ multiBar.add('devops', '[28] DevOps Config Generation', '#3A86FF');
2251
+ multiBar.add('qa', '[25-27] QA Tests + Bug Reports', '#FFBE0B');
2252
+ multiBar.add('scaffold', '[30] Final Backend Scaffolding', '#FF6B6B');
940
2253
  multiBar.start();
941
2254
 
942
- // Step 1 Env detection
2255
+ // PHASE 1: Project structure ──────────────────────────────────────────
943
2256
  multiBar.update('env', 10);
944
2257
  const envInfo = await detectProjectEnvironment(process.cwd());
945
- multiBar.update('env', 100, `Detected: ${envInfo.framework?.name || 'Unknown'} · ${envInfo.packageManager}`);
2258
+ multiBar.update('env', 100,
2259
+ `${envInfo.framework?.name || 'Unknown'} · ${envInfo.packageManager} · ${envInfo.folderStructure.length} dirs`
2260
+ );
946
2261
  await sleep(200);
947
2262
 
948
- // Step 2 AST parallel scan with live progress
2263
+ // PHASE 2-4: Full AST parallel scan ──────────────────────────────────
949
2264
  multiBar.update('ast', 5);
950
2265
  const astResult = await AST_ENGINE.scan(options.frontendSrcDir, (pct, processed, total, epCount) => {
951
- multiBar.update('ast', pct, `AST Scan: ${processed}/${total} files · ${epCount} endpoints`);
2266
+ multiBar.update('ast', pct, `AST: ${processed}/${total} files · ${epCount} endpoints`);
952
2267
  });
953
- multiBar.update('ast', 100, `AST: ${astResult.files} files · ${astResult.endpoints.length} endpoints · ${astResult.duration}s`);
2268
+ multiBar.update('ast', 100,
2269
+ `${astResult.files} files · ${astResult.endpoints.length} endpoints · ${astResult.routes?.length || 0} routes · ${astResult.duration}s`
2270
+ );
2271
+ await sleep(150);
2272
+
2273
+ // ─ PHASE 5-7: Payloads + Forms + Validations ──────────────────────────
2274
+ multiBar.update('payload', 50);
2275
+ const formCount = astResult.forms?.length || 0;
2276
+ const validationCount = astResult.validations?.length || 0;
2277
+ const payloadCount = astResult.payloads?.length || 0;
2278
+ multiBar.update('payload', 100,
2279
+ `${formCount} forms · ${validationCount} validators · ${payloadCount} payloads`
2280
+ );
954
2281
  await sleep(150);
955
2282
 
956
- // Step 3 GraphQL/tRPC
957
- multiBar.update('graphql', 20);
2283
+ // PHASE 8-9: Auth + RBAC ─────────────────────────────────────────────
2284
+ multiBar.update('auth', 50);
2285
+ const authCount = astResult.authSignals?.length || 0;
2286
+ const roleCount = (astResult.roleSignals?.flatMap(r => r.roles || []) || []).length;
2287
+ multiBar.update('auth', 100, `${authCount} auth signals · ${roleCount} roles detected`);
2288
+ await sleep(150);
2289
+
2290
+ // ─ PHASE 10-16: Services scan ─────────────────────────────────────────
2291
+ multiBar.update('services', 30);
958
2292
  const gqlSchemas = await AST_ENGINE.extractGraphQLSchema(options.frontendSrcDir);
959
2293
  const trpcRouters = await AST_ENGINE.extractTRPCRouters(options.frontendSrcDir);
960
- multiBar.update('graphql', 100, `GraphQL: ${gqlSchemas.length} ops · tRPC: ${trpcRouters.reduce((a,r) => a + r.count, 0)} procedures`);
2294
+ multiBar.update('services', 70,
2295
+ `GraphQL: ${gqlSchemas.length} · tRPC: ${trpcRouters.reduce((a,r) => a+r.count,0)} · 3rd-party: ${astResult.thirdParty?.length || 0}`
2296
+ );
2297
+ multiBar.update('services', 100,
2298
+ `DB models: ${astResult.models?.length || 0} · Uploads: ${astResult.uploads?.length || 0} · Payments: ${astResult.payments?.length || 0}`
2299
+ );
2300
+ await sleep(150);
2301
+
2302
+ // ─ PHASE 17-20: Security + SEO + Components ───────────────────────────
2303
+ multiBar.update('security', 50);
2304
+ const riskCount = astResult.securityRisks?.length || 0;
2305
+ const critCount = astResult.securityRisks?.filter(r => r.severity === 'CRITICAL').length || 0;
2306
+ multiBar.update('security', 100,
2307
+ `${riskCount} risks · ${critCount} critical · ${astResult.components?.length || 0} component behaviors`
2308
+ );
961
2309
  await sleep(150);
962
2310
 
963
- // Step 4 OpenAPI spec
2311
+ // PHASE 22: OpenAPI spec ─────────────────────────────────────────────
964
2312
  multiBar.update('openapi', 20);
965
- const openApiSpec = AST_ENGINE.generateOpenAPISpec(astResult.endpoints, options.projectName);
2313
+ const openApiSpec = AST_ENGINE.generateOpenAPISpec(astResult.endpoints, options.projectName, '1.0.0', astResult);
966
2314
  const specPath = path.join(options.projectDir, 'openapi.json');
967
2315
  await fs.ensureDir(options.projectDir);
968
2316
  await fs.writeJson(specPath, openApiSpec, { spaces: 2 });
969
- multiBar.update('openapi', 100, `OpenAPI 3.1 spec: ${Object.keys(openApiSpec.paths).length} paths written`);
2317
+ multiBar.update('openapi', 100,
2318
+ `OpenAPI 3.1 · ${Object.keys(openApiSpec.paths).length} paths · ${Object.keys(openApiSpec.components?.schemas || {}).length} schemas`
2319
+ );
2320
+ await sleep(150);
2321
+
2322
+ // ─ PHASE 24: Prisma schema ────────────────────────────────────────────
2323
+ const prismaSchema = AST_ENGINE.generatePrismaSchema(astResult, options.dbType);
2324
+ if (prismaSchema && options.ormType === 'prisma') {
2325
+ const prismaDir = path.join(options.projectDir, 'prisma');
2326
+ await fs.ensureDir(prismaDir);
2327
+ await fs.writeFile(path.join(prismaDir, 'schema.prisma'), prismaSchema);
2328
+ }
2329
+
2330
+ // ─ PHASE 28: DevOps configs ───────────────────────────────────────────
2331
+ multiBar.update('devops', 30);
2332
+ const devopsConfig = AST_ENGINE.generateDevOpsConfig(options.stack, options.projectName, astResult);
2333
+ if (devopsConfig.dockerCompose) {
2334
+ await fs.writeFile(path.join(options.projectDir, 'docker-compose.yml'), devopsConfig.dockerCompose);
2335
+ }
2336
+ if (devopsConfig.dockerfile) {
2337
+ await fs.writeFile(path.join(options.projectDir, 'Dockerfile'), devopsConfig.dockerfile);
2338
+ }
2339
+ if (options.ciProvider === 'github' && devopsConfig.githubWorkflow) {
2340
+ await fs.ensureDir(path.join(options.projectDir, '.github', 'workflows'));
2341
+ await fs.writeFile(path.join(options.projectDir, '.github', 'workflows', 'ci.yml'), devopsConfig.githubWorkflow);
2342
+ }
2343
+ multiBar.update('devops', 100, 'Dockerfile · docker-compose.yml · CI/CD workflow');
2344
+ await sleep(150);
2345
+
2346
+ // ─ PHASE 25-27: QA tests + Bug report ────────────────────────────────
2347
+ multiBar.update('qa', 30);
2348
+ const qaTests = AST_ENGINE.generateQATests(astResult, options.projectName);
2349
+ const bugReport = AST_ENGINE.generateBugReport(astResult);
2350
+
2351
+ if (qaTests.length > 0) {
2352
+ const testDir = path.join(options.projectDir, 'tests');
2353
+ await fs.ensureDir(testDir);
2354
+ const testCode = `// Auto-generated by create-backlist v${VERSION}
2355
+ import request from 'supertest';
2356
+ import app from '../src/app';
2357
+
2358
+ let testToken = process.env.TEST_TOKEN || '';
2359
+
2360
+ describe('Auto-generated API Tests', () => {
2361
+ ${qaTests.map(t => t.code).join('\n')}
2362
+ });
2363
+ `;
2364
+ await fs.writeFile(path.join(testDir, 'api.test.js'), testCode);
2365
+ }
2366
+
2367
+ if (bugReport.length > 0) {
2368
+ await fs.writeJson(path.join(options.projectDir, 'bug-report.json'), { generatedAt: new Date().toISOString(), bugs: bugReport }, { spaces: 2 });
2369
+ }
2370
+
2371
+ multiBar.update('qa', 100, `${qaTests.length} tests · ${bugReport.length} bugs found`);
970
2372
  await sleep(150);
971
2373
 
972
- // Step 5 Scaffolding
2374
+ // PHASE 30: Final backend scaffolding ───────────────────────────────
973
2375
  multiBar.update('scaffold', 10);
974
2376
  options.astResult = astResult;
975
2377
  options.envInfo = envInfo;
@@ -991,19 +2393,29 @@ async function runUltraPipeline(options) {
991
2393
  await sleep(100);
992
2394
  const drift = await performSmartDriftCheck(options.frontendSrcDir, astResult.endpoints);
993
2395
 
994
- // Summary
2396
+ // ─ Extended summary console output ────────────────────────────────────
995
2397
  const duration = ((performance.now() - t0) / 1000).toFixed(2);
996
2398
  console.log(`\n ${rgbText('◆', '#00F5FF')} Scan: ${rgbText(astResult.files + ' files', '#00FF9F')} · ${rgbText(astResult.endpoints.length + ' endpoints', '#FFBE0B')} · ${rgbText(duration + 's', '#BF40FF')} · Cache: ${rgbText(astResult.cacheHits + ' hits', '#00F5FF')}`);
2399
+ console.log(` ${rgbText('◆', '#BF40FF')} Forms: ${rgbText((astResult.forms?.length || 0) + '', '#FFBE0B')} · Validations: ${rgbText((astResult.validations?.length || 0) + '', '#FFBE0B')} · Auth signals: ${rgbText((astResult.authSignals?.length || 0) + '', '#00FF9F')}`);
2400
+ console.log(` ${rgbText('◆', '#FF6B6B')} DB Models: ${rgbText((astResult.models?.length || 0) + '', '#00F5FF')} · 3rd-party: ${rgbText((astResult.thirdParty?.length || 0) + '', '#FFBE0B')} · Payments: ${rgbText((astResult.payments?.length || 0) + '', '#00FF9F')}`);
2401
+
2402
+ if (astResult.securityRisks?.length > 0) {
2403
+ const crit = astResult.securityRisks.filter(r => r.severity === 'CRITICAL').length;
2404
+ console.log(` ${rgbText('⚠', '#FF006E')} Security: ${rgbText(astResult.securityRisks.length + ' risks', '#FF006E')} · ${rgbText(crit + ' CRITICAL', crit > 0 ? '#FF006E' : '#FFBE0B')} — see bug-report.json`);
2405
+ } else {
2406
+ console.log(` ${rgbText('✓', '#00FF9F')} Security scan passed — no critical risks found`);
2407
+ }
997
2408
 
998
2409
  if (envInfo.hasTRPC) console.log(` ${rgbText('◆', '#BF40FF')} tRPC: ${rgbText(trpcRouters.reduce((a,r)=>a+r.count,0) + ' procedures', '#BF40FF')}`);
999
2410
  if (envInfo.hasGraphQL) console.log(` ${rgbText('◆', '#00F5FF')} GraphQL: ${rgbText(gqlSchemas.length + ' operations', '#00F5FF')}`);
1000
2411
  if (drift.length > 0) console.log(` ${rgbText('⚠', '#FFBE0B')} DOM drift: ${chalk.yellow(drift.length + ' inconsistencies detected')}`);
1001
2412
  else console.log(` ${rgbText('✓', '#00FF9F')} DOM drift check passed`);
1002
- if (envInfo.hasPrisma) console.log(` ${rgbText('◆', '#FF6B6B')} Prisma 5 schema inference enabled`);
2413
+ if (envInfo.hasPrisma) console.log(` ${rgbText('◆', '#FF6B6B')} Prisma 5 schema: ${rgbText((astResult.models?.length || 0) + ' models inferred', '#FF6B6B')}`);
1003
2414
  if (envInfo.hasDrizzle) console.log(` ${rgbText('◆', '#3A86FF')} DrizzleORM detected — type-safe queries`);
1004
2415
 
1005
- // Quality score
1006
2416
  console.log(` ${rgbText('◆', '#FFBE0B')} Code quality: ${rgbText(astResult.quality + '/100', astResult.quality >= 80 ? '#00FF9F' : astResult.quality >= 60 ? '#FFBE0B' : '#FF006E')}`);
2417
+ console.log(` ${rgbText('◆', '#00FF9F')} OpenAPI spec: ${rgbText(Object.keys(openApiSpec.paths).length + ' paths', '#00FF9F')} — openapi.json`);
2418
+ console.log(` ${rgbText('◆', '#3A86FF')} Tests: ${rgbText(qaTests.length + ' test cases', '#3A86FF')} — tests/api.test.js`);
1007
2419
 
1008
2420
  options._meta = {
1009
2421
  endpointCount : astResult.endpoints.length,
@@ -1013,13 +2425,17 @@ async function runUltraPipeline(options) {
1013
2425
  cacheHits : astResult.cacheHits,
1014
2426
  hasGraphQL : envInfo.hasGraphQL,
1015
2427
  hasTRPC : envInfo.hasTRPC,
2428
+ formCount,
2429
+ modelCount : astResult.models?.length || 0,
2430
+ securityRisks : riskCount,
2431
+ testsGenerated: qaTests.length,
1016
2432
  };
1017
2433
  }
1018
2434
 
1019
- // Smart drift check — compares AST endpoints against actual file paths
2435
+ // Smart drift check
1020
2436
  async function performSmartDriftCheck(srcDir, endpoints) {
1021
2437
  const inconsistencies = [];
1022
- const files = await AST_ENGINE.collectFiles(srcDir);
2438
+ const files = await AST_ENGINE.collectFiles(srcDir);
1023
2439
  const fileSet = new Set(files.map(f => path.relative(srcDir, f)));
1024
2440
 
1025
2441
  for (const ep of endpoints.slice(0, 50)) {
@@ -1038,7 +2454,6 @@ async function performSmartDriftCheck(srcDir, endpoints) {
1038
2454
  // ═══════════════════════════════════════════════════════════════════════════════════
1039
2455
 
1040
2456
  async function dispatchGenerator(options) {
1041
- // Dynamic imports for generators — only load what's needed
1042
2457
  const generators = {
1043
2458
  'node-ts-express' : () => import('../src/generators/node.js').then(m => m.generateNodeProject(options)),
1044
2459
  'js-express' : () => import('../src/generators/js.js').then(m => m.generateJsProject(options)),
@@ -1065,7 +2480,7 @@ async function dispatchGenerator(options) {
1065
2480
  }
1066
2481
 
1067
2482
  // ═══════════════════════════════════════════════════════════════════════════════════
1068
- // 🔍 Enhanced Pre-flight Checks
2483
+ // 🔍 Pre-flight Checks
1069
2484
  // ═══════════════════════════════════════════════════════════════════════════════════
1070
2485
 
1071
2486
  async function isCommandAvailable(cmd) {
@@ -1084,26 +2499,23 @@ async function runPreflightChecks(stack) {
1084
2499
  { name: 'Gradle installed', cmd: 'gradle', hint: 'https://gradle.org/install/' }],
1085
2500
  'python-fastapi' : [{ name: 'Python ≥ 3.10', cmd: 'python3', hint: 'https://python.org' }],
1086
2501
  'python-django' : [{ name: 'Python ≥ 3.10', cmd: 'python3', hint: 'https://python.org' }],
1087
- 'go-fiber' : [{ name: 'Go ≥ 1.21', cmd: 'go', hint: 'https://go.dev/dl/' }],
1088
- 'go-gin' : [{ name: 'Go ≥ 1.21', cmd: 'go', hint: 'https://go.dev/dl/' }],
1089
- 'rust-actix' : [{ name: 'Rust + Cargo', cmd: 'cargo', hint: 'https://rustup.rs' }],
1090
- 'rust-axum' : [{ name: 'Rust + Cargo', cmd: 'cargo', hint: 'https://rustup.rs' }],
1091
- 'deno-oak' : [{ name: 'Deno ≥ 1.40', cmd: 'deno', hint: 'https://deno.land/#installation' }],
1092
- 'php-laravel' : [{ name: 'PHP ≥ 8.2', cmd: 'php', hint: 'https://php.net' },
1093
- { name: 'Composer', cmd: 'composer', hint: 'https://getcomposer.org' }],
1094
- 'elixir-phoenix' : [{ name: 'Elixir ≥ 1.15', cmd: 'elixir', hint: 'https://elixir-lang.org/install.html' },
1095
- { name: 'Mix installed', cmd: 'mix', hint: 'Comes with Elixir' }],
1096
- 'bun-elysia' : [{ name: 'Bun ≥ 1.0', cmd: 'bun', hint: 'https://bun.sh' }],
2502
+ 'go-fiber' : [{ name: 'Go ≥ 1.21', cmd: 'go', hint: 'https://go.dev/dl/' }],
2503
+ 'go-gin' : [{ name: 'Go ≥ 1.21', cmd: 'go', hint: 'https://go.dev/dl/' }],
2504
+ 'rust-actix' : [{ name: 'Rust + Cargo', cmd: 'cargo', hint: 'https://rustup.rs' }],
2505
+ 'rust-axum' : [{ name: 'Rust + Cargo', cmd: 'cargo', hint: 'https://rustup.rs' }],
2506
+ 'deno-oak' : [{ name: 'Deno ≥ 1.40', cmd: 'deno', hint: 'https://deno.land/#installation' }],
2507
+ 'php-laravel' : [{ name: 'PHP ≥ 8.2', cmd: 'php', hint: 'https://php.net' },
2508
+ { name: 'Composer', cmd: 'composer', hint: 'https://getcomposer.org' }],
2509
+ 'elixir-phoenix' : [{ name: 'Elixir ≥ 1.15', cmd: 'elixir', hint: 'https://elixir-lang.org/install.html' },
2510
+ { name: 'Mix installed', cmd: 'mix', hint: 'Comes with Elixir' }],
2511
+ 'bun-elysia' : [{ name: 'Bun ≥ 1.0', cmd: 'bun', hint: 'https://bun.sh' }],
1097
2512
  };
1098
2513
 
1099
2514
  const checks = [
1100
- { name: 'Node.js ≥ 18', test: () => ENV.nodeVer >= 18, fix: 'https://nodejs.org' },
2515
+ { name: 'Node.js ≥ 18', test: () => ENV.nodeVer >= 18, fix: 'https://nodejs.org' },
1101
2516
  ...(stackChecks[stack] || []).map(c => ({
1102
- name: c.name,
1103
- test: () => isCommandAvailable(c.cmd),
1104
- fix : c.hint,
2517
+ name: c.name, test: () => isCommandAvailable(c.cmd), fix: c.hint,
1105
2518
  })),
1106
- // Ubuntu-specific
1107
2519
  ...(ENV.isLinux ? [{ name: 'Linux filesystem r/w', test: () => true, fix: '' }] : []),
1108
2520
  ];
1109
2521
 
@@ -1123,11 +2535,15 @@ async function runPreflightChecks(stack) {
1123
2535
  await sleep(50);
1124
2536
  }
1125
2537
 
1126
- // Ubuntu package hints
1127
2538
  if (ENV.isLinux && failed.length > 0) {
1128
2539
  console.log('');
1129
2540
  console.log(chalk.dim(' 📦 Ubuntu quick-install hints:'));
1130
- const aptMap = { 'Java': 'sudo apt install default-jdk', 'Python': 'sudo apt install python3 python3-pip', 'Go': 'sudo apt install golang-go', 'PHP': 'sudo apt install php8.2 php8.2-cli composer' };
2541
+ const aptMap = {
2542
+ 'Java' : 'sudo apt install default-jdk',
2543
+ 'Python' : 'sudo apt install python3 python3-pip',
2544
+ 'Go' : 'sudo apt install golang-go',
2545
+ 'PHP' : 'sudo apt install php8.2 php8.2-cli composer',
2546
+ };
1131
2547
  for (const f of failed) {
1132
2548
  for (const [k, v] of Object.entries(aptMap)) {
1133
2549
  if (f.name.includes(k)) console.log(chalk.gray(` → ${v}`));
@@ -1166,7 +2582,6 @@ async function saveSession(options) {
1166
2582
  } catch {}
1167
2583
  }
1168
2584
 
1169
- // State snapshot for crash recovery
1170
2585
  async function saveSnapshot(options, phase) {
1171
2586
  try {
1172
2587
  await fs.ensureDir(SNAPSHOTS_DIR);
@@ -1178,7 +2593,7 @@ async function saveSnapshot(options, phase) {
1178
2593
 
1179
2594
  async function loadSnapshot(projectName) {
1180
2595
  try {
1181
- const files = await fs.readdir(SNAPSHOTS_DIR);
2596
+ const files = await fs.readdir(SNAPSHOTS_DIR);
1182
2597
  const snapFiles = files.filter(f => f.startsWith(projectName + '-')).sort();
1183
2598
  if (snapFiles.length === 0) return null;
1184
2599
  return await fs.readJson(path.join(SNAPSHOTS_DIR, snapFiles[snapFiles.length - 1]));
@@ -1213,16 +2628,19 @@ async function printAnimatedStats(mode, startTime, extra = {}) {
1213
2628
  await printSectionHeader('Generation Summary', '📊', '#BF40FF');
1214
2629
 
1215
2630
  const stats = [
1216
- { l: 'Mode', v: mode === 'pro' ? 'PRO AI ✦' : 'Ultra AST ⚡', c: mode === 'pro' ? '#BF40FF' : '#00F5FF' },
1217
- { l: 'Elapsed', v: elapsed + 's', c: '#00FF9F' },
1218
- { l: 'CPU Used', v: sys.cpuAvg + '%', c: sys.cpuAvg > 80 ? '#FF006E' : '#FFBE0B' },
1219
- { l: 'RAM Used', v: sys.heapUsed + 'MB', c: '#BF40FF' },
1220
- ...(extra.endpointCount !== undefined ? [{ l: 'Endpoints', v: extra.endpointCount + ' detected', c: '#00F5FF' }] : []),
2631
+ { l: 'Mode', v: mode === 'pro' ? 'PRO AI ✦' : 'Full Index Engine ⚡', c: mode === 'pro' ? '#BF40FF' : '#00F5FF' },
2632
+ { l: 'Elapsed', v: elapsed + 's', c: '#00FF9F' },
2633
+ { l: 'CPU Used', v: sys.cpuAvg + '%', c: sys.cpuAvg > 80 ? '#FF006E' : '#FFBE0B' },
2634
+ { l: 'RAM Used', v: sys.heapUsed + 'MB', c: '#BF40FF' },
2635
+ ...(extra.endpointCount !== undefined ? [{ l: 'Endpoints', v: extra.endpointCount + ' detected', c: '#00F5FF' }] : []),
1221
2636
  ...(extra.filesScanned !== undefined ? [{ l: 'Files scanned', v: String(extra.filesScanned), c: '#FFBE0B' }] : []),
1222
- ...(extra.filesGenerated !== undefined ? [{ l: 'Files written', v: String(extra.filesGenerated), c: '#FFBE0B' }] : []),
2637
+ ...(extra.formCount !== undefined ? [{ l: 'Forms found', v: String(extra.formCount), c: '#FFBE0B' }] : []),
2638
+ ...(extra.modelCount !== undefined ? [{ l: 'DB Models', v: String(extra.modelCount), c: '#00FF9F' }] : []),
2639
+ ...(extra.securityRisks !== undefined ? [{ l: 'Security risks', v: String(extra.securityRisks), c: extra.securityRisks > 0 ? '#FF006E' : '#00FF9F' }] : []),
2640
+ ...(extra.testsGenerated !== undefined ? [{ l: 'Tests gen.', v: String(extra.testsGenerated), c: '#3A86FF' }] : []),
1223
2641
  ...(extra.quality !== undefined ? [{ l: 'Code quality', v: extra.quality + '/100', c: extra.quality >= 80 ? '#00FF9F' : '#FFBE0B' }] : []),
1224
- ...(extra.cacheHits > 0 ? [{ l: 'Cache hits', v: extra.cacheHits + ' (faster!)', c: '#00FF9F' }] : []),
1225
- ...(extra.retries > 0 ? [{ l: 'Retries', v: String(extra.retries), c: '#FF6B6B' }] : []),
2642
+ ...(extra.cacheHits > 0 ? [{ l: 'Cache hits', v: extra.cacheHits + ' (faster!)', c: '#00FF9F' }] : []),
2643
+ ...(extra.retries > 0 ? [{ l: 'Retries', v: String(extra.retries), c: '#FF6B6B' }] : []),
1226
2644
  ];
1227
2645
 
1228
2646
  for (const s of stats) {
@@ -1235,10 +2653,7 @@ async function printAnimatedStats(mode, startTime, extra = {}) {
1235
2653
  console.log('');
1236
2654
  }
1237
2655
 
1238
- // ═══════════════════════════════════════════════════════════════════════════════════
1239
- // 📁 Animated File Tree
1240
- // ═══════════════════════════════════════════════════════════════════════════════════
1241
-
2656
+ // ── Animated file tree ──────────────────────────────────────────────────────
1242
2657
  async function printAnimatedFileTree(dir, depth = 0, maxDepth = 3) {
1243
2658
  if (depth === 0) await printSectionHeader('Generated Project Structure', '📁', '#00F5FF');
1244
2659
  try {
@@ -1261,12 +2676,9 @@ async function printAnimatedFileTree(dir, depth = 0, maxDepth = 3) {
1261
2676
  if (depth === 0) console.log('');
1262
2677
  }
1263
2678
 
1264
- // ═══════════════════════════════════════════════════════════════════════════════════
1265
- // 🚀 Next Steps — Stack-aware
1266
- // ═══════════════════════════════════════════════════════════════════════════════════
1267
-
2679
+ // ── Next Steps ──────────────────────────────────────────────────────────────
1268
2680
  async function printAnimatedNextSteps(options) {
1269
- const { projectName, stack, dbType, ormType, envInfo, ciProvider } = options;
2681
+ const { projectName, stack, dbType, ormType } = options;
1270
2682
  await printSectionHeader('Next Steps', '🚀', '#00F5FF');
1271
2683
 
1272
2684
  const pkgMgr = options.envInfo?.packageManager || (ENV.hasBun ? 'bun' : ENV.hasPnpm ? 'pnpm' : 'npm');
@@ -1304,11 +2716,12 @@ async function printAnimatedNextSteps(options) {
1304
2716
  }
1305
2717
 
1306
2718
  console.log('');
1307
- // Extra hints
1308
2719
  const hints = [
1309
2720
  ' 💡 Use `npx backlist qa` to validate your generated API.',
1310
2721
  ' 📖 OpenAPI spec: openapi.json — import into Postman or Swagger UI.',
1311
- ' 🐳 Docker: `docker-compose up -d` (if Docker features selected).',
2722
+ ' 🔒 Review bug-report.json for security findings.',
2723
+ ' 🧪 Tests ready: npm test (uses tests/api.test.js)',
2724
+ ' 🐳 Docker: `docker-compose up -d` starts all services.',
1312
2725
  ' 📚 Docs: https://backlist.dev/docs · Discord: https://backlist.dev/discord',
1313
2726
  ];
1314
2727
  for (const hint of hints) {
@@ -1365,26 +2778,25 @@ async function main() {
1365
2778
  fs.ensureDir(SNAPSHOTS_DIR),
1366
2779
  ]);
1367
2780
 
1368
- // Intro line
1369
2781
  for (let i = 0; i <= VERSION.length + 40; i += 3) {
1370
- const txt = ` ◆ create-backlist ${VERSION} — 18 Stacks · Parallel AST · Ultra Engine`;
2782
+ const txt = ` ◆ create-backlist ${VERSION} — 18 Stacks · 30-Point Index Engine · Parallel AST`;
1371
2783
  process.stdout.write(CLEAR_LINE + gradientText(txt.slice(0, i), _off));
1372
2784
  await sleep(5);
1373
2785
  }
1374
- process.stdout.write(CLEAR_LINE + gradientText(` ◆ create-backlist ${VERSION} — 18 Stacks · Parallel AST · Ultra Engine`, _off) + '\n\n');
2786
+ process.stdout.write(CLEAR_LINE + gradientText(` ◆ create-backlist ${VERSION} — 18 Stacks · 30-Point Index Engine · Parallel AST`, _off) + '\n\n');
1375
2787
 
1376
2788
  // ── Mode Selection ──────────────────────────────────────────────────────
1377
2789
  const mode = await p.select({
1378
2790
  message: 'Select mode:',
1379
2791
  options: [
1380
- { value: 'free', label: '⚡ Ultra AST Mode', hint: 'Free — Parallel AST v5 + 5X faster + OpenAPI gen' },
1381
- { value: 'pro', label: '🧠 Pro AI Mode', hint: 'AI-powered schema, auth, relations generation' },
1382
- { value: 'qa-url', label: '🌐 URL QA Scan', hint: 'Real browser + HTTP security + SEO + Lighthouse' },
1383
- { value: 'qa-manual', label: '🧪 Manual QA', hint: 'Interactive test cases' },
1384
- { value: 'qa-history', label: '📜 QA History', hint: 'Browse past runs' },
1385
- { value: 'watch', label: '👁️ Watch Mode', hint: 'Hot-reload generation on file changes' },
1386
- { value: 'cache', label: '🗄️ Cache Manager', hint: 'View / clear AST cache' },
1387
- { value: 'config', label: '⚙️ Config', hint: 'API keys & sessions' },
2792
+ { value: 'free', label: '⚡ Full Index Engine', hint: 'Free — 30-point AST scan · OpenAPI · Prisma · DevOps · Tests' },
2793
+ { value: 'pro', label: '🧠 Pro AI Mode', hint: 'AI-powered schema, auth, relations generation' },
2794
+ { value: 'qa-url', label: '🌐 URL QA Scan', hint: 'Real browser + HTTP security + SEO + Lighthouse' },
2795
+ { value: 'qa-manual', label: '🧪 Manual QA', hint: 'Interactive test cases' },
2796
+ { value: 'qa-history', label: '📜 QA History', hint: 'Browse past runs' },
2797
+ { value: 'watch', label: '👁️ Watch Mode', hint: 'Hot-reload generation on file changes' },
2798
+ { value: 'cache', label: '🗄️ Cache Manager', hint: 'View / clear AST cache' },
2799
+ { value: 'config', label: '⚙️ Config', hint: 'API keys & sessions' },
1388
2800
  ],
1389
2801
  });
1390
2802
  if (p.isCancel(mode)) { p.cancel('Cancelled.'); process.exit(0); }
@@ -1393,9 +2805,9 @@ async function main() {
1393
2805
  if (mode === 'cache') {
1394
2806
  await printSectionHeader('AST Cache Manager', '🗄️', '#00F5FF');
1395
2807
  try {
1396
- const files = await fs.readdir(CACHE_DIR);
1397
- console.log(` ${rgbText('◆', '#00F5FF')} Cache entries: ${rgbText(String(files.length), '#FFBE0B')}`);
2808
+ const files = await fs.readdir(CACHE_DIR);
1398
2809
  const totalSize = (await Promise.all(files.map(f => fs.stat(path.join(CACHE_DIR, f)).then(s => s.size)))).reduce((a, b) => a + b, 0);
2810
+ console.log(` ${rgbText('◆', '#00F5FF')} Cache entries: ${rgbText(String(files.length), '#FFBE0B')}`);
1399
2811
  console.log(` ${rgbText('◆', '#00F5FF')} Cache size: ${rgbText((totalSize / 1024).toFixed(1) + ' KB', '#FFBE0B')}`);
1400
2812
  } catch { console.log(chalk.gray(' No cache entries.')); }
1401
2813
  const clearIt = await p.confirm({ message: 'Clear cache?', initialValue: false });
@@ -1410,9 +2822,36 @@ async function main() {
1410
2822
  await printSectionHeader('Watch Mode', '👁️', '#FFBE0B');
1411
2823
  console.log(chalk.dim(' Watch mode: re-generates backend on frontend file changes.'));
1412
2824
  console.log(chalk.dim(' Press Ctrl+C to stop.\n'));
1413
- // Watch mode stub — implement with chokidar
1414
- console.log(chalk.gray(' → Install chokidar: npm install chokidar'));
1415
- console.log(chalk.gray(' → Then watch mode will auto-trigger generation on save.'));
2825
+
2826
+ // Watch mode requires chokidar
2827
+ try {
2828
+ const chokidar = await import('chokidar');
2829
+ const srcPath = process.argv[3] || 'src';
2830
+ const resolved = path.resolve(process.cwd(), srcPath);
2831
+
2832
+ console.log(chalk.dim(` Watching: ${resolved}\n`));
2833
+ const watcher = chokidar.watch(resolved, {
2834
+ ignored : /(node_modules|\.next|dist|build)/,
2835
+ persistent : true,
2836
+ ignoreInitial: true,
2837
+ });
2838
+
2839
+ let debounce = null;
2840
+ watcher.on('change', (changedFile) => {
2841
+ clearTimeout(debounce);
2842
+ debounce = setTimeout(async () => {
2843
+ console.log(rgbText(`\n 🔄 Changed: ${path.relative(resolved, changedFile)}`, '#FFBE0B'));
2844
+ console.log(chalk.dim(' Re-scanning AST cache...'));
2845
+ AST_ENGINE.getSummary(); // invalidates changed file from cache
2846
+ console.log(rgbText(' ✓ Run generation again to rebuild backend.', '#00FF9F'));
2847
+ }, 500);
2848
+ });
2849
+
2850
+ await new Promise(() => {}); // keep alive
2851
+ } catch {
2852
+ console.log(chalk.gray(' → Install chokidar: npm install chokidar'));
2853
+ console.log(chalk.gray(' → Then watch mode will auto-trigger on file save.'));
2854
+ }
1416
2855
  return;
1417
2856
  }
1418
2857
 
@@ -1440,11 +2879,11 @@ async function main() {
1440
2879
  const action = await p.select({
1441
2880
  message: 'Action:',
1442
2881
  options: [
1443
- { value: 'clear-key', label: '🗑️ Clear API key' },
1444
- { value: 'clear-sess', label: '🗑️ Clear sessions' },
1445
- { value: 'clear-snap', label: '🗑️ Clear snapshots' },
1446
- { value: 'sysinfo', label: '🖥️ System info' },
1447
- { value: 'back', label: '← Back' },
2882
+ { value: 'clear-key', label: '🗑️ Clear API key' },
2883
+ { value: 'clear-sess', label: '🗑️ Clear sessions' },
2884
+ { value: 'clear-snap', label: '🗑️ Clear snapshots' },
2885
+ { value: 'sysinfo', label: '🖥️ System info' },
2886
+ { value: 'back', label: '← Back' },
1448
2887
  ],
1449
2888
  });
1450
2889
  if (p.isCancel(action) || action === 'back') return;
@@ -1478,7 +2917,7 @@ async function main() {
1478
2917
  if (p.isCancel(cont) || !cont) { p.cancel('Aborted.'); process.exit(0); }
1479
2918
  }
1480
2919
 
1481
- // Check for crash recovery snapshot
2920
+ // Crash recovery
1482
2921
  const snap = await loadSnapshot(String(projectName));
1483
2922
  if (snap && snap.ts > Date.now() - 1000 * 60 * 30) {
1484
2923
  p.log.warn(chalk.yellow(`⚡ Found recovery snapshot from ${new Date(snap.ts).toLocaleTimeString()}`));
@@ -1495,23 +2934,23 @@ async function main() {
1495
2934
  const stack = await p.select({
1496
2935
  message: 'Stack:',
1497
2936
  options: [
1498
- { value: 'node-ts-express', label: '🔷 Node.js TypeScript + Express', hint: 'Hexagonal · Prisma/Drizzle/Mongoose' },
1499
- { value: 'js-express', label: '🟨 Node.js JavaScript ESM + Express', hint: 'Lightweight · No TS' },
1500
- { value: 'nestjs', label: '🔴 NestJS (TypeScript)', hint: 'Enterprise · Modular · Decorators' },
1501
- { value: 'bun-elysia', label: '🥟 Bun + ElysiaJS', hint: '🆕 Ultra-fast · End-to-end types' },
1502
- { value: 'dotnet-webapi', label: '🟣 C# ASP.NET Core Web API', hint: 'Requires .NET 8 SDK' },
1503
- { value: 'dotnet-minimal', label: '🔵 C# .NET Minimal API', hint: 'Requires .NET 8 SDK · Tiny + fast' },
1504
- { value: 'java-spring', label: '🍃 Java Spring Boot 3', hint: 'Requires JDK 17+' },
1505
- { value: 'kotlin-ktor', label: '🎯 Kotlin + Ktor', hint: '🆕 Requires JDK 17+ + Gradle' },
1506
- { value: 'python-fastapi', label: '🐍 Python FastAPI', hint: 'Async · Auto docs · Pydantic v2' },
1507
- { value: 'python-django', label: '🎸 Python Django 5 REST', hint: '🆕 Batteries included · DRF' },
1508
- { value: 'go-fiber', label: '🩵 Go + Fiber v2', hint: '🆕 Requires Go ≥1.21 · Ultra-fast' },
1509
- { value: 'go-gin', label: '🍸 Go + Gin', hint: '🆕 Requires Go ≥1.21 · Popular' },
1510
- { value: 'rust-actix', label: '🦀 Rust + Actix-Web 4', hint: '🆕 Requires Cargo · Maximum perf' },
1511
- { value: 'rust-axum', label: '⚙️ Rust + Axum', hint: '🆕 Requires Cargo · Tokio async' },
1512
- { value: 'deno-oak', label: '🦕 Deno + Oak', hint: '🆕 Requires Deno ≥1.40' },
1513
- { value: 'php-laravel', label: '🔴 PHP Laravel 11', hint: '🆕 Requires PHP 8.2 + Composer' },
1514
- { value: 'elixir-phoenix', label: '🔥 Elixir Phoenix', hint: '🆕 Requires Elixir ≥1.15' },
2937
+ { value: 'node-ts-express', label: '🔷 Node.js TypeScript + Express', hint: 'Hexagonal · Prisma/Drizzle/Mongoose' },
2938
+ { value: 'js-express', label: '🟨 Node.js JavaScript ESM + Express', hint: 'Lightweight · No TS' },
2939
+ { value: 'nestjs', label: '🔴 NestJS (TypeScript)', hint: 'Enterprise · Modular · Decorators' },
2940
+ { value: 'bun-elysia', label: '🥟 Bun + ElysiaJS', hint: '🆕 Ultra-fast · End-to-end types' },
2941
+ { value: 'dotnet-webapi', label: '🟣 C# ASP.NET Core Web API', hint: 'Requires .NET 8 SDK' },
2942
+ { value: 'dotnet-minimal', label: '🔵 C# .NET Minimal API', hint: 'Requires .NET 8 SDK · Tiny + fast' },
2943
+ { value: 'java-spring', label: '🍃 Java Spring Boot 3', hint: 'Requires JDK 17+' },
2944
+ { value: 'kotlin-ktor', label: '🎯 Kotlin + Ktor', hint: '🆕 Requires JDK 17+ + Gradle' },
2945
+ { value: 'python-fastapi', label: '🐍 Python FastAPI', hint: 'Async · Auto docs · Pydantic v2' },
2946
+ { value: 'python-django', label: '🎸 Python Django 5 REST', hint: '🆕 Batteries included · DRF' },
2947
+ { value: 'go-fiber', label: '🩵 Go + Fiber v2', hint: '🆕 Requires Go ≥1.21 · Ultra-fast' },
2948
+ { value: 'go-gin', label: '🍸 Go + Gin', hint: '🆕 Requires Go ≥1.21 · Popular' },
2949
+ { value: 'rust-actix', label: '🦀 Rust + Actix-Web 4', hint: '🆕 Requires Cargo · Maximum perf' },
2950
+ { value: 'rust-axum', label: '⚙️ Rust + Axum', hint: '🆕 Requires Cargo · Tokio async' },
2951
+ { value: 'deno-oak', label: '🦕 Deno + Oak', hint: '🆕 Requires Deno ≥1.40' },
2952
+ { value: 'php-laravel', label: '🔴 PHP Laravel 11', hint: '🆕 Requires PHP 8.2 + Composer' },
2953
+ { value: 'elixir-phoenix', label: '🔥 Elixir Phoenix', hint: '🆕 Requires Elixir ≥1.15' },
1515
2954
  ],
1516
2955
  });
1517
2956
  if (p.isCancel(stack)) { p.cancel('Cancelled.'); process.exit(0); }
@@ -1546,11 +2985,11 @@ async function main() {
1546
2985
  ormType = await p.select({
1547
2986
  message: 'ORM / Database layer:',
1548
2987
  options: [
1549
- { value: 'prisma', label: '🔺 Prisma 5', hint: 'PostgreSQL/MySQL/SQLite/MongoDB' },
1550
- { value: 'drizzle', label: '💧 DrizzleORM', hint: '🆕 Type-safe SQL · Lightweight' },
1551
- { value: 'typeorm', label: '🗄️ TypeORM', hint: 'Decorators · Postgres/MySQL' },
1552
- { value: 'mongoose', label: '🍃 Mongoose 8', hint: 'MongoDB · Document model' },
1553
- { value: 'sequelize', label: '📊 Sequelize 6', hint: 'Classic ORM · Multi-DB' },
2988
+ { value: 'prisma', label: '🔺 Prisma 5', hint: 'PostgreSQL/MySQL/SQLite/MongoDB' },
2989
+ { value: 'drizzle', label: '💧 DrizzleORM', hint: '🆕 Type-safe SQL · Lightweight' },
2990
+ { value: 'typeorm', label: '🗄️ TypeORM', hint: 'Decorators · Postgres/MySQL' },
2991
+ { value: 'mongoose', label: '🍃 Mongoose 8', hint: 'MongoDB · Document model' },
2992
+ { value: 'sequelize', label: '📊 Sequelize 6', hint: 'Classic ORM · Multi-DB' },
1554
2993
  ],
1555
2994
  });
1556
2995
  if (p.isCancel(ormType)) { p.cancel('Cancelled.'); process.exit(0); }
@@ -1558,11 +2997,11 @@ async function main() {
1558
2997
  dbType = await p.select({
1559
2998
  message: 'Database:',
1560
2999
  options: [
1561
- { value: 'postgres', label: '🐘 PostgreSQL 16' },
1562
- { value: 'mysql', label: '🐬 MySQL 8' },
1563
- { value: 'sqlite', label: '📦 SQLite 3' },
1564
- { value: 'mongodb', label: '🍃 MongoDB 7' },
1565
- { value: 'redis', label: '🔴 Redis 7' },
3000
+ { value: 'postgres', label: '🐘 PostgreSQL 16' },
3001
+ { value: 'mysql', label: '🐬 MySQL 8' },
3002
+ { value: 'sqlite', label: '📦 SQLite 3' },
3003
+ { value: 'mongodb', label: '🍃 MongoDB 7' },
3004
+ { value: 'redis', label: '🔴 Redis 7' },
1566
3005
  ],
1567
3006
  });
1568
3007
  if (p.isCancel(dbType)) { p.cancel('Cancelled.'); process.exit(0); }
@@ -1576,14 +3015,14 @@ async function main() {
1576
3015
  extraFeatures = await p.multiselect({
1577
3016
  message: 'Additional features:',
1578
3017
  options: [
1579
- { value: 'docker', label: '🐳 Docker Compose v3', hint: 'Multi-service + DB container' },
1580
- { value: 'kubernetes', label: '☸️ Kubernetes Helm', hint: '🆕 Helm chart + values.yaml' },
1581
- { value: 'testing', label: '🧪 Test suite', hint: 'Jest/Vitest + supertest' },
1582
- { value: 'swagger', label: '📖 Swagger/OpenAPI UI', hint: 'Interactive docs' },
1583
- { value: 'websocket', label: '🔌 WebSocket support', hint: '🆕 Real-time events' },
1584
- { value: 'cache', label: '🚀 Redis caching', hint: '🆕 Response + query cache' },
1585
- { value: 'logging', label: '📝 Structured logging', hint: '🆕 Pino + Winston' },
1586
- { value: 'monitoring', label: '📊 Prometheus metrics', hint: '🆕 /metrics endpoint' },
3018
+ { value: 'docker', label: '🐳 Docker Compose v3', hint: 'Multi-service + DB container' },
3019
+ { value: 'kubernetes', label: '☸️ Kubernetes Helm', hint: '🆕 Helm chart + values.yaml' },
3020
+ { value: 'testing', label: '🧪 Test suite', hint: 'Jest/Vitest + supertest' },
3021
+ { value: 'swagger', label: '📖 Swagger/OpenAPI UI', hint: 'Interactive docs' },
3022
+ { value: 'websocket', label: '🔌 WebSocket support', hint: '🆕 Real-time events' },
3023
+ { value: 'cache', label: '🚀 Redis caching', hint: '🆕 Response + query cache' },
3024
+ { value: 'logging', label: '📝 Structured logging', hint: '🆕 Pino + Winston' },
3025
+ { value: 'monitoring', label: '📊 Prometheus metrics', hint: '🆕 /metrics endpoint' },
1587
3026
  ],
1588
3027
  initialValues: ['docker','testing','swagger'],
1589
3028
  });
@@ -1592,24 +3031,25 @@ async function main() {
1592
3031
  ciProvider = await p.select({
1593
3032
  message: 'CI/CD provider:',
1594
3033
  options: [
1595
- { value: 'github', label: '⚙️ GitHub Actions' },
1596
- { value: 'gitlab', label: '🦊 GitLab CI' },
3034
+ { value: 'github', label: '⚙️ GitHub Actions' },
3035
+ { value: 'gitlab', label: '🦊 GitLab CI' },
1597
3036
  { value: 'bitbucket', label: '🪣 Bitbucket Pipelines' },
1598
- { value: 'none', label: '✕ Skip CI' },
3037
+ { value: 'none', label: '✕ Skip CI' },
1599
3038
  ],
1600
3039
  });
1601
3040
  if (p.isCancel(ciProvider)) { p.cancel('Cancelled.'); process.exit(0); }
1602
3041
  }
1603
3042
 
1604
- // ── Generation plan ──────────────────────────────────────────────────────
3043
+ // ── Generation plan ───────────────────────────────────────────────────────
1605
3044
  await printSectionHeader('Generation Plan', '📋', '#BF40FF');
1606
3045
  const meta = STACK_META[String(stack)] || {};
1607
3046
  const planItems = [
1608
- { l: 'Project', v: String(projectName), c: '#00FF9F' },
1609
- { l: 'Stack', v: `${meta.icon || ''} ${stack}`, c: '#00F5FF' },
1610
- { l: 'Language', v: meta.lang || '?', c: '#BF40FF' },
1611
- { l: 'Runtime', v: meta.runtime || '?', c: '#BF40FF' },
1612
- { l: 'Mode', v: generationMode === 'pro' ? 'PRO AI ✦' : 'Ultra AST ⚡', c: generationMode === 'pro' ? '#BF40FF' : '#00F5FF' },
3047
+ { l: 'Project', v: String(projectName), c: '#00FF9F' },
3048
+ { l: 'Stack', v: `${meta.icon || ''} ${stack}`, c: '#00F5FF' },
3049
+ { l: 'Language', v: meta.lang || '?', c: '#BF40FF' },
3050
+ { l: 'Runtime', v: meta.runtime || '?', c: '#BF40FF' },
3051
+ { l: 'Mode', v: generationMode === 'pro' ? 'PRO AI ✦' : 'Full Index Engine ⚡', c: generationMode === 'pro' ? '#BF40FF' : '#00F5FF' },
3052
+ { l: 'Scan Depth', v: '30-Point Index Engine', c: '#00FF9F' },
1613
3053
  ...(isNodeStack ? [
1614
3054
  { l: 'ORM', v: ormType, c: '#00F5FF' },
1615
3055
  { l: 'Database', v: String(dbType), c: '#00F5FF' },
@@ -1618,14 +3058,14 @@ async function main() {
1618
3058
  { l: 'Extras', v: Array.isArray(extraFeatures) ? extraFeatures.join(', ') : 'none', c: '#FFBE0B' },
1619
3059
  { l: 'CI/CD', v: String(ciProvider), c: '#BF40FF' },
1620
3060
  ] : []),
1621
- { l: 'Output', v: targetDir, c: '#64748b' },
1622
- { l: 'Env', v: ENV.isWSL ? 'WSL2' : ENV.isDocker ? 'Docker' : ENV.isLinux ? `Linux (${ENV.distro.split(' ')[0]})` : process.platform, c: '#00F5FF' },
3061
+ { l: 'Output', v: targetDir, c: '#64748b' },
3062
+ { l: 'Env', v: ENV.isWSL ? 'WSL2' : ENV.isDocker ? 'Docker' : ENV.isLinux ? `Linux (${ENV.distro.split(' ')[0]})` : process.platform, c: '#00F5FF' },
1623
3063
  ];
1624
3064
 
1625
3065
  for (const item of planItems) {
1626
3066
  await sleep(45);
1627
3067
  process.stdout.write(HIDE);
1628
- const line = ` ${chalk.dim(item.l.padEnd(12))} ${rgbText(String(item.v), item.c)}`;
3068
+ const line = ` ${chalk.dim(item.l.padEnd(14))} ${rgbText(String(item.v), item.c)}`;
1629
3069
  for (let i = 0; i <= line.length; i += 4) { process.stdout.write(CLEAR_LINE + line.slice(0, i)); await sleep(3); }
1630
3070
  process.stdout.write(CLEAR_LINE + line + '\n' + SHOW);
1631
3071
  }
@@ -1662,8 +3102,8 @@ async function executeGeneration(options, globalStart, _plugins = [], attempt =
1662
3102
  await saveSnapshot(options, 'start');
1663
3103
 
1664
3104
  if (options.generationMode === 'pro') {
1665
- const { BacklistAIAgent } = await import('../src/ai-agent.js');
1666
- const { extractComponentTreeTypes } = await import('../src/analyzer.js');
3105
+ const { BacklistAIAgent } = await import('../src/ai-agent.js');
3106
+ const { extractComponentTreeTypes } = await import('../src/analyzer.js');
1667
3107
 
1668
3108
  const apiKey = await getProApiKey();
1669
3109
 
@@ -1672,7 +3112,7 @@ async function executeGeneration(options, globalStart, _plugins = [], attempt =
1672
3112
  const astResult = await AST_ENGINE.scan(options.frontendSrcDir, (pct) => {
1673
3113
  spinAST.update(`AST: ${pct}%`);
1674
3114
  });
1675
- spinAST.succeed(`AST: ${rgbText(astResult.endpoints.length + ' endpoints', '#00FF9F')} in ${astResult.duration}s · ${astResult.cacheHits} cache hits`);
3115
+ spinAST.succeed(`AST: ${rgbText(astResult.endpoints.length + ' endpoints', '#00FF9F')} · ${rgbText(astResult.models?.length + ' models', '#FFBE0B')} in ${astResult.duration}s`);
1676
3116
 
1677
3117
  let thoughtCount = 0;
1678
3118
  const spin2 = new LiveSpinner();
@@ -1700,20 +3140,25 @@ async function executeGeneration(options, globalStart, _plugins = [], attempt =
1700
3140
 
1701
3141
  if (deployData) {
1702
3142
  await fs.ensureDir(path.join(options.projectDir, '.github', 'workflows'));
1703
- if (deployData.dockerCompose) await fs.writeFile(path.join(options.projectDir, 'docker-compose.yml'), deployData.dockerCompose);
1704
- if (deployData.githubWorkflow) await fs.writeFile(path.join(options.projectDir, '.github', 'workflows', 'deploy.yml'), deployData.githubWorkflow);
1705
- if (deployData.gitlabCi) await fs.writeFile(path.join(options.projectDir, '.gitlab-ci.yml'), deployData.gitlabCi);
3143
+ if (deployData.dockerCompose) await fs.writeFile(path.join(options.projectDir, 'docker-compose.yml'), deployData.dockerCompose);
3144
+ if (deployData.githubWorkflow) await fs.writeFile(path.join(options.projectDir, '.github', 'workflows', 'deploy.yml'), deployData.githubWorkflow);
3145
+ if (deployData.gitlabCi) await fs.writeFile(path.join(options.projectDir, '.gitlab-ci.yml'), deployData.gitlabCi);
1706
3146
  }
1707
3147
 
1708
3148
  await printAnimatedDashboard([
1709
3149
  { label: 'Security Profile', score: finalBlocks.aiSecurityConfig?.length > 20 ? 98 : 75, icon: '🛡️ ' },
1710
3150
  { label: 'Hexagonal Compliance', score: finalBlocks.aiDbRelations?.length > 20 ? 99 : 80, icon: '🏛️ ' },
1711
- { label: 'Test Coverage', score: 87, icon: '🧪' },
1712
- { label: 'Dependency Health', score: 94, icon: '📦' },
1713
- { label: 'AST Accuracy', score: 96, icon: '⚡' },
3151
+ { label: 'Test Coverage', score: 87, icon: '🧪' },
3152
+ { label: 'Dependency Health', score: 94, icon: '📦' },
3153
+ { label: 'AST Accuracy', score: 96, icon: '⚡' },
1714
3154
  ]);
1715
3155
 
1716
- await printAnimatedStats('pro', startTime, { endpointCount: astResult.endpoints.length, filesScanned: astResult.files, quality: astResult.quality });
3156
+ await printAnimatedStats('pro', startTime, {
3157
+ endpointCount: astResult.endpoints.length,
3158
+ filesScanned : astResult.files,
3159
+ modelCount : astResult.models?.length || 0,
3160
+ quality : astResult.quality,
3161
+ });
1717
3162
 
1718
3163
  } else {
1719
3164
  await runUltraPipeline(options);
@@ -1744,7 +3189,6 @@ async function executeGeneration(options, globalStart, _plugins = [], attempt =
1744
3189
  const cleanStack = (error.stack ?? '').split('\n').filter(l => !l.includes('node_modules')).slice(0, 4).join('\n');
1745
3190
  if (cleanStack) console.log(chalk.gray(cleanStack));
1746
3191
 
1747
- // Save snapshot for recovery
1748
3192
  await saveSnapshot(options, `error-attempt-${attempt}`);
1749
3193
 
1750
3194
  if (attempt < MAX_RETRIES) {
@@ -1772,7 +3216,7 @@ async function executeGeneration(options, globalStart, _plugins = [], attempt =
1772
3216
  }
1773
3217
  }
1774
3218
 
1775
- // ── Graceful shutdown ───────────────────────────────────────────────────────
3219
+ // ── Graceful shutdown ────────────────────────────────────────────────────────
1776
3220
  async function gracefulShutdown(signal) {
1777
3221
  process.stdout.write(SHOW);
1778
3222
  console.log('');
@@ -1795,10 +3239,10 @@ process.on('uncaughtException', err => {
1795
3239
  process.exit(1);
1796
3240
  });
1797
3241
 
1798
- // ── Launch ──────────────────────────────────────────────────────────────────
3242
+ // ── Launch ───────────────────────────────────────────────────────────────────
1799
3243
  main().catch(err => {
1800
3244
  process.stdout.write(SHOW);
1801
3245
  console.error(rgbText(`\n Fatal: ${err.message || err}`, '#FF006E'));
1802
3246
  if (err.stack) console.error(chalk.gray(err.stack.split('\n').slice(1, 4).join('\n')));
1803
3247
  process.exit(1);
1804
- });
3248
+ });