@codexa/cli 9.0.31 → 9.0.32

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.
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { spawnSync } from "child_process";
9
9
  import { readFileSync, existsSync } from "fs";
10
- import { getDb } from "../db/connection";
10
+ import { dbGet, dbAll, dbRun } from "../db/connection";
11
11
  import { initSchema } from "../db/schema";
12
12
 
13
13
  // ═══════════════════════════════════════════════════════════════
@@ -43,13 +43,13 @@ export function isValidWorkspaceName(name: string): boolean {
43
43
  return WORKSPACE_NAME_REGEX.test(name);
44
44
  }
45
45
 
46
- export function getGrepaiWorkspace(): string | null {
46
+ export async function getGrepaiWorkspace(): Promise<string | null> {
47
47
  try {
48
- initSchema();
49
- const db = getDb();
50
- const project = db.query(
51
- "SELECT grepai_workspace FROM project WHERE id = 'default'"
52
- ).get() as any;
48
+ await initSchema();
49
+ const project = await dbGet<any>(
50
+ "SELECT grepai_workspace FROM project WHERE id = 'default'",
51
+ []
52
+ );
53
53
  return project?.grepai_workspace || null;
54
54
  } catch {
55
55
  return null;
@@ -126,13 +126,11 @@ function analyzeFile(filePath: string): FileAnalysis | null {
126
126
  for (const line of lines) {
127
127
  const trimmed = line.trim();
128
128
 
129
- // Extrair imports (modulo)
130
129
  const importMatch = trimmed.match(/^import\s+.*\s+from\s+['"]([^'"]+)['"]/);
131
130
  if (importMatch) {
132
131
  imports.push(importMatch[1]);
133
132
  }
134
133
 
135
- // Extrair hooks dos imports: import { useX, useY } from "..."
136
134
  const hooksInImport = trimmed.match(/^import\s+\{([^}]+)\}\s+from/);
137
135
  if (hooksInImport) {
138
136
  const specifiers = hooksInImport[1].split(",").map(s => s.trim());
@@ -143,7 +141,6 @@ function analyzeFile(filePath: string): FileAnalysis | null {
143
141
  }
144
142
  }
145
143
 
146
- // Extrair exports
147
144
  if (trimmed.startsWith("export default")) {
148
145
  hasDefaultExport = true;
149
146
  exports.push("default");
@@ -158,7 +155,6 @@ function analyzeFile(filePath: string): FileAnalysis | null {
158
155
  }
159
156
  }
160
157
 
161
- // Directives
162
158
  if (trimmed === "'use client'" || trimmed === '"use client"') {
163
159
  directives.push("use client");
164
160
  conventions.push("use client");
@@ -172,12 +168,10 @@ function analyzeFile(filePath: string): FileAnalysis | null {
172
168
  conventions.push("use cache");
173
169
  }
174
170
 
175
- // Async patterns
176
171
  if (trimmed.includes("async function") || trimmed.includes("async (")) {
177
172
  if (!conventions.includes("async")) conventions.push("async");
178
173
  }
179
174
 
180
- // React patterns (metadata complementar ao grepai)
181
175
  if (trimmed.includes("forwardRef") && !reactPatterns.includes("forwardRef")) {
182
176
  reactPatterns.push("forwardRef");
183
177
  }
@@ -236,7 +230,6 @@ export function extractUtilitiesFromFile(filePath: string): ExtractedUtility[] {
236
230
  for (const line of lines) {
237
231
  const trimmed = line.trim();
238
232
 
239
- // export [async] function name(args): ReturnType
240
233
  const funcMatch = trimmed.match(
241
234
  /^export\s+(?:async\s+)?function\s+(\w+)\s*(\([^)]*\)(?:\s*:\s*[^{]+)?)?/
242
235
  );
@@ -249,7 +242,6 @@ export function extractUtilitiesFromFile(filePath: string): ExtractedUtility[] {
249
242
  continue;
250
243
  }
251
244
 
252
- // export const name: Type = ...
253
245
  const constMatch = trimmed.match(
254
246
  /^export\s+const\s+(\w+)(?:\s*:\s*([^=]+))?\s*=/
255
247
  );
@@ -262,21 +254,18 @@ export function extractUtilitiesFromFile(filePath: string): ExtractedUtility[] {
262
254
  continue;
263
255
  }
264
256
 
265
- // export class Name
266
257
  const classMatch = trimmed.match(/^export\s+class\s+(\w+)/);
267
258
  if (classMatch) {
268
259
  utilities.push({ name: classMatch[1], type: "class" });
269
260
  continue;
270
261
  }
271
262
 
272
- // export interface Name
273
263
  const ifaceMatch = trimmed.match(/^export\s+interface\s+(\w+)/);
274
264
  if (ifaceMatch) {
275
265
  utilities.push({ name: ifaceMatch[1], type: "interface" });
276
266
  continue;
277
267
  }
278
268
 
279
- // export type Name
280
269
  const typeMatch = trimmed.match(/^export\s+type\s+(\w+)/);
281
270
  if (typeMatch) {
282
271
  utilities.push({ name: typeMatch[1], type: "type" });
@@ -308,12 +297,11 @@ export function inferScopeFromPath(filePath: string): string {
308
297
  // ATUALIZACAO INCREMENTAL DE PATTERNS (chamado por taskDone)
309
298
  // ═══════════════════════════════════════════════════════════════
310
299
 
311
- export function updatePatternsIncremental(files: string[], taskNumber: number): void {
312
- initSchema();
313
- const db = getDb();
300
+ export async function updatePatternsIncremental(files: string[], taskNumber: number): Promise<void> {
301
+ await initSchema();
314
302
  const now = new Date().toISOString();
315
303
 
316
- const allPatterns = db.query("SELECT * FROM implementation_patterns").all() as any[];
304
+ const allPatterns = await dbAll<any>("SELECT * FROM implementation_patterns", []);
317
305
  if (allPatterns.length === 0) return;
318
306
 
319
307
  for (const file of files) {
@@ -353,7 +341,7 @@ export function updatePatternsIncremental(files: string[], taskNumber: number):
353
341
  const newExtractedFrom = (pattern.extracted_from || 0) + 1;
354
342
  const newConfidence = Math.min(newExtractedFrom / 10, 1);
355
343
 
356
- db.run(
344
+ await dbRun(
357
345
  `UPDATE implementation_patterns
358
346
  SET examples = ?, extracted_from = ?, confidence = ?,
359
347
  structure = ?, updated_at = ?
@@ -399,11 +387,10 @@ export interface SemanticPattern {
399
387
  export function detectSemanticPatterns(workspace?: string): SemanticPattern[] {
400
388
  if (!isGrepaiAvailable()) return [];
401
389
 
402
- const ws = workspace || getGrepaiWorkspace() || undefined;
403
390
  const detected: SemanticPattern[] = [];
404
391
 
405
392
  for (const { query, category, scope } of SEMANTIC_PATTERN_QUERIES) {
406
- const results = searchWithGrepai(query, 5, ws);
393
+ const results = searchWithGrepai(query, 5, workspace);
407
394
  if (results.length < 2) continue;
408
395
 
409
396
  const avgScore = results.reduce((sum, r) => sum + r.score, 0) / results.length;
@@ -422,27 +409,27 @@ export function detectSemanticPatterns(workspace?: string): SemanticPattern[] {
422
409
  return detected;
423
410
  }
424
411
 
425
- export function saveSemanticPatterns(patterns: SemanticPattern[]): number {
426
- initSchema();
427
- const db = getDb();
412
+ export async function saveSemanticPatterns(patterns: SemanticPattern[]): Promise<number> {
413
+ await initSchema();
428
414
  const now = new Date().toISOString();
429
415
  let saved = 0;
430
416
 
431
417
  for (const p of patterns) {
432
418
  try {
433
- const existing = db.query(
434
- "SELECT id FROM implementation_patterns WHERE name = ?"
435
- ).get(p.name) as any;
419
+ const existing = await dbGet<any>(
420
+ "SELECT id FROM implementation_patterns WHERE name = ?",
421
+ [p.name]
422
+ );
436
423
 
437
424
  if (existing) {
438
- db.run(
425
+ await dbRun(
439
426
  `UPDATE implementation_patterns SET
440
427
  confidence = ?, examples = ?, extracted_from = ?, updated_at = ?
441
428
  WHERE id = ?`,
442
429
  [p.confidence, JSON.stringify(p.files.map(f => ({ path: f, relevance: p.confidence }))), p.files.length, now, existing.id]
443
430
  );
444
431
  } else {
445
- db.run(
432
+ await dbRun(
446
433
  `INSERT INTO implementation_patterns
447
434
  (category, name, scope, applies_to, structure, template, examples, confidence, extracted_from, created_at, updated_at)
448
435
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
@@ -473,19 +460,19 @@ export interface PatternComplianceResult {
473
460
  checked: number;
474
461
  }
475
462
 
476
- export function validatePatternCompliance(
463
+ export async function validatePatternCompliance(
477
464
  files: string[],
478
465
  minConfidence: number = 0.7
479
- ): PatternComplianceResult {
480
- initSchema();
481
- const db = getDb();
466
+ ): Promise<PatternComplianceResult> {
467
+ await initSchema();
482
468
  const result: PatternComplianceResult = { passed: true, violations: [], checked: 0 };
483
469
 
484
470
  if (files.length === 0) return result;
485
471
 
486
- const allPatterns = db.query(
487
- "SELECT * FROM implementation_patterns WHERE confidence >= ?"
488
- ).all(minConfidence) as any[];
472
+ const allPatterns = await dbAll<any>(
473
+ "SELECT * FROM implementation_patterns WHERE confidence >= ?",
474
+ [minConfidence]
475
+ );
489
476
 
490
477
  if (allPatterns.length === 0) return result;
491
478
 
@@ -504,10 +491,9 @@ export function validatePatternCompliance(
504
491
  let structure: any;
505
492
  try { structure = JSON.parse(pattern.structure); } catch { continue; }
506
493
 
507
- // Semantic patterns (source: "semantic") — use grepai if available
508
494
  if (structure.source === "semantic") {
509
495
  if (isGrepaiAvailable()) {
510
- const ws = getGrepaiWorkspace() || undefined;
496
+ const ws = await getGrepaiWorkspace() || undefined;
511
497
  const grepaiResults = searchWithGrepai(
512
498
  `file:${file} follows ${pattern.category} pattern`, 1, ws
513
499
  );
@@ -522,7 +508,6 @@ export function validatePatternCompliance(
522
508
  continue;
523
509
  }
524
510
 
525
- // Structural patterns — check imports, hooks, conventions
526
511
  if (structure.requiredImports) {
527
512
  for (const imp of structure.requiredImports) {
528
513
  if (!analysis.imports.includes(imp)) {
package/commands/plan.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { getDb } from "../db/connection";
1
+ import { dbGet, dbAll, dbRun } from "../db/connection";
2
2
  import { initSchema, getArchitecturalAnalysisForSpec } from "../db/schema";
3
3
  import { resolveSpec } from "./spec-resolver";
4
4
  import { CodexaError, ValidationError } from "../errors";
@@ -154,16 +154,16 @@ export function generateSpecId(name: string): string {
154
154
 
155
155
  // v8.4: Suporte a --from-analysis para import automatico de baby steps
156
156
  // v9.9: Suporte a fases com budget calculator e auto-split
157
- export function planStart(description: string, options: { fromAnalysis?: string; json?: boolean; maxTasksPerPhase?: number } = {}): void {
158
- initSchema();
159
- const db = getDb();
157
+ export async function planStart(description: string, options: { fromAnalysis?: string; json?: boolean; maxTasksPerPhase?: number } = {}): Promise<void> {
158
+ await initSchema();
160
159
 
161
160
  // v8.4: Buscar analise arquitetural se --from-analysis fornecido
162
161
  let analysis: any = null;
163
162
  if (options.fromAnalysis) {
164
- analysis = db.query(
165
- "SELECT * FROM architectural_analyses WHERE id = ?"
166
- ).get(options.fromAnalysis) as any;
163
+ analysis = await dbGet<any>(
164
+ "SELECT * FROM architectural_analyses WHERE id = ?",
165
+ [options.fromAnalysis]
166
+ );
167
167
 
168
168
  if (!analysis) {
169
169
  if (options.json) {
@@ -186,7 +186,7 @@ export function planStart(description: string, options: { fromAnalysis?: string;
186
186
  } else {
187
187
  // v8.4: Auto-deteccao por nome
188
188
  // v10.0: Alertar se analise aprovada existe mas --from-analysis nao foi usado
189
- const match = getArchitecturalAnalysisForSpec(description);
189
+ const match = await getArchitecturalAnalysisForSpec(description);
190
190
  if (match && match.status === "approved") {
191
191
  const msg = `Analise arquitetural aprovada encontrada: ${match.id} (${match.name}).\n`
192
192
  + `Use: plan start "${description}" --from-analysis ${match.id}`;
@@ -215,19 +215,19 @@ export function planStart(description: string, options: { fromAnalysis?: string;
215
215
 
216
216
  // Criar spec (com analysis_id se disponivel)
217
217
  if (analysis) {
218
- db.run(
218
+ await dbRun(
219
219
  "INSERT INTO specs (id, name, phase, analysis_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
220
220
  [specId, description, "planning", analysis.id, now, now]
221
221
  );
222
222
  } else {
223
- db.run(
223
+ await dbRun(
224
224
  "INSERT INTO specs (id, name, phase, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
225
225
  [specId, description, "planning", now, now]
226
226
  );
227
227
  }
228
228
 
229
229
  // Criar contexto inicial
230
- db.run(
230
+ await dbRun(
231
231
  "INSERT INTO context (spec_id, objective, updated_at) VALUES (?, ?, ?)",
232
232
  [specId, description, now]
233
233
  );
@@ -266,7 +266,7 @@ export function planStart(description: string, options: { fromAnalysis?: string;
266
266
  const phase = phaseMap.get(step.number) || 1;
267
267
 
268
268
  const normalizedStepAgent = step.agent ? resolveAgentName(step.agent) : null;
269
- db.run(
269
+ await dbRun(
270
270
  `INSERT INTO tasks (spec_id, number, name, agent, depends_on, can_parallel, files, phase)
271
271
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
272
272
  [specId, step.number, step.name, normalizedStepAgent, dependsOn, 1, files, phase]
@@ -275,13 +275,13 @@ export function planStart(description: string, options: { fromAnalysis?: string;
275
275
  }
276
276
 
277
277
  // Atualizar total de tasks e fases no contexto
278
- db.run(
278
+ await dbRun(
279
279
  "UPDATE context SET total_tasks = ?, total_phases = ?, current_phase = 1, updated_at = ? WHERE spec_id = ?",
280
280
  [tasksCreated, totalPhases, now, specId]
281
281
  );
282
282
 
283
283
  // Marcar analise como implemented
284
- db.run(
284
+ await dbRun(
285
285
  "UPDATE architectural_analyses SET status = 'implemented', updated_at = ? WHERE id = ?",
286
286
  [now, analysis.id]
287
287
  );
@@ -301,8 +301,8 @@ export function planStart(description: string, options: { fromAnalysis?: string;
301
301
  console.error(`\n[ERRO] ${msg}\n`);
302
302
  }
303
303
  // Cleanup: remover spec vazio criado
304
- db.run("DELETE FROM context WHERE spec_id = ?", [specId]);
305
- db.run("DELETE FROM specs WHERE id = ?", [specId]);
304
+ await dbRun("DELETE FROM context WHERE spec_id = ?", [specId]);
305
+ await dbRun("DELETE FROM specs WHERE id = ?", [specId]);
306
306
  throw new CodexaError(msg);
307
307
  }
308
308
  }
@@ -341,7 +341,7 @@ export function planStart(description: string, options: { fromAnalysis?: string;
341
341
  console.log(`\n Plano dividido em ${totalPhases} fases:`);
342
342
  // Count tasks per phase
343
343
  const tasksByPhase = new Map<number, number>();
344
- const allTasks = db.query("SELECT phase FROM tasks WHERE spec_id = ?").all(specId) as any[];
344
+ const allTasks = await dbAll<any>("SELECT phase FROM tasks WHERE spec_id = ?", [specId]);
345
345
  for (const t of allTasks) {
346
346
  tasksByPhase.set(t.phase || 1, (tasksByPhase.get(t.phase || 1) || 0) + 1);
347
347
  }
@@ -367,19 +367,20 @@ export function planStart(description: string, options: { fromAnalysis?: string;
367
367
  }
368
368
  }
369
369
 
370
- export function planShow(json: boolean = false, specId?: string): void {
371
- initSchema();
372
- const db = getDb();
370
+ export async function planShow(json: boolean = false, specId?: string): Promise<void> {
371
+ await initSchema();
373
372
 
374
373
  const spec = resolveSpec(specId);
375
374
 
376
- const tasks = db
377
- .query("SELECT * FROM tasks WHERE spec_id = ? ORDER BY number")
378
- .all(spec.id) as any[];
375
+ const tasks = await dbAll<any>(
376
+ "SELECT * FROM tasks WHERE spec_id = ? ORDER BY number",
377
+ [spec.id]
378
+ );
379
379
 
380
- const context = db
381
- .query("SELECT * FROM context WHERE spec_id = ?")
382
- .get(spec.id) as any;
380
+ const context = await dbGet<any>(
381
+ "SELECT * FROM context WHERE spec_id = ?",
382
+ [spec.id]
383
+ );
383
384
 
384
385
  if (json) {
385
386
  console.log(JSON.stringify({ spec, context, tasks }, null, 2));
@@ -450,23 +451,23 @@ function printTaskLine(task: any): void {
450
451
  console.log(`${statusIcon} #${task.number}: ${task.name} (${task.agent || "geral"})${depsStr}${parallelStr}`);
451
452
  }
452
453
 
453
- export function planTaskAdd(options: {
454
+ export async function planTaskAdd(options: {
454
455
  name: string;
455
456
  agent?: string;
456
457
  depends?: string;
457
458
  files?: string;
458
459
  sequential?: boolean;
459
460
  specId?: string;
460
- }): void {
461
- initSchema();
462
- const db = getDb();
461
+ }): Promise<void> {
462
+ await initSchema();
463
463
 
464
464
  const spec = resolveSpec(options.specId, ["planning"]);
465
465
 
466
466
  // Pegar proximo numero
467
- const lastTask = db
468
- .query("SELECT MAX(number) as max FROM tasks WHERE spec_id = ?")
469
- .get(spec.id) as any;
467
+ const lastTask = await dbGet<any>(
468
+ "SELECT MAX(number) as max FROM tasks WHERE spec_id = ?",
469
+ [spec.id]
470
+ );
470
471
  const nextNumber = (lastTask?.max || 0) + 1;
471
472
 
472
473
  // Validar dependencias
@@ -474,9 +475,10 @@ export function planTaskAdd(options: {
474
475
  if (options.depends) {
475
476
  dependsOn = options.depends.split(",").map((s) => parseInt(s.trim()));
476
477
  for (const depId of dependsOn) {
477
- const exists = db
478
- .query("SELECT id FROM tasks WHERE spec_id = ? AND number = ?")
479
- .get(spec.id, depId);
478
+ const exists = await dbGet(
479
+ "SELECT id FROM tasks WHERE spec_id = ? AND number = ?",
480
+ [spec.id, depId]
481
+ );
480
482
  if (!exists) {
481
483
  throw new ValidationError(`Dependencia invalida: task #${depId} nao existe.`);
482
484
  }
@@ -484,9 +486,10 @@ export function planTaskAdd(options: {
484
486
 
485
487
  // v8.1: Detectar dependencias circulares
486
488
  // Construir grafo de dependencias e verificar se adicionar esta task cria ciclo
487
- const allTasks = db
488
- .query("SELECT number, depends_on FROM tasks WHERE spec_id = ?")
489
- .all(spec.id) as any[];
489
+ const allTasks = await dbAll<any>(
490
+ "SELECT number, depends_on FROM tasks WHERE spec_id = ?",
491
+ [spec.id]
492
+ );
490
493
 
491
494
  // Grafo: taskNumber -> [dependencias]
492
495
  const graph = new Map<number, number[]>();
@@ -553,7 +556,7 @@ export function planTaskAdd(options: {
553
556
  files = options.files.split(",").map((s) => s.trim());
554
557
  }
555
558
 
556
- db.run(
559
+ await dbRun(
557
560
  `INSERT INTO tasks (spec_id, number, name, agent, depends_on, can_parallel, files)
558
561
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
559
562
  [
@@ -568,9 +571,9 @@ export function planTaskAdd(options: {
568
571
  );
569
572
 
570
573
  // Atualizar total de tasks no contexto
571
- const count = db.query("SELECT COUNT(*) as c FROM tasks WHERE spec_id = ?").get(spec.id) as any;
572
- db.run("UPDATE context SET total_tasks = ?, updated_at = ? WHERE spec_id = ?", [
573
- count.c,
574
+ const count = await dbGet<any>("SELECT COUNT(*) as c FROM tasks WHERE spec_id = ?", [spec.id]);
575
+ await dbRun("UPDATE context SET total_tasks = ?, updated_at = ? WHERE spec_id = ?", [
576
+ count?.c || 0,
574
577
  new Date().toISOString(),
575
578
  spec.id,
576
579
  ]);
@@ -582,22 +585,21 @@ export function planTaskAdd(options: {
582
585
  console.log(` Paralelizavel: ${options.sequential ? "Nao" : "Sim"}\n`);
583
586
  }
584
587
 
585
- export function planCancel(specId?: string): void {
586
- initSchema();
587
- const db = getDb();
588
+ export async function planCancel(specId?: string): Promise<void> {
589
+ await initSchema();
588
590
 
589
591
  const spec = resolveSpec(specId);
590
592
 
591
593
  const now = new Date().toISOString();
592
594
 
593
595
  // Marcar spec como cancelled
594
- db.run(
596
+ await dbRun(
595
597
  "UPDATE specs SET phase = 'cancelled', updated_at = ? WHERE id = ?",
596
598
  [now, spec.id]
597
599
  );
598
600
 
599
601
  // Marcar tasks pendentes/running como cancelled
600
- const updatedTasks = db.run(
602
+ const updatedTasks = await dbRun(
601
603
  "UPDATE tasks SET status = 'cancelled' WHERE spec_id = ? AND status IN ('pending', 'running')",
602
604
  [spec.id]
603
605
  );