@codexa/cli 9.0.7 → 9.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,12 @@
1
1
  /**
2
- * Codexa Workflow v8.0 - Extração Automática de Patterns via Grepai
2
+ * Codexa Workflow - Patterns & Utilities
3
3
  *
4
- * Usa busca semântica (grepai search) para encontrar arquivos por intenção
5
- * e extrair patterns de código automaticamente.
4
+ * Grepai helpers (busca semântica), análise de arquivos,
5
+ * extração de utilities (DRY enforcement), e atualização incremental de patterns.
6
6
  */
7
7
 
8
- import { execSync, spawnSync } from "child_process";
8
+ import { spawnSync } from "child_process";
9
9
  import { readFileSync, existsSync } from "fs";
10
- import { extname, basename, dirname } from "path";
11
- import { CodexaError, ValidationError } from "../errors";
12
10
  import { getDb } from "../db/connection";
13
11
  import { initSchema } from "../db/schema";
14
12
 
@@ -16,87 +14,17 @@ import { initSchema } from "../db/schema";
16
14
  // TIPOS
17
15
  // ═══════════════════════════════════════════════════════════════
18
16
 
19
- interface PatternQuery {
20
- scope: string;
21
- category: string;
22
- query: string;
23
- appliesTo: string; // glob pattern
24
- }
25
-
26
- interface ExtractedPattern {
27
- category: string;
28
- name: string;
29
- scope: string;
30
- appliesTo: string;
31
- structure: {
32
- commonImports: string[];
33
- commonExports: string[];
34
- conventions: string[];
35
- // v9.0
36
- commonHooks: string[];
37
- commonReactPatterns: string[];
38
- hasTypes: boolean;
39
- };
40
- template: string;
41
- examples: Array<{ path: string; relevance: number }>;
42
- antiPatterns: string[];
43
- confidence: number;
44
- extractedFrom: number;
45
- }
46
-
47
- interface GrepaiResult {
17
+ export interface GrepaiResult {
48
18
  path: string;
49
19
  score: number;
50
20
  content?: string;
51
21
  }
52
22
 
53
- // ═══════════════════════════════════════════════════════════════
54
- // QUERIES SEMÂNTICAS POR ESCOPO
55
- // ═══════════════════════════════════════════════════════════════
56
-
57
- const PATTERN_QUERIES: PatternQuery[] = [
58
- // Frontend
59
- { scope: "frontend", category: "component", query: "React page component with layout", appliesTo: "app/**/page.tsx" },
60
- { scope: "frontend", category: "component", query: "React client component with state", appliesTo: "components/**/*.tsx" },
61
- { scope: "frontend", category: "hook", query: "custom React hook with useState", appliesTo: "hooks/**/*.ts" },
62
- { scope: "frontend", category: "component", query: "React server component with async data", appliesTo: "app/**/*.tsx" },
63
-
64
- // Backend
65
- { scope: "backend", category: "route", query: "API route handler with request response", appliesTo: "app/api/**/*.ts" },
66
- { scope: "backend", category: "service", query: "service class with methods", appliesTo: "services/**/*.ts" },
67
- { scope: "backend", category: "action", query: "server action with use server", appliesTo: "actions/**/*.ts" },
68
-
69
- // Database
70
- { scope: "database", category: "schema", query: "database schema definition", appliesTo: "db/**/*.ts" },
71
- { scope: "database", category: "schema", query: "drizzle table schema", appliesTo: "schema/**/*.ts" },
72
-
73
- // Testing
74
- { scope: "testing", category: "test", query: "test file with describe and it", appliesTo: "**/*.test.ts" },
75
- { scope: "testing", category: "test", query: "vitest test with expect", appliesTo: "**/*.spec.ts" },
76
-
77
- // v9.0: Data Fetching
78
- { scope: "frontend", category: "data-fetching", query: "data fetching with loading and error states", appliesTo: "**/*.{tsx,ts}" },
79
- { scope: "backend", category: "data-fetching", query: "server action that fetches or mutates data", appliesTo: "actions/**/*.ts" },
80
-
81
- // v9.0: State Management
82
- { scope: "frontend", category: "state-management", query: "shared state store context provider or global state", appliesTo: "**/*.{ts,tsx}" },
83
-
84
- // v9.0: Error Handling
85
- { scope: "frontend", category: "error-handling", query: "error handling with try catch error boundary or validation", appliesTo: "**/*.{ts,tsx}" },
86
- { scope: "backend", category: "error-handling", query: "API error handling with status codes and error responses", appliesTo: "app/api/**/*.ts" },
87
-
88
- // v9.0: Composition
89
- { scope: "frontend", category: "composition", query: "component composition with children slots or render props", appliesTo: "components/**/*.tsx" },
90
-
91
- // v9.0: API Integration
92
- { scope: "backend", category: "api-integration", query: "external API integration with fetch axios or SDK", appliesTo: "**/*.ts" },
93
- ];
94
-
95
23
  // ═══════════════════════════════════════════════════════════════
96
24
  // VERIFICAR GREPAI
97
25
  // ═══════════════════════════════════════════════════════════════
98
26
 
99
- function isGrepaiAvailable(): boolean {
27
+ export function isGrepaiAvailable(): boolean {
100
28
  try {
101
29
  const result = spawnSync("grepai", ["help"], { encoding: "utf-8", timeout: 5000 });
102
30
  return !result.error;
@@ -109,7 +37,7 @@ function isGrepaiAvailable(): boolean {
109
37
  // BUSCA SEMÂNTICA
110
38
  // ═══════════════════════════════════════════════════════════════
111
39
 
112
- function searchWithGrepai(query: string, topK: number = 10): GrepaiResult[] {
40
+ export function searchWithGrepai(query: string, topK: number = 10): GrepaiResult[] {
113
41
  try {
114
42
  const result = spawnSync(
115
43
  "grepai",
@@ -144,7 +72,6 @@ interface FileAnalysis {
144
72
  hasNamedExports: boolean;
145
73
  conventions: string[];
146
74
  size: number;
147
- // v9.0: Metadata complementar ao grepai
148
75
  directives: string[];
149
76
  hooksUsed: string[];
150
77
  reactPatterns: string[];
@@ -350,571 +277,8 @@ export function inferScopeFromPath(filePath: string): string {
350
277
  return "shared";
351
278
  }
352
279
 
353
- function findCommonElements<T>(arrays: T[][]): T[] {
354
- if (arrays.length === 0) return [];
355
- if (arrays.length === 1) return arrays[0];
356
-
357
- // Elementos que aparecem em pelo menos 50% dos arquivos
358
- const threshold = Math.ceil(arrays.length * 0.5);
359
- const counts = new Map<string, number>();
360
-
361
- for (const arr of arrays) {
362
- const seen = new Set<string>();
363
- for (const item of arr) {
364
- const key = String(item);
365
- if (!seen.has(key)) {
366
- seen.add(key);
367
- counts.set(key, (counts.get(key) || 0) + 1);
368
- }
369
- }
370
- }
371
-
372
- return [...counts.entries()]
373
- .filter(([_, count]) => count >= threshold)
374
- .map(([item]) => item as unknown as T);
375
- }
376
-
377
- // ═══════════════════════════════════════════════════════════════
378
- // GERAÇÃO DE TEMPLATE
379
- // ═══════════════════════════════════════════════════════════════
380
-
381
- function generateTemplate(analyses: FileAnalysis[], category: string): string {
382
- const commonImports = findCommonElements(analyses.map((a) => a.imports));
383
- const commonHooks = findCommonElements(analyses.map((a) => a.hooksUsed));
384
- const hasUseClient = analyses.some((a) => a.directives.includes("use client"));
385
- const hasUseServer = analyses.some((a) => a.directives.includes("use server"));
386
- const hasUseCache = analyses.some((a) => a.directives.includes("use cache"));
387
- const hasAsync = analyses.some((a) => a.conventions.includes("async"));
388
- const hasForwardRef = analyses.some((a) => a.reactPatterns.includes("forwardRef"));
389
- const hasTypes = analyses.filter((a) => a.hasTypeExports).length > analyses.length * 0.5;
390
-
391
- let template = "";
392
-
393
- // Diretiva
394
- if (hasUseClient) {
395
- template += `"use client"\n\n`;
396
- } else if (hasUseServer) {
397
- template += `"use server"\n\n`;
398
- } else if (hasUseCache) {
399
- template += `"use cache"\n\n`;
400
- }
401
-
402
- // Imports comuns (max 5)
403
- if (commonImports.length > 0) {
404
- for (const imp of commonImports.slice(0, 5)) {
405
- if (imp.startsWith("react") || imp.startsWith("next")) {
406
- template += `import { /* ... */ } from "${imp}"\n`;
407
- } else if (imp.startsWith("@/") || imp.startsWith("~/")) {
408
- template += `import { /* ... */ } from "${imp}"\n`;
409
- }
410
- }
411
- template += "\n";
412
- }
413
-
414
- // Estrutura baseada na categoria + dados reais dos arquivos
415
- switch (category) {
416
- case "component": {
417
- const propsType = hasForwardRef
418
- ? `interface {{ComponentName}}Props extends React.ComponentPropsWithRef<"div">`
419
- : `interface {{ComponentName}}Props`;
420
- template += `${propsType} {\n // Props\n}\n\n`;
421
-
422
- if (hasForwardRef) {
423
- template += `const {{ComponentName}} = React.forwardRef<HTMLDivElement, {{ComponentName}}Props>(\n function {{ComponentName}}({ /* props */ }, ref) {\n`;
424
- } else if (hasAsync) {
425
- template += `export default async function {{ComponentName}}({ /* props */ }: {{ComponentName}}Props) {\n`;
426
- } else {
427
- template += `export default function {{ComponentName}}({ /* props */ }: {{ComponentName}}Props) {\n`;
428
- }
429
-
430
- if (commonHooks.length > 0) {
431
- for (const hook of commonHooks.slice(0, 3)) {
432
- template += ` // ${hook}(...)\n`;
433
- }
434
- template += "\n";
435
- }
436
-
437
- template += ` return (\n <div>\n {/* JSX */}\n </div>\n )\n}`;
438
-
439
- if (hasForwardRef) {
440
- template += `\n)\n\n{{ComponentName}}.displayName = "{{ComponentName}}"`;
441
- }
442
- break;
443
- }
444
-
445
- case "hook":
446
- template += `export function use{{HookName}}() {\n`;
447
- if (commonHooks.length > 0) {
448
- for (const hook of commonHooks.slice(0, 3)) {
449
- template += ` // ${hook}(...)\n`;
450
- }
451
- template += "\n";
452
- }
453
- template += ` return {\n // Valores e funcoes\n }\n}`;
454
- break;
455
-
456
- case "route":
457
- template += `import { NextRequest, NextResponse } from "next/server"\n\n`;
458
- template += `export async function GET(request: NextRequest) {\n // Implementacao GET\n return NextResponse.json({ /* data */ })\n}\n\n`;
459
- template += `export async function POST(request: NextRequest) {\n // Implementacao POST\n return NextResponse.json({ /* data */ })\n}`;
460
- break;
461
-
462
- case "service":
463
- template += `export class {{ServiceName}}Service {\n constructor() {\n // Inicializacao\n }\n\n async execute() {\n // Logica do servico\n }\n}`;
464
- break;
465
-
466
- case "action":
467
- template += `"use server"\n\n`;
468
- template += `export async function {{actionName}}(formData: FormData) {\n // Validacao\n \n // Logica\n \n // Retorno\n}`;
469
- break;
470
-
471
- case "schema":
472
- template += `import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"\n\n`;
473
- 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})`;
474
- if (hasTypes) {
475
- template += `\n\nexport type {{TableName}} = typeof {{tableName}}.$inferSelect\nexport type New{{TableName}} = typeof {{tableName}}.$inferInsert`;
476
- }
477
- break;
478
-
479
- case "test":
480
- template += `import { describe, it, expect } from "vitest"\n\n`;
481
- template += `describe("{{TestSubject}}", () => {\n it("should do something", () => {\n // Arrange\n \n // Act\n \n // Assert\n expect(true).toBe(true)\n })\n})`;
482
- break;
483
-
484
- case "data-fetching":
485
- if (hasUseClient) {
486
- template += `// Data fetching pattern (client)\n`;
487
- if (commonHooks.some(h => h === "useQuery" || h === "useSWR")) {
488
- template += `export function use{{Name}}() {\n // ${commonHooks.filter(h => h.startsWith("use")).join(", ")}\n}`;
489
- } else {
490
- template += `export function use{{Name}}() {\n // Fetch logic\n}`;
491
- }
492
- } else {
493
- template += `// Data fetching pattern (server)\nexport async function {{name}}() {\n // Fetch data\n}`;
494
- }
495
- break;
496
-
497
- case "state-management":
498
- template += `// State management pattern\n`;
499
- if (commonHooks.length > 0) {
500
- template += `// Hooks: ${commonHooks.join(", ")}\n`;
501
- }
502
- break;
503
-
504
- case "error-handling":
505
- template += `// Error handling pattern\n`;
506
- break;
507
-
508
- case "composition":
509
- template += `// Composition pattern\n`;
510
- break;
511
-
512
- case "api-integration":
513
- template += `// API integration pattern\n`;
514
- break;
515
-
516
- default:
517
- template += `// Template para ${category}\n`;
518
- }
519
-
520
- return template;
521
- }
522
-
523
- // ═══════════════════════════════════════════════════════════════
524
- // EXTRAÇÃO DE PATTERNS
525
- // ═══════════════════════════════════════════════════════════════
526
-
527
- function extractPatternFromFiles(
528
- query: PatternQuery,
529
- files: GrepaiResult[]
530
- ): ExtractedPattern | null {
531
- if (files.length < 2) {
532
- // Precisa de pelo menos 2 arquivos para identificar pattern
533
- return null;
534
- }
535
-
536
- // Analisar cada arquivo
537
- const analyses: FileAnalysis[] = [];
538
- for (const file of files) {
539
- const analysis = analyzeFile(file.path);
540
- if (analysis) {
541
- analyses.push(analysis);
542
- }
543
- }
544
-
545
- if (analyses.length < 2) return null;
546
-
547
- // Encontrar elementos comuns
548
- const commonImports = findCommonElements(analyses.map((a) => a.imports));
549
- const commonExports = findCommonElements(analyses.map((a) => a.exports));
550
- const commonConventions = findCommonElements(analyses.map((a) => a.conventions));
551
- // v9.0
552
- const commonHooks = findCommonElements(analyses.map((a) => a.hooksUsed));
553
- const commonReactPatterns = findCommonElements(analyses.map((a) => a.reactPatterns));
554
- const hasTypes = analyses.filter((a) => a.hasTypeExports).length > analyses.length * 0.5;
555
-
556
- // Gerar template
557
- const template = generateTemplate(analyses, query.category);
558
-
559
- // Calcular confianca baseada em quantos arquivos seguem o pattern
560
- const confidence = Math.min(analyses.length / 10, 1); // Max 1.0 com 10+ arquivos
561
-
562
- // Gerar nome unico
563
- const name = `${query.scope}-${query.category}-pattern`;
564
-
565
- return {
566
- category: query.category,
567
- name: name,
568
- scope: query.scope,
569
- appliesTo: query.appliesTo,
570
- structure: {
571
- commonImports,
572
- commonExports,
573
- conventions: commonConventions,
574
- commonHooks,
575
- commonReactPatterns,
576
- hasTypes,
577
- },
578
- template,
579
- examples: files.slice(0, 5).map((f) => ({
580
- path: f.path,
581
- relevance: f.score || 0.8,
582
- })),
583
- antiPatterns: [],
584
- confidence,
585
- extractedFrom: analyses.length,
586
- };
587
- }
588
-
589
- // ═══════════════════════════════════════════════════════════════
590
- // COMANDOS PÚBLICOS
591
- // ═══════════════════════════════════════════════════════════════
592
-
593
- export interface ExtractOptions {
594
- scope?: string;
595
- all?: boolean;
596
- json?: boolean;
597
- dryRun?: boolean;
598
- }
599
-
600
- export function patternsExtract(options: ExtractOptions): void {
601
- initSchema();
602
-
603
- // Verificar grepai
604
- if (!isGrepaiAvailable()) {
605
- throw new CodexaError("grepai nao encontrado.\nInstale com: go install github.com/your-org/grepai@latest\nOu configure no PATH.");
606
- }
607
-
608
- const db = getDb();
609
- const now = new Date().toISOString();
610
-
611
- // Filtrar queries por escopo
612
- let queries = PATTERN_QUERIES;
613
- if (options.scope && !options.all) {
614
- queries = queries.filter((q) => q.scope === options.scope);
615
- }
616
-
617
- if (queries.length === 0) {
618
- throw new ValidationError("Escopo '" + options.scope + "' nao reconhecido.\nEscopos validos: frontend, backend, database, testing");
619
- }
620
-
621
- console.log(`\n🔍 Extraindo patterns via grepai...`);
622
- console.log(` Queries: ${queries.length}`);
623
- console.log(` Escopo: ${options.scope || "todos"}\n`);
624
-
625
- const extracted: ExtractedPattern[] = [];
626
-
627
- for (const query of queries) {
628
- console.log(` [${query.scope}/${query.category}] "${query.query}"`);
629
-
630
- // Buscar arquivos via grepai
631
- const results = searchWithGrepai(query.query, 15);
632
-
633
- if (results.length === 0) {
634
- console.log(` → Nenhum arquivo encontrado`);
635
- continue;
636
- }
637
-
638
- console.log(` → ${results.length} arquivos encontrados`);
639
-
640
- // Extrair pattern
641
- const pattern = extractPatternFromFiles(query, results);
642
-
643
- if (pattern) {
644
- extracted.push(pattern);
645
- console.log(` ✓ Pattern extraido (confiança: ${(pattern.confidence * 100).toFixed(0)}%)`);
646
- } else {
647
- console.log(` → Arquivos insuficientes para pattern`);
648
- }
649
- }
650
-
651
- console.log(`\n${"─".repeat(50)}`);
652
- console.log(`Patterns extraidos: ${extracted.length}`);
653
-
654
- if (extracted.length === 0) {
655
- console.log("Nenhum pattern encontrado. Execute 'grepai index' primeiro.\n");
656
- return;
657
- }
658
-
659
- // Output JSON se solicitado
660
- if (options.json) {
661
- console.log(JSON.stringify({ patterns: extracted }, null, 2));
662
- return;
663
- }
664
-
665
- // Dry run - apenas mostrar
666
- if (options.dryRun) {
667
- console.log("\n[DRY RUN] Patterns que seriam salvos:");
668
- for (const p of extracted) {
669
- console.log(` - ${p.name} (${p.scope}/${p.category})`);
670
- console.log(` Exemplos: ${p.examples.length}`);
671
- console.log(` Confiança: ${(p.confidence * 100).toFixed(0)}%`);
672
- }
673
- console.log("\nUse sem --dry-run para salvar no banco.\n");
674
- return;
675
- }
676
-
677
- // Salvar no banco
678
- console.log("\nSalvando patterns no banco...");
679
-
680
- for (const pattern of extracted) {
681
- // Verificar se já existe
682
- const existing = db
683
- .query("SELECT id FROM implementation_patterns WHERE name = ?")
684
- .get(pattern.name);
685
-
686
- if (existing) {
687
- // Atualizar
688
- db.run(
689
- `UPDATE implementation_patterns SET
690
- structure = ?, template = ?, examples = ?,
691
- confidence = ?, extracted_from = ?, updated_at = ?
692
- WHERE name = ?`,
693
- [
694
- JSON.stringify(pattern.structure),
695
- pattern.template,
696
- JSON.stringify(pattern.examples),
697
- pattern.confidence,
698
- pattern.extractedFrom,
699
- now,
700
- pattern.name,
701
- ]
702
- );
703
- console.log(` ✓ Atualizado: ${pattern.name}`);
704
- } else {
705
- // Inserir novo
706
- db.run(
707
- `INSERT INTO implementation_patterns
708
- (category, name, scope, applies_to, structure, template, examples, anti_patterns, confidence, extracted_from, created_at)
709
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
710
- [
711
- pattern.category,
712
- pattern.name,
713
- pattern.scope,
714
- pattern.appliesTo,
715
- JSON.stringify(pattern.structure),
716
- pattern.template,
717
- JSON.stringify(pattern.examples),
718
- JSON.stringify(pattern.antiPatterns),
719
- pattern.confidence,
720
- pattern.extractedFrom,
721
- now,
722
- ]
723
- );
724
- console.log(` ✓ Criado: ${pattern.name}`);
725
- }
726
- }
727
-
728
- console.log(`\n${"─".repeat(50)}`);
729
- console.log(`✓ ${extracted.length} patterns salvos no banco.`);
730
- console.log(`Use 'discover patterns' para visualizar.`);
731
- console.log(`Use 'discover export-patterns' para gerar patterns.md\n`);
732
- }
733
-
734
- export function patternsAnalyze(filePath: string, json: boolean = false): void {
735
- if (!existsSync(filePath)) {
736
- throw new CodexaError("Arquivo nao encontrado: " + filePath);
737
- }
738
-
739
- const analysis = analyzeFile(filePath);
740
-
741
- if (!analysis) {
742
- throw new CodexaError("Nao foi possivel analisar o arquivo.");
743
- }
744
-
745
- if (json) {
746
- console.log(JSON.stringify(analysis, null, 2));
747
- return;
748
- }
749
-
750
- console.log(`\n📄 Analise: ${filePath}`);
751
- console.log(`${"─".repeat(50)}`);
752
- console.log(`\nImports (${analysis.imports.length}):`);
753
- for (const imp of analysis.imports) {
754
- console.log(` - ${imp}`);
755
- }
756
- console.log(`\nExports (${analysis.exports.length}):`);
757
- for (const exp of analysis.exports) {
758
- console.log(` - ${exp}`);
759
- }
760
- console.log(`\nConvencoes:`);
761
- console.log(` - Default export: ${analysis.hasDefaultExport ? "sim" : "nao"}`);
762
- console.log(` - Named exports: ${analysis.hasNamedExports ? "sim" : "nao"}`);
763
- console.log(` - Type exports: ${analysis.hasTypeExports ? "sim" : "nao"}`);
764
- if (analysis.directives.length > 0) {
765
- console.log(` - Directives: ${analysis.directives.join(", ")}`);
766
- }
767
- if (analysis.hooksUsed.length > 0) {
768
- console.log(` - Hooks: ${analysis.hooksUsed.join(", ")}`);
769
- }
770
- if (analysis.reactPatterns.length > 0) {
771
- console.log(` - React patterns: ${analysis.reactPatterns.join(", ")}`);
772
- }
773
- if (analysis.conventions.length > 0) {
774
- console.log(` - Convencoes: ${analysis.conventions.join(", ")}`);
775
- }
776
- console.log(`\nTamanho: ${analysis.size} bytes\n`);
777
- }
778
-
779
- // ═══════════════════════════════════════════════════════════════
780
- // v9.0: ANTI-PATTERNS DE GATE BYPASSES
781
- // ═══════════════════════════════════════════════════════════════
782
-
783
- export function extractAntiPatternsFromHistory(): void {
784
- initSchema();
785
- const db = getDb();
786
-
787
- const bypasses = db.query(
788
- "SELECT * FROM gate_bypasses ORDER BY created_at DESC LIMIT 50"
789
- ).all() as any[];
790
-
791
- const antiPatterns: Map<string, { pattern: string; count: number; source: string }> = new Map();
792
-
793
- for (const bypass of bypasses) {
794
- if (!bypass.reason) continue;
795
-
796
- const key = `${bypass.gate_name}:${bypass.reason}`;
797
- const existing = antiPatterns.get(key);
798
-
799
- if (existing) {
800
- existing.count++;
801
- } else {
802
- const prefix = bypass.gate_name === "dry-check" ? "DRY" : "Evite";
803
- antiPatterns.set(key, {
804
- pattern: `${prefix}: ${bypass.reason} (gate ${bypass.gate_name})`,
805
- count: 1,
806
- source: bypass.gate_name,
807
- });
808
- }
809
- }
810
-
811
- const now = new Date().toISOString();
812
- let saved = 0;
813
-
814
- for (const [, ap] of antiPatterns) {
815
- if (ap.count < 2) continue;
816
-
817
- const allPatterns = db.query("SELECT * FROM implementation_patterns").all() as any[];
818
-
819
- for (const pattern of allPatterns) {
820
- const currentAnti: string[] = pattern.anti_patterns ? JSON.parse(pattern.anti_patterns) : [];
821
- if (!currentAnti.includes(ap.pattern)) {
822
- currentAnti.push(ap.pattern);
823
- db.run(
824
- "UPDATE implementation_patterns SET anti_patterns = ?, updated_at = ? WHERE id = ?",
825
- [JSON.stringify(currentAnti), now, pattern.id]
826
- );
827
- saved++;
828
- break;
829
- }
830
- }
831
- }
832
-
833
- console.log(`\nAnti-patterns extraidos do historico: ${antiPatterns.size}`);
834
- console.log(`Anti-patterns recorrentes (>=2x): ${[...antiPatterns.values()].filter(a => a.count >= 2).length}`);
835
- console.log(`Anti-patterns salvos em patterns: ${saved}`);
836
- console.log(`\nFonte: ${bypasses.length} gate bypasses analisados\n`);
837
- }
838
-
839
- // ═══════════════════════════════════════════════════════════════
840
- // v9.0: ANALISE PROFUNDA (analyze-deep)
841
- // ═══════════════════════════════════════════════════════════════
842
-
843
- export function patternsAnalyzeDeep(files: string[], json: boolean = false): void {
844
- const analyses: FileAnalysis[] = [];
845
-
846
- for (const file of files) {
847
- const analysis = analyzeFile(file);
848
- if (analysis) analyses.push(analysis);
849
- }
850
-
851
- if (analyses.length === 0) {
852
- throw new CodexaError("Nenhum arquivo analisado.");
853
- }
854
-
855
- const summary = {
856
- filesAnalyzed: analyses.length,
857
- directives: aggregateItems(analyses.flatMap(a => a.directives)),
858
- hooks: aggregateItems(analyses.flatMap(a => a.hooksUsed)),
859
- reactPatterns: aggregateItems(analyses.flatMap(a => a.reactPatterns)),
860
- conventions: aggregateItems(analyses.flatMap(a => a.conventions)),
861
- topImports: aggregateItems(analyses.flatMap(a => a.imports)),
862
- hasTypeExports: analyses.filter(a => a.hasTypeExports).length,
863
- };
864
-
865
- if (json) {
866
- console.log(JSON.stringify(summary, null, 2));
867
- return;
868
- }
869
-
870
- console.log(`\nAnalise Profunda (${analyses.length} arquivos)`);
871
- console.log("=".repeat(50));
872
- printAggregation("Directives", summary.directives);
873
- printAggregation("Hooks Usados", summary.hooks);
874
- printAggregation("React Patterns", summary.reactPatterns);
875
- printAggregation("Convencoes", summary.conventions);
876
- printAggregation("Top Imports", Object.fromEntries(
877
- Object.entries(summary.topImports).sort(([, a], [, b]) => b - a).slice(0, 10)
878
- ));
879
- console.log(`\nArquivos com type exports: ${summary.hasTypeExports}/${analyses.length}`);
880
-
881
- if (isGrepaiAvailable() && analyses.length > 0) {
882
- const topHooks = Object.keys(summary.hooks).slice(0, 3);
883
- if (topHooks.length > 0) {
884
- console.log(`\nBuscando arquivos similares via grepai...`);
885
- const query = `files using ${topHooks.join(" and ")}`;
886
- const similar = searchWithGrepai(query, 5);
887
- if (similar.length > 0) {
888
- console.log(`Arquivos similares encontrados:`);
889
- for (const s of similar) {
890
- console.log(` - ${s.path} (score: ${(s.score || 0).toFixed(2)})`);
891
- }
892
- }
893
- }
894
- }
895
-
896
- console.log();
897
- }
898
-
899
- function aggregateItems(items: string[]): Record<string, number> {
900
- const counts: Record<string, number> = {};
901
- for (const item of items) {
902
- counts[item] = (counts[item] || 0) + 1;
903
- }
904
- return counts;
905
- }
906
-
907
- function printAggregation(title: string, data: Record<string, number>): void {
908
- const entries = Object.entries(data);
909
- if (entries.length === 0) return;
910
- console.log(`\n${title}:`);
911
- for (const [name, count] of entries.sort(([, a], [, b]) => b - a)) {
912
- console.log(` - ${name}: ${count}x`);
913
- }
914
- }
915
-
916
280
  // ═══════════════════════════════════════════════════════════════
917
- // v9.0: ATUALIZACAO INCREMENTAL DE PATTERNS
281
+ // ATUALIZACAO INCREMENTAL DE PATTERNS (chamado por taskDone)
918
282
  // ═══════════════════════════════════════════════════════════════
919
283
 
920
284
  export function updatePatternsIncremental(files: string[], taskNumber: number): void {