@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,302 @@
1
+ /**
2
+ * PRD Validate Command
3
+ *
4
+ * Validates a PRD file and previews the epic/story extraction that would
5
+ * occur during a workflow run. This is a dry-run inspection tool that:
6
+ * - Parses the PRD to extract epic definitions
7
+ * - Checks for existing epic files
8
+ * - Counts stories in existing epic files
9
+ * - Reports what would be generated by the workflow command
10
+ *
11
+ * @example
12
+ * ```bash
13
+ * # Validate PRD and show extraction preview
14
+ * bmad-workflow prd validate docs/PRD-feature.md
15
+ *
16
+ * # Output as JSON for scripting
17
+ * bmad-workflow prd validate docs/PRD-feature.md --json
18
+ * ```
19
+ */
20
+ import { Args, Command, Flags } from '@oclif/core';
21
+ import Table from 'cli-table3';
22
+ import { relative } from 'node:path';
23
+ import { FileManager } from '../../services/file-system/file-manager.js';
24
+ import { PathResolver } from '../../services/file-system/path-resolver.js';
25
+ import { EpicParser } from '../../services/parsers/epic-parser.js';
26
+ import { PrdParser } from '../../services/parsers/prd-parser.js';
27
+ import * as Colors from '../../utils/colors.js';
28
+ import { createLogger } from '../../utils/logger.js';
29
+ /**
30
+ * PRD Validate Command
31
+ *
32
+ * Parses a PRD file and reports what epics and stories would be
33
+ * extracted by the workflow command.
34
+ */
35
+ export default class PrdValidate extends Command {
36
+ static args = {
37
+ prdFile: Args.string({
38
+ description: 'Path to PRD markdown file to validate',
39
+ required: true,
40
+ }),
41
+ };
42
+ static description = 'Validate PRD and preview epic/story extraction for workflow';
43
+ static examples = [
44
+ '<%= config.bin %> <%= command.id %> docs/PRD-feature.md',
45
+ '<%= config.bin %> <%= command.id %> docs/PRD-feature.md --json',
46
+ '<%= config.bin %> <%= command.id %> ./my-prd.md -j',
47
+ ];
48
+ static flags = {
49
+ json: Flags.boolean({
50
+ char: 'j',
51
+ default: false,
52
+ description: 'Output validation results as JSON',
53
+ }),
54
+ };
55
+ /**
56
+ * EpicParser instance for parsing story content
57
+ */
58
+ epicParser;
59
+ /**
60
+ * FileManager instance for file operations
61
+ */
62
+ fileManager;
63
+ /**
64
+ * Logger instance
65
+ */
66
+ logger = createLogger({ namespace: 'bmad-workflow:commands:prd:validate' });
67
+ /**
68
+ * PathResolver instance for resolving paths
69
+ */
70
+ pathResolver;
71
+ /**
72
+ * PrdParser instance for parsing PRD content
73
+ */
74
+ prdParser;
75
+ /**
76
+ * Run the validation command
77
+ */
78
+ async run() {
79
+ const { args, flags } = await this.parse(PrdValidate);
80
+ // Initialize services
81
+ this.initializeServices();
82
+ try {
83
+ // Step 1: Validate PRD file exists and read content
84
+ this.logger.info({ prdFile: args.prdFile }, 'Starting PRD validation');
85
+ const prdExists = await this.fileManager.fileExists(args.prdFile);
86
+ if (!prdExists) {
87
+ this.error(Colors.error(`PRD file not found: ${args.prdFile}`));
88
+ }
89
+ const prdContent = await this.fileManager.readFile(args.prdFile);
90
+ this.logger.debug({ contentLength: prdContent.length }, 'PRD content loaded');
91
+ // Step 2: Parse epics from PRD
92
+ let epics;
93
+ try {
94
+ epics = this.prdParser.parseEpics(prdContent, args.prdFile);
95
+ }
96
+ catch (error) {
97
+ const err = error;
98
+ this.logger.error({ error: err }, 'Failed to parse epics from PRD');
99
+ this.error(Colors.error(`Failed to parse PRD: ${err.message}`));
100
+ }
101
+ this.logger.info({ epicCount: epics.length }, 'Epics parsed from PRD');
102
+ // Step 3: Validate each epic (check for existing files, count stories)
103
+ const validationResults = await Promise.all(epics.map(async (epic) => this.validateEpic(epic)));
104
+ // Step 4: Calculate summary
105
+ const summary = this.calculateSummary(validationResults);
106
+ // Step 5: Build output
107
+ const output = {
108
+ epics: validationResults,
109
+ prdFile: relative(process.cwd(), args.prdFile) || args.prdFile,
110
+ summary,
111
+ };
112
+ // Step 6: Display results
113
+ if (flags.json) {
114
+ this.outputJson(output);
115
+ }
116
+ else {
117
+ this.outputTable(output);
118
+ }
119
+ }
120
+ catch (error) {
121
+ const err = error;
122
+ this.logger.error({ error: err }, 'PRD validation failed');
123
+ this.error(Colors.error(`Validation failed: ${err.message}`));
124
+ }
125
+ }
126
+ /**
127
+ * Calculate summary statistics from validation results
128
+ *
129
+ * @param results - Array of epic validation results
130
+ * @returns Summary statistics
131
+ */
132
+ calculateSummary(results) {
133
+ const existingEpics = results.filter((r) => r.status === 'exists').length;
134
+ const pendingEpics = results.filter((r) => r.status === 'pending').length;
135
+ const totalStories = results.reduce((sum, r) => sum + r.stories, 0);
136
+ return {
137
+ existingEpics,
138
+ pendingEpics,
139
+ totalEpics: results.length,
140
+ totalStories,
141
+ };
142
+ }
143
+ /**
144
+ * Find epic file matching the epic number
145
+ *
146
+ * Searches the epics directory for files matching pattern:
147
+ * - epic-{N}-*.md
148
+ * - *-epic-{N}.md
149
+ * - *-epic-{N}-*.md
150
+ *
151
+ * @param epicNumber - Epic number to search for
152
+ * @returns Path to epic file if found, null otherwise
153
+ */
154
+ async findEpicFile(epicNumber) {
155
+ try {
156
+ const epicDir = this.pathResolver.getEpicDir();
157
+ const files = await this.fileManager.listFiles(epicDir, '*.md');
158
+ // Look for files matching epic number patterns
159
+ const patterns = [
160
+ new RegExp(`^epic-${epicNumber}-`, 'i'),
161
+ new RegExp(`-epic-${epicNumber}\\.md$`, 'i'),
162
+ new RegExp(`-epic-${epicNumber}-`, 'i'),
163
+ ];
164
+ for (const file of files) {
165
+ const filename = file.split('/').pop() || file;
166
+ for (const pattern of patterns) {
167
+ if (pattern.test(filename)) {
168
+ this.logger.debug({ epicNumber, file }, 'Epic file found');
169
+ return file;
170
+ }
171
+ }
172
+ }
173
+ this.logger.debug({ epicNumber }, 'No epic file found');
174
+ return null;
175
+ }
176
+ catch {
177
+ // Directory might not exist yet
178
+ this.logger.debug({ epicNumber }, 'Epic directory not found or empty');
179
+ return null;
180
+ }
181
+ }
182
+ /**
183
+ * Initialize service instances
184
+ */
185
+ initializeServices() {
186
+ this.fileManager = new FileManager(this.logger);
187
+ this.pathResolver = new PathResolver(this.fileManager, this.logger);
188
+ this.prdParser = new PrdParser(this.logger);
189
+ this.epicParser = new EpicParser(this.logger);
190
+ }
191
+ /**
192
+ * Output validation results as JSON
193
+ *
194
+ * @param output - Complete validation output
195
+ */
196
+ outputJson(output) {
197
+ this.log(JSON.stringify(output, null, 2));
198
+ }
199
+ /**
200
+ * Output validation results as formatted table
201
+ *
202
+ * @param output - Complete validation output
203
+ */
204
+ outputTable(output) {
205
+ // Header
206
+ this.log('');
207
+ this.log(Colors.bold('╔════════════════════════════════════════════════════════════════╗'));
208
+ this.log(Colors.bold('║ PRD Validation Summary ║'));
209
+ this.log(Colors.bold('╚════════════════════════════════════════════════════════════════╝'));
210
+ this.log('');
211
+ this.log(Colors.info(`PRD: ${output.prdFile}`));
212
+ this.log('');
213
+ // Create table
214
+ const table = new Table({
215
+ colWidths: [10, 40, 10, 30],
216
+ head: [
217
+ Colors.bold(Colors.highlight('Epic #')),
218
+ Colors.bold(Colors.highlight('Title')),
219
+ Colors.bold(Colors.highlight('Stories')),
220
+ Colors.bold(Colors.highlight('Status')),
221
+ ],
222
+ wordWrap: true,
223
+ });
224
+ // Add rows
225
+ for (const epic of output.epics) {
226
+ const statusDisplay = epic.status === 'exists' ? Colors.success('✓ Exists') : Colors.warning('○ Pending');
227
+ const storiesDisplay = epic.status === 'exists' ? epic.stories.toString() : Colors.dim('-');
228
+ table.push([epic.number.toString(), this.truncateText(epic.title, 38), storiesDisplay, statusDisplay]);
229
+ }
230
+ this.log(table.toString());
231
+ this.log('');
232
+ // Summary section
233
+ this.log(Colors.bold('Summary:'));
234
+ this.log(` Epics: ${output.summary.totalEpics} total (${output.summary.existingEpics} existing, ${output.summary.pendingEpics} pending)`);
235
+ this.log(` Stories: ${output.summary.totalStories} found in existing epic files`);
236
+ this.log('');
237
+ // Warnings
238
+ if (output.summary.pendingEpics > 0) {
239
+ this.log(Colors.warning(`⚠ ${output.summary.pendingEpics} epic file(s) not found - would be created by workflow`));
240
+ }
241
+ // Success indicator
242
+ if (output.summary.pendingEpics === 0 && output.summary.totalStories > 0) {
243
+ this.log(Colors.success('✓ All epic files exist and contain stories'));
244
+ }
245
+ this.log('');
246
+ }
247
+ /**
248
+ * Truncate text with ellipsis if too long
249
+ *
250
+ * @param text - Text to truncate
251
+ * @param maxLength - Maximum length
252
+ * @returns Truncated text
253
+ */
254
+ truncateText(text, maxLength) {
255
+ if (text.length <= maxLength) {
256
+ return text;
257
+ }
258
+ return text.slice(0, maxLength - 3) + '...';
259
+ }
260
+ /**
261
+ * Validate a single epic
262
+ *
263
+ * Checks if the epic file exists and counts stories if it does.
264
+ *
265
+ * @param epic - Epic information from PRD
266
+ * @returns Validation result for the epic
267
+ */
268
+ async validateEpic(epic) {
269
+ this.logger.debug({ epic }, 'Validating epic');
270
+ // Try to find existing epic file
271
+ const epicFile = await this.findEpicFile(epic.number);
272
+ if (!epicFile) {
273
+ return {
274
+ epicFile: null,
275
+ number: epic.number,
276
+ status: 'pending',
277
+ stories: 0,
278
+ title: epic.title,
279
+ };
280
+ }
281
+ // Epic file exists - try to count stories
282
+ let storyCount = 0;
283
+ try {
284
+ const epicContent = await this.fileManager.readFile(epicFile);
285
+ const stories = this.epicParser.parseStories(epicContent, epicFile);
286
+ storyCount = stories.length;
287
+ this.logger.debug({ epicFile, storyCount }, 'Stories counted in epic');
288
+ }
289
+ catch (error) {
290
+ // Epic file exists but couldn't parse stories (maybe no Stories section yet)
291
+ this.logger.warn({ epicFile, error }, 'Could not parse stories from epic file');
292
+ storyCount = 0;
293
+ }
294
+ return {
295
+ epicFile: relative(process.cwd(), epicFile) || epicFile,
296
+ number: epic.number,
297
+ status: 'exists',
298
+ stories: storyCount,
299
+ title: epic.title,
300
+ };
301
+ }
302
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Stories Create Command
3
+ *
4
+ * Automatically generates story markdown files from an epic using parallel Claude AI agent execution.
5
+ * Creates story files with configurable concurrency and batch processing.
6
+ *
7
+ * @example
8
+ * ```bash
9
+ * bmad-workflow stories create docs/epics/epic-1-foundation.md
10
+ * bmad-workflow stories create epic.md --parallel 10
11
+ * bmad-workflow stories create epic.md --start 3
12
+ * bmad-workflow stories create epic.md --reference docs/architecture.md
13
+ * ```
14
+ */
15
+ import { Command } from '@oclif/core';
16
+ /**
17
+ * Stories Create Command
18
+ *
19
+ * Creates story files from epic using Claude AI agents with parallel processing.
20
+ */
21
+ export default class StoriesCreateCommand extends Command {
22
+ static args: {
23
+ 'epic-path': import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
24
+ };
25
+ static description: string;
26
+ static examples: {
27
+ command: string;
28
+ description: string;
29
+ }[];
30
+ static flags: {
31
+ interval: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
32
+ parallel: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
33
+ prefix: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
34
+ reference: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
35
+ start: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
36
+ agent: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
37
+ cwd: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
38
+ provider: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
39
+ task: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
40
+ };
41
+ private agentRunner;
42
+ private batchProcessor;
43
+ private epicParser;
44
+ private fileManager;
45
+ private fileScaffolder;
46
+ private logger;
47
+ private pathResolver;
48
+ /**
49
+ * Run the command
50
+ */
51
+ run(): Promise<void>;
52
+ /**
53
+ * Build Claude CLI prompt for story creation
54
+ */
55
+ private buildClaudePrompt;
56
+ /**
57
+ * Check for existing story files across all story directories
58
+ *
59
+ * Checks docs/stories, docs/qa/stories, and docs/done/stories to prevent
60
+ * recreating stories that have been moved for QA or marked as done.
61
+ */
62
+ private checkExistingStories;
63
+ /**
64
+ * Create stories in parallel batches
65
+ */
66
+ private createStoriesInParallel;
67
+ /**
68
+ * Create a single story file
69
+ */
70
+ private createStory;
71
+ /**
72
+ * Display summary report
73
+ */
74
+ private displaySummaryReport;
75
+ /**
76
+ * Filter stories by start number
77
+ */
78
+ private filterStoriesByStart;
79
+ /**
80
+ * Generate story filename from story metadata
81
+ */
82
+ private generateStoryFilename;
83
+ /**
84
+ * Initialize service dependencies
85
+ */
86
+ private initializeServices;
87
+ /**
88
+ * Parse epic file and extract stories
89
+ */
90
+ private parseEpicStories;
91
+ /**
92
+ * Validate parallel flag value
93
+ */
94
+ private validateParallelFlag;
95
+ }