@codexa/cli 9.0.2 → 9.0.4

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/utils.ts CHANGED
@@ -1,35 +1,25 @@
1
1
  import { getDb } from "../db/connection";
2
- import { initSchema, getPatternsForFiles, getLastSessionSummary, getSessionSummaries, getRelatedDecisions, findContradictions, getArchitecturalAnalysisForSpec, getUtilitiesForContext } from "../db/schema";
2
+ import { initSchema, getArchitecturalAnalysisForSpec } from "../db/schema";
3
3
  import { getKnowledgeForTask } from "./knowledge";
4
4
  import { getLibContextsForAgent } from "./research";
5
+ import { resolveSpec, resolveSpecOrNull, getAllActiveSpecs } from "./spec-resolver";
6
+ import { CodexaError } from "../errors";
5
7
  import pkg from "../package.json";
6
8
 
7
- // v8.3: Limite maximo de contexto para subagents (16KB)
8
- const MAX_CONTEXT_SIZE = 16384;
9
+ // Re-export context modules for backward compatibility
10
+ export { getContextForSubagent, getMinimalContextForSubagent } from "../context/generator";
11
+ export { AGENT_SECTIONS, MAX_CONTEXT_SIZE } from "../context/assembly";
9
12
 
10
13
  export function contextUpdate(options: {
11
14
  approach?: string;
12
15
  pattern?: string;
13
16
  constraint?: string;
17
+ specId?: string;
14
18
  }): void {
15
19
  initSchema();
16
20
  const db = getDb();
17
21
 
18
- const spec = db
19
- .query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
20
- .get() as any;
21
-
22
- if (!spec) {
23
- console.error("\nNenhuma feature ativa.\n");
24
- process.exit(1);
25
- }
26
-
27
- // Verificar se está na fase de implementação
28
- if (spec.phase !== "implementing") {
29
- console.error(`\nContexto só pode ser atualizado na fase 'implementing'.`);
30
- console.error(`Fase atual: ${spec.phase}\n`);
31
- process.exit(1);
32
- }
22
+ const spec = resolveSpec(options.specId, ["implementing"]);
33
23
 
34
24
  const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id) as any;
35
25
  const now = new Date().toISOString();
@@ -98,18 +88,24 @@ export function contextUpdate(options: {
98
88
  console.log(`\nContexto atualizado para: ${spec.name}\n`);
99
89
  }
100
90
 
101
- export function status(json: boolean = false): void {
91
+ export function status(json: boolean = false, specId?: string): void {
102
92
  initSchema();
103
93
 
104
94
  const db = getDb();
105
95
 
106
- const spec = db
107
- .query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
108
- .get() as any;
96
+ // Se --spec fornecido, mostra spec especifico
97
+ // Se nao fornecido, mostra todos os ativos (multi-spec)
98
+ if (specId) {
99
+ const spec = resolveSpec(specId);
100
+ showSingleSpecStatus(db, spec, json);
101
+ return;
102
+ }
103
+
104
+ const allSpecs = getAllActiveSpecs();
109
105
 
110
- if (!spec) {
106
+ if (allSpecs.length === 0) {
111
107
  if (json) {
112
- console.log(JSON.stringify({ active: false, version: pkg.version, message: "Nenhuma feature ativa" }));
108
+ console.log(JSON.stringify({ active: false, version: pkg.version, specs: [], message: "Nenhuma feature ativa" }));
113
109
  } else {
114
110
  console.log(`\nCodexa Workflow v${pkg.version}`);
115
111
  console.log("\nNenhuma feature ativa.");
@@ -118,13 +114,23 @@ export function status(json: boolean = false): void {
118
114
  return;
119
115
  }
120
116
 
117
+ if (allSpecs.length === 1) {
118
+ showSingleSpecStatus(db, allSpecs[0], json);
119
+ return;
120
+ }
121
+
122
+ // Multi-spec: show summary table
123
+ showMultiSpecStatus(db, allSpecs, json);
124
+ }
125
+
126
+ function showSingleSpecStatus(db: any, spec: any, json: boolean): void {
121
127
  const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id) as any;
122
128
  const tasks = db.query("SELECT * FROM tasks WHERE spec_id = ?").all(spec.id) as any[];
123
129
  const decisions = db.query("SELECT * FROM decisions WHERE spec_id = ? AND status = 'active'").all(spec.id) as any[];
124
130
 
125
- const tasksDone = tasks.filter((t) => t.status === "done").length;
126
- const tasksRunning = tasks.filter((t) => t.status === "running").length;
127
- const tasksPending = tasks.filter((t) => t.status === "pending").length;
131
+ const tasksDone = tasks.filter((t: any) => t.status === "done").length;
132
+ const tasksRunning = tasks.filter((t: any) => t.status === "running").length;
133
+ const tasksPending = tasks.filter((t: any) => t.status === "pending").length;
128
134
 
129
135
  const progress = tasks.length > 0 ? Math.round((tasksDone / tasks.length) * 100) : 0;
130
136
 
@@ -156,7 +162,7 @@ export function status(json: boolean = false): void {
156
162
  console.log(`Aprovado: ${spec.approved_at ? "Sim" : "Nao"}`);
157
163
 
158
164
  console.log(`\nProgresso:`);
159
- const bar = "".repeat(Math.floor(progress / 5)) + "".repeat(20 - Math.floor(progress / 5));
165
+ const bar = "\u2588".repeat(Math.floor(progress / 5)) + "\u2591".repeat(20 - Math.floor(progress / 5));
160
166
  console.log(` [${bar}] ${progress}%`);
161
167
  console.log(` Concluidas: ${tasksDone}/${tasks.length}`);
162
168
  console.log(` Em execucao: ${tasksRunning}`);
@@ -171,35 +177,6 @@ export function status(json: boolean = false): void {
171
177
  console.log(`\nDecisoes ativas: ${decisions.length}`);
172
178
  }
173
179
 
174
- // v8.1: Mostrar ultimo session summary para continuidade
175
- const lastSession = getLastSessionSummary(spec.id);
176
- if (lastSession) {
177
- console.log(`\nUltima sessao:`);
178
- console.log(` ${lastSession.summary}`);
179
- if (lastSession.next_steps) {
180
- try {
181
- const nextSteps = JSON.parse(lastSession.next_steps);
182
- if (nextSteps.length > 0) {
183
- console.log(` Proximos passos sugeridos:`);
184
- for (const step of nextSteps.slice(0, 3)) {
185
- console.log(` - ${step}`);
186
- }
187
- }
188
- } catch { /* ignore */ }
189
- }
190
- if (lastSession.blockers) {
191
- try {
192
- const blockers = JSON.parse(lastSession.blockers);
193
- if (blockers.length > 0) {
194
- console.log(` Blockers pendentes:`);
195
- for (const b of blockers) {
196
- console.log(` [!] ${b}`);
197
- }
198
- }
199
- } catch { /* ignore */ }
200
- }
201
- }
202
-
203
180
  console.log(`\n${"─".repeat(60)}`);
204
181
 
205
182
  // Sugerir proximo passo
@@ -220,20 +197,50 @@ export function status(json: boolean = false): void {
220
197
  console.log();
221
198
  }
222
199
 
223
- export function contextExport(options: { task?: string; json?: boolean; }): void {
224
- initSchema();
200
+ function showMultiSpecStatus(db: any, specs: any[], json: boolean): void {
201
+ if (json) {
202
+ const specsData = specs.map((spec: any) => {
203
+ const tasks = db.query("SELECT * FROM tasks WHERE spec_id = ?").all(spec.id) as any[];
204
+ const done = tasks.filter((t: any) => t.status === "done").length;
205
+ return {
206
+ id: spec.id,
207
+ name: spec.name,
208
+ phase: spec.phase,
209
+ progress: tasks.length > 0 ? Math.round((done / tasks.length) * 100) : 0,
210
+ tasks: { done, total: tasks.length },
211
+ };
212
+ });
213
+ console.log(JSON.stringify({ active: true, version: pkg.version, specs: specsData }));
214
+ return;
215
+ }
225
216
 
226
- const db = getDb();
217
+ console.log(`\n${"=".repeat(60)}`);
218
+ console.log(`Codexa Workflow v${pkg.version} | ${specs.length} FEATURES ATIVAS`);
219
+ console.log(`${"=".repeat(60)}\n`);
227
220
 
228
- const spec = db
229
- .query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
230
- .get() as any;
221
+ for (const spec of specs) {
222
+ const tasks = db.query("SELECT * FROM tasks WHERE spec_id = ?").all(spec.id) as any[];
223
+ const done = tasks.filter((t: any) => t.status === "done").length;
224
+ const progress = tasks.length > 0 ? Math.round((done / tasks.length) * 100) : 0;
225
+ const bar = "\u2588".repeat(Math.floor(progress / 5)) + "\u2591".repeat(20 - Math.floor(progress / 5));
231
226
 
232
- if (!spec) {
233
- console.error("\nNenhuma feature ativa.\n");
234
- process.exit(1);
227
+ console.log(` ${spec.name}`);
228
+ console.log(` Fase: ${spec.phase} | [${bar}] ${progress}% (${done}/${tasks.length})`);
229
+ console.log(` ID: ${spec.id}`);
230
+ console.log();
235
231
  }
236
232
 
233
+ console.log(`${"─".repeat(60)}`);
234
+ console.log(`Use: status --spec <id> para detalhes de uma feature\n`);
235
+ }
236
+
237
+ export function contextExport(options: { task?: string; json?: boolean; specId?: string; }): void {
238
+ initSchema();
239
+
240
+ const db = getDb();
241
+
242
+ const spec = resolveSpec(options.specId);
243
+
237
244
  const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id) as any;
238
245
  const decisions = db
239
246
  .query("SELECT * FROM decisions WHERE spec_id = ? AND status = 'active'")
@@ -345,642 +352,11 @@ export function contextExport(options: { task?: string; json?: boolean; }): void
345
352
  console.log(JSON.stringify(exportData, null, 2));
346
353
  }
347
354
 
348
- // v9.0: Contexto minimo para subagent (max ~2KB)
349
- const MAX_MINIMAL_CONTEXT = 2048;
350
-
351
- export function getMinimalContextForSubagent(taskId: number): string {
355
+ export function contextDetail(section: string, json: boolean = false, specId?: string): void {
352
356
  initSchema();
353
357
  const db = getDb();
354
358
 
355
- const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
356
- if (!task) return "ERRO: Task nao encontrada";
357
-
358
- const spec = db.query("SELECT * FROM specs WHERE id = ?").get(task.spec_id) as any;
359
- const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(task.spec_id) as any;
360
-
361
- const taskFiles = task.files ? JSON.parse(task.files) : [];
362
- const domain = task.agent?.split("-")[0] || "all";
363
-
364
- // 1. Standards REQUIRED apenas (nao recommended)
365
- const requiredStandards = db.query(
366
- `SELECT rule FROM standards
367
- WHERE enforcement = 'required' AND (scope = 'all' OR scope = ?)
368
- LIMIT 10`
369
- ).all(domain) as any[];
370
-
371
- // 2. Blockers CRITICAL apenas
372
- const criticalBlockers = db.query(
373
- `SELECT content FROM knowledge
374
- WHERE spec_id = ? AND severity = 'critical'
375
- ORDER BY created_at DESC LIMIT 5`
376
- ).all(task.spec_id) as any[];
377
-
378
- // 3. Contradicoes (se existir)
379
- let contradictions: any[] = [];
380
- try {
381
- contradictions = findContradictions(task.spec_id);
382
- } catch { /* ignore */ }
383
-
384
- // 4. Decisoes da task anterior (dependency direta)
385
- const dependsOn = task.depends_on ? JSON.parse(task.depends_on) : [];
386
- let depDecisions: any[] = [];
387
- if (dependsOn.length > 0) {
388
- const placeholders = dependsOn.map(() => '?').join(',');
389
- const depTasks = db.query(
390
- `SELECT id FROM tasks WHERE spec_id = ? AND number IN (${placeholders})`
391
- ).all(task.spec_id, ...dependsOn) as any[];
392
-
393
- for (const dt of depTasks) {
394
- const reasoning = db.query(
395
- `SELECT thought FROM reasoning_log
396
- WHERE spec_id = ? AND task_id = ? AND category = 'recommendation'
397
- ORDER BY created_at DESC LIMIT 2`
398
- ).all(task.spec_id, dt.id) as any[];
399
- depDecisions.push(...reasoning);
400
- }
401
- }
402
-
403
- // Montar output minimo
404
- let output = `## CONTEXTO MINIMO (Task #${task.number})
405
-
406
- **Feature:** ${spec.name}
407
- **Objetivo:** ${context?.objective || "N/A"}
408
- **Arquivos:** ${taskFiles.join(", ") || "Nenhum especificado"}
409
- `;
410
-
411
- if (requiredStandards.length > 0) {
412
- output += `\n**Standards (OBRIGATORIOS):**\n${requiredStandards.map((s: any) => `- ${s.rule}`).join("\n")}\n`;
413
- }
414
-
415
- if (criticalBlockers.length > 0) {
416
- output += `\n**BLOCKERS CRITICOS:**\n${criticalBlockers.map((b: any) => `[X] ${b.content}`).join("\n")}\n`;
417
- }
418
-
419
- if (contradictions.length > 0) {
420
- output += `\n**CONTRADICOES:**\n${contradictions.map((c: any) => `[!] "${c.decision1}" <-> "${c.decision2}"`).join("\n")}\n`;
421
- }
422
-
423
- if (depDecisions.length > 0) {
424
- output += `\n**Recomendacoes de tasks anteriores:**\n${depDecisions.map((d: any) => `- ${d.thought}`).join("\n")}\n`;
425
- }
426
-
427
- output += `
428
- **Contexto expandido**: Se precisar de mais contexto, execute:
429
- codexa context detail standards # Todos os standards
430
- codexa context detail decisions # Todas as decisoes
431
- codexa context detail patterns # Patterns do projeto
432
- codexa context detail knowledge # Knowledge completo
433
- codexa context detail architecture # Analise arquitetural
434
- `;
435
-
436
- // Truncar se exceder limite
437
- if (output.length > MAX_MINIMAL_CONTEXT) {
438
- output = output.substring(0, MAX_MINIMAL_CONTEXT - 100) + "\n\n[CONTEXTO TRUNCADO - use: codexa context detail <secao>]\n";
439
- }
440
-
441
- return output;
442
- }
443
-
444
- // Funcao helper para formatar contexto para subagent (uso interno)
445
- // v8.1: Contexto COMPLETO mas ESTRUTURADO - todas as fontes relevantes incluidas
446
- export function getContextForSubagent(taskId: number): string {
447
- initSchema();
448
-
449
- const db = getDb();
450
-
451
- const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
452
- if (!task) {
453
- return "ERRO: Task nao encontrada";
454
- }
455
-
456
- const spec = db.query("SELECT * FROM specs WHERE id = ?").get(task.spec_id) as any;
457
- const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(task.spec_id) as any;
458
- const project = db.query("SELECT * FROM project WHERE id = 'default'").get() as any;
459
-
460
- // v8.4: Analise arquitetural (link explicito via analysis_id ou nome)
461
- const archAnalysis = getArchitecturalAnalysisForSpec(spec.name, task.spec_id);
462
-
463
- // Arquivos da task para filtrar contexto relevante
464
- const taskFiles = task.files ? JSON.parse(task.files) : [];
465
- const domain = task.agent?.split("-")[0] || "all";
466
-
467
- // Decisoes relevantes (max 8, priorizando as que mencionam arquivos da task)
468
- const allDecisions = db
469
- .query("SELECT * FROM decisions WHERE spec_id = ? AND status = 'active' ORDER BY created_at DESC")
470
- .all(task.spec_id) as any[];
471
- const decisions = filterRelevantDecisions(allDecisions, taskFiles, 8);
472
-
473
- // Standards required + recommended que se aplicam aos arquivos
474
- const standards = db
475
- .query(
476
- `SELECT * FROM standards
477
- WHERE (scope = 'all' OR scope = ?)
478
- ORDER BY enforcement DESC, category`
479
- )
480
- .all(domain) as any[];
481
- const relevantStandards = filterRelevantStandards(standards, taskFiles);
482
-
483
- // v8.3: Knowledge com TTL, caps e indicadores de truncamento
484
- const allKnowledge = getKnowledgeForTask(task.spec_id, taskId);
485
- const now48h = new Date(Date.now() - 48 * 60 * 60 * 1000).toISOString();
486
- const recentKnowledge = allKnowledge.filter((k: any) => k.created_at > now48h);
487
- const staleKnowledge = allKnowledge.filter((k: any) => k.created_at <= now48h);
488
- // Conhecimento recente primeiro; stale so se critical
489
- const effectiveKnowledge = [
490
- ...recentKnowledge,
491
- ...staleKnowledge.filter((k: any) => k.severity === 'critical'),
492
- ];
493
- const allCriticalKnowledge = effectiveKnowledge.filter((k: any) => k.severity === 'critical' || k.severity === 'warning');
494
- const criticalKnowledge = allCriticalKnowledge.slice(0, 20);
495
- const truncatedCritical = allCriticalKnowledge.length - criticalKnowledge.length;
496
- const allInfoKnowledge = effectiveKnowledge.filter((k: any) => k.severity === 'info');
497
- const infoKnowledge = allInfoKnowledge.slice(0, 10);
498
- const truncatedInfo = allInfoKnowledge.length - infoKnowledge.length;
499
-
500
- // v8.1: Contexto de produto (resumido - problema, usuarios, restricoes)
501
- const productContext = db.query("SELECT * FROM product_context WHERE id = 'default'").get() as any;
502
-
503
- // v8.1: Patterns de implementacao matchados aos arquivos da task
504
- const patterns = getPatternsForFiles(taskFiles);
505
-
506
- // v8.2: Reasoning de tasks dependentes + todas tasks completas recentes
507
- const dependsOn = task.depends_on ? JSON.parse(task.depends_on) : [];
508
- const depReasoning: any[] = [];
509
- const seenTaskIds = new Set<number>();
510
-
511
- // 1. Reasoning de dependencias diretas (prioridade maxima)
512
- if (dependsOn.length > 0) {
513
- const placeholders = dependsOn.map(() => '?').join(',');
514
- const depTasks = db.query(
515
- `SELECT id, number FROM tasks WHERE spec_id = ? AND number IN (${placeholders})`
516
- ).all(task.spec_id, ...dependsOn) as any[];
517
-
518
- for (const depTask of depTasks) {
519
- seenTaskIds.add(depTask.id);
520
- const reasoning = db.query(
521
- `SELECT category, thought FROM reasoning_log
522
- WHERE spec_id = ? AND task_id = ?
523
- AND category IN ('recommendation', 'decision', 'challenge')
524
- ORDER BY
525
- CASE importance WHEN 'critical' THEN 1 WHEN 'high' THEN 2 ELSE 3 END,
526
- created_at DESC
527
- LIMIT 3`
528
- ).all(task.spec_id, depTask.id) as any[];
529
- for (const r of reasoning) {
530
- depReasoning.push({ ...r, fromTask: depTask.number });
531
- }
532
- }
533
- }
534
-
535
- // 2. Reasoning de TODAS tasks completas (recommendations e challenges de alta importancia)
536
- const completedTasks = db.query(
537
- `SELECT t.id, t.number FROM tasks t
538
- WHERE t.spec_id = ? AND t.status = 'done' AND t.id != ?
539
- ORDER BY t.completed_at DESC LIMIT 10`
540
- ).all(task.spec_id, taskId) as any[];
541
-
542
- for (const ct of completedTasks) {
543
- if (seenTaskIds.has(ct.id)) continue;
544
- const reasoning = db.query(
545
- `SELECT category, thought FROM reasoning_log
546
- WHERE spec_id = ? AND task_id = ?
547
- AND category IN ('recommendation', 'challenge')
548
- AND importance IN ('critical', 'high')
549
- ORDER BY
550
- CASE importance WHEN 'critical' THEN 1 ELSE 2 END,
551
- created_at DESC
552
- LIMIT 2`
553
- ).all(task.spec_id, ct.id) as any[];
554
- for (const r of reasoning) {
555
- depReasoning.push({ ...r, fromTask: ct.number });
556
- }
557
- }
558
-
559
- // v8.1: Lib contexts do projeto (resumido - nomes e versoes)
560
- const libContexts = db.query(
561
- "SELECT lib_name, version FROM lib_contexts ORDER BY lib_name LIMIT 10"
562
- ).all() as any[];
563
-
564
- // v8.3: Sessoes compostas para continuidade cross-session (todas, nao so ultima)
565
- const sessions = getSessionSummaries(task.spec_id, 5);
566
-
567
- // v8.2: Knowledge graph - decisoes que afetam arquivos da task
568
- const graphDecisions: any[] = [];
569
- for (const file of taskFiles) {
570
- try {
571
- const related = getRelatedDecisions(file, "file");
572
- for (const d of related) {
573
- if (!graphDecisions.find((gd: any) => gd.id === d.id)) {
574
- graphDecisions.push(d);
575
- }
576
- }
577
- } catch { /* ignore if graph empty */ }
578
- }
579
-
580
- // v8.2: Deteccao de contradicoes
581
- let contradictions: any[] = [];
582
- try {
583
- contradictions = findContradictions(task.spec_id);
584
- } catch { /* ignore if no contradictions */ }
585
-
586
- // v8.2: Patterns descobertos por subagents anteriores
587
- const discoveredPatterns = context?.patterns ? JSON.parse(context.patterns) : [];
588
-
589
- // ===== MONTAR OUTPUT ESTRUTURADO =====
590
-
591
- let output = `## CONTEXTO (Task #${task.number})
592
-
593
- **Feature:** ${spec.name}
594
- **Objetivo:** ${context?.objective || "N/A"}
595
- **Arquivos:** ${taskFiles.join(", ") || "Nenhum especificado"}
596
- `;
597
-
598
- // Contexto de produto
599
- if (productContext) {
600
- output += `
601
- ### PRODUTO
602
- - **Problema:** ${productContext.problem || "N/A"}
603
- - **Usuarios:** ${productContext.target_users || "N/A"}`;
604
- if (productContext.constraints) {
605
- try {
606
- const constraints = JSON.parse(productContext.constraints);
607
- if (constraints.length > 0) {
608
- output += `\n- **Restricoes:** ${constraints.slice(0, 3).join("; ")}${constraints.length > 3 ? ` [+${constraints.length - 3} mais]` : ''}`;
609
- }
610
- } catch { /* ignore parse errors */ }
611
- }
612
- output += "\n";
613
- }
614
-
615
- // v8.3: Secao ARQUITETURA (Fase 3 - Architect Pipeline)
616
- if (archAnalysis) {
617
- output += `
618
- ### ARQUITETURA (${archAnalysis.id})`;
619
- if (archAnalysis.approach) {
620
- const approachPreview = archAnalysis.approach.length > 500
621
- ? archAnalysis.approach.substring(0, 500) + "..."
622
- : archAnalysis.approach;
623
- output += `\n**Abordagem:** ${approachPreview}`;
624
- }
625
- if (archAnalysis.risks) {
626
- try {
627
- const risks = JSON.parse(archAnalysis.risks);
628
- if (risks.length > 0) {
629
- const highRisks = risks.filter((r: any) => r.impact === 'high' || r.probability === 'high');
630
- const risksToShow = highRisks.length > 0 ? highRisks.slice(0, 3) : risks.slice(0, 3);
631
- output += `\n**Riscos:**`;
632
- for (const r of risksToShow) {
633
- output += `\n - ${r.description} (${r.probability}/${r.impact}) -> ${r.mitigation}`;
634
- }
635
- if (risks.length > risksToShow.length) {
636
- output += `\n [+${risks.length - risksToShow.length} mais riscos]`;
637
- }
638
- }
639
- } catch { /* ignore */ }
640
- }
641
- if (archAnalysis.decisions) {
642
- try {
643
- const archDecisions = JSON.parse(archAnalysis.decisions);
644
- if (archDecisions.length > 0) {
645
- output += `\n**Decisoes arquiteturais:**`;
646
- for (const d of archDecisions.slice(0, 5)) {
647
- output += `\n - ${d.decision}: ${d.rationale || ''}`;
648
- }
649
- if (archDecisions.length > 5) {
650
- output += `\n [+${archDecisions.length - 5} mais]`;
651
- }
652
- }
653
- } catch { /* ignore */ }
654
- }
655
- output += "\n";
656
- }
657
-
658
- // Standards
659
- const requiredStds = relevantStandards.filter((s: any) => s.enforcement === 'required');
660
- const recommendedStds = relevantStandards.filter((s: any) => s.enforcement === 'recommended');
661
-
662
- output += `
663
- ### STANDARDS (${relevantStandards.length})`;
664
- if (requiredStds.length > 0) {
665
- output += `\n**Obrigatorios:**\n${requiredStds.map((s: any) => `- ${s.rule}`).join("\n")}`;
666
- }
667
- if (recommendedStds.length > 0) {
668
- output += `\n**Recomendados:**\n${recommendedStds.map((s: any) => `- ${s.rule}`).join("\n")}`;
669
- }
670
- if (relevantStandards.length === 0) {
671
- output += "\nNenhum standard aplicavel";
672
- }
673
- output += "\n";
674
-
675
- // v8.3: Decisoes com indicador de truncamento
676
- const truncatedDecisions = allDecisions.length - decisions.length;
677
- output += `
678
- ### DECISOES (${decisions.length}${truncatedDecisions > 0 ? ` [+${truncatedDecisions} mais - use: decisions list]` : ''})
679
- ${decisions.length > 0 ? decisions.map((d) => `- **${d.title}**: ${d.decision}`).join("\n") : "Nenhuma"}
680
- `;
681
-
682
- // Reasoning de tasks anteriores
683
- if (depReasoning.length > 0) {
684
- output += `
685
- ### CONTEXTO DE TASKS ANTERIORES
686
- ${depReasoning.map((r: any) => `- [Task #${r.fromTask}/${r.category}] ${r.thought}`).join("\n")}
687
- `;
688
- }
689
-
690
- // v8.3: Alertas com cap de 20 e indicador de truncamento
691
- if (criticalKnowledge.length > 0) {
692
- const severityIcon: Record<string, string> = { warning: "!!", critical: "XX" };
693
- output += `
694
- ### ALERTAS (${criticalKnowledge.length}${truncatedCritical > 0 ? ` [+${truncatedCritical} mais - use: knowledge list --severity warning]` : ''})
695
- ${criticalKnowledge.map((k: any) => `[${severityIcon[k.severity] || "!"}] ${k.content}`).join("\n")}
696
- `;
697
- }
698
-
699
- // v8.3: Discoveries com indicador de truncamento
700
- if (infoKnowledge.length > 0) {
701
- output += `
702
- ### DISCOVERIES DE TASKS ANTERIORES (${infoKnowledge.length}${truncatedInfo > 0 ? ` [+${truncatedInfo} mais - use: knowledge list]` : ''})
703
- ${infoKnowledge.map((k: any) => `- ${k.content}`).join("\n")}
704
- `;
705
- }
706
-
707
- // Patterns de implementacao matchados
708
- if (patterns.length > 0) {
709
- output += `
710
- ### PATTERNS DO PROJETO (${patterns.length})
711
- ${patterns.map((p: any) => {
712
- const tmpl = p.template || "";
713
- const preview = tmpl.length > 200 ? tmpl.substring(0, 200) + `... [truncado de ${tmpl.length} chars]` : tmpl;
714
- return `- **${p.name}** (${p.category}): ${preview || p.description || "Sem template"}`;
715
- }).join("\n")}
716
- `;
717
- }
718
-
719
- // v8.5: Utility Map (DRY prevention - Camada 1)
720
- try {
721
- const taskDirs = [...new Set(taskFiles.map((f: string) => {
722
- const parts = f.replace(/\\/g, "/").split("/");
723
- return parts.slice(0, -1).join("/");
724
- }).filter(Boolean))];
725
-
726
- const agentScope = task.agent?.split("-")[0] || undefined;
727
- let relevantUtilities = getUtilitiesForContext(taskDirs, undefined, 15);
728
-
729
- // Se poucos resultados por diretorio, ampliar para scope do agent
730
- if (relevantUtilities.length < 5 && agentScope) {
731
- const scopeUtils = getUtilitiesForContext([], agentScope, 15);
732
- const existingKeys = new Set(relevantUtilities.map((u: any) => `${u.file_path}:${u.utility_name}`));
733
- for (const u of scopeUtils) {
734
- if (!existingKeys.has(`${u.file_path}:${u.utility_name}`)) {
735
- relevantUtilities.push(u);
736
- }
737
- if (relevantUtilities.length >= 15) break;
738
- }
739
- }
740
-
741
- if (relevantUtilities.length > 0) {
742
- const totalCount = (db.query("SELECT COUNT(*) as c FROM project_utilities").get() as any)?.c || 0;
743
- const truncated = totalCount - relevantUtilities.length;
744
- output += `
745
- ### UTILITIES EXISTENTES (${relevantUtilities.length}${truncated > 0 ? ` [+${truncated} mais]` : ''})
746
- **REGRA DRY**: Reutilize ao inves de recriar. Importe do arquivo existente.
747
- ${relevantUtilities.map((u: any) => {
748
- const sig = u.signature ? ` ${u.signature}` : '';
749
- return `- **${u.utility_name}** [${u.utility_type}]${sig} <- \`${u.file_path}\``;
750
- }).join("\n")}
751
- `;
752
- }
753
- } catch { /* tabela pode nao existir ainda */ }
754
-
755
- // v8.3: Sessoes compostas (todas, nao so ultima)
756
- if (sessions.length > 0) {
757
- output += `
758
- ### HISTORICO DE SESSOES (${sessions.length})`;
759
- // Sessao mais recente: detalhes completos
760
- const latest = sessions[0];
761
- output += `\n**Ultima:** ${latest.summary}`;
762
- if (latest.next_steps) {
763
- try {
764
- const steps = JSON.parse(latest.next_steps);
765
- if (steps.length > 0) {
766
- output += `\n**Proximos passos:** ${steps.slice(0, 3).join("; ")}${steps.length > 3 ? ` [+${steps.length - 3} mais]` : ''}`;
767
- }
768
- } catch { /* ignore */ }
769
- }
770
- if (latest.blockers) {
771
- try {
772
- const blockers = JSON.parse(latest.blockers);
773
- if (blockers.length > 0) {
774
- output += `\n**Blockers:** ${blockers.join("; ")}`;
775
- }
776
- } catch { /* ignore */ }
777
- }
778
- // Sessoes anteriores: resumo de 1 linha
779
- if (sessions.length > 1) {
780
- output += `\n**Sessoes anteriores:**`;
781
- for (const s of sessions.slice(1)) {
782
- output += `\n - ${s.summary.substring(0, 100)}${s.summary.length > 100 ? '...' : ''} (${s.tasks_completed || 0} tasks)`;
783
- }
784
- }
785
- output += "\n";
786
- }
787
-
788
- // Decisoes do knowledge graph que afetam arquivos desta task
789
- if (graphDecisions.length > 0) {
790
- output += `
791
- ### DECISOES QUE AFETAM SEUS ARQUIVOS (${graphDecisions.length})
792
- ${graphDecisions.map((d: any) => `- **${d.title}**: ${d.decision}`).join("\n")}
793
- `;
794
- }
795
-
796
- // Contradicoes detectadas
797
- if (contradictions.length > 0) {
798
- output += `
799
- ### [!!] CONTRADICOES DETECTADAS (${contradictions.length})
800
- ${contradictions.map((c: any) => `- "${c.decision1}" <-> "${c.decision2}"`).join("\n")}
801
- Verifique estas contradicoes antes de prosseguir.
802
- `;
803
- }
804
-
805
- // v8.3: Patterns descobertos com indicador de truncamento
806
- if (discoveredPatterns.length > 0) {
807
- const patternsToShow = discoveredPatterns.slice(-10);
808
- output += `
809
- ### PATTERNS DESCOBERTOS (${discoveredPatterns.length})
810
- ${discoveredPatterns.length > 10 ? `[mostrando ultimos 10 de ${discoveredPatterns.length}]\n` : ''}${patternsToShow.map((p: any) => `- ${p.pattern}${p.source_task ? ` (Task #${p.source_task})` : ""}`).join("\n")}
811
- `;
812
- }
813
-
814
- // Stack
815
- if (project) {
816
- const stack = JSON.parse(project.stack);
817
- const allStackEntries = Object.entries(stack);
818
- const mainStack = allStackEntries.slice(0, 6);
819
- output += `
820
- ### STACK
821
- ${mainStack.map(([k, v]) => `${k}: ${v}`).join(" | ")}${allStackEntries.length > 6 ? ` [+${allStackEntries.length - 6} mais]` : ''}
822
- `;
823
- }
824
-
825
- // Bibliotecas do projeto
826
- if (libContexts.length > 0) {
827
- output += `
828
- ### BIBLIOTECAS
829
- ${libContexts.map((l: any) => `- ${l.lib_name}${l.version ? ` v${l.version}` : ""}`).join("\n")}
830
- `;
831
- }
832
-
833
- // Protocolo de retorno
834
- output += `
835
- ### RETORNO OBRIGATORIO
836
- \`\`\`json
837
- {"status": "completed|blocked", "summary": "...", "files_created": [], "files_modified": [], "reasoning": {"approach": "como abordou", "challenges": [], "recommendations": "para proximas tasks"}}
838
- \`\`\`
839
- `;
840
-
841
- // v8.3: Overall size cap com truncamento inteligente por secao
842
- if (output.length > MAX_CONTEXT_SIZE) {
843
- const sections = output.split('\n### ');
844
- let trimmed = sections[0]; // Sempre manter header
845
-
846
- for (let i = 1; i < sections.length; i++) {
847
- const candidate = trimmed + '\n### ' + sections[i];
848
- if (candidate.length > MAX_CONTEXT_SIZE - 200) {
849
- break;
850
- }
851
- trimmed = candidate;
852
- }
853
-
854
- const omittedSections = sections.length - trimmed.split('\n### ').length;
855
- if (omittedSections > 0) {
856
- trimmed += `\n\n[CONTEXTO TRUNCADO: ${omittedSections} secao(oes) omitida(s) por limite de ${MAX_CONTEXT_SIZE} chars. Use: context-export para contexto completo]`;
857
- }
858
-
859
- output = trimmed;
860
- }
861
-
862
- return output;
863
- }
864
-
865
- // v8.3: Filtrar decisoes relevantes com scoring melhorado
866
- function filterRelevantDecisions(decisions: any[], taskFiles: string[], maxCount: number): any[] {
867
- if (decisions.length === 0) return [];
868
- if (taskFiles.length === 0) return decisions.slice(0, maxCount);
869
-
870
- // Extrair keywords semanticas dos arquivos da task
871
- const fileExtensions = new Set(taskFiles.map(f => f.split('.').pop()?.toLowerCase()).filter(Boolean));
872
- const fileDirs = new Set(taskFiles.flatMap(f => f.split('/').slice(0, -1)).filter(Boolean));
873
-
874
- // Keywords de dominio para scoring contextual
875
- const domainKeywords: Record<string, string[]> = {
876
- frontend: ['component', 'page', 'layout', 'css', 'style', 'ui', 'react', 'next', 'hook'],
877
- backend: ['api', 'route', 'handler', 'middleware', 'server', 'endpoint', 'controller'],
878
- database: ['schema', 'migration', 'query', 'table', 'index', 'sql', 'model'],
879
- testing: ['test', 'spec', 'mock', 'fixture', 'assert', 'jest', 'vitest'],
880
- };
881
-
882
- const scored = decisions.map((d) => {
883
- let score = 0;
884
- const combined = `${d.decision || ''} ${d.title || ''} ${d.rationale || ''}`.toLowerCase();
885
-
886
- // Arquivo exato e diretorio (+10/+5)
887
- for (const file of taskFiles) {
888
- const fileName = file.split("/").pop() || file;
889
- const dirName = file.split("/").slice(-2, -1)[0] || "";
890
-
891
- if (combined.includes(fileName.toLowerCase())) score += 10;
892
- if (dirName && combined.includes(dirName.toLowerCase())) score += 5;
893
- }
894
-
895
- // v8.3: Extensao dos arquivos (+3)
896
- for (const ext of fileExtensions) {
897
- if (combined.includes(`.${ext}`)) { score += 3; break; }
898
- }
899
-
900
- // v8.3: Diretorio mencionado (+4)
901
- for (const dir of fileDirs) {
902
- if (combined.includes(dir.toLowerCase())) { score += 4; break; }
903
- }
904
-
905
- // v8.3: Keywords de dominio (+2)
906
- const taskCombined = taskFiles.join(' ').toLowerCase();
907
- for (const [, keywords] of Object.entries(domainKeywords)) {
908
- for (const kw of keywords) {
909
- if (combined.includes(kw) && taskCombined.includes(kw)) {
910
- score += 2;
911
- break;
912
- }
913
- }
914
- }
915
-
916
- // Recencia (+3/+1)
917
- const age = Date.now() - new Date(d.created_at).getTime();
918
- const hoursOld = age / (1000 * 60 * 60);
919
- if (hoursOld < 1) score += 3;
920
- else if (hoursOld < 24) score += 1;
921
-
922
- return { ...d, _relevanceScore: score };
923
- });
924
-
925
- return scored
926
- .sort((a, b) => b._relevanceScore - a._relevanceScore)
927
- .slice(0, maxCount)
928
- .map(({ _relevanceScore, ...d }) => d);
929
- }
930
-
931
- // v8.0: Filtrar standards relevantes para os arquivos da task
932
- function filterRelevantStandards(standards: any[], taskFiles: string[]): any[] {
933
- if (standards.length === 0) return [];
934
- if (taskFiles.length === 0) return standards;
935
-
936
- // Extrair extensoes e diretorios dos arquivos da task
937
- const extensions = new Set(taskFiles.map((f) => {
938
- const ext = f.split(".").pop();
939
- return ext ? `.${ext}` : "";
940
- }).filter(Boolean));
941
-
942
- const directories = new Set(taskFiles.map((f) => {
943
- const parts = f.split("/");
944
- return parts.length > 1 ? parts.slice(0, -1).join("/") : "";
945
- }).filter(Boolean));
946
-
947
- // Filtrar standards que se aplicam
948
- return standards.filter((s) => {
949
- // Standards de naming sempre aplicam
950
- if (s.category === "naming") return true;
951
-
952
- // Standards de code aplicam se mencionam extensao dos arquivos
953
- if (s.category === "code") {
954
- for (const ext of extensions) {
955
- if (s.rule?.includes(ext) || s.scope === "all") return true;
956
- }
957
- }
958
-
959
- // Standards de structure aplicam se mencionam diretorio
960
- if (s.category === "structure") {
961
- for (const dir of directories) {
962
- if (s.rule?.includes(dir)) return true;
963
- }
964
- return s.scope === "all";
965
- }
966
-
967
- // Outros standards: incluir se scope combina
968
- return true;
969
- });
970
- }
971
-
972
- export function contextDetail(section: string, json: boolean = false): void {
973
- initSchema();
974
- const db = getDb();
975
-
976
- const spec = db
977
- .query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
978
- .get() as any;
979
-
980
- if (!spec) {
981
- console.error("\nNenhuma feature ativa.\n");
982
- process.exit(1);
983
- }
359
+ const spec = resolveSpec(specId);
984
360
 
985
361
  // Encontrar task running (para contexto relevante)
986
362
  const runningTask = db.query(
@@ -1109,9 +485,7 @@ export function contextDetail(section: string, json: boolean = false): void {
1109
485
  }
1110
486
 
1111
487
  default:
1112
- console.error(`\nSecao invalida: ${section}`);
1113
- console.error("Secoes validas: standards, decisions, patterns, knowledge, architecture\n");
1114
- process.exit(1);
488
+ throw new CodexaError("Secao invalida: " + section + "\nSecoes validas: standards, decisions, patterns, knowledge, architecture");
1115
489
  }
1116
490
 
1117
491
  console.log(output || "\nNenhum dado encontrado.\n");
@@ -1160,8 +534,7 @@ export function recover(options: {
1160
534
  }
1161
535
 
1162
536
  if (!snapshot) {
1163
- console.error("\nNenhum snapshot para restaurar.\n");
1164
- process.exit(1);
537
+ throw new CodexaError("Nenhum snapshot para restaurar.");
1165
538
  }
1166
539
 
1167
540
  const data = JSON.parse(snapshot.data);
@@ -1232,8 +605,7 @@ export function recover(options: {
1232
605
  const snapshot = db.query("SELECT * FROM snapshots WHERE id = ?").get(snapshotId) as any;
1233
606
 
1234
607
  if (!snapshot) {
1235
- console.error(`\nSnapshot #${snapshotId} nao encontrado.\n`);
1236
- process.exit(1);
608
+ throw new CodexaError("Snapshot #" + snapshotId + " nao encontrado.");
1237
609
  }
1238
610
 
1239
611
  const data = JSON.parse(snapshot.data);