agileflow 2.81.0 → 2.82.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/README.md +3 -3
- package/lib/colors.js +190 -0
- package/lib/errors.js +279 -0
- package/lib/paths.js +99 -0
- package/lib/validate.js +337 -0
- package/package.json +2 -1
- package/scripts/ralph-loop.js +293 -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/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,39 @@ 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(
|
|
678
|
+
` ${marker} **${condition.replace(/\*\*/g, '')}**: ${result.message}${c.reset}`
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Track which conditions have been verified
|
|
683
|
+
const allConditionsPassed = discretionResults.every(r => r.passed);
|
|
684
|
+
state.ralph_loop.conditions_verified = allConditionsPassed;
|
|
685
|
+
state.ralph_loop.condition_results = discretionResults.map(r => ({
|
|
686
|
+
condition: r.condition,
|
|
687
|
+
passed: r.passed,
|
|
688
|
+
message: r.message,
|
|
689
|
+
}));
|
|
690
|
+
}
|
|
691
|
+
|
|
478
692
|
// Check if all verification modes passed
|
|
693
|
+
const allDiscretionPassed = !hasDiscretionConditions || discretionResults.every(r => r.passed);
|
|
479
694
|
const canComplete =
|
|
480
695
|
testResult.passed &&
|
|
481
696
|
(!visualMode || screenshotResult.passed) &&
|
|
482
|
-
(!coverageMode || coverageResult.passed)
|
|
697
|
+
(!coverageMode || coverageResult.passed) &&
|
|
698
|
+
allDiscretionPassed;
|
|
483
699
|
|
|
484
700
|
if (!canComplete) {
|
|
485
701
|
// Something not verified yet
|
|
@@ -499,12 +715,21 @@ function handleLoop(rootDir) {
|
|
|
499
715
|
console.log(`${c.dim} Target: ${coverageThreshold}%${c.reset}`);
|
|
500
716
|
console.log(`${c.dim} Write more tests to cover uncovered code paths.${c.reset}`);
|
|
501
717
|
}
|
|
718
|
+
if (hasDiscretionConditions && !allDiscretionPassed) {
|
|
719
|
+
const failedConditions = discretionResults.filter(r => !r.passed);
|
|
720
|
+
console.log(`${c.cyan}▶ Fix failing conditions:${c.reset}`);
|
|
721
|
+
for (const fc of failedConditions) {
|
|
722
|
+
console.log(`${c.dim} - ${fc.condition.replace(/\*\*/g, '')}: ${fc.message}${c.reset}`);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
502
725
|
return;
|
|
503
726
|
}
|
|
504
727
|
console.log('');
|
|
505
728
|
|
|
506
729
|
// Mark story complete
|
|
507
730
|
markStoryComplete(rootDir, currentStoryId);
|
|
731
|
+
// State Narration: Completion marker
|
|
732
|
+
console.log(`✅ Story complete: ${currentStoryId}`);
|
|
508
733
|
console.log(`${c.green}✓ Marked ${currentStoryId} as completed${c.reset}`);
|
|
509
734
|
|
|
510
735
|
// Get next story
|
|
@@ -558,6 +783,8 @@ function handleLoop(rootDir) {
|
|
|
558
783
|
}
|
|
559
784
|
} else {
|
|
560
785
|
// Tests failed - feed back to Claude
|
|
786
|
+
// State Narration: Error marker
|
|
787
|
+
console.log(`⚠️ Error: Test failure - ${(testResult.duration / 1000).toFixed(1)}s`);
|
|
561
788
|
console.log(`${c.red}✗ Tests failed${c.reset} (${(testResult.duration / 1000).toFixed(1)}s)`);
|
|
562
789
|
console.log('');
|
|
563
790
|
|
|
@@ -597,6 +824,8 @@ function handleCLI() {
|
|
|
597
824
|
if (loop.visual_mode) modeLabel += ` ${c.cyan}[VISUAL]${c.reset}`;
|
|
598
825
|
if (loop.coverage_mode)
|
|
599
826
|
modeLabel += ` ${c.magenta}[COVERAGE ≥${loop.coverage_threshold}%]${c.reset}`;
|
|
827
|
+
if (loop.conditions?.length > 0)
|
|
828
|
+
modeLabel += ` ${c.blue}[${loop.conditions.length} CONDITIONS]${c.reset}`;
|
|
600
829
|
console.log(`${c.green}Ralph Loop: active${c.reset}${modeLabel}`);
|
|
601
830
|
console.log(` Epic: ${loop.epic}`);
|
|
602
831
|
console.log(` Current Story: ${loop.current_story}`);
|
|
@@ -616,6 +845,18 @@ function handleCLI() {
|
|
|
616
845
|
);
|
|
617
846
|
console.log(` Baseline: ${(loop.coverage_baseline || 0).toFixed(1)}%`);
|
|
618
847
|
}
|
|
848
|
+
if (loop.conditions?.length > 0) {
|
|
849
|
+
const verified = loop.conditions_verified
|
|
850
|
+
? `${c.green}yes${c.reset}`
|
|
851
|
+
: `${c.yellow}no${c.reset}`;
|
|
852
|
+
console.log(
|
|
853
|
+
` Discretion Conditions: ${loop.conditions.length} (All Verified: ${verified})`
|
|
854
|
+
);
|
|
855
|
+
for (const result of loop.condition_results || []) {
|
|
856
|
+
const mark = result.passed ? `${c.green}✓${c.reset}` : `${c.yellow}⏳${c.reset}`;
|
|
857
|
+
console.log(` ${mark} ${result.condition.replace(/\*\*/g, '')}`);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
619
860
|
}
|
|
620
861
|
return true;
|
|
621
862
|
}
|
|
@@ -647,6 +888,9 @@ function handleCLI() {
|
|
|
647
888
|
const maxArg = args.find(a => a.startsWith('--max='));
|
|
648
889
|
const visualArg = args.includes('--visual') || args.includes('-v');
|
|
649
890
|
const coverageArg = args.find(a => a.startsWith('--coverage='));
|
|
891
|
+
// Parse conditions (--condition="**all tests passing**" or -c "...")
|
|
892
|
+
const conditionArgs = args.filter(a => a.startsWith('--condition=') || a.startsWith('-c='));
|
|
893
|
+
const conditions = conditionArgs.map(a => a.split('=').slice(1).join('=').replace(/"/g, ''));
|
|
650
894
|
|
|
651
895
|
if (!epicArg) {
|
|
652
896
|
console.log(`${c.red}Error: --epic=EP-XXXX is required${c.reset}`);
|
|
@@ -711,6 +955,9 @@ function handleCLI() {
|
|
|
711
955
|
}
|
|
712
956
|
}
|
|
713
957
|
|
|
958
|
+
// Get conditions from metadata if not provided via CLI
|
|
959
|
+
const allConditions = conditions.length > 0 ? conditions : getDiscretionConditions(rootDir);
|
|
960
|
+
|
|
714
961
|
// Initialize loop state
|
|
715
962
|
const state = getSessionState(rootDir);
|
|
716
963
|
state.ralph_loop = {
|
|
@@ -726,6 +973,9 @@ function handleCLI() {
|
|
|
726
973
|
coverage_baseline: coverageBaseline,
|
|
727
974
|
coverage_current: coverageBaseline,
|
|
728
975
|
coverage_verified: false,
|
|
976
|
+
conditions: allConditions,
|
|
977
|
+
conditions_verified: false,
|
|
978
|
+
condition_results: [],
|
|
729
979
|
started_at: new Date().toISOString(),
|
|
730
980
|
};
|
|
731
981
|
saveSessionState(rootDir, state);
|
|
@@ -750,7 +1000,13 @@ function handleCLI() {
|
|
|
750
1000
|
);
|
|
751
1001
|
console.log(` Baseline: ${coverageBaseline.toFixed(1)}%`);
|
|
752
1002
|
}
|
|
753
|
-
if (
|
|
1003
|
+
if (allConditions.length > 0) {
|
|
1004
|
+
console.log(` Conditions: ${c.blue}${allConditions.length} discretion conditions${c.reset}`);
|
|
1005
|
+
for (const cond of allConditions) {
|
|
1006
|
+
console.log(` - **${cond.replace(/\*\*/g, '')}**`);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
if (visualMode || coverageMode || allConditions.length > 0) {
|
|
754
1010
|
console.log(` Min Iterations: 2 (for confirmation)`);
|
|
755
1011
|
}
|
|
756
1012
|
console.log(`${c.dim}${'─'.repeat(40)}${c.reset}`);
|
|
@@ -783,15 +1039,17 @@ ${c.bold}Usage:${c.reset}
|
|
|
783
1039
|
node scripts/ralph-loop.js --init --epic=EP-XXX Initialize loop for epic
|
|
784
1040
|
node scripts/ralph-loop.js --init --epic=EP-XXX --visual Initialize with Visual Mode
|
|
785
1041
|
node scripts/ralph-loop.js --init --epic=EP-XXX --coverage=80 Initialize with Coverage Mode
|
|
1042
|
+
node scripts/ralph-loop.js --init --epic=EP-XXX --condition="**all tests passing**"
|
|
786
1043
|
node scripts/ralph-loop.js --status Check loop status
|
|
787
1044
|
node scripts/ralph-loop.js --stop Stop the loop
|
|
788
1045
|
node scripts/ralph-loop.js --reset Reset loop state
|
|
789
1046
|
|
|
790
1047
|
${c.bold}Options:${c.reset}
|
|
791
|
-
--epic=EP-XXXX
|
|
792
|
-
--max=N
|
|
793
|
-
--visual, -v
|
|
794
|
-
--coverage=N
|
|
1048
|
+
--epic=EP-XXXX Epic ID to process (required for --init)
|
|
1049
|
+
--max=N Max iterations (default: 20)
|
|
1050
|
+
--visual, -v Enable Visual Mode (screenshot verification)
|
|
1051
|
+
--coverage=N Enable Coverage Mode (iterate until N% coverage)
|
|
1052
|
+
--condition="..." Add discretion condition (can use multiple times)
|
|
795
1053
|
|
|
796
1054
|
${c.bold}Visual Mode:${c.reset}
|
|
797
1055
|
When --visual is enabled, the loop also verifies that all screenshots
|
|
@@ -814,6 +1072,30 @@ ${c.bold}Coverage Mode:${c.reset}
|
|
|
814
1072
|
3. Minimum 2 iterations → confirms coverage is stable
|
|
815
1073
|
4. Only then → story marked complete
|
|
816
1074
|
|
|
1075
|
+
${c.bold}Discretion Conditions:${c.reset}
|
|
1076
|
+
Semantic conditions that must pass before story completion.
|
|
1077
|
+
Use --condition multiple times for multiple conditions.
|
|
1078
|
+
|
|
1079
|
+
Built-in conditions:
|
|
1080
|
+
**all tests passing** Tests must pass
|
|
1081
|
+
**tests pass** Tests must pass (alias)
|
|
1082
|
+
**coverage above 80%** Coverage must meet threshold
|
|
1083
|
+
**no linting errors** npm run lint must pass
|
|
1084
|
+
**no type errors** npx tsc --noEmit must pass
|
|
1085
|
+
**build succeeds** npm run build must pass
|
|
1086
|
+
**all screenshots verified** Screenshots need verified- prefix
|
|
1087
|
+
**all acceptance criteria verified** AC marked complete in status.json
|
|
1088
|
+
|
|
1089
|
+
Configure in docs/00-meta/agileflow-metadata.json:
|
|
1090
|
+
{
|
|
1091
|
+
"ralph_loop": {
|
|
1092
|
+
"conditions": [
|
|
1093
|
+
"**all tests passing**",
|
|
1094
|
+
"**no linting errors**"
|
|
1095
|
+
]
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
817
1099
|
${c.bold}How it works:${c.reset}
|
|
818
1100
|
1. Start loop with /agileflow:babysit EPIC=EP-XXX MODE=loop COVERAGE=80
|
|
819
1101
|
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
|
---
|