agileflow 3.3.0 → 3.4.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.
Files changed (210) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +6 -6
  3. package/lib/skill-loader.js +0 -1
  4. package/package.json +1 -1
  5. package/scripts/agileflow-statusline.sh +81 -0
  6. package/scripts/agileflow-welcome.js +79 -0
  7. package/scripts/claude-tmux.sh +90 -23
  8. package/scripts/claude-watchdog.sh +225 -0
  9. package/scripts/generators/agent-registry.js +14 -1
  10. package/scripts/generators/inject-babysit.js +22 -9
  11. package/scripts/generators/inject-help.js +19 -9
  12. package/scripts/lib/ac-test-matcher.js +452 -0
  13. package/scripts/lib/audit-cleanup.js +250 -0
  14. package/scripts/lib/audit-registry.js +304 -0
  15. package/scripts/lib/configure-features.js +35 -0
  16. package/scripts/lib/feature-catalog.js +3 -3
  17. package/scripts/lib/gate-enforcer.js +295 -0
  18. package/scripts/lib/model-profiles.js +118 -0
  19. package/scripts/lib/quality-gates.js +163 -0
  20. package/scripts/lib/signal-detectors.js +44 -1
  21. package/scripts/lib/skill-catalog.js +557 -0
  22. package/scripts/lib/skill-recommender.js +311 -0
  23. package/scripts/lib/status-writer.js +255 -0
  24. package/scripts/lib/story-claiming.js +128 -45
  25. package/scripts/lib/task-sync.js +32 -38
  26. package/scripts/lib/tdd-phase-manager.js +455 -0
  27. package/scripts/lib/team-events.js +34 -3
  28. package/scripts/lib/tmux-audit-monitor.js +611 -0
  29. package/scripts/lib/tmux-group-colors.js +113 -0
  30. package/scripts/lib/tool-registry.yaml +241 -0
  31. package/scripts/lib/tool-shed.js +441 -0
  32. package/scripts/messaging-bridge.js +209 -1
  33. package/scripts/native-team-observer.js +219 -0
  34. package/scripts/obtain-context.js +14 -0
  35. package/scripts/ralph-loop.js +30 -5
  36. package/scripts/smart-detect.js +21 -0
  37. package/scripts/spawn-audit-sessions.js +877 -0
  38. package/scripts/team-manager.js +56 -16
  39. package/scripts/tmux-close-windows.sh +180 -0
  40. package/src/core/agents/a11y-analyzer-aria.md +155 -0
  41. package/src/core/agents/a11y-analyzer-forms.md +162 -0
  42. package/src/core/agents/a11y-analyzer-keyboard.md +175 -0
  43. package/src/core/agents/a11y-analyzer-semantic.md +153 -0
  44. package/src/core/agents/a11y-analyzer-visual.md +158 -0
  45. package/src/core/agents/a11y-consensus.md +248 -0
  46. package/src/core/agents/ads-audit-budget.md +181 -0
  47. package/src/core/agents/ads-audit-compliance.md +169 -0
  48. package/src/core/agents/ads-audit-creative.md +164 -0
  49. package/src/core/agents/ads-audit-google.md +226 -0
  50. package/src/core/agents/ads-audit-meta.md +183 -0
  51. package/src/core/agents/ads-audit-tracking.md +197 -0
  52. package/src/core/agents/ads-consensus.md +396 -0
  53. package/src/core/agents/ads-generate.md +145 -0
  54. package/src/core/agents/ads-performance-tracker.md +197 -0
  55. package/src/core/agents/api-quality-analyzer-conventions.md +148 -0
  56. package/src/core/agents/api-quality-analyzer-docs.md +176 -0
  57. package/src/core/agents/api-quality-analyzer-errors.md +183 -0
  58. package/src/core/agents/api-quality-analyzer-pagination.md +171 -0
  59. package/src/core/agents/api-quality-analyzer-versioning.md +143 -0
  60. package/src/core/agents/api-quality-consensus.md +214 -0
  61. package/src/core/agents/arch-analyzer-circular.md +148 -0
  62. package/src/core/agents/arch-analyzer-complexity.md +171 -0
  63. package/src/core/agents/arch-analyzer-coupling.md +146 -0
  64. package/src/core/agents/arch-analyzer-layering.md +151 -0
  65. package/src/core/agents/arch-analyzer-patterns.md +162 -0
  66. package/src/core/agents/arch-consensus.md +227 -0
  67. package/src/core/agents/brainstorm-analyzer-features.md +169 -0
  68. package/src/core/agents/brainstorm-analyzer-growth.md +161 -0
  69. package/src/core/agents/brainstorm-analyzer-integration.md +172 -0
  70. package/src/core/agents/brainstorm-analyzer-market.md +147 -0
  71. package/src/core/agents/brainstorm-analyzer-ux.md +167 -0
  72. package/src/core/agents/brainstorm-consensus.md +237 -0
  73. package/src/core/agents/completeness-consensus.md +5 -5
  74. package/src/core/agents/perf-consensus.md +2 -2
  75. package/src/core/agents/security-consensus.md +2 -2
  76. package/src/core/agents/seo-analyzer-content.md +167 -0
  77. package/src/core/agents/seo-analyzer-images.md +187 -0
  78. package/src/core/agents/seo-analyzer-performance.md +206 -0
  79. package/src/core/agents/seo-analyzer-schema.md +176 -0
  80. package/src/core/agents/seo-analyzer-sitemap.md +172 -0
  81. package/src/core/agents/seo-analyzer-technical.md +144 -0
  82. package/src/core/agents/seo-consensus.md +289 -0
  83. package/src/core/agents/test-consensus.md +2 -2
  84. package/src/core/commands/adr.md +1 -0
  85. package/src/core/commands/ads/audit.md +375 -0
  86. package/src/core/commands/ads/budget.md +97 -0
  87. package/src/core/commands/ads/competitor.md +112 -0
  88. package/src/core/commands/ads/creative.md +85 -0
  89. package/src/core/commands/ads/generate.md +238 -0
  90. package/src/core/commands/ads/google.md +112 -0
  91. package/src/core/commands/ads/health.md +327 -0
  92. package/src/core/commands/ads/landing.md +119 -0
  93. package/src/core/commands/ads/linkedin.md +112 -0
  94. package/src/core/commands/ads/meta.md +91 -0
  95. package/src/core/commands/ads/microsoft.md +115 -0
  96. package/src/core/commands/ads/plan.md +321 -0
  97. package/src/core/commands/ads/test-plan.md +317 -0
  98. package/src/core/commands/ads/tiktok.md +129 -0
  99. package/src/core/commands/ads/track.md +288 -0
  100. package/src/core/commands/ads/youtube.md +124 -0
  101. package/src/core/commands/ads.md +140 -0
  102. package/src/core/commands/assign.md +1 -0
  103. package/src/core/commands/audit.md +43 -6
  104. package/src/core/commands/babysit.md +315 -1266
  105. package/src/core/commands/baseline.md +1 -0
  106. package/src/core/commands/blockers.md +1 -0
  107. package/src/core/commands/board.md +1 -0
  108. package/src/core/commands/changelog.md +1 -0
  109. package/src/core/commands/choose.md +1 -0
  110. package/src/core/commands/ci.md +1 -0
  111. package/src/core/commands/code/accessibility.md +347 -0
  112. package/src/core/commands/code/api.md +297 -0
  113. package/src/core/commands/code/architecture.md +297 -0
  114. package/src/core/commands/{audit → code}/completeness.md +72 -25
  115. package/src/core/commands/{audit → code}/legal.md +63 -16
  116. package/src/core/commands/{audit → code}/logic.md +64 -16
  117. package/src/core/commands/{audit → code}/performance.md +67 -20
  118. package/src/core/commands/{audit → code}/security.md +69 -19
  119. package/src/core/commands/{audit → code}/test.md +67 -20
  120. package/src/core/commands/configure.md +1 -0
  121. package/src/core/commands/council.md +1 -0
  122. package/src/core/commands/deploy.md +1 -0
  123. package/src/core/commands/diagnose.md +1 -0
  124. package/src/core/commands/docs.md +1 -0
  125. package/src/core/commands/epic/edit.md +213 -0
  126. package/src/core/commands/epic.md +1 -0
  127. package/src/core/commands/export.md +238 -0
  128. package/src/core/commands/help.md +16 -1
  129. package/src/core/commands/{discovery → ideate}/brief.md +12 -12
  130. package/src/core/commands/{discovery/new.md → ideate/discover.md} +20 -16
  131. package/src/core/commands/ideate/features.md +496 -0
  132. package/src/core/commands/ideate/new.md +158 -124
  133. package/src/core/commands/impact.md +1 -0
  134. package/src/core/commands/learn/explain.md +118 -0
  135. package/src/core/commands/learn/glossary.md +135 -0
  136. package/src/core/commands/learn/patterns.md +138 -0
  137. package/src/core/commands/learn/tour.md +126 -0
  138. package/src/core/commands/migrate/codemods.md +151 -0
  139. package/src/core/commands/migrate/plan.md +131 -0
  140. package/src/core/commands/migrate/scan.md +114 -0
  141. package/src/core/commands/migrate/validate.md +119 -0
  142. package/src/core/commands/multi-expert.md +1 -0
  143. package/src/core/commands/pr.md +1 -0
  144. package/src/core/commands/review.md +1 -0
  145. package/src/core/commands/seo/audit.md +373 -0
  146. package/src/core/commands/seo/competitor.md +174 -0
  147. package/src/core/commands/seo/content.md +107 -0
  148. package/src/core/commands/seo/geo.md +229 -0
  149. package/src/core/commands/seo/hreflang.md +140 -0
  150. package/src/core/commands/seo/images.md +96 -0
  151. package/src/core/commands/seo/page.md +198 -0
  152. package/src/core/commands/seo/plan.md +163 -0
  153. package/src/core/commands/seo/programmatic.md +131 -0
  154. package/src/core/commands/seo/references/cwv-thresholds.md +64 -0
  155. package/src/core/commands/seo/references/eeat-framework.md +110 -0
  156. package/src/core/commands/seo/references/quality-gates.md +91 -0
  157. package/src/core/commands/seo/references/schema-types.md +102 -0
  158. package/src/core/commands/seo/schema.md +183 -0
  159. package/src/core/commands/seo/sitemap.md +97 -0
  160. package/src/core/commands/seo/technical.md +100 -0
  161. package/src/core/commands/seo.md +107 -0
  162. package/src/core/commands/skill/list.md +68 -212
  163. package/src/core/commands/skill/recommend.md +216 -0
  164. package/src/core/commands/sprint.md +1 -0
  165. package/src/core/commands/status/undo.md +191 -0
  166. package/src/core/commands/status.md +1 -0
  167. package/src/core/commands/story/edit.md +204 -0
  168. package/src/core/commands/story/view.md +29 -7
  169. package/src/core/commands/story-validate.md +1 -0
  170. package/src/core/commands/story.md +1 -0
  171. package/src/core/commands/tdd-next.md +238 -0
  172. package/src/core/commands/tdd.md +211 -0
  173. package/src/core/commands/team/start.md +10 -6
  174. package/src/core/commands/tests.md +1 -0
  175. package/src/core/commands/verify.md +27 -1
  176. package/src/core/commands/workflow.md +2 -0
  177. package/src/core/experts/_core-expertise.yaml +105 -0
  178. package/src/core/experts/analytics/expertise.yaml +5 -99
  179. package/src/core/experts/codebase-query/expertise.yaml +3 -72
  180. package/src/core/experts/compliance/expertise.yaml +6 -72
  181. package/src/core/experts/database/expertise.yaml +9 -52
  182. package/src/core/experts/documentation/expertise.yaml +7 -140
  183. package/src/core/experts/integrations/expertise.yaml +7 -127
  184. package/src/core/experts/mentor/expertise.yaml +8 -35
  185. package/src/core/experts/monitoring/expertise.yaml +7 -49
  186. package/src/core/experts/performance/expertise.yaml +1 -26
  187. package/src/core/experts/security/expertise.yaml +9 -34
  188. package/src/core/experts/ui/expertise.yaml +6 -36
  189. package/src/core/knowledge/ads/ad-audit-checklist-scoring.md +424 -0
  190. package/src/core/knowledge/ads/ad-optimization-logic.md +590 -0
  191. package/src/core/knowledge/ads/ad-technical-specifications.md +385 -0
  192. package/src/core/knowledge/ads/definitive-advertising-reference-2026.md +506 -0
  193. package/src/core/knowledge/ads/paid-advertising-research-2026.md +445 -0
  194. package/src/core/teams/backend.json +41 -0
  195. package/src/core/teams/frontend.json +41 -0
  196. package/src/core/teams/qa.json +41 -0
  197. package/src/core/teams/solo.json +35 -0
  198. package/src/core/templates/agileflow-metadata.json +20 -1
  199. package/tools/cli/commands/setup.js +85 -3
  200. package/tools/cli/commands/update.js +42 -0
  201. package/tools/cli/installers/ide/_base-ide.js +42 -5
  202. package/tools/cli/installers/ide/claude-code.js +71 -3
  203. package/tools/cli/lib/content-injector.js +160 -12
  204. package/tools/cli/lib/docs-setup.js +1 -1
  205. package/src/core/commands/skill/create.md +0 -698
  206. package/src/core/commands/skill/delete.md +0 -316
  207. package/src/core/commands/skill/edit.md +0 -359
  208. package/src/core/commands/skill/test.md +0 -394
  209. package/src/core/commands/skill/upgrade.md +0 -552
  210. package/src/core/templates/skill-template.md +0 -117
@@ -0,0 +1,455 @@
1
+ /**
2
+ * tdd-phase-manager.js - TDD Phase Tracking for AgileFlow
3
+ *
4
+ * Manages RED→GREEN→REFACTOR phase transitions for TDD workflow.
5
+ * Phase state is stored in status.json story entries under `tdd_phase`.
6
+ *
7
+ * Phases:
8
+ * - red: Write failing tests first (no implementation code allowed)
9
+ * - green: Write minimal code to make tests pass
10
+ * - refactor: Clean up code while keeping tests green
11
+ * - complete: TDD cycle done, ready for commit
12
+ *
13
+ * Transitions:
14
+ * - red → green: Requires test_status = "failing" (tests exist and fail)
15
+ * - green → refactor: Requires test_status = "passing" (tests pass)
16
+ * - refactor → red: Start new cycle (tests must still pass)
17
+ * - refactor → complete: TDD done (tests must pass)
18
+ * - any → cancelled: Exit TDD workflow
19
+ *
20
+ * Usage:
21
+ * const { startTDD, advancePhase, getPhaseInstructions } = require('./tdd-phase-manager');
22
+ * const result = startTDD(statusData, 'US-0042');
23
+ * const advance = advancePhase(statusData, 'US-0042', testStatus);
24
+ */
25
+
26
+ 'use strict';
27
+
28
+ const fs = require('fs');
29
+ const path = require('path');
30
+
31
+ // ============================================================================
32
+ // Constants
33
+ // ============================================================================
34
+
35
+ const PHASES = {
36
+ RED: 'red',
37
+ GREEN: 'green',
38
+ REFACTOR: 'refactor',
39
+ COMPLETE: 'complete',
40
+ CANCELLED: 'cancelled',
41
+ };
42
+
43
+ const VALID_TRANSITIONS = {
44
+ [PHASES.RED]: [PHASES.GREEN, PHASES.CANCELLED],
45
+ [PHASES.GREEN]: [PHASES.REFACTOR, PHASES.CANCELLED],
46
+ [PHASES.REFACTOR]: [PHASES.RED, PHASES.COMPLETE, PHASES.CANCELLED],
47
+ [PHASES.COMPLETE]: [], // Terminal
48
+ [PHASES.CANCELLED]: [], // Terminal
49
+ };
50
+
51
+ /**
52
+ * Conditions required for each transition
53
+ */
54
+ const TRANSITION_CONDITIONS = {
55
+ [`${PHASES.RED}->${PHASES.GREEN}`]: {
56
+ requires: 'test_status_failing',
57
+ message: 'Tests must exist and be FAILING before moving to GREEN phase',
58
+ hint: 'Write your failing tests first, then run /agileflow:verify to confirm they fail',
59
+ },
60
+ [`${PHASES.GREEN}->${PHASES.REFACTOR}`]: {
61
+ requires: 'test_status_passing',
62
+ message: 'Tests must be PASSING before moving to REFACTOR phase',
63
+ hint: 'Write minimal code to make tests pass, then run /agileflow:verify',
64
+ },
65
+ [`${PHASES.REFACTOR}->${PHASES.RED}`]: {
66
+ requires: 'test_status_passing',
67
+ message: 'Tests must still be PASSING before starting a new RED cycle',
68
+ hint: 'Ensure refactoring did not break tests',
69
+ },
70
+ [`${PHASES.REFACTOR}->${PHASES.COMPLETE}`]: {
71
+ requires: 'test_status_passing',
72
+ message: 'Tests must be PASSING to complete TDD workflow',
73
+ hint: 'Run /agileflow:verify to confirm all tests pass',
74
+ },
75
+ };
76
+
77
+ /**
78
+ * Phase-specific instructions for the AI agent
79
+ */
80
+ const PHASE_INSTRUCTIONS = {
81
+ [PHASES.RED]: {
82
+ emoji: '🔴',
83
+ title: 'RED Phase - Write Failing Tests',
84
+ rules: [
85
+ 'Write test files ONLY - do NOT write implementation code yet',
86
+ 'Tests should cover the acceptance criteria for this story',
87
+ 'Tests MUST fail when run (they test code that does not exist yet)',
88
+ 'Focus on the public API/interface - what should the code DO?',
89
+ 'Use `.skip()` for tests you plan to implement later in this cycle',
90
+ ],
91
+ allowed_file_patterns: [
92
+ '**/*.test.*',
93
+ '**/*.spec.*',
94
+ '**/test_*',
95
+ '**/*_test.*',
96
+ '**/tests/**',
97
+ '**/__tests__/**',
98
+ '**/test/**',
99
+ '**/spec/**',
100
+ '**/fixtures/**',
101
+ '**/mocks/**',
102
+ '**/helpers/**',
103
+ ],
104
+ next_action: 'Run /agileflow:verify to confirm tests FAIL, then /agileflow:tdd-next to advance',
105
+ },
106
+ [PHASES.GREEN]: {
107
+ emoji: '🟢',
108
+ title: 'GREEN Phase - Make Tests Pass',
109
+ rules: [
110
+ 'Write MINIMAL implementation code to make failing tests pass',
111
+ 'Do NOT refactor yet - focus only on making tests green',
112
+ 'Do NOT add features beyond what tests require',
113
+ 'Do NOT modify test files (except removing .skip())',
114
+ 'Simple, direct solutions - even if ugly',
115
+ ],
116
+ next_action: 'Run /agileflow:verify to confirm tests PASS, then /agileflow:tdd-next to advance',
117
+ },
118
+ [PHASES.REFACTOR]: {
119
+ emoji: '🔵',
120
+ title: 'REFACTOR Phase - Clean Up',
121
+ rules: [
122
+ 'Improve code quality while keeping ALL tests green',
123
+ 'Extract functions, rename variables, reduce duplication',
124
+ 'Run tests frequently - any failure means you broke something',
125
+ 'Do NOT add new features or change behavior',
126
+ 'When satisfied, use /agileflow:tdd-next to either start new RED cycle or complete',
127
+ ],
128
+ next_action:
129
+ 'Run /agileflow:verify, then /agileflow:tdd-next (choose "complete" or "new cycle")',
130
+ },
131
+ [PHASES.COMPLETE]: {
132
+ emoji: '✅',
133
+ title: 'TDD Complete',
134
+ rules: ['All tests pass', 'Code is clean', 'Ready for code review and commit'],
135
+ next_action: 'Run code review, then commit',
136
+ },
137
+ };
138
+
139
+ // ============================================================================
140
+ // Phase Management
141
+ // ============================================================================
142
+
143
+ /**
144
+ * Start TDD workflow for a story
145
+ * @param {Object} statusData - Full status.json data
146
+ * @param {string} storyId - Story ID (e.g., 'US-0042')
147
+ * @returns {{ success: boolean, phase: string, message: string, instructions: Object }}
148
+ */
149
+ function startTDD(statusData, storyId) {
150
+ if (!statusData || typeof statusData !== 'object') {
151
+ return { success: false, phase: null, message: 'Invalid status data', instructions: null };
152
+ }
153
+ if (!storyId || typeof storyId !== 'string') {
154
+ return {
155
+ success: false,
156
+ phase: null,
157
+ message: `Invalid story ID: ${storyId}`,
158
+ instructions: null,
159
+ };
160
+ }
161
+ const story = statusData.stories && statusData.stories[storyId];
162
+ if (!story) {
163
+ return {
164
+ success: false,
165
+ phase: null,
166
+ message: `Story ${storyId} not found in status.json`,
167
+ instructions: null,
168
+ };
169
+ }
170
+
171
+ // Check if already in TDD
172
+ if (
173
+ story.tdd_phase &&
174
+ story.tdd_phase !== PHASES.COMPLETE &&
175
+ story.tdd_phase !== PHASES.CANCELLED
176
+ ) {
177
+ return {
178
+ success: true,
179
+ phase: story.tdd_phase,
180
+ message: `Story ${storyId} already in TDD ${story.tdd_phase.toUpperCase()} phase - resuming`,
181
+ instructions: PHASE_INSTRUCTIONS[story.tdd_phase],
182
+ };
183
+ }
184
+
185
+ // Set RED phase
186
+ story.tdd_phase = PHASES.RED;
187
+ story.tdd_started_at = new Date().toISOString();
188
+ story.tdd_cycles = (story.tdd_cycles || 0) + 1;
189
+ statusData.updated_at = new Date().toISOString();
190
+
191
+ return {
192
+ success: true,
193
+ phase: PHASES.RED,
194
+ message: `TDD started for ${storyId} - entering RED phase (cycle ${story.tdd_cycles})`,
195
+ instructions: PHASE_INSTRUCTIONS[PHASES.RED],
196
+ };
197
+ }
198
+
199
+ /**
200
+ * Advance to the next TDD phase
201
+ * @param {Object} statusData - Full status.json data
202
+ * @param {string} storyId - Story ID
203
+ * @param {string} targetPhase - Desired next phase
204
+ * @param {Object} context - Current context
205
+ * @param {string} context.test_status - 'passing' | 'failing' | null
206
+ * @returns {{ success: boolean, phase: string, message: string, instructions: Object }}
207
+ */
208
+ function advancePhase(statusData, storyId, targetPhase, context = {}) {
209
+ if (!statusData || typeof statusData !== 'object') {
210
+ return { success: false, phase: null, message: 'Invalid status data', instructions: null };
211
+ }
212
+ if (!storyId || typeof storyId !== 'string') {
213
+ return {
214
+ success: false,
215
+ phase: null,
216
+ message: `Invalid story ID: ${storyId}`,
217
+ instructions: null,
218
+ };
219
+ }
220
+ if (!targetPhase || typeof targetPhase !== 'string') {
221
+ return {
222
+ success: false,
223
+ phase: null,
224
+ message: `Invalid target phase: ${targetPhase}`,
225
+ instructions: null,
226
+ };
227
+ }
228
+ // Normalize context if null passed explicitly
229
+ if (!context || typeof context !== 'object') {
230
+ context = {};
231
+ }
232
+ const story = statusData.stories && statusData.stories[storyId];
233
+ if (!story) {
234
+ return {
235
+ success: false,
236
+ phase: null,
237
+ message: `Story ${storyId} not found`,
238
+ instructions: null,
239
+ };
240
+ }
241
+
242
+ const currentPhase = story.tdd_phase;
243
+ if (!currentPhase) {
244
+ return {
245
+ success: false,
246
+ phase: null,
247
+ message: `Story ${storyId} is not in TDD mode. Start with /agileflow:tdd ${storyId}`,
248
+ instructions: null,
249
+ };
250
+ }
251
+
252
+ // Validate currentPhase is a known phase (catch corrupted data)
253
+ if (!Object.values(PHASES).includes(currentPhase)) {
254
+ return {
255
+ success: false,
256
+ phase: currentPhase,
257
+ message: `Story ${storyId} has invalid TDD phase: "${currentPhase}". Valid: ${Object.values(PHASES).join(', ')}`,
258
+ instructions: null,
259
+ };
260
+ }
261
+
262
+ // Check if transition is valid
263
+ const validTargets = VALID_TRANSITIONS[currentPhase] || [];
264
+ if (!validTargets.includes(targetPhase)) {
265
+ return {
266
+ success: false,
267
+ phase: currentPhase,
268
+ message: `Cannot transition from ${currentPhase.toUpperCase()} to ${targetPhase.toUpperCase()}. Valid: ${validTargets.join(', ') || 'none'}`,
269
+ instructions: PHASE_INSTRUCTIONS[currentPhase],
270
+ };
271
+ }
272
+
273
+ // Cancel is always allowed
274
+ if (targetPhase === PHASES.CANCELLED) {
275
+ story.tdd_phase = PHASES.CANCELLED;
276
+ story.tdd_cancelled_at = new Date().toISOString();
277
+ statusData.updated_at = new Date().toISOString();
278
+ return {
279
+ success: true,
280
+ phase: PHASES.CANCELLED,
281
+ message: `TDD cancelled for ${storyId}`,
282
+ instructions: null,
283
+ };
284
+ }
285
+
286
+ // Check transition conditions
287
+ const conditionKey = `${currentPhase}->${targetPhase}`;
288
+ const condition = TRANSITION_CONDITIONS[conditionKey];
289
+
290
+ if (condition) {
291
+ const { test_status } = context;
292
+
293
+ if (condition.requires === 'test_status_failing' && test_status !== 'failing') {
294
+ return {
295
+ success: false,
296
+ phase: currentPhase,
297
+ message: `🚫 ${condition.message}`,
298
+ hint: condition.hint,
299
+ instructions: PHASE_INSTRUCTIONS[currentPhase],
300
+ gate_blocked: true,
301
+ };
302
+ }
303
+
304
+ if (condition.requires === 'test_status_passing' && test_status !== 'passing') {
305
+ return {
306
+ success: false,
307
+ phase: currentPhase,
308
+ message: `🚫 ${condition.message}`,
309
+ hint: condition.hint,
310
+ instructions: PHASE_INSTRUCTIONS[currentPhase],
311
+ gate_blocked: true,
312
+ };
313
+ }
314
+ }
315
+
316
+ // Transition
317
+ const previousPhase = currentPhase;
318
+ story.tdd_phase = targetPhase;
319
+ story.tdd_last_transition = new Date().toISOString();
320
+
321
+ // Track cycles
322
+ if (targetPhase === PHASES.RED && previousPhase === PHASES.REFACTOR) {
323
+ story.tdd_cycles = (story.tdd_cycles || 0) + 1;
324
+ }
325
+
326
+ if (targetPhase === PHASES.COMPLETE) {
327
+ story.tdd_completed_at = new Date().toISOString();
328
+ }
329
+
330
+ statusData.updated_at = new Date().toISOString();
331
+
332
+ return {
333
+ success: true,
334
+ phase: targetPhase,
335
+ message: `${previousPhase.toUpperCase()} → ${targetPhase.toUpperCase()} for ${storyId}`,
336
+ instructions: PHASE_INSTRUCTIONS[targetPhase] || null,
337
+ };
338
+ }
339
+
340
+ /**
341
+ * Get current phase info for a story
342
+ * @param {Object} statusData - Full status.json data
343
+ * @param {string} storyId - Story ID
344
+ * @returns {{ phase: string|null, instructions: Object|null, active: boolean }}
345
+ */
346
+ function getPhaseInfo(statusData, storyId) {
347
+ if (!statusData || typeof statusData !== 'object') {
348
+ return { phase: null, instructions: null, active: false };
349
+ }
350
+ const story = statusData.stories && statusData.stories[storyId];
351
+ if (!story || !story.tdd_phase) {
352
+ return { phase: null, instructions: null, active: false };
353
+ }
354
+
355
+ const active = story.tdd_phase !== PHASES.COMPLETE && story.tdd_phase !== PHASES.CANCELLED;
356
+
357
+ return {
358
+ phase: story.tdd_phase,
359
+ instructions: PHASE_INSTRUCTIONS[story.tdd_phase] || null,
360
+ active,
361
+ cycles: story.tdd_cycles || 0,
362
+ started_at: story.tdd_started_at || null,
363
+ };
364
+ }
365
+
366
+ /**
367
+ * Get the next valid phases from current phase
368
+ * @param {string} currentPhase - Current TDD phase
369
+ * @returns {string[]} Valid next phases
370
+ */
371
+ function getNextPhases(currentPhase) {
372
+ return VALID_TRANSITIONS[currentPhase] || [];
373
+ }
374
+
375
+ /**
376
+ * Format phase status for display
377
+ * @param {Object} statusData - Full status.json data
378
+ * @param {string} storyId - Story ID
379
+ * @returns {string} Formatted status string
380
+ */
381
+ function formatPhaseStatus(statusData, storyId) {
382
+ const info = getPhaseInfo(statusData, storyId);
383
+
384
+ if (!info.phase) {
385
+ return `${storyId}: No TDD workflow active`;
386
+ }
387
+
388
+ const inst = info.instructions;
389
+ const lines = [
390
+ `${inst ? inst.emoji : '?'} ${storyId}: TDD ${info.phase.toUpperCase()} phase (cycle ${info.cycles})`,
391
+ ];
392
+
393
+ if (inst) {
394
+ lines.push(` ${inst.title}`);
395
+ if (inst.next_action) {
396
+ lines.push(` Next: ${inst.next_action}`);
397
+ }
398
+ }
399
+
400
+ return lines.join('\n');
401
+ }
402
+
403
+ // ============================================================================
404
+ // Status.json Helpers
405
+ // ============================================================================
406
+
407
+ /**
408
+ * Load status.json from the standard path
409
+ * @param {string} projectRoot - Project root directory
410
+ * @returns {Object|null} Parsed status data or null
411
+ */
412
+ function loadStatusData(projectRoot) {
413
+ const statusPath = path.join(projectRoot, 'docs', '09-agents', 'status.json');
414
+ try {
415
+ const content = fs.readFileSync(statusPath, 'utf8');
416
+ return JSON.parse(content);
417
+ } catch {
418
+ return null;
419
+ }
420
+ }
421
+
422
+ /**
423
+ * Save status.json to the standard path
424
+ * @param {string} projectRoot - Project root directory
425
+ * @param {Object} statusData - Status data to save
426
+ */
427
+ function saveStatusData(projectRoot, statusData) {
428
+ const statusPath = path.join(projectRoot, 'docs', '09-agents', 'status.json');
429
+ fs.writeFileSync(statusPath, JSON.stringify(statusData, null, 2) + '\n', 'utf8');
430
+ }
431
+
432
+ // ============================================================================
433
+ // Exports
434
+ // ============================================================================
435
+
436
+ module.exports = {
437
+ // Constants
438
+ PHASES,
439
+ VALID_TRANSITIONS,
440
+ TRANSITION_CONDITIONS,
441
+ PHASE_INSTRUCTIONS,
442
+
443
+ // Phase management
444
+ startTDD,
445
+ advancePhase,
446
+ getPhaseInfo,
447
+ getNextPhases,
448
+
449
+ // Display
450
+ formatPhaseStatus,
451
+
452
+ // Status.json helpers
453
+ loadStatusData,
454
+ saveStatusData,
455
+ };
@@ -73,6 +73,8 @@ function getPaths() {
73
73
  const EVENT_TYPES = [
74
74
  'team_created',
75
75
  'team_stopped',
76
+ 'team_completed',
77
+ 'team_message',
76
78
  'task_assigned',
77
79
  'task_completed',
78
80
  'agent_error',
@@ -171,9 +173,19 @@ function checkCostThreshold(rootDir, traceId, totalCostUsd, threshold) {
171
173
  * @returns {{ ok: boolean, error?: string }}
172
174
  */
173
175
  function trackEvent(rootDir, eventType, data = {}) {
176
+ // Detect native Agent Teams mode for metrics equivalence (AC4)
177
+ let isNative = false;
178
+ try {
179
+ const ff = require('../../lib/feature-flags');
180
+ isNative = ff.isAgentTeamsEnabled({ rootDir });
181
+ } catch (e) {
182
+ // Non-critical - default to false
183
+ }
184
+
174
185
  const event = {
175
186
  type: eventType,
176
187
  at: new Date().toISOString(),
188
+ agent_teams: isNative,
177
189
  ...data,
178
190
  };
179
191
 
@@ -380,12 +392,30 @@ function aggregateTeamMetrics(rootDir, traceId) {
380
392
  perGate[gate].pass_rate = total > 0 ? perGate[gate].passed / total : 0;
381
393
  }
382
394
 
383
- // Team completion time from team_created team_stopped
395
+ // Count team_message events per agent for message-level observability
396
+ const messagesSent = {};
397
+ let totalMessagesSent = 0;
398
+ for (const e of events) {
399
+ if (e.type === 'team_message' && e.from) {
400
+ if (!messagesSent[e.from]) messagesSent[e.from] = 0;
401
+ messagesSent[e.from]++;
402
+ totalMessagesSent++;
403
+ }
404
+ }
405
+ // Merge messages_sent into per-agent metrics
406
+ for (const [agent, count] of Object.entries(messagesSent)) {
407
+ ensureAgent(agent);
408
+ perAgent[agent].messages_sent = count;
409
+ }
410
+
411
+ // Team completion time from team_created → team_completed (or team_stopped as fallback)
384
412
  let teamCompletionMs = null;
385
413
  const created = events.find(e => e.type === 'team_created');
414
+ const completed = events.find(e => e.type === 'team_completed');
386
415
  const stopped = events.find(e => e.type === 'team_stopped');
387
- if (created && stopped) {
388
- teamCompletionMs = new Date(stopped.at).getTime() - new Date(created.at).getTime();
416
+ const endEvent = completed || stopped;
417
+ if (created && endEvent) {
418
+ teamCompletionMs = new Date(endEvent.at).getTime() - new Date(created.at).getTime();
389
419
  }
390
420
 
391
421
  return {
@@ -395,6 +425,7 @@ function aggregateTeamMetrics(rootDir, traceId) {
395
425
  per_gate: perGate,
396
426
  all_files_modified: allFilesModified,
397
427
  team_completion_ms: teamCompletionMs,
428
+ total_messages_sent: totalMessagesSent,
398
429
  total_cost_usd: Math.round(totalCostUsd * 1_000_000) / 1_000_000,
399
430
  computed_at: new Date().toISOString(),
400
431
  };