@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,10 @@
1
+ /**
2
+ * Orchestration Services
3
+ *
4
+ * Services for orchestrating workflows and batch processing
5
+ */
6
+ export { BatchProcessor } from './batch-processor.js';
7
+ export type { BatchProcessorOptions, BatchResult, ProcessorFunction, ProgressCallback, ProgressInfo, } from './batch-processor.js';
8
+ export { StoryQueue } from './story-queue.js';
9
+ export { WorkflowOrchestrator } from './workflow-orchestrator.js';
10
+ export type { InputDetector } from './workflow-orchestrator.js';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Orchestration Services
3
+ *
4
+ * Services for orchestrating workflows and batch processing
5
+ */
6
+ export { BatchProcessor } from './batch-processor.js';
7
+ export { StoryQueue } from './story-queue.js';
8
+ export { WorkflowOrchestrator } from './workflow-orchestrator.js';
@@ -0,0 +1,125 @@
1
+ /**
2
+ * InputDetector Service
3
+ *
4
+ * Automatically detects whether input is a PRD file, epic file, or story pattern
5
+ * to determine the correct workflow entry point.
6
+ */
7
+ import type pino from 'pino';
8
+ import type { InputDetectionResult } from '../../models/index.js';
9
+ import { FileManager } from '../file-system/file-manager.js';
10
+ /**
11
+ * InputDetector service for automatic input type detection
12
+ *
13
+ * Examines file content and filenames to detect PRD files, epic files,
14
+ * or story patterns, returning a typed detection result with confidence score.
15
+ */
16
+ export declare class InputDetector {
17
+ /**
18
+ * FileManager instance for file system operations
19
+ */
20
+ private readonly fileManager;
21
+ /**
22
+ * Logger instance for detection operations
23
+ */
24
+ private readonly logger;
25
+ /**
26
+ * Create a new InputDetector instance
27
+ *
28
+ * @param fileManager - FileManager instance for file operations
29
+ * @param logger - Pino logger instance for logging detection operations
30
+ * @example
31
+ * const logger = createLogger({ namespace: 'services:orchestration:input-detector' })
32
+ * const fileManager = new FileManager(logger)
33
+ * const detector = new InputDetector(fileManager, logger)
34
+ */
35
+ constructor(fileManager: FileManager, logger: pino.Logger);
36
+ /**
37
+ * Detect input type from file path or pattern (alias for detectInputType)
38
+ *
39
+ * @param input - File path or pattern to analyze
40
+ * @returns Detection result with type, confidence, and metadata
41
+ */
42
+ detect(input: string): Promise<InputDetectionResult>;
43
+ /**
44
+ * Detect input type from file path or pattern
45
+ *
46
+ * Examines the input to determine if it's a PRD file, epic file, or story pattern.
47
+ * Detection order: story pattern → PRD content → epic filename
48
+ *
49
+ * @param input - File path or pattern to analyze
50
+ * @returns Detection result with type, confidence, and metadata
51
+ * @example
52
+ * const result = await detector.detectInputType('docs/prd.md')
53
+ * if (result.type === 'prd') {
54
+ * // Start epic creation phase
55
+ * }
56
+ */
57
+ detectInputType(input: string): Promise<InputDetectionResult>;
58
+ /**
59
+ * Calculate confidence score from detection checks
60
+ *
61
+ * @param checks - Array of detection checks performed
62
+ * @returns Confidence score (0.0 to 1.0)
63
+ * @private
64
+ */
65
+ private calculateConfidence;
66
+ /**
67
+ * Extract epic number from filename
68
+ *
69
+ * @param filename - Filename containing epic number
70
+ * @returns Epic number or undefined if not found
71
+ * @private
72
+ */
73
+ private extractEpicNumber;
74
+ /**
75
+ * Check if filename matches epic pattern
76
+ *
77
+ * @param filename - Filename to analyze (not full path)
78
+ * @returns True if epic pattern matched
79
+ * @private
80
+ */
81
+ private isEpicFile;
82
+ /**
83
+ * Check if file path indicates an epic file
84
+ *
85
+ * @param filePath - Full file path to analyze
86
+ * @returns True if path indicates epic file
87
+ * @private
88
+ */
89
+ private isEpicPath;
90
+ /**
91
+ * Check if content contains PRD markers
92
+ *
93
+ * @param content - File content to analyze
94
+ * @returns True if PRD markers found
95
+ * @private
96
+ */
97
+ private isPrdFile;
98
+ /**
99
+ * Check if filename indicates a PRD file
100
+ *
101
+ * @param filename - Filename to analyze (not full path)
102
+ * @returns True if PRD filename pattern matched
103
+ * @private
104
+ */
105
+ private isPrdFilename;
106
+ /**
107
+ * Check if file path indicates a story file
108
+ *
109
+ * Checks docs/stories, docs/qa/stories, and docs/done/stories to recognize
110
+ * all valid story locations (active, in QA, and completed).
111
+ *
112
+ * @param filePath - Full file path to analyze
113
+ * @returns True if path indicates story file
114
+ * @private
115
+ */
116
+ private isStoryPath;
117
+ /**
118
+ * Check if input is a story pattern (glob wildcards or story directory reference)
119
+ *
120
+ * @param input - Input string to analyze
121
+ * @returns True if story pattern detected
122
+ * @private
123
+ */
124
+ private isStoryPattern;
125
+ }
@@ -0,0 +1,381 @@
1
+ /**
2
+ * InputDetector Service
3
+ *
4
+ * Automatically detects whether input is a PRD file, epic file, or story pattern
5
+ * to determine the correct workflow entry point.
6
+ */
7
+ import { basename } from 'node:path';
8
+ /**
9
+ * InputDetector service for automatic input type detection
10
+ *
11
+ * Examines file content and filenames to detect PRD files, epic files,
12
+ * or story patterns, returning a typed detection result with confidence score.
13
+ */
14
+ export class InputDetector {
15
+ /**
16
+ * FileManager instance for file system operations
17
+ */
18
+ fileManager;
19
+ /**
20
+ * Logger instance for detection operations
21
+ */
22
+ logger;
23
+ /**
24
+ * Create a new InputDetector instance
25
+ *
26
+ * @param fileManager - FileManager instance for file operations
27
+ * @param logger - Pino logger instance for logging detection operations
28
+ * @example
29
+ * const logger = createLogger({ namespace: 'services:orchestration:input-detector' })
30
+ * const fileManager = new FileManager(logger)
31
+ * const detector = new InputDetector(fileManager, logger)
32
+ */
33
+ constructor(fileManager, logger) {
34
+ this.fileManager = fileManager;
35
+ this.logger = logger;
36
+ }
37
+ /**
38
+ * Detect input type from file path or pattern (alias for detectInputType)
39
+ *
40
+ * @param input - File path or pattern to analyze
41
+ * @returns Detection result with type, confidence, and metadata
42
+ */
43
+ async detect(input) {
44
+ return this.detectInputType(input);
45
+ }
46
+ /**
47
+ * Detect input type from file path or pattern
48
+ *
49
+ * Examines the input to determine if it's a PRD file, epic file, or story pattern.
50
+ * Detection order: story pattern → PRD content → epic filename
51
+ *
52
+ * @param input - File path or pattern to analyze
53
+ * @returns Detection result with type, confidence, and metadata
54
+ * @example
55
+ * const result = await detector.detectInputType('docs/prd.md')
56
+ * if (result.type === 'prd') {
57
+ * // Start epic creation phase
58
+ * }
59
+ */
60
+ async detectInputType(input) {
61
+ this.logger.info({ input, strategy: 'multi-strategy' }, 'Starting input type detection');
62
+ const checks = [];
63
+ try {
64
+ // Strategy 1: Check for story pattern (no file system access needed)
65
+ if (this.isStoryPattern(input)) {
66
+ this.logger.info({ input, type: 'story-pattern' }, 'Detected story pattern');
67
+ return {
68
+ confidence: 1,
69
+ filePath: input,
70
+ metadata: {
71
+ markers: ['glob-wildcard'],
72
+ },
73
+ type: 'story-pattern',
74
+ };
75
+ }
76
+ // Strategy 2: Check if file exists
77
+ const exists = await this.fileManager.fileExists(input);
78
+ if (!exists) {
79
+ const error = `File not found: ${input}. Check path and current directory.`;
80
+ this.logger.error({ error, input }, 'File not found during detection');
81
+ return {
82
+ confidence: 0,
83
+ error,
84
+ filePath: input,
85
+ type: null,
86
+ };
87
+ }
88
+ // Strategy 3: Read file content for PRD detection
89
+ const content = await this.fileManager.readFile(input);
90
+ const filename = basename(input);
91
+ // Check PRD markers in content
92
+ const prdContentCheck = this.isPrdFile(content);
93
+ checks.push({
94
+ evidence: prdContentCheck ? 'PRD markers found in content' : undefined,
95
+ name: 'prd-content',
96
+ passed: prdContentCheck,
97
+ weight: 0.8,
98
+ });
99
+ // Check PRD filename pattern (e.g., PRD-feature.md, prd.md)
100
+ const prdFilenameCheck = this.isPrdFilename(filename);
101
+ checks.push({
102
+ evidence: prdFilenameCheck ? `PRD filename pattern: ${filename}` : undefined,
103
+ name: 'prd-filename',
104
+ passed: prdFilenameCheck,
105
+ weight: 0.7,
106
+ });
107
+ // Combined PRD check - either content or filename indicates PRD
108
+ const prdCheck = prdContentCheck || prdFilenameCheck;
109
+ // Check epic filename pattern
110
+ const epicCheck = this.isEpicFile(filename);
111
+ const epicNumber = epicCheck ? this.extractEpicNumber(filename) : undefined;
112
+ checks.push({
113
+ evidence: epicCheck ? `Epic pattern matched: ${filename}` : undefined,
114
+ name: 'epic-filename',
115
+ passed: epicCheck,
116
+ weight: 0.8,
117
+ });
118
+ // Check path-based indicators (strongest signal)
119
+ const epicPathCheck = this.isEpicPath(input);
120
+ if (epicPathCheck) {
121
+ checks.push({
122
+ evidence: `File in epics directory: ${input}`,
123
+ name: 'epic-path',
124
+ passed: true,
125
+ weight: 1,
126
+ });
127
+ }
128
+ const storyPathCheck = this.isStoryPath(input);
129
+ if (storyPathCheck) {
130
+ checks.push({
131
+ evidence: `File in stories directory: ${input}`,
132
+ name: 'story-path',
133
+ passed: true,
134
+ weight: 1,
135
+ });
136
+ }
137
+ // Determine type based on checks
138
+ // Priority order (highest to lowest):
139
+ // 1. Path-based detection (most reliable - user explicitly placed file in typed directory)
140
+ // 2. Content patterns (PRD markers are explicit and should override filename ambiguity)
141
+ // 3. Filename patterns (when no content markers found)
142
+ let detectedType = null;
143
+ if (storyPathCheck) {
144
+ detectedType = 'story-pattern';
145
+ }
146
+ else if (epicPathCheck) {
147
+ detectedType = 'epic';
148
+ }
149
+ else if (prdCheck) {
150
+ // PRD content markers take priority over epic filename patterns
151
+ detectedType = 'prd';
152
+ }
153
+ else if (epicCheck) {
154
+ detectedType = 'epic';
155
+ }
156
+ // Calculate confidence score
157
+ const confidence = this.calculateConfidence(checks);
158
+ const result = {
159
+ confidence,
160
+ filePath: input,
161
+ metadata: {
162
+ epicNumber,
163
+ filename,
164
+ markers: checks.filter((c) => c.passed).map((c) => c.name),
165
+ },
166
+ type: detectedType,
167
+ };
168
+ // Add ambiguity reason for low confidence
169
+ if (confidence < 0.7 && detectedType) {
170
+ result.metadata = {
171
+ ...result.metadata,
172
+ ambiguityReason: 'File exists but no clear type markers found',
173
+ };
174
+ }
175
+ this.logger.info({ confidence, input, metadata: result.metadata, type: detectedType }, 'Detection complete');
176
+ return result;
177
+ }
178
+ catch (error) {
179
+ const err = error;
180
+ const errorMessage = `Failed to detect input type: ${err.message}`;
181
+ this.logger.error({ error: err.message, input, operation: 'detectInputType' }, errorMessage);
182
+ return {
183
+ confidence: 0,
184
+ error: errorMessage,
185
+ filePath: input,
186
+ type: null,
187
+ };
188
+ }
189
+ }
190
+ /**
191
+ * Calculate confidence score from detection checks
192
+ *
193
+ * @param checks - Array of detection checks performed
194
+ * @returns Confidence score (0.0 to 1.0)
195
+ * @private
196
+ */
197
+ calculateConfidence(checks) {
198
+ if (checks.length === 0) {
199
+ return 0.5; // Low confidence for no checks
200
+ }
201
+ const passedChecks = checks.filter((c) => c.passed);
202
+ if (passedChecks.length === 0) {
203
+ return 0.5; // Low confidence - no indicators matched
204
+ }
205
+ if (passedChecks.length === 1) {
206
+ return 0.7; // Medium confidence - single strong indicator
207
+ }
208
+ // High confidence - multiple indicators
209
+ return 1;
210
+ }
211
+ /**
212
+ * Extract epic number from filename
213
+ *
214
+ * @param filename - Filename containing epic number
215
+ * @returns Epic number or undefined if not found
216
+ * @private
217
+ */
218
+ extractEpicNumber(filename) {
219
+ const match = /epic-(\d+)/i.exec(filename);
220
+ if (match?.[1]) {
221
+ const epicNumber = Number.parseInt(match[1], 10);
222
+ this.logger.debug({ epicNumber, filename }, 'Extracted epic number');
223
+ return epicNumber;
224
+ }
225
+ return undefined;
226
+ }
227
+ /**
228
+ * Check if filename matches epic pattern
229
+ *
230
+ * @param filename - Filename to analyze (not full path)
231
+ * @returns True if epic pattern matched
232
+ * @private
233
+ */
234
+ isEpicFile(filename) {
235
+ this.logger.debug({ filename }, 'Checking for epic filename pattern');
236
+ const epicPatterns = [
237
+ /epic-\d+-.+\.md$/i, // epic-1-foundation.md
238
+ /.+epic-\d+.+\.md$/i, // BMAD-epic-1.md
239
+ /^epic-\d+\.md$/i, // epic-3.md
240
+ ];
241
+ for (const pattern of epicPatterns) {
242
+ if (pattern.test(filename)) {
243
+ this.logger.debug({ filename, pattern: pattern.source }, 'Epic filename pattern matched');
244
+ return true;
245
+ }
246
+ }
247
+ this.logger.debug({ filename }, 'No epic filename pattern matched');
248
+ return false;
249
+ }
250
+ /**
251
+ * Check if file path indicates an epic file
252
+ *
253
+ * @param filePath - Full file path to analyze
254
+ * @returns True if path indicates epic file
255
+ * @private
256
+ */
257
+ isEpicPath(filePath) {
258
+ this.logger.debug({ filePath }, 'Checking for epic path pattern');
259
+ // Normalize path separators for cross-platform compatibility
260
+ const normalizedPath = filePath.replaceAll('\\', '/');
261
+ // Check for epics directory in path
262
+ const hasEpicsDir = /\/epics\//i.test(normalizedPath) || normalizedPath.startsWith('epics/');
263
+ if (hasEpicsDir) {
264
+ this.logger.debug({ filePath }, 'Epic path pattern matched: epics directory');
265
+ return true;
266
+ }
267
+ this.logger.debug({ filePath }, 'No epic path pattern matched');
268
+ return false;
269
+ }
270
+ /**
271
+ * Check if content contains PRD markers
272
+ *
273
+ * @param content - File content to analyze
274
+ * @returns True if PRD markers found
275
+ * @private
276
+ */
277
+ isPrdFile(content) {
278
+ this.logger.debug('Checking for PRD markers in content');
279
+ const prdMarkers = [
280
+ /^##\s+Epic\s+List/im, // ## Epic List
281
+ /^##\s+Epics?\s*$/im, // ## Epics or ## Epic (as section header)
282
+ /^#\s+Epics?(?:\s+List)?$/im, // # Epic List, # Epics, # Epics List (but not # Epic N:)
283
+ /^##\s+(?:\d+\.\s+)?Epic\s+\d+:/im, // ## Epic 1: or ## 7. Epic 1:
284
+ /^##\s+\d+\.\s+Epic\s+and\s+Story\s+Structure/im, // ## 6. Epic and Story Structure
285
+ ];
286
+ for (const marker of prdMarkers) {
287
+ if (marker.test(content)) {
288
+ this.logger.debug({ marker: marker.source }, 'PRD marker matched');
289
+ return true;
290
+ }
291
+ }
292
+ this.logger.debug('No PRD markers found');
293
+ return false;
294
+ }
295
+ /**
296
+ * Check if filename indicates a PRD file
297
+ *
298
+ * @param filename - Filename to analyze (not full path)
299
+ * @returns True if PRD filename pattern matched
300
+ * @private
301
+ */
302
+ isPrdFilename(filename) {
303
+ this.logger.debug({ filename }, 'Checking for PRD filename pattern');
304
+ // PRD filename patterns
305
+ const prdPatterns = [
306
+ /^prd[-_]/i, // prd-feature.md, prd_feature.md
307
+ /^prd\.md$/i, // prd.md
308
+ /[-_]prd[-_]/i, // feature-prd-v1.md
309
+ /[-_]prd\.md$/i, // feature-prd.md
310
+ ];
311
+ for (const pattern of prdPatterns) {
312
+ if (pattern.test(filename)) {
313
+ this.logger.debug({ filename, pattern: pattern.source }, 'PRD filename pattern matched');
314
+ return true;
315
+ }
316
+ }
317
+ this.logger.debug({ filename }, 'No PRD filename pattern matched');
318
+ return false;
319
+ }
320
+ /**
321
+ * Check if file path indicates a story file
322
+ *
323
+ * Checks docs/stories, docs/qa/stories, and docs/done/stories to recognize
324
+ * all valid story locations (active, in QA, and completed).
325
+ *
326
+ * @param filePath - Full file path to analyze
327
+ * @returns True if path indicates story file
328
+ * @private
329
+ */
330
+ isStoryPath(filePath) {
331
+ this.logger.debug({ filePath }, 'Checking for story path pattern');
332
+ // Normalize path separators for cross-platform compatibility
333
+ const normalizedPath = filePath.replaceAll('\\', '/');
334
+ // Check for stories directory in any of the three valid locations:
335
+ // - docs/stories (active development)
336
+ // - docs/qa/stories (in QA review)
337
+ // - docs/done/stories (completed)
338
+ const hasStoriesDir = /\/stories\//i.test(normalizedPath) ||
339
+ normalizedPath.startsWith('stories/') ||
340
+ /\/qa\/stories\//i.test(normalizedPath) ||
341
+ normalizedPath.startsWith('qa/stories/') ||
342
+ /\/done\/stories\//i.test(normalizedPath) ||
343
+ normalizedPath.startsWith('done/stories/');
344
+ if (hasStoriesDir) {
345
+ this.logger.debug({ filePath }, 'Story path pattern matched: stories directory');
346
+ return true;
347
+ }
348
+ this.logger.debug({ filePath }, 'No story path pattern matched');
349
+ return false;
350
+ }
351
+ /**
352
+ * Check if input is a story pattern (glob wildcards or story directory reference)
353
+ *
354
+ * @param input - Input string to analyze
355
+ * @returns True if story pattern detected
356
+ * @private
357
+ */
358
+ isStoryPattern(input) {
359
+ this.logger.debug({ input }, 'Checking for story pattern indicators');
360
+ // Check for glob wildcards
361
+ const hasWildcards = /[*?[\]]/.test(input);
362
+ if (hasWildcards) {
363
+ this.logger.debug({ input }, 'Story pattern: glob wildcards detected');
364
+ return true;
365
+ }
366
+ // Check for story directory reference
367
+ const hasStoryDirectory = input.includes('stories/');
368
+ if (hasStoryDirectory) {
369
+ this.logger.debug({ input }, 'Story pattern: stories/ directory detected');
370
+ return true;
371
+ }
372
+ // Check for story filename format: N.N-*.md
373
+ const storyFilenamePattern = /\d+\.\d+-.*\.md/;
374
+ if (storyFilenamePattern.test(input)) {
375
+ this.logger.debug({ input }, 'Story pattern: N.N-*.md format detected');
376
+ return true;
377
+ }
378
+ this.logger.debug({ input }, 'No story pattern indicators found');
379
+ return false;
380
+ }
381
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Story Queue
3
+ *
4
+ * Thread-safe queue mechanism for capturing completed stories during the story creation phase
5
+ * and making them available to the development phase. Supports concurrent consumers with
6
+ * async waiting when queue is empty.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import pino from 'pino'
11
+ * import { StoryQueue } from './story-queue.js'
12
+ *
13
+ * const logger = pino()
14
+ * const queue = new StoryQueue(logger)
15
+ *
16
+ * // Producer: enqueue completed stories
17
+ * queue.enqueue(story1)
18
+ * queue.enqueue(story2)
19
+ *
20
+ * // Consumer: dequeue stories for processing
21
+ * const story = await queue.dequeue() // Waits if queue is empty
22
+ * if (story === null) {
23
+ * // Queue is closed and empty
24
+ * return
25
+ * }
26
+ *
27
+ * // When all stories are created, close the queue
28
+ * queue.close()
29
+ * ```
30
+ */
31
+ import type pino from 'pino';
32
+ import type { Story } from '../../models/story.js';
33
+ /**
34
+ * Thread-safe queue for story pipeline coordination.
35
+ *
36
+ * Provides FIFO queueing with async waiting for consumers. Safe for concurrent
37
+ * access in Node.js event loop (no mutex needed due to single-threaded execution).
38
+ */
39
+ export declare class StoryQueue {
40
+ /**
41
+ * Flag indicating no more stories will be added
42
+ */
43
+ private closed;
44
+ /**
45
+ * Structured logger instance
46
+ */
47
+ private readonly logger;
48
+ /**
49
+ * Internal FIFO queue of stories
50
+ */
51
+ private queue;
52
+ /**
53
+ * Array of waiting dequeue promises
54
+ */
55
+ private waiters;
56
+ /**
57
+ * Creates a new StoryQueue instance
58
+ *
59
+ * @param logger - Pino logger for structured logging
60
+ */
61
+ constructor(logger: pino.Logger);
62
+ /**
63
+ * Signals that no more stories will be added to the queue.
64
+ * All waiting dequeue calls will be resolved with null.
65
+ */
66
+ close(): void;
67
+ /**
68
+ * Removes and returns the next story from the queue.
69
+ * If queue is empty and not closed, waits asynchronously for a story to be enqueued.
70
+ * If queue is closed and empty, returns null.
71
+ *
72
+ * @returns Promise that resolves to the next story, or null if queue is closed and empty
73
+ */
74
+ dequeue(): Promise<null | Story>;
75
+ /**
76
+ * Adds a story to the queue
77
+ *
78
+ * @param story - The story to enqueue
79
+ * @throws Error if queue is already closed
80
+ */
81
+ enqueue(story: Story): void;
82
+ /**
83
+ * Gets the number of pending stories in the queue
84
+ *
85
+ * @returns Number of stories waiting to be dequeued
86
+ */
87
+ getPendingCount(): number;
88
+ /**
89
+ * Checks if the queue is empty
90
+ *
91
+ * @returns true if queue has no items, false otherwise
92
+ */
93
+ isEmpty(): boolean;
94
+ }