@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.
- package/commands/knowledge.ts +51 -0
- package/commands/patterns.ts +410 -28
- package/commands/task.ts +64 -13
- package/commands/utils.ts +244 -0
- package/gates/typecheck-validator.ts +174 -0
- package/gates/validator.ts +88 -0
- package/package.json +1 -1
- package/protocol/subagent-protocol.ts +25 -4
- package/workflow.ts +39 -3
package/commands/knowledge.ts
CHANGED
|
@@ -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
|
}
|
package/commands/patterns.ts
CHANGED
|
@@ -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
|
-
//
|
|
163
|
-
if (trimmed
|
|
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
|
|
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
|
|
307
|
-
const
|
|
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
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
|
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 //
|
|
348
|
-
template += `export async function POST(request: NextRequest) {\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 //
|
|
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 //
|
|
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
|
|
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
|
|
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: [],
|
|
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(`\
|
|
615
|
-
console.log(` - Default export: ${analysis.hasDefaultExport ? "sim" : "
|
|
616
|
-
console.log(` - Named exports: ${analysis.hasNamedExports ? "sim" : "
|
|
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(` -
|
|
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
|
+
}
|