@codexa/cli 9.0.1 → 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/task.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import { getDb } from "../db/connection";
2
- import { initSchema, getPatternsForFiles, getPatternsByScope, addSessionSummary, getRecentReasoning } from "../db/schema";
2
+ import { initSchema, getPatternsForFiles, getPatternsByScope, getRecentReasoning, claimTask } from "../db/schema";
3
3
  import { enforceGate } from "../gates/validator";
4
4
  import { parseSubagentReturn, formatValidationErrors } from "../protocol/subagent-protocol";
5
5
  import { processSubagentReturn, formatProcessResult } from "../protocol/process-return";
6
6
  import { getContextForSubagent, getMinimalContextForSubagent } from "./utils";
7
7
  import { getUnreadKnowledgeForTask } from "./knowledge";
8
+ import { loadTemplate } from "../templates/loader";
9
+ import { TaskStateError, ValidationError, KnowledgeBlockError } from "../errors";
8
10
 
9
11
  export function taskNext(json: boolean = false): void {
10
12
  initSchema();
@@ -17,11 +19,9 @@ export function taskNext(json: boolean = false): void {
17
19
  if (!spec) {
18
20
  if (json) {
19
21
  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");
22
+ return;
23
23
  }
24
- process.exit(1);
24
+ throw new TaskStateError("Nenhuma feature em fase de implementacao.\nAprove o plano com: check approve");
25
25
  }
26
26
 
27
27
  // Buscar tasks pendentes cujas dependencias estao todas concluidas
@@ -119,20 +119,19 @@ export function taskStart(ids: string, json: boolean = false, fullContext: boole
119
119
  const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
120
120
 
121
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);
122
+ throw new TaskStateError(`Task #${taskId} nao encontrada.`);
129
123
  }
130
124
 
131
125
  // Validar dependencias para esta task especifica
132
126
  enforceGate("task-start", { taskId });
133
127
 
134
- // Marcar como running
135
- db.run("UPDATE tasks SET status = 'running' WHERE id = ?", [taskId]);
128
+ // Claim atomico: UPDATE ... WHERE status = 'pending'
129
+ if (!claimTask(taskId)) {
130
+ const current = db.query("SELECT status FROM tasks WHERE id = ?").get(taskId) as any;
131
+ throw new TaskStateError(
132
+ `Task #${task.number} nao pode ser iniciada (status atual: ${current?.status || "desconhecido"}).`
133
+ );
134
+ }
136
135
 
137
136
  startedTasks.push(task);
138
137
  }
@@ -233,77 +232,15 @@ export function taskStart(ids: string, json: boolean = false, fullContext: boole
233
232
  // NOVO v7.4: Implementation patterns extraidos do projeto
234
233
  implementationPatterns: formattedPatterns,
235
234
  // Contexto para o SUBAGENT (o orquestrador deve passar isso via Task tool)
236
- subagentContext: `
237
- ╔══════════════════════════════════════════════════════════════════════════════╗
238
- ║ DIRETIVA CRITICA: USE Write/Edit PARA CRIAR OS ARQUIVOS ║
239
- ║ NAO descreva. NAO planeje. NAO simule. EXECUTE AGORA. ║
240
- ║ Se retornar sem usar Write/Edit, a task FALHA. ║
241
- ╚══════════════════════════════════════════════════════════════════════════════╝
242
-
243
- ARQUIVOS QUE VOCE DEVE CRIAR (use Write para cada um):
244
- ${taskFiles.map(f => ` - ${f}`).join('\n') || ' (nenhum arquivo especificado - analise o contexto)'}
245
-
246
- ╔══════════════════════════════════════════════════════════════════════════════╗
247
- ║ POLLING OBRIGATORIO: Verifique blockers a cada 3 arquivos modificados ║
248
- ╚══════════════════════════════════════════════════════════════════════════════╝
249
-
250
- ANTES de criar o 4o, 7o, 10o arquivo (a cada 3), execute:
251
- codexa knowledge list --severity critical --unread
252
-
253
- Se retornar QUALQUER blocker:
254
- 1. PARE imediatamente
255
- 2. Retorne com status "blocked" e inclua o blocker encontrado
256
- 3. NAO continue criando arquivos apos encontrar blocker
257
-
258
- CHECKLIST OBRIGATORIO (verifique ANTES de retornar):
259
- - [ ] Usei Write ou Edit para criar/modificar arquivos?
260
- - [ ] Verifiquei blockers a cada 3 arquivos?
261
- - [ ] Os arquivos que vou listar em files_created EXISTEM no disco?
262
-
263
- Se nao marcou todos os items, PARE e corrija AGORA.
264
-
265
- CONTEXTO ON-DEMAND: Se precisar de mais contexto alem do fornecido, execute:
266
- codexa context detail standards # Regras do projeto
267
- codexa context detail decisions # Decisoes tomadas
268
- codexa context detail patterns # Patterns de codigo
269
- codexa context detail knowledge # Discoveries de outras tasks
270
- NAO execute todos - apenas o que for NECESSARIO para sua task.
271
- `.trim(),
235
+ subagentContext: loadTemplate("subagent-context", {
236
+ filesList: taskFiles.map(f => ` - ${f}`).join('\n') || ' (nenhum arquivo especificado - analise o contexto)',
237
+ }),
272
238
  // Instrucoes de retorno para o SUBAGENT
273
- subagentReturnProtocol: `
274
- FORMATO DE RETORNO (apos criar os arquivos):
275
- {
276
- "status": "completed | blocked | needs_decision",
277
- "summary": "Resumo do que foi feito (10-500 chars)",
278
- "files_created": ["path/arquivo.ts"],
279
- "files_modified": ["path/outro.ts"],
280
- "reasoning": {
281
- "approach": "OBRIGATORIO (min 20 chars): Como voce abordou o problema e POR QUE tomou essas decisoes",
282
- "challenges": ["Desafios encontrados"],
283
- "recommendations": "Sugestoes para proximas tasks"
284
- },
285
- "patterns_discovered": ["Pattern identificado"],
286
- "decisions_made": [{"title": "...", "decision": "..."}],
287
- "blockers": ["Se status != completed"],
288
- "knowledge_to_broadcast": [{"category": "discovery|pattern|constraint", "content": "...", "severity": "info|warning|critical"}]
289
- }
290
-
291
- ATENCAO: O campo "reasoning.approach" e OBRIGATORIO para status "completed".
292
- Se retornar sem ele, Gate 4.4 BLOQUEIA a finalizacao da task.
293
- Descreva COMO abordou o problema, nao apenas O QUE fez.
294
-
295
- Se NAO conseguir criar arquivos (sem permissao, sem ferramentas), retorne:
296
- {
297
- "status": "blocked",
298
- "blockers": ["Descreva por que nao conseguiu criar os arquivos"]
299
- }
300
-
301
- Veja .claude/agents/PROTOCOL.md para detalhes completos.
302
- ${formattedPatterns.length > 0 ? `
303
- PATTERNS: Voce recebeu ${formattedPatterns.length} implementation patterns extraidos do projeto.
304
- Use os TEMPLATES fornecidos para criar codigo CONSISTENTE com o projeto existente.
305
- ` : ''}
306
- `.trim(),
239
+ subagentReturnProtocol: loadTemplate("subagent-return-protocol", {
240
+ patternsNote: formattedPatterns.length > 0
241
+ ? `\nPATTERNS: Voce recebeu ${formattedPatterns.length} implementation patterns extraidos do projeto.\nUse os TEMPLATES fornecidos para criar codigo CONSISTENTE com o projeto existente.\n`
242
+ : '',
243
+ }),
307
244
  };
308
245
  });
309
246
  console.log(JSON.stringify({ started: contexts }));
@@ -325,8 +262,7 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
325
262
  const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
326
263
 
327
264
  if (!task) {
328
- console.error(`\nTask #${taskId} nao encontrada.\n`);
329
- process.exit(2);
265
+ throw new TaskStateError(`Task #${taskId} nao encontrada.`);
330
266
  }
331
267
 
332
268
  const spec = db.query("SELECT * FROM specs WHERE id = ?").get(task.spec_id) as any;
@@ -344,10 +280,11 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
344
280
  const parseResult = parseSubagentReturn(options.output);
345
281
 
346
282
  if (!parseResult.success) {
347
- console.error(formatValidationErrors(parseResult));
348
- console.error("\nTask NAO pode ser completada sem retorno valido.");
349
- console.error("Corrija o formato do retorno do subagent e tente novamente.\n");
350
- process.exit(2);
283
+ throw new ValidationError(
284
+ formatValidationErrors(parseResult) +
285
+ "\nTask NAO pode ser completada sem retorno valido." +
286
+ "\nCorrija o formato do retorno do subagent e tente novamente."
287
+ );
351
288
  }
352
289
 
353
290
  subagentData = parseResult.data!;
@@ -411,14 +348,19 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
411
348
  .filter((k: any) => k.severity === 'critical' && k.task_origin !== taskId);
412
349
  if (unackedCritical.length > 0) {
413
350
  if (!options.force) {
414
- console.error(`\n[X] BLOQUEADO: ${unackedCritical.length} knowledge(s) critico(s) nao reconhecido(s):`);
415
- for (const k of unackedCritical) {
416
- console.error(` [X] ${k.content} (de Task #${k.task_origin})`);
417
- }
418
- console.error(`\n O subagent NAO verificou o polling obrigatorio.`);
419
- console.error(` Reconheca com: knowledge ack <id>`);
420
- console.error(` Ou force com: task done ${id} --checkpoint "..." --force --force-reason "motivo"\n`);
421
- process.exit(1);
351
+ // Task permanece em "running" (subagent output ja processado acima).
352
+ // Nao pode ser marcada "done" ate knowledge ser reconhecido.
353
+ const items = unackedCritical.map(
354
+ (k: any) => ` [X] ${k.content} (de Task #${k.task_origin})`
355
+ ).join("\n");
356
+
357
+ throw new KnowledgeBlockError(
358
+ `BLOQUEADO: ${unackedCritical.length} knowledge(s) critico(s) nao reconhecido(s):\n${items}\n\n` +
359
+ `O subagent NAO verificou o polling obrigatorio.\n` +
360
+ `Reconheca com: knowledge ack <id>\n` +
361
+ `Ou force com: task done ${id} --checkpoint "..." --force --force-reason "motivo"`,
362
+ unackedCritical
363
+ );
422
364
  } else {
423
365
  console.log(`\n[!] AVISO: ${unackedCritical.length} knowledge(s) critico(s) ignorado(s) (--force usado)`);
424
366
  for (const k of unackedCritical) {
@@ -494,73 +436,15 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
494
436
  [checkpoint, now, spec.id]
495
437
  );
496
438
 
497
- // Criar snapshot automatico COMPLETO
498
- const allTasks = db.query("SELECT * FROM tasks WHERE spec_id = ?").all(spec.id) as any[];
499
- const allDecisions = db.query("SELECT * FROM decisions WHERE spec_id = ?").all(spec.id) as any[];
500
- const allArtifacts = db.query("SELECT * FROM artifacts WHERE spec_id = ?").all(spec.id) as any[];
501
- const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id);
502
-
503
- const snapshotData = {
504
- spec,
505
- context,
506
- tasks: allTasks,
507
- decisions: allDecisions,
508
- artifacts: allArtifacts,
509
- checkpoint: options.checkpoint,
510
- taskCompleted: task.number,
511
- timestamp: now,
512
- };
513
- db.run("INSERT INTO snapshots (spec_id, data, trigger, created_at) VALUES (?, ?, 'auto', ?)", [
514
- spec.id,
515
- JSON.stringify(snapshotData),
516
- now,
517
- ]);
518
-
519
- // v8.1: Gerar session summary automaticamente
520
- try {
521
- const taskDecisions = allDecisions.filter((d) => d.task_ref === task.number);
522
- const taskArtifacts = allArtifacts.filter((a) => a.task_ref === task.number);
523
- const blockers = db.query(
524
- "SELECT content FROM knowledge WHERE spec_id = ? AND task_origin = ? AND category = 'blocker'"
525
- ).all(spec.id, taskId) as any[];
526
- const reasoning = getRecentReasoning(spec.id, 5);
527
- const nextSteps: string[] = [];
528
-
529
- // Extrair recommendations do reasoning
530
- for (const r of reasoning) {
531
- if (r.category === 'recommendation' && r.thought) {
532
- nextSteps.push(r.thought);
533
- }
534
- }
535
-
536
- // Se nao completou tudo, sugerir proximo passo
537
- if (doneCount.c < totalCount.c) {
538
- nextSteps.push(`Continuar implementacao: ${totalCount.c - doneCount.c} tasks restantes`);
539
- } else {
540
- nextSteps.push("Todas tasks concluidas. Iniciar review.");
541
- }
542
-
543
- addSessionSummary(spec.id, {
544
- startTime: task.completed_at || now, // approximation
545
- endTime: now,
546
- summary: `Task #${task.number} (${task.name}) concluida. ${checkpoint}`,
547
- decisions: taskDecisions.map((d: any) => `${d.title}: ${d.decision}`),
548
- blockers: blockers.map((b: any) => b.content),
549
- nextSteps,
550
- tasksCompleted: 1,
551
- filesCreated: taskArtifacts.filter((a: any) => a.action === 'created').length,
552
- filesModified: taskArtifacts.filter((a: any) => a.action === 'modified').length,
553
- });
554
- } catch (e) {
555
- // Session summary e best-effort, nao deve bloquear o fluxo
556
- }
557
-
558
439
  console.log(`\nTask #${task.number} concluida!`);
559
440
  console.log(`Checkpoint: ${options.checkpoint}`);
560
441
  console.log(`Progresso: ${doneCount.c}/${totalCount.c} tasks`);
561
442
 
562
443
  if (doneCount.c === totalCount.c) {
563
444
  // Mostrar resumo completo da implementacao
445
+ const allTasks = db.query("SELECT * FROM tasks WHERE spec_id = ?").all(spec.id) as any[];
446
+ const allDecisions = db.query("SELECT * FROM decisions WHERE spec_id = ?").all(spec.id) as any[];
447
+ const allArtifacts = db.query("SELECT * FROM artifacts WHERE spec_id = ?").all(spec.id) as any[];
564
448
  showImplementationSummary(spec.id, allTasks, allArtifacts, allDecisions);
565
449
  } else {
566
450
  console.log(`\nProximas tasks: task next\n`);