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.
- package/CHANGELOG.md +10 -0
- package/README.md +6 -6
- package/lib/skill-loader.js +0 -1
- package/package.json +1 -1
- package/scripts/agileflow-statusline.sh +81 -0
- package/scripts/agileflow-welcome.js +79 -0
- package/scripts/claude-tmux.sh +90 -23
- package/scripts/claude-watchdog.sh +225 -0
- package/scripts/generators/agent-registry.js +14 -1
- package/scripts/generators/inject-babysit.js +22 -9
- package/scripts/generators/inject-help.js +19 -9
- package/scripts/lib/ac-test-matcher.js +452 -0
- package/scripts/lib/audit-cleanup.js +250 -0
- package/scripts/lib/audit-registry.js +304 -0
- package/scripts/lib/configure-features.js +35 -0
- package/scripts/lib/feature-catalog.js +3 -3
- package/scripts/lib/gate-enforcer.js +295 -0
- package/scripts/lib/model-profiles.js +118 -0
- package/scripts/lib/quality-gates.js +163 -0
- package/scripts/lib/signal-detectors.js +44 -1
- package/scripts/lib/skill-catalog.js +557 -0
- package/scripts/lib/skill-recommender.js +311 -0
- package/scripts/lib/status-writer.js +255 -0
- package/scripts/lib/story-claiming.js +128 -45
- package/scripts/lib/task-sync.js +32 -38
- package/scripts/lib/tdd-phase-manager.js +455 -0
- package/scripts/lib/team-events.js +34 -3
- package/scripts/lib/tmux-audit-monitor.js +611 -0
- package/scripts/lib/tmux-group-colors.js +113 -0
- package/scripts/lib/tool-registry.yaml +241 -0
- package/scripts/lib/tool-shed.js +441 -0
- package/scripts/messaging-bridge.js +209 -1
- package/scripts/native-team-observer.js +219 -0
- package/scripts/obtain-context.js +14 -0
- package/scripts/ralph-loop.js +30 -5
- package/scripts/smart-detect.js +21 -0
- package/scripts/spawn-audit-sessions.js +877 -0
- package/scripts/team-manager.js +56 -16
- package/scripts/tmux-close-windows.sh +180 -0
- package/src/core/agents/a11y-analyzer-aria.md +155 -0
- package/src/core/agents/a11y-analyzer-forms.md +162 -0
- package/src/core/agents/a11y-analyzer-keyboard.md +175 -0
- package/src/core/agents/a11y-analyzer-semantic.md +153 -0
- package/src/core/agents/a11y-analyzer-visual.md +158 -0
- package/src/core/agents/a11y-consensus.md +248 -0
- package/src/core/agents/ads-audit-budget.md +181 -0
- package/src/core/agents/ads-audit-compliance.md +169 -0
- package/src/core/agents/ads-audit-creative.md +164 -0
- package/src/core/agents/ads-audit-google.md +226 -0
- package/src/core/agents/ads-audit-meta.md +183 -0
- package/src/core/agents/ads-audit-tracking.md +197 -0
- package/src/core/agents/ads-consensus.md +396 -0
- package/src/core/agents/ads-generate.md +145 -0
- package/src/core/agents/ads-performance-tracker.md +197 -0
- package/src/core/agents/api-quality-analyzer-conventions.md +148 -0
- package/src/core/agents/api-quality-analyzer-docs.md +176 -0
- package/src/core/agents/api-quality-analyzer-errors.md +183 -0
- package/src/core/agents/api-quality-analyzer-pagination.md +171 -0
- package/src/core/agents/api-quality-analyzer-versioning.md +143 -0
- package/src/core/agents/api-quality-consensus.md +214 -0
- package/src/core/agents/arch-analyzer-circular.md +148 -0
- package/src/core/agents/arch-analyzer-complexity.md +171 -0
- package/src/core/agents/arch-analyzer-coupling.md +146 -0
- package/src/core/agents/arch-analyzer-layering.md +151 -0
- package/src/core/agents/arch-analyzer-patterns.md +162 -0
- package/src/core/agents/arch-consensus.md +227 -0
- package/src/core/agents/brainstorm-analyzer-features.md +169 -0
- package/src/core/agents/brainstorm-analyzer-growth.md +161 -0
- package/src/core/agents/brainstorm-analyzer-integration.md +172 -0
- package/src/core/agents/brainstorm-analyzer-market.md +147 -0
- package/src/core/agents/brainstorm-analyzer-ux.md +167 -0
- package/src/core/agents/brainstorm-consensus.md +237 -0
- package/src/core/agents/completeness-consensus.md +5 -5
- package/src/core/agents/perf-consensus.md +2 -2
- package/src/core/agents/security-consensus.md +2 -2
- package/src/core/agents/seo-analyzer-content.md +167 -0
- package/src/core/agents/seo-analyzer-images.md +187 -0
- package/src/core/agents/seo-analyzer-performance.md +206 -0
- package/src/core/agents/seo-analyzer-schema.md +176 -0
- package/src/core/agents/seo-analyzer-sitemap.md +172 -0
- package/src/core/agents/seo-analyzer-technical.md +144 -0
- package/src/core/agents/seo-consensus.md +289 -0
- package/src/core/agents/test-consensus.md +2 -2
- package/src/core/commands/adr.md +1 -0
- package/src/core/commands/ads/audit.md +375 -0
- package/src/core/commands/ads/budget.md +97 -0
- package/src/core/commands/ads/competitor.md +112 -0
- package/src/core/commands/ads/creative.md +85 -0
- package/src/core/commands/ads/generate.md +238 -0
- package/src/core/commands/ads/google.md +112 -0
- package/src/core/commands/ads/health.md +327 -0
- package/src/core/commands/ads/landing.md +119 -0
- package/src/core/commands/ads/linkedin.md +112 -0
- package/src/core/commands/ads/meta.md +91 -0
- package/src/core/commands/ads/microsoft.md +115 -0
- package/src/core/commands/ads/plan.md +321 -0
- package/src/core/commands/ads/test-plan.md +317 -0
- package/src/core/commands/ads/tiktok.md +129 -0
- package/src/core/commands/ads/track.md +288 -0
- package/src/core/commands/ads/youtube.md +124 -0
- package/src/core/commands/ads.md +140 -0
- package/src/core/commands/assign.md +1 -0
- package/src/core/commands/audit.md +43 -6
- package/src/core/commands/babysit.md +315 -1266
- package/src/core/commands/baseline.md +1 -0
- package/src/core/commands/blockers.md +1 -0
- package/src/core/commands/board.md +1 -0
- package/src/core/commands/changelog.md +1 -0
- package/src/core/commands/choose.md +1 -0
- package/src/core/commands/ci.md +1 -0
- package/src/core/commands/code/accessibility.md +347 -0
- package/src/core/commands/code/api.md +297 -0
- package/src/core/commands/code/architecture.md +297 -0
- package/src/core/commands/{audit → code}/completeness.md +72 -25
- package/src/core/commands/{audit → code}/legal.md +63 -16
- package/src/core/commands/{audit → code}/logic.md +64 -16
- package/src/core/commands/{audit → code}/performance.md +67 -20
- package/src/core/commands/{audit → code}/security.md +69 -19
- package/src/core/commands/{audit → code}/test.md +67 -20
- package/src/core/commands/configure.md +1 -0
- package/src/core/commands/council.md +1 -0
- package/src/core/commands/deploy.md +1 -0
- package/src/core/commands/diagnose.md +1 -0
- package/src/core/commands/docs.md +1 -0
- package/src/core/commands/epic/edit.md +213 -0
- package/src/core/commands/epic.md +1 -0
- package/src/core/commands/export.md +238 -0
- package/src/core/commands/help.md +16 -1
- package/src/core/commands/{discovery → ideate}/brief.md +12 -12
- package/src/core/commands/{discovery/new.md → ideate/discover.md} +20 -16
- package/src/core/commands/ideate/features.md +496 -0
- package/src/core/commands/ideate/new.md +158 -124
- package/src/core/commands/impact.md +1 -0
- package/src/core/commands/learn/explain.md +118 -0
- package/src/core/commands/learn/glossary.md +135 -0
- package/src/core/commands/learn/patterns.md +138 -0
- package/src/core/commands/learn/tour.md +126 -0
- package/src/core/commands/migrate/codemods.md +151 -0
- package/src/core/commands/migrate/plan.md +131 -0
- package/src/core/commands/migrate/scan.md +114 -0
- package/src/core/commands/migrate/validate.md +119 -0
- package/src/core/commands/multi-expert.md +1 -0
- package/src/core/commands/pr.md +1 -0
- package/src/core/commands/review.md +1 -0
- package/src/core/commands/seo/audit.md +373 -0
- package/src/core/commands/seo/competitor.md +174 -0
- package/src/core/commands/seo/content.md +107 -0
- package/src/core/commands/seo/geo.md +229 -0
- package/src/core/commands/seo/hreflang.md +140 -0
- package/src/core/commands/seo/images.md +96 -0
- package/src/core/commands/seo/page.md +198 -0
- package/src/core/commands/seo/plan.md +163 -0
- package/src/core/commands/seo/programmatic.md +131 -0
- package/src/core/commands/seo/references/cwv-thresholds.md +64 -0
- package/src/core/commands/seo/references/eeat-framework.md +110 -0
- package/src/core/commands/seo/references/quality-gates.md +91 -0
- package/src/core/commands/seo/references/schema-types.md +102 -0
- package/src/core/commands/seo/schema.md +183 -0
- package/src/core/commands/seo/sitemap.md +97 -0
- package/src/core/commands/seo/technical.md +100 -0
- package/src/core/commands/seo.md +107 -0
- package/src/core/commands/skill/list.md +68 -212
- package/src/core/commands/skill/recommend.md +216 -0
- package/src/core/commands/sprint.md +1 -0
- package/src/core/commands/status/undo.md +191 -0
- package/src/core/commands/status.md +1 -0
- package/src/core/commands/story/edit.md +204 -0
- package/src/core/commands/story/view.md +29 -7
- package/src/core/commands/story-validate.md +1 -0
- package/src/core/commands/story.md +1 -0
- package/src/core/commands/tdd-next.md +238 -0
- package/src/core/commands/tdd.md +211 -0
- package/src/core/commands/team/start.md +10 -6
- package/src/core/commands/tests.md +1 -0
- package/src/core/commands/verify.md +27 -1
- package/src/core/commands/workflow.md +2 -0
- package/src/core/experts/_core-expertise.yaml +105 -0
- package/src/core/experts/analytics/expertise.yaml +5 -99
- package/src/core/experts/codebase-query/expertise.yaml +3 -72
- package/src/core/experts/compliance/expertise.yaml +6 -72
- package/src/core/experts/database/expertise.yaml +9 -52
- package/src/core/experts/documentation/expertise.yaml +7 -140
- package/src/core/experts/integrations/expertise.yaml +7 -127
- package/src/core/experts/mentor/expertise.yaml +8 -35
- package/src/core/experts/monitoring/expertise.yaml +7 -49
- package/src/core/experts/performance/expertise.yaml +1 -26
- package/src/core/experts/security/expertise.yaml +9 -34
- package/src/core/experts/ui/expertise.yaml +6 -36
- package/src/core/knowledge/ads/ad-audit-checklist-scoring.md +424 -0
- package/src/core/knowledge/ads/ad-optimization-logic.md +590 -0
- package/src/core/knowledge/ads/ad-technical-specifications.md +385 -0
- package/src/core/knowledge/ads/definitive-advertising-reference-2026.md +506 -0
- package/src/core/knowledge/ads/paid-advertising-research-2026.md +445 -0
- package/src/core/teams/backend.json +41 -0
- package/src/core/teams/frontend.json +41 -0
- package/src/core/teams/qa.json +41 -0
- package/src/core/teams/solo.json +35 -0
- package/src/core/templates/agileflow-metadata.json +20 -1
- package/tools/cli/commands/setup.js +85 -3
- package/tools/cli/commands/update.js +42 -0
- package/tools/cli/installers/ide/_base-ide.js +42 -5
- package/tools/cli/installers/ide/claude-code.js +71 -3
- package/tools/cli/lib/content-injector.js +160 -12
- package/tools/cli/lib/docs-setup.js +1 -1
- package/src/core/commands/skill/create.md +0 -698
- package/src/core/commands/skill/delete.md +0 -316
- package/src/core/commands/skill/edit.md +0 -359
- package/src/core/commands/skill/test.md +0 -394
- package/src/core/commands/skill/upgrade.md +0 -552
- 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
|
-
//
|
|
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
|
-
|
|
388
|
-
|
|
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
|
};
|