@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,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
|
+
}
|