@hyperdrive.bot/bmad-workflow 1.0.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.
- package/LICENSE +21 -0
- package/README.md +1017 -0
- package/bin/dev +5 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +5 -0
- package/bin/run +5 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +5 -0
- package/dist/commands/config/show.d.ts +34 -0
- package/dist/commands/config/show.js +108 -0
- package/dist/commands/config/validate.d.ts +29 -0
- package/dist/commands/config/validate.js +131 -0
- package/dist/commands/decompose.d.ts +79 -0
- package/dist/commands/decompose.js +327 -0
- package/dist/commands/demo.d.ts +18 -0
- package/dist/commands/demo.js +107 -0
- package/dist/commands/epics/create.d.ts +123 -0
- package/dist/commands/epics/create.js +459 -0
- package/dist/commands/epics/list.d.ts +120 -0
- package/dist/commands/epics/list.js +280 -0
- package/dist/commands/hello/index.d.ts +12 -0
- package/dist/commands/hello/index.js +34 -0
- package/dist/commands/hello/world.d.ts +8 -0
- package/dist/commands/hello/world.js +24 -0
- package/dist/commands/prd/fix.d.ts +39 -0
- package/dist/commands/prd/fix.js +140 -0
- package/dist/commands/prd/validate.d.ts +112 -0
- package/dist/commands/prd/validate.js +302 -0
- package/dist/commands/stories/create.d.ts +95 -0
- package/dist/commands/stories/create.js +431 -0
- package/dist/commands/stories/develop.d.ts +91 -0
- package/dist/commands/stories/develop.js +460 -0
- package/dist/commands/stories/list.d.ts +84 -0
- package/dist/commands/stories/list.js +291 -0
- package/dist/commands/stories/move.d.ts +66 -0
- package/dist/commands/stories/move.js +273 -0
- package/dist/commands/stories/qa.d.ts +99 -0
- package/dist/commands/stories/qa.js +530 -0
- package/dist/commands/workflow.d.ts +97 -0
- package/dist/commands/workflow.js +390 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/models/agent-options.d.ts +50 -0
- package/dist/models/agent-options.js +1 -0
- package/dist/models/agent-result.d.ts +29 -0
- package/dist/models/agent-result.js +1 -0
- package/dist/models/index.d.ts +10 -0
- package/dist/models/index.js +10 -0
- package/dist/models/phase-result.d.ts +65 -0
- package/dist/models/phase-result.js +7 -0
- package/dist/models/provider.d.ts +28 -0
- package/dist/models/provider.js +18 -0
- package/dist/models/story.d.ts +154 -0
- package/dist/models/story.js +18 -0
- package/dist/models/workflow-config.d.ts +148 -0
- package/dist/models/workflow-config.js +1 -0
- package/dist/models/workflow-result.d.ts +164 -0
- package/dist/models/workflow-result.js +7 -0
- package/dist/services/agents/agent-runner-factory.d.ts +31 -0
- package/dist/services/agents/agent-runner-factory.js +44 -0
- package/dist/services/agents/agent-runner.d.ts +46 -0
- package/dist/services/agents/agent-runner.js +29 -0
- package/dist/services/agents/claude-agent-runner.d.ts +81 -0
- package/dist/services/agents/claude-agent-runner.js +332 -0
- package/dist/services/agents/gemini-agent-runner.d.ts +82 -0
- package/dist/services/agents/gemini-agent-runner.js +350 -0
- package/dist/services/agents/index.d.ts +7 -0
- package/dist/services/agents/index.js +7 -0
- package/dist/services/file-system/file-manager.d.ts +110 -0
- package/dist/services/file-system/file-manager.js +223 -0
- package/dist/services/file-system/glob-matcher.d.ts +75 -0
- package/dist/services/file-system/glob-matcher.js +126 -0
- package/dist/services/file-system/path-resolver.d.ts +183 -0
- package/dist/services/file-system/path-resolver.js +400 -0
- package/dist/services/logging/workflow-logger.d.ts +232 -0
- package/dist/services/logging/workflow-logger.js +552 -0
- package/dist/services/orchestration/batch-processor.d.ts +113 -0
- package/dist/services/orchestration/batch-processor.js +187 -0
- package/dist/services/orchestration/dependency-graph-executor.d.ts +60 -0
- package/dist/services/orchestration/dependency-graph-executor.js +447 -0
- package/dist/services/orchestration/index.d.ts +10 -0
- package/dist/services/orchestration/index.js +8 -0
- package/dist/services/orchestration/input-detector.d.ts +125 -0
- package/dist/services/orchestration/input-detector.js +381 -0
- package/dist/services/orchestration/story-queue.d.ts +94 -0
- package/dist/services/orchestration/story-queue.js +170 -0
- package/dist/services/orchestration/story-type-detector.d.ts +80 -0
- package/dist/services/orchestration/story-type-detector.js +258 -0
- package/dist/services/orchestration/task-decomposition-service.d.ts +67 -0
- package/dist/services/orchestration/task-decomposition-service.js +607 -0
- package/dist/services/orchestration/workflow-orchestrator.d.ts +659 -0
- package/dist/services/orchestration/workflow-orchestrator.js +2201 -0
- package/dist/services/parsers/epic-parser.d.ts +117 -0
- package/dist/services/parsers/epic-parser.js +264 -0
- package/dist/services/parsers/prd-fixer.d.ts +86 -0
- package/dist/services/parsers/prd-fixer.js +194 -0
- package/dist/services/parsers/prd-parser.d.ts +123 -0
- package/dist/services/parsers/prd-parser.js +286 -0
- package/dist/services/parsers/standalone-story-parser.d.ts +114 -0
- package/dist/services/parsers/standalone-story-parser.js +255 -0
- package/dist/services/parsers/story-parser-factory.d.ts +81 -0
- package/dist/services/parsers/story-parser-factory.js +108 -0
- package/dist/services/parsers/story-parser.d.ts +122 -0
- package/dist/services/parsers/story-parser.js +262 -0
- package/dist/services/scaffolding/decompose-session-scaffolder.d.ts +74 -0
- package/dist/services/scaffolding/decompose-session-scaffolder.js +315 -0
- package/dist/services/scaffolding/file-scaffolder.d.ts +94 -0
- package/dist/services/scaffolding/file-scaffolder.js +314 -0
- package/dist/services/validation/config-validator.d.ts +88 -0
- package/dist/services/validation/config-validator.js +167 -0
- package/dist/types/task-graph.d.ts +142 -0
- package/dist/types/task-graph.js +5 -0
- package/dist/utils/colors.d.ts +49 -0
- package/dist/utils/colors.js +50 -0
- package/dist/utils/error-formatter.d.ts +64 -0
- package/dist/utils/error-formatter.js +279 -0
- package/dist/utils/errors.d.ts +170 -0
- package/dist/utils/errors.js +233 -0
- package/dist/utils/formatters.d.ts +84 -0
- package/dist/utils/formatters.js +162 -0
- package/dist/utils/logger.d.ts +63 -0
- package/dist/utils/logger.js +78 -0
- package/dist/utils/progress.d.ts +104 -0
- package/dist/utils/progress.js +161 -0
- package/dist/utils/retry.d.ts +114 -0
- package/dist/utils/retry.js +160 -0
- package/dist/utils/shared-flags.d.ts +28 -0
- package/dist/utils/shared-flags.js +43 -0
- package/package.json +119 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stories Develop Command
|
|
3
|
+
*
|
|
4
|
+
* Executes development workflow for stories matching glob patterns sequentially.
|
|
5
|
+
* Updates story status from Draft to Ready, spawns Claude dev agents, and moves
|
|
6
|
+
* completed stories to QA folder. Optionally runs QA workflow after development.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```bash
|
|
10
|
+
* bmad-workflow stories develop "docs/stories/AUTH-*.md"
|
|
11
|
+
* bmad-workflow stories develop "stories/**-auth-*.md" --interval 60
|
|
12
|
+
* bmad-workflow stories develop "stories/*.md" --reference docs/architecture.md
|
|
13
|
+
* bmad-workflow stories develop "stories/*.md" --qa --qa-retries 2
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { isEpicStory } from '../../models/story.js';
|
|
19
|
+
import { createAgentRunner } from '../../services/agents/agent-runner-factory.js';
|
|
20
|
+
import { FileManager } from '../../services/file-system/file-manager.js';
|
|
21
|
+
import { GlobMatcher } from '../../services/file-system/glob-matcher.js';
|
|
22
|
+
import { PathResolver } from '../../services/file-system/path-resolver.js';
|
|
23
|
+
import { StoryTypeDetector } from '../../services/orchestration/story-type-detector.js';
|
|
24
|
+
import { StoryParserFactory } from '../../services/parsers/story-parser-factory.js';
|
|
25
|
+
import * as colors from '../../utils/colors.js';
|
|
26
|
+
import { createLogger, generateCorrelationId } from '../../utils/logger.js';
|
|
27
|
+
import { createSpinner } from '../../utils/progress.js';
|
|
28
|
+
import { agentFlags } from '../../utils/shared-flags.js';
|
|
29
|
+
/**
|
|
30
|
+
* Agent timeout in milliseconds (30 minutes)
|
|
31
|
+
* Dev agents need sufficient time for comprehensive implementation
|
|
32
|
+
*/
|
|
33
|
+
const DEV_AGENT_TIMEOUT_MS = 1_800_000;
|
|
34
|
+
/**
|
|
35
|
+
* Stories Develop Command
|
|
36
|
+
*
|
|
37
|
+
* Develops stories sequentially using Claude AI dev agents.
|
|
38
|
+
* CRITICAL: No parallel execution - stories are developed one at a time to prevent conflicts.
|
|
39
|
+
*/
|
|
40
|
+
export default class StoriesDevelopCommand extends Command {
|
|
41
|
+
static args = {
|
|
42
|
+
pattern: Args.string({
|
|
43
|
+
description: 'Glob pattern to match story files',
|
|
44
|
+
required: true,
|
|
45
|
+
}),
|
|
46
|
+
};
|
|
47
|
+
static description = 'Execute development workflow for stories matching glob pattern (sequential only)';
|
|
48
|
+
static examples = [
|
|
49
|
+
{
|
|
50
|
+
command: '<%= config.bin %> <%= command.id %> "docs/stories/AUTH-*.md"',
|
|
51
|
+
description: 'Develop all AUTH stories',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
command: '<%= config.bin %> <%= command.id %> "**/*-auth-*.md" --interval 60',
|
|
55
|
+
description: 'Develop stories with 60s countdown between each',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
command: '<%= config.bin %> <%= command.id %> "stories/*.md" --reference docs/architecture.md',
|
|
59
|
+
description: 'Develop stories with architecture reference',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
command: '<%= config.bin %> <%= command.id %> "stories/*.md" --agent qa --task review-story',
|
|
63
|
+
description: 'Use QA agent with custom task instead of dev agent',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
command: '<%= config.bin %> <%= command.id %> "stories/*.md" --qa',
|
|
67
|
+
description: 'Develop stories and automatically run QA workflow after',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
command: '<%= config.bin %> <%= command.id %> "stories/*.md" --qa --qa-retries 3',
|
|
71
|
+
description: 'Develop with QA workflow using 3 fix-forward retries',
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
static flags = {
|
|
75
|
+
...agentFlags,
|
|
76
|
+
interval: Flags.integer({
|
|
77
|
+
default: 30,
|
|
78
|
+
description: 'Seconds to wait between stories',
|
|
79
|
+
}),
|
|
80
|
+
qa: Flags.boolean({
|
|
81
|
+
default: false,
|
|
82
|
+
description: 'Run QA workflow after development completes',
|
|
83
|
+
helpGroup: 'QA Workflow',
|
|
84
|
+
}),
|
|
85
|
+
'qa-prompt': Flags.string({
|
|
86
|
+
description: 'Custom prompt/instructions for QA review phase',
|
|
87
|
+
helpGroup: 'QA Workflow',
|
|
88
|
+
}),
|
|
89
|
+
'qa-retries': Flags.integer({
|
|
90
|
+
default: 2,
|
|
91
|
+
description: 'Maximum QA → Dev fix cycles (only used with --qa)',
|
|
92
|
+
helpGroup: 'QA Workflow',
|
|
93
|
+
}),
|
|
94
|
+
reference: Flags.string({
|
|
95
|
+
description: 'Additional context files for dev agents',
|
|
96
|
+
multiple: true,
|
|
97
|
+
}),
|
|
98
|
+
};
|
|
99
|
+
// Service instances
|
|
100
|
+
agentRunner;
|
|
101
|
+
fileManager;
|
|
102
|
+
globMatcher;
|
|
103
|
+
logger;
|
|
104
|
+
pathResolver;
|
|
105
|
+
storyParserFactory;
|
|
106
|
+
storyTypeDetector;
|
|
107
|
+
/**
|
|
108
|
+
* Run the command
|
|
109
|
+
*/
|
|
110
|
+
async run() {
|
|
111
|
+
const { args, flags } = await this.parse(StoriesDevelopCommand);
|
|
112
|
+
const startTime = Date.now();
|
|
113
|
+
const correlationId = generateCorrelationId();
|
|
114
|
+
// Initialize services with selected provider
|
|
115
|
+
const provider = (flags.provider || 'claude');
|
|
116
|
+
this.initializeServices(provider);
|
|
117
|
+
this.logger.info({
|
|
118
|
+
correlationId,
|
|
119
|
+
flags,
|
|
120
|
+
pattern: args.pattern,
|
|
121
|
+
}, 'Starting stories develop command');
|
|
122
|
+
try {
|
|
123
|
+
// Match story files using glob pattern
|
|
124
|
+
const storyFiles = await this.matchStoryFiles(args.pattern);
|
|
125
|
+
if (storyFiles.length === 0) {
|
|
126
|
+
this.log(colors.warning(`No story files matched pattern: ${args.pattern}`));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
this.log(colors.info(`Found ${storyFiles.length} story file(s) to develop`));
|
|
130
|
+
this.log('');
|
|
131
|
+
// Develop stories sequentially
|
|
132
|
+
const results = await this.developStoriesSequentially(storyFiles, flags);
|
|
133
|
+
// Display summary report
|
|
134
|
+
const duration = Date.now() - startTime;
|
|
135
|
+
this.displaySummaryReport(results, duration);
|
|
136
|
+
this.logger.info({
|
|
137
|
+
correlationId,
|
|
138
|
+
duration,
|
|
139
|
+
failed: results.filter((r) => !r.success).length,
|
|
140
|
+
succeeded: results.filter((r) => r.success).length,
|
|
141
|
+
total: results.length,
|
|
142
|
+
}, 'Stories develop command completed');
|
|
143
|
+
// Exit with error if any stories failed (before QA)
|
|
144
|
+
const failedCount = results.filter((r) => !r.success).length;
|
|
145
|
+
if (failedCount > 0) {
|
|
146
|
+
this.error(colors.error(`${failedCount} story development(s) failed`), { exit: 1 });
|
|
147
|
+
}
|
|
148
|
+
// Run QA workflow if --qa flag is set
|
|
149
|
+
if (flags.qa) {
|
|
150
|
+
const successfulStories = results.filter((r) => r.success && r.qaStoryPath !== '');
|
|
151
|
+
if (successfulStories.length > 0) {
|
|
152
|
+
this.log('');
|
|
153
|
+
this.log(colors.bold('═══════════════════════════════════════════'));
|
|
154
|
+
this.log(colors.bold(' Starting QA Workflow for developed stories'));
|
|
155
|
+
this.log(colors.bold('═══════════════════════════════════════════'));
|
|
156
|
+
this.log('');
|
|
157
|
+
await this.runQaWorkflow(successfulStories, {
|
|
158
|
+
interval: flags.interval,
|
|
159
|
+
maxRetries: flags['qa-retries'],
|
|
160
|
+
qaPrompt: flags['qa-prompt'],
|
|
161
|
+
reference: flags.reference,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
this.log(colors.warning('No successful stories to run QA workflow on'));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
const err = error;
|
|
171
|
+
this.logger.error({ correlationId, error: err }, 'Command failed');
|
|
172
|
+
this.error(colors.error(err.message), { exit: 1 });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Build Claude CLI prompt for story development
|
|
177
|
+
*/
|
|
178
|
+
async buildClaudePrompt(options) {
|
|
179
|
+
const { agent = 'dev', cwd, references, storyContent, storyPath, task = 'develop-story' } = options;
|
|
180
|
+
const agentFile = agent;
|
|
181
|
+
const taskCommand = task;
|
|
182
|
+
let prompt = `@.bmad-core/agents/${agentFile}.md\n\n`;
|
|
183
|
+
// Add working directory instruction if specified
|
|
184
|
+
if (cwd) {
|
|
185
|
+
prompt += `Working directory: ${cwd}\n\n`;
|
|
186
|
+
}
|
|
187
|
+
prompt += `*${taskCommand} ${storyPath}\n\n`;
|
|
188
|
+
// Detect story type and auto-include relevant documentation
|
|
189
|
+
const detection = this.storyTypeDetector.detectStoryType(storyContent);
|
|
190
|
+
const autoReferences = this.storyTypeDetector.getDocumentationReferences(detection.type);
|
|
191
|
+
this.logger.info({
|
|
192
|
+
autoReferencesCount: autoReferences.length,
|
|
193
|
+
confidence: detection.confidence,
|
|
194
|
+
matchedKeywordsCount: detection.matchedKeywords.length,
|
|
195
|
+
storyType: detection.type,
|
|
196
|
+
}, 'Story type detected, auto-including documentation references');
|
|
197
|
+
// Combine auto-detected references with user-provided references
|
|
198
|
+
const allReferences = [...(references || [])];
|
|
199
|
+
if (allReferences.length > 0) {
|
|
200
|
+
prompt += 'References:\n';
|
|
201
|
+
for (const ref of allReferences) {
|
|
202
|
+
const resolvedPath = path.resolve(ref);
|
|
203
|
+
prompt += `@${resolvedPath}\n`;
|
|
204
|
+
}
|
|
205
|
+
prompt += '\n';
|
|
206
|
+
}
|
|
207
|
+
// Log which references were included
|
|
208
|
+
if (autoReferences.length > 0) {
|
|
209
|
+
this.log(colors.info(` Auto-included ${autoReferences.length} ${detection.type} documentation reference(s)`));
|
|
210
|
+
}
|
|
211
|
+
prompt += '*yolo mode* always update the story file as you go\n';
|
|
212
|
+
return prompt;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Develop stories sequentially (CRITICAL: No parallelism)
|
|
216
|
+
*/
|
|
217
|
+
async developStoriesSequentially(storyFiles, flags) {
|
|
218
|
+
const results = [];
|
|
219
|
+
const total = storyFiles.length;
|
|
220
|
+
/* eslint-disable no-await-in-loop */
|
|
221
|
+
// Sequential loop - DO NOT use Promise.all - await in loop is REQUIRED here
|
|
222
|
+
for (let index = 0; index < storyFiles.length; index++) {
|
|
223
|
+
const storyPath = storyFiles[index];
|
|
224
|
+
const storyNum = index + 1;
|
|
225
|
+
this.log(colors.bold(`\n[${storyNum}/${total}] Developing: ${path.basename(storyPath)}`));
|
|
226
|
+
// Parse story metadata
|
|
227
|
+
const spinner = createSpinner('Parsing story metadata...');
|
|
228
|
+
spinner.start();
|
|
229
|
+
let storyMetadata;
|
|
230
|
+
try {
|
|
231
|
+
storyMetadata = await this.storyParserFactory.parseStory(storyPath);
|
|
232
|
+
const storyId = storyMetadata.id;
|
|
233
|
+
spinner.succeed(colors.success(`Story ${storyId}: ${storyMetadata.title}`));
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
const err = error;
|
|
237
|
+
spinner.fail(colors.error(`Failed to parse story: ${err.message}`));
|
|
238
|
+
results.push({
|
|
239
|
+
error: `Parse error: ${err.message}`,
|
|
240
|
+
movedToQa: false,
|
|
241
|
+
qaStoryPath: '',
|
|
242
|
+
storyNumber: 'unknown',
|
|
243
|
+
storyPath,
|
|
244
|
+
success: false,
|
|
245
|
+
});
|
|
246
|
+
continue; // Skip to next story
|
|
247
|
+
}
|
|
248
|
+
// Develop story
|
|
249
|
+
const devSpinner = createSpinner('Running dev agent...');
|
|
250
|
+
devSpinner.start();
|
|
251
|
+
const result = await this.developStory({
|
|
252
|
+
agent: flags.agent,
|
|
253
|
+
cwd: flags.cwd,
|
|
254
|
+
references: flags.reference,
|
|
255
|
+
storyMetadata,
|
|
256
|
+
storyPath,
|
|
257
|
+
task: flags.task,
|
|
258
|
+
});
|
|
259
|
+
results.push(result);
|
|
260
|
+
if (result.success) {
|
|
261
|
+
devSpinner.succeed(colors.success(`✓ Development completed, moved to QA`));
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
devSpinner.fail(colors.error(`✗ Development failed: ${result.error}`));
|
|
265
|
+
}
|
|
266
|
+
// Display countdown timer before next story (except for last story)
|
|
267
|
+
if (index < storyFiles.length - 1) {
|
|
268
|
+
await this.displayCountdown(flags.interval);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/* eslint-enable no-await-in-loop */
|
|
272
|
+
return results;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Develop a single story
|
|
276
|
+
*/
|
|
277
|
+
async developStory(options) {
|
|
278
|
+
const { agent, cwd, references, storyMetadata, storyPath, task } = options;
|
|
279
|
+
const storyNumber = isEpicStory(storyMetadata) ? storyMetadata.number : storyMetadata.id;
|
|
280
|
+
this.logger.info({ storyNumber, storyPath }, 'Starting story development');
|
|
281
|
+
try {
|
|
282
|
+
// Step 1: Update status from Draft to Ready if needed
|
|
283
|
+
if (storyMetadata.status === 'Draft') {
|
|
284
|
+
this.logger.info({ storyNumber }, 'Updating story status from Draft to Ready');
|
|
285
|
+
// Use the appropriate parser to update status
|
|
286
|
+
if (isEpicStory(storyMetadata)) {
|
|
287
|
+
const { StoryParser } = await import('../../services/parsers/story-parser.js');
|
|
288
|
+
const parser = new StoryParser(this.fileManager, this.logger);
|
|
289
|
+
await parser.updateStoryStatus(storyPath, 'Ready');
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
const { StandaloneStoryParser } = await import('../../services/parsers/standalone-story-parser.js');
|
|
293
|
+
const parser = new StandaloneStoryParser(this.fileManager, this.logger);
|
|
294
|
+
await parser.updateStoryStatus(storyPath, 'Ready');
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// Step 2: Read story content for type detection
|
|
298
|
+
const storyContent = await this.fileManager.readFile(storyPath);
|
|
299
|
+
// Step 3: Build Claude CLI prompt with auto-detected documentation references
|
|
300
|
+
const prompt = await this.buildClaudePrompt({
|
|
301
|
+
agent,
|
|
302
|
+
cwd,
|
|
303
|
+
references,
|
|
304
|
+
storyContent,
|
|
305
|
+
storyPath,
|
|
306
|
+
task,
|
|
307
|
+
});
|
|
308
|
+
// Step 4: Run Claude dev agent
|
|
309
|
+
this.logger.info({ storyNumber }, 'Running Claude dev agent');
|
|
310
|
+
const result = await this.agentRunner.runAgent(prompt, {
|
|
311
|
+
agentType: 'dev',
|
|
312
|
+
timeout: DEV_AGENT_TIMEOUT_MS,
|
|
313
|
+
});
|
|
314
|
+
if (!result.success) {
|
|
315
|
+
throw new Error(`Dev agent failed: ${result.errors}`);
|
|
316
|
+
}
|
|
317
|
+
// Step 5: Move completed story to QA folder
|
|
318
|
+
const qaStoryDir = this.pathResolver.getQaStoryDir();
|
|
319
|
+
const destPath = path.join(qaStoryDir, path.basename(storyPath));
|
|
320
|
+
this.logger.info({ destPath, storyNumber }, 'Moving completed story to QA folder');
|
|
321
|
+
await this.fileManager.moveFile(storyPath, destPath);
|
|
322
|
+
this.logger.info({ storyNumber, storyPath }, 'Story development completed successfully');
|
|
323
|
+
return {
|
|
324
|
+
movedToQa: true,
|
|
325
|
+
qaStoryPath: destPath,
|
|
326
|
+
storyNumber,
|
|
327
|
+
storyPath,
|
|
328
|
+
success: true,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
const err = error;
|
|
333
|
+
this.logger.error({ error: err, storyNumber, storyPath }, 'Story development failed');
|
|
334
|
+
return {
|
|
335
|
+
error: err.message,
|
|
336
|
+
movedToQa: false,
|
|
337
|
+
qaStoryPath: '',
|
|
338
|
+
storyNumber,
|
|
339
|
+
storyPath,
|
|
340
|
+
success: false,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Display countdown timer between stories
|
|
346
|
+
*/
|
|
347
|
+
async displayCountdown(intervalSeconds) {
|
|
348
|
+
/* eslint-disable no-await-in-loop */
|
|
349
|
+
for (let remaining = intervalSeconds; remaining > 0; remaining--) {
|
|
350
|
+
process.stdout.write(`\r${colors.warning(`⏳ Next story in ${remaining}s...`)}`);
|
|
351
|
+
await this.sleep(1000);
|
|
352
|
+
}
|
|
353
|
+
/* eslint-enable no-await-in-loop */
|
|
354
|
+
process.stdout.write('\r' + ' '.repeat(40) + '\r'); // Clear countdown line
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Display summary report
|
|
358
|
+
*/
|
|
359
|
+
displaySummaryReport(results, duration) {
|
|
360
|
+
const successCount = results.filter((r) => r.success).length;
|
|
361
|
+
const failedCount = results.filter((r) => !r.success).length;
|
|
362
|
+
// Box drawing
|
|
363
|
+
const boxTop = '┌─────────────────────────────────────────┐';
|
|
364
|
+
const boxDivider = '├─────────────────────────────────────────┤';
|
|
365
|
+
const boxBottom = '└─────────────────────────────────────────┘';
|
|
366
|
+
this.log('');
|
|
367
|
+
this.log(boxTop);
|
|
368
|
+
this.log('│ Story Development Summary │');
|
|
369
|
+
this.log(boxDivider);
|
|
370
|
+
this.log(`│ ${colors.success('Succeeded:')} ${successCount.toString().padEnd(20)}│`);
|
|
371
|
+
if (failedCount > 0) {
|
|
372
|
+
this.log(`│ ${colors.error('Failed:')} ${failedCount.toString().padEnd(20)}│`);
|
|
373
|
+
}
|
|
374
|
+
this.log(boxDivider);
|
|
375
|
+
this.log(`│ Duration: ${(duration / 1000).toFixed(2)}s${' '.repeat(15)}│`);
|
|
376
|
+
this.log(boxBottom);
|
|
377
|
+
// List failures if any
|
|
378
|
+
if (failedCount > 0) {
|
|
379
|
+
this.log('');
|
|
380
|
+
this.log(colors.bold('Failed Stories:'));
|
|
381
|
+
for (const result of results.filter((r) => !r.success)) {
|
|
382
|
+
this.log(colors.error(` ✗ ${result.storyNumber}: ${path.basename(result.storyPath)} - ${result.error || 'Unknown error'}`));
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
this.log('');
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Initialize service dependencies
|
|
389
|
+
*/
|
|
390
|
+
initializeServices(provider = 'claude') {
|
|
391
|
+
this.logger = createLogger({ namespace: 'commands:stories:develop' });
|
|
392
|
+
this.logger.info({ provider }, 'Initializing services with AI provider');
|
|
393
|
+
this.fileManager = new FileManager(this.logger);
|
|
394
|
+
this.pathResolver = new PathResolver(this.fileManager, this.logger);
|
|
395
|
+
this.globMatcher = new GlobMatcher(this.fileManager, this.logger);
|
|
396
|
+
this.storyParserFactory = new StoryParserFactory(this.fileManager, this.logger);
|
|
397
|
+
this.storyTypeDetector = new StoryTypeDetector(this.logger);
|
|
398
|
+
this.agentRunner = createAgentRunner(provider, this.logger);
|
|
399
|
+
this.logger.debug({ provider }, 'Services initialized successfully');
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Match story files using glob pattern
|
|
403
|
+
*/
|
|
404
|
+
async matchStoryFiles(pattern) {
|
|
405
|
+
this.logger.info({ pattern }, 'Matching story files with glob pattern');
|
|
406
|
+
// Expand glob pattern
|
|
407
|
+
const matches = await this.globMatcher.expandPattern(pattern);
|
|
408
|
+
this.logger.info({ matchCount: matches.length, pattern }, 'Glob pattern matching complete');
|
|
409
|
+
return matches;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Run QA workflow for successfully developed stories
|
|
413
|
+
* Dynamically imports and delegates to StoriesQaCommand
|
|
414
|
+
*/
|
|
415
|
+
async runQaWorkflow(successfulStories, options) {
|
|
416
|
+
// Dynamically import QA command to avoid circular dependencies
|
|
417
|
+
const { default: StoriesQaCommand } = await import('./qa.js');
|
|
418
|
+
// Create a temporary glob pattern from successful story paths
|
|
419
|
+
// We'll run QA on each story individually to maintain proper tracking
|
|
420
|
+
for (const story of successfulStories) {
|
|
421
|
+
if (story.qaStoryPath === '')
|
|
422
|
+
continue;
|
|
423
|
+
this.log(colors.bold(`\nQA Workflow: ${path.basename(story.qaStoryPath)}`));
|
|
424
|
+
try {
|
|
425
|
+
// Build args for QA command - use the exact path as pattern
|
|
426
|
+
const qaArgs = [story.qaStoryPath];
|
|
427
|
+
// Build flags
|
|
428
|
+
const qaFlags = [`--max-retries=${options.maxRetries}`, `--interval=${options.interval}`];
|
|
429
|
+
if (options.qaPrompt) {
|
|
430
|
+
qaFlags.push(`--qa-prompt=${options.qaPrompt}`);
|
|
431
|
+
}
|
|
432
|
+
if (options.reference && options.reference.length > 0) {
|
|
433
|
+
for (const ref of options.reference) {
|
|
434
|
+
qaFlags.push(`--reference=${ref}`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
// Run QA command (sequential by design - one story at a time)
|
|
438
|
+
// eslint-disable-next-line no-await-in-loop
|
|
439
|
+
await StoriesQaCommand.run([...qaArgs, ...qaFlags]);
|
|
440
|
+
this.log(colors.success(` ✓ QA workflow completed for ${story.storyNumber}`));
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
const err = error;
|
|
444
|
+
this.logger.error({ error: err, storyNumber: story.storyNumber }, 'QA workflow failed for story');
|
|
445
|
+
this.log(colors.error(` ✗ QA workflow failed: ${err.message}`));
|
|
446
|
+
// Continue with other stories even if one fails
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
this.log('');
|
|
450
|
+
this.log(colors.bold('QA Workflow Complete'));
|
|
451
|
+
this.log('');
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Sleep for specified milliseconds
|
|
455
|
+
*/
|
|
456
|
+
async sleep(ms) {
|
|
457
|
+
// eslint-disable-next-line no-promise-executor-return -- Simple setTimeout wrapper
|
|
458
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stories List Command
|
|
3
|
+
*
|
|
4
|
+
* Lists all stories with filtering by status and epic number.
|
|
5
|
+
* Displays results in table format or JSON format with counts summary.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```bash
|
|
9
|
+
* bmad-workflow stories list
|
|
10
|
+
* bmad-workflow stories list --status=draft
|
|
11
|
+
* bmad-workflow stories list --epic=4
|
|
12
|
+
* bmad-workflow stories list --status=ready --epic=4 --json
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
import { Command } from '@oclif/core';
|
|
16
|
+
/**
|
|
17
|
+
* Stories List Command
|
|
18
|
+
*
|
|
19
|
+
* Lists all stories with optional filtering by status and epic number.
|
|
20
|
+
*/
|
|
21
|
+
export default class StoriesListCommand extends Command {
|
|
22
|
+
static description: string;
|
|
23
|
+
static examples: {
|
|
24
|
+
command: string;
|
|
25
|
+
description: string;
|
|
26
|
+
}[];
|
|
27
|
+
static flags: {
|
|
28
|
+
epic: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
29
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
30
|
+
status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
31
|
+
};
|
|
32
|
+
private fileManager;
|
|
33
|
+
private logger;
|
|
34
|
+
private pathResolver;
|
|
35
|
+
private storyParserFactory;
|
|
36
|
+
/**
|
|
37
|
+
* Run command
|
|
38
|
+
*/
|
|
39
|
+
run(): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Apply filters to stories
|
|
42
|
+
*/
|
|
43
|
+
private applyFilters;
|
|
44
|
+
/**
|
|
45
|
+
* Display count summary
|
|
46
|
+
*/
|
|
47
|
+
private displaySummary;
|
|
48
|
+
/**
|
|
49
|
+
* Get color for status
|
|
50
|
+
*/
|
|
51
|
+
private getStatusColor;
|
|
52
|
+
/**
|
|
53
|
+
* Handle errors with user-friendly messages
|
|
54
|
+
*/
|
|
55
|
+
private handleError;
|
|
56
|
+
/**
|
|
57
|
+
* Initialize services
|
|
58
|
+
*/
|
|
59
|
+
private initServices;
|
|
60
|
+
/**
|
|
61
|
+
* Output stories as JSON
|
|
62
|
+
*/
|
|
63
|
+
private outputJson;
|
|
64
|
+
/**
|
|
65
|
+
* Output stories as table
|
|
66
|
+
*/
|
|
67
|
+
private outputTable;
|
|
68
|
+
/**
|
|
69
|
+
* Parse stories from file paths
|
|
70
|
+
*/
|
|
71
|
+
private parseStories;
|
|
72
|
+
/**
|
|
73
|
+
* Sort stories by type and ID
|
|
74
|
+
*
|
|
75
|
+
* Epic stories are sorted by epic.story number ascending.
|
|
76
|
+
* Standalone stories are sorted alphabetically by ID.
|
|
77
|
+
* Epic stories appear before standalone stories.
|
|
78
|
+
*/
|
|
79
|
+
private sortStories;
|
|
80
|
+
/**
|
|
81
|
+
* Truncate string to max length
|
|
82
|
+
*/
|
|
83
|
+
private truncate;
|
|
84
|
+
}
|