@codexa/cli 8.5.0 → 8.6.9

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/task.ts CHANGED
@@ -1,624 +1,624 @@
1
- import { getDb } from "../db/connection";
2
- import { initSchema, getPatternsForFiles, getPatternsByScope, addSessionSummary, getRecentReasoning } from "../db/schema";
3
- import { enforceGate } from "../gates/validator";
4
- import { parseSubagentReturn, formatValidationErrors } from "../protocol/subagent-protocol";
5
- import { processSubagentReturn, formatProcessResult } from "../protocol/process-return";
6
- import { getContextForSubagent } from "./utils";
7
- import { getUnreadKnowledgeForTask } from "./knowledge";
8
-
9
- export function taskNext(json: boolean = false): void {
10
- initSchema();
11
-
12
- const db = getDb();
13
- const spec = db
14
- .query("SELECT * FROM specs WHERE phase = 'implementing' ORDER BY created_at DESC LIMIT 1")
15
- .get() as any;
16
-
17
- if (!spec) {
18
- if (json) {
19
- console.log(JSON.stringify({ available: [], message: "Nenhuma feature em implementacao" }));
20
- } else {
21
- console.error("\nNenhuma feature em fase de implementacao.");
22
- console.error("Aprove o plano com: check approve\n");
23
- }
24
- process.exit(1);
25
- }
26
-
27
- // Buscar tasks pendentes cujas dependencias estao todas concluidas
28
- const allTasks = db
29
- .query("SELECT * FROM tasks WHERE spec_id = ? ORDER BY number")
30
- .all(spec.id) as any[];
31
-
32
- const available: any[] = [];
33
-
34
- for (const task of allTasks) {
35
- if (task.status !== "pending") continue;
36
-
37
- // Verificar dependencias
38
- const deps = task.depends_on ? JSON.parse(task.depends_on) : [];
39
- const allDepsDone = deps.every((depNum: number) => {
40
- const depTask = allTasks.find((t) => t.number === depNum);
41
- return depTask?.status === "done";
42
- });
43
-
44
- if (allDepsDone) {
45
- available.push(task);
46
- }
47
- }
48
-
49
- if (json) {
50
- console.log(JSON.stringify({ available }));
51
- return;
52
- }
53
-
54
- if (available.length === 0) {
55
- // Verificar se todas estao concluidas
56
- const pending = allTasks.filter((t) => t.status !== "done");
57
- if (pending.length === 0) {
58
- console.log("\nTodas as tasks foram concluidas!");
59
- console.log("Inicie o review com: review start\n");
60
- } else {
61
- console.log("\nNenhuma task disponivel no momento.");
62
- console.log(`Tasks em execucao ou bloqueadas: ${pending.length}`);
63
- console.log("Aguarde as tasks em andamento serem concluidas.\n");
64
- }
65
- return;
66
- }
67
-
68
- console.log(`\nTasks disponiveis para execucao (${available.length}):`);
69
- console.log(`${"─".repeat(50)}`);
70
-
71
- const parallelizable = available.filter((t) => t.can_parallel);
72
- const sequential = available.filter((t) => !t.can_parallel);
73
-
74
- if (parallelizable.length > 0) {
75
- if (parallelizable.length > 1) {
76
- console.log(`\nPodem executar em PARALELO:`);
77
- } else {
78
- console.log(`\nDisponivel:`);
79
- }
80
- const ids = parallelizable.map((t) => t.id).join(",");
81
- for (const task of parallelizable) {
82
- const files = task.files ? JSON.parse(task.files) : [];
83
- console.log(` #${task.number}: ${task.name} (${task.agent || "geral"}) [id: ${task.id}]`);
84
- if (files.length > 0) console.log(` Arquivos: ${files.join(", ")}`);
85
- }
86
- if (parallelizable.length > 1) {
87
- console.log(`\n Para iniciar todas: task start ${ids}`);
88
- } else {
89
- console.log(`\n Para iniciar: task start ${ids}`);
90
- }
91
- }
92
-
93
- if (sequential.length > 0) {
94
- console.log(`\nSequenciais (dependencias pendentes):`);
95
- for (const task of sequential) {
96
- console.log(` #${task.number}: ${task.name} (${task.agent || "geral"})`);
97
- }
98
- }
99
-
100
- console.log(`\n${"─".repeat(50)}`);
101
- console.log(`Use: task start <id> ou task start <id1>,<id2>,...\n`);
102
- }
103
-
104
- export function taskStart(ids: string, json: boolean = false): void {
105
- initSchema();
106
- enforceGate("task-start");
107
-
108
- const db = getDb();
109
- const now = new Date().toISOString();
110
-
111
- const spec = db
112
- .query("SELECT * FROM specs WHERE phase = 'implementing' ORDER BY created_at DESC LIMIT 1")
113
- .get() as any;
114
-
115
- const taskIds = ids.split(",").map((s) => parseInt(s.trim()));
116
- const startedTasks: any[] = [];
117
-
118
- for (const taskId of taskIds) {
119
- const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
120
-
121
- if (!task) {
122
- console.error(`\nTask #${taskId} nao encontrada.\n`);
123
- process.exit(2);
124
- }
125
-
126
- if (task.status !== "pending") {
127
- console.error(`\nTask #${task.number} nao esta pendente (status: ${task.status}).\n`);
128
- process.exit(2);
129
- }
130
-
131
- // Validar dependencias para esta task especifica
132
- enforceGate("task-start", { taskId });
133
-
134
- // Marcar como running
135
- db.run("UPDATE tasks SET status = 'running' WHERE id = ?", [taskId]);
136
-
137
- startedTasks.push(task);
138
- }
139
-
140
- // Atualizar contexto
141
- db.run("UPDATE context SET current_task = ?, updated_at = ? WHERE spec_id = ?", [
142
- startedTasks[0].number,
143
- now,
144
- spec.id,
145
- ]);
146
-
147
- if (json) {
148
- // NOVO: Incluir contexto COMPLETO para cada task
149
- const contexts = startedTasks.map((task) => {
150
- const contextText = getContextForSubagent(task.id);
151
- const unreadKnowledge = getUnreadKnowledgeForTask(spec.id, task.id);
152
-
153
- // NOVO v7.4: Buscar implementation patterns relevantes
154
- const taskFiles = task.files ? JSON.parse(task.files) : [];
155
- const agentScope = task.agent?.split("-")[0] || "all"; // frontend-next -> frontend
156
-
157
- // Buscar patterns por escopo do agente E por arquivos esperados
158
- let relevantPatterns: any[] = [];
159
-
160
- // Primeiro: patterns que correspondem aos arquivos esperados
161
- if (taskFiles.length > 0) {
162
- const filePatterns = getPatternsForFiles(taskFiles);
163
- relevantPatterns.push(...filePatterns);
164
- }
165
-
166
- // Segundo: patterns do escopo do agente (se nao foram encontrados via arquivos)
167
- if (relevantPatterns.length === 0) {
168
- const scopePatterns = getPatternsByScope(agentScope);
169
- relevantPatterns.push(...scopePatterns);
170
- }
171
-
172
- // Formatar patterns para o contexto (sem duplicatas)
173
- const patternNames = new Set<string>();
174
- const formattedPatterns = relevantPatterns
175
- .filter(p => {
176
- if (patternNames.has(p.name)) return false;
177
- patternNames.add(p.name);
178
- return true;
179
- })
180
- .map(p => ({
181
- name: p.name,
182
- category: p.category,
183
- applies_to: p.applies_to,
184
- template: p.template,
185
- structure: JSON.parse(p.structure || "{}"),
186
- examples: JSON.parse(p.examples || "[]").slice(0, 3), // Top 3 exemplos
187
- anti_patterns: JSON.parse(p.anti_patterns || "[]"),
188
- confidence: p.confidence,
189
- }));
190
-
191
- return {
192
- taskId: task.id,
193
- number: task.number,
194
- name: task.name,
195
- agent: task.agent,
196
- files: taskFiles,
197
- // AVISO PARA O ORQUESTRADOR - NAO EXECUTE, DELEGUE
198
- _orchestratorWarning: "NAO execute esta task diretamente. Use Task tool com subagent_type='general-purpose' para delegar. O campo 'subagentContext' abaixo e o prompt para o SUBAGENT.",
199
- // Contexto completo para o subagent (NAO para o orquestrador)
200
- context: contextText,
201
- // Knowledge nao lido (broadcast de outras tasks)
202
- unreadKnowledge: unreadKnowledge.map((k: any) => ({
203
- id: k.id,
204
- category: k.category,
205
- content: k.content,
206
- severity: k.severity,
207
- origin_task: k.task_origin,
208
- })),
209
- // NOVO v7.4: Implementation patterns extraidos do projeto
210
- implementationPatterns: formattedPatterns,
211
- // Contexto para o SUBAGENT (o orquestrador deve passar isso via Task tool)
212
- subagentContext: `
213
- ╔══════════════════════════════════════════════════════════════════════════════╗
214
- ║ DIRETIVA CRITICA: USE Write/Edit PARA CRIAR OS ARQUIVOS ║
215
- ║ NAO descreva. NAO planeje. NAO simule. EXECUTE AGORA. ║
216
- ║ Se retornar sem usar Write/Edit, a task FALHA. ║
217
- ╚══════════════════════════════════════════════════════════════════════════════╝
218
-
219
- ARQUIVOS QUE VOCE DEVE CRIAR (use Write para cada um):
220
- ${taskFiles.map(f => ` - ${f}`).join('\n') || ' (nenhum arquivo especificado - analise o contexto)'}
221
-
222
- ╔══════════════════════════════════════════════════════════════════════════════╗
223
- ║ POLLING OBRIGATORIO: Verifique blockers a cada 3 arquivos modificados ║
224
- ╚══════════════════════════════════════════════════════════════════════════════╝
225
-
226
- ANTES de criar o 4o, 7o, 10o arquivo (a cada 3), execute:
227
- bun run .claude/cli/workflow.ts knowledge list --severity critical --unread
228
-
229
- Se retornar QUALQUER blocker:
230
- 1. PARE imediatamente
231
- 2. Retorne com status "blocked" e inclua o blocker encontrado
232
- 3. NAO continue criando arquivos apos encontrar blocker
233
-
234
- CHECKLIST OBRIGATORIO (verifique ANTES de retornar):
235
- - [ ] Usei Write ou Edit para criar/modificar arquivos?
236
- - [ ] Verifiquei blockers a cada 3 arquivos?
237
- - [ ] Os arquivos que vou listar em files_created EXISTEM no disco?
238
-
239
- Se nao marcou todos os items, PARE e corrija AGORA.
240
- `.trim(),
241
- // Instrucoes de retorno para o SUBAGENT
242
- subagentReturnProtocol: `
243
- FORMATO DE RETORNO (apos criar os arquivos):
244
- {
245
- "status": "completed | blocked | needs_decision",
246
- "summary": "Resumo do que foi feito (10-500 chars)",
247
- "files_created": ["path/arquivo.ts"],
248
- "files_modified": ["path/outro.ts"],
249
- "patterns_discovered": ["Pattern identificado"],
250
- "decisions_made": [{"title": "...", "decision": "..."}],
251
- "blockers": ["Se status != completed"],
252
- "knowledge_to_broadcast": [{"category": "discovery|pattern|constraint", "content": "...", "severity": "info|warning|critical"}],
253
- "reasoning": {
254
- "approach": "Como voce abordou o problema (RECOMENDADO)",
255
- "challenges": ["Desafios encontrados"],
256
- "recommendations": "Sugestoes para proximas tasks"
257
- }
258
- }
259
-
260
- IMPORTANTE: O campo "reasoning" ajuda a preservar contexto entre sessoes.
261
- Inclua pelo menos "approach" para explicar suas escolhas.
262
-
263
- Se NAO conseguir criar arquivos (sem permissao, sem ferramentas), retorne:
264
- {
265
- "status": "blocked",
266
- "blockers": ["Descreva por que nao conseguiu criar os arquivos"]
267
- }
268
-
269
- Veja .claude/agents/PROTOCOL.md para detalhes completos.
270
- ${formattedPatterns.length > 0 ? `
271
- PATTERNS: Voce recebeu ${formattedPatterns.length} implementation patterns extraidos do projeto.
272
- Use os TEMPLATES fornecidos para criar codigo CONSISTENTE com o projeto existente.
273
- ` : ''}
274
- `.trim(),
275
- };
276
- });
277
- console.log(JSON.stringify({ started: contexts }));
278
- return;
279
- }
280
-
281
- console.log(`\nTasks iniciadas:`);
282
- for (const task of startedTasks) {
283
- console.log(` #${task.number}: ${task.name} (${task.agent || "geral"})`);
284
- }
285
- console.log(`\nAo concluir, use: task done <id> --checkpoint "resumo"\n`);
286
- }
287
-
288
- export function taskDone(id: string, options: { checkpoint: string; files?: string; force?: boolean; forceReason?: string; output?: string }): void {
289
- initSchema();
290
-
291
- const taskId = parseInt(id);
292
- const db = getDb();
293
- const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
294
-
295
- if (!task) {
296
- console.error(`\nTask #${taskId} nao encontrada.\n`);
297
- process.exit(2);
298
- }
299
-
300
- const spec = db.query("SELECT * FROM specs WHERE id = ?").get(task.spec_id) as any;
301
- const now = new Date().toISOString();
302
-
303
- // ============================================================
304
- // NOVO: Processar retorno do subagent se fornecido via --output
305
- // ============================================================
306
- let checkpoint = options.checkpoint;
307
- let expectedFiles: string[] = [];
308
- let subagentData = null;
309
-
310
- if (options.output) {
311
- // Parse e valida o retorno do subagent
312
- const parseResult = parseSubagentReturn(options.output);
313
-
314
- if (!parseResult.success) {
315
- console.error(formatValidationErrors(parseResult));
316
- console.error("\nTask NAO pode ser completada sem retorno valido.");
317
- console.error("Corrija o formato do retorno do subagent e tente novamente.\n");
318
- process.exit(2);
319
- }
320
-
321
- subagentData = parseResult.data!;
322
-
323
- // Extrair checkpoint do summary se nao fornecido
324
- if (!checkpoint || checkpoint === "") {
325
- checkpoint = subagentData.summary;
326
- }
327
-
328
- // Combinar arquivos do retorno com --files
329
- expectedFiles = [
330
- ...subagentData.files_created,
331
- ...subagentData.files_modified,
332
- ];
333
-
334
- // Se --files tambem foi fornecido, adicionar
335
- if (options.files) {
336
- expectedFiles.push(...options.files.split(",").map((f) => f.trim()));
337
- }
338
-
339
- // Remover duplicatas
340
- expectedFiles = [...new Set(expectedFiles)];
341
-
342
- // Se status nao e completed, informar e sair
343
- if (subagentData.status === "blocked") {
344
- console.log("\n[!] Subagent retornou status 'blocked'");
345
- console.log("\nBlockers encontrados:");
346
- for (const blocker of subagentData.blockers || []) {
347
- console.log(` - ${blocker}`);
348
- }
349
-
350
- // Registrar blockers como knowledge critico
351
- const processResult = processSubagentReturn(spec.id, taskId, task.number, subagentData);
352
- console.log(formatProcessResult(processResult));
353
-
354
- console.log("\nTask permanece em execucao. Resolva os blockers e tente novamente.\n");
355
- return;
356
- }
357
-
358
- if (subagentData.status === "needs_decision") {
359
- console.log("\n[?] Subagent retornou status 'needs_decision'");
360
- console.log("\nDecisao necessaria:");
361
- for (const item of subagentData.blockers || []) {
362
- console.log(` - ${item}`);
363
- }
364
-
365
- // Registrar como knowledge
366
- const processResult = processSubagentReturn(spec.id, taskId, task.number, subagentData);
367
- console.log(formatProcessResult(processResult));
368
-
369
- console.log("\nTask permanece em execucao. Tome a decisao e tente novamente.\n");
370
- return;
371
- }
372
-
373
- // Status completed - processar dados automaticamente
374
- const processResult = processSubagentReturn(spec.id, taskId, task.number, subagentData);
375
- console.log(formatProcessResult(processResult));
376
-
377
- // v8.3: BLOCKING check para knowledge critico nao reconhecido (substitui warning v8.2)
378
- const unackedCritical = getUnreadKnowledgeForTask(spec.id, taskId)
379
- .filter((k: any) => k.severity === 'critical' && k.task_origin !== taskId);
380
- if (unackedCritical.length > 0) {
381
- if (!options.force) {
382
- console.error(`\n[X] BLOQUEADO: ${unackedCritical.length} knowledge(s) critico(s) nao reconhecido(s):`);
383
- for (const k of unackedCritical) {
384
- console.error(` [X] ${k.content} (de Task #${k.task_origin})`);
385
- }
386
- console.error(`\n O subagent NAO verificou o polling obrigatorio.`);
387
- console.error(` Reconheca com: knowledge ack <id>`);
388
- console.error(` Ou force com: task done ${id} --checkpoint "..." --force --force-reason "motivo"\n`);
389
- process.exit(1);
390
- } else {
391
- console.log(`\n[!] AVISO: ${unackedCritical.length} knowledge(s) critico(s) ignorado(s) (--force usado)`);
392
- for (const k of unackedCritical) {
393
- console.log(` [X] ${k.content}`);
394
- }
395
- }
396
- }
397
- } else {
398
- // Modo antigo: arquivos via --files
399
- expectedFiles = options.files
400
- ? options.files.split(",").map((f) => f.trim())
401
- : [];
402
- }
403
-
404
- // Validar gates incluindo files-exist e standards-follow
405
- enforceGate("task-done", {
406
- taskId,
407
- checkpoint,
408
- files: expectedFiles,
409
- force: options.force,
410
- forceReason: options.forceReason,
411
- });
412
-
413
- // Nota: Validacao de standards agora e feita pelo Gate 4.2 (standards-follow) em enforceGate
414
-
415
- // Marcar como done
416
- db.run(
417
- "UPDATE tasks SET status = 'done', checkpoint = ?, completed_at = ? WHERE id = ?",
418
- [checkpoint, now, taskId]
419
- );
420
-
421
- // Registrar artefatos se NAO veio do subagent (ja foi processado acima)
422
- if (!subagentData && options.files) {
423
- const files = options.files.split(",").map((s) => s.trim());
424
- for (const file of files) {
425
- db.run(
426
- `INSERT OR REPLACE INTO artifacts (spec_id, task_ref, path, action, created_at)
427
- VALUES (?, ?, ?, 'created', ?)`,
428
- [spec.id, task.number, file, now]
429
- );
430
- }
431
- }
432
-
433
- // Atualizar contexto
434
- const doneCount = db
435
- .query("SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND status = 'done'")
436
- .get(spec.id) as any;
437
- const totalCount = db
438
- .query("SELECT COUNT(*) as c FROM tasks WHERE spec_id = ?")
439
- .get(spec.id) as any;
440
-
441
- db.run(
442
- "UPDATE context SET last_checkpoint = ?, updated_at = ? WHERE spec_id = ?",
443
- [checkpoint, now, spec.id]
444
- );
445
-
446
- // Criar snapshot automatico COMPLETO
447
- const allTasks = db.query("SELECT * FROM tasks WHERE spec_id = ?").all(spec.id) as any[];
448
- const allDecisions = db.query("SELECT * FROM decisions WHERE spec_id = ?").all(spec.id) as any[];
449
- const allArtifacts = db.query("SELECT * FROM artifacts WHERE spec_id = ?").all(spec.id) as any[];
450
- const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id);
451
-
452
- const snapshotData = {
453
- spec,
454
- context,
455
- tasks: allTasks,
456
- decisions: allDecisions,
457
- artifacts: allArtifacts,
458
- checkpoint: options.checkpoint,
459
- taskCompleted: task.number,
460
- timestamp: now,
461
- };
462
- db.run("INSERT INTO snapshots (spec_id, data, trigger, created_at) VALUES (?, ?, 'auto', ?)", [
463
- spec.id,
464
- JSON.stringify(snapshotData),
465
- now,
466
- ]);
467
-
468
- // v8.1: Gerar session summary automaticamente
469
- try {
470
- const taskDecisions = allDecisions.filter((d) => d.task_ref === task.number);
471
- const taskArtifacts = allArtifacts.filter((a) => a.task_ref === task.number);
472
- const blockers = db.query(
473
- "SELECT content FROM knowledge WHERE spec_id = ? AND task_origin = ? AND category = 'blocker'"
474
- ).all(spec.id, taskId) as any[];
475
- const reasoning = getRecentReasoning(spec.id, 5);
476
- const nextSteps: string[] = [];
477
-
478
- // Extrair recommendations do reasoning
479
- for (const r of reasoning) {
480
- if (r.category === 'recommendation' && r.thought) {
481
- nextSteps.push(r.thought);
482
- }
483
- }
484
-
485
- // Se nao completou tudo, sugerir proximo passo
486
- if (doneCount.c < totalCount.c) {
487
- nextSteps.push(`Continuar implementacao: ${totalCount.c - doneCount.c} tasks restantes`);
488
- } else {
489
- nextSteps.push("Todas tasks concluidas. Iniciar review.");
490
- }
491
-
492
- addSessionSummary(spec.id, {
493
- startTime: task.completed_at || now, // approximation
494
- endTime: now,
495
- summary: `Task #${task.number} (${task.name}) concluida. ${checkpoint}`,
496
- decisions: taskDecisions.map((d: any) => `${d.title}: ${d.decision}`),
497
- blockers: blockers.map((b: any) => b.content),
498
- nextSteps,
499
- tasksCompleted: 1,
500
- filesCreated: taskArtifacts.filter((a: any) => a.action === 'created').length,
501
- filesModified: taskArtifacts.filter((a: any) => a.action === 'modified').length,
502
- });
503
- } catch (e) {
504
- // Session summary e best-effort, nao deve bloquear o fluxo
505
- }
506
-
507
- console.log(`\nTask #${task.number} concluida!`);
508
- console.log(`Checkpoint: ${options.checkpoint}`);
509
- console.log(`Progresso: ${doneCount.c}/${totalCount.c} tasks`);
510
-
511
- if (doneCount.c === totalCount.c) {
512
- // Mostrar resumo completo da implementacao
513
- showImplementationSummary(spec.id, allTasks, allArtifacts, allDecisions);
514
- } else {
515
- console.log(`\nProximas tasks: task next\n`);
516
- }
517
- }
518
-
519
- /**
520
- * Mostra resumo detalhado da implementacao apos todas as tasks concluidas
521
- * Permite ao usuario decidir se quer fazer review ou nao
522
- */
523
- export function showImplementationSummary(
524
- specId: number,
525
- tasks: any[],
526
- artifacts: any[],
527
- decisions: any[]
528
- ): void {
529
- const db = getDb();
530
- const spec = db.query("SELECT * FROM specs WHERE id = ?").get(specId) as any;
531
- const knowledge = db.query("SELECT * FROM knowledge WHERE spec_id = ?").all(specId) as any[];
532
-
533
- console.log(`\n${"=".repeat(60)}`);
534
- console.log(`IMPLEMENTACAO CONCLUIDA: ${spec.name}`);
535
- console.log(`${"=".repeat(60)}`);
536
-
537
- // Resumo por subagente
538
- console.log(`\n📋 RESUMO POR SUBAGENTE:`);
539
- console.log(`${"─".repeat(50)}`);
540
-
541
- // Agrupar tasks por agente
542
- const tasksByAgent: Record<string, any[]> = {};
543
- for (const task of tasks) {
544
- const agent = task.agent || "geral";
545
- if (!tasksByAgent[agent]) {
546
- tasksByAgent[agent] = [];
547
- }
548
- tasksByAgent[agent].push(task);
549
- }
550
-
551
- for (const [agent, agentTasks] of Object.entries(tasksByAgent)) {
552
- console.log(`\n [${agent.toUpperCase()}]`);
553
- for (const task of agentTasks) {
554
- console.log(` #${task.number}: ${task.name}`);
555
- if (task.checkpoint) {
556
- console.log(` └─ ${task.checkpoint}`);
557
- }
558
- // Mostrar arquivos criados por esta task
559
- const taskArtifacts = artifacts.filter((a) => a.task_ref === task.number);
560
- if (taskArtifacts.length > 0) {
561
- console.log(` Arquivos: ${taskArtifacts.map((a) => a.path).join(", ")}`);
562
- }
563
- }
564
- }
565
-
566
- // Artefatos criados
567
- console.log(`\n📁 ARTEFATOS CRIADOS (${artifacts.length}):`);
568
- console.log(`${"─".repeat(50)}`);
569
- for (const artifact of artifacts) {
570
- const task = tasks.find((t) => t.number === artifact.task_ref);
571
- const agentInfo = task?.agent ? ` [${task.agent}]` : "";
572
- console.log(` - ${artifact.path}${agentInfo}`);
573
- }
574
-
575
- // Decisoes tomadas
576
- if (decisions.length > 0) {
577
- console.log(`\n🎯 DECISOES TOMADAS (${decisions.length}):`);
578
- console.log(`${"─".repeat(50)}`);
579
- for (const dec of decisions) {
580
- const task = tasks.find((t) => t.number === dec.task_ref);
581
- const agentInfo = task?.agent ? ` [${task.agent}]` : "";
582
- console.log(` - ${dec.title}${agentInfo}`);
583
- console.log(` └─ ${dec.decision}`);
584
- }
585
- }
586
-
587
- // Knowledge descoberto
588
- const discoveries = knowledge.filter((k) => k.category === "discovery" || k.category === "pattern");
589
- if (discoveries.length > 0) {
590
- console.log(`\n💡 DESCOBERTAS E PADROES (${discoveries.length}):`);
591
- console.log(`${"─".repeat(50)}`);
592
- for (const item of discoveries) {
593
- const severityIcon = item.severity === "critical" ? "🚨" : item.severity === "warning" ? "⚠️" : "ℹ️";
594
- console.log(` ${severityIcon} ${item.content}`);
595
- }
596
- }
597
-
598
- // Blockers encontrados
599
- const blockers = knowledge.filter((k) => k.category === "blocker");
600
- if (blockers.length > 0) {
601
- console.log(`\n⛔ BLOCKERS ENCONTRADOS (${blockers.length}):`);
602
- console.log(`${"─".repeat(50)}`);
603
- for (const blocker of blockers) {
604
- console.log(` - ${blocker.content}`);
605
- }
606
- }
607
-
608
- // Estatisticas finais
609
- console.log(`\n📊 ESTATISTICAS:`);
610
- console.log(`${"─".repeat(50)}`);
611
- console.log(` Tasks concluidas: ${tasks.length}`);
612
- console.log(` Artefatos criados: ${artifacts.length}`);
613
- console.log(` Decisoes tomadas: ${decisions.length}`);
614
- console.log(` Knowledge items: ${knowledge.length}`);
615
- console.log(` Subagentes envolvidos: ${Object.keys(tasksByAgent).length}`);
616
-
617
- console.log(`\n${"=".repeat(60)}`);
618
- console.log(`PROXIMOS PASSOS:`);
619
- console.log(`${"=".repeat(60)}`);
620
- console.log(`\n Para revisar o codigo: review start`);
621
- console.log(` Para pular o review: review skip`);
622
- console.log(`\n O agente aguarda sua decisao.`);
623
- console.log(`${"─".repeat(50)}\n`);
1
+ import { getDb } from "../db/connection";
2
+ import { initSchema, getPatternsForFiles, getPatternsByScope, addSessionSummary, getRecentReasoning } from "../db/schema";
3
+ import { enforceGate } from "../gates/validator";
4
+ import { parseSubagentReturn, formatValidationErrors } from "../protocol/subagent-protocol";
5
+ import { processSubagentReturn, formatProcessResult } from "../protocol/process-return";
6
+ import { getContextForSubagent } from "./utils";
7
+ import { getUnreadKnowledgeForTask } from "./knowledge";
8
+
9
+ export function taskNext(json: boolean = false): void {
10
+ initSchema();
11
+
12
+ const db = getDb();
13
+ const spec = db
14
+ .query("SELECT * FROM specs WHERE phase = 'implementing' ORDER BY created_at DESC LIMIT 1")
15
+ .get() as any;
16
+
17
+ if (!spec) {
18
+ if (json) {
19
+ console.log(JSON.stringify({ available: [], message: "Nenhuma feature em implementacao" }));
20
+ } else {
21
+ console.error("\nNenhuma feature em fase de implementacao.");
22
+ console.error("Aprove o plano com: check approve\n");
23
+ }
24
+ process.exit(1);
25
+ }
26
+
27
+ // Buscar tasks pendentes cujas dependencias estao todas concluidas
28
+ const allTasks = db
29
+ .query("SELECT * FROM tasks WHERE spec_id = ? ORDER BY number")
30
+ .all(spec.id) as any[];
31
+
32
+ const available: any[] = [];
33
+
34
+ for (const task of allTasks) {
35
+ if (task.status !== "pending") continue;
36
+
37
+ // Verificar dependencias
38
+ const deps = task.depends_on ? JSON.parse(task.depends_on) : [];
39
+ const allDepsDone = deps.every((depNum: number) => {
40
+ const depTask = allTasks.find((t) => t.number === depNum);
41
+ return depTask?.status === "done";
42
+ });
43
+
44
+ if (allDepsDone) {
45
+ available.push(task);
46
+ }
47
+ }
48
+
49
+ if (json) {
50
+ console.log(JSON.stringify({ available }));
51
+ return;
52
+ }
53
+
54
+ if (available.length === 0) {
55
+ // Verificar se todas estao concluidas
56
+ const pending = allTasks.filter((t) => t.status !== "done");
57
+ if (pending.length === 0) {
58
+ console.log("\nTodas as tasks foram concluidas!");
59
+ console.log("Inicie o review com: review start\n");
60
+ } else {
61
+ console.log("\nNenhuma task disponivel no momento.");
62
+ console.log(`Tasks em execucao ou bloqueadas: ${pending.length}`);
63
+ console.log("Aguarde as tasks em andamento serem concluidas.\n");
64
+ }
65
+ return;
66
+ }
67
+
68
+ console.log(`\nTasks disponiveis para execucao (${available.length}):`);
69
+ console.log(`${"─".repeat(50)}`);
70
+
71
+ const parallelizable = available.filter((t) => t.can_parallel);
72
+ const sequential = available.filter((t) => !t.can_parallel);
73
+
74
+ if (parallelizable.length > 0) {
75
+ if (parallelizable.length > 1) {
76
+ console.log(`\nPodem executar em PARALELO:`);
77
+ } else {
78
+ console.log(`\nDisponivel:`);
79
+ }
80
+ const ids = parallelizable.map((t) => t.id).join(",");
81
+ for (const task of parallelizable) {
82
+ const files = task.files ? JSON.parse(task.files) : [];
83
+ console.log(` #${task.number}: ${task.name} (${task.agent || "geral"}) [id: ${task.id}]`);
84
+ if (files.length > 0) console.log(` Arquivos: ${files.join(", ")}`);
85
+ }
86
+ if (parallelizable.length > 1) {
87
+ console.log(`\n Para iniciar todas: task start ${ids}`);
88
+ } else {
89
+ console.log(`\n Para iniciar: task start ${ids}`);
90
+ }
91
+ }
92
+
93
+ if (sequential.length > 0) {
94
+ console.log(`\nSequenciais (dependencias pendentes):`);
95
+ for (const task of sequential) {
96
+ console.log(` #${task.number}: ${task.name} (${task.agent || "geral"})`);
97
+ }
98
+ }
99
+
100
+ console.log(`\n${"─".repeat(50)}`);
101
+ console.log(`Use: task start <id> ou task start <id1>,<id2>,...\n`);
102
+ }
103
+
104
+ export function taskStart(ids: string, json: boolean = false): void {
105
+ initSchema();
106
+ enforceGate("task-start");
107
+
108
+ const db = getDb();
109
+ const now = new Date().toISOString();
110
+
111
+ const spec = db
112
+ .query("SELECT * FROM specs WHERE phase = 'implementing' ORDER BY created_at DESC LIMIT 1")
113
+ .get() as any;
114
+
115
+ const taskIds = ids.split(",").map((s) => parseInt(s.trim()));
116
+ const startedTasks: any[] = [];
117
+
118
+ for (const taskId of taskIds) {
119
+ const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
120
+
121
+ if (!task) {
122
+ console.error(`\nTask #${taskId} nao encontrada.\n`);
123
+ process.exit(2);
124
+ }
125
+
126
+ if (task.status !== "pending") {
127
+ console.error(`\nTask #${task.number} nao esta pendente (status: ${task.status}).\n`);
128
+ process.exit(2);
129
+ }
130
+
131
+ // Validar dependencias para esta task especifica
132
+ enforceGate("task-start", { taskId });
133
+
134
+ // Marcar como running
135
+ db.run("UPDATE tasks SET status = 'running' WHERE id = ?", [taskId]);
136
+
137
+ startedTasks.push(task);
138
+ }
139
+
140
+ // Atualizar contexto
141
+ db.run("UPDATE context SET current_task = ?, updated_at = ? WHERE spec_id = ?", [
142
+ startedTasks[0].number,
143
+ now,
144
+ spec.id,
145
+ ]);
146
+
147
+ if (json) {
148
+ // NOVO: Incluir contexto COMPLETO para cada task
149
+ const contexts = startedTasks.map((task) => {
150
+ const contextText = getContextForSubagent(task.id);
151
+ const unreadKnowledge = getUnreadKnowledgeForTask(spec.id, task.id);
152
+
153
+ // NOVO v7.4: Buscar implementation patterns relevantes
154
+ const taskFiles = task.files ? JSON.parse(task.files) : [];
155
+ const agentScope = task.agent?.split("-")[0] || "all"; // frontend-next -> frontend
156
+
157
+ // Buscar patterns por escopo do agente E por arquivos esperados
158
+ let relevantPatterns: any[] = [];
159
+
160
+ // Primeiro: patterns que correspondem aos arquivos esperados
161
+ if (taskFiles.length > 0) {
162
+ const filePatterns = getPatternsForFiles(taskFiles);
163
+ relevantPatterns.push(...filePatterns);
164
+ }
165
+
166
+ // Segundo: patterns do escopo do agente (se nao foram encontrados via arquivos)
167
+ if (relevantPatterns.length === 0) {
168
+ const scopePatterns = getPatternsByScope(agentScope);
169
+ relevantPatterns.push(...scopePatterns);
170
+ }
171
+
172
+ // Formatar patterns para o contexto (sem duplicatas)
173
+ const patternNames = new Set<string>();
174
+ const formattedPatterns = relevantPatterns
175
+ .filter(p => {
176
+ if (patternNames.has(p.name)) return false;
177
+ patternNames.add(p.name);
178
+ return true;
179
+ })
180
+ .map(p => ({
181
+ name: p.name,
182
+ category: p.category,
183
+ applies_to: p.applies_to,
184
+ template: p.template,
185
+ structure: JSON.parse(p.structure || "{}"),
186
+ examples: JSON.parse(p.examples || "[]").slice(0, 3), // Top 3 exemplos
187
+ anti_patterns: JSON.parse(p.anti_patterns || "[]"),
188
+ confidence: p.confidence,
189
+ }));
190
+
191
+ return {
192
+ taskId: task.id,
193
+ number: task.number,
194
+ name: task.name,
195
+ agent: task.agent,
196
+ files: taskFiles,
197
+ // AVISO PARA O ORQUESTRADOR - NAO EXECUTE, DELEGUE
198
+ _orchestratorWarning: "NAO execute esta task diretamente. Use Task tool com subagent_type='general-purpose' para delegar. O campo 'subagentContext' abaixo e o prompt para o SUBAGENT.",
199
+ // Contexto completo para o subagent (NAO para o orquestrador)
200
+ context: contextText,
201
+ // Knowledge nao lido (broadcast de outras tasks)
202
+ unreadKnowledge: unreadKnowledge.map((k: any) => ({
203
+ id: k.id,
204
+ category: k.category,
205
+ content: k.content,
206
+ severity: k.severity,
207
+ origin_task: k.task_origin,
208
+ })),
209
+ // NOVO v7.4: Implementation patterns extraidos do projeto
210
+ implementationPatterns: formattedPatterns,
211
+ // Contexto para o SUBAGENT (o orquestrador deve passar isso via Task tool)
212
+ subagentContext: `
213
+ ╔══════════════════════════════════════════════════════════════════════════════╗
214
+ ║ DIRETIVA CRITICA: USE Write/Edit PARA CRIAR OS ARQUIVOS ║
215
+ ║ NAO descreva. NAO planeje. NAO simule. EXECUTE AGORA. ║
216
+ ║ Se retornar sem usar Write/Edit, a task FALHA. ║
217
+ ╚══════════════════════════════════════════════════════════════════════════════╝
218
+
219
+ ARQUIVOS QUE VOCE DEVE CRIAR (use Write para cada um):
220
+ ${taskFiles.map(f => ` - ${f}`).join('\n') || ' (nenhum arquivo especificado - analise o contexto)'}
221
+
222
+ ╔══════════════════════════════════════════════════════════════════════════════╗
223
+ ║ POLLING OBRIGATORIO: Verifique blockers a cada 3 arquivos modificados ║
224
+ ╚══════════════════════════════════════════════════════════════════════════════╝
225
+
226
+ ANTES de criar o 4o, 7o, 10o arquivo (a cada 3), execute:
227
+ codexa knowledge list --severity critical --unread
228
+
229
+ Se retornar QUALQUER blocker:
230
+ 1. PARE imediatamente
231
+ 2. Retorne com status "blocked" e inclua o blocker encontrado
232
+ 3. NAO continue criando arquivos apos encontrar blocker
233
+
234
+ CHECKLIST OBRIGATORIO (verifique ANTES de retornar):
235
+ - [ ] Usei Write ou Edit para criar/modificar arquivos?
236
+ - [ ] Verifiquei blockers a cada 3 arquivos?
237
+ - [ ] Os arquivos que vou listar em files_created EXISTEM no disco?
238
+
239
+ Se nao marcou todos os items, PARE e corrija AGORA.
240
+ `.trim(),
241
+ // Instrucoes de retorno para o SUBAGENT
242
+ subagentReturnProtocol: `
243
+ FORMATO DE RETORNO (apos criar os arquivos):
244
+ {
245
+ "status": "completed | blocked | needs_decision",
246
+ "summary": "Resumo do que foi feito (10-500 chars)",
247
+ "files_created": ["path/arquivo.ts"],
248
+ "files_modified": ["path/outro.ts"],
249
+ "patterns_discovered": ["Pattern identificado"],
250
+ "decisions_made": [{"title": "...", "decision": "..."}],
251
+ "blockers": ["Se status != completed"],
252
+ "knowledge_to_broadcast": [{"category": "discovery|pattern|constraint", "content": "...", "severity": "info|warning|critical"}],
253
+ "reasoning": {
254
+ "approach": "Como voce abordou o problema (RECOMENDADO)",
255
+ "challenges": ["Desafios encontrados"],
256
+ "recommendations": "Sugestoes para proximas tasks"
257
+ }
258
+ }
259
+
260
+ IMPORTANTE: O campo "reasoning" ajuda a preservar contexto entre sessoes.
261
+ Inclua pelo menos "approach" para explicar suas escolhas.
262
+
263
+ Se NAO conseguir criar arquivos (sem permissao, sem ferramentas), retorne:
264
+ {
265
+ "status": "blocked",
266
+ "blockers": ["Descreva por que nao conseguiu criar os arquivos"]
267
+ }
268
+
269
+ Veja .claude/agents/PROTOCOL.md para detalhes completos.
270
+ ${formattedPatterns.length > 0 ? `
271
+ PATTERNS: Voce recebeu ${formattedPatterns.length} implementation patterns extraidos do projeto.
272
+ Use os TEMPLATES fornecidos para criar codigo CONSISTENTE com o projeto existente.
273
+ ` : ''}
274
+ `.trim(),
275
+ };
276
+ });
277
+ console.log(JSON.stringify({ started: contexts }));
278
+ return;
279
+ }
280
+
281
+ console.log(`\nTasks iniciadas:`);
282
+ for (const task of startedTasks) {
283
+ console.log(` #${task.number}: ${task.name} (${task.agent || "geral"})`);
284
+ }
285
+ console.log(`\nAo concluir, use: task done <id> --checkpoint "resumo"\n`);
286
+ }
287
+
288
+ export function taskDone(id: string, options: { checkpoint: string; files?: string; force?: boolean; forceReason?: string; output?: string }): void {
289
+ initSchema();
290
+
291
+ const taskId = parseInt(id);
292
+ const db = getDb();
293
+ const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
294
+
295
+ if (!task) {
296
+ console.error(`\nTask #${taskId} nao encontrada.\n`);
297
+ process.exit(2);
298
+ }
299
+
300
+ const spec = db.query("SELECT * FROM specs WHERE id = ?").get(task.spec_id) as any;
301
+ const now = new Date().toISOString();
302
+
303
+ // ============================================================
304
+ // NOVO: Processar retorno do subagent se fornecido via --output
305
+ // ============================================================
306
+ let checkpoint = options.checkpoint;
307
+ let expectedFiles: string[] = [];
308
+ let subagentData = null;
309
+
310
+ if (options.output) {
311
+ // Parse e valida o retorno do subagent
312
+ const parseResult = parseSubagentReturn(options.output);
313
+
314
+ if (!parseResult.success) {
315
+ console.error(formatValidationErrors(parseResult));
316
+ console.error("\nTask NAO pode ser completada sem retorno valido.");
317
+ console.error("Corrija o formato do retorno do subagent e tente novamente.\n");
318
+ process.exit(2);
319
+ }
320
+
321
+ subagentData = parseResult.data!;
322
+
323
+ // Extrair checkpoint do summary se nao fornecido
324
+ if (!checkpoint || checkpoint === "") {
325
+ checkpoint = subagentData.summary;
326
+ }
327
+
328
+ // Combinar arquivos do retorno com --files
329
+ expectedFiles = [
330
+ ...subagentData.files_created,
331
+ ...subagentData.files_modified,
332
+ ];
333
+
334
+ // Se --files tambem foi fornecido, adicionar
335
+ if (options.files) {
336
+ expectedFiles.push(...options.files.split(",").map((f) => f.trim()));
337
+ }
338
+
339
+ // Remover duplicatas
340
+ expectedFiles = [...new Set(expectedFiles)];
341
+
342
+ // Se status nao e completed, informar e sair
343
+ if (subagentData.status === "blocked") {
344
+ console.log("\n[!] Subagent retornou status 'blocked'");
345
+ console.log("\nBlockers encontrados:");
346
+ for (const blocker of subagentData.blockers || []) {
347
+ console.log(` - ${blocker}`);
348
+ }
349
+
350
+ // Registrar blockers como knowledge critico
351
+ const processResult = processSubagentReturn(spec.id, taskId, task.number, subagentData);
352
+ console.log(formatProcessResult(processResult));
353
+
354
+ console.log("\nTask permanece em execucao. Resolva os blockers e tente novamente.\n");
355
+ return;
356
+ }
357
+
358
+ if (subagentData.status === "needs_decision") {
359
+ console.log("\n[?] Subagent retornou status 'needs_decision'");
360
+ console.log("\nDecisao necessaria:");
361
+ for (const item of subagentData.blockers || []) {
362
+ console.log(` - ${item}`);
363
+ }
364
+
365
+ // Registrar como knowledge
366
+ const processResult = processSubagentReturn(spec.id, taskId, task.number, subagentData);
367
+ console.log(formatProcessResult(processResult));
368
+
369
+ console.log("\nTask permanece em execucao. Tome a decisao e tente novamente.\n");
370
+ return;
371
+ }
372
+
373
+ // Status completed - processar dados automaticamente
374
+ const processResult = processSubagentReturn(spec.id, taskId, task.number, subagentData);
375
+ console.log(formatProcessResult(processResult));
376
+
377
+ // v8.3: BLOCKING check para knowledge critico nao reconhecido (substitui warning v8.2)
378
+ const unackedCritical = getUnreadKnowledgeForTask(spec.id, taskId)
379
+ .filter((k: any) => k.severity === 'critical' && k.task_origin !== taskId);
380
+ if (unackedCritical.length > 0) {
381
+ if (!options.force) {
382
+ console.error(`\n[X] BLOQUEADO: ${unackedCritical.length} knowledge(s) critico(s) nao reconhecido(s):`);
383
+ for (const k of unackedCritical) {
384
+ console.error(` [X] ${k.content} (de Task #${k.task_origin})`);
385
+ }
386
+ console.error(`\n O subagent NAO verificou o polling obrigatorio.`);
387
+ console.error(` Reconheca com: knowledge ack <id>`);
388
+ console.error(` Ou force com: task done ${id} --checkpoint "..." --force --force-reason "motivo"\n`);
389
+ process.exit(1);
390
+ } else {
391
+ console.log(`\n[!] AVISO: ${unackedCritical.length} knowledge(s) critico(s) ignorado(s) (--force usado)`);
392
+ for (const k of unackedCritical) {
393
+ console.log(` [X] ${k.content}`);
394
+ }
395
+ }
396
+ }
397
+ } else {
398
+ // Modo antigo: arquivos via --files
399
+ expectedFiles = options.files
400
+ ? options.files.split(",").map((f) => f.trim())
401
+ : [];
402
+ }
403
+
404
+ // Validar gates incluindo files-exist e standards-follow
405
+ enforceGate("task-done", {
406
+ taskId,
407
+ checkpoint,
408
+ files: expectedFiles,
409
+ force: options.force,
410
+ forceReason: options.forceReason,
411
+ });
412
+
413
+ // Nota: Validacao de standards agora e feita pelo Gate 4.2 (standards-follow) em enforceGate
414
+
415
+ // Marcar como done
416
+ db.run(
417
+ "UPDATE tasks SET status = 'done', checkpoint = ?, completed_at = ? WHERE id = ?",
418
+ [checkpoint, now, taskId]
419
+ );
420
+
421
+ // Registrar artefatos se NAO veio do subagent (ja foi processado acima)
422
+ if (!subagentData && options.files) {
423
+ const files = options.files.split(",").map((s) => s.trim());
424
+ for (const file of files) {
425
+ db.run(
426
+ `INSERT OR REPLACE INTO artifacts (spec_id, task_ref, path, action, created_at)
427
+ VALUES (?, ?, ?, 'created', ?)`,
428
+ [spec.id, task.number, file, now]
429
+ );
430
+ }
431
+ }
432
+
433
+ // Atualizar contexto
434
+ const doneCount = db
435
+ .query("SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND status = 'done'")
436
+ .get(spec.id) as any;
437
+ const totalCount = db
438
+ .query("SELECT COUNT(*) as c FROM tasks WHERE spec_id = ?")
439
+ .get(spec.id) as any;
440
+
441
+ db.run(
442
+ "UPDATE context SET last_checkpoint = ?, updated_at = ? WHERE spec_id = ?",
443
+ [checkpoint, now, spec.id]
444
+ );
445
+
446
+ // Criar snapshot automatico COMPLETO
447
+ const allTasks = db.query("SELECT * FROM tasks WHERE spec_id = ?").all(spec.id) as any[];
448
+ const allDecisions = db.query("SELECT * FROM decisions WHERE spec_id = ?").all(spec.id) as any[];
449
+ const allArtifacts = db.query("SELECT * FROM artifacts WHERE spec_id = ?").all(spec.id) as any[];
450
+ const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id);
451
+
452
+ const snapshotData = {
453
+ spec,
454
+ context,
455
+ tasks: allTasks,
456
+ decisions: allDecisions,
457
+ artifacts: allArtifacts,
458
+ checkpoint: options.checkpoint,
459
+ taskCompleted: task.number,
460
+ timestamp: now,
461
+ };
462
+ db.run("INSERT INTO snapshots (spec_id, data, trigger, created_at) VALUES (?, ?, 'auto', ?)", [
463
+ spec.id,
464
+ JSON.stringify(snapshotData),
465
+ now,
466
+ ]);
467
+
468
+ // v8.1: Gerar session summary automaticamente
469
+ try {
470
+ const taskDecisions = allDecisions.filter((d) => d.task_ref === task.number);
471
+ const taskArtifacts = allArtifacts.filter((a) => a.task_ref === task.number);
472
+ const blockers = db.query(
473
+ "SELECT content FROM knowledge WHERE spec_id = ? AND task_origin = ? AND category = 'blocker'"
474
+ ).all(spec.id, taskId) as any[];
475
+ const reasoning = getRecentReasoning(spec.id, 5);
476
+ const nextSteps: string[] = [];
477
+
478
+ // Extrair recommendations do reasoning
479
+ for (const r of reasoning) {
480
+ if (r.category === 'recommendation' && r.thought) {
481
+ nextSteps.push(r.thought);
482
+ }
483
+ }
484
+
485
+ // Se nao completou tudo, sugerir proximo passo
486
+ if (doneCount.c < totalCount.c) {
487
+ nextSteps.push(`Continuar implementacao: ${totalCount.c - doneCount.c} tasks restantes`);
488
+ } else {
489
+ nextSteps.push("Todas tasks concluidas. Iniciar review.");
490
+ }
491
+
492
+ addSessionSummary(spec.id, {
493
+ startTime: task.completed_at || now, // approximation
494
+ endTime: now,
495
+ summary: `Task #${task.number} (${task.name}) concluida. ${checkpoint}`,
496
+ decisions: taskDecisions.map((d: any) => `${d.title}: ${d.decision}`),
497
+ blockers: blockers.map((b: any) => b.content),
498
+ nextSteps,
499
+ tasksCompleted: 1,
500
+ filesCreated: taskArtifacts.filter((a: any) => a.action === 'created').length,
501
+ filesModified: taskArtifacts.filter((a: any) => a.action === 'modified').length,
502
+ });
503
+ } catch (e) {
504
+ // Session summary e best-effort, nao deve bloquear o fluxo
505
+ }
506
+
507
+ console.log(`\nTask #${task.number} concluida!`);
508
+ console.log(`Checkpoint: ${options.checkpoint}`);
509
+ console.log(`Progresso: ${doneCount.c}/${totalCount.c} tasks`);
510
+
511
+ if (doneCount.c === totalCount.c) {
512
+ // Mostrar resumo completo da implementacao
513
+ showImplementationSummary(spec.id, allTasks, allArtifacts, allDecisions);
514
+ } else {
515
+ console.log(`\nProximas tasks: task next\n`);
516
+ }
517
+ }
518
+
519
+ /**
520
+ * Mostra resumo detalhado da implementacao apos todas as tasks concluidas
521
+ * Permite ao usuario decidir se quer fazer review ou nao
522
+ */
523
+ function showImplementationSummary(
524
+ specId: number,
525
+ tasks: any[],
526
+ artifacts: any[],
527
+ decisions: any[]
528
+ ): void {
529
+ const db = getDb();
530
+ const spec = db.query("SELECT * FROM specs WHERE id = ?").get(specId) as any;
531
+ const knowledge = db.query("SELECT * FROM knowledge WHERE spec_id = ?").all(specId) as any[];
532
+
533
+ console.log(`\n${"=".repeat(60)}`);
534
+ console.log(`IMPLEMENTACAO CONCLUIDA: ${spec.name}`);
535
+ console.log(`${"=".repeat(60)}`);
536
+
537
+ // Resumo por subagente
538
+ console.log(`\n📋 RESUMO POR SUBAGENTE:`);
539
+ console.log(`${"─".repeat(50)}`);
540
+
541
+ // Agrupar tasks por agente
542
+ const tasksByAgent: Record<string, any[]> = {};
543
+ for (const task of tasks) {
544
+ const agent = task.agent || "geral";
545
+ if (!tasksByAgent[agent]) {
546
+ tasksByAgent[agent] = [];
547
+ }
548
+ tasksByAgent[agent].push(task);
549
+ }
550
+
551
+ for (const [agent, agentTasks] of Object.entries(tasksByAgent)) {
552
+ console.log(`\n [${agent.toUpperCase()}]`);
553
+ for (const task of agentTasks) {
554
+ console.log(` #${task.number}: ${task.name}`);
555
+ if (task.checkpoint) {
556
+ console.log(` └─ ${task.checkpoint}`);
557
+ }
558
+ // Mostrar arquivos criados por esta task
559
+ const taskArtifacts = artifacts.filter((a) => a.task_ref === task.number);
560
+ if (taskArtifacts.length > 0) {
561
+ console.log(` Arquivos: ${taskArtifacts.map((a) => a.path).join(", ")}`);
562
+ }
563
+ }
564
+ }
565
+
566
+ // Artefatos criados
567
+ console.log(`\n📁 ARTEFATOS CRIADOS (${artifacts.length}):`);
568
+ console.log(`${"─".repeat(50)}`);
569
+ for (const artifact of artifacts) {
570
+ const task = tasks.find((t) => t.number === artifact.task_ref);
571
+ const agentInfo = task?.agent ? ` [${task.agent}]` : "";
572
+ console.log(` - ${artifact.path}${agentInfo}`);
573
+ }
574
+
575
+ // Decisoes tomadas
576
+ if (decisions.length > 0) {
577
+ console.log(`\n🎯 DECISOES TOMADAS (${decisions.length}):`);
578
+ console.log(`${"─".repeat(50)}`);
579
+ for (const dec of decisions) {
580
+ const task = tasks.find((t) => t.number === dec.task_ref);
581
+ const agentInfo = task?.agent ? ` [${task.agent}]` : "";
582
+ console.log(` - ${dec.title}${agentInfo}`);
583
+ console.log(` └─ ${dec.decision}`);
584
+ }
585
+ }
586
+
587
+ // Knowledge descoberto
588
+ const discoveries = knowledge.filter((k) => k.category === "discovery" || k.category === "pattern");
589
+ if (discoveries.length > 0) {
590
+ console.log(`\n💡 DESCOBERTAS E PADROES (${discoveries.length}):`);
591
+ console.log(`${"─".repeat(50)}`);
592
+ for (const item of discoveries) {
593
+ const severityIcon = item.severity === "critical" ? "🚨" : item.severity === "warning" ? "⚠️" : "ℹ️";
594
+ console.log(` ${severityIcon} ${item.content}`);
595
+ }
596
+ }
597
+
598
+ // Blockers encontrados
599
+ const blockers = knowledge.filter((k) => k.category === "blocker");
600
+ if (blockers.length > 0) {
601
+ console.log(`\n⛔ BLOCKERS ENCONTRADOS (${blockers.length}):`);
602
+ console.log(`${"─".repeat(50)}`);
603
+ for (const blocker of blockers) {
604
+ console.log(` - ${blocker.content}`);
605
+ }
606
+ }
607
+
608
+ // Estatisticas finais
609
+ console.log(`\n📊 ESTATISTICAS:`);
610
+ console.log(`${"─".repeat(50)}`);
611
+ console.log(` Tasks concluidas: ${tasks.length}`);
612
+ console.log(` Artefatos criados: ${artifacts.length}`);
613
+ console.log(` Decisoes tomadas: ${decisions.length}`);
614
+ console.log(` Knowledge items: ${knowledge.length}`);
615
+ console.log(` Subagentes envolvidos: ${Object.keys(tasksByAgent).length}`);
616
+
617
+ console.log(`\n${"=".repeat(60)}`);
618
+ console.log(`PROXIMOS PASSOS:`);
619
+ console.log(`${"=".repeat(60)}`);
620
+ console.log(`\n Para revisar o codigo: review start`);
621
+ console.log(` Para pular o review: review skip`);
622
+ console.log(`\n O agente aguarda sua decisao.`);
623
+ console.log(`${"─".repeat(50)}\n`);
624
624
  }