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.
@@ -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
- // Visual and Coverage modes require at least 2 iterations for confirmation
355
- const minIterations = visualMode || coverageMode ? 2 : 1;
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
- console.log(
396
- `${c.cyan}Current Story:${c.reset} ${currentStoryId} - ${currentStory.title || 'Untitled'}`
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 (visualMode || coverageMode) {
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 Epic ID to process (required for --init)
792
- --max=N Max iterations (default: 20)
793
- --visual, -v Enable Visual Mode (screenshot verification)
794
- --coverage=N Enable Coverage Mode (iterate until N% coverage)
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 (analyze same code):
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 (same task, different approaches):
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
- - Compare and select best
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
  ---