@codexa/cli 9.0.15 → 9.0.17

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.
@@ -237,6 +237,78 @@ describe("parseBabySteps", () => {
237
237
  const result = parseBabySteps(section);
238
238
  expect(result[0].files).toEqual(["file1.ts", "file2.ts"]);
239
239
  });
240
+
241
+ // v9.9: Phase support in baby steps
242
+ it("should parse steps with explicit phase headers", () => {
243
+ const section = `## Fase 1: Fundacao
244
+ ### 1. Create schema
245
+ **O que**: Build database schema
246
+ **Agente**: expert-database
247
+
248
+ ### 2. Setup testes
249
+ **O que**: Configure test framework
250
+ **Agente**: expert-testing
251
+
252
+ ## Fase 2: Core
253
+ ### 3. Implement API
254
+ **O que**: Create REST endpoints
255
+ **Agente**: expert-backend
256
+ **Depende de**: 1`;
257
+
258
+ const result = parseBabySteps(section);
259
+ expect(result).toHaveLength(3);
260
+ expect(result[0].phase).toBe(1);
261
+ expect(result[1].phase).toBe(1);
262
+ expect(result[2].phase).toBe(2);
263
+ });
264
+
265
+ it("should not assign phases when no phase headers exist", () => {
266
+ const section = `### 1. First step
267
+ **O que**: Do first thing
268
+
269
+ ### 2. Second step
270
+ **O que**: Do second thing`;
271
+
272
+ const result = parseBabySteps(section);
273
+ expect(result).toHaveLength(2);
274
+ expect(result[0].phase).toBeUndefined();
275
+ expect(result[1].phase).toBeUndefined();
276
+ });
277
+
278
+ it("should handle 'Phase N:' header format (English)", () => {
279
+ const section = `## Phase 1: Foundation
280
+ ### 1. Create schema
281
+ **O que**: Build database
282
+
283
+ ## Phase 2: Features
284
+ ### 2. Implement API
285
+ **O que**: Create endpoints`;
286
+
287
+ const result = parseBabySteps(section);
288
+ expect(result).toHaveLength(2);
289
+ expect(result[0].phase).toBe(1);
290
+ expect(result[1].phase).toBe(2);
291
+ });
292
+
293
+ it("should parse 3+ phases correctly", () => {
294
+ const section = `## Fase 1: Base
295
+ ### 1. Step one
296
+ **O que**: First
297
+
298
+ ## Fase 2: Core
299
+ ### 2. Step two
300
+ **O que**: Second
301
+
302
+ ## Fase 3: Polish
303
+ ### 3. Step three
304
+ **O que**: Third`;
305
+
306
+ const result = parseBabySteps(section);
307
+ expect(result).toHaveLength(3);
308
+ expect(result[0].phase).toBe(1);
309
+ expect(result[1].phase).toBe(2);
310
+ expect(result[2].phase).toBe(3);
311
+ });
240
312
  });
241
313
 
242
314
  describe("parseRisks", () => {
@@ -17,6 +17,7 @@ export interface BabyStep {
17
17
  files: string[];
18
18
  agent: string;
19
19
  dependsOn?: number[];
20
+ phase?: number;
20
21
  }
21
22
 
22
23
  export interface Risk {
@@ -180,36 +181,85 @@ export function extractSection(content: string, header: string): string {
180
181
  export function parseBabySteps(section: string): BabyStep[] {
181
182
  if (!section) return [];
182
183
  const steps: BabyStep[] = [];
183
- // Match "### N. Name" or "### Step N: Name"
184
- const stepBlocks = section.split(/^###\s+/m).filter(Boolean);
185
184
 
186
- for (const block of stepBlocks) {
187
- const headerMatch = block.match(/^(?:Step\s+)?(\d+)[.:]\s*(.+)/);
188
- if (!headerMatch) continue;
185
+ // Detect phase headers: "## Fase N:" or "## Phase N:"
186
+ let currentPhase = 1;
187
+ let hasExplicitPhases = false;
188
+ const phaseRegex = /^##\s+(?:Fase|Phase)\s+(\d+)/im;
189
+ if (phaseRegex.test(section)) {
190
+ hasExplicitPhases = true;
191
+ }
189
192
 
190
- const number = parseInt(headerMatch[1]);
191
- const name = headerMatch[2].trim();
192
- const what = block.match(/\*\*O que\*\*:\s*(.+)/)?.[1]?.trim() || "";
193
- const why = block.match(/\*\*Por que\*\*:\s*(.+)/)?.[1]?.trim() || "";
194
- const result = block.match(/\*\*Resultado\*\*:\s*(.+)/)?.[1]?.trim() || "";
195
- const filesStr = block.match(/\*\*Arquivos?\*\*:\s*(.+)/)?.[1]?.trim() || "";
196
- const agent = block.match(/\*\*Agente\*\*:\s*(.+)/)?.[1]?.trim() || "general";
197
- const depsStr = block.match(/\*\*Depende de\*\*:\s*(.+)/)?.[1]?.trim() || "";
193
+ // Split by phase headers first, then by step headers
194
+ // We process the section line-by-line to track phase context
195
+ const lines = section.split("\n");
196
+ let currentBlock = "";
197
+ let blockPhase = currentPhase;
198
198
 
199
- const files = filesStr
200
- ? filesStr.split(/[,;]/).map(f => f.trim().replace(/`/g, "")).filter(Boolean)
201
- : [];
199
+ for (const line of lines) {
200
+ const phaseMatch = line.match(/^##\s+(?:Fase|Phase)\s+(\d+)/i);
201
+ if (phaseMatch) {
202
+ // Process pending block BEFORE changing phase
203
+ if (currentBlock.trim()) {
204
+ const step = parseStepBlock(currentBlock, hasExplicitPhases ? blockPhase : undefined);
205
+ if (step) steps.push(step);
206
+ currentBlock = "";
207
+ }
208
+ currentPhase = parseInt(phaseMatch[1]);
209
+ blockPhase = currentPhase;
210
+ continue;
211
+ }
202
212
 
203
- const dependsOn = depsStr
204
- ? (depsStr.match(/\d+/g) || []).map(Number)
205
- : [];
213
+ // When we hit a new step header, process the previous block
214
+ if (line.match(/^###\s+/) && currentBlock.trim()) {
215
+ const step = parseStepBlock(currentBlock, hasExplicitPhases ? blockPhase : undefined);
216
+ if (step) steps.push(step);
217
+ currentBlock = line + "\n";
218
+ blockPhase = currentPhase;
219
+ } else {
220
+ currentBlock += line + "\n";
221
+ }
222
+ }
206
223
 
207
- steps.push({ number, name, what, why, result, files, agent, dependsOn: dependsOn.length > 0 ? dependsOn : undefined });
224
+ // Process the last block
225
+ if (currentBlock.trim()) {
226
+ const step = parseStepBlock(currentBlock, hasExplicitPhases ? blockPhase : undefined);
227
+ if (step) steps.push(step);
208
228
  }
209
229
 
210
230
  return steps;
211
231
  }
212
232
 
233
+ function parseStepBlock(block: string, phase?: number): BabyStep | null {
234
+ // Remove leading "### " prefix for matching
235
+ const content = block.replace(/^###\s+/, "");
236
+ const headerMatch = content.match(/^(?:Step\s+)?(\d+)[.:]\s*(.+)/);
237
+ if (!headerMatch) return null;
238
+
239
+ const number = parseInt(headerMatch[1]);
240
+ const name = headerMatch[2].trim();
241
+ const what = block.match(/\*\*O que\*\*:\s*(.+)/)?.[1]?.trim() || "";
242
+ const why = block.match(/\*\*Por que\*\*:\s*(.+)/)?.[1]?.trim() || "";
243
+ const result = block.match(/\*\*Resultado\*\*:\s*(.+)/)?.[1]?.trim() || "";
244
+ const filesStr = block.match(/\*\*Arquivos?\*\*:\s*(.+)/)?.[1]?.trim() || "";
245
+ const agent = block.match(/\*\*Agente\*\*:\s*(.+)/)?.[1]?.trim() || "general";
246
+ const depsStr = block.match(/\*\*Depende de\*\*:\s*(.+)/)?.[1]?.trim() || "";
247
+
248
+ const files = filesStr
249
+ ? filesStr.split(/[,;]/).map(f => f.trim().replace(/`/g, "")).filter(Boolean)
250
+ : [];
251
+
252
+ const dependsOn = depsStr
253
+ ? (depsStr.match(/\d+/g) || []).map(Number)
254
+ : [];
255
+
256
+ return {
257
+ number, name, what, why, result, files, agent,
258
+ dependsOn: dependsOn.length > 0 ? dependsOn : undefined,
259
+ phase,
260
+ };
261
+ }
262
+
213
263
  export function parseRisks(section: string): Risk[] {
214
264
  if (!section) return [];
215
265
  const risks: Risk[] = [];
@@ -594,6 +644,29 @@ export function architectSave(options: { file?: string; json?: boolean }): void
594
644
  const content = readFileSync(filePath, "utf-8");
595
645
  const parsed = parseAnalysisMd(content);
596
646
 
647
+ // v10.0: Validar secoes criticas antes de salvar
648
+ if (!parsed.babySteps || parsed.babySteps.length === 0) {
649
+ const msg = "Secao 'Baby Steps' esta vazia ou nao foi encontrada no .md. "
650
+ + "Verifique se os headers seguem o formato '## Baby Steps' e cada step usa '### N. Nome'.";
651
+ if (options.json) {
652
+ console.log(JSON.stringify({ error: "EMPTY_BABY_STEPS", message: msg }));
653
+ } else {
654
+ console.error(`\n[ERRO] ${msg}\n`);
655
+ }
656
+ throw new CodexaError(msg);
657
+ }
658
+
659
+ if (!parsed.approach) {
660
+ const msg = "Secao 'Solucao Proposta' esta vazia ou nao foi encontrada no .md. "
661
+ + "Verifique se o header segue o formato '## Solucao Proposta'.";
662
+ if (options.json) {
663
+ console.log(JSON.stringify({ error: "EMPTY_APPROACH", message: msg }));
664
+ } else {
665
+ console.error(`\n[ERRO] ${msg}\n`);
666
+ }
667
+ throw new CodexaError(msg);
668
+ }
669
+
597
670
  // Popular DB com dados extraidos do .md
598
671
  const updates: string[] = [];
599
672
  const values: any[] = [];
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from "bun:test";
2
- import { generateSpecId } from "./plan";
2
+ import { generateSpecId, estimateContextPressure, assignPhases, SAFE_TASKS_PER_PHASE } from "./plan";
3
3
 
4
4
  describe("generateSpecId", () => {
5
5
  it("generates ID with date-slug-hash format", () => {
@@ -71,3 +71,151 @@ describe("generateSpecId", () => {
71
71
  expect(id).toContain("add-user-auth");
72
72
  });
73
73
  });
74
+
75
+ // ═══════════════════════════════════════════════════════════════
76
+ // v9.9: Context Pressure Budget Calculator
77
+ // ═══════════════════════════════════════════════════════════════
78
+
79
+ describe("estimateContextPressure", () => {
80
+ it("returns green for small task counts", () => {
81
+ const result = estimateContextPressure(5);
82
+ expect(result.level).toBe("green");
83
+ expect(result.taskCount).toBe(5);
84
+ expect(result.suggestedPhases).toBe(1);
85
+ });
86
+
87
+ it("returns green at the threshold boundary", () => {
88
+ const result = estimateContextPressure(SAFE_TASKS_PER_PHASE);
89
+ expect(result.level).toBe("green");
90
+ expect(result.suggestedPhases).toBe(1);
91
+ });
92
+
93
+ it("returns yellow for moderate task counts", () => {
94
+ const result = estimateContextPressure(18);
95
+ expect(result.level).toBe("yellow");
96
+ expect(result.suggestedPhases).toBe(2);
97
+ });
98
+
99
+ it("returns red for large task counts", () => {
100
+ const result = estimateContextPressure(30);
101
+ expect(result.level).toBe("red");
102
+ expect(result.suggestedPhases).toBe(3);
103
+ });
104
+
105
+ it("calculates decision coverage correctly", () => {
106
+ const result = estimateContextPressure(10);
107
+ // 10 tasks * 3 decisions = 30, coverage = 8/30 = 27%
108
+ expect(result.estimatedDecisions).toBe(30);
109
+ expect(result.decisionCoverage).toBe(27);
110
+ });
111
+
112
+ it("caps coverage at 100%", () => {
113
+ const result = estimateContextPressure(1);
114
+ // 1 task * 3 = 3 decisions, 8/3 = 267% -> capped at 100
115
+ expect(result.decisionCoverage).toBe(100);
116
+ expect(result.knowledgeCoverage).toBe(100);
117
+ });
118
+
119
+ it("respects custom maxPerPhase", () => {
120
+ const result = estimateContextPressure(20, 20);
121
+ expect(result.level).toBe("green");
122
+ expect(result.suggestedPhases).toBe(1);
123
+ expect(result.maxTasksPerPhase).toBe(20);
124
+ });
125
+
126
+ it("suggests correct number of phases for 38 tasks", () => {
127
+ const result = estimateContextPressure(38);
128
+ expect(result.level).toBe("red");
129
+ expect(result.suggestedPhases).toBe(4); // ceil(38/12) = 4
130
+ });
131
+ });
132
+
133
+ // ═══════════════════════════════════════════════════════════════
134
+ // v9.9: Phase Auto-Assignment (Topological Sort)
135
+ // ═══════════════════════════════════════════════════════════════
136
+
137
+ describe("assignPhases", () => {
138
+ it("assigns single phase for small task lists", () => {
139
+ const tasks = [
140
+ { number: 1, dependsOn: [] },
141
+ { number: 2, dependsOn: [1] },
142
+ { number: 3, dependsOn: [2] },
143
+ ];
144
+ const result = assignPhases(tasks, 12);
145
+ expect(result.get(1)).toBe(1);
146
+ expect(result.get(2)).toBe(1);
147
+ expect(result.get(3)).toBe(1);
148
+ });
149
+
150
+ it("splits tasks into phases based on topological depth", () => {
151
+ // Create 15 tasks: levels 0-4 with 3 tasks each
152
+ const tasks = [];
153
+ for (let i = 1; i <= 15; i++) {
154
+ const level = Math.floor((i - 1) / 3);
155
+ const deps = level > 0 ? [(level - 1) * 3 + 1] : [];
156
+ tasks.push({ number: i, dependsOn: deps });
157
+ }
158
+ const result = assignPhases(tasks, 6);
159
+
160
+ // Should split into at least 2 phases
161
+ const phases = new Set(result.values());
162
+ expect(phases.size).toBeGreaterThanOrEqual(2);
163
+ });
164
+
165
+ it("returns empty map for empty task list", () => {
166
+ const result = assignPhases([], 12);
167
+ expect(result.size).toBe(0);
168
+ });
169
+
170
+ it("handles independent tasks (no dependencies)", () => {
171
+ const tasks = Array.from({ length: 20 }, (_, i) => ({
172
+ number: i + 1,
173
+ dependsOn: [] as number[],
174
+ }));
175
+ const result = assignPhases(tasks, 10);
176
+
177
+ // All independent tasks are at level 0, but can't fit in one phase
178
+ // They should be split: 10 in phase 1, 10 in phase 2
179
+ const phases = new Set(result.values());
180
+ expect(phases.size).toBe(2);
181
+ });
182
+
183
+ it("respects dependency order across phases", () => {
184
+ const tasks = [
185
+ { number: 1, dependsOn: [] },
186
+ { number: 2, dependsOn: [] },
187
+ { number: 3, dependsOn: [1, 2] },
188
+ { number: 4, dependsOn: [3] },
189
+ ];
190
+ const result = assignPhases(tasks, 3);
191
+
192
+ // Task 3 depends on 1 and 2, so it must be in same or later phase
193
+ const phase1 = result.get(1)!;
194
+ const phase3 = result.get(3)!;
195
+ const phase4 = result.get(4)!;
196
+ expect(phase3).toBeGreaterThanOrEqual(phase1);
197
+ expect(phase4).toBeGreaterThanOrEqual(phase3);
198
+ });
199
+
200
+ it("handles a linear chain exceeding maxPerPhase", () => {
201
+ // 6 tasks in a linear chain, max 2 per phase
202
+ const tasks = [
203
+ { number: 1, dependsOn: [] },
204
+ { number: 2, dependsOn: [1] },
205
+ { number: 3, dependsOn: [2] },
206
+ { number: 4, dependsOn: [3] },
207
+ { number: 5, dependsOn: [4] },
208
+ { number: 6, dependsOn: [5] },
209
+ ];
210
+ const result = assignPhases(tasks, 2);
211
+
212
+ // Each level has 1 task, so we accumulate 2 per phase
213
+ const phases = new Set(result.values());
214
+ expect(phases.size).toBe(3);
215
+
216
+ // Verify order is preserved
217
+ for (let i = 1; i <= 5; i++) {
218
+ expect(result.get(i)!).toBeLessThanOrEqual(result.get(i + 1)!);
219
+ }
220
+ });
221
+ });
package/commands/plan.ts CHANGED
@@ -4,6 +4,143 @@ import { resolveSpec } from "./spec-resolver";
4
4
  import { CodexaError, ValidationError } from "../errors";
5
5
  import { resolveAgent, resolveAgentName, suggestAgent, getCanonicalAgentNames, getAgentsByDomain } from "../context/agent-registry";
6
6
 
7
+ // ═══════════════════════════════════════════════════════════════
8
+ // CONTEXT PRESSURE: Budget Calculator + Phase Auto-Assignment
9
+ // ═══════════════════════════════════════════════════════════════
10
+
11
+ export const SAFE_TASKS_PER_PHASE = 12;
12
+ const DECISIONS_PER_TASK = 3;
13
+ const KNOWLEDGE_PER_TASK = 5;
14
+ const MAX_DECISIONS_IN_CONTEXT = 8;
15
+ const MAX_KNOWLEDGE_IN_CONTEXT = 30; // 20 critical + 10 info
16
+
17
+ export type PressureLevel = "green" | "yellow" | "red";
18
+
19
+ export interface ContextPressureEstimate {
20
+ level: PressureLevel;
21
+ taskCount: number;
22
+ estimatedDecisions: number;
23
+ decisionCoverage: number;
24
+ estimatedKnowledge: number;
25
+ knowledgeCoverage: number;
26
+ suggestedPhases: number;
27
+ maxTasksPerPhase: number;
28
+ }
29
+
30
+ export function estimateContextPressure(taskCount: number, maxPerPhase: number = SAFE_TASKS_PER_PHASE): ContextPressureEstimate {
31
+ const estimatedDecisions = taskCount * DECISIONS_PER_TASK;
32
+ const estimatedKnowledge = taskCount * KNOWLEDGE_PER_TASK;
33
+ const decisionCoverage = Math.min(100, Math.round((MAX_DECISIONS_IN_CONTEXT / estimatedDecisions) * 100));
34
+ const knowledgeCoverage = Math.min(100, Math.round((MAX_KNOWLEDGE_IN_CONTEXT / estimatedKnowledge) * 100));
35
+
36
+ let level: PressureLevel = "green";
37
+ if (taskCount > 25) level = "red";
38
+ else if (taskCount > maxPerPhase) level = "yellow";
39
+
40
+ const suggestedPhases = Math.max(1, Math.ceil(taskCount / maxPerPhase));
41
+
42
+ return {
43
+ level,
44
+ taskCount,
45
+ estimatedDecisions,
46
+ decisionCoverage,
47
+ estimatedKnowledge,
48
+ knowledgeCoverage,
49
+ suggestedPhases,
50
+ maxTasksPerPhase: maxPerPhase,
51
+ };
52
+ }
53
+
54
+ interface TaskForPhasing {
55
+ number: number;
56
+ dependsOn: number[];
57
+ phase?: number;
58
+ }
59
+
60
+ export function assignPhases(tasks: TaskForPhasing[], maxPerPhase: number = SAFE_TASKS_PER_PHASE): Map<number, number> {
61
+ const phaseMap = new Map<number, number>();
62
+ if (tasks.length === 0) return phaseMap;
63
+
64
+ // Build dependency graph and compute topological depth via BFS
65
+ const depthMap = new Map<number, number>();
66
+ const taskNums = new Set(tasks.map(t => t.number));
67
+ const depsOf = new Map<number, number[]>();
68
+ for (const t of tasks) {
69
+ depsOf.set(t.number, t.dependsOn.filter(d => taskNums.has(d)));
70
+ }
71
+
72
+ // Kahn's algorithm for topological levels
73
+ const inDegree = new Map<number, number>();
74
+ const children = new Map<number, number[]>();
75
+ for (const t of tasks) {
76
+ inDegree.set(t.number, 0);
77
+ children.set(t.number, []);
78
+ }
79
+ for (const t of tasks) {
80
+ for (const dep of depsOf.get(t.number) || []) {
81
+ inDegree.set(t.number, (inDegree.get(t.number) || 0) + 1);
82
+ children.get(dep)?.push(t.number);
83
+ }
84
+ }
85
+
86
+ // BFS by level
87
+ let queue = tasks.filter(t => (inDegree.get(t.number) || 0) === 0).map(t => t.number);
88
+ let level = 0;
89
+ while (queue.length > 0) {
90
+ const nextQueue: number[] = [];
91
+ for (const num of queue) {
92
+ depthMap.set(num, level);
93
+ for (const child of children.get(num) || []) {
94
+ inDegree.set(child, (inDegree.get(child) || 0) - 1);
95
+ if ((inDegree.get(child) || 0) === 0) {
96
+ nextQueue.push(child);
97
+ }
98
+ }
99
+ }
100
+ queue = nextQueue;
101
+ level++;
102
+ }
103
+
104
+ // Handle cycles (tasks not reached): assign max level
105
+ for (const t of tasks) {
106
+ if (!depthMap.has(t.number)) {
107
+ depthMap.set(t.number, level);
108
+ }
109
+ }
110
+
111
+ // Group by level, then assign phases by accumulating up to maxPerPhase
112
+ const maxLevel = Math.max(...Array.from(depthMap.values()));
113
+ let currentPhase = 1;
114
+ let tasksInCurrentPhase = 0;
115
+
116
+ for (let l = 0; l <= maxLevel; l++) {
117
+ const levelTasks = tasks.filter(t => depthMap.get(t.number) === l);
118
+ if (levelTasks.length === 0) continue;
119
+
120
+ // If adding this level would exceed max, start a new phase
121
+ if (tasksInCurrentPhase > 0 && tasksInCurrentPhase + levelTasks.length > maxPerPhase) {
122
+ currentPhase++;
123
+ tasksInCurrentPhase = 0;
124
+ }
125
+
126
+ // Handle levels with more tasks than maxPerPhase (split within level)
127
+ for (const t of levelTasks) {
128
+ if (tasksInCurrentPhase >= maxPerPhase) {
129
+ currentPhase++;
130
+ tasksInCurrentPhase = 0;
131
+ }
132
+ phaseMap.set(t.number, currentPhase);
133
+ tasksInCurrentPhase++;
134
+ }
135
+ }
136
+
137
+ return phaseMap;
138
+ }
139
+
140
+ // ═══════════════════════════════════════════════════════════════
141
+ // COMMANDS
142
+ // ═══════════════════════════════════════════════════════════════
143
+
7
144
  export function generateSpecId(name: string): string {
8
145
  const date = new Date().toISOString().split("T")[0];
9
146
  const slug = name
@@ -16,7 +153,8 @@ export function generateSpecId(name: string): string {
16
153
  }
17
154
 
18
155
  // v8.4: Suporte a --from-analysis para import automatico de baby steps
19
- export function planStart(description: string, options: { fromAnalysis?: string; json?: boolean } = {}): void {
156
+ // v9.9: Suporte a fases com budget calculator e auto-split
157
+ export function planStart(description: string, options: { fromAnalysis?: string; json?: boolean; maxTasksPerPhase?: number } = {}): void {
20
158
  initSchema();
21
159
  const db = getDb();
22
160
 
@@ -47,8 +185,23 @@ export function planStart(description: string, options: { fromAnalysis?: string;
47
185
  }
48
186
  } else {
49
187
  // v8.4: Auto-deteccao por nome
188
+ // v10.0: Alertar se analise aprovada existe mas --from-analysis nao foi usado
50
189
  const match = getArchitecturalAnalysisForSpec(description);
51
- if (match) {
190
+ if (match && match.status === "approved") {
191
+ const msg = `Analise arquitetural aprovada encontrada: ${match.id} (${match.name}).\n`
192
+ + `Use: plan start "${description}" --from-analysis ${match.id}`;
193
+ if (options.json) {
194
+ console.log(JSON.stringify({
195
+ error: "APPROVED_ANALYSIS_EXISTS",
196
+ analysisId: match.id,
197
+ analysisName: match.name,
198
+ suggestion: `plan start "${description}" --from-analysis ${match.id}`,
199
+ }));
200
+ } else {
201
+ console.error(`\n[ERRO] ${msg}\n`);
202
+ }
203
+ throw new CodexaError(msg);
204
+ } else if (match) {
52
205
  analysis = match;
53
206
  if (!options.json) {
54
207
  console.log(`\n[INFO] Analise arquitetural encontrada: ${match.id} (${match.name})`);
@@ -80,27 +233,52 @@ export function planStart(description: string, options: { fromAnalysis?: string;
80
233
  );
81
234
 
82
235
  // v8.4: Import automatico de baby steps da analise
236
+ // v9.9: Com suporte a fases (phase column + auto-split)
83
237
  let tasksCreated = 0;
238
+ let totalPhases = 1;
239
+ const maxPerPhase = options.maxTasksPerPhase || SAFE_TASKS_PER_PHASE;
240
+
84
241
  if (analysis && analysis.baby_steps) {
85
242
  try {
86
243
  const babySteps = JSON.parse(analysis.baby_steps);
244
+
245
+ // v9.9: Determine phases
246
+ const hasExplicitPhases = babySteps.some((s: any) => s.phase != null);
247
+ let phaseMap: Map<number, number>;
248
+
249
+ if (hasExplicitPhases) {
250
+ // Use phases from architect markdown
251
+ phaseMap = new Map(babySteps.map((s: any) => [s.number, s.phase || 1]));
252
+ } else {
253
+ // Auto-assign phases based on dependency topology + budget
254
+ const tasksForPhasing: TaskForPhasing[] = babySteps.map((s: any) => ({
255
+ number: s.number,
256
+ dependsOn: s.dependsOn || [],
257
+ }));
258
+ phaseMap = assignPhases(tasksForPhasing, maxPerPhase);
259
+ }
260
+
261
+ totalPhases = Math.max(1, ...Array.from(phaseMap.values()));
262
+
87
263
  for (const step of babySteps) {
88
264
  const files = step.files && step.files.length > 0 ? JSON.stringify(step.files) : null;
89
265
  const dependsOn = step.dependsOn && step.dependsOn.length > 0 ? JSON.stringify(step.dependsOn) : null;
266
+ const phase = phaseMap.get(step.number) || 1;
90
267
 
91
268
  const normalizedStepAgent = step.agent ? resolveAgentName(step.agent) : null;
92
269
  db.run(
93
- `INSERT INTO tasks (spec_id, number, name, agent, depends_on, can_parallel, files)
94
- VALUES (?, ?, ?, ?, ?, ?, ?)`,
95
- [specId, step.number, step.name, normalizedStepAgent, dependsOn, 1, files]
270
+ `INSERT INTO tasks (spec_id, number, name, agent, depends_on, can_parallel, files, phase)
271
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
272
+ [specId, step.number, step.name, normalizedStepAgent, dependsOn, 1, files, phase]
96
273
  );
97
274
  tasksCreated++;
98
275
  }
99
276
 
100
- // Atualizar total de tasks no contexto
101
- db.run("UPDATE context SET total_tasks = ?, updated_at = ? WHERE spec_id = ?", [
102
- tasksCreated, now, specId
103
- ]);
277
+ // Atualizar total de tasks e fases no contexto
278
+ db.run(
279
+ "UPDATE context SET total_tasks = ?, total_phases = ?, current_phase = 1, updated_at = ? WHERE spec_id = ?",
280
+ [tasksCreated, totalPhases, now, specId]
281
+ );
104
282
 
105
283
  // Marcar analise como implemented
106
284
  db.run(
@@ -112,8 +290,26 @@ export function planStart(description: string, options: { fromAnalysis?: string;
112
290
  console.error("[AVISO] Falha ao parsear baby steps da analise. Adicione tasks manualmente.\n");
113
291
  }
114
292
  }
293
+
294
+ // v10.0: Se --from-analysis foi explicito e importou 0 tasks, e um erro
295
+ if (options.fromAnalysis && tasksCreated === 0) {
296
+ const msg = `Analise ${analysis.id} nao contem baby steps validos. `
297
+ + `Verifique o .md com 'architect show --id ${analysis.id}' e corrija a secao Baby Steps.`;
298
+ if (options.json) {
299
+ console.log(JSON.stringify({ error: "ZERO_TASKS_IMPORTED", analysisId: analysis.id, message: msg }));
300
+ } else {
301
+ console.error(`\n[ERRO] ${msg}\n`);
302
+ }
303
+ // Cleanup: remover spec vazio criado
304
+ db.run("DELETE FROM context WHERE spec_id = ?", [specId]);
305
+ db.run("DELETE FROM specs WHERE id = ?", [specId]);
306
+ throw new CodexaError(msg);
307
+ }
115
308
  }
116
309
 
310
+ // v9.9: Context pressure analysis
311
+ const pressure = tasksCreated > 0 ? estimateContextPressure(tasksCreated, maxPerPhase) : null;
312
+
117
313
  if (options.json) {
118
314
  console.log(JSON.stringify({
119
315
  success: true,
@@ -122,6 +318,8 @@ export function planStart(description: string, options: { fromAnalysis?: string;
122
318
  phase: "planning",
123
319
  analysisId: analysis?.id || null,
124
320
  tasksImported: tasksCreated,
321
+ totalPhases,
322
+ contextPressure: pressure,
125
323
  }));
126
324
  } else {
127
325
  console.log(`\nFeature iniciada: ${description}`);
@@ -131,6 +329,32 @@ export function planStart(description: string, options: { fromAnalysis?: string;
131
329
  if (tasksCreated > 0) {
132
330
  console.log(`Analise: ${analysis.id}`);
133
331
  console.log(`Tasks importadas: ${tasksCreated}`);
332
+
333
+ // v9.9: Show pressure warning + phase breakdown
334
+ if (pressure && pressure.level !== "green") {
335
+ const icon = pressure.level === "red" ? "[ALTO]" : "[MEDIO]";
336
+ console.log(`\n${icon} Pressao de contexto: ${pressure.level.toUpperCase()} (${tasksCreated} tasks)`);
337
+ console.log(` Apos ~${maxPerPhase} tasks, a qualidade do contexto degrada.`);
338
+ console.log(` Cobertura estimada: ${pressure.decisionCoverage}% decisions, ${pressure.knowledgeCoverage}% knowledge`);
339
+
340
+ if (totalPhases > 1) {
341
+ console.log(`\n Plano dividido em ${totalPhases} fases:`);
342
+ // Count tasks per phase
343
+ const tasksByPhase = new Map<number, number>();
344
+ const allTasks = db.query("SELECT phase FROM tasks WHERE spec_id = ?").all(specId) as any[];
345
+ for (const t of allTasks) {
346
+ tasksByPhase.set(t.phase || 1, (tasksByPhase.get(t.phase || 1) || 0) + 1);
347
+ }
348
+ for (let p = 1; p <= totalPhases; p++) {
349
+ const count = tasksByPhase.get(p) || 0;
350
+ console.log(` Fase ${p}: ${count} tasks`);
351
+ }
352
+ console.log(`\n Entre cada fase, o contexto sera compactado automaticamente.`);
353
+ }
354
+ } else if (totalPhases > 1) {
355
+ console.log(`\nFases: ${totalPhases} (definidas pela analise arquitetural)`);
356
+ }
357
+
134
358
  console.log(`\nProximos passos:`);
135
359
  console.log(`1. Visualize o plano: plan show`);
136
360
  console.log(`2. Solicite aprovacao: check request\n`);
@@ -162,6 +386,9 @@ export function planShow(json: boolean = false, specId?: string): void {
162
386
  return;
163
387
  }
164
388
 
389
+ const currentPhase = context?.current_phase || 1;
390
+ const totalPhases = context?.total_phases || 1;
391
+
165
392
  console.log(`\n${"=".repeat(60)}`);
166
393
  console.log(`PLANO: ${spec.name}`);
167
394
  console.log(`${"=".repeat(60)}`);
@@ -176,25 +403,53 @@ export function planShow(json: boolean = false, specId?: string): void {
176
403
  return;
177
404
  }
178
405
 
179
- console.log(`\nTasks (${tasks.length}):`);
180
- console.log(`${"─".repeat(60)}`);
406
+ // v9.9: Show phase-aware progress
407
+ if (totalPhases > 1) {
408
+ const totalDone = tasks.filter(t => t.status === "done").length;
409
+ const phaseTasks = tasks.filter(t => (t.phase || 1) === currentPhase);
410
+ const phaseDone = phaseTasks.filter(t => t.status === "done").length;
411
+ console.log(`\nFase atual: ${currentPhase}/${totalPhases}`);
412
+ console.log(`Progresso: ${phaseDone}/${phaseTasks.length} (fase ${currentPhase}) | ${totalDone}/${tasks.length} (total)`);
413
+ }
414
+
415
+ // v9.9: Group tasks by phase
416
+ if (totalPhases > 1) {
417
+ for (let phase = 1; phase <= totalPhases; phase++) {
418
+ const phaseTasks = tasks.filter(t => (t.phase || 1) === phase);
419
+ if (phaseTasks.length === 0) continue;
181
420
 
182
- for (const task of tasks) {
183
- const deps = task.depends_on ? JSON.parse(task.depends_on) : [];
184
- const depsStr = deps.length > 0 ? ` [deps: ${deps.join(",")}]` : "";
185
- const parallelStr = task.can_parallel ? " ||" : "";
186
- const statusIcon =
187
- task.status === "done" ? "[x]" :
188
- task.status === "running" ? "[>]" :
189
- task.status === "failed" ? "[!]" : "[ ]";
421
+ const phaseLabel = phase === currentPhase ? "(atual)" :
422
+ phase < currentPhase ? "(concluida)" : "(bloqueada)";
423
+ console.log(`\n--- Fase ${phase} ${phaseLabel} ---`);
190
424
 
191
- console.log(`${statusIcon} #${task.number}: ${task.name} (${task.agent || "geral"})${depsStr}${parallelStr}`);
425
+ for (const task of phaseTasks) {
426
+ printTaskLine(task);
427
+ }
428
+ }
429
+ } else {
430
+ console.log(`\nTasks (${tasks.length}):`);
431
+ console.log(`${"─".repeat(60)}`);
432
+ for (const task of tasks) {
433
+ printTaskLine(task);
434
+ }
192
435
  }
193
436
 
194
437
  console.log(`${"─".repeat(60)}`);
195
438
  console.log(`Legenda: [ ] pendente [>] executando [x] concluido || paralelizavel\n`);
196
439
  }
197
440
 
441
+ function printTaskLine(task: any): void {
442
+ const deps = task.depends_on ? JSON.parse(task.depends_on) : [];
443
+ const depsStr = deps.length > 0 ? ` [deps: ${deps.join(",")}]` : "";
444
+ const parallelStr = task.can_parallel ? " ||" : "";
445
+ const statusIcon =
446
+ task.status === "done" ? "[x]" :
447
+ task.status === "running" ? "[>]" :
448
+ task.status === "failed" ? "[!]" : "[ ]";
449
+
450
+ console.log(`${statusIcon} #${task.number}: ${task.name} (${task.agent || "geral"})${depsStr}${parallelStr}`);
451
+ }
452
+
198
453
  export function planTaskAdd(options: {
199
454
  name: string;
200
455
  agent?: string;
package/commands/task.ts CHANGED
@@ -4,7 +4,7 @@ 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
- import { getUnreadKnowledgeForTask } from "./knowledge";
7
+ import { getUnreadKnowledgeForTask, compactKnowledge } from "./knowledge";
8
8
  import { loadTemplate } from "../templates/loader";
9
9
  import { TaskStateError, ValidationError, KnowledgeBlockError } from "../errors";
10
10
  import { resolveSpec, resolveSpecOrNull } from "./spec-resolver";
@@ -26,6 +26,11 @@ export function taskNext(json: boolean = false, specId?: string): void {
26
26
  throw new TaskStateError("Nenhuma feature em fase de implementacao.\nAprove o plano com: check approve");
27
27
  }
28
28
 
29
+ // v9.9: Get current phase info
30
+ const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id) as any;
31
+ const currentPhase = context?.current_phase || 1;
32
+ const totalPhases = context?.total_phases || 1;
33
+
29
34
  // Buscar tasks pendentes cujas dependencias estao todas concluidas
30
35
  const allTasks = db
31
36
  .query("SELECT * FROM tasks WHERE spec_id = ? ORDER BY number")
@@ -36,6 +41,9 @@ export function taskNext(json: boolean = false, specId?: string): void {
36
41
  for (const task of allTasks) {
37
42
  if (task.status !== "pending") continue;
38
43
 
44
+ // v9.9: Only show tasks from current phase (if phases are configured)
45
+ if (totalPhases > 1 && (task.phase || 1) !== currentPhase) continue;
46
+
39
47
  // Verificar dependencias
40
48
  const deps = task.depends_on ? JSON.parse(task.depends_on) : [];
41
49
  const allDepsDone = deps.every((depNum: number) => {
@@ -49,13 +57,25 @@ export function taskNext(json: boolean = false, specId?: string): void {
49
57
  }
50
58
 
51
59
  if (json) {
52
- console.log(JSON.stringify({ available }));
60
+ console.log(JSON.stringify({ available, currentPhase, totalPhases }));
53
61
  return;
54
62
  }
55
63
 
56
64
  if (available.length === 0) {
65
+ // v9.9: Check if current phase is complete but more phases remain
66
+ if (totalPhases > 1) {
67
+ const phaseTasks = allTasks.filter((t) => (t.phase || 1) === currentPhase);
68
+ const phaseDone = phaseTasks.every((t) => t.status === "done");
69
+
70
+ if (phaseDone && currentPhase < totalPhases) {
71
+ console.log(`\nFase ${currentPhase}/${totalPhases} concluida!`);
72
+ console.log(`Avance para a proxima fase com: task phase-advance\n`);
73
+ return;
74
+ }
75
+ }
76
+
57
77
  // Verificar se todas estao concluidas
58
- const pending = allTasks.filter((t) => t.status !== "done");
78
+ const pending = allTasks.filter((t) => t.status !== "done" && t.status !== "cancelled");
59
79
  if (pending.length === 0) {
60
80
  console.log("\nTodas as tasks foram concluidas!");
61
81
  console.log("Inicie o review com: review start\n");
@@ -72,6 +92,13 @@ export function taskNext(json: boolean = false, specId?: string): void {
72
92
  return;
73
93
  }
74
94
 
95
+ // v9.9: Show phase context
96
+ if (totalPhases > 1) {
97
+ const phaseTasks = allTasks.filter((t) => (t.phase || 1) === currentPhase);
98
+ const phaseDone = phaseTasks.filter((t) => t.status === "done").length;
99
+ console.log(`\nFase ${currentPhase}/${totalPhases} (${phaseDone}/${phaseTasks.length} concluidas)`);
100
+ }
101
+
75
102
  console.log(`\nTasks disponiveis para execucao (${available.length}):`);
76
103
  console.log(`${"─".repeat(50)}`);
77
104
 
@@ -119,7 +146,7 @@ function showStuckWarning(stuck: any[]): void {
119
146
  console.log(` Use: task done <id> --force --force-reason "timeout" para liberar\n`);
120
147
  }
121
148
 
122
- export function taskStart(ids: string, json: boolean = false, fullContext: boolean = false, specId?: string): void {
149
+ export function taskStart(ids: string, json: boolean = false, minimalContext: boolean = false, specId?: string): void {
123
150
  initSchema();
124
151
  enforceGate("task-start");
125
152
 
@@ -182,10 +209,10 @@ export function taskStart(ids: string, json: boolean = false, fullContext: boole
182
209
  if (json) {
183
210
  // NOVO: Incluir contexto COMPLETO para cada task
184
211
  const contexts = startedTasks.map((task) => {
185
- // v9.0: Usar contexto minimo por padrao, expandido via --full-context
186
- const contextText = fullContext
187
- ? getContextForSubagent(task.id)
188
- : getMinimalContextForSubagent(task.id);
212
+ // v10.0: Contexto completo por padrao, reduzido via --minimal-context
213
+ const contextText = minimalContext
214
+ ? getMinimalContextForSubagent(task.id)
215
+ : getContextForSubagent(task.id);
189
216
  const unreadKnowledge = getUnreadKnowledgeForTask(spec.id, task.id);
190
217
 
191
218
  // NOVO v7.4: Buscar implementation patterns relevantes
@@ -249,7 +276,7 @@ export function taskStart(ids: string, json: boolean = false, fullContext: boole
249
276
  _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.",
250
277
  // Contexto para o subagent (NAO para o orquestrador)
251
278
  context: contextText,
252
- contextMode: fullContext ? "full" : "minimal",
279
+ contextMode: minimalContext ? "minimal" : "full",
253
280
  // Knowledge nao lido (broadcast de outras tasks)
254
281
  unreadKnowledge: unreadKnowledge.map((k: any) => ({
255
282
  id: k.id,
@@ -493,10 +520,48 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
493
520
  [checkpoint, now, spec.id]
494
521
  );
495
522
 
523
+ // v9.9: Phase-aware progress
524
+ const ctx = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id) as any;
525
+ const currentPhase = ctx?.current_phase || 1;
526
+ const totalPhases = ctx?.total_phases || 1;
527
+
496
528
  console.log(`\nTask #${task.number} concluida!`);
497
529
  console.log(`Checkpoint: ${options.checkpoint}`);
498
530
  console.log(`Progresso: ${doneCount.c}/${totalCount.c} tasks`);
499
531
 
532
+ if (totalPhases > 1) {
533
+ const phaseTasks = db
534
+ .query("SELECT * FROM tasks WHERE spec_id = ? AND phase = ?")
535
+ .all(spec.id, currentPhase) as any[];
536
+ const phaseDone = phaseTasks.filter((t: any) => t.status === "done").length;
537
+ console.log(`Fase ${currentPhase}/${totalPhases}: ${phaseDone}/${phaseTasks.length} tasks`);
538
+
539
+ if (phaseDone === phaseTasks.length && currentPhase < totalPhases) {
540
+ // Phase complete — show phase summary
541
+ const phaseDecisions = db
542
+ .query("SELECT COUNT(*) as c FROM decisions WHERE spec_id = ?")
543
+ .get(spec.id) as any;
544
+ const phaseKnowledge = db
545
+ .query("SELECT COUNT(*) as c FROM knowledge WHERE spec_id = ? AND severity != 'archived'")
546
+ .get(spec.id) as any;
547
+
548
+ const nextPhaseTasks = db
549
+ .query("SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND phase = ?")
550
+ .get(spec.id, currentPhase + 1) as any;
551
+
552
+ console.log(`\n${"=".repeat(55)}`);
553
+ console.log(`FASE ${currentPhase} CONCLUIDA (${phaseDone}/${phaseTasks.length} tasks)`);
554
+ console.log(`${"=".repeat(55)}`);
555
+ console.log(` Decisions acumuladas: ${phaseDecisions.c}`);
556
+ console.log(` Knowledge acumulado: ${phaseKnowledge.c}`);
557
+ console.log(`\n Proxima fase: ${currentPhase + 1}/${totalPhases} (${nextPhaseTasks?.c || 0} tasks)`);
558
+ console.log(`\n Recomendacao: Compactar contexto antes de continuar`);
559
+ console.log(` -> codexa task phase-advance [--no-compact] [--spec ${spec.id}]`);
560
+ console.log(`${"=".repeat(55)}\n`);
561
+ return;
562
+ }
563
+ }
564
+
500
565
  if (doneCount.c === totalCount.c) {
501
566
  // Mostrar resumo completo da implementacao
502
567
  const allTasks = db.query("SELECT * FROM tasks WHERE spec_id = ?").all(spec.id) as any[];
@@ -613,4 +678,86 @@ function showImplementationSummary(
613
678
  console.log(` Para pular o review: review skip`);
614
679
  console.log(`\n O agente aguarda sua decisao.`);
615
680
  console.log(`${"─".repeat(50)}\n`);
681
+ }
682
+
683
+ // v9.9: Phase advance — compact context and move to next phase
684
+ export function taskPhaseAdvance(options: { noCompact?: boolean; spec?: string } = {}): void {
685
+ initSchema();
686
+ const db = getDb();
687
+
688
+ const spec = resolveSpec(options.spec, ["implementing"]);
689
+ const ctx = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id) as any;
690
+ const currentPhase = ctx?.current_phase || 1;
691
+ const totalPhases = ctx?.total_phases || 1;
692
+
693
+ if (totalPhases <= 1) {
694
+ console.log("\nEsta feature nao possui multiplas fases.\n");
695
+ return;
696
+ }
697
+
698
+ if (currentPhase >= totalPhases) {
699
+ console.log("\nTodas as fases ja foram concluidas!");
700
+ console.log("Inicie o review com: review start\n");
701
+ return;
702
+ }
703
+
704
+ // Verify current phase is fully complete
705
+ const phaseTasks = db
706
+ .query("SELECT * FROM tasks WHERE spec_id = ? AND phase = ?")
707
+ .all(spec.id, currentPhase) as any[];
708
+ const pendingInPhase = phaseTasks.filter((t: any) => t.status !== "done" && t.status !== "cancelled");
709
+
710
+ if (pendingInPhase.length > 0) {
711
+ console.log(`\n[ERRO] Fase ${currentPhase} nao esta completa.`);
712
+ console.log(` Tasks pendentes: ${pendingInPhase.map((t: any) => `#${t.number}`).join(", ")}`);
713
+ console.log(` Complete todas as tasks antes de avancar.\n`);
714
+ return;
715
+ }
716
+
717
+ const now = new Date().toISOString();
718
+
719
+ // Compact knowledge (unless --no-compact)
720
+ if (!options.noCompact) {
721
+ console.log(`\nCompactando contexto da fase ${currentPhase}...`);
722
+ compactKnowledge({ specId: spec.id });
723
+ }
724
+
725
+ // Create phase summary as critical knowledge
726
+ const phaseTaskNames = phaseTasks.map((t: any) => t.name).join(", ");
727
+ const decisionCount = (db.query("SELECT COUNT(*) as c FROM decisions WHERE spec_id = ?").get(spec.id) as any).c;
728
+ const artifactCount = (db.query("SELECT COUNT(*) as c FROM artifacts WHERE spec_id = ?").get(spec.id) as any).c;
729
+
730
+ const summaryContent = `Resumo Fase ${currentPhase}: ${phaseTasks.length} tasks concluidas (${phaseTaskNames.substring(0, 200)}). ${decisionCount} decisions, ${artifactCount} artefatos acumulados.`;
731
+
732
+ db.run(
733
+ `INSERT INTO knowledge (spec_id, category, content, severity, source, created_at)
734
+ VALUES (?, 'phase_summary', ?, 'critical', 'system', ?)`,
735
+ [spec.id, summaryContent, now]
736
+ );
737
+
738
+ // Advance to next phase
739
+ const nextPhase = currentPhase + 1;
740
+ db.run(
741
+ "UPDATE context SET current_phase = ?, updated_at = ? WHERE spec_id = ?",
742
+ [nextPhase, now, spec.id]
743
+ );
744
+
745
+ // Show next phase info
746
+ const nextPhaseTasks = db
747
+ .query("SELECT * FROM tasks WHERE spec_id = ? AND phase = ? ORDER BY number")
748
+ .all(spec.id, nextPhase) as any[];
749
+
750
+ console.log(`\n${"=".repeat(55)}`);
751
+ console.log(`FASE ${nextPhase}/${totalPhases} INICIADA`);
752
+ console.log(`${"=".repeat(55)}`);
753
+ console.log(` Tasks nesta fase: ${nextPhaseTasks.length}`);
754
+
755
+ for (const t of nextPhaseTasks) {
756
+ const deps = t.depends_on ? JSON.parse(t.depends_on) : [];
757
+ const depsStr = deps.length > 0 ? ` [deps: ${deps.join(",")}]` : "";
758
+ console.log(` #${t.number}: ${t.name} (${t.agent || "geral"})${depsStr}`);
759
+ }
760
+
761
+ console.log(`\n Proxima task: task next`);
762
+ console.log(`${"=".repeat(55)}\n`);
616
763
  }
package/db/schema.ts CHANGED
@@ -487,6 +487,15 @@ const MIGRATIONS: Migration[] = [
487
487
  db.exec(`ALTER TABLE project ADD COLUMN grepai_workspace TEXT`);
488
488
  },
489
489
  },
490
+ {
491
+ version: "9.9.0",
492
+ description: "Adicionar suporte a fases: phase em tasks, total_phases/current_phase em context",
493
+ up: (db) => {
494
+ db.exec(`ALTER TABLE tasks ADD COLUMN phase INTEGER DEFAULT 1`);
495
+ db.exec(`ALTER TABLE context ADD COLUMN total_phases INTEGER DEFAULT 1`);
496
+ db.exec(`ALTER TABLE context ADD COLUMN current_phase INTEGER DEFAULT 1`);
497
+ },
498
+ },
490
499
  ];
491
500
 
492
501
  export function runMigrations(): void {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codexa/cli",
3
- "version": "9.0.15",
3
+ "version": "9.0.17",
4
4
  "description": "Orchestrated workflow system for Claude Code - manages feature development through parallel subagents with structured phases, gates, and quality enforcement.",
5
5
  "type": "module",
6
6
  "bin": {
package/workflow.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  import { Command } from "commander";
4
4
  import { planStart, planShow, planTaskAdd, planCancel } from "./commands/plan";
5
5
  import { checkRequest, checkApprove, checkReject } from "./commands/check";
6
- import { taskNext, taskStart, taskDone } from "./commands/task";
6
+ import { taskNext, taskStart, taskDone, taskPhaseAdvance } from "./commands/task";
7
7
  import { decide, listDecisions, revokeDecision, supersedeDecision } from "./commands/decide";
8
8
  import { reviewStart, reviewApprove, reviewSkip } from "./commands/review";
9
9
  import { addKnowledge, listKnowledge, acknowledgeKnowledge, queryGraph, resolveKnowledge, compactKnowledge } from "./commands/knowledge";
@@ -168,8 +168,9 @@ planCmd
168
168
  .command("start <description>")
169
169
  .description("Inicia uma nova feature")
170
170
  .option("--from-analysis <id>", "Importar baby steps de analise arquitetural aprovada")
171
+ .option("--max-tasks-per-phase <n>", "Maximo de tasks por fase (padrao: 12)", parseInt)
171
172
  .option("--json", "Saida em JSON")
172
- .action((description: string, options: { fromAnalysis?: string; json?: boolean }) => {
173
+ .action((description: string, options: { fromAnalysis?: string; maxTasksPerPhase?: number; json?: boolean }) => {
173
174
  planStart(description, options);
174
175
  });
175
176
 
@@ -252,10 +253,10 @@ taskCmd
252
253
  .command("start <ids>")
253
254
  .description("Inicia task(s) - pode ser multiplas separadas por virgula")
254
255
  .option("--json", "Saida em JSON")
255
- .option("--full-context", "Incluir contexto completo (modo legado)")
256
+ .option("--minimal-context", "Usar contexto reduzido (2KB) em vez do completo (16KB)")
256
257
  .option("--spec <id>", "ID do spec (padrao: mais recente)")
257
258
  .action(wrapAction((ids: string, options) => {
258
- taskStart(ids, options.json, options.fullContext, options.spec);
259
+ taskStart(ids, options.json, options.minimalContext, options.spec);
259
260
  }));
260
261
 
261
262
  taskCmd
@@ -306,6 +307,15 @@ taskCmd
306
307
  });
307
308
  }));
308
309
 
310
+ taskCmd
311
+ .command("phase-advance")
312
+ .description("Avanca para a proxima fase (compacta contexto automaticamente)")
313
+ .option("--no-compact", "Nao compactar knowledge antes de avancar")
314
+ .option("--spec <id>", "ID do spec (padrao: mais recente)")
315
+ .action(wrapAction((options) => {
316
+ taskPhaseAdvance({ noCompact: options.compact === false, spec: options.spec });
317
+ }));
318
+
309
319
  // ═══════════════════════════════════════════════════════════════
310
320
  // DECISOES
311
321
  // ═══════════════════════════════════════════════════════════════