@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.
Files changed (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1017 -0
  3. package/bin/dev +5 -0
  4. package/bin/dev.cmd +3 -0
  5. package/bin/dev.js +5 -0
  6. package/bin/run +5 -0
  7. package/bin/run.cmd +3 -0
  8. package/bin/run.js +5 -0
  9. package/dist/commands/config/show.d.ts +34 -0
  10. package/dist/commands/config/show.js +108 -0
  11. package/dist/commands/config/validate.d.ts +29 -0
  12. package/dist/commands/config/validate.js +131 -0
  13. package/dist/commands/decompose.d.ts +79 -0
  14. package/dist/commands/decompose.js +327 -0
  15. package/dist/commands/demo.d.ts +18 -0
  16. package/dist/commands/demo.js +107 -0
  17. package/dist/commands/epics/create.d.ts +123 -0
  18. package/dist/commands/epics/create.js +459 -0
  19. package/dist/commands/epics/list.d.ts +120 -0
  20. package/dist/commands/epics/list.js +280 -0
  21. package/dist/commands/hello/index.d.ts +12 -0
  22. package/dist/commands/hello/index.js +34 -0
  23. package/dist/commands/hello/world.d.ts +8 -0
  24. package/dist/commands/hello/world.js +24 -0
  25. package/dist/commands/prd/fix.d.ts +39 -0
  26. package/dist/commands/prd/fix.js +140 -0
  27. package/dist/commands/prd/validate.d.ts +112 -0
  28. package/dist/commands/prd/validate.js +302 -0
  29. package/dist/commands/stories/create.d.ts +95 -0
  30. package/dist/commands/stories/create.js +431 -0
  31. package/dist/commands/stories/develop.d.ts +91 -0
  32. package/dist/commands/stories/develop.js +460 -0
  33. package/dist/commands/stories/list.d.ts +84 -0
  34. package/dist/commands/stories/list.js +291 -0
  35. package/dist/commands/stories/move.d.ts +66 -0
  36. package/dist/commands/stories/move.js +273 -0
  37. package/dist/commands/stories/qa.d.ts +99 -0
  38. package/dist/commands/stories/qa.js +530 -0
  39. package/dist/commands/workflow.d.ts +97 -0
  40. package/dist/commands/workflow.js +390 -0
  41. package/dist/index.d.ts +1 -0
  42. package/dist/index.js +1 -0
  43. package/dist/models/agent-options.d.ts +50 -0
  44. package/dist/models/agent-options.js +1 -0
  45. package/dist/models/agent-result.d.ts +29 -0
  46. package/dist/models/agent-result.js +1 -0
  47. package/dist/models/index.d.ts +10 -0
  48. package/dist/models/index.js +10 -0
  49. package/dist/models/phase-result.d.ts +65 -0
  50. package/dist/models/phase-result.js +7 -0
  51. package/dist/models/provider.d.ts +28 -0
  52. package/dist/models/provider.js +18 -0
  53. package/dist/models/story.d.ts +154 -0
  54. package/dist/models/story.js +18 -0
  55. package/dist/models/workflow-config.d.ts +148 -0
  56. package/dist/models/workflow-config.js +1 -0
  57. package/dist/models/workflow-result.d.ts +164 -0
  58. package/dist/models/workflow-result.js +7 -0
  59. package/dist/services/agents/agent-runner-factory.d.ts +31 -0
  60. package/dist/services/agents/agent-runner-factory.js +44 -0
  61. package/dist/services/agents/agent-runner.d.ts +46 -0
  62. package/dist/services/agents/agent-runner.js +29 -0
  63. package/dist/services/agents/claude-agent-runner.d.ts +81 -0
  64. package/dist/services/agents/claude-agent-runner.js +332 -0
  65. package/dist/services/agents/gemini-agent-runner.d.ts +82 -0
  66. package/dist/services/agents/gemini-agent-runner.js +350 -0
  67. package/dist/services/agents/index.d.ts +7 -0
  68. package/dist/services/agents/index.js +7 -0
  69. package/dist/services/file-system/file-manager.d.ts +110 -0
  70. package/dist/services/file-system/file-manager.js +223 -0
  71. package/dist/services/file-system/glob-matcher.d.ts +75 -0
  72. package/dist/services/file-system/glob-matcher.js +126 -0
  73. package/dist/services/file-system/path-resolver.d.ts +183 -0
  74. package/dist/services/file-system/path-resolver.js +400 -0
  75. package/dist/services/logging/workflow-logger.d.ts +232 -0
  76. package/dist/services/logging/workflow-logger.js +552 -0
  77. package/dist/services/orchestration/batch-processor.d.ts +113 -0
  78. package/dist/services/orchestration/batch-processor.js +187 -0
  79. package/dist/services/orchestration/dependency-graph-executor.d.ts +60 -0
  80. package/dist/services/orchestration/dependency-graph-executor.js +447 -0
  81. package/dist/services/orchestration/index.d.ts +10 -0
  82. package/dist/services/orchestration/index.js +8 -0
  83. package/dist/services/orchestration/input-detector.d.ts +125 -0
  84. package/dist/services/orchestration/input-detector.js +381 -0
  85. package/dist/services/orchestration/story-queue.d.ts +94 -0
  86. package/dist/services/orchestration/story-queue.js +170 -0
  87. package/dist/services/orchestration/story-type-detector.d.ts +80 -0
  88. package/dist/services/orchestration/story-type-detector.js +258 -0
  89. package/dist/services/orchestration/task-decomposition-service.d.ts +67 -0
  90. package/dist/services/orchestration/task-decomposition-service.js +607 -0
  91. package/dist/services/orchestration/workflow-orchestrator.d.ts +659 -0
  92. package/dist/services/orchestration/workflow-orchestrator.js +2201 -0
  93. package/dist/services/parsers/epic-parser.d.ts +117 -0
  94. package/dist/services/parsers/epic-parser.js +264 -0
  95. package/dist/services/parsers/prd-fixer.d.ts +86 -0
  96. package/dist/services/parsers/prd-fixer.js +194 -0
  97. package/dist/services/parsers/prd-parser.d.ts +123 -0
  98. package/dist/services/parsers/prd-parser.js +286 -0
  99. package/dist/services/parsers/standalone-story-parser.d.ts +114 -0
  100. package/dist/services/parsers/standalone-story-parser.js +255 -0
  101. package/dist/services/parsers/story-parser-factory.d.ts +81 -0
  102. package/dist/services/parsers/story-parser-factory.js +108 -0
  103. package/dist/services/parsers/story-parser.d.ts +122 -0
  104. package/dist/services/parsers/story-parser.js +262 -0
  105. package/dist/services/scaffolding/decompose-session-scaffolder.d.ts +74 -0
  106. package/dist/services/scaffolding/decompose-session-scaffolder.js +315 -0
  107. package/dist/services/scaffolding/file-scaffolder.d.ts +94 -0
  108. package/dist/services/scaffolding/file-scaffolder.js +314 -0
  109. package/dist/services/validation/config-validator.d.ts +88 -0
  110. package/dist/services/validation/config-validator.js +167 -0
  111. package/dist/types/task-graph.d.ts +142 -0
  112. package/dist/types/task-graph.js +5 -0
  113. package/dist/utils/colors.d.ts +49 -0
  114. package/dist/utils/colors.js +50 -0
  115. package/dist/utils/error-formatter.d.ts +64 -0
  116. package/dist/utils/error-formatter.js +279 -0
  117. package/dist/utils/errors.d.ts +170 -0
  118. package/dist/utils/errors.js +233 -0
  119. package/dist/utils/formatters.d.ts +84 -0
  120. package/dist/utils/formatters.js +162 -0
  121. package/dist/utils/logger.d.ts +63 -0
  122. package/dist/utils/logger.js +78 -0
  123. package/dist/utils/progress.d.ts +104 -0
  124. package/dist/utils/progress.js +161 -0
  125. package/dist/utils/retry.d.ts +114 -0
  126. package/dist/utils/retry.js +160 -0
  127. package/dist/utils/shared-flags.d.ts +28 -0
  128. package/dist/utils/shared-flags.js +43 -0
  129. 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
+ }