agileflow 2.81.0 → 2.82.0
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/README.md +3 -3
- package/package.json +1 -1
- package/scripts/ralph-loop.js +291 -11
- package/src/core/agents/orchestrator.md +135 -6
- package/src/core/commands/babysit.md +132 -0
- package/src/core/commands/batch.md +362 -0
- package/src/core/commands/choose.md +337 -0
- package/src/core/commands/workflow.md +344 -0
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/agileflow)
|
|
6
|
-
[](docs/04-architecture/commands.md)
|
|
7
7
|
[](docs/04-architecture/subagents.md)
|
|
8
8
|
[](docs/04-architecture/skills.md)
|
|
9
9
|
|
|
@@ -65,7 +65,7 @@ AgileFlow combines three proven methodologies:
|
|
|
65
65
|
|
|
66
66
|
| Component | Count | Description |
|
|
67
67
|
|-----------|-------|-------------|
|
|
68
|
-
| [Commands](docs/04-architecture/commands.md) |
|
|
68
|
+
| [Commands](docs/04-architecture/commands.md) | 71 | Slash commands for agile workflows |
|
|
69
69
|
| [Agents/Experts](docs/04-architecture/subagents.md) | 29 | Specialized agents with self-improving knowledge bases |
|
|
70
70
|
| [Skills](docs/04-architecture/skills.md) | Dynamic | Generated on-demand with `/agileflow:skill:create` |
|
|
71
71
|
|
|
@@ -76,7 +76,7 @@ AgileFlow combines three proven methodologies:
|
|
|
76
76
|
Full documentation lives in [`docs/04-architecture/`](docs/04-architecture/):
|
|
77
77
|
|
|
78
78
|
### Reference
|
|
79
|
-
- [Commands](docs/04-architecture/commands.md) - All
|
|
79
|
+
- [Commands](docs/04-architecture/commands.md) - All 71 slash commands
|
|
80
80
|
- [Agents/Experts](docs/04-architecture/subagents.md) - 29 specialized agents with self-improving knowledge
|
|
81
81
|
- [Skills](docs/04-architecture/skills.md) - Dynamic skill generator with MCP integration
|
|
82
82
|
|
package/package.json
CHANGED
package/scripts/ralph-loop.js
CHANGED
|
@@ -129,6 +129,190 @@ function getCoverageReportPath(rootDir) {
|
|
|
129
129
|
return 'coverage/coverage-summary.json';
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
// ===== DISCRETION MARKERS =====
|
|
133
|
+
// Semantic conditions wrapped in **...**
|
|
134
|
+
// These are evaluated by the loop to determine completion
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Built-in discretion conditions that can be evaluated programmatically
|
|
138
|
+
* Format: condition key -> evaluation function
|
|
139
|
+
*/
|
|
140
|
+
const DISCRETION_CONDITIONS = {
|
|
141
|
+
// Test-related conditions
|
|
142
|
+
'all tests passing': (rootDir, _ctx) => {
|
|
143
|
+
const testCommand = getTestCommand(rootDir);
|
|
144
|
+
const result = runTests(rootDir, testCommand);
|
|
145
|
+
return {
|
|
146
|
+
passed: result.passed,
|
|
147
|
+
message: result.passed
|
|
148
|
+
? 'All tests passing'
|
|
149
|
+
: `Tests failing: ${result.output.split('\n').slice(-3).join(' ').substring(0, 100)}`,
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
'tests pass': (rootDir, _ctx) => {
|
|
154
|
+
const testCommand = getTestCommand(rootDir);
|
|
155
|
+
const result = runTests(rootDir, testCommand);
|
|
156
|
+
return {
|
|
157
|
+
passed: result.passed,
|
|
158
|
+
message: result.passed ? 'Tests pass' : 'Tests failing',
|
|
159
|
+
};
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
// Coverage conditions (requires threshold in context)
|
|
163
|
+
'coverage above threshold': (rootDir, ctx) => {
|
|
164
|
+
const threshold = ctx.coverageThreshold || 80;
|
|
165
|
+
const result = verifyCoverage(rootDir, threshold);
|
|
166
|
+
return {
|
|
167
|
+
passed: result.passed,
|
|
168
|
+
message: `Coverage: ${result.coverage?.toFixed(1) || 0}% (threshold: ${threshold}%)`,
|
|
169
|
+
};
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
// Lint conditions
|
|
173
|
+
'no linting errors': (rootDir, _ctx) => {
|
|
174
|
+
try {
|
|
175
|
+
execSync('npm run lint', {
|
|
176
|
+
cwd: rootDir,
|
|
177
|
+
encoding: 'utf8',
|
|
178
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
179
|
+
timeout: 120000,
|
|
180
|
+
});
|
|
181
|
+
return { passed: true, message: 'No linting errors' };
|
|
182
|
+
} catch (e) {
|
|
183
|
+
return { passed: false, message: 'Linting errors found' };
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
// Type checking conditions
|
|
188
|
+
'no type errors': (rootDir, _ctx) => {
|
|
189
|
+
try {
|
|
190
|
+
execSync('npx tsc --noEmit', {
|
|
191
|
+
cwd: rootDir,
|
|
192
|
+
encoding: 'utf8',
|
|
193
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
194
|
+
timeout: 120000,
|
|
195
|
+
});
|
|
196
|
+
return { passed: true, message: 'No type errors' };
|
|
197
|
+
} catch (e) {
|
|
198
|
+
return { passed: false, message: 'Type errors found' };
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
// Build conditions
|
|
203
|
+
'build succeeds': (rootDir, _ctx) => {
|
|
204
|
+
try {
|
|
205
|
+
execSync('npm run build', {
|
|
206
|
+
cwd: rootDir,
|
|
207
|
+
encoding: 'utf8',
|
|
208
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
209
|
+
timeout: 300000,
|
|
210
|
+
});
|
|
211
|
+
return { passed: true, message: 'Build succeeds' };
|
|
212
|
+
} catch (e) {
|
|
213
|
+
return { passed: false, message: 'Build failed' };
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
// Screenshot/visual conditions
|
|
218
|
+
'all screenshots verified': (rootDir, _ctx) => {
|
|
219
|
+
const result = verifyScreenshots(rootDir);
|
|
220
|
+
return {
|
|
221
|
+
passed: result.passed,
|
|
222
|
+
message: result.passed
|
|
223
|
+
? 'All screenshots verified'
|
|
224
|
+
: `${result.unverified?.length || 0} unverified screenshots`,
|
|
225
|
+
};
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
// AC conditions (checks story acceptance criteria in status.json)
|
|
229
|
+
'all acceptance criteria verified': (rootDir, ctx) => {
|
|
230
|
+
const storyId = ctx.currentStoryId;
|
|
231
|
+
if (!storyId) {
|
|
232
|
+
return { passed: false, message: 'No story ID in context' };
|
|
233
|
+
}
|
|
234
|
+
const status = getStatus(rootDir);
|
|
235
|
+
const story = status.stories?.[storyId];
|
|
236
|
+
if (!story) {
|
|
237
|
+
return { passed: false, message: `Story ${storyId} not found` };
|
|
238
|
+
}
|
|
239
|
+
// Check if story has AC and if they're marked complete
|
|
240
|
+
const ac = story.acceptance_criteria || story.ac || [];
|
|
241
|
+
if (!Array.isArray(ac) || ac.length === 0) {
|
|
242
|
+
return { passed: true, message: 'No AC defined (assuming complete)' };
|
|
243
|
+
}
|
|
244
|
+
// Check for ac_status field or assume AC are verified if tests pass
|
|
245
|
+
const acStatus = story.ac_status || {};
|
|
246
|
+
const allVerified = ac.every((_, i) => acStatus[i] === 'verified' || acStatus[i] === true);
|
|
247
|
+
return {
|
|
248
|
+
passed: allVerified,
|
|
249
|
+
message: allVerified
|
|
250
|
+
? 'All AC verified'
|
|
251
|
+
: `${Object.values(acStatus).filter(v => v === 'verified' || v === true).length}/${ac.length} AC verified`,
|
|
252
|
+
};
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Parse discretion condition from string
|
|
258
|
+
* @param {string} condition - e.g., "**all tests passing**" or "**coverage above 80%**"
|
|
259
|
+
* @returns {object} { key, threshold? }
|
|
260
|
+
*/
|
|
261
|
+
function parseDiscretionCondition(condition) {
|
|
262
|
+
// Remove ** markers
|
|
263
|
+
const cleaned = condition.replace(/\*\*/g, '').trim().toLowerCase();
|
|
264
|
+
|
|
265
|
+
// Check for threshold patterns like "coverage above 80%"
|
|
266
|
+
const coverageMatch = cleaned.match(/coverage (?:above|>=?) (\d+)%?/);
|
|
267
|
+
if (coverageMatch) {
|
|
268
|
+
return { key: 'coverage above threshold', threshold: parseInt(coverageMatch[1]) };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return { key: cleaned };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Evaluate a discretion condition
|
|
276
|
+
* @param {string} condition - The condition string (with or without ** markers)
|
|
277
|
+
* @param {string} rootDir - Project root
|
|
278
|
+
* @param {object} ctx - Context (currentStoryId, coverageThreshold, etc.)
|
|
279
|
+
* @returns {object} { passed: boolean, message: string }
|
|
280
|
+
*/
|
|
281
|
+
function evaluateDiscretionCondition(condition, rootDir, ctx = {}) {
|
|
282
|
+
const parsed = parseDiscretionCondition(condition);
|
|
283
|
+
|
|
284
|
+
// Set threshold in context if parsed from condition
|
|
285
|
+
if (parsed.threshold) {
|
|
286
|
+
ctx.coverageThreshold = parsed.threshold;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const evaluator = DISCRETION_CONDITIONS[parsed.key];
|
|
290
|
+
if (!evaluator) {
|
|
291
|
+
return {
|
|
292
|
+
passed: false,
|
|
293
|
+
message: `Unknown condition: "${parsed.key}". Available: ${Object.keys(DISCRETION_CONDITIONS).join(', ')}`,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return evaluator(rootDir, ctx);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Get discretion conditions from metadata
|
|
302
|
+
* @param {string} rootDir
|
|
303
|
+
* @returns {string[]} Array of condition strings
|
|
304
|
+
*/
|
|
305
|
+
function getDiscretionConditions(rootDir) {
|
|
306
|
+
const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
|
|
307
|
+
const result = safeReadJSON(metadataPath, { defaultValue: {} });
|
|
308
|
+
|
|
309
|
+
if (result.ok && result.data?.ralph_loop?.conditions) {
|
|
310
|
+
return result.data.ralph_loop.conditions;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return [];
|
|
314
|
+
}
|
|
315
|
+
|
|
132
316
|
// Parse coverage report (Jest/NYC format)
|
|
133
317
|
function parseCoverageReport(rootDir) {
|
|
134
318
|
const reportPath = getCoverageReportPath(rootDir);
|
|
@@ -351,8 +535,10 @@ function handleLoop(rootDir) {
|
|
|
351
535
|
const visualMode = loop.visual_mode || false;
|
|
352
536
|
const coverageMode = loop.coverage_mode || false;
|
|
353
537
|
const coverageThreshold = loop.coverage_threshold || 80;
|
|
354
|
-
|
|
355
|
-
|
|
538
|
+
const discretionConditions = loop.conditions || getDiscretionConditions(rootDir);
|
|
539
|
+
// Visual, Coverage, and Discretion modes require at least 2 iterations for confirmation
|
|
540
|
+
const hasDiscretionConditions = discretionConditions.length > 0;
|
|
541
|
+
const minIterations = visualMode || coverageMode || hasDiscretionConditions ? 2 : 1;
|
|
356
542
|
|
|
357
543
|
console.log('');
|
|
358
544
|
console.log(
|
|
@@ -361,6 +547,7 @@ function handleLoop(rootDir) {
|
|
|
361
547
|
let modeLabel = '';
|
|
362
548
|
if (visualMode) modeLabel += ' [VISUAL]';
|
|
363
549
|
if (coverageMode) modeLabel += ` [COVERAGE ≥${coverageThreshold}%]`;
|
|
550
|
+
if (hasDiscretionConditions) modeLabel += ` [${discretionConditions.length} CONDITIONS]`;
|
|
364
551
|
console.log(
|
|
365
552
|
`${c.brand}${c.bold} RALPH LOOP - Iteration ${iteration}/${maxIterations}${modeLabel}${c.reset}`
|
|
366
553
|
);
|
|
@@ -368,6 +555,8 @@ function handleLoop(rootDir) {
|
|
|
368
555
|
`${c.brand}${c.bold}══════════════════════════════════════════════════════════${c.reset}`
|
|
369
556
|
);
|
|
370
557
|
console.log('');
|
|
558
|
+
// State Narration: Loop iteration marker
|
|
559
|
+
console.log(`🔄 Iteration ${iteration}/${maxIterations}`);
|
|
371
560
|
|
|
372
561
|
// Check iteration limit
|
|
373
562
|
if (iteration > maxIterations) {
|
|
@@ -392,9 +581,8 @@ function handleLoop(rootDir) {
|
|
|
392
581
|
return;
|
|
393
582
|
}
|
|
394
583
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
);
|
|
584
|
+
// State Narration: Current position marker
|
|
585
|
+
console.log(`📍 Working on: ${currentStoryId} - ${currentStory.title || 'Untitled'}`);
|
|
398
586
|
console.log('');
|
|
399
587
|
|
|
400
588
|
// Run tests
|
|
@@ -475,11 +663,38 @@ function handleLoop(rootDir) {
|
|
|
475
663
|
return;
|
|
476
664
|
}
|
|
477
665
|
|
|
666
|
+
// Evaluate discretion conditions
|
|
667
|
+
let discretionResults = [];
|
|
668
|
+
if (hasDiscretionConditions) {
|
|
669
|
+
console.log('');
|
|
670
|
+
console.log(`${c.blue}Evaluating discretion conditions...${c.reset}`);
|
|
671
|
+
const ctx = { currentStoryId, coverageThreshold };
|
|
672
|
+
|
|
673
|
+
for (const condition of discretionConditions) {
|
|
674
|
+
const result = evaluateDiscretionCondition(condition, rootDir, ctx);
|
|
675
|
+
discretionResults.push({ condition, ...result });
|
|
676
|
+
const marker = result.passed ? `${c.green}✓` : `${c.yellow}⏳`;
|
|
677
|
+
console.log(` ${marker} **${condition.replace(/\*\*/g, '')}**: ${result.message}${c.reset}`);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Track which conditions have been verified
|
|
681
|
+
const allConditionsPassed = discretionResults.every(r => r.passed);
|
|
682
|
+
state.ralph_loop.conditions_verified = allConditionsPassed;
|
|
683
|
+
state.ralph_loop.condition_results = discretionResults.map(r => ({
|
|
684
|
+
condition: r.condition,
|
|
685
|
+
passed: r.passed,
|
|
686
|
+
message: r.message,
|
|
687
|
+
}));
|
|
688
|
+
}
|
|
689
|
+
|
|
478
690
|
// Check if all verification modes passed
|
|
691
|
+
const allDiscretionPassed =
|
|
692
|
+
!hasDiscretionConditions || discretionResults.every(r => r.passed);
|
|
479
693
|
const canComplete =
|
|
480
694
|
testResult.passed &&
|
|
481
695
|
(!visualMode || screenshotResult.passed) &&
|
|
482
|
-
(!coverageMode || coverageResult.passed)
|
|
696
|
+
(!coverageMode || coverageResult.passed) &&
|
|
697
|
+
allDiscretionPassed;
|
|
483
698
|
|
|
484
699
|
if (!canComplete) {
|
|
485
700
|
// Something not verified yet
|
|
@@ -499,12 +714,21 @@ function handleLoop(rootDir) {
|
|
|
499
714
|
console.log(`${c.dim} Target: ${coverageThreshold}%${c.reset}`);
|
|
500
715
|
console.log(`${c.dim} Write more tests to cover uncovered code paths.${c.reset}`);
|
|
501
716
|
}
|
|
717
|
+
if (hasDiscretionConditions && !allDiscretionPassed) {
|
|
718
|
+
const failedConditions = discretionResults.filter(r => !r.passed);
|
|
719
|
+
console.log(`${c.cyan}▶ Fix failing conditions:${c.reset}`);
|
|
720
|
+
for (const fc of failedConditions) {
|
|
721
|
+
console.log(`${c.dim} - ${fc.condition.replace(/\*\*/g, '')}: ${fc.message}${c.reset}`);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
502
724
|
return;
|
|
503
725
|
}
|
|
504
726
|
console.log('');
|
|
505
727
|
|
|
506
728
|
// Mark story complete
|
|
507
729
|
markStoryComplete(rootDir, currentStoryId);
|
|
730
|
+
// State Narration: Completion marker
|
|
731
|
+
console.log(`✅ Story complete: ${currentStoryId}`);
|
|
508
732
|
console.log(`${c.green}✓ Marked ${currentStoryId} as completed${c.reset}`);
|
|
509
733
|
|
|
510
734
|
// Get next story
|
|
@@ -558,6 +782,8 @@ function handleLoop(rootDir) {
|
|
|
558
782
|
}
|
|
559
783
|
} else {
|
|
560
784
|
// Tests failed - feed back to Claude
|
|
785
|
+
// State Narration: Error marker
|
|
786
|
+
console.log(`⚠️ Error: Test failure - ${(testResult.duration / 1000).toFixed(1)}s`);
|
|
561
787
|
console.log(`${c.red}✗ Tests failed${c.reset} (${(testResult.duration / 1000).toFixed(1)}s)`);
|
|
562
788
|
console.log('');
|
|
563
789
|
|
|
@@ -597,6 +823,8 @@ function handleCLI() {
|
|
|
597
823
|
if (loop.visual_mode) modeLabel += ` ${c.cyan}[VISUAL]${c.reset}`;
|
|
598
824
|
if (loop.coverage_mode)
|
|
599
825
|
modeLabel += ` ${c.magenta}[COVERAGE ≥${loop.coverage_threshold}%]${c.reset}`;
|
|
826
|
+
if (loop.conditions?.length > 0)
|
|
827
|
+
modeLabel += ` ${c.blue}[${loop.conditions.length} CONDITIONS]${c.reset}`;
|
|
600
828
|
console.log(`${c.green}Ralph Loop: active${c.reset}${modeLabel}`);
|
|
601
829
|
console.log(` Epic: ${loop.epic}`);
|
|
602
830
|
console.log(` Current Story: ${loop.current_story}`);
|
|
@@ -616,6 +844,16 @@ function handleCLI() {
|
|
|
616
844
|
);
|
|
617
845
|
console.log(` Baseline: ${(loop.coverage_baseline || 0).toFixed(1)}%`);
|
|
618
846
|
}
|
|
847
|
+
if (loop.conditions?.length > 0) {
|
|
848
|
+
const verified = loop.conditions_verified
|
|
849
|
+
? `${c.green}yes${c.reset}`
|
|
850
|
+
: `${c.yellow}no${c.reset}`;
|
|
851
|
+
console.log(` Discretion Conditions: ${loop.conditions.length} (All Verified: ${verified})`);
|
|
852
|
+
for (const result of loop.condition_results || []) {
|
|
853
|
+
const mark = result.passed ? `${c.green}✓${c.reset}` : `${c.yellow}⏳${c.reset}`;
|
|
854
|
+
console.log(` ${mark} ${result.condition.replace(/\*\*/g, '')}`);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
619
857
|
}
|
|
620
858
|
return true;
|
|
621
859
|
}
|
|
@@ -647,6 +885,9 @@ function handleCLI() {
|
|
|
647
885
|
const maxArg = args.find(a => a.startsWith('--max='));
|
|
648
886
|
const visualArg = args.includes('--visual') || args.includes('-v');
|
|
649
887
|
const coverageArg = args.find(a => a.startsWith('--coverage='));
|
|
888
|
+
// Parse conditions (--condition="**all tests passing**" or -c "...")
|
|
889
|
+
const conditionArgs = args.filter(a => a.startsWith('--condition=') || a.startsWith('-c='));
|
|
890
|
+
const conditions = conditionArgs.map(a => a.split('=').slice(1).join('=').replace(/"/g, ''));
|
|
650
891
|
|
|
651
892
|
if (!epicArg) {
|
|
652
893
|
console.log(`${c.red}Error: --epic=EP-XXXX is required${c.reset}`);
|
|
@@ -711,6 +952,10 @@ function handleCLI() {
|
|
|
711
952
|
}
|
|
712
953
|
}
|
|
713
954
|
|
|
955
|
+
// Get conditions from metadata if not provided via CLI
|
|
956
|
+
const allConditions =
|
|
957
|
+
conditions.length > 0 ? conditions : getDiscretionConditions(rootDir);
|
|
958
|
+
|
|
714
959
|
// Initialize loop state
|
|
715
960
|
const state = getSessionState(rootDir);
|
|
716
961
|
state.ralph_loop = {
|
|
@@ -726,6 +971,9 @@ function handleCLI() {
|
|
|
726
971
|
coverage_baseline: coverageBaseline,
|
|
727
972
|
coverage_current: coverageBaseline,
|
|
728
973
|
coverage_verified: false,
|
|
974
|
+
conditions: allConditions,
|
|
975
|
+
conditions_verified: false,
|
|
976
|
+
condition_results: [],
|
|
729
977
|
started_at: new Date().toISOString(),
|
|
730
978
|
};
|
|
731
979
|
saveSessionState(rootDir, state);
|
|
@@ -750,7 +998,13 @@ function handleCLI() {
|
|
|
750
998
|
);
|
|
751
999
|
console.log(` Baseline: ${coverageBaseline.toFixed(1)}%`);
|
|
752
1000
|
}
|
|
753
|
-
if (
|
|
1001
|
+
if (allConditions.length > 0) {
|
|
1002
|
+
console.log(` Conditions: ${c.blue}${allConditions.length} discretion conditions${c.reset}`);
|
|
1003
|
+
for (const cond of allConditions) {
|
|
1004
|
+
console.log(` - **${cond.replace(/\*\*/g, '')}**`);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
if (visualMode || coverageMode || allConditions.length > 0) {
|
|
754
1008
|
console.log(` Min Iterations: 2 (for confirmation)`);
|
|
755
1009
|
}
|
|
756
1010
|
console.log(`${c.dim}${'─'.repeat(40)}${c.reset}`);
|
|
@@ -783,15 +1037,17 @@ ${c.bold}Usage:${c.reset}
|
|
|
783
1037
|
node scripts/ralph-loop.js --init --epic=EP-XXX Initialize loop for epic
|
|
784
1038
|
node scripts/ralph-loop.js --init --epic=EP-XXX --visual Initialize with Visual Mode
|
|
785
1039
|
node scripts/ralph-loop.js --init --epic=EP-XXX --coverage=80 Initialize with Coverage Mode
|
|
1040
|
+
node scripts/ralph-loop.js --init --epic=EP-XXX --condition="**all tests passing**"
|
|
786
1041
|
node scripts/ralph-loop.js --status Check loop status
|
|
787
1042
|
node scripts/ralph-loop.js --stop Stop the loop
|
|
788
1043
|
node scripts/ralph-loop.js --reset Reset loop state
|
|
789
1044
|
|
|
790
1045
|
${c.bold}Options:${c.reset}
|
|
791
|
-
--epic=EP-XXXX
|
|
792
|
-
--max=N
|
|
793
|
-
--visual, -v
|
|
794
|
-
--coverage=N
|
|
1046
|
+
--epic=EP-XXXX Epic ID to process (required for --init)
|
|
1047
|
+
--max=N Max iterations (default: 20)
|
|
1048
|
+
--visual, -v Enable Visual Mode (screenshot verification)
|
|
1049
|
+
--coverage=N Enable Coverage Mode (iterate until N% coverage)
|
|
1050
|
+
--condition="..." Add discretion condition (can use multiple times)
|
|
795
1051
|
|
|
796
1052
|
${c.bold}Visual Mode:${c.reset}
|
|
797
1053
|
When --visual is enabled, the loop also verifies that all screenshots
|
|
@@ -814,6 +1070,30 @@ ${c.bold}Coverage Mode:${c.reset}
|
|
|
814
1070
|
3. Minimum 2 iterations → confirms coverage is stable
|
|
815
1071
|
4. Only then → story marked complete
|
|
816
1072
|
|
|
1073
|
+
${c.bold}Discretion Conditions:${c.reset}
|
|
1074
|
+
Semantic conditions that must pass before story completion.
|
|
1075
|
+
Use --condition multiple times for multiple conditions.
|
|
1076
|
+
|
|
1077
|
+
Built-in conditions:
|
|
1078
|
+
**all tests passing** Tests must pass
|
|
1079
|
+
**tests pass** Tests must pass (alias)
|
|
1080
|
+
**coverage above 80%** Coverage must meet threshold
|
|
1081
|
+
**no linting errors** npm run lint must pass
|
|
1082
|
+
**no type errors** npx tsc --noEmit must pass
|
|
1083
|
+
**build succeeds** npm run build must pass
|
|
1084
|
+
**all screenshots verified** Screenshots need verified- prefix
|
|
1085
|
+
**all acceptance criteria verified** AC marked complete in status.json
|
|
1086
|
+
|
|
1087
|
+
Configure in docs/00-meta/agileflow-metadata.json:
|
|
1088
|
+
{
|
|
1089
|
+
"ralph_loop": {
|
|
1090
|
+
"conditions": [
|
|
1091
|
+
"**all tests passing**",
|
|
1092
|
+
"**no linting errors**"
|
|
1093
|
+
]
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
817
1097
|
${c.bold}How it works:${c.reset}
|
|
818
1098
|
1. Start loop with /agileflow:babysit EPIC=EP-XXX MODE=loop COVERAGE=80
|
|
819
1099
|
2. Work on the current story
|
|
@@ -48,6 +48,22 @@ RULE #3: DEPENDENCY DETECTION
|
|
|
48
48
|
| Same domain, different experts | PARALLEL | Security + Performance analyzing same code |
|
|
49
49
|
| Best-of-N comparison | PARALLEL | Expert1 vs Expert2 vs Expert3 approaches |
|
|
50
50
|
|
|
51
|
+
RULE #3b: JOIN STRATEGIES (for parallel deployment)
|
|
52
|
+
| Strategy | When | Behavior |
|
|
53
|
+
|----------|------|----------|
|
|
54
|
+
| `all` | Full implementation | Wait for all, fail if any fails |
|
|
55
|
+
| `first` | Racing approaches | Take first completion |
|
|
56
|
+
| `any` | Fallback patterns | Take first success |
|
|
57
|
+
| `any-N` | Multiple perspectives | Take first N successes |
|
|
58
|
+
| `majority` | High-stakes decisions | Take consensus (2+ agree) |
|
|
59
|
+
|
|
60
|
+
RULE #3c: FAILURE POLICIES
|
|
61
|
+
| Policy | When | Behavior |
|
|
62
|
+
|--------|------|----------|
|
|
63
|
+
| `fail-fast` | Critical work (default) | Stop on first failure |
|
|
64
|
+
| `continue` | Analysis/review | Run all, report failures |
|
|
65
|
+
| `ignore` | Optional enrichments | Skip failures silently |
|
|
66
|
+
|
|
51
67
|
RULE #4: SYNTHESIS REQUIREMENTS
|
|
52
68
|
- NEVER give final answer without all expert results
|
|
53
69
|
- Flag conflicts explicitly: "Expert A recommends X (rationale: ...), Expert B recommends Y (rationale: ...)"
|
|
@@ -238,11 +254,114 @@ TaskOutput(task_id: "<ui_expert_id>", block: true)
|
|
|
238
254
|
|
|
239
255
|
---
|
|
240
256
|
|
|
257
|
+
## JOIN STRATEGIES
|
|
258
|
+
|
|
259
|
+
When spawning parallel experts, specify how to handle results:
|
|
260
|
+
|
|
261
|
+
| Strategy | Behavior | Use Case |
|
|
262
|
+
|----------|----------|----------|
|
|
263
|
+
| `all` | Wait for all, fail if any fails | Full feature implementation |
|
|
264
|
+
| `first` | Take first result, cancel others | Racing alternative approaches |
|
|
265
|
+
| `any` | Take first success, ignore failures | Fallback patterns |
|
|
266
|
+
| `any-N` | Take first N successes | Get multiple perspectives |
|
|
267
|
+
| `majority` | Take consensus result | High-stakes decisions |
|
|
268
|
+
|
|
269
|
+
### Failure Policies
|
|
270
|
+
|
|
271
|
+
Combine with strategies to handle errors gracefully:
|
|
272
|
+
|
|
273
|
+
| Policy | Behavior | Use Case |
|
|
274
|
+
|--------|----------|----------|
|
|
275
|
+
| `fail-fast` | Stop all on first failure (default) | Critical operations |
|
|
276
|
+
| `continue` | Run all to completion, report failures | Comprehensive analysis |
|
|
277
|
+
| `ignore` | Skip failed branches silently | Optional enrichments |
|
|
278
|
+
|
|
279
|
+
**Usage:**
|
|
280
|
+
```
|
|
281
|
+
Deploy parallel (strategy: all, on-fail: continue):
|
|
282
|
+
- agileflow-security (may fail if no vulnerabilities)
|
|
283
|
+
- agileflow-performance (may fail if no issues)
|
|
284
|
+
- agileflow-testing
|
|
285
|
+
|
|
286
|
+
Run all to completion. Report any failures at end.
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**When to use each policy:**
|
|
290
|
+
|
|
291
|
+
| Scenario | Recommended Policy |
|
|
292
|
+
|----------|-------------------|
|
|
293
|
+
| Implementation work | `fail-fast` (need all parts) |
|
|
294
|
+
| Code review/analysis | `continue` (want all perspectives) |
|
|
295
|
+
| Optional enrichments | `ignore` (nice-to-have) |
|
|
296
|
+
|
|
297
|
+
### Strategy: all (Default)
|
|
298
|
+
|
|
299
|
+
Wait for all experts to complete. Report all results in synthesis.
|
|
300
|
+
|
|
301
|
+
```
|
|
302
|
+
Deploy parallel (strategy: all):
|
|
303
|
+
- agileflow-api (endpoint)
|
|
304
|
+
- agileflow-ui (component)
|
|
305
|
+
|
|
306
|
+
Collect ALL results before synthesizing.
|
|
307
|
+
If ANY expert fails → report failure with details.
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Strategy: first
|
|
311
|
+
|
|
312
|
+
Take the first expert that completes. Useful for racing approaches.
|
|
313
|
+
|
|
314
|
+
```
|
|
315
|
+
Deploy parallel (strategy: first):
|
|
316
|
+
- Expert A (approach: caching)
|
|
317
|
+
- Expert B (approach: pagination)
|
|
318
|
+
- Expert C (approach: batching)
|
|
319
|
+
|
|
320
|
+
First to complete wins → use that approach.
|
|
321
|
+
Cancel/ignore other results.
|
|
322
|
+
|
|
323
|
+
Use case: Finding ANY working solution when multiple approaches are valid.
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Strategy: any
|
|
327
|
+
|
|
328
|
+
Take first successful result. Ignore failures. Useful for fallbacks.
|
|
329
|
+
|
|
330
|
+
```
|
|
331
|
+
Deploy parallel (strategy: any):
|
|
332
|
+
- Expert A (primary approach)
|
|
333
|
+
- Expert B (fallback approach)
|
|
334
|
+
|
|
335
|
+
First SUCCESS wins → use that result.
|
|
336
|
+
If A fails but B succeeds → use B.
|
|
337
|
+
If all fail → report all failures.
|
|
338
|
+
|
|
339
|
+
Use case: Resilient operations where any working solution is acceptable.
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Strategy: majority
|
|
343
|
+
|
|
344
|
+
Multiple experts analyze same thing. Take consensus.
|
|
345
|
+
|
|
346
|
+
```
|
|
347
|
+
Deploy parallel (strategy: majority):
|
|
348
|
+
- Security Expert 1
|
|
349
|
+
- Security Expert 2
|
|
350
|
+
- Security Expert 3
|
|
351
|
+
|
|
352
|
+
If 2+ agree → use consensus recommendation.
|
|
353
|
+
If no consensus → report conflict, request decision.
|
|
354
|
+
|
|
355
|
+
Use case: High-stakes security reviews, architecture decisions.
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
241
360
|
## PARALLEL PATTERNS
|
|
242
361
|
|
|
243
362
|
### Full-Stack Feature
|
|
244
363
|
```
|
|
245
|
-
Parallel:
|
|
364
|
+
Parallel (strategy: all):
|
|
246
365
|
- agileflow-api (endpoint)
|
|
247
366
|
- agileflow-ui (component)
|
|
248
367
|
Then:
|
|
@@ -251,22 +370,32 @@ Then:
|
|
|
251
370
|
|
|
252
371
|
### Code Review/Analysis
|
|
253
372
|
```
|
|
254
|
-
Parallel (
|
|
373
|
+
Parallel (strategy: all):
|
|
255
374
|
- agileflow-security
|
|
256
375
|
- agileflow-performance
|
|
257
376
|
- agileflow-testing
|
|
258
377
|
Then:
|
|
259
|
-
- Synthesize findings
|
|
378
|
+
- Synthesize all findings
|
|
260
379
|
```
|
|
261
380
|
|
|
262
|
-
### Best-of-N
|
|
381
|
+
### Best-of-N (Racing)
|
|
263
382
|
```
|
|
264
|
-
Parallel (
|
|
383
|
+
Parallel (strategy: first):
|
|
265
384
|
- Expert A (approach 1)
|
|
266
385
|
- Expert B (approach 2)
|
|
267
386
|
- Expert C (approach 3)
|
|
268
387
|
Then:
|
|
269
|
-
-
|
|
388
|
+
- Use first completion
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Consensus Decision
|
|
392
|
+
```
|
|
393
|
+
Parallel (strategy: majority):
|
|
394
|
+
- Security Expert 1
|
|
395
|
+
- Security Expert 2
|
|
396
|
+
- Security Expert 3
|
|
397
|
+
Then:
|
|
398
|
+
- Take consensus recommendation
|
|
270
399
|
```
|
|
271
400
|
|
|
272
401
|
---
|