@ai-dev-methodologies/rlp-desk 0.8.0 → 0.9.1
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/docs/plans/validated-snacking-crayon.md +204 -0
- package/docs/superpowers/plans/2026-04-12-flywheel-redesign.md +704 -0
- package/docs/superpowers/specs/2026-04-12-flywheel-redesign.md +161 -0
- package/package.json +1 -1
- package/src/commands/rlp-desk.md +25 -2
- package/src/governance.md +1 -1
- package/src/node/reporting/campaign-reporting.mjs +364 -0
- package/src/node/run.mjs +12 -0
- package/src/node/runner/campaign-main-loop.mjs +104 -1
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
# Flywheel Redesign Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Replace the incomplete pivot step with a proper flywheel direction-review step that internalizes plan-ceo-review's core framework (premise challenge, forced alternatives, scope decisions, CEO cognitive patterns) within the campaign loop.
|
|
6
|
+
|
|
7
|
+
**Architecture:** On Verifier FAIL, a fresh-context Flywheel agent runs BEFORE the next Worker. It reviews premises, proposes alternatives, decides scope (HOLD/PIVOT/REDUCE/EXPAND), rewrites the iteration contract, and records rejected directions. Worker then executes the updated contract. In tmux mode, Flywheel gets its own pane (leftmost). In agent mode, Leader calls Agent().
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Node.js (ESM), zsh (init/run scripts), Claude Code Agent API
|
|
10
|
+
|
|
11
|
+
**Spec:** `docs/superpowers/specs/2026-04-12-flywheel-redesign.md`
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
### Task 1: Remove existing pivot code
|
|
16
|
+
|
|
17
|
+
**Files:**
|
|
18
|
+
- Modify: `src/node/runner/campaign-main-loop.mjs` (remove pivot functions + wiring)
|
|
19
|
+
- Modify: `src/node/run.mjs` (remove --pivot-mode, --pivot-model)
|
|
20
|
+
- Modify: `src/scripts/init_ralph_desk.zsh` (remove pivot prompt template + cleanup refs)
|
|
21
|
+
- Modify: `src/commands/rlp-desk.md` (remove pivot flags from options reference)
|
|
22
|
+
- Delete: `tests/node/test-pivot-step.mjs`
|
|
23
|
+
|
|
24
|
+
- [ ] **Step 1: Remove pivot functions from campaign-main-loop.mjs**
|
|
25
|
+
|
|
26
|
+
Remove these functions entirely:
|
|
27
|
+
- `shouldRunPivot()` (lines 402-408)
|
|
28
|
+
- `buildPivotTriggerCmd()` (lines 410-412)
|
|
29
|
+
- `dispatchPivot()` (lines 414-425)
|
|
30
|
+
|
|
31
|
+
Remove pivot paths from `buildPaths()`:
|
|
32
|
+
```javascript
|
|
33
|
+
// DELETE these two lines:
|
|
34
|
+
pivotPromptFile: path.join(deskRoot, 'prompts', `${slug}.pivot.prompt.md`),
|
|
35
|
+
pivotSignalFile: path.join(deskRoot, 'memos', `${slug}-pivot-signal.json`),
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Remove pivot wiring in the main loop (around line 539):
|
|
39
|
+
```javascript
|
|
40
|
+
// DELETE this entire block:
|
|
41
|
+
if (shouldRunPivot(options.pivotMode ?? 'off', state, lastVerdict)) {
|
|
42
|
+
state.phase = 'pivot';
|
|
43
|
+
// ... through to closing }
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Remove `lastVerdict` variable declaration and assignment (lines 474, 608).
|
|
48
|
+
|
|
49
|
+
- [ ] **Step 2: Remove pivot flags from run.mjs**
|
|
50
|
+
|
|
51
|
+
Remove from defaults (lines 24-25):
|
|
52
|
+
```javascript
|
|
53
|
+
// DELETE:
|
|
54
|
+
pivotMode: 'off',
|
|
55
|
+
pivotModel: 'opus',
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Remove from help text (lines 60-61):
|
|
59
|
+
```javascript
|
|
60
|
+
// DELETE:
|
|
61
|
+
' --pivot-mode off|every|on-fail',
|
|
62
|
+
' --pivot-model MODEL',
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Remove from flag parser (lines 150-155):
|
|
66
|
+
```javascript
|
|
67
|
+
// DELETE:
|
|
68
|
+
case '--pivot-mode':
|
|
69
|
+
options.pivotMode = consumeValue(args, index, token);
|
|
70
|
+
index += 1;
|
|
71
|
+
break;
|
|
72
|
+
case '--pivot-model':
|
|
73
|
+
options.pivotModel = consumeValue(args, index, token);
|
|
74
|
+
index += 1;
|
|
75
|
+
break;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- [ ] **Step 3: Remove pivot prompt template from init_ralph_desk.zsh**
|
|
79
|
+
|
|
80
|
+
Remove the entire pivot prompt section (starts with `# --- Pivot Prompt ---` around line 609, ends with the closing `fi` around line 660).
|
|
81
|
+
|
|
82
|
+
Remove pivot files from re-execution cleanup (lines 282-283):
|
|
83
|
+
```bash
|
|
84
|
+
# DELETE these from the cleanup list:
|
|
85
|
+
"$DESK/memos/$SLUG-pivot-signal.json" \
|
|
86
|
+
"$DESK/memos/$SLUG-pivot-review.md"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Remove pivot prompt from prompt cleanup (line 298):
|
|
90
|
+
```bash
|
|
91
|
+
# DELETE:
|
|
92
|
+
"$DESK/prompts/$SLUG.pivot.prompt.md"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Remove `--pivot-mode` and `--pivot-model` from `print_run_presets()` options reference.
|
|
96
|
+
|
|
97
|
+
- [ ] **Step 4: Remove pivot flags from rlp-desk.md options reference**
|
|
98
|
+
|
|
99
|
+
Remove `--pivot-mode` and `--pivot-model` lines from both codex-installed and codex-not-installed options blocks.
|
|
100
|
+
|
|
101
|
+
- [ ] **Step 5: Delete test-pivot-step.mjs**
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
rm tests/node/test-pivot-step.mjs
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- [ ] **Step 6: Fix us008 test regression**
|
|
108
|
+
|
|
109
|
+
`tests/node/us008-cli-entrypoint.test.mjs` has a deepEqual check on RUN_DEFAULTS that includes pivotMode/pivotModel. Update to remove them.
|
|
110
|
+
|
|
111
|
+
- [ ] **Step 7: Verify removal is clean**
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# No pivot references should remain (except blueprint doc):
|
|
115
|
+
grep -r "pivot" src/ tests/node/ --include="*.mjs" --include="*.zsh" --include="*.md" -l
|
|
116
|
+
# Expected: only docs/blueprints/blueprint-pivot-step.md
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
zsh -n src/scripts/init_ralph_desk.zsh && echo "SYNTAX OK"
|
|
121
|
+
node --test tests/node/us007-analytics-reporting.test.mjs
|
|
122
|
+
node --test tests/node/us008-cli-entrypoint.test.mjs
|
|
123
|
+
node --test tests/node/test-sv-report.mjs
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
- [ ] **Step 8: Commit**
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
git add -A
|
|
130
|
+
git commit -m "refactor: remove incomplete pivot step implementation"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
### Task 2: Add flywheel CLI flags + shouldRunFlywheel logic
|
|
136
|
+
|
|
137
|
+
**Files:**
|
|
138
|
+
- Modify: `src/node/run.mjs`
|
|
139
|
+
- Modify: `src/node/runner/campaign-main-loop.mjs`
|
|
140
|
+
- Create: `tests/node/test-flywheel.mjs`
|
|
141
|
+
|
|
142
|
+
- [ ] **Step 1: Write failing tests**
|
|
143
|
+
|
|
144
|
+
Create `tests/node/test-flywheel.mjs`:
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
import test from 'node:test';
|
|
148
|
+
import assert from 'node:assert/strict';
|
|
149
|
+
|
|
150
|
+
test('T1: shouldRunFlywheel returns false when flywheel=off', async () => {
|
|
151
|
+
const { shouldRunFlywheel } = await import('../../src/node/runner/campaign-main-loop.mjs');
|
|
152
|
+
assert.equal(shouldRunFlywheel('off', { consecutive_failures: 3 }), false);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('T2: shouldRunFlywheel returns true when flywheel=on-fail and consecutive_failures > 0', async () => {
|
|
156
|
+
const { shouldRunFlywheel } = await import('../../src/node/runner/campaign-main-loop.mjs');
|
|
157
|
+
assert.equal(shouldRunFlywheel('on-fail', { consecutive_failures: 1 }), true);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('T3: shouldRunFlywheel returns false when flywheel=on-fail and consecutive_failures=0', async () => {
|
|
161
|
+
const { shouldRunFlywheel } = await import('../../src/node/runner/campaign-main-loop.mjs');
|
|
162
|
+
assert.equal(shouldRunFlywheel('on-fail', { consecutive_failures: 0 }), false);
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
node --test tests/node/test-flywheel.mjs
|
|
170
|
+
```
|
|
171
|
+
Expected: FAIL (shouldRunFlywheel not exported)
|
|
172
|
+
|
|
173
|
+
- [ ] **Step 3: Implement shouldRunFlywheel + CLI flags**
|
|
174
|
+
|
|
175
|
+
In `src/node/runner/campaign-main-loop.mjs`, add:
|
|
176
|
+
```javascript
|
|
177
|
+
export function shouldRunFlywheel(flywheelMode, state) {
|
|
178
|
+
if (flywheelMode === 'off') return false;
|
|
179
|
+
if (flywheelMode === 'on-fail' && (state.consecutive_failures ?? 0) > 0) return true;
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
In `src/node/run.mjs`, add defaults:
|
|
185
|
+
```javascript
|
|
186
|
+
flywheel: 'off',
|
|
187
|
+
flywheelModel: 'opus',
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Add to help text:
|
|
191
|
+
```javascript
|
|
192
|
+
' --flywheel off|on-fail',
|
|
193
|
+
' --flywheel-model MODEL',
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Add to flag parser:
|
|
197
|
+
```javascript
|
|
198
|
+
case '--flywheel':
|
|
199
|
+
options.flywheel = consumeValue(args, index, token);
|
|
200
|
+
index += 1;
|
|
201
|
+
break;
|
|
202
|
+
case '--flywheel-model':
|
|
203
|
+
options.flywheelModel = consumeValue(args, index, token);
|
|
204
|
+
index += 1;
|
|
205
|
+
break;
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
- [ ] **Step 4: Run tests to verify they pass**
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
node --test tests/node/test-flywheel.mjs
|
|
212
|
+
```
|
|
213
|
+
Expected: 3 PASS
|
|
214
|
+
|
|
215
|
+
- [ ] **Step 5: Commit**
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
git add src/node/run.mjs src/node/runner/campaign-main-loop.mjs tests/node/test-flywheel.mjs
|
|
219
|
+
git commit -m "feat: add --flywheel flag and shouldRunFlywheel logic"
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
### Task 3: Flywheel prompt template in init_ralph_desk.zsh
|
|
225
|
+
|
|
226
|
+
**Files:**
|
|
227
|
+
- Modify: `src/scripts/init_ralph_desk.zsh`
|
|
228
|
+
- Modify: `tests/node/test-flywheel.mjs` (add prompt tests)
|
|
229
|
+
|
|
230
|
+
- [ ] **Step 1: Write failing tests**
|
|
231
|
+
|
|
232
|
+
Add to `tests/node/test-flywheel.mjs`:
|
|
233
|
+
|
|
234
|
+
```javascript
|
|
235
|
+
import fs from 'node:fs/promises';
|
|
236
|
+
import path from 'node:path';
|
|
237
|
+
import { fileURLToPath } from 'node:url';
|
|
238
|
+
|
|
239
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
240
|
+
|
|
241
|
+
test('T4: init generates flywheel prompt with 6 review steps', async () => {
|
|
242
|
+
const script = path.join(repoRoot, 'src', 'scripts', 'init_ralph_desk.zsh');
|
|
243
|
+
const content = await fs.readFile(script, 'utf8');
|
|
244
|
+
assert.match(content, /Premise Challenge/);
|
|
245
|
+
assert.match(content, /Existing Code Leverage/);
|
|
246
|
+
assert.match(content, /Ideal State Mapping/);
|
|
247
|
+
assert.match(content, /Implementation Alternatives/);
|
|
248
|
+
assert.match(content, /Scope Decision/);
|
|
249
|
+
assert.match(content, /Contract Rewrite/);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test('T5: flywheel prompt contains 10 CEO cognitive patterns', async () => {
|
|
253
|
+
const script = path.join(repoRoot, 'src', 'scripts', 'init_ralph_desk.zsh');
|
|
254
|
+
const content = await fs.readFile(script, 'utf8');
|
|
255
|
+
assert.match(content, /First-principles/);
|
|
256
|
+
assert.match(content, /10x check/);
|
|
257
|
+
assert.match(content, /Inversion/);
|
|
258
|
+
assert.match(content, /Simplicity bias/);
|
|
259
|
+
assert.match(content, /User-back/);
|
|
260
|
+
assert.match(content, /Time-value/);
|
|
261
|
+
assert.match(content, /Sunk cost immunity/);
|
|
262
|
+
assert.match(content, /Blast radius/);
|
|
263
|
+
assert.match(content, /Reversibility/);
|
|
264
|
+
assert.match(content, /Evidence > opinion/);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test('T6: flywheel prompt contains 4 scope decisions', async () => {
|
|
268
|
+
const script = path.join(repoRoot, 'src', 'scripts', 'init_ralph_desk.zsh');
|
|
269
|
+
const content = await fs.readFile(script, 'utf8');
|
|
270
|
+
assert.match(content, /HOLD.*current approach/i);
|
|
271
|
+
assert.match(content, /PIVOT.*alternative/i);
|
|
272
|
+
assert.match(content, /REDUCE.*simplif/i);
|
|
273
|
+
assert.match(content, /EXPAND.*missing/i);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test('T7: flywheel prompt writes to flywheel-signal.json', async () => {
|
|
277
|
+
const script = path.join(repoRoot, 'src', 'scripts', 'init_ralph_desk.zsh');
|
|
278
|
+
const content = await fs.readFile(script, 'utf8');
|
|
279
|
+
assert.match(content, /flywheel-signal\.json/);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test('T8: flywheel prompt records rejected directions', async () => {
|
|
283
|
+
const script = path.join(repoRoot, 'src', 'scripts', 'init_ralph_desk.zsh');
|
|
284
|
+
const content = await fs.readFile(script, 'utf8');
|
|
285
|
+
assert.match(content, /Rejected Directions/);
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
node --test tests/node/test-flywheel.mjs
|
|
293
|
+
```
|
|
294
|
+
Expected: T1-T3 PASS, T4-T8 FAIL
|
|
295
|
+
|
|
296
|
+
- [ ] **Step 3: Add flywheel prompt template to init_ralph_desk.zsh**
|
|
297
|
+
|
|
298
|
+
After the Verifier prompt section, add:
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
# --- Flywheel Prompt ---
|
|
302
|
+
F="$DESK/prompts/$SLUG.flywheel.prompt.md"
|
|
303
|
+
if [[ ! -f "$F" ]]; then
|
|
304
|
+
cat > "$F" <<'FLYWHEEL_EOF'
|
|
305
|
+
# Flywheel Direction Review
|
|
306
|
+
|
|
307
|
+
You are an independent direction reviewer with fresh context. After a Worker iteration failed verification, you decide whether the current approach should continue, pivot, or change scope.
|
|
308
|
+
|
|
309
|
+
## Context Files
|
|
310
|
+
Read these in order:
|
|
311
|
+
1. Campaign Memory: {DESK}/memos/{SLUG}-memory.md — especially Next Iteration Contract, Key Decisions, Rejected Directions
|
|
312
|
+
2. PRD: {DESK}/plans/prd-{SLUG}.md — acceptance criteria
|
|
313
|
+
3. Done Claim: {DESK}/memos/{SLUG}-done-claim.json — what Worker actually did
|
|
314
|
+
4. Verify Verdict: {DESK}/memos/{SLUG}-verify-verdict.json — why Verifier failed it
|
|
315
|
+
5. Latest Context: {DESK}/context/{SLUG}-latest.md — current state
|
|
316
|
+
|
|
317
|
+
## CEO Cognitive Patterns (apply throughout your review)
|
|
318
|
+
1. First-principles — ignore convention, start from the problem itself
|
|
319
|
+
2. 10x check — can 2x effort yield 10x better result?
|
|
320
|
+
3. Inversion — what must be true for this approach to fail?
|
|
321
|
+
4. Simplicity bias — prefer simple over complex solutions
|
|
322
|
+
5. User-back — reason backwards from end-user experience
|
|
323
|
+
6. Time-value — does this direction change save 3+ iterations?
|
|
324
|
+
7. Sunk cost immunity — ignore what was already invested
|
|
325
|
+
8. Blast radius — assess impact scope of direction change
|
|
326
|
+
9. Reversibility — prefer easily reversible decisions
|
|
327
|
+
10. Evidence > opinion — judge only by this iteration's actual results
|
|
328
|
+
|
|
329
|
+
## Review Process
|
|
330
|
+
|
|
331
|
+
### Step 0A: Premise Challenge
|
|
332
|
+
List every assumption the current approach depends on.
|
|
333
|
+
For each assumption, state whether THIS iteration's evidence supports or contradicts it.
|
|
334
|
+
- Supported: "Assumption X — SUPPORTED: [evidence from done-claim/verdict]"
|
|
335
|
+
- Contradicted: "Assumption X — BROKEN: [evidence]. This means [implication]."
|
|
336
|
+
If any premise is broken, PIVOT or REDUCE is likely the right call.
|
|
337
|
+
|
|
338
|
+
### Step 0B: Existing Code Leverage
|
|
339
|
+
- Did the Worker miss reusable code that already exists in the project?
|
|
340
|
+
- Would a different approach align better with existing patterns?
|
|
341
|
+
- Check: are there utilities, helpers, or patterns the Worker could have used?
|
|
342
|
+
|
|
343
|
+
### Step 0C: Ideal State Mapping
|
|
344
|
+
Describe what this US looks like when perfectly implemented (2-3 sentences).
|
|
345
|
+
How far is the current approach from this ideal? What is the gap?
|
|
346
|
+
|
|
347
|
+
### Step 0D: Implementation Alternatives (MANDATORY)
|
|
348
|
+
Propose at least 2 alternative approaches. For each:
|
|
349
|
+
- Summary (1-2 sentences)
|
|
350
|
+
- Effort: S (< 1 iteration) / M (1-2 iterations) / L (3+ iterations)
|
|
351
|
+
- Risk: low / medium / high
|
|
352
|
+
- Key tradeoff vs current approach
|
|
353
|
+
|
|
354
|
+
Do NOT skip this step. Even if the current approach seems correct, articulate alternatives.
|
|
355
|
+
|
|
356
|
+
### Step 0E: Scope Decision
|
|
357
|
+
Choose ONE. Justify with evidence from this iteration only:
|
|
358
|
+
- **HOLD**: Premises valid, approach correct. Refine the contract with specific fixes: "[fix 1], [fix 2]"
|
|
359
|
+
- **PIVOT**: Premise [X] broken. Switch to Alternative [A]. Reason: [evidence]
|
|
360
|
+
- **REDUCE**: AC [N] too complex at current scope. Split into [parts] or simplify to [simpler version]
|
|
361
|
+
- **EXPAND**: Missing prerequisite [Y] discovered. Add to contract: [what to add]
|
|
362
|
+
|
|
363
|
+
### Step 0F: Contract Rewrite
|
|
364
|
+
Based on your decision, update campaign memory:
|
|
365
|
+
1. Rewrite "Next Iteration Contract" with the new direction
|
|
366
|
+
2. Append your decision and reasoning to "Key Decisions"
|
|
367
|
+
3. If rejecting an approach, append to "Rejected Directions" section:
|
|
368
|
+
"DO NOT retry: [approach description]. Reason: [why it failed]. Evidence: [from iteration N]."
|
|
369
|
+
The next Worker MUST read Rejected Directions before starting.
|
|
370
|
+
|
|
371
|
+
## Output Files
|
|
372
|
+
|
|
373
|
+
1. Write analysis to: {DESK}/memos/{SLUG}-flywheel-review.md
|
|
374
|
+
2. Update campaign memory: {DESK}/memos/{SLUG}-memory.md
|
|
375
|
+
3. Write signal: {DESK}/memos/{SLUG}-flywheel-signal.json
|
|
376
|
+
Format: {"iteration": N, "decision": "hold|pivot|reduce|expand", "summary": "one line", "rejected_directions": ["approach X because Y"], "contract_updated": true, "timestamp": "ISO"}
|
|
377
|
+
FLYWHEEL_EOF
|
|
378
|
+
|
|
379
|
+
# Replace placeholders with actual paths
|
|
380
|
+
sed -i '' "s|{DESK}|$DESK|g; s|{SLUG}|$SLUG|g" "$F"
|
|
381
|
+
|
|
382
|
+
echo " + $F"
|
|
383
|
+
else echo " · $F"; fi
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Also add flywheel files to re-execution cleanup and add `--flywheel` flags to `print_run_presets()` options reference.
|
|
387
|
+
|
|
388
|
+
- [ ] **Step 4: Run tests to verify they pass**
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
zsh -n src/scripts/init_ralph_desk.zsh && echo "SYNTAX OK"
|
|
392
|
+
node --test tests/node/test-flywheel.mjs
|
|
393
|
+
```
|
|
394
|
+
Expected: T1-T8 all PASS, syntax OK
|
|
395
|
+
|
|
396
|
+
- [ ] **Step 5: Commit**
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
git add src/scripts/init_ralph_desk.zsh tests/node/test-flywheel.mjs
|
|
400
|
+
git commit -m "feat: add flywheel prompt template with 6-step CEO framework"
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
### Task 4: Wire flywheel into campaign-main-loop.mjs
|
|
406
|
+
|
|
407
|
+
**Files:**
|
|
408
|
+
- Modify: `src/node/runner/campaign-main-loop.mjs`
|
|
409
|
+
- Modify: `tests/node/test-flywheel.mjs` (add wiring tests)
|
|
410
|
+
|
|
411
|
+
- [ ] **Step 1: Write failing tests**
|
|
412
|
+
|
|
413
|
+
Add to `tests/node/test-flywheel.mjs`:
|
|
414
|
+
|
|
415
|
+
```javascript
|
|
416
|
+
test('T9: buildPaths includes flywheel paths', async () => {
|
|
417
|
+
// buildPaths is not exported, so test indirectly via init+run
|
|
418
|
+
const script = path.join(repoRoot, 'src', 'node', 'runner', 'campaign-main-loop.mjs');
|
|
419
|
+
const content = await fs.readFile(script, 'utf8');
|
|
420
|
+
assert.match(content, /flywheelPromptFile/);
|
|
421
|
+
assert.match(content, /flywheelSignalFile/);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
test('T10: flywheel dispatch exists for both tmux and agent modes', async () => {
|
|
425
|
+
const script = path.join(repoRoot, 'src', 'node', 'runner', 'campaign-main-loop.mjs');
|
|
426
|
+
const content = await fs.readFile(script, 'utf8');
|
|
427
|
+
assert.match(content, /dispatchFlywheel/);
|
|
428
|
+
assert.match(content, /phase.*flywheel/i);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test('T11: flywheel runs BEFORE worker in the loop', async () => {
|
|
432
|
+
const script = path.join(repoRoot, 'src', 'node', 'runner', 'campaign-main-loop.mjs');
|
|
433
|
+
const content = await fs.readFile(script, 'utf8');
|
|
434
|
+
const flywheelPos = content.indexOf('shouldRunFlywheel');
|
|
435
|
+
const workerPos = content.indexOf('dispatchWorker', flywheelPos);
|
|
436
|
+
assert.ok(flywheelPos < workerPos, 'flywheel check must appear before worker dispatch');
|
|
437
|
+
});
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
node --test tests/node/test-flywheel.mjs
|
|
444
|
+
```
|
|
445
|
+
Expected: T1-T8 PASS, T9-T11 FAIL
|
|
446
|
+
|
|
447
|
+
- [ ] **Step 3: Implement flywheel wiring**
|
|
448
|
+
|
|
449
|
+
In `campaign-main-loop.mjs`:
|
|
450
|
+
|
|
451
|
+
Add to `buildPaths()`:
|
|
452
|
+
```javascript
|
|
453
|
+
flywheelPromptFile: path.join(deskRoot, 'prompts', `${slug}.flywheel.prompt.md`),
|
|
454
|
+
flywheelSignalFile: path.join(deskRoot, 'memos', `${slug}-flywheel-signal.json`),
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Add `buildFlywheelTriggerCmd()`:
|
|
458
|
+
```javascript
|
|
459
|
+
function buildFlywheelTriggerCmd({ flywheelPromptFile, flywheelModel, rootDir }) {
|
|
460
|
+
return `cd ${JSON.stringify(rootDir)} && DISABLE_OMC=1 claude --model ${flywheelModel} --no-mcp -p "$(cat ${JSON.stringify(flywheelPromptFile)})"`;
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Add `dispatchFlywheel()`:
|
|
465
|
+
```javascript
|
|
466
|
+
async function dispatchFlywheel({ paths, sendKeys, flywheelPaneId, flywheelModel, rootDir }) {
|
|
467
|
+
const triggerCmd = buildFlywheelTriggerCmd({
|
|
468
|
+
flywheelPromptFile: paths.flywheelPromptFile,
|
|
469
|
+
flywheelModel,
|
|
470
|
+
rootDir,
|
|
471
|
+
});
|
|
472
|
+
await sendKeys({ targetPaneId: flywheelPaneId, keys: triggerCmd });
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
In the main `while` loop, AFTER verdict handling and BEFORE `dispatchWorker`, insert:
|
|
477
|
+
```javascript
|
|
478
|
+
// Flywheel direction review (runs BEFORE Worker)
|
|
479
|
+
if (shouldRunFlywheel(options.flywheel ?? 'off', state)) {
|
|
480
|
+
state.phase = 'flywheel';
|
|
481
|
+
await writeStatus(paths, state, options.onStatusChange, options.now);
|
|
482
|
+
|
|
483
|
+
await dispatchFlywheel({
|
|
484
|
+
paths,
|
|
485
|
+
sendKeys,
|
|
486
|
+
flywheelPaneId: state.flywheel_pane_id ?? state.verifier_pane_id,
|
|
487
|
+
flywheelModel: options.flywheelModel ?? 'opus',
|
|
488
|
+
rootDir,
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
const flywheelSignal = await pollForSignal(paths.flywheelSignalFile, {
|
|
492
|
+
mode: 'claude',
|
|
493
|
+
paneId: state.flywheel_pane_id ?? state.verifier_pane_id,
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
state.last_flywheel_decision = flywheelSignal.decision;
|
|
497
|
+
// Campaign memory already updated by flywheel agent
|
|
498
|
+
// Clean signal file for next iteration
|
|
499
|
+
await fs.unlink(paths.flywheelSignalFile).catch(() => {});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
state.phase = 'worker';
|
|
503
|
+
// ... existing dispatchWorker code
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
- [ ] **Step 4: Run tests to verify they pass**
|
|
507
|
+
|
|
508
|
+
```bash
|
|
509
|
+
node --test tests/node/test-flywheel.mjs
|
|
510
|
+
```
|
|
511
|
+
Expected: T1-T11 all PASS
|
|
512
|
+
|
|
513
|
+
- [ ] **Step 5: Run regression tests**
|
|
514
|
+
|
|
515
|
+
```bash
|
|
516
|
+
node --test tests/node/us007-analytics-reporting.test.mjs
|
|
517
|
+
node --test tests/node/us008-cli-entrypoint.test.mjs
|
|
518
|
+
node --test tests/node/test-sv-report.mjs
|
|
519
|
+
```
|
|
520
|
+
Expected: all PASS
|
|
521
|
+
|
|
522
|
+
- [ ] **Step 6: Commit**
|
|
523
|
+
|
|
524
|
+
```bash
|
|
525
|
+
git add src/node/runner/campaign-main-loop.mjs tests/node/test-flywheel.mjs
|
|
526
|
+
git commit -m "feat: wire flywheel into campaign loop (before worker, after verify fail)"
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
### Task 5: 3-pane tmux layout + flywheel flags in docs
|
|
532
|
+
|
|
533
|
+
**Files:**
|
|
534
|
+
- Modify: `src/scripts/init_ralph_desk.zsh` (print_run_presets)
|
|
535
|
+
- Modify: `src/commands/rlp-desk.md` (options reference + flywheel docs)
|
|
536
|
+
- Modify: `src/node/runner/campaign-main-loop.mjs` (3rd pane creation)
|
|
537
|
+
- Modify: `tests/node/test-flywheel.mjs` (add docs tests)
|
|
538
|
+
|
|
539
|
+
- [ ] **Step 1: Write failing tests**
|
|
540
|
+
|
|
541
|
+
Add to `tests/node/test-flywheel.mjs`:
|
|
542
|
+
|
|
543
|
+
```javascript
|
|
544
|
+
test('T12: rlp-desk.md options reference includes flywheel flags', async () => {
|
|
545
|
+
const content = await fs.readFile(path.join(repoRoot, 'src', 'commands', 'rlp-desk.md'), 'utf8');
|
|
546
|
+
assert.match(content, /--flywheel off\|on-fail/);
|
|
547
|
+
assert.match(content, /--flywheel-model MODEL/);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
test('T13: init presets include flywheel in options reference', async () => {
|
|
551
|
+
const content = await fs.readFile(path.join(repoRoot, 'src', 'scripts', 'init_ralph_desk.zsh'), 'utf8');
|
|
552
|
+
assert.match(content, /--flywheel off\|on-fail/);
|
|
553
|
+
assert.match(content, /--flywheel-model MODEL/);
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
test('T14: campaign-main-loop creates flywheel pane in tmux mode', async () => {
|
|
557
|
+
const content = await fs.readFile(path.join(repoRoot, 'src', 'node', 'runner', 'campaign-main-loop.mjs'), 'utf8');
|
|
558
|
+
assert.match(content, /flywheel_pane_id/);
|
|
559
|
+
});
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
563
|
+
|
|
564
|
+
```bash
|
|
565
|
+
node --test tests/node/test-flywheel.mjs
|
|
566
|
+
```
|
|
567
|
+
Expected: T1-T11 PASS, T12-T14 FAIL
|
|
568
|
+
|
|
569
|
+
- [ ] **Step 3: Add flywheel flags to options references**
|
|
570
|
+
|
|
571
|
+
In `src/commands/rlp-desk.md`, add to BOTH codex-installed and codex-not-installed options blocks:
|
|
572
|
+
```
|
|
573
|
+
# --flywheel off|on-fail direction review on fail (default: off)
|
|
574
|
+
# --flywheel-model MODEL flywheel reviewer model (default: opus)
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
In `src/scripts/init_ralph_desk.zsh` `print_run_presets()`, add to the options reference echo block:
|
|
578
|
+
```bash
|
|
579
|
+
echo "# --flywheel off|on-fail direction review on fail (default: off)"
|
|
580
|
+
echo "# --flywheel-model MODEL flywheel reviewer model (default: opus)"
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
- [ ] **Step 4: Add 3rd pane creation in tmux mode**
|
|
584
|
+
|
|
585
|
+
In `campaign-main-loop.mjs`, in the session creation block (around line 440):
|
|
586
|
+
```javascript
|
|
587
|
+
state.flywheel_pane_id = await createPane({
|
|
588
|
+
targetPaneId: session.leaderPaneId,
|
|
589
|
+
layout: 'horizontal',
|
|
590
|
+
});
|
|
591
|
+
state.worker_pane_id = await createPane({
|
|
592
|
+
targetPaneId: session.leaderPaneId,
|
|
593
|
+
layout: 'horizontal',
|
|
594
|
+
});
|
|
595
|
+
state.verifier_pane_id = await createPane({
|
|
596
|
+
targetPaneId: session.leaderPaneId,
|
|
597
|
+
layout: 'vertical',
|
|
598
|
+
});
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
- [ ] **Step 5: Run tests to verify they pass**
|
|
602
|
+
|
|
603
|
+
```bash
|
|
604
|
+
node --test tests/node/test-flywheel.mjs
|
|
605
|
+
zsh -n src/scripts/init_ralph_desk.zsh && echo "SYNTAX OK"
|
|
606
|
+
```
|
|
607
|
+
Expected: T1-T14 all PASS, syntax OK
|
|
608
|
+
|
|
609
|
+
- [ ] **Step 6: Run full regression**
|
|
610
|
+
|
|
611
|
+
```bash
|
|
612
|
+
node --test tests/node/*.mjs tests/node/*.test.mjs 2>&1 | tail -5
|
|
613
|
+
```
|
|
614
|
+
Expected: 0 failures
|
|
615
|
+
|
|
616
|
+
- [ ] **Step 7: Commit**
|
|
617
|
+
|
|
618
|
+
```bash
|
|
619
|
+
git add -A
|
|
620
|
+
git commit -m "feat: flywheel 3-pane tmux layout + flags in docs and presets"
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
### Task 6: Self-verification (3 scenarios)
|
|
626
|
+
|
|
627
|
+
**Files:** none modified — verification only
|
|
628
|
+
|
|
629
|
+
- [ ] **Step 1: Scenario LOW — flywheel off**
|
|
630
|
+
|
|
631
|
+
```bash
|
|
632
|
+
SV_DIR=$(mktemp -d) && cd "$SV_DIR" && git init -q
|
|
633
|
+
mkdir -p .claude/ralph-desk
|
|
634
|
+
zsh /path/to/src/scripts/init_ralph_desk.zsh "sv-flywheel-off" "test"
|
|
635
|
+
# Verify: flywheel prompt generated but loop unchanged when --flywheel off
|
|
636
|
+
grep -q "flywheel" .claude/ralph-desk/prompts/sv-flywheel-off.flywheel.prompt.md && echo "PASS: prompt exists"
|
|
637
|
+
# Verify no flywheel pane created when off (Agent mode default)
|
|
638
|
+
rm -rf "$SV_DIR"
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
- [ ] **Step 2: Scenario MEDIUM — flywheel on-fail + FAIL triggers flywheel**
|
|
642
|
+
|
|
643
|
+
Run actual Worker → Verifier → Flywheel → Worker sequence:
|
|
644
|
+
1. Init campaign in test project
|
|
645
|
+
2. Worker agent implements (intentionally incomplete)
|
|
646
|
+
3. Verifier FAIL
|
|
647
|
+
4. Flywheel agent runs direction review
|
|
648
|
+
5. Verify: flywheel-signal.json written with decision
|
|
649
|
+
6. Verify: campaign memory updated with Key Decisions + Rejected Directions
|
|
650
|
+
7. Next Worker reads updated contract
|
|
651
|
+
|
|
652
|
+
- [ ] **Step 3: Scenario CRITICAL — PIVOT decision propagation**
|
|
653
|
+
|
|
654
|
+
1. Force a PIVOT decision from flywheel
|
|
655
|
+
2. Verify: Rejected Directions section in memory has the old approach
|
|
656
|
+
3. Verify: Next Iteration Contract is rewritten (not just patched)
|
|
657
|
+
4. Verify: Next Worker's done-claim shows a different approach
|
|
658
|
+
|
|
659
|
+
- [ ] **Step 4: Record results**
|
|
660
|
+
|
|
661
|
+
All 3 scenarios must PASS. Record in commit message.
|
|
662
|
+
|
|
663
|
+
- [ ] **Step 5: Final commit**
|
|
664
|
+
|
|
665
|
+
```bash
|
|
666
|
+
git add -A
|
|
667
|
+
git commit -m "feat: flywheel direction review — verified with 3 self-verification scenarios
|
|
668
|
+
|
|
669
|
+
Flywheel adds a CEO-framework direction review step that runs after Verifier FAIL
|
|
670
|
+
and before the next Worker. Internalizes premise challenge, forced alternatives,
|
|
671
|
+
scope decisions (HOLD/PIVOT/REDUCE/EXPAND), and 10 CEO cognitive patterns.
|
|
672
|
+
|
|
673
|
+
Self-verified: LOW (off mode), MEDIUM (on-fail trigger), CRITICAL (PIVOT propagation)."
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
---
|
|
677
|
+
|
|
678
|
+
### Task 7: Local sync + docs
|
|
679
|
+
|
|
680
|
+
**Files:**
|
|
681
|
+
- Sync all distributable files to `~/.claude/`
|
|
682
|
+
|
|
683
|
+
- [ ] **Step 1: Local file sync**
|
|
684
|
+
|
|
685
|
+
```bash
|
|
686
|
+
cp src/commands/rlp-desk.md ~/.claude/commands/rlp-desk.md
|
|
687
|
+
cp src/governance.md ~/.claude/ralph-desk/governance.md
|
|
688
|
+
cp src/scripts/init_ralph_desk.zsh ~/.claude/ralph-desk/init_ralph_desk.zsh
|
|
689
|
+
cp src/scripts/run_ralph_desk.zsh ~/.claude/ralph-desk/run_ralph_desk.zsh
|
|
690
|
+
cp src/scripts/lib_ralph_desk.zsh ~/.claude/ralph-desk/lib_ralph_desk.zsh
|
|
691
|
+
cp README.md ~/.claude/ralph-desk/README.md
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
- [ ] **Step 2: Verify sync**
|
|
695
|
+
|
|
696
|
+
```bash
|
|
697
|
+
diff -q src/commands/rlp-desk.md ~/.claude/commands/rlp-desk.md
|
|
698
|
+
diff -q src/governance.md ~/.claude/ralph-desk/governance.md
|
|
699
|
+
diff -q src/scripts/init_ralph_desk.zsh ~/.claude/ralph-desk/init_ralph_desk.zsh
|
|
700
|
+
diff -q src/scripts/run_ralph_desk.zsh ~/.claude/ralph-desk/run_ralph_desk.zsh
|
|
701
|
+
diff -q src/scripts/lib_ralph_desk.zsh ~/.claude/ralph-desk/lib_ralph_desk.zsh
|
|
702
|
+
diff -q README.md ~/.claude/ralph-desk/README.md
|
|
703
|
+
```
|
|
704
|
+
All must produce no output.
|