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 +1 -1
- package/src/agents/claudecode.ts +2 -1
- package/src/config.ts +2 -0
- package/src/engine.ts +19 -18
- package/src/prompts.ts +37 -1
- package/src/state.test.ts +40 -0
- package/src/state.ts +6 -1
- package/templates/CLAUDE.md +31 -0
- package/templates/pipeline/prompts/groom.md +113 -0
- package/templates/pipeline/prompts/next.md +54 -0
- package/templates/pipeline/prompts/reflect.md +27 -32
- package/templates/pipeline/prompts/spec.md +32 -5
- package/templates/pipeline/workflow.yaml +13 -1
package/package.json
CHANGED
package/src/agents/claudecode.ts
CHANGED
|
@@ -47,7 +47,8 @@ export class ClaudeCodeAgent extends BaseAgent {
|
|
|
47
47
|
|
|
48
48
|
const appendOutput = (line: string) => {
|
|
49
49
|
try {
|
|
50
|
-
|
|
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
|
|
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
|
|
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 <=
|
|
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
|
|
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' };
|
package/templates/CLAUDE.md
CHANGED
|
@@ -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
|
|
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
|
-
##
|
|
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
|
-
|
|
31
|
+
## What Didn't Work
|
|
34
32
|
- [Problem encountered]: [what happened and why]
|
|
35
33
|
- [Bad assumption]: [what we got wrong]
|
|
36
34
|
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
66
|
-
|
|
67
|
-
|
|
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
|
|
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. **
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
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
|