cc-pipeline 0.6.7 → 0.7.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-pipeline",
3
- "version": "0.6.7",
3
+ "version": "0.7.2",
4
4
  "description": "Autonomous Claude Code pipeline engine. Install into any repo, write a BRIEF.md, and let Claude build your project phase by phase.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -47,7 +47,8 @@ export class ClaudeCodeAgent extends BaseAgent {
47
47
 
48
48
  const appendOutput = (line: string) => {
49
49
  try {
50
- appendFileSync(outputPath, line + '\n', 'utf-8');
50
+ const ts = new Date().toISOString().replace('T', ' ').slice(0, 19);
51
+ appendFileSync(outputPath, `[${ts}] ${line}\n`, 'utf-8');
51
52
  lastActivityMs = Date.now();
52
53
  } catch (_) {}
53
54
  };
package/src/config.ts CHANGED
@@ -19,6 +19,7 @@ export interface PipelineConfig {
19
19
  name: string;
20
20
  version: number;
21
21
  phasesDir: string;
22
+ maxPhases: number;
22
23
  steps: StepConfig[];
23
24
  usageCheck: { when: string };
24
25
  usageLimits: { sessionBudgetUSD: number; weeklyBudgetUSD: number };
@@ -45,6 +46,7 @@ export function loadConfig(projectDir: string): PipelineConfig {
45
46
  name: raw.name || 'Unnamed Pipeline',
46
47
  version: raw.version || 1,
47
48
  phasesDir: raw.phases_dir || 'docs/phases',
49
+ maxPhases: raw.max_phases ?? 100,
48
50
  steps: [],
49
51
  usageCheck: raw.usage_check || { when: 'phase_boundary' },
50
52
  usageLimits: {
package/src/engine.ts CHANGED
@@ -10,7 +10,7 @@ import { CodexAgent } from './agents/codex.js';
10
10
  import { pipelineEvents } from './events.js';
11
11
  import { computeUsagePercentages } from './usage.js';
12
12
 
13
- const MAX_PHASES = 20;
13
+ const DEFAULT_MAX_PHASES = 100;
14
14
 
15
15
  /**
16
16
  * Main pipeline engine loop.
@@ -21,7 +21,7 @@ const MAX_PHASES = 20;
21
21
  * - Print banner
22
22
  * - Loop through phases, executing steps
23
23
  * - Check for PROJECT COMPLETE in reflections
24
- * - Handle phase limits and MAX_PHASES
24
+ * - Handle phase limits and maxPhases
25
25
  * - Signal handling for clean shutdown
26
26
  */
27
27
  export async function runEngine(projectDir: string, options: any = {}) {
@@ -40,6 +40,7 @@ export async function runEngine(projectDir: string, options: any = {}) {
40
40
 
41
41
  // Load config
42
42
  const config = loadConfig(projectDir);
43
+ const maxPhases = config.maxPhases ?? DEFAULT_MAX_PHASES;
43
44
 
44
45
  // Derive current state
45
46
  const state = getCurrentState(logFile);
@@ -110,21 +111,7 @@ export async function runEngine(projectDir: string, options: any = {}) {
110
111
 
111
112
  const phaseLimit = options.phases || 0;
112
113
 
113
- while (phase <= MAX_PHASES) {
114
- // Check for PROJECT COMPLETE
115
- if (phase > 1) {
116
- const prevPhaseDir = join(projectDir, config.phasesDir, `phase-${phase - 1}`);
117
- const reflectFile = join(prevPhaseDir, 'REFLECTIONS.md');
118
- if (existsSync(reflectFile)) {
119
- const firstLine = readFileSync(reflectFile, 'utf8').split('\n')[0];
120
- if (firstLine && /PROJECT COMPLETE/i.test(firstLine)) {
121
- appendEvent(logFile, { event: 'project_complete', phase: phase - 1 });
122
- log(`PROJECT COMPLETE detected in phase ${phase - 1} reflections.`);
123
- return;
124
- }
125
- }
126
- }
127
-
114
+ while (phase <= maxPhases) {
128
115
  pipelineEvents.emit('phase:start', { phase });
129
116
 
130
117
  // Execute all steps in phase
@@ -179,6 +166,20 @@ export async function runEngine(projectDir: string, options: any = {}) {
179
166
  }
180
167
  }
181
168
 
169
+ // After GROOM step: check if it signaled project complete
170
+ if (stepDef.name === 'groom' && lastResult === 'ok') {
171
+ const groomFile = join(projectDir, config.phasesDir, `phase-${phase}`, 'GROOM.md');
172
+ if (existsSync(groomFile)) {
173
+ const groomContent = readFileSync(groomFile, 'utf8');
174
+ if (/PROJECT COMPLETE/i.test(groomContent)) {
175
+ appendEvent(logFile, { event: 'project_complete', phase });
176
+ log(`PROJECT COMPLETE — all Epics finished (detected by GROOM in phase ${phase}).`);
177
+ pipelineEvents.emit('phase:done', { phase });
178
+ return;
179
+ }
180
+ }
181
+ }
182
+
182
183
  // If still failed after all retries, stop the pipeline
183
184
  if (lastResult === 'error' && stepDef.agent !== 'bash' && !stepDef.continueOnError) {
184
185
  logErr(`\n Step "${stepDef.name}" failed after 3 attempts. Pipeline stopped.`);
@@ -221,7 +222,7 @@ export async function runEngine(projectDir: string, options: any = {}) {
221
222
  }
222
223
  }
223
224
 
224
- log(`Hit MAX_PHASES (${MAX_PHASES}). Stopping.`);
225
+ log(`Hit max phases (${maxPhases}). Stopping.`);
225
226
  } finally {
226
227
  cleanup();
227
228
  }
package/src/prompts.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { readFileSync, existsSync } from 'node:fs';
1
+ import { readFileSync, existsSync, readdirSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
 
4
4
  /**
@@ -41,6 +41,42 @@ export function generatePrompt(projectDir: string, config: any, phase: number, p
41
41
  }
42
42
  prompt = prompt.replace(/\{\{BRIEF\}\}/g, briefContent);
43
43
 
44
+ // Substitute {{NEXT}} — contents of previous phase's NEXT.md (if exists)
45
+ let nextContent = '';
46
+ if (phase > 1) {
47
+ const prevNextPath = join(projectDir, config.phasesDir, `phase-${phase - 1}`, 'NEXT.md');
48
+ if (existsSync(prevNextPath)) {
49
+ nextContent = readFileSync(prevNextPath, 'utf-8');
50
+ }
51
+ }
52
+ prompt = prompt.replace(/\{\{NEXT\}\}/g, nextContent);
53
+
54
+ // Substitute {{EPIC}} — contents of the Epic file referenced in NEXT.md
55
+ // Parses "Epic: epic-N-name.md" from NEXT content to find the file
56
+ let epicContent = '';
57
+ if (nextContent) {
58
+ const epicMatch = nextContent.match(/^Epic:\s*(.+\.md)/m);
59
+ if (epicMatch) {
60
+ const epicPath = join(projectDir, 'docs', 'epics', epicMatch[1].trim());
61
+ if (existsSync(epicPath)) {
62
+ epicContent = readFileSync(epicPath, 'utf-8');
63
+ }
64
+ }
65
+ }
66
+ prompt = prompt.replace(/\{\{EPIC\}\}/g, epicContent);
67
+
68
+ // Substitute {{ALL_EPICS}} — concatenated contents of all docs/epics/*.md files
69
+ let allEpics = '';
70
+ const epicsDir = join(projectDir, 'docs', 'epics');
71
+ if (existsSync(epicsDir)) {
72
+ const files = readdirSync(epicsDir).filter(f => f.endsWith('.md')).sort();
73
+ allEpics = files.map(f => {
74
+ const content = readFileSync(join(epicsDir, f), 'utf-8');
75
+ return `--- ${f} ---\n${content}`;
76
+ }).join('\n\n');
77
+ }
78
+ prompt = prompt.replace(/\{\{ALL_EPICS\}\}/g, allEpics);
79
+
44
80
  // Substitute {{FILE_TREE}} - placeholder for now
45
81
  const fileTree = '(file tree generation not yet implemented)';
46
82
  prompt = prompt.replace(/\{\{FILE_TREE\}\}/g, fileTree);
package/src/state.test.ts CHANGED
@@ -357,6 +357,46 @@ test('deriveResumePoint: advance to next phase after last step', () => {
357
357
  rmSync(tempDir, { recursive: true });
358
358
  });
359
359
 
360
+ test('getCurrentState: project_complete returns done state (same as phase_complete)', () => {
361
+ const tempDir = mkdtempSync(join(tmpdir(), 'cc-pipeline-test-'));
362
+ const logFile = join(tempDir, 'pipeline.jsonl');
363
+
364
+ const events = [
365
+ { event: 'step_start', phase: 34, step: 'groom', ts: '2025-01-01T00:00:00Z' },
366
+ { event: 'step_done', phase: 34, step: 'groom', status: 'ok', ts: '2025-01-01T00:01:00Z' },
367
+ { event: 'project_complete', phase: 34, ts: '2025-01-01T00:02:00Z' },
368
+ ];
369
+ writeFileSync(logFile, events.map(e => JSON.stringify(e)).join('\n') + '\n', 'utf8');
370
+
371
+ const state = getCurrentState(logFile);
372
+
373
+ assert.strictEqual(state.phase, 34);
374
+ assert.strictEqual(state.step, 'done');
375
+ assert.strictEqual(state.status, 'complete');
376
+
377
+ rmSync(tempDir, { recursive: true });
378
+ });
379
+
380
+ test('deriveResumePoint: project_complete advances to next phase first step', () => {
381
+ const tempDir = mkdtempSync(join(tmpdir(), 'cc-pipeline-test-'));
382
+ const logFile = join(tempDir, 'pipeline.jsonl');
383
+
384
+ const events = [
385
+ { event: 'step_start', phase: 34, step: 'spec', ts: '2025-01-01T00:00:00Z' },
386
+ { event: 'step_done', phase: 34, step: 'spec', status: 'ok', ts: '2025-01-01T00:01:00Z' },
387
+ { event: 'project_complete', phase: 34, ts: '2025-01-01T00:02:00Z' },
388
+ ];
389
+ writeFileSync(logFile, events.map(e => JSON.stringify(e)).join('\n') + '\n', 'utf8');
390
+
391
+ const resume = deriveResumePoint(logFile, mockSteps);
392
+
393
+ // project_complete is treated as phase_complete — next run starts fresh in the next phase
394
+ assert.strictEqual(resume.phase, 35);
395
+ assert.strictEqual(resume.stepName, 'spec');
396
+
397
+ rmSync(tempDir, { recursive: true });
398
+ });
399
+
360
400
  test('deriveResumePoint: handles unknown step by starting from beginning', () => {
361
401
  const tempDir = mkdtempSync(join(tmpdir(), 'cc-pipeline-test-'));
362
402
  const logFile = join(tempDir, 'pipeline.jsonl');
package/src/state.ts CHANGED
@@ -75,7 +75,7 @@ export function getCurrentState(logFile: string): { phase: number; step: string;
75
75
  }
76
76
 
77
77
  // Find last relevant event
78
- const relevantEvents = ['step_start', 'step_done', 'step_skip', 'phase_complete'];
78
+ const relevantEvents = ['step_start', 'step_done', 'step_skip', 'phase_complete', 'project_complete'];
79
79
  const lastEvent = events
80
80
  .filter(e => relevantEvents.includes(e.event))
81
81
  .pop();
@@ -98,6 +98,11 @@ export function getCurrentState(logFile: string): { phase: number; step: string;
98
98
  case 'step_skip':
99
99
  return { phase, step, status: 'complete' };
100
100
  case 'phase_complete':
101
+ case 'project_complete':
102
+ // Treat project_complete the same as phase_complete — resume starts at next phase.
103
+ // This ensures that if the user adds new Epics after completion and reruns, the
104
+ // engine advances to the next phase and runs groom fresh rather than resuming
105
+ // mid-phase after the groom step.
101
106
  return { phase, step: 'done', status: 'complete' };
102
107
  default:
103
108
  return { phase: 1, step: 'pending', status: 'ready' };
@@ -62,6 +62,37 @@ Phase outputs are saved to `docs/phases/phase-N/`.
62
62
 
63
63
  The pipeline stops automatically when the project is complete (`PROJECT COMPLETE` in REFLECTIONS.md).
64
64
 
65
+ ## Adding New Epics
66
+
67
+ The pipeline works through `docs/epics/` one Epic at a time. When all Epics are
68
+ complete the pipeline stops. To continue development, add a new Epic and run again.
69
+
70
+ **Epic files** live at `docs/epics/epic-N.md` where N is the next number in sequence.
71
+ Check what exists and increment: if `epic-3.md` is the last one, create `epic-4.md`.
72
+
73
+ **Minimum viable Epic** — the pipeline's groom step will fill in research and detail,
74
+ so you only need to provide intent:
75
+
76
+ ```markdown
77
+ # Epic N: [Short name]
78
+
79
+ ## Goal
80
+ [One paragraph: what the user can do when this Epic is complete.
81
+ Must be user-testable — a real capability, not an infrastructure layer.]
82
+
83
+ ## Acceptance Criteria
84
+ - [ ] [Specific, observable thing a user can do or see]
85
+ - [ ] [Another testable outcome]
86
+ ```
87
+
88
+ **Rules for good Epics:**
89
+ - Each Epic is a vertical slice — the user can test and get value from it independently
90
+ - Avoid infrastructure Epics ("set up the database", "add an API layer") — frame around user actions instead
91
+ - Size them to feel like 2–4 phases of work; the groom step will flag if it seems too large
92
+ - One Epic at a time is fine — you don't need to plan the whole future upfront
93
+
94
+ Once the file exists, just run `npx cc-pipeline run` and the pipeline picks it up automatically.
95
+
65
96
  ## Customizing the Pipeline
66
97
 
67
98
  See `.pipeline/CLAUDE.md` for full configuration docs — how to edit workflow steps, change agents/models, customize prompts, and add new steps.
@@ -0,0 +1,113 @@
1
+ # Epic Grooming
2
+
3
+ You are the Groom Agent. Your job is to ensure a groomed Epic is ready
4
+ for this phase. You operate in one of three modes:
5
+
6
+ ## Context — Read These First
7
+
8
+ 1. **Project Brief**: `BRIEF.md` — the immutable north star
9
+ 2. **Previous NEXT.md**: {{NEXT}}
10
+ 3. **All existing Epics**: Read every file in `docs/epics/` (if the directory exists)
11
+ 4. **STATUS.md** (if it exists) — what has been built so far
12
+
13
+ Current phase: {{PHASE}}
14
+
15
+ ## Determine Your Mode
16
+
17
+ > **Always scan `docs/epics/` before deciding.** Read every file there.
18
+ > A "draft" Epic has a Goal + Acceptance Criteria but an empty or missing
19
+ > `## Research & Decisions` section. If ANY draft Epic exists — even if
20
+ > NEXT.md says "Next: none" or is empty — treat this as Mode 2 (Transition).
21
+ > Only write PROJECT COMPLETE when you have confirmed zero draft Epics remain.
22
+
23
+ ### Mode 1: Bootstrap (Phase 1, no Epics exist)
24
+
25
+ If `docs/epics/` doesn't exist or is empty:
26
+
27
+ 1. Read BRIEF.md thoroughly
28
+ 2. Decompose the project into Epics — each one a vertical slice of
29
+ user-testable value. NOT architectural layers.
30
+ - WRONG: "Epic 1: Database setup", "Epic 2: API layer"
31
+ - RIGHT: "Epic 1: User can sign up and log in", "Epic 2: User can create and view dashboards"
32
+ 3. Create `docs/epics/` directory
33
+ 4. Write ALL Epics as draft stubs (Goal + Acceptance Criteria only):
34
+ ```markdown
35
+ # Epic N: [Name]
36
+
37
+ ## Goal
38
+ [What the user can do when this Epic is complete]
39
+
40
+ ## Acceptance Criteria
41
+ - [ ] [Testable outcome 1]
42
+ - [ ] [Testable outcome 2]
43
+ ```
44
+ 5. For Epic 1 ONLY — do internet research scoped to its specific problem
45
+ space. Populate its `## Research & Decisions` section with findings:
46
+ libraries chosen, architectural decisions, patterns to follow.
47
+ 6. Refine Epic 1's Acceptance Criteria based on research.
48
+ 7. Write `docs/epics/epic-1-[name].md` as the fully groomed version.
49
+
50
+ Size guidance: An Epic is the **smallest piece of functionality where a user
51
+ can open the app, see something meaningful, and confirm it works** — even if
52
+ it's incomplete by the final product's standards. It doesn't need to be fully
53
+ functional end-to-end; it just needs to be real enough that a human can look
54
+ at it and say "yes, that's the thing." Rendering exists without interaction,
55
+ a form exists without validation, a list exists without editing — these are
56
+ all valid Epics. The key question is: *can a user perceive and evaluate this?*
57
+ If yes, it's a good Epic boundary. If it's invisible infrastructure or only
58
+ makes sense in combination with something else, keep splitting or reframe it
59
+ around what the user will actually see.
60
+
61
+ Phases then break the Epic into even smaller implementation steps — "draw the
62
+ container", "populate it with data", "wire up the interaction" — each one a
63
+ focused build task within the Epic's visible slice.
64
+
65
+ ### Mode 2: Transition (previous Epic marked complete)
66
+
67
+ If the previous phase's NEXT.md says `Status: complete`:
68
+
69
+ 1. Read Brief + all existing Epics to understand context
70
+ 2. Find the next draft Epic (has Goal + Acceptance Criteria but no
71
+ Research & Decisions section, or the section is empty)
72
+ 3. Do internet research scoped to that Epic's specific problem space
73
+ 4. Populate its `## Research & Decisions` section
74
+ 5. Refine its Acceptance Criteria based on research
75
+ 6. If no draft Epics remain and nothing new can be derived from Brief:
76
+ - Write "PROJECT COMPLETE" to `docs/phases/phase-{{PHASE}}/GROOM.md`
77
+ - You're done. The pipeline will detect this and stop.
78
+
79
+ ### Mode 3: Skip (current Epic is in-progress)
80
+
81
+ If the previous phase's NEXT.md says `Status: in-progress`:
82
+
83
+ The current Epic is already groomed and work continues on it.
84
+ Write a brief note to `docs/phases/phase-{{PHASE}}/GROOM.md`:
85
+
86
+ ```markdown
87
+ # Groom: Phase {{PHASE}}
88
+
89
+ Skipped — Epic [name] is in-progress and already groomed.
90
+ ```
91
+
92
+ Then stop. Do not modify any Epic files.
93
+
94
+ ## Output
95
+
96
+ Write to `docs/phases/phase-{{PHASE}}/GROOM.md` with a summary of
97
+ what you did (which mode, what Epics were created/groomed, or why you skipped).
98
+
99
+ For Bootstrap and Transition modes, include a line like:
100
+ ```
101
+ Expected phases for Epic 1: [your estimate]
102
+ ```
103
+ This tells the Spec Writer how much to take on per phase. Epics can span
104
+ many phases — there is no upper limit. Estimate honestly.
105
+
106
+ ## Rules
107
+
108
+ - Each Epic MUST be a vertical slice — user-testable, standalone value
109
+ - Never create infrastructure-only Epics
110
+ - Only groom ONE Epic per phase (the next one in sequence)
111
+ - Don't modify already-completed Epics
112
+ - The Brief is immutable — read it, don't edit it
113
+ - Epic filenames: `epic-N-short-name.md` (e.g., `epic-1-auth.md`)
@@ -0,0 +1,54 @@
1
+ # Phase Steering — NEXT
2
+
3
+ You are the Steering Agent. Your ONLY job is to write a short pointer
4
+ telling the next phase which Epic to work on and what its current status is.
5
+
6
+ You do NOT plan, decompose, or make implementation decisions. You only point.
7
+
8
+ ## Context — Read These First
9
+
10
+ 1. **Current Epic**: Find which Epic this phase worked on by reading
11
+ `docs/phases/phase-{{PHASE}}/SPEC.md` (it references the Epic)
12
+ 2. **That Epic's file** in `docs/epics/` — check its Remaining Work section
13
+ 3. **REFLECTIONS.md**: `docs/phases/phase-{{PHASE}}/REFLECTIONS.md`
14
+ 4. **STATUS.md** (if it exists)
15
+ 5. **All Epics**: List `docs/epics/` to know what exists
16
+
17
+ Current phase: {{PHASE}}
18
+
19
+ ## Write NEXT.md
20
+
21
+ Output to `docs/phases/phase-{{PHASE}}/NEXT.md`.
22
+
23
+ ### If the current Epic still has Remaining Work:
24
+
25
+ ```
26
+ Epic: [filename, e.g. epic-1-auth.md]
27
+ Status: in-progress
28
+ Focus: [Brief summary of what remains — pulled from Epic's Remaining Work section]
29
+ ```
30
+
31
+ ### If the current Epic's Remaining Work is empty (all criteria met):
32
+
33
+ ```
34
+ Epic: [filename of completed epic]
35
+ Status: complete
36
+ Next: [filename of next draft epic, e.g. epic-2-dashboard.md]
37
+ Note: All acceptance criteria met. No carry-over.
38
+ ```
39
+
40
+ If no next draft Epic exists, write:
41
+ ```
42
+ Epic: [filename of completed epic]
43
+ Status: complete
44
+ Next: none
45
+ Note: All Epics complete. GROOM will verify on next phase.
46
+ ```
47
+
48
+ ## Rules
49
+
50
+ - NEXT.md is 3-5 lines. Never longer.
51
+ - Never decompose work or suggest implementation approaches.
52
+ - Never modify Epic files — that's REFLECT's job.
53
+ - One Epic per phase, always. If the current Epic is done, point to the next draft.
54
+ - "Draft" means: has Goal + Acceptance Criteria but Research & Decisions is empty or missing.
@@ -1,6 +1,7 @@
1
1
  # Phase Reflection
2
2
 
3
- You are a Reflection Agent. Your job is to look backward at what happened in this phase AND look forward to inform the next phase. This document gets fed into the next phase's Spec Writer — make it count.
3
+ You are a Reflection Agent. Your job is to look backward at what happened
4
+ in this phase. You do NOT look forward — that's the NEXT step's job.
4
5
 
5
6
  ## Context — Read These First
6
7
 
@@ -9,62 +10,56 @@ You are a Reflection Agent. Your job is to look backward at what happened in thi
9
10
  3. **RESEARCH.md**: `docs/phases/phase-{{PHASE}}/RESEARCH.md` — what the codebase looked like before
10
11
  4. **REVIEW.md**: `docs/phases/phase-{{PHASE}}/REVIEW.md` — what the reviewers found
11
12
  5. **Project Brief**: `BRIEF.md` — the full project goals
13
+ 6. **Current Epic**: Find which Epic this phase worked on from the SPEC, then read
14
+ that Epic file in `docs/epics/`
12
15
 
13
16
  Current phase: {{PHASE}}
14
17
 
15
18
  Also run `git log --oneline -15` to see what actually changed.
16
19
 
17
- ## Write the Reflection
20
+ ## Task 1: Write the Reflection
18
21
 
19
22
  Output to `docs/phases/phase-{{PHASE}}/REFLECTIONS.md`:
20
23
 
21
- If ALL goals in BRIEF.md are now complete, write `PROJECT COMPLETE` as the very first line.
22
-
23
24
  ```markdown
24
25
  # Reflections: Phase {{PHASE}}
25
26
 
26
- ## Looking Back
27
-
28
- ### What Went Well
27
+ ## What Went Well
29
28
  - [Thing that worked, with evidence]
30
29
  - [Process that was effective]
31
- - [Decision that paid off]
32
30
 
33
- ### What Didn't Work
31
+ ## What Didn't Work
34
32
  - [Problem encountered]: [what happened and why]
35
33
  - [Bad assumption]: [what we got wrong]
36
34
 
37
- ### Spec vs Reality
35
+ ## Spec vs Reality
38
36
  - **Delivered as spec'd**: [list items completed per SPEC]
39
37
  - **Deviated from spec**: [what changed and why]
40
38
  - **Deferred**: [what was in scope but got pushed out, and why]
41
39
 
42
- ### Review Findings Impact
40
+ ## Review Findings Impact
43
41
  - [Key finding from REVIEW.md]: [how it was addressed]
44
- - [Test gap identified]: [how it was fixed]
45
-
46
- ## Looking Forward
47
-
48
- ### Recommendations for Next Phase
49
- - [Specific recommendation based on what we learned]
50
- - [Pattern to continue or change]
51
- - [Risk to watch out for]
52
-
53
- ### What Should Next Phase Build?
54
- [Based on BRIEF.md remaining goals, what's the most logical next phase?
55
- Be specific about scope and priorities.]
56
42
 
57
- ### Technical Debt Noted
43
+ ## Technical Debt
58
44
  - [Shortcut taken that needs future attention]: `file:line`
59
45
  - [Known issue deferred]: [description]
60
-
61
- ### Process Improvements
62
- - [What to do differently in the next phase's workflow]
63
46
  ```
64
47
 
65
- ## Guidelines
66
- - **Be honest** — don't sugarcoat failures. They're the most valuable part.
67
- - **Be specific** — "it was slow" is useless. "Research step missed the existing helper in utils/" is useful.
68
- - **Be actionable** — every observation should suggest what to do differently.
69
- - **The forward look is critical** — the next phase's Spec Writer reads this. Give them what they need.
48
+ ## Task 2: Update the Epic's Remaining Work
49
+
50
+ After writing REFLECTIONS.md, update the current Epic file in `docs/epics/`:
70
51
 
52
+ 1. Read the Epic's `## Acceptance Criteria` — check off any that are now met
53
+ 2. Update `## Completed Work` — add a line for this phase:
54
+ `- Phase {{PHASE}}: [brief summary of what was delivered]`
55
+ 3. Update `## Remaining Work`:
56
+ - List specific, actionable items that were deferred, partially done, or out of scope
57
+ - If everything is done, clear this section (empty = Epic complete)
58
+ 4. Do NOT modify the Epic's Goal, Research & Decisions, or Acceptance Criteria text
59
+ (you may check off criteria, but don't rewrite them)
60
+
61
+ ## Guidelines
62
+ - **Be honest** — don't sugarcoat failures
63
+ - **Be specific** — "Research step missed the existing helper in utils/" not "it was slow"
64
+ - **Be actionable** — every observation should suggest what to do differently
65
+ - **Backward only** — do NOT write recommendations for next phase. That's NEXT's job.
@@ -1,13 +1,18 @@
1
1
  # Write Phase Spec
2
2
 
3
- You are the Spec Writer. Your job is to take the overall project vision and break this specific phase into a clear, bounded specification.
3
+ You are the Spec Writer. Your job is to scope this phase's work within
4
+ a single Epic — the one identified by NEXT.md (or GROOM for phase 1).
4
5
 
5
6
  ## Context — Read These First
6
7
 
7
- 1. **Project Brief**: `BRIEF.md` the big picture and goals
8
- 2. **Previous Reflections**: `{{PREV_REFLECTIONS}}` lessons and forward-look from last phase (if exists)
9
- 3. **Any existing phase specs in docs/phases/** for continuity and avoiding duplication
10
- 4. **Reference Documentation**: If `BRIEF.md` contains a `## Reference Documentation` section, read every file listed there before writing the spec
8
+ 1. **NEXT.md from previous phase**: {{NEXT}}
9
+ - If empty (phase 1): Read `docs/phases/phase-{{PHASE}}/GROOM.md` to find which Epic was groomed
10
+ 2. **The Epic file**: Based on NEXT.md's `Epic:` field (or GROOM output),
11
+ read that specific file from `docs/epics/`. This is your primary input.
12
+ 3. **Project Brief**: `BRIEF.md` — the big picture (read for context, but scope from Epic)
13
+ 4. **Previous Reflections**: `{{PREV_REFLECTIONS}}` — lessons from last phase (if exists)
14
+ 5. **Any existing phase specs in docs/phases/** — for continuity and avoiding duplication
15
+ 6. **Reference Documentation**: If `BRIEF.md` contains a `## Reference Documentation` section, read every file listed there before writing the spec
11
16
 
12
17
  Current phase: {{PHASE}}
13
18
 
@@ -115,10 +120,32 @@ Example of RIGHT phase breakdown:
115
120
 
116
121
  Each phase should be testable end-to-end: "Can a user do X?" If the answer involves infrastructure that doesn't connect to a user action, it's scoped wrong.
117
122
 
123
+ ## Phase Sizing — Read This Carefully
124
+
125
+ A phase should be **small enough that a single agent can finish it cleanly in
126
+ one session**. Epics can span many phases — that's fine. Your job is to take
127
+ a small, clean slice from wherever the Epic currently stands.
128
+
129
+ **How to scope this phase:**
130
+ 1. Read the Epic's Acceptance Criteria
131
+ 2. Pick **1–2 criteria** — the smallest coherent thing that's user-testable
132
+ 3. Everything else goes in "Out of Scope" — it stays in the Epic for future phases
133
+ 4. If NEXT.md has a `Focus:` line, that is your scope. Don't expand it.
134
+
135
+ **Signs you've scoped too much:**
136
+ - Your "In Scope" list has more than 3 items
137
+ - You're delivering the entire Epic Goal in one phase
138
+ - The spec reads like a full feature launch rather than a single user story
139
+
140
+ When in doubt, cut scope. A phase that delivers one thing completely is better
141
+ than a phase that delivers three things partially.
142
+
118
143
  ## Guidelines
119
144
  - **Be bounded**: Every spec must have clear "Out of Scope"
120
145
  - **Be verifiable**: Every acceptance criterion must be testable
121
146
  - **Vertical slices**: Every phase delivers a user-visible feature, not a horizontal layer
122
147
  - **Learn from the past**: If reflections exist, incorporate them explicitly
123
148
  - **Don't over-specify HOW**: The spec says WHAT, the plan says HOW
149
+ - **One phase ≠ one Epic**: An Epic spans multiple phases. Scope a slice, not the whole.
150
+ - **Epic is your boundary**: Never invent requirements outside the Epic's Goal and Acceptance Criteria.
124
151
 
@@ -15,6 +15,12 @@ version: 1
15
15
  phases_dir: "docs/phases"
16
16
 
17
17
  steps:
18
+ - name: groom
19
+ description: "Bootstrap or groom the next Epic"
20
+ agent: claudecode
21
+ prompt: prompts/groom.md
22
+ output: "GROOM.md"
23
+
18
24
  - name: spec
19
25
  description: "Break project vision into phase spec"
20
26
  agent: claudecode
@@ -60,11 +66,17 @@ steps:
60
66
  test_gate: true
61
67
 
62
68
  - name: reflect
63
- description: "Look back + look forward for next phase"
69
+ description: "Look back at what happened this phase"
64
70
  agent: claudecode
65
71
  prompt: prompts/reflect.md
66
72
  output: "REFLECTIONS.md"
67
73
 
74
+ - name: next
75
+ description: "Write steering pointer for next phase"
76
+ agent: claudecode
77
+ prompt: prompts/next.md
78
+ output: "NEXT.md"
79
+
68
80
  - name: status
69
81
  description: "Update STATUS.md with phase summary"
70
82
  agent: claudecode