@codexa/cli 9.0.30 → 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.
@@ -588,12 +588,10 @@ class User:
588
588
  // ═══════════════════════════════════════════════════════════════
589
589
 
590
590
  describe("enforceGate", () => {
591
- it("should throw GateError when gate fails", () => {
592
- // "task-done" with no taskId makes "task-is-running" fail immediately
593
- // without needing DB access
591
+ it("should throw GateError when gate fails", async () => {
594
592
  try {
595
- enforceGate("task-done", {});
596
- expect(true).toBe(false); // Should not reach here
593
+ await enforceGate("task-done", {});
594
+ expect(true).toBe(false);
597
595
  } catch (e) {
598
596
  expect(e instanceof GateError).toBe(true);
599
597
  const ge = e as GateError;
@@ -603,13 +601,12 @@ describe("enforceGate", () => {
603
601
  }
604
602
  });
605
603
 
606
- it("should not throw for unknown command (no gates defined)", () => {
607
- // Unknown commands have no gate checks, so they pass
608
- expect(() => enforceGate("nonexistent-command")).not.toThrow();
604
+ it("should not throw for unknown command (no gates defined)", async () => {
605
+ await enforceGate("nonexistent-command");
609
606
  });
610
607
 
611
- it("should return a GateResult from validateGate", () => {
612
- const result = validateGate("task-done", {});
608
+ it("should return a GateResult from validateGate", async () => {
609
+ const result = await validateGate("task-done", {});
613
610
  expect(result.passed).toBe(false);
614
611
  expect(result.reason).toBeDefined();
615
612
  expect(result.resolution).toBeDefined();
@@ -621,25 +618,19 @@ describe("enforceGate", () => {
621
618
  // ═══════════════════════════════════════════════════════════════
622
619
 
623
620
  describe("Recovery Strategies (P3.3)", () => {
624
- it("GateError should carry recovery suggestion", () => {
621
+ it("GateError should carry recovery suggestion", async () => {
625
622
  try {
626
- // "task-done" with no taskId fails on "task-is-running"
627
- // task-is-running has no recovery strategy, so recovery should be undefined
628
- enforceGate("task-done", {});
623
+ await enforceGate("task-done", {});
629
624
  expect(true).toBe(false);
630
625
  } catch (e) {
631
626
  const ge = e as GateError;
632
627
  expect(ge instanceof GateError).toBe(true);
633
- // task-is-running has no recovery strategy
634
628
  expect(ge.recovery).toBeUndefined();
635
629
  }
636
630
  });
637
631
 
638
- it("validateGate should return recovery for checkpoint-filled failure", () => {
639
- // Simulate a task-done call where task-is-running passes but checkpoint fails
640
- // We pass taskId to skip task-is-running (it needs DB), so we test checkpoint directly
641
- const result = validateGate("task-done", { taskId: null });
642
- // Without taskId, task-is-running fails first (no recovery for it)
632
+ it("validateGate should return recovery for checkpoint-filled failure", async () => {
633
+ const result = await validateGate("task-done", { taskId: null });
643
634
  expect(result.passed).toBe(false);
644
635
  });
645
636
 
@@ -667,8 +658,8 @@ describe("Recovery Strategies (P3.3)", () => {
667
658
  expect(err.recovery).toBeUndefined();
668
659
  });
669
660
 
670
- it("validateGate for unknown command should pass with no recovery", () => {
671
- const result = validateGate("nonexistent", {});
661
+ it("validateGate for unknown command should pass with no recovery", async () => {
662
+ const result = await validateGate("nonexistent", {});
672
663
  expect(result.passed).toBe(true);
673
664
  expect(result.recovery).toBeUndefined();
674
665
  });
@@ -1,4 +1,4 @@
1
- import { getDb } from "../db/connection";
1
+ import { dbGet, dbAll, dbRun } from "../db/connection";
2
2
  import { existsSync, readFileSync, statSync } from "fs";
3
3
  import { extname } from "path";
4
4
  import { validateAgainstStandards, printValidationResult } from "./standards-validator";
@@ -194,47 +194,47 @@ const RECOVERY_STRATEGIES: Record<string, (details?: string) => RecoverySuggesti
194
194
  }),
195
195
  };
196
196
 
197
- function getActiveSpec(specId?: string): any {
198
- return resolveSpecOrNull(specId);
197
+ async function getActiveSpec(specId?: string): Promise<any> {
198
+ return await resolveSpecOrNull(specId);
199
199
  }
200
200
 
201
- function executeCheck(check: string, context: any): { passed: boolean; details?: string } {
202
- const db = getDb();
203
-
201
+ async function executeCheck(check: string, context: any): Promise<{ passed: boolean; details?: string }> {
204
202
  switch (check) {
205
203
  case "plan-exists": {
206
- const spec = getActiveSpec(context.specId);
204
+ const spec = await getActiveSpec(context.specId);
207
205
  return { passed: spec !== null };
208
206
  }
209
207
 
210
208
  case "has-tasks": {
211
- const spec = getActiveSpec(context.specId);
209
+ const spec = await getActiveSpec(context.specId);
212
210
  if (!spec) return { passed: false };
213
- const count = db.query("SELECT COUNT(*) as c FROM tasks WHERE spec_id = ?").get(spec.id) as any;
214
- return { passed: count.c > 0 };
211
+ const count = await dbGet<any>("SELECT COUNT(*) as c FROM tasks WHERE spec_id = ?", [spec.id]);
212
+ return { passed: count?.c > 0 };
215
213
  }
216
214
 
217
215
  case "phase-is-checking": {
218
- const spec = getActiveSpec(context.specId);
216
+ const spec = await getActiveSpec(context.specId);
219
217
  return { passed: spec?.phase === "checking" };
220
218
  }
221
219
 
222
220
  case "spec-approved": {
223
- const spec = getActiveSpec(context.specId);
221
+ const spec = await getActiveSpec(context.specId);
224
222
  return { passed: spec?.approved_at !== null };
225
223
  }
226
224
 
227
225
  case "dependencies-done": {
228
226
  if (!context.taskId) return { passed: true };
229
- const task = db.query("SELECT * FROM tasks WHERE id = ?").get(context.taskId) as any;
227
+ const task = await dbGet<any>("SELECT * FROM tasks WHERE id = ?", [context.taskId]);
230
228
  if (!task || !task.depends_on) return { passed: true };
231
229
 
232
230
  const deps = JSON.parse(task.depends_on) as number[];
233
- const pending = deps.filter((depNum) => {
234
- // Buscar por number (não id) - depends_on armazena números de tasks
235
- const depTask = db.query("SELECT status FROM tasks WHERE spec_id = ? AND number = ?").get(task.spec_id, depNum) as any;
236
- return depTask?.status !== "done";
237
- });
231
+ const pending: number[] = [];
232
+ for (const depNum of deps) {
233
+ const depTask = await dbGet<any>("SELECT status FROM tasks WHERE spec_id = ? AND number = ?", [task.spec_id, depNum]);
234
+ if (depTask?.status !== "done") {
235
+ pending.push(depNum);
236
+ }
237
+ }
238
238
 
239
239
  return {
240
240
  passed: pending.length === 0,
@@ -244,7 +244,7 @@ function executeCheck(check: string, context: any): { passed: boolean; details?:
244
244
 
245
245
  case "task-is-running": {
246
246
  if (!context.taskId) return { passed: false };
247
- const task = db.query("SELECT status FROM tasks WHERE id = ?").get(context.taskId) as any;
247
+ const task = await dbGet<any>("SELECT status FROM tasks WHERE id = ?", [context.taskId]);
248
248
  return { passed: task?.status === "running" };
249
249
  }
250
250
 
@@ -257,11 +257,12 @@ function executeCheck(check: string, context: any): { passed: boolean; details?:
257
257
  }
258
258
 
259
259
  case "all-tasks-done": {
260
- const spec = getActiveSpec(context.specId);
260
+ const spec = await getActiveSpec(context.specId);
261
261
  if (!spec) return { passed: false };
262
- const pending = db.query(
263
- "SELECT number FROM tasks WHERE spec_id = ? AND status != 'done'"
264
- ).all(spec.id) as any[];
262
+ const pending = await dbAll<any>(
263
+ "SELECT number FROM tasks WHERE spec_id = ? AND status != 'done'",
264
+ [spec.id]
265
+ );
265
266
  return {
266
267
  passed: pending.length === 0,
267
268
  details: pending.map((t) => `#${t.number}`).join(", "),
@@ -269,33 +270,27 @@ function executeCheck(check: string, context: any): { passed: boolean; details?:
269
270
  }
270
271
 
271
272
  case "review-exists": {
272
- const spec = getActiveSpec(context.specId);
273
+ const spec = await getActiveSpec(context.specId);
273
274
  if (!spec) return { passed: false };
274
- const review = db.query("SELECT * FROM review WHERE spec_id = ?").get(spec.id);
275
+ const review = await dbGet("SELECT * FROM review WHERE spec_id = ?", [spec.id]);
275
276
  return { passed: review !== null };
276
277
  }
277
278
 
278
279
  case "files-exist": {
279
280
  if (!context.files || context.files.length === 0) return { passed: true };
280
281
 
281
- // v8.0: Validar não apenas existência, mas conteúdo mínimo
282
- // v9.2: Validar que arquivo foi modificado DURANTE a task
283
- // v9.3: Tolerancia de 5s para clock skew em sandbox
284
- // v9.6: Confiar no protocolo quando subagent reporta arquivos (sandbox)
285
282
  const MTIME_TOLERANCE_MS = 5000;
286
283
  const issues: string[] = [];
287
284
 
288
- // Construir set de arquivos reportados pelo subagent (podem estar em sandbox)
289
285
  const subagentReportedFiles = new Set<string>();
290
286
  if (context.subagentData) {
291
287
  for (const f of context.subagentData.files_created || []) subagentReportedFiles.add(f);
292
288
  for (const f of context.subagentData.files_modified || []) subagentReportedFiles.add(f);
293
289
  }
294
290
 
295
- // Buscar started_at da task para comparacao temporal
296
291
  let taskStartTime: number | null = null;
297
292
  if (context.taskId) {
298
- const taskRow = db.query("SELECT started_at FROM tasks WHERE id = ?").get(context.taskId) as any;
293
+ const taskRow = await dbGet<any>("SELECT started_at FROM tasks WHERE id = ?", [context.taskId]);
299
294
  if (taskRow?.started_at) {
300
295
  taskStartTime = new Date(taskRow.started_at).getTime();
301
296
  }
@@ -303,10 +298,7 @@ function executeCheck(check: string, context: any): { passed: boolean; details?:
303
298
 
304
299
  for (const file of context.files) {
305
300
  if (!existsSync(file)) {
306
- // Arquivo nao existe no disco — verificar se veio de subagent
307
301
  if (subagentReportedFiles.has(file)) {
308
- // Subagent reportou o arquivo via protocolo → confiar
309
- // (arquivo pode estar em sandbox, sera materializado no commit)
310
302
  continue;
311
303
  }
312
304
  issues.push(`${file}: arquivo nao encontrado no disco`);
@@ -318,7 +310,6 @@ function executeCheck(check: string, context: any): { passed: boolean; details?:
318
310
  issues.push(`${file}: ${validation.reason}`);
319
311
  continue;
320
312
  }
321
- // Verificar que arquivo foi tocado durante a task (com tolerancia)
322
313
  if (taskStartTime) {
323
314
  try {
324
315
  const stat = statSync(file);
@@ -328,7 +319,6 @@ function executeCheck(check: string, context: any): { passed: boolean; details?:
328
319
  issues.push(`${file}: arquivo nao foi modificado durante esta task (mtime ${diffSec}s anterior ao start)`);
329
320
  }
330
321
  } catch {
331
- // statSync falhou — arquivo pode nao existir (ja reportado acima)
332
322
  }
333
323
  }
334
324
  }
@@ -340,23 +330,18 @@ function executeCheck(check: string, context: any): { passed: boolean; details?:
340
330
  }
341
331
 
342
332
  case "standards-follow": {
343
- // Se --force foi passado, registrar bypass e passar
344
333
  if (context.force) {
345
- logGateBypass(context.taskId, "standards-follow", context.forceReason);
334
+ await logGateBypass(context.taskId, "standards-follow", context.forceReason);
346
335
  return { passed: true };
347
336
  }
348
337
 
349
- // Se nenhum arquivo, passar
350
338
  if (!context.files || context.files.length === 0) return { passed: true };
351
339
 
352
- // Obter agente da task
353
- const task = db.query("SELECT * FROM tasks WHERE id = ?").get(context.taskId) as any;
340
+ const task = await dbGet<any>("SELECT * FROM tasks WHERE id = ?", [context.taskId]);
354
341
  const agentDomain = domainToScope(getAgentDomain(task?.agent));
355
342
 
356
- // Validar arquivos contra standards
357
- const result = validateAgainstStandards(context.files, agentDomain);
343
+ const result = await validateAgainstStandards(context.files, agentDomain);
358
344
 
359
- // Mostrar resultado se houver violacoes
360
345
  if (!result.passed || result.warnings.length > 0) {
361
346
  printValidationResult(result);
362
347
  }
@@ -370,9 +355,8 @@ function executeCheck(check: string, context: any): { passed: boolean; details?:
370
355
  }
371
356
 
372
357
  case "dry-check": {
373
- // v8.5: Verificar se arquivos criados exportam utilities que ja existem
374
358
  if (context.force) {
375
- logGateBypass(context.taskId, "dry-check", context.forceReason);
359
+ await logGateBypass(context.taskId, "dry-check", context.forceReason);
376
360
  return { passed: true };
377
361
  }
378
362
 
@@ -384,14 +368,12 @@ function executeCheck(check: string, context: any): { passed: boolean; details?:
384
368
  if (!existsSync(file)) continue;
385
369
 
386
370
  const fileScope = inferScopeFromPath(file);
387
- // Arquivos de teste nao conflitam com production code
388
371
  if (fileScope === "testing") continue;
389
372
 
390
373
  const utilities = extractUtilitiesFromFile(file);
391
374
 
392
375
  for (const util of utilities) {
393
- const existing = findDuplicateUtilities(util.name, file);
394
- // Filtrar: duplicatas em scope "testing" nao contam
376
+ const existing = await findDuplicateUtilities(util.name, file);
395
377
  const realDuplicates = existing.filter(
396
378
  (e: any) => inferScopeFromPath(e.file_path) !== "testing"
397
379
  );
@@ -404,7 +386,6 @@ function executeCheck(check: string, context: any): { passed: boolean; details?:
404
386
  }
405
387
 
406
388
  if (duplicates.length > 0) {
407
- // Apenas function/const/class bloqueiam; interface/type so avisam
408
389
  const blockingDupes = duplicates.filter(d =>
409
390
  d.includes("(function)") || d.includes("(const)") || d.includes("(class)")
410
391
  );
@@ -420,7 +401,6 @@ function executeCheck(check: string, context: any): { passed: boolean; details?:
420
401
  details: blockingDupes.join("\n"),
421
402
  };
422
403
  } else {
423
- // Types/interfaces: aviso nao-bloqueante
424
404
  console.warn("\n[DRY] Aviso: nomes de tipo duplicados (nao bloqueante):");
425
405
  for (const d of duplicates) {
426
406
  console.warn(` - ${d}`);
@@ -434,14 +414,13 @@ function executeCheck(check: string, context: any): { passed: boolean; details?:
434
414
 
435
415
  case "typecheck-pass": {
436
416
  if (context.force) {
437
- logGateBypass(context.taskId, "typecheck-pass", context.forceReason);
417
+ await logGateBypass(context.taskId, "typecheck-pass", context.forceReason);
438
418
  return { passed: true };
439
419
  }
440
420
 
441
421
  if (!context.files || context.files.length === 0) return { passed: true };
442
422
 
443
- // Buscar stack e custom command do projeto
444
- const projectRow = db.query("SELECT stack, typecheck_command FROM project WHERE id = 'default'").get() as any;
423
+ const projectRow = await dbGet<any>("SELECT stack, typecheck_command FROM project WHERE id = 'default'");
445
424
  const typecheckResult = runTypecheck(context.files, {
446
425
  customCommand: projectRow?.typecheck_command || null,
447
426
  stackJson: projectRow?.stack || null,
@@ -458,25 +437,28 @@ function executeCheck(check: string, context: any): { passed: boolean; details?:
458
437
  }
459
438
 
460
439
  case "no-critical-blockers": {
461
- const spec = getActiveSpec(context.specId);
440
+ const spec = await getActiveSpec(context.specId);
462
441
  if (!spec) return { passed: true };
463
442
 
464
- const allKnowledge = db
465
- .query(
466
- `SELECT k.*, t.number as task_number, t.name as task_name
467
- FROM knowledge k
468
- LEFT JOIN tasks t ON k.task_origin = t.id
469
- WHERE k.spec_id = ? AND k.severity = 'critical'
470
- ORDER BY k.created_at DESC`
471
- )
472
- .all(spec.id) as any[];
473
-
474
- const unresolved = allKnowledge.filter((k: any) => {
475
- const hasAck = db.query(
476
- "SELECT 1 FROM knowledge_acknowledgments WHERE knowledge_id = ?"
477
- ).get(k.id);
478
- return !hasAck;
479
- });
443
+ const allKnowledge = await dbAll<any>(
444
+ `SELECT k.*, t.number as task_number, t.name as task_name
445
+ FROM knowledge k
446
+ LEFT JOIN tasks t ON k.task_origin = t.id
447
+ WHERE k.spec_id = ? AND k.severity = 'critical'
448
+ ORDER BY k.created_at DESC`,
449
+ [spec.id]
450
+ );
451
+
452
+ const unresolved: any[] = [];
453
+ for (const k of allKnowledge) {
454
+ const hasAck = await dbGet(
455
+ "SELECT 1 FROM knowledge_acknowledgments WHERE knowledge_id = ?",
456
+ [k.id]
457
+ );
458
+ if (!hasAck) {
459
+ unresolved.push(k);
460
+ }
461
+ }
480
462
 
481
463
  if (unresolved.length === 0) return { passed: true };
482
464
 
@@ -493,23 +475,21 @@ function executeCheck(check: string, context: any): { passed: boolean; details?:
493
475
 
494
476
  case "decision-conflicts": {
495
477
  if (context.force) {
496
- logGateBypass(context.taskId, "decision-conflicts", context.forceReason);
478
+ await logGateBypass(context.taskId, "decision-conflicts", context.forceReason);
497
479
  return { passed: true };
498
480
  }
499
481
 
500
482
  if (!context.subagentData?.decisions_made?.length) return { passed: true };
501
483
 
502
- const dcTask = db.query("SELECT * FROM tasks WHERE id = ?").get(context.taskId) as any;
484
+ const dcTask = await dbGet<any>("SELECT * FROM tasks WHERE id = ?", [context.taskId]);
503
485
  if (!dcTask) return { passed: true };
504
486
 
505
- const existingDecisions = db
506
- .query("SELECT * FROM decisions WHERE spec_id = ? AND status = 'active'")
507
- .all(dcTask.spec_id) as any[];
487
+ const existingDecisions = await dbAll<any>(
488
+ "SELECT * FROM decisions WHERE spec_id = ? AND status = 'active'",
489
+ [dcTask.spec_id]
490
+ );
508
491
  if (existingDecisions.length === 0) return { passed: true };
509
492
 
510
- // v10.1: Filter out decisions from the CURRENT task.
511
- // processSubagentReturn() already saved them to DB before this gate runs,
512
- // so without filtering, the new decisions would match against themselves.
513
493
  const priorDecisions = existingDecisions.filter(
514
494
  (d: any) => d.task_ref !== dcTask.number
515
495
  );
@@ -534,18 +514,15 @@ function executeCheck(check: string, context: any): { passed: boolean; details?:
534
514
  }
535
515
 
536
516
  case "pattern-compliance": {
537
- // v10.2: Gate 4.7 — validate code follows project patterns
538
517
  if (context.force) {
539
- logGateBypass(context.taskId, "pattern-compliance", context.forceReason);
518
+ await logGateBypass(context.taskId, "pattern-compliance", context.forceReason);
540
519
  return { passed: true };
541
520
  }
542
521
 
543
522
  if (!context.files || context.files.length === 0) return { passed: true };
544
523
 
545
- const patternResult = validatePatternCompliance(context.files);
524
+ const patternResult = await validatePatternCompliance(context.files);
546
525
  if (!patternResult.passed && patternResult.violations.length > 0) {
547
- // v10.2: Start in warning mode (don't block, just warn)
548
- // Change to `return { passed: false, ... }` after validation in real projects
549
526
  console.warn("\n[PATTERN] Divergencias de pattern detectadas (aviso, nao bloqueante):");
550
527
  for (const v of patternResult.violations) {
551
528
  console.warn(` - ${v.file}: ${v.reason} (pattern: ${v.pattern})`);
@@ -575,13 +552,12 @@ function executeCheck(check: string, context: any): { passed: boolean; details?:
575
552
  }
576
553
  }
577
554
 
578
- function logGateBypass(taskId: number, gateName: string, reason?: string): void {
579
- const db = getDb();
580
- const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
555
+ async function logGateBypass(taskId: number, gateName: string, reason?: string): Promise<void> {
556
+ const task = await dbGet<any>("SELECT * FROM tasks WHERE id = ?", [taskId]);
581
557
  if (!task) return;
582
558
 
583
559
  const now = new Date().toISOString();
584
- db.run(
560
+ await dbRun(
585
561
  `INSERT INTO gate_bypasses (spec_id, task_id, gate_name, reason, created_at)
586
562
  VALUES (?, ?, ?, ?, ?)`,
587
563
  [task.spec_id, taskId, gateName, reason || "Nao informado", now]
@@ -592,11 +568,11 @@ function logGateBypass(taskId: number, gateName: string, reason?: string): void
592
568
  console.warn(` Isso sera auditado no review.\n`);
593
569
  }
594
570
 
595
- export function validateGate(command: string, context: any = {}): GateResult {
571
+ export async function validateGate(command: string, context: any = {}): Promise<GateResult> {
596
572
  const gates = GATES[command] || [];
597
573
 
598
574
  for (const gate of gates) {
599
- const result = executeCheck(gate.check, context);
575
+ const result = await executeCheck(gate.check, context);
600
576
 
601
577
  if (!result.passed) {
602
578
  const recoveryFn = RECOVERY_STRATEGIES[gate.check];
@@ -616,8 +592,8 @@ export function validateGate(command: string, context: any = {}): GateResult {
616
592
  return { passed: true };
617
593
  }
618
594
 
619
- export function enforceGate(command: string, context: any = {}): void {
620
- const result = validateGate(command, context);
595
+ export async function enforceGate(command: string, context: any = {}): Promise<void> {
596
+ const result = await validateGate(command, context);
621
597
 
622
598
  if (!result.passed) {
623
599
  throw new GateError(
@@ -629,35 +605,29 @@ export function enforceGate(command: string, context: any = {}): void {
629
605
  }
630
606
  }
631
607
 
632
- // v8.0: Validar conteúdo de arquivos criados (não apenas existência)
633
608
  export interface FileValidationResult {
634
609
  valid: boolean;
635
610
  reason?: string;
636
611
  }
637
612
 
638
613
  function validateFileContent(filePath: string): FileValidationResult {
639
- // 1. Verificar existência
640
614
  if (!existsSync(filePath)) {
641
615
  return { valid: false, reason: "arquivo nao encontrado" };
642
616
  }
643
617
 
644
618
  try {
645
- // 2. Verificar tamanho (arquivo vazio ou muito pequeno)
646
619
  const stats = statSync(filePath);
647
620
  if (stats.size === 0) {
648
621
  return { valid: false, reason: "arquivo vazio (0 bytes)" };
649
622
  }
650
623
 
651
- // 3. Ler conteúdo e verificar estrutura mínima
652
624
  const content = readFileSync(filePath, "utf-8");
653
625
  const trimmed = content.trim();
654
626
 
655
- // Arquivo trivialmente pequeno (< 20 caracteres não é código real)
656
627
  if (trimmed.length < 20) {
657
628
  return { valid: false, reason: `conteudo trivial (${trimmed.length} chars)` };
658
629
  }
659
630
 
660
- // 4. Validações específicas por extensão
661
631
  const ext = extname(filePath).toLowerCase();
662
632
  const validation = validateByExtension(ext, content);
663
633
  if (!validation.valid) {
@@ -678,11 +648,9 @@ export function validateByExtension(ext: string, content: string): FileValidatio
678
648
  case ".tsx":
679
649
  case ".js":
680
650
  case ".jsx":
681
- // Arquivos JS/TS devem ter pelo menos uma declaração
682
651
  if (!hasCodeStructure(trimmed, ["export", "import", "function", "const", "let", "var", "class", "interface", "type"])) {
683
652
  return { valid: false, reason: "sem declaracoes validas (export, function, class, etc)" };
684
653
  }
685
- // TSX/JSX devem ter JSX ou export de componente
686
654
  if ((ext === ".tsx" || ext === ".jsx") && !trimmed.includes("<") && !trimmed.includes("export")) {
687
655
  return { valid: false, reason: "componente sem JSX ou export" };
688
656
  }
@@ -691,14 +659,12 @@ export function validateByExtension(ext: string, content: string): FileValidatio
691
659
  case ".css":
692
660
  case ".scss":
693
661
  case ".sass":
694
- // CSS deve ter pelo menos um seletor e propriedade
695
662
  if (!trimmed.includes("{") || !trimmed.includes(":")) {
696
663
  return { valid: false, reason: "CSS sem regras validas" };
697
664
  }
698
665
  break;
699
666
 
700
667
  case ".json":
701
- // JSON deve ser válido
702
668
  try {
703
669
  JSON.parse(content);
704
670
  } catch {
@@ -707,28 +673,24 @@ export function validateByExtension(ext: string, content: string): FileValidatio
707
673
  break;
708
674
 
709
675
  case ".sql":
710
- // SQL deve ter statements
711
676
  if (!hasCodeStructure(trimmed.toUpperCase(), ["SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", "ALTER", "DROP"])) {
712
677
  return { valid: false, reason: "SQL sem statements validos" };
713
678
  }
714
679
  break;
715
680
 
716
681
  case ".py":
717
- // Python deve ter definições
718
682
  if (!hasCodeStructure(trimmed, ["def ", "class ", "import ", "from "])) {
719
683
  return { valid: false, reason: "Python sem definicoes (def, class, import)" };
720
684
  }
721
685
  break;
722
686
 
723
687
  case ".go":
724
- // Go deve ter package
725
688
  if (!trimmed.includes("package ")) {
726
689
  return { valid: false, reason: "Go sem declaracao package" };
727
690
  }
728
691
  break;
729
692
 
730
693
  case ".md":
731
- // Markdown deve ter conteúdo mínimo
732
694
  if (trimmed.length < 50) {
733
695
  return { valid: false, reason: "Markdown muito curto" };
734
696
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codexa/cli",
3
- "version": "9.0.30",
3
+ "version": "9.0.32",
4
4
  "description": "Orchestrated workflow system for Claude Code - manages feature development through parallel subagents with structured phases, gates, and quality enforcement.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,6 +27,7 @@
27
27
  "bun": ">=1.0.0"
28
28
  },
29
29
  "dependencies": {
30
+ "@libsql/client": "^0.17.2",
30
31
  "commander": "^12.0.0"
31
32
  },
32
33
  "devDependencies": {