@codexa/cli 8.6.14 → 9.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -359,4 +359,55 @@ export function queryGraph(options: {
359
359
  console.log(` knowledge graph --file <path> Relacoes de um arquivo`);
360
360
  console.log(` knowledge graph --decision <id> Arquivos afetados por decisao`);
361
361
  console.log(` knowledge graph --contradictions Detectar contradicoes\n`);
362
+ }
363
+
364
+ // v9.0: Resolver/reconhecer knowledge critico (para desbloquear task start)
365
+ export function resolveKnowledge(ids: string, resolution?: string): void {
366
+ initSchema();
367
+ const db = getDb();
368
+ const now = new Date().toISOString();
369
+
370
+ const spec = getActiveSpec();
371
+ if (!spec) {
372
+ console.error("\nNenhuma feature ativa.\n");
373
+ process.exit(1);
374
+ }
375
+
376
+ const knowledgeIds = ids.split(",").map(s => parseInt(s.trim()));
377
+
378
+ for (const kid of knowledgeIds) {
379
+ const knowledge = db.query("SELECT * FROM knowledge WHERE id = ?").get(kid) as any;
380
+
381
+ if (!knowledge) {
382
+ console.error(`Knowledge #${kid} nao encontrado.`);
383
+ continue;
384
+ }
385
+
386
+ const acknowledged = knowledge.acknowledged_by
387
+ ? (JSON.parse(knowledge.acknowledged_by) as number[])
388
+ : [];
389
+
390
+ // Usar -1 como marker de "resolvido pelo orquestrador"
391
+ if (!acknowledged.includes(-1)) {
392
+ acknowledged.push(-1);
393
+ }
394
+
395
+ db.run(
396
+ "UPDATE knowledge SET acknowledged_by = ? WHERE id = ?",
397
+ [JSON.stringify(acknowledged), kid]
398
+ );
399
+
400
+ console.log(`Knowledge #${kid} resolvido.`);
401
+ }
402
+
403
+ if (resolution) {
404
+ db.run(
405
+ `INSERT INTO knowledge (spec_id, task_origin, category, content, severity, broadcast_to, created_at)
406
+ VALUES (?, 0, 'decision', ?, 'info', 'all', ?)`,
407
+ [spec.id, `Resolucao de blockers: ${resolution}`, now]
408
+ );
409
+ console.log(`Resolucao registrada como knowledge.`);
410
+ }
411
+
412
+ console.log();
362
413
  }
@@ -31,6 +31,10 @@ interface ExtractedPattern {
31
31
  commonImports: string[];
32
32
  commonExports: string[];
33
33
  conventions: string[];
34
+ // v9.0
35
+ commonHooks: string[];
36
+ commonReactPatterns: string[];
37
+ hasTypes: boolean;
34
38
  };
35
39
  template: string;
36
40
  examples: Array<{ path: string; relevance: number }>;
@@ -68,6 +72,23 @@ const PATTERN_QUERIES: PatternQuery[] = [
68
72
  // Testing
69
73
  { scope: "testing", category: "test", query: "test file with describe and it", appliesTo: "**/*.test.ts" },
70
74
  { scope: "testing", category: "test", query: "vitest test with expect", appliesTo: "**/*.spec.ts" },
75
+
76
+ // v9.0: Data Fetching
77
+ { scope: "frontend", category: "data-fetching", query: "data fetching with loading and error states", appliesTo: "**/*.{tsx,ts}" },
78
+ { scope: "backend", category: "data-fetching", query: "server action that fetches or mutates data", appliesTo: "actions/**/*.ts" },
79
+
80
+ // v9.0: State Management
81
+ { scope: "frontend", category: "state-management", query: "shared state store context provider or global state", appliesTo: "**/*.{ts,tsx}" },
82
+
83
+ // v9.0: Error Handling
84
+ { scope: "frontend", category: "error-handling", query: "error handling with try catch error boundary or validation", appliesTo: "**/*.{ts,tsx}" },
85
+ { scope: "backend", category: "error-handling", query: "API error handling with status codes and error responses", appliesTo: "app/api/**/*.ts" },
86
+
87
+ // v9.0: Composition
88
+ { scope: "frontend", category: "composition", query: "component composition with children slots or render props", appliesTo: "components/**/*.tsx" },
89
+
90
+ // v9.0: API Integration
91
+ { scope: "backend", category: "api-integration", query: "external API integration with fetch axios or SDK", appliesTo: "**/*.ts" },
71
92
  ];
72
93
 
73
94
  // ═══════════════════════════════════════════════════════════════
@@ -122,6 +143,11 @@ interface FileAnalysis {
122
143
  hasNamedExports: boolean;
123
144
  conventions: string[];
124
145
  size: number;
146
+ // v9.0: Metadata complementar ao grepai
147
+ directives: string[];
148
+ hooksUsed: string[];
149
+ reactPatterns: string[];
150
+ hasTypeExports: boolean;
125
151
  }
126
152
 
127
153
  function analyzeFile(filePath: string): FileAnalysis | null {
@@ -134,19 +160,34 @@ function analyzeFile(filePath: string): FileAnalysis | null {
134
160
  const imports: string[] = [];
135
161
  const exports: string[] = [];
136
162
  const conventions: string[] = [];
163
+ const hooksUsed: string[] = [];
164
+ const reactPatterns: string[] = [];
165
+ const directives: string[] = [];
137
166
 
138
167
  let hasDefaultExport = false;
139
168
  let hasNamedExports = false;
169
+ let hasTypeExports = false;
140
170
 
141
171
  for (const line of lines) {
142
172
  const trimmed = line.trim();
143
173
 
144
- // Extrair imports
174
+ // Extrair imports (modulo)
145
175
  const importMatch = trimmed.match(/^import\s+.*\s+from\s+['"]([^'"]+)['"]/);
146
176
  if (importMatch) {
147
177
  imports.push(importMatch[1]);
148
178
  }
149
179
 
180
+ // Extrair hooks dos imports: import { useX, useY } from "..."
181
+ const hooksInImport = trimmed.match(/^import\s+\{([^}]+)\}\s+from/);
182
+ if (hooksInImport) {
183
+ const specifiers = hooksInImport[1].split(",").map(s => s.trim());
184
+ for (const spec of specifiers) {
185
+ if (/^use[A-Z]\w*$/.test(spec)) {
186
+ hooksUsed.push(spec);
187
+ }
188
+ }
189
+ }
190
+
150
191
  // Extrair exports
151
192
  if (trimmed.startsWith("export default")) {
152
193
  hasDefaultExport = true;
@@ -157,17 +198,48 @@ function analyzeFile(filePath: string): FileAnalysis | null {
157
198
  if (exportMatch) {
158
199
  exports.push(exportMatch[1]);
159
200
  }
201
+ if (trimmed.startsWith("export type ") || trimmed.startsWith("export interface ")) {
202
+ hasTypeExports = true;
203
+ }
160
204
  }
161
205
 
162
- // Detectar convenções
163
- if (trimmed.includes("'use client'") || trimmed.includes('"use client"')) {
206
+ // Directives
207
+ if (trimmed === "'use client'" || trimmed === '"use client"') {
208
+ directives.push("use client");
164
209
  conventions.push("use client");
165
210
  }
166
- if (trimmed.includes("'use server'") || trimmed.includes('"use server"')) {
211
+ if (trimmed === "'use server'" || trimmed === '"use server"') {
212
+ directives.push("use server");
167
213
  conventions.push("use server");
168
214
  }
215
+ if (trimmed === "'use cache'" || trimmed === '"use cache"') {
216
+ directives.push("use cache");
217
+ conventions.push("use cache");
218
+ }
219
+
220
+ // Async patterns
169
221
  if (trimmed.includes("async function") || trimmed.includes("async (")) {
170
- conventions.push("async");
222
+ if (!conventions.includes("async")) conventions.push("async");
223
+ }
224
+
225
+ // React patterns (metadata complementar ao grepai)
226
+ if (trimmed.includes("forwardRef") && !reactPatterns.includes("forwardRef")) {
227
+ reactPatterns.push("forwardRef");
228
+ }
229
+ if (trimmed.includes("createContext") && !reactPatterns.includes("createContext")) {
230
+ reactPatterns.push("createContext");
231
+ }
232
+ if (trimmed.includes("ErrorBoundary") && !reactPatterns.includes("ErrorBoundary")) {
233
+ reactPatterns.push("ErrorBoundary");
234
+ }
235
+ if (trimmed.includes("Suspense") && !reactPatterns.includes("Suspense")) {
236
+ reactPatterns.push("Suspense");
237
+ }
238
+ if (trimmed.includes("memo(") && !reactPatterns.includes("memo")) {
239
+ reactPatterns.push("memo");
240
+ }
241
+ if (trimmed.includes("lazy(") && !reactPatterns.includes("lazy")) {
242
+ reactPatterns.push("lazy");
171
243
  }
172
244
  }
173
245
 
@@ -179,6 +251,10 @@ function analyzeFile(filePath: string): FileAnalysis | null {
179
251
  hasNamedExports,
180
252
  conventions: [...new Set(conventions)],
181
253
  size: content.length,
254
+ directives: [...new Set(directives)],
255
+ hooksUsed: [...new Set(hooksUsed)],
256
+ reactPatterns: [...new Set(reactPatterns)],
257
+ hasTypeExports,
182
258
  };
183
259
  } catch {
184
260
  return null;
@@ -303,23 +379,28 @@ function findCommonElements<T>(arrays: T[][]): T[] {
303
379
 
304
380
  function generateTemplate(analyses: FileAnalysis[], category: string): string {
305
381
  const commonImports = findCommonElements(analyses.map((a) => a.imports));
306
- const hasUseClient = analyses.some((a) => a.conventions.includes("use client"));
307
- const hasUseServer = analyses.some((a) => a.conventions.includes("use server"));
382
+ const commonHooks = findCommonElements(analyses.map((a) => a.hooksUsed));
383
+ const hasUseClient = analyses.some((a) => a.directives.includes("use client"));
384
+ const hasUseServer = analyses.some((a) => a.directives.includes("use server"));
385
+ const hasUseCache = analyses.some((a) => a.directives.includes("use cache"));
308
386
  const hasAsync = analyses.some((a) => a.conventions.includes("async"));
387
+ const hasForwardRef = analyses.some((a) => a.reactPatterns.includes("forwardRef"));
388
+ const hasTypes = analyses.filter((a) => a.hasTypeExports).length > analyses.length * 0.5;
309
389
 
310
390
  let template = "";
311
391
 
312
- // Diretiva de cliente/servidor
392
+ // Diretiva
313
393
  if (hasUseClient) {
314
394
  template += `"use client"\n\n`;
315
395
  } else if (hasUseServer) {
316
396
  template += `"use server"\n\n`;
397
+ } else if (hasUseCache) {
398
+ template += `"use cache"\n\n`;
317
399
  }
318
400
 
319
- // Imports comuns
401
+ // Imports comuns (max 5)
320
402
  if (commonImports.length > 0) {
321
403
  for (const imp of commonImports.slice(0, 5)) {
322
- // Max 5 imports
323
404
  if (imp.startsWith("react") || imp.startsWith("next")) {
324
405
  template += `import { /* ... */ } from "${imp}"\n`;
325
406
  } else if (imp.startsWith("@/") || imp.startsWith("~/")) {
@@ -329,37 +410,69 @@ function generateTemplate(analyses: FileAnalysis[], category: string): string {
329
410
  template += "\n";
330
411
  }
331
412
 
332
- // Estrutura baseada na categoria
413
+ // Estrutura baseada na categoria + dados reais dos arquivos
333
414
  switch (category) {
334
- case "component":
335
- template += `interface {{ComponentName}}Props {\n // Props\n}\n\n`;
336
- template += hasAsync
337
- ? `export default async function {{ComponentName}}({ /* props */ }: {{ComponentName}}Props) {\n // Implementação\n return (\n <div>\n {/* JSX */}\n </div>\n )\n}`
338
- : `export default function {{ComponentName}}({ /* props */ }: {{ComponentName}}Props) {\n // Implementação\n return (\n <div>\n {/* JSX */}\n </div>\n )\n}`;
415
+ case "component": {
416
+ const propsType = hasForwardRef
417
+ ? `interface {{ComponentName}}Props extends React.ComponentPropsWithRef<"div">`
418
+ : `interface {{ComponentName}}Props`;
419
+ template += `${propsType} {\n // Props\n}\n\n`;
420
+
421
+ if (hasForwardRef) {
422
+ template += `const {{ComponentName}} = React.forwardRef<HTMLDivElement, {{ComponentName}}Props>(\n function {{ComponentName}}({ /* props */ }, ref) {\n`;
423
+ } else if (hasAsync) {
424
+ template += `export default async function {{ComponentName}}({ /* props */ }: {{ComponentName}}Props) {\n`;
425
+ } else {
426
+ template += `export default function {{ComponentName}}({ /* props */ }: {{ComponentName}}Props) {\n`;
427
+ }
428
+
429
+ if (commonHooks.length > 0) {
430
+ for (const hook of commonHooks.slice(0, 3)) {
431
+ template += ` // ${hook}(...)\n`;
432
+ }
433
+ template += "\n";
434
+ }
435
+
436
+ template += ` return (\n <div>\n {/* JSX */}\n </div>\n )\n}`;
437
+
438
+ if (hasForwardRef) {
439
+ template += `\n)\n\n{{ComponentName}}.displayName = "{{ComponentName}}"`;
440
+ }
339
441
  break;
442
+ }
340
443
 
341
444
  case "hook":
342
- template += `export function use{{HookName}}() {\n // Estado e lógica\n \n return {\n // Valores e funções\n }\n}`;
445
+ template += `export function use{{HookName}}() {\n`;
446
+ if (commonHooks.length > 0) {
447
+ for (const hook of commonHooks.slice(0, 3)) {
448
+ template += ` // ${hook}(...)\n`;
449
+ }
450
+ template += "\n";
451
+ }
452
+ template += ` return {\n // Valores e funcoes\n }\n}`;
343
453
  break;
344
454
 
345
455
  case "route":
346
456
  template += `import { NextRequest, NextResponse } from "next/server"\n\n`;
347
- template += `export async function GET(request: NextRequest) {\n // Implementação GET\n return NextResponse.json({ /* data */ })\n}\n\n`;
348
- template += `export async function POST(request: NextRequest) {\n // Implementação POST\n return NextResponse.json({ /* data */ })\n}`;
457
+ template += `export async function GET(request: NextRequest) {\n // Implementacao GET\n return NextResponse.json({ /* data */ })\n}\n\n`;
458
+ template += `export async function POST(request: NextRequest) {\n // Implementacao POST\n return NextResponse.json({ /* data */ })\n}`;
349
459
  break;
350
460
 
351
461
  case "service":
352
- template += `export class {{ServiceName}}Service {\n constructor() {\n // Inicialização\n }\n\n async execute() {\n // Lógica do serviço\n }\n}`;
462
+ template += `export class {{ServiceName}}Service {\n constructor() {\n // Inicializacao\n }\n\n async execute() {\n // Logica do servico\n }\n}`;
353
463
  break;
354
464
 
355
465
  case "action":
356
466
  template += `"use server"\n\n`;
357
- template += `export async function {{actionName}}(formData: FormData) {\n // Validação\n \n // Lógica\n \n // Retorno\n}`;
467
+ template += `export async function {{actionName}}(formData: FormData) {\n // Validacao\n \n // Logica\n \n // Retorno\n}`;
358
468
  break;
359
469
 
360
470
  case "schema":
361
471
  template += `import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"\n\n`;
362
472
  template += `export const {{tableName}} = pgTable("{{table_name}}", {\n id: uuid("id").primaryKey().defaultRandom(),\n // Colunas\n createdAt: timestamp("created_at").defaultNow(),\n updatedAt: timestamp("updated_at").defaultNow(),\n})`;
473
+ if (hasTypes) {
474
+ template += `\n\nexport type {{TableName}} = typeof {{tableName}}.$inferSelect\nexport type New{{TableName}} = typeof {{tableName}}.$inferInsert`;
475
+ }
363
476
  break;
364
477
 
365
478
  case "test":
@@ -367,6 +480,38 @@ function generateTemplate(analyses: FileAnalysis[], category: string): string {
367
480
  template += `describe("{{TestSubject}}", () => {\n it("should do something", () => {\n // Arrange\n \n // Act\n \n // Assert\n expect(true).toBe(true)\n })\n})`;
368
481
  break;
369
482
 
483
+ case "data-fetching":
484
+ if (hasUseClient) {
485
+ template += `// Data fetching pattern (client)\n`;
486
+ if (commonHooks.some(h => h === "useQuery" || h === "useSWR")) {
487
+ template += `export function use{{Name}}() {\n // ${commonHooks.filter(h => h.startsWith("use")).join(", ")}\n}`;
488
+ } else {
489
+ template += `export function use{{Name}}() {\n // Fetch logic\n}`;
490
+ }
491
+ } else {
492
+ template += `// Data fetching pattern (server)\nexport async function {{name}}() {\n // Fetch data\n}`;
493
+ }
494
+ break;
495
+
496
+ case "state-management":
497
+ template += `// State management pattern\n`;
498
+ if (commonHooks.length > 0) {
499
+ template += `// Hooks: ${commonHooks.join(", ")}\n`;
500
+ }
501
+ break;
502
+
503
+ case "error-handling":
504
+ template += `// Error handling pattern\n`;
505
+ break;
506
+
507
+ case "composition":
508
+ template += `// Composition pattern\n`;
509
+ break;
510
+
511
+ case "api-integration":
512
+ template += `// API integration pattern\n`;
513
+ break;
514
+
370
515
  default:
371
516
  template += `// Template para ${category}\n`;
372
517
  }
@@ -402,14 +547,18 @@ function extractPatternFromFiles(
402
547
  const commonImports = findCommonElements(analyses.map((a) => a.imports));
403
548
  const commonExports = findCommonElements(analyses.map((a) => a.exports));
404
549
  const commonConventions = findCommonElements(analyses.map((a) => a.conventions));
550
+ // v9.0
551
+ const commonHooks = findCommonElements(analyses.map((a) => a.hooksUsed));
552
+ const commonReactPatterns = findCommonElements(analyses.map((a) => a.reactPatterns));
553
+ const hasTypes = analyses.filter((a) => a.hasTypeExports).length > analyses.length * 0.5;
405
554
 
406
555
  // Gerar template
407
556
  const template = generateTemplate(analyses, query.category);
408
557
 
409
- // Calcular confiança baseada em quantos arquivos seguem o pattern
558
+ // Calcular confianca baseada em quantos arquivos seguem o pattern
410
559
  const confidence = Math.min(analyses.length / 10, 1); // Max 1.0 com 10+ arquivos
411
560
 
412
- // Gerar nome único
561
+ // Gerar nome unico
413
562
  const name = `${query.scope}-${query.category}-pattern`;
414
563
 
415
564
  return {
@@ -421,13 +570,16 @@ function extractPatternFromFiles(
421
570
  commonImports,
422
571
  commonExports,
423
572
  conventions: commonConventions,
573
+ commonHooks,
574
+ commonReactPatterns,
575
+ hasTypes,
424
576
  },
425
577
  template,
426
578
  examples: files.slice(0, 5).map((f) => ({
427
579
  path: f.path,
428
580
  relevance: f.score || 0.8,
429
581
  })),
430
- antiPatterns: [], // Será preenchido se detectarmos inconsistências
582
+ antiPatterns: [],
431
583
  confidence,
432
584
  extractedFrom: analyses.length,
433
585
  };
@@ -611,11 +763,241 @@ export function patternsAnalyze(filePath: string, json: boolean = false): void {
611
763
  for (const exp of analysis.exports) {
612
764
  console.log(` - ${exp}`);
613
765
  }
614
- console.log(`\nConvenções:`);
615
- console.log(` - Default export: ${analysis.hasDefaultExport ? "sim" : "não"}`);
616
- console.log(` - Named exports: ${analysis.hasNamedExports ? "sim" : "não"}`);
766
+ console.log(`\nConvencoes:`);
767
+ console.log(` - Default export: ${analysis.hasDefaultExport ? "sim" : "nao"}`);
768
+ console.log(` - Named exports: ${analysis.hasNamedExports ? "sim" : "nao"}`);
769
+ console.log(` - Type exports: ${analysis.hasTypeExports ? "sim" : "nao"}`);
770
+ if (analysis.directives.length > 0) {
771
+ console.log(` - Directives: ${analysis.directives.join(", ")}`);
772
+ }
773
+ if (analysis.hooksUsed.length > 0) {
774
+ console.log(` - Hooks: ${analysis.hooksUsed.join(", ")}`);
775
+ }
776
+ if (analysis.reactPatterns.length > 0) {
777
+ console.log(` - React patterns: ${analysis.reactPatterns.join(", ")}`);
778
+ }
617
779
  if (analysis.conventions.length > 0) {
618
- console.log(` - Diretivas: ${analysis.conventions.join(", ")}`);
780
+ console.log(` - Convencoes: ${analysis.conventions.join(", ")}`);
619
781
  }
620
782
  console.log(`\nTamanho: ${analysis.size} bytes\n`);
621
783
  }
784
+
785
+ // ═══════════════════════════════════════════════════════════════
786
+ // v9.0: ANTI-PATTERNS DE GATE BYPASSES
787
+ // ═══════════════════════════════════════════════════════════════
788
+
789
+ export function extractAntiPatternsFromHistory(): void {
790
+ initSchema();
791
+ const db = getDb();
792
+
793
+ const bypasses = db.query(
794
+ "SELECT * FROM gate_bypasses ORDER BY created_at DESC LIMIT 50"
795
+ ).all() as any[];
796
+
797
+ const antiPatterns: Map<string, { pattern: string; count: number; source: string }> = new Map();
798
+
799
+ for (const bypass of bypasses) {
800
+ if (!bypass.reason) continue;
801
+
802
+ const key = `${bypass.gate_name}:${bypass.reason}`;
803
+ const existing = antiPatterns.get(key);
804
+
805
+ if (existing) {
806
+ existing.count++;
807
+ } else {
808
+ const prefix = bypass.gate_name === "dry-check" ? "DRY" : "Evite";
809
+ antiPatterns.set(key, {
810
+ pattern: `${prefix}: ${bypass.reason} (gate ${bypass.gate_name})`,
811
+ count: 1,
812
+ source: bypass.gate_name,
813
+ });
814
+ }
815
+ }
816
+
817
+ const now = new Date().toISOString();
818
+ let saved = 0;
819
+
820
+ for (const [, ap] of antiPatterns) {
821
+ if (ap.count < 2) continue;
822
+
823
+ const allPatterns = db.query("SELECT * FROM implementation_patterns").all() as any[];
824
+
825
+ for (const pattern of allPatterns) {
826
+ const currentAnti: string[] = pattern.anti_patterns ? JSON.parse(pattern.anti_patterns) : [];
827
+ if (!currentAnti.includes(ap.pattern)) {
828
+ currentAnti.push(ap.pattern);
829
+ db.run(
830
+ "UPDATE implementation_patterns SET anti_patterns = ?, updated_at = ? WHERE id = ?",
831
+ [JSON.stringify(currentAnti), now, pattern.id]
832
+ );
833
+ saved++;
834
+ break;
835
+ }
836
+ }
837
+ }
838
+
839
+ console.log(`\nAnti-patterns extraidos do historico: ${antiPatterns.size}`);
840
+ console.log(`Anti-patterns recorrentes (>=2x): ${[...antiPatterns.values()].filter(a => a.count >= 2).length}`);
841
+ console.log(`Anti-patterns salvos em patterns: ${saved}`);
842
+ console.log(`\nFonte: ${bypasses.length} gate bypasses analisados\n`);
843
+ }
844
+
845
+ // ═══════════════════════════════════════════════════════════════
846
+ // v9.0: ANALISE PROFUNDA (analyze-deep)
847
+ // ═══════════════════════════════════════════════════════════════
848
+
849
+ export function patternsAnalyzeDeep(files: string[], json: boolean = false): void {
850
+ const analyses: FileAnalysis[] = [];
851
+
852
+ for (const file of files) {
853
+ const analysis = analyzeFile(file);
854
+ if (analysis) analyses.push(analysis);
855
+ }
856
+
857
+ if (analyses.length === 0) {
858
+ console.error("\nNenhum arquivo analisado.\n");
859
+ process.exit(1);
860
+ }
861
+
862
+ const summary = {
863
+ filesAnalyzed: analyses.length,
864
+ directives: aggregateItems(analyses.flatMap(a => a.directives)),
865
+ hooks: aggregateItems(analyses.flatMap(a => a.hooksUsed)),
866
+ reactPatterns: aggregateItems(analyses.flatMap(a => a.reactPatterns)),
867
+ conventions: aggregateItems(analyses.flatMap(a => a.conventions)),
868
+ topImports: aggregateItems(analyses.flatMap(a => a.imports)),
869
+ hasTypeExports: analyses.filter(a => a.hasTypeExports).length,
870
+ };
871
+
872
+ if (json) {
873
+ console.log(JSON.stringify(summary, null, 2));
874
+ return;
875
+ }
876
+
877
+ console.log(`\nAnalise Profunda (${analyses.length} arquivos)`);
878
+ console.log("=".repeat(50));
879
+ printAggregation("Directives", summary.directives);
880
+ printAggregation("Hooks Usados", summary.hooks);
881
+ printAggregation("React Patterns", summary.reactPatterns);
882
+ printAggregation("Convencoes", summary.conventions);
883
+ printAggregation("Top Imports", Object.fromEntries(
884
+ Object.entries(summary.topImports).sort(([, a], [, b]) => b - a).slice(0, 10)
885
+ ));
886
+ console.log(`\nArquivos com type exports: ${summary.hasTypeExports}/${analyses.length}`);
887
+
888
+ if (isGrepaiAvailable() && analyses.length > 0) {
889
+ const topHooks = Object.keys(summary.hooks).slice(0, 3);
890
+ if (topHooks.length > 0) {
891
+ console.log(`\nBuscando arquivos similares via grepai...`);
892
+ const query = `files using ${topHooks.join(" and ")}`;
893
+ const similar = searchWithGrepai(query, 5);
894
+ if (similar.length > 0) {
895
+ console.log(`Arquivos similares encontrados:`);
896
+ for (const s of similar) {
897
+ console.log(` - ${s.path} (score: ${(s.score || 0).toFixed(2)})`);
898
+ }
899
+ }
900
+ }
901
+ }
902
+
903
+ console.log();
904
+ }
905
+
906
+ function aggregateItems(items: string[]): Record<string, number> {
907
+ const counts: Record<string, number> = {};
908
+ for (const item of items) {
909
+ counts[item] = (counts[item] || 0) + 1;
910
+ }
911
+ return counts;
912
+ }
913
+
914
+ function printAggregation(title: string, data: Record<string, number>): void {
915
+ const entries = Object.entries(data);
916
+ if (entries.length === 0) return;
917
+ console.log(`\n${title}:`);
918
+ for (const [name, count] of entries.sort(([, a], [, b]) => b - a)) {
919
+ console.log(` - ${name}: ${count}x`);
920
+ }
921
+ }
922
+
923
+ // ═══════════════════════════════════════════════════════════════
924
+ // v9.0: ATUALIZACAO INCREMENTAL DE PATTERNS
925
+ // ═══════════════════════════════════════════════════════════════
926
+
927
+ export function updatePatternsIncremental(files: string[], taskNumber: number): void {
928
+ initSchema();
929
+ const db = getDb();
930
+ const now = new Date().toISOString();
931
+
932
+ const allPatterns = db.query("SELECT * FROM implementation_patterns").all() as any[];
933
+ if (allPatterns.length === 0) return;
934
+
935
+ for (const file of files) {
936
+ const analysis = analyzeFile(file);
937
+ if (!analysis) continue;
938
+
939
+ for (const pattern of allPatterns) {
940
+ if (!fileMatchesGlob(file, pattern.applies_to)) continue;
941
+
942
+ const examples: Array<{ path: string; relevance: number }> = JSON.parse(pattern.examples || "[]");
943
+ if (!examples.some((e: { path: string }) => e.path === file)) {
944
+ examples.push({ path: file, relevance: 0.7 });
945
+ if (examples.length > 10) examples.shift();
946
+ }
947
+
948
+ const structure = JSON.parse(pattern.structure);
949
+ let structureChanged = false;
950
+
951
+ if (structure.commonHooks && analysis.hooksUsed.length > 0) {
952
+ for (const hook of analysis.hooksUsed) {
953
+ if (!structure.commonHooks.includes(hook)) {
954
+ structure.commonHooks.push(hook);
955
+ structureChanged = true;
956
+ }
957
+ }
958
+ }
959
+
960
+ if (structure.commonReactPatterns && analysis.reactPatterns.length > 0) {
961
+ for (const rp of analysis.reactPatterns) {
962
+ if (!structure.commonReactPatterns.includes(rp)) {
963
+ structure.commonReactPatterns.push(rp);
964
+ structureChanged = true;
965
+ }
966
+ }
967
+ }
968
+
969
+ const newExtractedFrom = (pattern.extracted_from || 0) + 1;
970
+ const newConfidence = Math.min(newExtractedFrom / 10, 1);
971
+
972
+ db.run(
973
+ `UPDATE implementation_patterns
974
+ SET examples = ?, extracted_from = ?, confidence = ?,
975
+ structure = ?, updated_at = ?
976
+ WHERE id = ?`,
977
+ [
978
+ JSON.stringify(examples),
979
+ newExtractedFrom,
980
+ newConfidence,
981
+ structureChanged ? JSON.stringify(structure) : pattern.structure,
982
+ now,
983
+ pattern.id,
984
+ ]
985
+ );
986
+ break;
987
+ }
988
+ }
989
+ }
990
+
991
+ function fileMatchesGlob(filePath: string, glob: string): boolean {
992
+ const normalized = filePath.replace(/\\/g, "/");
993
+ const regexStr = glob
994
+ .replace(/\./g, "\\.")
995
+ .replace(/\*\*/g, "<<DOUBLESTAR>>")
996
+ .replace(/\*/g, "[^/]*")
997
+ .replace(/<<DOUBLESTAR>>/g, ".*");
998
+ try {
999
+ return new RegExp(regexStr).test(normalized);
1000
+ } catch {
1001
+ return false;
1002
+ }
1003
+ }