@codexa/cli 9.0.3 → 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, getRelatedDecisions, 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}`);
@@ -191,20 +197,50 @@ export function status(json: boolean = false): void {
191
197
  console.log();
192
198
  }
193
199
 
194
- export function contextExport(options: { task?: string; json?: boolean; }): void {
195
- 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
+ }
196
216
 
197
- 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`);
198
220
 
199
- const spec = db
200
- .query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
201
- .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));
202
226
 
203
- if (!spec) {
204
- console.error("\nNenhuma feature ativa.\n");
205
- 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();
206
231
  }
207
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
+
208
244
  const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id) as any;
209
245
  const decisions = db
210
246
  .query("SELECT * FROM decisions WHERE spec_id = ? AND status = 'active'")
@@ -316,673 +352,11 @@ export function contextExport(options: { task?: string; json?: boolean; }): void
316
352
  console.log(JSON.stringify(exportData, null, 2));
317
353
  }
318
354
 
319
- // v9.0: Contexto minimo para subagent (max ~2KB)
320
- const MAX_MINIMAL_CONTEXT = 2048;
321
-
322
- export function getMinimalContextForSubagent(taskId: number): string {
355
+ export function contextDetail(section: string, json: boolean = false, specId?: string): void {
323
356
  initSchema();
324
357
  const db = getDb();
325
358
 
326
- const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
327
- if (!task) return "ERRO: Task nao encontrada";
328
-
329
- const spec = db.query("SELECT * FROM specs WHERE id = ?").get(task.spec_id) as any;
330
- const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(task.spec_id) as any;
331
-
332
- const taskFiles = task.files ? JSON.parse(task.files) : [];
333
- const domain = task.agent?.split("-")[0] || "all";
334
-
335
- // 1. Standards REQUIRED apenas (nao recommended)
336
- const requiredStandards = db.query(
337
- `SELECT rule FROM standards
338
- WHERE enforcement = 'required' AND (scope = 'all' OR scope = ?)
339
- LIMIT 10`
340
- ).all(domain) as any[];
341
-
342
- // 2. Blockers CRITICAL apenas
343
- const criticalBlockers = db.query(
344
- `SELECT content FROM knowledge
345
- WHERE spec_id = ? AND severity = 'critical'
346
- ORDER BY created_at DESC LIMIT 5`
347
- ).all(task.spec_id) as any[];
348
-
349
- // 3. Decisoes da task anterior (dependency direta)
350
- const dependsOn = task.depends_on ? JSON.parse(task.depends_on) : [];
351
- let depDecisions: any[] = [];
352
- if (dependsOn.length > 0) {
353
- const placeholders = dependsOn.map(() => '?').join(',');
354
- const depTasks = db.query(
355
- `SELECT id FROM tasks WHERE spec_id = ? AND number IN (${placeholders})`
356
- ).all(task.spec_id, ...dependsOn) as any[];
357
-
358
- for (const dt of depTasks) {
359
- const reasoning = db.query(
360
- `SELECT thought FROM reasoning_log
361
- WHERE spec_id = ? AND task_id = ? AND category = 'recommendation'
362
- ORDER BY created_at DESC LIMIT 2`
363
- ).all(task.spec_id, dt.id) as any[];
364
- depDecisions.push(...reasoning);
365
- }
366
- }
367
-
368
- // Montar output minimo
369
- let output = `## CONTEXTO MINIMO (Task #${task.number})
370
-
371
- **Feature:** ${spec.name}
372
- **Objetivo:** ${context?.objective || "N/A"}
373
- **Arquivos:** ${taskFiles.join(", ") || "Nenhum especificado"}
374
- `;
375
-
376
- if (requiredStandards.length > 0) {
377
- output += `\n**Standards (OBRIGATORIOS):**\n${requiredStandards.map((s: any) => `- ${s.rule}`).join("\n")}\n`;
378
- }
379
-
380
- if (criticalBlockers.length > 0) {
381
- output += `\n**BLOCKERS CRITICOS:**\n${criticalBlockers.map((b: any) => `[X] ${b.content}`).join("\n")}\n`;
382
- }
383
-
384
- if (depDecisions.length > 0) {
385
- output += `\n**Recomendacoes de tasks anteriores:**\n${depDecisions.map((d: any) => `- ${d.thought}`).join("\n")}\n`;
386
- }
387
-
388
- output += `
389
- **Contexto expandido**: Se precisar de mais contexto, execute:
390
- codexa context detail standards # Todos os standards
391
- codexa context detail decisions # Todas as decisoes
392
- codexa context detail patterns # Patterns do projeto
393
- codexa context detail knowledge # Knowledge completo
394
- codexa context detail architecture # Analise arquitetural
395
- `;
396
-
397
- // Truncar se exceder limite
398
- if (output.length > MAX_MINIMAL_CONTEXT) {
399
- output = output.substring(0, MAX_MINIMAL_CONTEXT - 100) + "\n\n[CONTEXTO TRUNCADO - use: codexa context detail <secao>]\n";
400
- }
401
-
402
- return output;
403
- }
404
-
405
- // ═══════════════════════════════════════════════════════════════
406
- // CONTEXT BUILDER (v9.0 — decomposed from v8.1 monolith)
407
- // ═══════════════════════════════════════════════════════════════
408
-
409
- interface ContextSection {
410
- name: string;
411
- content: string;
412
- priority: number; // Lower = kept during truncation
413
- }
414
-
415
- interface ContextData {
416
- db: any;
417
- task: any;
418
- spec: any;
419
- context: any;
420
- project: any;
421
- taskFiles: string[];
422
- domain: string;
423
- archAnalysis: any;
424
- decisions: any[];
425
- allDecisions: any[];
426
- relevantStandards: any[];
427
- criticalKnowledge: any[];
428
- truncatedCritical: number;
429
- infoKnowledge: any[];
430
- truncatedInfo: number;
431
- productContext: any;
432
- patterns: any[];
433
- depReasoning: any[];
434
- libContexts: any[];
435
- graphDecisions: any[];
436
- discoveredPatterns: any[];
437
- }
438
-
439
- function fetchContextData(taskId: number): ContextData | null {
440
- const db = getDb();
441
-
442
- const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
443
- if (!task) return null;
444
-
445
- const spec = db.query("SELECT * FROM specs WHERE id = ?").get(task.spec_id) as any;
446
- const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(task.spec_id) as any;
447
- const project = db.query("SELECT * FROM project WHERE id = 'default'").get() as any;
448
-
449
- // v8.4: Analise arquitetural (link explicito via analysis_id ou nome)
450
- const archAnalysis = getArchitecturalAnalysisForSpec(spec.name, task.spec_id);
451
-
452
- // Arquivos da task para filtrar contexto relevante
453
- const taskFiles = task.files ? JSON.parse(task.files) : [];
454
- const domain = task.agent?.split("-")[0] || "all";
455
-
456
- // Decisoes relevantes (max 8, priorizando as que mencionam arquivos da task)
457
- const allDecisions = db
458
- .query("SELECT * FROM decisions WHERE spec_id = ? AND status = 'active' ORDER BY created_at DESC")
459
- .all(task.spec_id) as any[];
460
- const decisions = filterRelevantDecisions(allDecisions, taskFiles, 8);
461
-
462
- // Standards required + recommended que se aplicam aos arquivos
463
- const standards = db
464
- .query(
465
- `SELECT * FROM standards
466
- WHERE (scope = 'all' OR scope = ?)
467
- ORDER BY enforcement DESC, category`
468
- )
469
- .all(domain) as any[];
470
- const relevantStandards = filterRelevantStandards(standards, taskFiles);
471
-
472
- // Knowledge com caps e indicadores de truncamento
473
- const allKnowledge = getKnowledgeForTask(task.spec_id, taskId);
474
- const allCriticalKnowledge = allKnowledge.filter((k: any) => k.severity === 'critical' || k.severity === 'warning');
475
- const criticalKnowledge = allCriticalKnowledge.slice(0, 20);
476
- const truncatedCritical = allCriticalKnowledge.length - criticalKnowledge.length;
477
- const allInfoKnowledge = allKnowledge.filter((k: any) => k.severity === 'info');
478
- const infoKnowledge = allInfoKnowledge.slice(0, 10);
479
- const truncatedInfo = allInfoKnowledge.length - infoKnowledge.length;
480
-
481
- const productContext = db.query("SELECT * FROM product_context WHERE id = 'default'").get() as any;
482
- const patterns = getPatternsForFiles(taskFiles);
483
-
484
- // v8.2: Reasoning de tasks dependentes + todas tasks completas recentes
485
- const dependsOn = task.depends_on ? JSON.parse(task.depends_on) : [];
486
- const depReasoning: any[] = [];
487
- const seenTaskIds = new Set<number>();
488
-
489
- if (dependsOn.length > 0) {
490
- const placeholders = dependsOn.map(() => '?').join(',');
491
- const depTasks = db.query(
492
- `SELECT id, number FROM tasks WHERE spec_id = ? AND number IN (${placeholders})`
493
- ).all(task.spec_id, ...dependsOn) as any[];
494
-
495
- for (const depTask of depTasks) {
496
- seenTaskIds.add(depTask.id);
497
- const reasoning = db.query(
498
- `SELECT category, thought FROM reasoning_log
499
- WHERE spec_id = ? AND task_id = ?
500
- AND category IN ('recommendation', 'decision', 'challenge')
501
- ORDER BY
502
- CASE importance WHEN 'critical' THEN 1 WHEN 'high' THEN 2 ELSE 3 END,
503
- created_at DESC
504
- LIMIT 3`
505
- ).all(task.spec_id, depTask.id) as any[];
506
- for (const r of reasoning) {
507
- depReasoning.push({ ...r, fromTask: depTask.number });
508
- }
509
- }
510
- }
511
-
512
- const completedTasks = db.query(
513
- `SELECT t.id, t.number FROM tasks t
514
- WHERE t.spec_id = ? AND t.status = 'done' AND t.id != ?
515
- ORDER BY t.completed_at DESC LIMIT 10`
516
- ).all(task.spec_id, taskId) as any[];
517
-
518
- for (const ct of completedTasks) {
519
- if (seenTaskIds.has(ct.id)) continue;
520
- const reasoning = db.query(
521
- `SELECT category, thought FROM reasoning_log
522
- WHERE spec_id = ? AND task_id = ?
523
- AND category IN ('recommendation', 'challenge')
524
- AND importance IN ('critical', 'high')
525
- ORDER BY
526
- CASE importance WHEN 'critical' THEN 1 ELSE 2 END,
527
- created_at DESC
528
- LIMIT 2`
529
- ).all(task.spec_id, ct.id) as any[];
530
- for (const r of reasoning) {
531
- depReasoning.push({ ...r, fromTask: ct.number });
532
- }
533
- }
534
-
535
- const libContexts = db.query(
536
- "SELECT lib_name, version FROM lib_contexts ORDER BY lib_name LIMIT 10"
537
- ).all() as any[];
538
-
539
- const graphDecisions: any[] = [];
540
- for (const file of taskFiles) {
541
- try {
542
- const related = getRelatedDecisions(file, "file");
543
- for (const d of related) {
544
- if (!graphDecisions.find((gd: any) => gd.id === d.id)) {
545
- graphDecisions.push(d);
546
- }
547
- }
548
- } catch { /* ignore if graph empty */ }
549
- }
550
-
551
- const discoveredPatterns = context?.patterns ? JSON.parse(context.patterns) : [];
552
-
553
- return {
554
- db, task, spec, context, project, taskFiles, domain, archAnalysis,
555
- decisions, allDecisions, relevantStandards,
556
- criticalKnowledge, truncatedCritical, infoKnowledge, truncatedInfo,
557
- productContext, patterns, depReasoning, libContexts,
558
- graphDecisions, discoveredPatterns,
559
- };
560
- }
561
-
562
- // ── Section Builders ──────────────────────────────────────────
563
-
564
- function buildProductSection(data: ContextData): ContextSection | null {
565
- if (!data.productContext) return null;
566
-
567
- let content = `
568
- ### PRODUTO
569
- - **Problema:** ${data.productContext.problem || "N/A"}
570
- - **Usuarios:** ${data.productContext.target_users || "N/A"}`;
571
- if (data.productContext.constraints) {
572
- try {
573
- const constraints = JSON.parse(data.productContext.constraints);
574
- if (constraints.length > 0) {
575
- content += `\n- **Restricoes:** ${constraints.slice(0, 3).join("; ")}${constraints.length > 3 ? ` [+${constraints.length - 3} mais]` : ''}`;
576
- }
577
- } catch { /* ignore parse errors */ }
578
- }
579
- content += "\n";
580
-
581
- return { name: "PRODUTO", content, priority: 7 };
582
- }
583
-
584
- function buildArchitectureSection(data: ContextData): ContextSection | null {
585
- if (!data.archAnalysis) return null;
586
-
587
- let content = `
588
- ### ARQUITETURA (${data.archAnalysis.id})`;
589
- if (data.archAnalysis.approach) {
590
- const approachPreview = data.archAnalysis.approach.length > 500
591
- ? data.archAnalysis.approach.substring(0, 500) + "..."
592
- : data.archAnalysis.approach;
593
- content += `\n**Abordagem:** ${approachPreview}`;
594
- }
595
- if (data.archAnalysis.risks) {
596
- try {
597
- const risks = JSON.parse(data.archAnalysis.risks);
598
- if (risks.length > 0) {
599
- const highRisks = risks.filter((r: any) => r.impact === 'high' || r.probability === 'high');
600
- const risksToShow = highRisks.length > 0 ? highRisks.slice(0, 3) : risks.slice(0, 3);
601
- content += `\n**Riscos:**`;
602
- for (const r of risksToShow) {
603
- content += `\n - ${r.description} (${r.probability}/${r.impact}) -> ${r.mitigation}`;
604
- }
605
- if (risks.length > risksToShow.length) {
606
- content += `\n [+${risks.length - risksToShow.length} mais riscos]`;
607
- }
608
- }
609
- } catch { /* ignore */ }
610
- }
611
- if (data.archAnalysis.decisions) {
612
- try {
613
- const archDecisions = JSON.parse(data.archAnalysis.decisions);
614
- if (archDecisions.length > 0) {
615
- content += `\n**Decisoes arquiteturais:**`;
616
- for (const d of archDecisions.slice(0, 5)) {
617
- content += `\n - ${d.decision}: ${d.rationale || ''}`;
618
- }
619
- if (archDecisions.length > 5) {
620
- content += `\n [+${archDecisions.length - 5} mais]`;
621
- }
622
- }
623
- } catch { /* ignore */ }
624
- }
625
- content += "\n";
626
-
627
- return { name: "ARQUITETURA", content, priority: 3 };
628
- }
629
-
630
- function buildStandardsSection(data: ContextData): ContextSection {
631
- const requiredStds = data.relevantStandards.filter((s: any) => s.enforcement === 'required');
632
- const recommendedStds = data.relevantStandards.filter((s: any) => s.enforcement === 'recommended');
633
-
634
- let content = `
635
- ### STANDARDS (${data.relevantStandards.length})`;
636
- if (requiredStds.length > 0) {
637
- content += `\n**Obrigatorios:**\n${requiredStds.map((s: any) => `- ${s.rule}`).join("\n")}`;
638
- }
639
- if (recommendedStds.length > 0) {
640
- content += `\n**Recomendados:**\n${recommendedStds.map((s: any) => `- ${s.rule}`).join("\n")}`;
641
- }
642
- if (data.relevantStandards.length === 0) {
643
- content += "\nNenhum standard aplicavel";
644
- }
645
- content += "\n";
646
-
647
- return { name: "STANDARDS", content, priority: 1 };
648
- }
649
-
650
- function buildDecisionsSection(data: ContextData): ContextSection {
651
- const truncatedDecisions = data.allDecisions.length - data.decisions.length;
652
- const content = `
653
- ### DECISOES (${data.decisions.length}${truncatedDecisions > 0 ? ` [+${truncatedDecisions} mais - use: decisions list]` : ''})
654
- ${data.decisions.length > 0 ? data.decisions.map((d) => `- **${d.title}**: ${d.decision}`).join("\n") : "Nenhuma"}
655
- `;
656
- return { name: "DECISOES", content, priority: 4 };
657
- }
658
-
659
- function buildReasoningSection(data: ContextData): ContextSection | null {
660
- if (data.depReasoning.length === 0) return null;
661
-
662
- const content = `
663
- ### CONTEXTO DE TASKS ANTERIORES
664
- ${data.depReasoning.map((r: any) => `- [Task #${r.fromTask}/${r.category}] ${r.thought}`).join("\n")}
665
- `;
666
- return { name: "CONTEXTO DE TASKS ANTERIORES", content, priority: 5 };
667
- }
668
-
669
- function buildAlertsSection(data: ContextData): ContextSection | null {
670
- if (data.criticalKnowledge.length === 0) return null;
671
-
672
- const severityIcon: Record<string, string> = { warning: "!!", critical: "XX" };
673
- const content = `
674
- ### ALERTAS (${data.criticalKnowledge.length}${data.truncatedCritical > 0 ? ` [+${data.truncatedCritical} mais - use: knowledge list --severity warning]` : ''})
675
- ${data.criticalKnowledge.map((k: any) => `[${severityIcon[k.severity] || "!"}] ${k.content}`).join("\n")}
676
- `;
677
- return { name: "ALERTAS", content, priority: 2 };
678
- }
679
-
680
- function buildDiscoveriesSection(data: ContextData): ContextSection | null {
681
- if (data.infoKnowledge.length === 0) return null;
682
-
683
- const content = `
684
- ### DISCOVERIES DE TASKS ANTERIORES (${data.infoKnowledge.length}${data.truncatedInfo > 0 ? ` [+${data.truncatedInfo} mais - use: knowledge list]` : ''})
685
- ${data.infoKnowledge.map((k: any) => `- ${k.content}`).join("\n")}
686
- `;
687
- return { name: "DISCOVERIES", content, priority: 8 };
688
- }
689
-
690
- function buildPatternsSection(data: ContextData): ContextSection | null {
691
- let content = "";
692
-
693
- if (data.patterns.length > 0) {
694
- content += `
695
- ### PATTERNS DO PROJETO (${data.patterns.length})
696
- ${data.patterns.map((p: any) => {
697
- const tmpl = p.template || "";
698
- const preview = tmpl.length > 200 ? tmpl.substring(0, 200) + `... [truncado de ${tmpl.length} chars]` : tmpl;
699
- return `- **${p.name}** (${p.category}): ${preview || p.description || "Sem template"}`;
700
- }).join("\n")}
701
- `;
702
- }
703
-
704
- if (data.discoveredPatterns.length > 0) {
705
- const patternsToShow = data.discoveredPatterns.slice(-10);
706
- content += `
707
- ### PATTERNS DESCOBERTOS (${data.discoveredPatterns.length})
708
- ${data.discoveredPatterns.length > 10 ? `[mostrando ultimos 10 de ${data.discoveredPatterns.length}]\n` : ''}${patternsToShow.map((p: any) => `- ${p.pattern}${p.source_task ? ` (Task #${p.source_task})` : ""}`).join("\n")}
709
- `;
710
- }
711
-
712
- if (!content) return null;
713
- return { name: "PATTERNS", content, priority: 9 };
714
- }
715
-
716
- function buildUtilitiesSection(data: ContextData): ContextSection | null {
717
- try {
718
- const taskDirs = [...new Set(data.taskFiles.map((f: string) => {
719
- const parts = f.replace(/\\/g, "/").split("/");
720
- return parts.slice(0, -1).join("/");
721
- }).filter(Boolean))];
722
-
723
- const agentScope = data.task.agent?.split("-")[0] || undefined;
724
- let relevantUtilities = getUtilitiesForContext(taskDirs, undefined, 15);
725
-
726
- if (relevantUtilities.length < 5 && agentScope) {
727
- const scopeUtils = getUtilitiesForContext([], agentScope, 15);
728
- const existingKeys = new Set(relevantUtilities.map((u: any) => `${u.file_path}:${u.utility_name}`));
729
- for (const u of scopeUtils) {
730
- if (!existingKeys.has(`${u.file_path}:${u.utility_name}`)) {
731
- relevantUtilities.push(u);
732
- }
733
- if (relevantUtilities.length >= 15) break;
734
- }
735
- }
736
-
737
- if (relevantUtilities.length === 0) return null;
738
-
739
- const totalCount = (data.db.query("SELECT COUNT(*) as c FROM project_utilities").get() as any)?.c || 0;
740
- const truncated = totalCount - relevantUtilities.length;
741
- const content = `
742
- ### UTILITIES EXISTENTES (${relevantUtilities.length}${truncated > 0 ? ` [+${truncated} mais]` : ''})
743
- **REGRA DRY**: Reutilize ao inves de recriar. Importe do arquivo existente.
744
- ${relevantUtilities.map((u: any) => {
745
- const sig = u.signature ? ` ${u.signature}` : '';
746
- return `- **${u.utility_name}** [${u.utility_type}]${sig} <- \`${u.file_path}\``;
747
- }).join("\n")}
748
- `;
749
- return { name: "UTILITIES", content, priority: 8 };
750
- } catch { /* tabela pode nao existir ainda */ }
751
- return null;
752
- }
753
-
754
- function buildGraphSection(data: ContextData): ContextSection | null {
755
- let content = "";
756
-
757
- if (data.graphDecisions.length > 0) {
758
- content += `
759
- ### DECISOES QUE AFETAM SEUS ARQUIVOS (${data.graphDecisions.length})
760
- ${data.graphDecisions.map((d: any) => `- **${d.title}**: ${d.decision}`).join("\n")}
761
- `;
762
- }
763
-
764
- if (!content) return null;
765
- return { name: "GRAPH", content, priority: 6 };
766
- }
767
-
768
- function buildStackSection(data: ContextData): ContextSection | null {
769
- let content = "";
770
-
771
- if (data.project) {
772
- const stack = JSON.parse(data.project.stack);
773
- const allStackEntries = Object.entries(stack);
774
- const mainStack = allStackEntries.slice(0, 6);
775
- content += `
776
- ### STACK
777
- ${mainStack.map(([k, v]) => `${k}: ${v}`).join(" | ")}${allStackEntries.length > 6 ? ` [+${allStackEntries.length - 6} mais]` : ''}
778
- `;
779
- }
780
-
781
- if (data.libContexts.length > 0) {
782
- content += `
783
- ### BIBLIOTECAS
784
- ${data.libContexts.map((l: any) => `- ${l.lib_name}${l.version ? ` v${l.version}` : ""}`).join("\n")}
785
- `;
786
- }
787
-
788
- if (!content) return null;
789
- return { name: "STACK", content, priority: 11 };
790
- }
791
-
792
- // ── Assembly + Truncation ─────────────────────────────────────
793
-
794
- function assembleSections(header: string, sections: ContextSection[]): string {
795
- // Sort by priority (lower = higher priority, kept during truncation)
796
- const sorted = [...sections].sort((a, b) => a.priority - b.priority);
797
-
798
- let output = header;
799
- for (const section of sorted) {
800
- output += section.content;
801
- }
802
-
803
- // Protocolo de retorno (sempre incluido)
804
- output += `
805
- ### RETORNO OBRIGATORIO
806
- \`\`\`json
807
- {"status": "completed|blocked", "summary": "...", "files_created": [], "files_modified": [], "reasoning": {"approach": "como abordou", "challenges": [], "recommendations": "para proximas tasks"}}
808
- \`\`\`
809
- `;
810
-
811
- // v8.3: Overall size cap com truncamento inteligente por secao
812
- if (output.length > MAX_CONTEXT_SIZE) {
813
- const parts = output.split('\n### ');
814
- let trimmed = parts[0]; // Sempre manter header
815
-
816
- for (let i = 1; i < parts.length; i++) {
817
- const candidate = trimmed + '\n### ' + parts[i];
818
- if (candidate.length > MAX_CONTEXT_SIZE - 200) {
819
- break;
820
- }
821
- trimmed = candidate;
822
- }
823
-
824
- const omittedSections = parts.length - trimmed.split('\n### ').length;
825
- if (omittedSections > 0) {
826
- trimmed += `\n\n[CONTEXTO TRUNCADO: ${omittedSections} secao(oes) omitida(s) por limite de ${MAX_CONTEXT_SIZE} chars. Use: context-export para contexto completo]`;
827
- }
828
-
829
- output = trimmed;
830
- }
831
-
832
- return output;
833
- }
834
-
835
- // ── Main Entry Point ──────────────────────────────────────────
836
-
837
- export function getContextForSubagent(taskId: number): string {
838
- initSchema();
839
-
840
- const data = fetchContextData(taskId);
841
- if (!data) return "ERRO: Task nao encontrada";
842
-
843
- const header = `## CONTEXTO (Task #${data.task.number})
844
-
845
- **Feature:** ${data.spec.name}
846
- **Objetivo:** ${data.context?.objective || "N/A"}
847
- **Arquivos:** ${data.taskFiles.join(", ") || "Nenhum especificado"}
848
- `;
849
-
850
- const sections = [
851
- buildProductSection(data),
852
- buildArchitectureSection(data),
853
- buildStandardsSection(data),
854
- buildDecisionsSection(data),
855
- buildReasoningSection(data),
856
- buildAlertsSection(data),
857
- buildDiscoveriesSection(data),
858
- buildPatternsSection(data),
859
- buildUtilitiesSection(data),
860
- buildGraphSection(data),
861
- buildStackSection(data),
862
- ].filter((s): s is ContextSection => s !== null);
863
-
864
- return assembleSections(header, sections);
865
- }
866
-
867
- // v8.3: Filtrar decisoes relevantes com scoring melhorado
868
- function filterRelevantDecisions(decisions: any[], taskFiles: string[], maxCount: number): any[] {
869
- if (decisions.length === 0) return [];
870
- if (taskFiles.length === 0) return decisions.slice(0, maxCount);
871
-
872
- // Extrair keywords semanticas dos arquivos da task
873
- const fileExtensions = new Set(taskFiles.map(f => f.split('.').pop()?.toLowerCase()).filter(Boolean));
874
- const fileDirs = new Set(taskFiles.flatMap(f => f.split('/').slice(0, -1)).filter(Boolean));
875
-
876
- // Keywords de dominio para scoring contextual
877
- const domainKeywords: Record<string, string[]> = {
878
- frontend: ['component', 'page', 'layout', 'css', 'style', 'ui', 'react', 'next', 'hook'],
879
- backend: ['api', 'route', 'handler', 'middleware', 'server', 'endpoint', 'controller'],
880
- database: ['schema', 'migration', 'query', 'table', 'index', 'sql', 'model'],
881
- testing: ['test', 'spec', 'mock', 'fixture', 'assert', 'jest', 'vitest'],
882
- };
883
-
884
- const scored = decisions.map((d) => {
885
- let score = 0;
886
- const combined = `${d.decision || ''} ${d.title || ''} ${d.rationale || ''}`.toLowerCase();
887
-
888
- // Arquivo exato e diretorio (+10/+5)
889
- for (const file of taskFiles) {
890
- const fileName = file.split("/").pop() || file;
891
- const dirName = file.split("/").slice(-2, -1)[0] || "";
892
-
893
- if (combined.includes(fileName.toLowerCase())) score += 10;
894
- if (dirName && combined.includes(dirName.toLowerCase())) score += 5;
895
- }
896
-
897
- // v8.3: Extensao dos arquivos (+3)
898
- for (const ext of fileExtensions) {
899
- if (combined.includes(`.${ext}`)) { score += 3; break; }
900
- }
901
-
902
- // v8.3: Diretorio mencionado (+4)
903
- for (const dir of fileDirs) {
904
- if (combined.includes(dir.toLowerCase())) { score += 4; break; }
905
- }
906
-
907
- // v8.3: Keywords de dominio (+2)
908
- const taskCombined = taskFiles.join(' ').toLowerCase();
909
- for (const [, keywords] of Object.entries(domainKeywords)) {
910
- for (const kw of keywords) {
911
- if (combined.includes(kw) && taskCombined.includes(kw)) {
912
- score += 2;
913
- break;
914
- }
915
- }
916
- }
917
-
918
- // Recencia (+3/+1)
919
- const age = Date.now() - new Date(d.created_at).getTime();
920
- const hoursOld = age / (1000 * 60 * 60);
921
- if (hoursOld < 1) score += 3;
922
- else if (hoursOld < 24) score += 1;
923
-
924
- return { ...d, _relevanceScore: score };
925
- });
926
-
927
- return scored
928
- .sort((a, b) => b._relevanceScore - a._relevanceScore)
929
- .slice(0, maxCount)
930
- .map(({ _relevanceScore, ...d }) => d);
931
- }
932
-
933
- // v8.0: Filtrar standards relevantes para os arquivos da task
934
- function filterRelevantStandards(standards: any[], taskFiles: string[]): any[] {
935
- if (standards.length === 0) return [];
936
- if (taskFiles.length === 0) return standards;
937
-
938
- // Extrair extensoes e diretorios dos arquivos da task
939
- const extensions = new Set(taskFiles.map((f) => {
940
- const ext = f.split(".").pop();
941
- return ext ? `.${ext}` : "";
942
- }).filter(Boolean));
943
-
944
- const directories = new Set(taskFiles.map((f) => {
945
- const parts = f.split("/");
946
- return parts.length > 1 ? parts.slice(0, -1).join("/") : "";
947
- }).filter(Boolean));
948
-
949
- // Filtrar standards que se aplicam
950
- return standards.filter((s) => {
951
- // Standards de naming sempre aplicam
952
- if (s.category === "naming") return true;
953
-
954
- // Standards de code aplicam se mencionam extensao dos arquivos
955
- if (s.category === "code") {
956
- for (const ext of extensions) {
957
- if (s.rule?.includes(ext) || s.scope === "all") return true;
958
- }
959
- }
960
-
961
- // Standards de structure aplicam se mencionam diretorio
962
- if (s.category === "structure") {
963
- for (const dir of directories) {
964
- if (s.rule?.includes(dir)) return true;
965
- }
966
- return s.scope === "all";
967
- }
968
-
969
- // Outros standards: incluir se scope combina
970
- return true;
971
- });
972
- }
973
-
974
- export function contextDetail(section: string, json: boolean = false): void {
975
- initSchema();
976
- const db = getDb();
977
-
978
- const spec = db
979
- .query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
980
- .get() as any;
981
-
982
- if (!spec) {
983
- console.error("\nNenhuma feature ativa.\n");
984
- process.exit(1);
985
- }
359
+ const spec = resolveSpec(specId);
986
360
 
987
361
  // Encontrar task running (para contexto relevante)
988
362
  const runningTask = db.query(
@@ -1111,9 +485,7 @@ export function contextDetail(section: string, json: boolean = false): void {
1111
485
  }
1112
486
 
1113
487
  default:
1114
- console.error(`\nSecao invalida: ${section}`);
1115
- console.error("Secoes validas: standards, decisions, patterns, knowledge, architecture\n");
1116
- process.exit(1);
488
+ throw new CodexaError("Secao invalida: " + section + "\nSecoes validas: standards, decisions, patterns, knowledge, architecture");
1117
489
  }
1118
490
 
1119
491
  console.log(output || "\nNenhum dado encontrado.\n");
@@ -1162,8 +534,7 @@ export function recover(options: {
1162
534
  }
1163
535
 
1164
536
  if (!snapshot) {
1165
- console.error("\nNenhum snapshot para restaurar.\n");
1166
- process.exit(1);
537
+ throw new CodexaError("Nenhum snapshot para restaurar.");
1167
538
  }
1168
539
 
1169
540
  const data = JSON.parse(snapshot.data);
@@ -1234,8 +605,7 @@ export function recover(options: {
1234
605
  const snapshot = db.query("SELECT * FROM snapshots WHERE id = ?").get(snapshotId) as any;
1235
606
 
1236
607
  if (!snapshot) {
1237
- console.error(`\nSnapshot #${snapshotId} nao encontrado.\n`);
1238
- process.exit(1);
608
+ throw new CodexaError("Snapshot #" + snapshotId + " nao encontrado.");
1239
609
  }
1240
610
 
1241
611
  const data = JSON.parse(snapshot.data);