@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.
- package/commands/decide.ts +120 -3
- package/commands/discover.ts +18 -9
- package/commands/integration.test.ts +754 -0
- package/commands/knowledge.test.ts +2 -6
- package/commands/knowledge.ts +20 -4
- package/commands/patterns.ts +8 -644
- package/commands/product.ts +41 -104
- package/commands/spec-resolver.test.ts +2 -13
- package/commands/standards.ts +33 -3
- package/commands/task.ts +21 -4
- package/commands/utils.test.ts +25 -87
- package/commands/utils.ts +20 -82
- package/context/assembly.ts +11 -12
- package/context/domains.test.ts +300 -0
- package/context/domains.ts +157 -0
- package/context/generator.ts +14 -13
- package/context/index.ts +6 -1
- package/context/references.test.ts +159 -0
- package/context/references.ts +159 -0
- package/context/sections.ts +18 -1
- package/db/schema.ts +40 -5
- package/db/test-helpers.ts +33 -0
- package/gates/standards-validator.test.ts +447 -0
- package/gates/standards-validator.ts +164 -125
- package/gates/typecheck-validator.ts +296 -92
- package/gates/validator.ts +93 -8
- package/package.json +1 -1
- package/protocol/process-return.ts +39 -4
- package/workflow.ts +54 -84
package/commands/patterns.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Codexa Workflow
|
|
2
|
+
* Codexa Workflow - Patterns & Utilities
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* e
|
|
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 {
|
|
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
|
|
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
|
-
//
|
|
281
|
+
// ATUALIZACAO INCREMENTAL DE PATTERNS (chamado por taskDone)
|
|
918
282
|
// ═══════════════════════════════════════════════════════════════
|
|
919
283
|
|
|
920
284
|
export function updatePatternsIncremental(files: string[], taskNumber: number): void {
|