agileflow 3.4.0 → 3.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +4 -4
  3. package/package.json +1 -1
  4. package/scripts/agileflow-welcome.js +79 -0
  5. package/scripts/claude-tmux.sh +12 -36
  6. package/scripts/lib/ac-test-matcher.js +452 -0
  7. package/scripts/lib/audit-registry.js +94 -2
  8. package/scripts/lib/configure-features.js +35 -0
  9. package/scripts/lib/model-profiles.js +25 -5
  10. package/scripts/lib/quality-gates.js +163 -0
  11. package/scripts/lib/signal-detectors.js +43 -0
  12. package/scripts/lib/status-writer.js +255 -0
  13. package/scripts/lib/story-claiming.js +128 -45
  14. package/scripts/lib/task-sync.js +32 -38
  15. package/scripts/lib/tmux-audit-monitor.js +611 -0
  16. package/scripts/lib/tmux-group-colors.js +2 -2
  17. package/scripts/lib/tool-registry.yaml +241 -0
  18. package/scripts/lib/tool-shed.js +441 -0
  19. package/scripts/native-team-observer.js +219 -0
  20. package/scripts/obtain-context.js +14 -0
  21. package/scripts/ralph-loop.js +30 -5
  22. package/scripts/smart-detect.js +21 -0
  23. package/scripts/spawn-audit-sessions.js +373 -45
  24. package/scripts/team-manager.js +19 -0
  25. package/src/core/agents/a11y-analyzer-aria.md +155 -0
  26. package/src/core/agents/a11y-analyzer-forms.md +162 -0
  27. package/src/core/agents/a11y-analyzer-keyboard.md +175 -0
  28. package/src/core/agents/a11y-analyzer-semantic.md +153 -0
  29. package/src/core/agents/a11y-analyzer-visual.md +158 -0
  30. package/src/core/agents/a11y-consensus.md +248 -0
  31. package/src/core/agents/ads-consensus.md +74 -0
  32. package/src/core/agents/ads-generate.md +145 -0
  33. package/src/core/agents/ads-performance-tracker.md +197 -0
  34. package/src/core/agents/api-quality-analyzer-conventions.md +148 -0
  35. package/src/core/agents/api-quality-analyzer-docs.md +176 -0
  36. package/src/core/agents/api-quality-analyzer-errors.md +183 -0
  37. package/src/core/agents/api-quality-analyzer-pagination.md +171 -0
  38. package/src/core/agents/api-quality-analyzer-versioning.md +143 -0
  39. package/src/core/agents/api-quality-consensus.md +214 -0
  40. package/src/core/agents/arch-analyzer-circular.md +148 -0
  41. package/src/core/agents/arch-analyzer-complexity.md +171 -0
  42. package/src/core/agents/arch-analyzer-coupling.md +146 -0
  43. package/src/core/agents/arch-analyzer-layering.md +151 -0
  44. package/src/core/agents/arch-analyzer-patterns.md +162 -0
  45. package/src/core/agents/arch-consensus.md +227 -0
  46. package/src/core/commands/adr.md +1 -0
  47. package/src/core/commands/ads/audit.md +67 -5
  48. package/src/core/commands/ads/generate.md +238 -0
  49. package/src/core/commands/ads/health.md +327 -0
  50. package/src/core/commands/ads/test-plan.md +317 -0
  51. package/src/core/commands/ads/track.md +288 -0
  52. package/src/core/commands/ads.md +28 -16
  53. package/src/core/commands/assign.md +1 -0
  54. package/src/core/commands/audit.md +43 -6
  55. package/src/core/commands/babysit.md +90 -6
  56. package/src/core/commands/baseline.md +1 -0
  57. package/src/core/commands/blockers.md +1 -0
  58. package/src/core/commands/board.md +1 -0
  59. package/src/core/commands/changelog.md +1 -0
  60. package/src/core/commands/choose.md +1 -0
  61. package/src/core/commands/ci.md +1 -0
  62. package/src/core/commands/code/accessibility.md +347 -0
  63. package/src/core/commands/code/api.md +297 -0
  64. package/src/core/commands/code/architecture.md +297 -0
  65. package/src/core/commands/code/completeness.md +43 -6
  66. package/src/core/commands/code/legal.md +43 -6
  67. package/src/core/commands/code/logic.md +43 -6
  68. package/src/core/commands/code/performance.md +43 -6
  69. package/src/core/commands/code/security.md +43 -6
  70. package/src/core/commands/code/test.md +43 -6
  71. package/src/core/commands/configure.md +1 -0
  72. package/src/core/commands/council.md +1 -0
  73. package/src/core/commands/deploy.md +1 -0
  74. package/src/core/commands/diagnose.md +1 -0
  75. package/src/core/commands/docs.md +1 -0
  76. package/src/core/commands/epic/edit.md +213 -0
  77. package/src/core/commands/epic.md +1 -0
  78. package/src/core/commands/export.md +238 -0
  79. package/src/core/commands/help.md +16 -1
  80. package/src/core/commands/ideate/discover.md +7 -3
  81. package/src/core/commands/ideate/features.md +65 -4
  82. package/src/core/commands/ideate/new.md +158 -124
  83. package/src/core/commands/impact.md +1 -0
  84. package/src/core/commands/learn/explain.md +118 -0
  85. package/src/core/commands/learn/glossary.md +135 -0
  86. package/src/core/commands/learn/patterns.md +138 -0
  87. package/src/core/commands/learn/tour.md +126 -0
  88. package/src/core/commands/migrate/codemods.md +151 -0
  89. package/src/core/commands/migrate/plan.md +131 -0
  90. package/src/core/commands/migrate/scan.md +114 -0
  91. package/src/core/commands/migrate/validate.md +119 -0
  92. package/src/core/commands/multi-expert.md +1 -0
  93. package/src/core/commands/pr.md +1 -0
  94. package/src/core/commands/review.md +1 -0
  95. package/src/core/commands/seo/audit.md +61 -6
  96. package/src/core/commands/sprint.md +1 -0
  97. package/src/core/commands/status/undo.md +191 -0
  98. package/src/core/commands/status.md +1 -0
  99. package/src/core/commands/story/edit.md +204 -0
  100. package/src/core/commands/story/view.md +29 -7
  101. package/src/core/commands/story-validate.md +1 -0
  102. package/src/core/commands/story.md +1 -0
  103. package/src/core/commands/tdd.md +1 -0
  104. package/src/core/commands/team/start.md +10 -6
  105. package/src/core/commands/tests.md +1 -0
  106. package/src/core/commands/verify.md +27 -1
  107. package/src/core/commands/workflow.md +2 -0
  108. package/src/core/teams/backend.json +41 -0
  109. package/src/core/teams/frontend.json +41 -0
  110. package/src/core/teams/qa.json +41 -0
  111. package/src/core/teams/solo.json +35 -0
  112. package/src/core/templates/agileflow-metadata.json +5 -0
  113. package/tools/cli/commands/setup.js +85 -3
  114. package/tools/cli/commands/update.js +42 -0
  115. package/tools/cli/installers/ide/claude-code.js +68 -0
@@ -132,6 +132,98 @@ const AUDIT_TYPES = {
132
132
  deep_analyzers: ['handlers', 'routes', 'api', 'stubs', 'state', 'imports', 'conditional'],
133
133
  },
134
134
 
135
+ brainstorm: {
136
+ name: 'Feature Brainstorm',
137
+ prefix: 'Brain',
138
+ color: '#c0caf5', // lavender
139
+ command: 'ideate/features',
140
+ analyzers: {
141
+ features: { subagent_type: 'brainstorm-analyzer-features', label: 'Feature Gaps' },
142
+ ux: { subagent_type: 'brainstorm-analyzer-ux', label: 'UX Improvements' },
143
+ market: { subagent_type: 'brainstorm-analyzer-market', label: 'Market Features' },
144
+ growth: { subagent_type: 'brainstorm-analyzer-growth', label: 'Growth & Engagement' },
145
+ integration: { subagent_type: 'brainstorm-analyzer-integration', label: 'Integrations' },
146
+ },
147
+ consensus: { subagent_type: 'brainstorm-consensus', label: 'Brainstorm Consensus' },
148
+ quick_analyzers: ['features', 'ux', 'market'],
149
+ deep_analyzers: ['features', 'ux', 'market', 'growth', 'integration'],
150
+ },
151
+
152
+ ideate: {
153
+ name: 'Ideation',
154
+ prefix: 'Idea',
155
+ color: '#ff9e64', // orange
156
+ command: 'ideate/new',
157
+ analyzers: {
158
+ security: { subagent_type: 'agileflow-security', label: 'Security' },
159
+ performance: { subagent_type: 'agileflow-performance', label: 'Performance' },
160
+ refactor: { subagent_type: 'agileflow-refactor', label: 'Code Quality' },
161
+ ui: { subagent_type: 'agileflow-ui', label: 'UX/Design' },
162
+ testing: { subagent_type: 'agileflow-testing', label: 'Testing' },
163
+ api: { subagent_type: 'agileflow-api', label: 'API/Architecture' },
164
+ accessibility: { subagent_type: 'agileflow-accessibility', label: 'Accessibility' },
165
+ compliance: { subagent_type: 'agileflow-compliance', label: 'Compliance' },
166
+ database: { subagent_type: 'agileflow-database', label: 'Database' },
167
+ monitoring: { subagent_type: 'agileflow-monitoring', label: 'Monitoring' },
168
+ qa: { subagent_type: 'agileflow-qa', label: 'QA' },
169
+ analytics: { subagent_type: 'agileflow-analytics', label: 'Analytics' },
170
+ documentation: { subagent_type: 'agileflow-documentation', label: 'Documentation' },
171
+ },
172
+ consensus: null, // ideation does its own synthesis (no consensus coordinator)
173
+ quick_analyzers: ['security', 'performance', 'refactor', 'ui', 'testing', 'api'],
174
+ deep_analyzers: [
175
+ 'security',
176
+ 'performance',
177
+ 'refactor',
178
+ 'ui',
179
+ 'testing',
180
+ 'api',
181
+ 'accessibility',
182
+ 'compliance',
183
+ 'database',
184
+ 'monitoring',
185
+ 'qa',
186
+ 'analytics',
187
+ 'documentation',
188
+ ],
189
+ },
190
+
191
+ seo: {
192
+ name: 'SEO Audit',
193
+ prefix: 'SEO',
194
+ color: '#ff9e64', // rose/orange
195
+ command: 'seo/audit',
196
+ analyzers: {
197
+ technical: { subagent_type: 'seo-analyzer-technical', label: 'Technical SEO' },
198
+ content: { subagent_type: 'seo-analyzer-content', label: 'Content Quality' },
199
+ schema: { subagent_type: 'seo-analyzer-schema', label: 'Schema Markup' },
200
+ images: { subagent_type: 'seo-analyzer-images', label: 'Image Optimization' },
201
+ performance: { subagent_type: 'seo-analyzer-performance', label: 'Core Web Vitals' },
202
+ sitemap: { subagent_type: 'seo-analyzer-sitemap', label: 'Sitemap' },
203
+ },
204
+ consensus: { subagent_type: 'seo-consensus', label: 'SEO Consensus' },
205
+ quick_analyzers: ['technical', 'content', 'schema', 'images', 'performance', 'sitemap'],
206
+ deep_analyzers: ['technical', 'content', 'schema', 'images', 'performance', 'sitemap'],
207
+ },
208
+
209
+ ads: {
210
+ name: 'Ads Audit',
211
+ prefix: 'Ads',
212
+ color: '#89ddff', // ice
213
+ command: 'ads/audit',
214
+ analyzers: {
215
+ google: { subagent_type: 'ads-audit-google', label: 'Google Ads' },
216
+ meta: { subagent_type: 'ads-audit-meta', label: 'Meta Ads' },
217
+ budget: { subagent_type: 'ads-audit-budget', label: 'Budget & Bidding' },
218
+ creative: { subagent_type: 'ads-audit-creative', label: 'Creative Quality' },
219
+ tracking: { subagent_type: 'ads-audit-tracking', label: 'Conversion Tracking' },
220
+ compliance: { subagent_type: 'ads-audit-compliance', label: 'Compliance' },
221
+ },
222
+ consensus: { subagent_type: 'ads-consensus', label: 'Ads Consensus' },
223
+ quick_analyzers: ['google', 'meta', 'budget', 'creative', 'tracking', 'compliance'],
224
+ deep_analyzers: ['google', 'meta', 'budget', 'creative', 'tracking', 'compliance'],
225
+ },
226
+
135
227
  legal: {
136
228
  name: 'Legal Risk',
137
229
  prefix: 'Legal',
@@ -167,7 +259,7 @@ const AUDIT_TYPES = {
167
259
  /**
168
260
  * Get audit type configuration.
169
261
  *
170
- * @param {string} type - Audit type key (logic, security, performance, test, completeness, legal)
262
+ * @param {string} type - Audit type key (logic, security, performance, test, completeness, legal, ideate)
171
263
  * @returns {object|null} Audit type config or null if invalid
172
264
  */
173
265
  function getAuditType(type) {
@@ -195,7 +287,7 @@ function getAnalyzersForAudit(type, depth, focus) {
195
287
  const audit = AUDIT_TYPES[type];
196
288
  if (!audit) return null;
197
289
 
198
- const effectiveDepth = depth === 'ultradeep' ? 'deep' : depth || 'quick';
290
+ const effectiveDepth = depth === 'ultradeep' || depth === 'extreme' ? 'deep' : depth || 'quick';
199
291
  const analyzerKeys = effectiveDepth === 'deep' ? audit.deep_analyzers : audit.quick_analyzers;
200
292
 
201
293
  // Filter by focus if specified
@@ -393,6 +393,25 @@ function enableFeature(feature, options = {}, version) {
393
393
  if (feature === 'agentteams') {
394
394
  settings.env = settings.env || {};
395
395
  settings.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = '1';
396
+
397
+ // Register PostToolUse hooks for native team observability
398
+ if (!settings.hooks) settings.hooks = {};
399
+ if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
400
+ const observerCmd = 'node $CLAUDE_PROJECT_DIR/.agileflow/scripts/native-team-observer.js';
401
+ for (const matcher of ['TeamCreate', 'SendMessage', 'ListTeams']) {
402
+ const exists = settings.hooks.PostToolUse.some(
403
+ h =>
404
+ h.matcher === matcher &&
405
+ h.hooks?.some(hk => hk.command && hk.command.includes('native-team-observer'))
406
+ );
407
+ if (!exists) {
408
+ settings.hooks.PostToolUse.push({
409
+ matcher,
410
+ hooks: [{ type: 'command', command: observerCmd, timeout: 5000 }],
411
+ });
412
+ }
413
+ }
414
+
396
415
  writeJSON('.claude/settings.json', settings);
397
416
  updateMetadata(
398
417
  {
@@ -408,6 +427,7 @@ function enableFeature(feature, options = {}, version) {
408
427
  );
409
428
  success('Native Agent Teams enabled');
410
429
  info('Set CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 in .claude/settings.json');
430
+ info('Registered PostToolUse hooks for native team observability');
411
431
  info('Claude Code will use native TeamCreate/SendMessage tools');
412
432
  info('Fallback: subagent mode (Task/TaskOutput) when native is unavailable');
413
433
  return true;
@@ -954,6 +974,20 @@ function disableFeature(feature, version) {
954
974
  delete settings.env;
955
975
  }
956
976
  }
977
+
978
+ // Remove PostToolUse hooks for native team observer
979
+ if (settings.hooks?.PostToolUse) {
980
+ settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter(
981
+ h => !h.hooks?.some(hk => hk.command && hk.command.includes('native-team-observer'))
982
+ );
983
+ if (settings.hooks.PostToolUse.length === 0) {
984
+ delete settings.hooks.PostToolUse;
985
+ }
986
+ if (Object.keys(settings.hooks).length === 0) {
987
+ delete settings.hooks;
988
+ }
989
+ }
990
+
957
991
  writeJSON('.claude/settings.json', settings);
958
992
  updateMetadata(
959
993
  {
@@ -969,6 +1003,7 @@ function disableFeature(feature, version) {
969
1003
  );
970
1004
  success('Native Agent Teams disabled');
971
1005
  info('Removed CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS from .claude/settings.json');
1006
+ info('Removed PostToolUse hooks for native team observer');
972
1007
  info('AgileFlow will use subagent mode (Task/TaskOutput) for multi-agent orchestration');
973
1008
  return true;
974
1009
  }
@@ -60,9 +60,10 @@ function isValidModel(model) {
60
60
  *
61
61
  * @param {string} model - Model name
62
62
  * @param {number} [analyzerCount=5] - Number of analyzers
63
- * @returns {{ multiplier: number, model: string, perAnalyzerCost: string }}
63
+ * @param {number} [partitions=1] - Number of partitions (extreme mode)
64
+ * @returns {{ multiplier: number, model: string, perAnalyzerCost: string, totalEstimate: string, partitions?: number, totalSessions?: number }}
64
65
  */
65
- function estimateCost(model, analyzerCount) {
66
+ function estimateCost(model, analyzerCount, partitions) {
66
67
  let MODEL_PRICING;
67
68
  try {
68
69
  MODEL_PRICING = require('./team-events').MODEL_PRICING;
@@ -75,19 +76,38 @@ function estimateCost(model, analyzerCount) {
75
76
  }
76
77
 
77
78
  const count = analyzerCount || 5;
79
+ const partCount = typeof partitions === 'number' && partitions > 1 ? partitions : 1;
78
80
  const resolved = resolveModel(model);
79
81
  const pricing = MODEL_PRICING[resolved] || MODEL_PRICING.haiku;
80
82
  const haikuPricing = MODEL_PRICING.haiku;
81
83
 
82
84
  const multiplier = pricing.output / haikuPricing.output;
83
- const perAnalyzer = `$${((pricing.input * 50000) / 1_000_000 + (pricing.output * 10000) / 1_000_000).toFixed(3)}`;
85
+ const perAnalyzerCostNum =
86
+ (pricing.input * 50000) / 1_000_000 + (pricing.output * 10000) / 1_000_000;
87
+ const perAnalyzer = `$${perAnalyzerCostNum.toFixed(3)}`;
84
88
 
85
- return {
89
+ // For extreme mode: each partition has a coordinator + all analyzers as sub-agents
90
+ // Estimated cost per partition coordinator session in USD (~10k input + 2k output at haiku rates)
91
+ const coordinatorCostUSD = 0.05;
92
+ const totalSessions = partCount * count;
93
+ const totalCost =
94
+ partCount > 1
95
+ ? partCount * coordinatorCostUSD + totalSessions * perAnalyzerCostNum
96
+ : count * perAnalyzerCostNum;
97
+
98
+ const result = {
86
99
  multiplier: Math.round(multiplier * 100) / 100,
87
100
  model: resolved,
88
101
  perAnalyzerCost: perAnalyzer,
89
- totalEstimate: `~$${(count * ((pricing.input * 50000) / 1_000_000 + (pricing.output * 10000) / 1_000_000)).toFixed(2)}`,
102
+ totalEstimate: `~$${totalCost.toFixed(2)}`,
90
103
  };
104
+
105
+ if (partCount > 1) {
106
+ result.partitions = partCount;
107
+ result.totalSessions = totalSessions;
108
+ }
109
+
110
+ return result;
91
111
  }
92
112
 
93
113
  module.exports = {
@@ -561,6 +561,164 @@ function createValidationReport(gateResults, options = {}) {
561
561
  return lines.join('\n');
562
562
  }
563
563
 
564
+ // ============================================================================
565
+ // CI Feedback Loop
566
+ // ============================================================================
567
+
568
+ /**
569
+ * Default CI feedback loop configuration
570
+ */
571
+ const CI_FEEDBACK_DEFAULTS = {
572
+ enabled: true,
573
+ max_rounds: 3,
574
+ };
575
+
576
+ /**
577
+ * Load CI feedback loop config from agileflow-metadata.json
578
+ * @param {string} projectRoot - Project root directory
579
+ * @returns {Object} CI feedback loop config
580
+ */
581
+ function loadCIFeedbackConfig(projectRoot) {
582
+ const metadataPath = path.join(projectRoot, 'docs', '00-meta', 'agileflow-metadata.json');
583
+ try {
584
+ if (fs.existsSync(metadataPath)) {
585
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
586
+ if (metadata.ci_feedback_loops) {
587
+ return {
588
+ ...CI_FEEDBACK_DEFAULTS,
589
+ ...metadata.ci_feedback_loops,
590
+ };
591
+ }
592
+ }
593
+ } catch {
594
+ // Fall through to defaults
595
+ }
596
+ return { ...CI_FEEDBACK_DEFAULTS };
597
+ }
598
+
599
+ /**
600
+ * Execute a CI feedback loop - runs gates, and if they fail, returns
601
+ * structured feedback for the agent to retry (up to max_rounds).
602
+ *
603
+ * This implements the Stripe "Minions" pattern: deterministic CI check
604
+ * followed by agent retry, with a hard iteration limit.
605
+ *
606
+ * @param {Object[]} gates - Quality gate definitions to check
607
+ * @param {Object} options - Loop options
608
+ * @param {string} [options.projectRoot] - Project root directory
609
+ * @param {number} [options.maxRounds] - Override max retry rounds (default: from config)
610
+ * @param {number} [options.currentRound] - Current round number (1-based, default: 1)
611
+ * @param {string} [options.cwd] - Working directory for gate execution
612
+ * @returns {Object} Loop result with status and agent feedback
613
+ */
614
+ function executeCIFeedbackLoop(gates, options = {}) {
615
+ const { projectRoot = process.cwd(), maxRounds, currentRound = 1, cwd } = options;
616
+
617
+ const config = loadCIFeedbackConfig(projectRoot);
618
+
619
+ if (!config.enabled) {
620
+ return {
621
+ status: 'disabled',
622
+ message: 'CI feedback loops are disabled in agileflow-metadata.json',
623
+ should_retry: false,
624
+ round: currentRound,
625
+ max_rounds: 0,
626
+ };
627
+ }
628
+
629
+ const effectiveMaxRounds = maxRounds || config.max_rounds || CI_FEEDBACK_DEFAULTS.max_rounds;
630
+
631
+ // Execute all gates
632
+ const gateResults = executeGates(gates, { cwd, stopOnFailure: false });
633
+
634
+ if (gateResults.passed) {
635
+ return {
636
+ status: 'passed',
637
+ message: `All ${gateResults.passed_count} gates passed on round ${currentRound}`,
638
+ should_retry: false,
639
+ round: currentRound,
640
+ max_rounds: effectiveMaxRounds,
641
+ gate_results: gateResults,
642
+ };
643
+ }
644
+
645
+ // Gates failed - determine if we should retry
646
+ const hasRoundsLeft = currentRound < effectiveMaxRounds;
647
+
648
+ if (!hasRoundsLeft) {
649
+ return {
650
+ status: 'exhausted',
651
+ message: `Gates failed after ${currentRound}/${effectiveMaxRounds} rounds. Escalating to human.`,
652
+ should_retry: false,
653
+ round: currentRound,
654
+ max_rounds: effectiveMaxRounds,
655
+ gate_results: gateResults,
656
+ failures: gateResults.results
657
+ .filter(r => r.status === GATE_STATUS.FAILED || r.status === GATE_STATUS.ERROR)
658
+ .map(r => ({
659
+ gate: r.gate,
660
+ message: r.message,
661
+ output: r.output,
662
+ error: r.error,
663
+ })),
664
+ };
665
+ }
666
+
667
+ // Build structured feedback for agent retry
668
+ const failures = gateResults.results.filter(
669
+ r => r.status === GATE_STATUS.FAILED || r.status === GATE_STATUS.ERROR
670
+ );
671
+
672
+ const feedbackLines = [
673
+ `## CI Feedback Loop - Round ${currentRound}/${effectiveMaxRounds}`,
674
+ '',
675
+ `**${failures.length} gate(s) failed.** ${effectiveMaxRounds - currentRound} retry round(s) remaining.`,
676
+ '',
677
+ '### Failures',
678
+ '',
679
+ ];
680
+
681
+ for (const failure of failures) {
682
+ feedbackLines.push(`#### ${failure.gate} (${failure.type})`);
683
+ feedbackLines.push(`- **Status**: ${failure.status}`);
684
+ feedbackLines.push(`- **Message**: ${failure.message}`);
685
+ if (failure.output) {
686
+ feedbackLines.push('- **Output**:');
687
+ feedbackLines.push('```');
688
+ feedbackLines.push(failure.output);
689
+ feedbackLines.push('```');
690
+ }
691
+ if (failure.error) {
692
+ feedbackLines.push('- **Error**:');
693
+ feedbackLines.push('```');
694
+ feedbackLines.push(failure.error);
695
+ feedbackLines.push('```');
696
+ }
697
+ feedbackLines.push('');
698
+ }
699
+
700
+ feedbackLines.push('### Action Required');
701
+ feedbackLines.push('');
702
+ feedbackLines.push('Fix the failing gates above, then re-run verification.');
703
+
704
+ return {
705
+ status: 'retry',
706
+ message: `Round ${currentRound}/${effectiveMaxRounds} failed. Agent should fix and retry.`,
707
+ should_retry: true,
708
+ round: currentRound,
709
+ max_rounds: effectiveMaxRounds,
710
+ next_round: currentRound + 1,
711
+ gate_results: gateResults,
712
+ agent_feedback: feedbackLines.join('\n'),
713
+ failures: failures.map(r => ({
714
+ gate: r.gate,
715
+ message: r.message,
716
+ output: r.output,
717
+ error: r.error,
718
+ })),
719
+ };
720
+ }
721
+
564
722
  // ============================================================================
565
723
  // Exports
566
724
  // ============================================================================
@@ -595,4 +753,9 @@ module.exports = {
595
753
 
596
754
  // Reporting
597
755
  createValidationReport,
756
+
757
+ // CI Feedback Loop
758
+ CI_FEEDBACK_DEFAULTS,
759
+ loadCIFeedbackConfig,
760
+ executeCIFeedbackLoop,
598
761
  };
@@ -233,6 +233,24 @@ const FEATURE_DETECTORS = {
233
233
  });
234
234
  },
235
235
 
236
+ 'scale-adaptive': signals => {
237
+ const { scale } = signals;
238
+ if (!scale || !scale.tier) return null;
239
+ // Only trigger when scale info provides actionable guidance
240
+ const rec = scale.recommendations;
241
+ if (!rec) return null;
242
+ // Suggest scale-adaptive workflow when project is not medium (the default)
243
+ if (scale.tier === 'medium') return null;
244
+ const label = scale.tier.charAt(0).toUpperCase() + scale.tier.slice(1);
245
+ return recommend('scale-adaptive', {
246
+ priority: scale.tier === 'enterprise' || scale.tier === 'large' ? 'medium' : 'low',
247
+ trigger: `${label} project detected (${scale.metrics.files} files, ${scale.metrics.stories} stories) — ${rec.description}`,
248
+ action: 'suggest',
249
+ command: '/agileflow:workflow',
250
+ phase: 'pre-story',
251
+ });
252
+ },
253
+
236
254
  // =========================================================================
237
255
  // PLANNING PHASE
238
256
  // =========================================================================
@@ -517,6 +535,29 @@ const FEATURE_DETECTORS = {
517
535
  });
518
536
  },
519
537
 
538
+ 'ac-verify': signals => {
539
+ const { story, tests } = signals;
540
+ if (!story || story.status !== 'in-progress') return null;
541
+ if (!tests || tests.passing !== true) return null; // Only after tests pass
542
+ if (!storyHasAC(story)) return null;
543
+ // Check if AC already verified (count by index to avoid extra keys)
544
+ const acStatus = story.ac_status || {};
545
+ const acList = story.acceptance_criteria || story.ac || [];
546
+ const verifiedCount = acList.filter(
547
+ (_, i) =>
548
+ acStatus[i] === 'verified' || acStatus[i] === 'auto-verified' || acStatus[i] === true
549
+ ).length;
550
+ if (verifiedCount === acList.length) return null;
551
+ const unverifiedCount = acList.length - verifiedCount;
552
+ return recommend('ac-verify', {
553
+ priority: 'high',
554
+ trigger: `Tests pass but ${unverifiedCount}/${acList.length} AC unverified`,
555
+ action: 'suggest',
556
+ command: '/agileflow:audit',
557
+ phase: 'post-impl',
558
+ });
559
+ },
560
+
520
561
  // =========================================================================
521
562
  // POST-IMPLEMENTATION PHASE
522
563
  // =========================================================================
@@ -704,6 +745,7 @@ const PHASE_MAP = {
704
745
  'workflow',
705
746
  'template',
706
747
  'configure',
748
+ 'scale-adaptive',
707
749
  ],
708
750
  planning: [
709
751
  'impact',
@@ -728,6 +770,7 @@ const PHASE_MAP = {
728
770
  'serve',
729
771
  ],
730
772
  'post-impl': [
773
+ 'ac-verify',
731
774
  'review',
732
775
  'logic-audit',
733
776
  'docs',