@codexa/cli 9.0.31 → 9.0.33
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/architect.ts +52 -87
- package/commands/check.ts +22 -23
- package/commands/clear.ts +42 -48
- package/commands/decide.ts +46 -44
- package/commands/discover.ts +81 -94
- package/commands/integration.test.ts +262 -313
- package/commands/knowledge.test.ts +56 -61
- package/commands/knowledge.ts +126 -131
- package/commands/patterns.ts +28 -43
- package/commands/plan.ts +50 -48
- package/commands/product.ts +57 -59
- package/commands/research.ts +64 -77
- package/commands/review.ts +100 -86
- package/commands/simplify.ts +24 -35
- package/commands/spec-resolver.test.ts +52 -48
- package/commands/spec-resolver.ts +21 -23
- package/commands/standards.ts +20 -27
- package/commands/sync.ts +2 -8
- package/commands/task.ts +106 -97
- package/commands/team.test.ts +22 -83
- package/commands/team.ts +62 -50
- package/commands/utils.ts +83 -81
- package/context/assembly.ts +0 -1
- package/context/generator.ts +66 -79
- package/context/sections.ts +8 -14
- package/db/connection.ts +209 -19
- package/db/schema.test.ts +288 -299
- package/db/schema.ts +298 -394
- package/db/test-helpers.ts +18 -29
- package/gates/standards-validator.test.ts +83 -86
- package/gates/standards-validator.ts +9 -41
- package/gates/validator.test.ts +13 -22
- package/gates/validator.ts +69 -107
- package/package.json +2 -1
- package/protocol/process-return.ts +41 -57
- package/simplify/prompt-builder.test.ts +44 -42
- package/simplify/prompt-builder.ts +12 -14
- package/workflow.ts +159 -174
package/commands/task.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dbGet, dbAll, dbRun } from "../db/connection";
|
|
2
2
|
import { initSchema, getPatternsForFiles, getPatternsByScope, getRecentReasoning, claimTask, recordAgentPerformance, detectStuckTasks } from "../db/schema";
|
|
3
3
|
import { enforceGate } from "../gates/validator";
|
|
4
4
|
import { parseSubagentReturn, formatValidationErrors } from "../protocol/subagent-protocol";
|
|
@@ -16,10 +16,9 @@ import { getContextBudget, formatContextWarning, estimateTokens } from "../conte
|
|
|
16
16
|
import { isAutoSimplifyEnabled } from "./simplify";
|
|
17
17
|
import { buildSimplifyPrompt, writeSimplifyContext, type SimplifyFile } from "../simplify/prompt-builder";
|
|
18
18
|
|
|
19
|
-
export function taskNext(json: boolean = false, specId?: string): void {
|
|
20
|
-
initSchema();
|
|
19
|
+
export async function taskNext(json: boolean = false, specId?: string): Promise<void> {
|
|
20
|
+
await initSchema();
|
|
21
21
|
|
|
22
|
-
const db = getDb();
|
|
23
22
|
const spec = resolveSpecOrNull(specId, ["implementing"]);
|
|
24
23
|
|
|
25
24
|
if (!spec) {
|
|
@@ -31,14 +30,12 @@ export function taskNext(json: boolean = false, specId?: string): void {
|
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
// v9.9: Get current phase info
|
|
34
|
-
const context =
|
|
33
|
+
const context = await dbGet<any>("SELECT * FROM context WHERE spec_id = ?", [spec.id]);
|
|
35
34
|
const currentPhase = context?.current_phase || 1;
|
|
36
35
|
const totalPhases = context?.total_phases || 1;
|
|
37
36
|
|
|
38
37
|
// Buscar tasks pendentes cujas dependencias estao todas concluidas
|
|
39
|
-
const allTasks =
|
|
40
|
-
.query("SELECT * FROM tasks WHERE spec_id = ? ORDER BY number")
|
|
41
|
-
.all(spec.id) as any[];
|
|
38
|
+
const allTasks = await dbAll<any>("SELECT * FROM tasks WHERE spec_id = ? ORDER BY number", [spec.id]);
|
|
42
39
|
|
|
43
40
|
const available: any[] = [];
|
|
44
41
|
|
|
@@ -86,7 +83,7 @@ export function taskNext(json: boolean = false, specId?: string): void {
|
|
|
86
83
|
} else {
|
|
87
84
|
console.log("\nNenhuma task disponivel no momento.");
|
|
88
85
|
console.log(`Tasks em execucao ou bloqueadas: ${pending.length}`);
|
|
89
|
-
const stuck = detectStuckTasks(spec.id);
|
|
86
|
+
const stuck = await detectStuckTasks(spec.id);
|
|
90
87
|
if (stuck.length > 0) {
|
|
91
88
|
showStuckWarning(stuck);
|
|
92
89
|
} else {
|
|
@@ -150,11 +147,10 @@ function showStuckWarning(stuck: any[]): void {
|
|
|
150
147
|
console.log(` Use: task done <id> --force --force-reason "timeout" para liberar\n`);
|
|
151
148
|
}
|
|
152
149
|
|
|
153
|
-
export function taskStart(ids: string, json: boolean = false, minimalContext: boolean = false, specId?: string): void {
|
|
154
|
-
initSchema();
|
|
150
|
+
export async function taskStart(ids: string, json: boolean = false, minimalContext: boolean = false, specId?: string): Promise<void> {
|
|
151
|
+
await initSchema();
|
|
155
152
|
enforceGate("task-start");
|
|
156
153
|
|
|
157
|
-
const db = getDb();
|
|
158
154
|
const now = new Date().toISOString();
|
|
159
155
|
|
|
160
156
|
const spec = resolveSpec(specId, ["implementing"]);
|
|
@@ -163,7 +159,7 @@ export function taskStart(ids: string, json: boolean = false, minimalContext: bo
|
|
|
163
159
|
const startedTasks: any[] = [];
|
|
164
160
|
|
|
165
161
|
for (const taskId of taskIds) {
|
|
166
|
-
const task =
|
|
162
|
+
const task = await dbGet<any>("SELECT * FROM tasks WHERE id = ?", [taskId]);
|
|
167
163
|
|
|
168
164
|
if (!task) {
|
|
169
165
|
throw new TaskStateError(`Task #${taskId} nao encontrada.`);
|
|
@@ -173,8 +169,8 @@ export function taskStart(ids: string, json: boolean = false, minimalContext: bo
|
|
|
173
169
|
enforceGate("task-start", { taskId });
|
|
174
170
|
|
|
175
171
|
// Claim atomico: UPDATE ... WHERE status = 'pending'
|
|
176
|
-
if (!claimTask(taskId)) {
|
|
177
|
-
const current =
|
|
172
|
+
if (!(await claimTask(taskId))) {
|
|
173
|
+
const current = await dbGet<any>("SELECT status FROM tasks WHERE id = ?", [taskId]);
|
|
178
174
|
throw new TaskStateError(
|
|
179
175
|
`Task #${task.number} nao pode ser iniciada (status atual: ${current?.status || "desconhecido"}).`
|
|
180
176
|
);
|
|
@@ -184,19 +180,20 @@ export function taskStart(ids: string, json: boolean = false, minimalContext: bo
|
|
|
184
180
|
}
|
|
185
181
|
|
|
186
182
|
// Atualizar contexto
|
|
187
|
-
|
|
183
|
+
await dbRun("UPDATE context SET current_task = ?, updated_at = ? WHERE spec_id = ?", [
|
|
188
184
|
startedTasks[0].number,
|
|
189
185
|
now,
|
|
190
186
|
spec.id,
|
|
191
187
|
]);
|
|
192
188
|
|
|
193
189
|
// v9.0: Mostrar resumo de knowledge relevante antes de iniciar
|
|
194
|
-
const allKnowledge =
|
|
190
|
+
const allKnowledge = await dbAll<any>(
|
|
195
191
|
`SELECT * FROM knowledge
|
|
196
192
|
WHERE spec_id = ? AND severity IN ('critical', 'warning')
|
|
197
193
|
ORDER BY severity DESC, created_at DESC
|
|
198
|
-
LIMIT 10
|
|
199
|
-
|
|
194
|
+
LIMIT 10`,
|
|
195
|
+
[spec.id]
|
|
196
|
+
);
|
|
200
197
|
|
|
201
198
|
if (allKnowledge.length > 0 && !json) {
|
|
202
199
|
console.log(`\n[i] Knowledge ativo (${allKnowledge.length}):`);
|
|
@@ -211,7 +208,8 @@ export function taskStart(ids: string, json: boolean = false, minimalContext: bo
|
|
|
211
208
|
}
|
|
212
209
|
|
|
213
210
|
if (json) {
|
|
214
|
-
const contexts =
|
|
211
|
+
const contexts: any[] = [];
|
|
212
|
+
for (const task of startedTasks) {
|
|
215
213
|
const taskFiles = task.files ? JSON.parse(task.files) : [];
|
|
216
214
|
const agentDomain = getAgentDomain(task.agent);
|
|
217
215
|
const agentScope = domainToScope(agentDomain);
|
|
@@ -233,16 +231,15 @@ export function taskStart(ids: string, json: boolean = false, minimalContext: bo
|
|
|
233
231
|
|
|
234
232
|
// v11.0: Inline context delivery — full context in prompt, no files
|
|
235
233
|
const inlineContext = minimalContext
|
|
236
|
-
? getMinimalContextForSubagent(task.id)
|
|
237
|
-
: getContextForSubagent(task.id);
|
|
234
|
+
? await getMinimalContextForSubagent(task.id)
|
|
235
|
+
: await getContextForSubagent(task.id);
|
|
238
236
|
|
|
239
237
|
// Build pending knowledge section
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
})();
|
|
238
|
+
const unreadForPrompt = await getUnreadKnowledgeForTask(spec.id, task.id);
|
|
239
|
+
const criticalForPrompt = unreadForPrompt.filter((k: any) => k.severity === 'critical' && k.task_origin !== task.id);
|
|
240
|
+
const pendingKnowledgeForPrompt = criticalForPrompt.length === 0
|
|
241
|
+
? ""
|
|
242
|
+
: `## KNOWLEDGE PENDENTE (OBRIGATORIO)\n\nVoce DEVE reconhecer estes items antes de completar a task:\n${criticalForPrompt.map((k: any) => `- [ID ${k.id}] ${k.content}`).join("\n")}\n\nUse: \`codexa knowledge ack <id>\` para cada item.`;
|
|
246
243
|
|
|
247
244
|
// Pre-built prompt for subagent (orchestrator just passes it)
|
|
248
245
|
const subagentPrompt = loadTemplate("subagent-prompt-lean", {
|
|
@@ -258,9 +255,11 @@ export function taskStart(ids: string, json: boolean = false, minimalContext: bo
|
|
|
258
255
|
});
|
|
259
256
|
|
|
260
257
|
// Context budget estimation
|
|
261
|
-
const
|
|
262
|
-
"SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND status = 'done'"
|
|
263
|
-
|
|
258
|
+
const completedTaskRow = await dbGet<any>(
|
|
259
|
+
"SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND status = 'done'",
|
|
260
|
+
[spec.id]
|
|
261
|
+
);
|
|
262
|
+
const completedTaskCount = completedTaskRow?.c || 0;
|
|
264
263
|
|
|
265
264
|
const agentProfile = getProfileForDomain(agentDomain);
|
|
266
265
|
const contextBudget = getContextBudget(subagentPrompt, completedTaskCount, agentProfile);
|
|
@@ -286,7 +285,7 @@ export function taskStart(ids: string, json: boolean = false, minimalContext: bo
|
|
|
286
285
|
};
|
|
287
286
|
|
|
288
287
|
// Unread knowledge for orchestrator awareness
|
|
289
|
-
const unreadKnowledge = getUnreadKnowledgeForTask(spec.id, task.id);
|
|
288
|
+
const unreadKnowledge = await getUnreadKnowledgeForTask(spec.id, task.id);
|
|
290
289
|
if (unreadKnowledge.length > 0) {
|
|
291
290
|
output.unreadKnowledge = unreadKnowledge.map((k: any) => ({
|
|
292
291
|
id: k.id, category: k.category, content: k.content,
|
|
@@ -294,8 +293,8 @@ export function taskStart(ids: string, json: boolean = false, minimalContext: bo
|
|
|
294
293
|
}));
|
|
295
294
|
}
|
|
296
295
|
|
|
297
|
-
|
|
298
|
-
}
|
|
296
|
+
contexts.push(output);
|
|
297
|
+
}
|
|
299
298
|
console.log(JSON.stringify({ started: contexts }));
|
|
300
299
|
return;
|
|
301
300
|
}
|
|
@@ -307,18 +306,17 @@ export function taskStart(ids: string, json: boolean = false, minimalContext: bo
|
|
|
307
306
|
console.log(`\nAo concluir, use: task done <id> --checkpoint "resumo"\n`);
|
|
308
307
|
}
|
|
309
308
|
|
|
310
|
-
export function taskDone(id: string, options: { checkpoint: string; files?: string; force?: boolean; forceReason?: string; output?: string; simplify?: boolean }): void {
|
|
311
|
-
initSchema();
|
|
309
|
+
export async function taskDone(id: string, options: { checkpoint: string; files?: string; force?: boolean; forceReason?: string; output?: string; simplify?: boolean }): Promise<void> {
|
|
310
|
+
await initSchema();
|
|
312
311
|
|
|
313
312
|
const taskId = parseInt(id);
|
|
314
|
-
const
|
|
315
|
-
const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
|
|
313
|
+
const task = await dbGet<any>("SELECT * FROM tasks WHERE id = ?", [taskId]);
|
|
316
314
|
|
|
317
315
|
if (!task) {
|
|
318
316
|
throw new TaskStateError(`Task #${taskId} nao encontrada.`);
|
|
319
317
|
}
|
|
320
318
|
|
|
321
|
-
const spec =
|
|
319
|
+
const spec = await dbGet<any>("SELECT * FROM specs WHERE id = ?", [task.spec_id]);
|
|
322
320
|
const now = new Date().toISOString();
|
|
323
321
|
|
|
324
322
|
// ============================================================
|
|
@@ -397,7 +395,8 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
|
|
|
397
395
|
console.log(formatProcessResult(processResult));
|
|
398
396
|
|
|
399
397
|
// v8.3: BLOCKING check para knowledge critico nao reconhecido (substitui warning v8.2)
|
|
400
|
-
const
|
|
398
|
+
const allUnackedKnowledge = await getUnreadKnowledgeForTask(spec.id, taskId);
|
|
399
|
+
const unackedCritical = allUnackedKnowledge
|
|
401
400
|
.filter((k: any) => k.severity === 'critical' && k.task_origin !== taskId);
|
|
402
401
|
if (unackedCritical.length > 0) {
|
|
403
402
|
if (!options.force) {
|
|
@@ -479,7 +478,7 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
|
|
|
479
478
|
}
|
|
480
479
|
|
|
481
480
|
// Marcar como done
|
|
482
|
-
|
|
481
|
+
await dbRun(
|
|
483
482
|
"UPDATE tasks SET status = 'done', checkpoint = ?, completed_at = ? WHERE id = ?",
|
|
484
483
|
[checkpoint, now, taskId]
|
|
485
484
|
);
|
|
@@ -489,12 +488,14 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
|
|
|
489
488
|
const agentType = task.agent || "general-purpose";
|
|
490
489
|
const startedAt = task.started_at ? new Date(task.started_at).getTime() : Date.now();
|
|
491
490
|
const duration = Date.now() - startedAt;
|
|
492
|
-
const
|
|
493
|
-
"SELECT COUNT(*) as c FROM gate_bypasses WHERE task_id = ?"
|
|
494
|
-
|
|
491
|
+
const bypassRow = await dbGet<any>(
|
|
492
|
+
"SELECT COUNT(*) as c FROM gate_bypasses WHERE task_id = ?",
|
|
493
|
+
[taskId]
|
|
494
|
+
);
|
|
495
|
+
const bypassCount = bypassRow?.c || 0;
|
|
495
496
|
const totalGates = 8;
|
|
496
497
|
|
|
497
|
-
recordAgentPerformance({
|
|
498
|
+
await recordAgentPerformance({
|
|
498
499
|
agentType,
|
|
499
500
|
specId: spec.id,
|
|
500
501
|
taskId,
|
|
@@ -503,7 +504,7 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
|
|
|
503
504
|
bypassesUsed: bypassCount,
|
|
504
505
|
filesCreated: subagentData?.files_created?.length || 0,
|
|
505
506
|
filesModified: subagentData?.files_modified?.length || 0,
|
|
506
|
-
contextSizeBytes: estimateTokens(getContextForSubagent(task.id)) * 4,
|
|
507
|
+
contextSizeBytes: estimateTokens(await getContextForSubagent(task.id)) * 4,
|
|
507
508
|
executionDurationMs: duration,
|
|
508
509
|
});
|
|
509
510
|
} catch { /* nao-critico: nao falhar task done por tracking de performance */ }
|
|
@@ -512,7 +513,7 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
|
|
|
512
513
|
if (!subagentData && options.files) {
|
|
513
514
|
const files = options.files.split(",").map((s) => s.trim());
|
|
514
515
|
for (const file of files) {
|
|
515
|
-
|
|
516
|
+
await dbRun(
|
|
516
517
|
`INSERT OR REPLACE INTO artifacts (spec_id, task_ref, path, action, created_at)
|
|
517
518
|
VALUES (?, ?, ?, 'created', ?)`,
|
|
518
519
|
[spec.id, task.number, file, now]
|
|
@@ -521,52 +522,58 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
|
|
|
521
522
|
}
|
|
522
523
|
|
|
523
524
|
// Atualizar contexto
|
|
524
|
-
const doneCount =
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
525
|
+
const doneCount = await dbGet<any>(
|
|
526
|
+
"SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND status = 'done'",
|
|
527
|
+
[spec.id]
|
|
528
|
+
);
|
|
529
|
+
const totalCount = await dbGet<any>(
|
|
530
|
+
"SELECT COUNT(*) as c FROM tasks WHERE spec_id = ?",
|
|
531
|
+
[spec.id]
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
await dbRun(
|
|
532
535
|
"UPDATE context SET last_checkpoint = ?, updated_at = ? WHERE spec_id = ?",
|
|
533
536
|
[checkpoint, now, spec.id]
|
|
534
537
|
);
|
|
535
538
|
|
|
536
539
|
// v9.9: Phase-aware progress
|
|
537
|
-
const ctx =
|
|
540
|
+
const ctx = await dbGet<any>("SELECT * FROM context WHERE spec_id = ?", [spec.id]);
|
|
538
541
|
const currentPhase = ctx?.current_phase || 1;
|
|
539
542
|
const totalPhases = ctx?.total_phases || 1;
|
|
540
543
|
|
|
541
544
|
console.log(`\nTask #${task.number} concluida!`);
|
|
542
545
|
console.log(`Checkpoint: ${options.checkpoint}`);
|
|
543
|
-
console.log(`Progresso: ${doneCount
|
|
546
|
+
console.log(`Progresso: ${doneCount?.c || 0}/${totalCount?.c || 0} tasks`);
|
|
544
547
|
|
|
545
548
|
if (totalPhases > 1) {
|
|
546
|
-
const phaseTasks =
|
|
547
|
-
|
|
548
|
-
|
|
549
|
+
const phaseTasks = await dbAll<any>(
|
|
550
|
+
"SELECT * FROM tasks WHERE spec_id = ? AND phase = ?",
|
|
551
|
+
[spec.id, currentPhase]
|
|
552
|
+
);
|
|
549
553
|
const phaseDone = phaseTasks.filter((t: any) => t.status === "done").length;
|
|
550
554
|
console.log(`Fase ${currentPhase}/${totalPhases}: ${phaseDone}/${phaseTasks.length} tasks`);
|
|
551
555
|
|
|
552
556
|
if (phaseDone === phaseTasks.length && currentPhase < totalPhases) {
|
|
553
557
|
// Phase complete — show phase summary
|
|
554
|
-
const phaseDecisions =
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
558
|
+
const phaseDecisions = await dbGet<any>(
|
|
559
|
+
"SELECT COUNT(*) as c FROM decisions WHERE spec_id = ?",
|
|
560
|
+
[spec.id]
|
|
561
|
+
);
|
|
562
|
+
const phaseKnowledge = await dbGet<any>(
|
|
563
|
+
"SELECT COUNT(*) as c FROM knowledge WHERE spec_id = ? AND severity != 'archived'",
|
|
564
|
+
[spec.id]
|
|
565
|
+
);
|
|
560
566
|
|
|
561
|
-
const nextPhaseTasks =
|
|
562
|
-
|
|
563
|
-
|
|
567
|
+
const nextPhaseTasks = await dbGet<any>(
|
|
568
|
+
"SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND phase = ?",
|
|
569
|
+
[spec.id, currentPhase + 1]
|
|
570
|
+
);
|
|
564
571
|
|
|
565
572
|
console.log(`\n${"=".repeat(55)}`);
|
|
566
573
|
console.log(`FASE ${currentPhase} CONCLUIDA (${phaseDone}/${phaseTasks.length} tasks)`);
|
|
567
574
|
console.log(`${"=".repeat(55)}`);
|
|
568
|
-
console.log(` Decisions acumuladas: ${phaseDecisions
|
|
569
|
-
console.log(` Knowledge acumulado: ${phaseKnowledge
|
|
575
|
+
console.log(` Decisions acumuladas: ${phaseDecisions?.c || 0}`);
|
|
576
|
+
console.log(` Knowledge acumulado: ${phaseKnowledge?.c || 0}`);
|
|
570
577
|
console.log(`\n Proxima fase: ${currentPhase + 1}/${totalPhases} (${nextPhaseTasks?.c || 0} tasks)`);
|
|
571
578
|
console.log(`\n Recomendacao: Compactar contexto antes de continuar`);
|
|
572
579
|
console.log(` -> codexa task phase-advance [--no-compact] [--spec ${spec.id}]`);
|
|
@@ -575,12 +582,12 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
|
|
|
575
582
|
}
|
|
576
583
|
}
|
|
577
584
|
|
|
578
|
-
if (doneCount
|
|
585
|
+
if (doneCount?.c === totalCount?.c) {
|
|
579
586
|
// Mostrar resumo completo da implementacao
|
|
580
|
-
const allTasks =
|
|
581
|
-
const allDecisions =
|
|
582
|
-
const allArtifacts =
|
|
583
|
-
showImplementationSummary(spec.id, allTasks, allArtifacts, allDecisions);
|
|
587
|
+
const allTasks = await dbAll<any>("SELECT * FROM tasks WHERE spec_id = ?", [spec.id]);
|
|
588
|
+
const allDecisions = await dbAll<any>("SELECT * FROM decisions WHERE spec_id = ?", [spec.id]);
|
|
589
|
+
const allArtifacts = await dbAll<any>("SELECT * FROM artifacts WHERE spec_id = ?", [spec.id]);
|
|
590
|
+
await showImplementationSummary(spec.id, allTasks, allArtifacts, allDecisions);
|
|
584
591
|
} else {
|
|
585
592
|
console.log(`\nProximas tasks: task next\n`);
|
|
586
593
|
}
|
|
@@ -590,15 +597,14 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
|
|
|
590
597
|
* Mostra resumo detalhado da implementacao apos todas as tasks concluidas
|
|
591
598
|
* Permite ao usuario decidir se quer fazer review ou nao
|
|
592
599
|
*/
|
|
593
|
-
function showImplementationSummary(
|
|
600
|
+
async function showImplementationSummary(
|
|
594
601
|
specId: number,
|
|
595
602
|
tasks: any[],
|
|
596
603
|
artifacts: any[],
|
|
597
604
|
decisions: any[]
|
|
598
|
-
): void {
|
|
599
|
-
const
|
|
600
|
-
const
|
|
601
|
-
const knowledge = db.query("SELECT * FROM knowledge WHERE spec_id = ?").all(specId) as any[];
|
|
605
|
+
): Promise<void> {
|
|
606
|
+
const spec = await dbGet<any>("SELECT * FROM specs WHERE id = ?", [specId]);
|
|
607
|
+
const knowledge = await dbAll<any>("SELECT * FROM knowledge WHERE spec_id = ?", [specId]);
|
|
602
608
|
|
|
603
609
|
console.log(`\n${"=".repeat(60)}`);
|
|
604
610
|
console.log(`IMPLEMENTACAO CONCLUIDA: ${spec.name}`);
|
|
@@ -694,12 +700,11 @@ function showImplementationSummary(
|
|
|
694
700
|
}
|
|
695
701
|
|
|
696
702
|
// v9.9: Phase advance — compact context and move to next phase
|
|
697
|
-
export function taskPhaseAdvance(options: { noCompact?: boolean; json?: boolean; spec?: string } = {}): void {
|
|
698
|
-
initSchema();
|
|
699
|
-
const db = getDb();
|
|
703
|
+
export async function taskPhaseAdvance(options: { noCompact?: boolean; json?: boolean; spec?: string } = {}): Promise<void> {
|
|
704
|
+
await initSchema();
|
|
700
705
|
|
|
701
706
|
const spec = resolveSpec(options.spec, ["implementing"]);
|
|
702
|
-
const ctx =
|
|
707
|
+
const ctx = await dbGet<any>("SELECT * FROM context WHERE spec_id = ?", [spec.id]);
|
|
703
708
|
const currentPhase = ctx?.current_phase || 1;
|
|
704
709
|
const totalPhases = ctx?.total_phases || 1;
|
|
705
710
|
|
|
@@ -715,9 +720,10 @@ export function taskPhaseAdvance(options: { noCompact?: boolean; json?: boolean;
|
|
|
715
720
|
}
|
|
716
721
|
|
|
717
722
|
// Verify current phase is fully complete
|
|
718
|
-
const phaseTasks =
|
|
719
|
-
|
|
720
|
-
|
|
723
|
+
const phaseTasks = await dbAll<any>(
|
|
724
|
+
"SELECT * FROM tasks WHERE spec_id = ? AND phase = ?",
|
|
725
|
+
[spec.id, currentPhase]
|
|
726
|
+
);
|
|
721
727
|
const pendingInPhase = phaseTasks.filter((t: any) => t.status !== "done" && t.status !== "cancelled");
|
|
722
728
|
|
|
723
729
|
if (pendingInPhase.length > 0) {
|
|
@@ -732,8 +738,8 @@ export function taskPhaseAdvance(options: { noCompact?: boolean; json?: boolean;
|
|
|
732
738
|
// Compact knowledge, reasoning, and clean up stale files (unless --no-compact)
|
|
733
739
|
if (!options.noCompact) {
|
|
734
740
|
console.log(`\nCompactando contexto da fase ${currentPhase}...`);
|
|
735
|
-
compactKnowledge({ specId: spec.id });
|
|
736
|
-
const reasoningResult = compactReasoning({ specId: spec.id });
|
|
741
|
+
await compactKnowledge({ specId: spec.id });
|
|
742
|
+
const reasoningResult = await compactReasoning({ specId: spec.id });
|
|
737
743
|
if (reasoningResult.archived > 0) {
|
|
738
744
|
console.log(` Reasoning compactado: ${reasoningResult.archived} entradas removidas, ${reasoningResult.kept} mantidas`);
|
|
739
745
|
}
|
|
@@ -741,12 +747,14 @@ export function taskPhaseAdvance(options: { noCompact?: boolean; json?: boolean;
|
|
|
741
747
|
|
|
742
748
|
// Create phase summary as critical knowledge
|
|
743
749
|
const phaseTaskNames = phaseTasks.map((t: any) => t.name).join(", ");
|
|
744
|
-
const
|
|
745
|
-
const
|
|
750
|
+
const decisionCountRow = await dbGet<any>("SELECT COUNT(*) as c FROM decisions WHERE spec_id = ?", [spec.id]);
|
|
751
|
+
const artifactCountRow = await dbGet<any>("SELECT COUNT(*) as c FROM artifacts WHERE spec_id = ?", [spec.id]);
|
|
752
|
+
const decisionCount = decisionCountRow?.c || 0;
|
|
753
|
+
const artifactCount = artifactCountRow?.c || 0;
|
|
746
754
|
|
|
747
755
|
const summaryContent = `Resumo Fase ${currentPhase}: ${phaseTasks.length} tasks concluidas (${phaseTaskNames.substring(0, 200)}). ${decisionCount} decisions, ${artifactCount} artefatos acumulados.`;
|
|
748
756
|
|
|
749
|
-
|
|
757
|
+
await dbRun(
|
|
750
758
|
`INSERT INTO knowledge (spec_id, task_origin, category, content, severity, broadcast_to, created_at)
|
|
751
759
|
VALUES (?, 0, 'phase_summary', ?, 'critical', 'all', ?)`,
|
|
752
760
|
[spec.id, summaryContent, now]
|
|
@@ -754,15 +762,16 @@ export function taskPhaseAdvance(options: { noCompact?: boolean; json?: boolean;
|
|
|
754
762
|
|
|
755
763
|
// Advance to next phase
|
|
756
764
|
const nextPhase = currentPhase + 1;
|
|
757
|
-
|
|
765
|
+
await dbRun(
|
|
758
766
|
"UPDATE context SET current_phase = ?, updated_at = ? WHERE spec_id = ?",
|
|
759
767
|
[nextPhase, now, spec.id]
|
|
760
768
|
);
|
|
761
769
|
|
|
762
770
|
// Show next phase info
|
|
763
|
-
const nextPhaseTasks =
|
|
764
|
-
|
|
765
|
-
|
|
771
|
+
const nextPhaseTasks = await dbAll<any>(
|
|
772
|
+
"SELECT * FROM tasks WHERE spec_id = ? AND phase = ? ORDER BY number",
|
|
773
|
+
[spec.id, nextPhase]
|
|
774
|
+
);
|
|
766
775
|
|
|
767
776
|
if (options.json) {
|
|
768
777
|
console.log(JSON.stringify({
|
package/commands/team.test.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
|
2
|
-
import {
|
|
2
|
+
import { createClient } from "@libsql/client";
|
|
3
|
+
import { setClient, resetClient, dbRun } from "../db/connection";
|
|
4
|
+
import { initSchema } from "../db/schema";
|
|
3
5
|
import {
|
|
4
6
|
detectPhase,
|
|
5
7
|
buildLeadSpawnInstruction,
|
|
@@ -10,103 +12,40 @@ import {
|
|
|
10
12
|
type TeammateConfig,
|
|
11
13
|
} from "./team";
|
|
12
14
|
|
|
13
|
-
// ─────────────────────────────────────────────────────────────────
|
|
14
|
-
// Helpers
|
|
15
|
-
// ─────────────────────────────────────────────────────────────────
|
|
16
|
-
|
|
17
|
-
function createTestDb(): Database {
|
|
18
|
-
const db = new Database(":memory:");
|
|
19
|
-
|
|
20
|
-
db.run(`CREATE TABLE specs (
|
|
21
|
-
id TEXT PRIMARY KEY, name TEXT, phase TEXT, approved_at TEXT,
|
|
22
|
-
analysis_id TEXT, created_at TEXT, updated_at TEXT
|
|
23
|
-
)`);
|
|
24
|
-
|
|
25
|
-
db.run(`CREATE TABLE tasks (
|
|
26
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT, spec_id TEXT, number INTEGER,
|
|
27
|
-
name TEXT, depends_on TEXT, can_parallel INTEGER DEFAULT 1,
|
|
28
|
-
agent TEXT, files TEXT, status TEXT DEFAULT 'pending',
|
|
29
|
-
checkpoint TEXT, started_at TEXT, completed_at TEXT
|
|
30
|
-
)`);
|
|
31
|
-
|
|
32
|
-
db.run(`CREATE TABLE artifacts (
|
|
33
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT, spec_id TEXT,
|
|
34
|
-
task_ref INTEGER, path TEXT, action TEXT, created_at TEXT
|
|
35
|
-
)`);
|
|
36
|
-
|
|
37
|
-
db.run(`CREATE TABLE architectural_analyses (
|
|
38
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, status TEXT DEFAULT 'pending'
|
|
39
|
-
)`);
|
|
40
|
-
|
|
41
|
-
db.run(`CREATE TABLE project (
|
|
42
|
-
id TEXT PRIMARY KEY DEFAULT 'default', stack TEXT,
|
|
43
|
-
cli_version TEXT, typecheck_command TEXT, grepai_workspace TEXT
|
|
44
|
-
)`);
|
|
45
|
-
|
|
46
|
-
db.run("INSERT INTO project (id) VALUES ('default')");
|
|
47
|
-
|
|
48
|
-
return db;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function insertSpec(db: Database, id: string, name: string, phase: string): void {
|
|
52
|
-
db.run("INSERT INTO specs (id, name, phase, created_at) VALUES (?, ?, ?, datetime('now'))", [id, name, phase]);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function insertTask(
|
|
56
|
-
db: Database,
|
|
57
|
-
specId: string,
|
|
58
|
-
number: number,
|
|
59
|
-
name: string,
|
|
60
|
-
options: { agent?: string; canParallel?: boolean; dependsOn?: number[]; status?: string } = {}
|
|
61
|
-
): number {
|
|
62
|
-
const result = db.run(
|
|
63
|
-
`INSERT INTO tasks (spec_id, number, name, agent, can_parallel, depends_on, status)
|
|
64
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
65
|
-
[
|
|
66
|
-
specId,
|
|
67
|
-
number,
|
|
68
|
-
name,
|
|
69
|
-
options.agent || "general",
|
|
70
|
-
options.canParallel !== false ? 1 : 0,
|
|
71
|
-
options.dependsOn ? JSON.stringify(options.dependsOn) : null,
|
|
72
|
-
options.status || "pending",
|
|
73
|
-
]
|
|
74
|
-
);
|
|
75
|
-
return Number(result.lastInsertRowid);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function insertArtifact(db: Database, specId: string, taskRef: number, path: string): void {
|
|
79
|
-
db.run("INSERT INTO artifacts (spec_id, task_ref, path, action) VALUES (?, ?, ?, 'created')", [specId, taskRef, path]);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
15
|
// ─────────────────────────────────────────────────────────────────
|
|
83
16
|
// Tests
|
|
84
17
|
// ─────────────────────────────────────────────────────────────────
|
|
85
18
|
|
|
86
19
|
describe("detectPhase", () => {
|
|
87
|
-
|
|
88
|
-
const
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
const client = createClient({ url: ":memory:" });
|
|
22
|
+
setClient(client);
|
|
23
|
+
await initSchema();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
resetClient();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("detecta imp quando spec.phase = implementing", async () => {
|
|
89
31
|
const spec = { phase: "implementing" };
|
|
90
|
-
expect(detectPhase(
|
|
32
|
+
expect(await detectPhase(spec)).toBe("imp");
|
|
91
33
|
});
|
|
92
34
|
|
|
93
|
-
it("detecta rev quando spec.phase = reviewing", () => {
|
|
94
|
-
const db = createTestDb();
|
|
35
|
+
it("detecta rev quando spec.phase = reviewing", async () => {
|
|
95
36
|
const spec = { phase: "reviewing" };
|
|
96
|
-
expect(detectPhase(
|
|
37
|
+
expect(await detectPhase(spec)).toBe("rev");
|
|
97
38
|
});
|
|
98
39
|
|
|
99
|
-
it("detecta architect quando ha analise pendente", () => {
|
|
100
|
-
|
|
101
|
-
db.run("INSERT INTO architectural_analyses (name, status) VALUES ('test', 'pending')");
|
|
40
|
+
it("detecta architect quando ha analise pendente", async () => {
|
|
41
|
+
await dbRun("INSERT INTO architectural_analyses (id, name, description, status) VALUES ('test-id', 'test', 'test description', 'pending')");
|
|
102
42
|
const spec = { phase: "planning" };
|
|
103
|
-
expect(detectPhase(
|
|
43
|
+
expect(await detectPhase(spec)).toBe("architect");
|
|
104
44
|
});
|
|
105
45
|
|
|
106
|
-
it("retorna phase original se nao ha analise pendente", () => {
|
|
107
|
-
const db = createTestDb();
|
|
46
|
+
it("retorna phase original se nao ha analise pendente", async () => {
|
|
108
47
|
const spec = { phase: "planning" };
|
|
109
|
-
expect(detectPhase(
|
|
48
|
+
expect(await detectPhase(spec)).toBe("planning");
|
|
110
49
|
});
|
|
111
50
|
});
|
|
112
51
|
|