@codexa/cli 9.0.2 → 9.0.3

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,5 +1,5 @@
1
1
  import { getDb } from "../db/connection";
2
- import { initSchema, getPatternsForFiles, getLastSessionSummary, getSessionSummaries, getRelatedDecisions, findContradictions, getArchitecturalAnalysisForSpec, getUtilitiesForContext } from "../db/schema";
2
+ import { initSchema, getPatternsForFiles, getRelatedDecisions, getArchitecturalAnalysisForSpec, getUtilitiesForContext } from "../db/schema";
3
3
  import { getKnowledgeForTask } from "./knowledge";
4
4
  import { getLibContextsForAgent } from "./research";
5
5
  import pkg from "../package.json";
@@ -171,35 +171,6 @@ export function status(json: boolean = false): void {
171
171
  console.log(`\nDecisoes ativas: ${decisions.length}`);
172
172
  }
173
173
 
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
174
  console.log(`\n${"─".repeat(60)}`);
204
175
 
205
176
  // Sugerir proximo passo
@@ -375,13 +346,7 @@ export function getMinimalContextForSubagent(taskId: number): string {
375
346
  ORDER BY created_at DESC LIMIT 5`
376
347
  ).all(task.spec_id) as any[];
377
348
 
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)
349
+ // 3. Decisoes da task anterior (dependency direta)
385
350
  const dependsOn = task.depends_on ? JSON.parse(task.depends_on) : [];
386
351
  let depDecisions: any[] = [];
387
352
  if (dependsOn.length > 0) {
@@ -416,10 +381,6 @@ export function getMinimalContextForSubagent(taskId: number): string {
416
381
  output += `\n**BLOCKERS CRITICOS:**\n${criticalBlockers.map((b: any) => `[X] ${b.content}`).join("\n")}\n`;
417
382
  }
418
383
 
419
- if (contradictions.length > 0) {
420
- output += `\n**CONTRADICOES:**\n${contradictions.map((c: any) => `[!] "${c.decision1}" <-> "${c.decision2}"`).join("\n")}\n`;
421
- }
422
-
423
384
  if (depDecisions.length > 0) {
424
385
  output += `\n**Recomendacoes de tasks anteriores:**\n${depDecisions.map((d: any) => `- ${d.thought}`).join("\n")}\n`;
425
386
  }
@@ -441,17 +402,45 @@ export function getMinimalContextForSubagent(taskId: number): string {
441
402
  return output;
442
403
  }
443
404
 
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();
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
+ }
448
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 {
449
440
  const db = getDb();
450
441
 
451
442
  const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
452
- if (!task) {
453
- return "ERRO: Task nao encontrada";
454
- }
443
+ if (!task) return null;
455
444
 
456
445
  const spec = db.query("SELECT * FROM specs WHERE id = ?").get(task.spec_id) as any;
457
446
  const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(task.spec_id) as any;
@@ -480,27 +469,16 @@ export function getContextForSubagent(taskId: number): string {
480
469
  .all(domain) as any[];
481
470
  const relevantStandards = filterRelevantStandards(standards, taskFiles);
482
471
 
483
- // v8.3: Knowledge com TTL, caps e indicadores de truncamento
472
+ // Knowledge com caps e indicadores de truncamento
484
473
  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');
474
+ const allCriticalKnowledge = allKnowledge.filter((k: any) => k.severity === 'critical' || k.severity === 'warning');
494
475
  const criticalKnowledge = allCriticalKnowledge.slice(0, 20);
495
476
  const truncatedCritical = allCriticalKnowledge.length - criticalKnowledge.length;
496
- const allInfoKnowledge = effectiveKnowledge.filter((k: any) => k.severity === 'info');
477
+ const allInfoKnowledge = allKnowledge.filter((k: any) => k.severity === 'info');
497
478
  const infoKnowledge = allInfoKnowledge.slice(0, 10);
498
479
  const truncatedInfo = allInfoKnowledge.length - infoKnowledge.length;
499
480
 
500
- // v8.1: Contexto de produto (resumido - problema, usuarios, restricoes)
501
481
  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
482
  const patterns = getPatternsForFiles(taskFiles);
505
483
 
506
484
  // v8.2: Reasoning de tasks dependentes + todas tasks completas recentes
@@ -508,7 +486,6 @@ export function getContextForSubagent(taskId: number): string {
508
486
  const depReasoning: any[] = [];
509
487
  const seenTaskIds = new Set<number>();
510
488
 
511
- // 1. Reasoning de dependencias diretas (prioridade maxima)
512
489
  if (dependsOn.length > 0) {
513
490
  const placeholders = dependsOn.map(() => '?').join(',');
514
491
  const depTasks = db.query(
@@ -532,7 +509,6 @@ export function getContextForSubagent(taskId: number): string {
532
509
  }
533
510
  }
534
511
 
535
- // 2. Reasoning de TODAS tasks completas (recommendations e challenges de alta importancia)
536
512
  const completedTasks = db.query(
537
513
  `SELECT t.id, t.number FROM tasks t
538
514
  WHERE t.spec_id = ? AND t.status = 'done' AND t.id != ?
@@ -556,15 +532,10 @@ export function getContextForSubagent(taskId: number): string {
556
532
  }
557
533
  }
558
534
 
559
- // v8.1: Lib contexts do projeto (resumido - nomes e versoes)
560
535
  const libContexts = db.query(
561
536
  "SELECT lib_name, version FROM lib_contexts ORDER BY lib_name LIMIT 10"
562
537
  ).all() as any[];
563
538
 
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
539
  const graphDecisions: any[] = [];
569
540
  for (const file of taskFiles) {
570
541
  try {
@@ -577,138 +548,152 @@ export function getContextForSubagent(taskId: number): string {
577
548
  } catch { /* ignore if graph empty */ }
578
549
  }
579
550
 
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
551
  const discoveredPatterns = context?.patterns ? JSON.parse(context.patterns) : [];
588
552
 
589
- // ===== MONTAR OUTPUT ESTRUTURADO =====
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
+ }
590
561
 
591
- let output = `## CONTEXTO (Task #${task.number})
562
+ // ── Section Builders ──────────────────────────────────────────
592
563
 
593
- **Feature:** ${spec.name}
594
- **Objetivo:** ${context?.objective || "N/A"}
595
- **Arquivos:** ${taskFiles.join(", ") || "Nenhum especificado"}
596
- `;
564
+ function buildProductSection(data: ContextData): ContextSection | null {
565
+ if (!data.productContext) return null;
597
566
 
598
- // Contexto de produto
599
- if (productContext) {
600
- output += `
567
+ let content = `
601
568
  ### 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";
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 */ }
613
578
  }
579
+ content += "\n";
614
580
 
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
- }
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}`;
638
604
  }
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
- }
605
+ if (risks.length > risksToShow.length) {
606
+ content += `\n [+${risks.length - risksToShow.length} mais riscos]`;
652
607
  }
653
- } catch { /* ignore */ }
654
- }
655
- output += "\n";
608
+ }
609
+ } catch { /* ignore */ }
656
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
+ }
657
629
 
658
- // Standards
659
- const requiredStds = relevantStandards.filter((s: any) => s.enforcement === 'required');
660
- const recommendedStds = relevantStandards.filter((s: any) => s.enforcement === 'recommended');
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');
661
633
 
662
- output += `
663
- ### STANDARDS (${relevantStandards.length})`;
634
+ let content = `
635
+ ### STANDARDS (${data.relevantStandards.length})`;
664
636
  if (requiredStds.length > 0) {
665
- output += `\n**Obrigatorios:**\n${requiredStds.map((s: any) => `- ${s.rule}`).join("\n")}`;
637
+ content += `\n**Obrigatorios:**\n${requiredStds.map((s: any) => `- ${s.rule}`).join("\n")}`;
666
638
  }
667
639
  if (recommendedStds.length > 0) {
668
- output += `\n**Recomendados:**\n${recommendedStds.map((s: any) => `- ${s.rule}`).join("\n")}`;
640
+ content += `\n**Recomendados:**\n${recommendedStds.map((s: any) => `- ${s.rule}`).join("\n")}`;
669
641
  }
670
- if (relevantStandards.length === 0) {
671
- output += "\nNenhum standard aplicavel";
642
+ if (data.relevantStandards.length === 0) {
643
+ content += "\nNenhum standard aplicavel";
672
644
  }
673
- output += "\n";
645
+ content += "\n";
674
646
 
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"}
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"}
680
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;
681
661
 
682
- // Reasoning de tasks anteriores
683
- if (depReasoning.length > 0) {
684
- output += `
662
+ const content = `
685
663
  ### CONTEXTO DE TASKS ANTERIORES
686
- ${depReasoning.map((r: any) => `- [Task #${r.fromTask}/${r.category}] ${r.thought}`).join("\n")}
664
+ ${data.depReasoning.map((r: any) => `- [Task #${r.fromTask}/${r.category}] ${r.thought}`).join("\n")}
687
665
  `;
688
- }
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;
689
671
 
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")}
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")}
696
676
  `;
697
- }
677
+ return { name: "ALERTAS", content, priority: 2 };
678
+ }
679
+
680
+ function buildDiscoveriesSection(data: ContextData): ContextSection | null {
681
+ if (data.infoKnowledge.length === 0) return null;
698
682
 
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")}
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")}
704
686
  `;
705
- }
687
+ return { name: "DISCOVERIES", content, priority: 8 };
688
+ }
706
689
 
707
- // Patterns de implementacao matchados
708
- if (patterns.length > 0) {
709
- output += `
710
- ### PATTERNS DO PROJETO (${patterns.length})
711
- ${patterns.map((p: any) => {
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) => {
712
697
  const tmpl = p.template || "";
713
698
  const preview = tmpl.length > 200 ? tmpl.substring(0, 200) + `... [truncado de ${tmpl.length} chars]` : tmpl;
714
699
  return `- **${p.name}** (${p.category}): ${preview || p.description || "Sem template"}`;
@@ -716,17 +701,28 @@ ${patterns.map((p: any) => {
716
701
  `;
717
702
  }
718
703
 
719
- // v8.5: Utility Map (DRY prevention - Camada 1)
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 {
720
717
  try {
721
- const taskDirs = [...new Set(taskFiles.map((f: string) => {
718
+ const taskDirs = [...new Set(data.taskFiles.map((f: string) => {
722
719
  const parts = f.replace(/\\/g, "/").split("/");
723
720
  return parts.slice(0, -1).join("/");
724
721
  }).filter(Boolean))];
725
722
 
726
- const agentScope = task.agent?.split("-")[0] || undefined;
723
+ const agentScope = data.task.agent?.split("-")[0] || undefined;
727
724
  let relevantUtilities = getUtilitiesForContext(taskDirs, undefined, 15);
728
725
 
729
- // Se poucos resultados por diretorio, ampliar para scope do agent
730
726
  if (relevantUtilities.length < 5 && agentScope) {
731
727
  const scopeUtils = getUtilitiesForContext([], agentScope, 15);
732
728
  const existingKeys = new Set(relevantUtilities.map((u: any) => `${u.file_path}:${u.utility_name}`));
@@ -738,10 +734,11 @@ ${patterns.map((p: any) => {
738
734
  }
739
735
  }
740
736
 
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 += `
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 = `
745
742
  ### UTILITIES EXISTENTES (${relevantUtilities.length}${truncated > 0 ? ` [+${truncated} mais]` : ''})
746
743
  **REGRA DRY**: Reutilize ao inves de recriar. Importe do arquivo existente.
747
744
  ${relevantUtilities.map((u: any) => {
@@ -749,88 +746,61 @@ ${relevantUtilities.map((u: any) => {
749
746
  return `- **${u.utility_name}** [${u.utility_type}]${sig} <- \`${u.file_path}\``;
750
747
  }).join("\n")}
751
748
  `;
752
- }
749
+ return { name: "UTILITIES", content, priority: 8 };
753
750
  } catch { /* tabela pode nao existir ainda */ }
751
+ return null;
752
+ }
754
753
 
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
- }
754
+ function buildGraphSection(data: ContextData): ContextSection | null {
755
+ let content = "";
787
756
 
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")}
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")}
793
761
  `;
794
762
  }
795
763
 
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
- }
764
+ if (!content) return null;
765
+ return { name: "GRAPH", content, priority: 6 };
766
+ }
804
767
 
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
- }
768
+ function buildStackSection(data: ContextData): ContextSection | null {
769
+ let content = "";
813
770
 
814
- // Stack
815
- if (project) {
816
- const stack = JSON.parse(project.stack);
771
+ if (data.project) {
772
+ const stack = JSON.parse(data.project.stack);
817
773
  const allStackEntries = Object.entries(stack);
818
774
  const mainStack = allStackEntries.slice(0, 6);
819
- output += `
775
+ content += `
820
776
  ### STACK
821
777
  ${mainStack.map(([k, v]) => `${k}: ${v}`).join(" | ")}${allStackEntries.length > 6 ? ` [+${allStackEntries.length - 6} mais]` : ''}
822
778
  `;
823
779
  }
824
780
 
825
- // Bibliotecas do projeto
826
- if (libContexts.length > 0) {
827
- output += `
781
+ if (data.libContexts.length > 0) {
782
+ content += `
828
783
  ### BIBLIOTECAS
829
- ${libContexts.map((l: any) => `- ${l.lib_name}${l.version ? ` v${l.version}` : ""}`).join("\n")}
784
+ ${data.libContexts.map((l: any) => `- ${l.lib_name}${l.version ? ` v${l.version}` : ""}`).join("\n")}
830
785
  `;
831
786
  }
832
787
 
833
- // Protocolo de retorno
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)
834
804
  output += `
835
805
  ### RETORNO OBRIGATORIO
836
806
  \`\`\`json
@@ -840,18 +810,18 @@ ${libContexts.map((l: any) => `- ${l.lib_name}${l.version ? ` v${l.version}` : "
840
810
 
841
811
  // v8.3: Overall size cap com truncamento inteligente por secao
842
812
  if (output.length > MAX_CONTEXT_SIZE) {
843
- const sections = output.split('\n### ');
844
- let trimmed = sections[0]; // Sempre manter header
813
+ const parts = output.split('\n### ');
814
+ let trimmed = parts[0]; // Sempre manter header
845
815
 
846
- for (let i = 1; i < sections.length; i++) {
847
- const candidate = trimmed + '\n### ' + sections[i];
816
+ for (let i = 1; i < parts.length; i++) {
817
+ const candidate = trimmed + '\n### ' + parts[i];
848
818
  if (candidate.length > MAX_CONTEXT_SIZE - 200) {
849
819
  break;
850
820
  }
851
821
  trimmed = candidate;
852
822
  }
853
823
 
854
- const omittedSections = sections.length - trimmed.split('\n### ').length;
824
+ const omittedSections = parts.length - trimmed.split('\n### ').length;
855
825
  if (omittedSections > 0) {
856
826
  trimmed += `\n\n[CONTEXTO TRUNCADO: ${omittedSections} secao(oes) omitida(s) por limite de ${MAX_CONTEXT_SIZE} chars. Use: context-export para contexto completo]`;
857
827
  }
@@ -862,6 +832,38 @@ ${libContexts.map((l: any) => `- ${l.lib_name}${l.version ? ` v${l.version}` : "
862
832
  return output;
863
833
  }
864
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
+
865
867
  // v8.3: Filtrar decisoes relevantes com scoring melhorado
866
868
  function filterRelevantDecisions(decisions: any[], taskFiles: string[], maxCount: number): any[] {
867
869
  if (decisions.length === 0) return [];