@hyperdrive.bot/bmad-workflow 1.0.13 → 1.0.14
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.
|
@@ -1376,9 +1376,15 @@ Write output to: ${outputPath}`;
|
|
|
1376
1376
|
this.logger.warn({ epicNumber: epic.number }, 'Epic file not found, skipping');
|
|
1377
1377
|
return [];
|
|
1378
1378
|
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1379
|
+
try {
|
|
1380
|
+
const epicContent = await this.fileManager.readFile(epicFilePath);
|
|
1381
|
+
const stories = this.epicParser.parseStories(epicContent, epicFilePath);
|
|
1382
|
+
return stories;
|
|
1383
|
+
}
|
|
1384
|
+
catch (error) {
|
|
1385
|
+
this.logger.warn({ epicNumber: epic.number, error: error.message }, 'Failed to parse stories from epic, skipping');
|
|
1386
|
+
return [];
|
|
1387
|
+
}
|
|
1382
1388
|
}));
|
|
1383
1389
|
// Flatten the array of story arrays
|
|
1384
1390
|
allStories.push(...epicStories.flat());
|
|
@@ -137,13 +137,10 @@ export class EpicParser {
|
|
|
137
137
|
this.logger.debug({ matches: matches.length, patternName: pattern.name }, 'Pattern matched stories');
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
|
-
//
|
|
140
|
+
// If no stories found, return empty array (epic may legitimately have no stories)
|
|
141
141
|
if (allMatches.length === 0) {
|
|
142
|
-
this.logger.
|
|
143
|
-
|
|
144
|
-
filePath: epicPath,
|
|
145
|
-
suggestion: 'Ensure epic has story headers like: ### Story 1.1: Initialize Project',
|
|
146
|
-
});
|
|
142
|
+
this.logger.warn({ epicPath }, 'No stories found in epic, returning empty array');
|
|
143
|
+
return [];
|
|
147
144
|
}
|
|
148
145
|
// Sort by position (natural document order)
|
|
149
146
|
allMatches.sort((a, b) => a.position - b.position);
|
|
@@ -53,6 +53,14 @@ export declare class PrdParser {
|
|
|
53
53
|
*
|
|
54
54
|
* CRITICAL: Pattern order matches prd-tmpl.yaml output format
|
|
55
55
|
* Template generates: ## Epic 1 Title (space-separated)
|
|
56
|
+
*
|
|
57
|
+
* Patterns support both H2 (##) and H3 (###) epic headers.
|
|
58
|
+
* PRDs may use H3 for epics when H2 is used for document sections
|
|
59
|
+
* (e.g., ## 6. Epic Details → ### Epic 1: Title).
|
|
60
|
+
*
|
|
61
|
+
* Generic patterns (N: and N.) are only used as fallback when no
|
|
62
|
+
* explicit "Epic N" patterns match — they can false-positive on
|
|
63
|
+
* numbered document sections like "## 1. Goals".
|
|
56
64
|
*/
|
|
57
65
|
private readonly patterns;
|
|
58
66
|
/**
|
|
@@ -32,12 +32,32 @@ export class PrdParser {
|
|
|
32
32
|
*
|
|
33
33
|
* CRITICAL: Pattern order matches prd-tmpl.yaml output format
|
|
34
34
|
* Template generates: ## Epic 1 Title (space-separated)
|
|
35
|
+
*
|
|
36
|
+
* Patterns support both H2 (##) and H3 (###) epic headers.
|
|
37
|
+
* PRDs may use H3 for epics when H2 is used for document sections
|
|
38
|
+
* (e.g., ## 6. Epic Details → ### Epic 1: Title).
|
|
39
|
+
*
|
|
40
|
+
* Generic patterns (N: and N.) are only used as fallback when no
|
|
41
|
+
* explicit "Epic N" patterns match — they can false-positive on
|
|
42
|
+
* numbered document sections like "## 1. Goals".
|
|
35
43
|
*/
|
|
36
44
|
patterns = [
|
|
37
45
|
{
|
|
38
46
|
name: 'Epic N Nested (N. Epic M:)',
|
|
39
47
|
regex: /^##\s+\d+\.\s+Epic\s+(\d+)\s*:\s*(.+?)$/gim,
|
|
40
48
|
},
|
|
49
|
+
{
|
|
50
|
+
name: 'H3 Epic N: Title',
|
|
51
|
+
regex: /^###\s+Epic\s+(\d+)\s*:\s*(.+?)$/gim,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'H3 Epic N - Title',
|
|
55
|
+
regex: /^###\s+Epic\s+(\d+)\s+-\s+(.+?)$/gim,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'H3 Epic N Title (space)',
|
|
59
|
+
regex: /^###\s+Epic\s+(\d+)\s+(?![:-])(.+?)$/gim,
|
|
60
|
+
},
|
|
41
61
|
{
|
|
42
62
|
name: 'Epic N: Title',
|
|
43
63
|
regex: /^##\s+Epic\s+(\d+)\s*:\s*(.+?)$/gim,
|
|
@@ -106,16 +126,27 @@ export class PrdParser {
|
|
|
106
126
|
// Collect matches from all patterns - patterns are ordered by specificity
|
|
107
127
|
// More specific "Epic N" patterns come first in the array
|
|
108
128
|
const allMatches = [];
|
|
129
|
+
let hasExplicitEpicMatches = false;
|
|
109
130
|
for (const pattern of this.patterns) {
|
|
110
131
|
// Skip "N. Title" pattern if nested format found (N. is used for sections)
|
|
111
132
|
if (hasNestedEpicFormat && pattern.name === 'N. Title') {
|
|
112
133
|
this.logger.debug({ patternName: pattern.name }, 'Skipping N. pattern - nested Epic format found in PRD');
|
|
113
134
|
continue;
|
|
114
135
|
}
|
|
136
|
+
// Skip generic number-only patterns when explicit "Epic N" patterns already matched
|
|
137
|
+
// Generic patterns like "## 1. Goals" false-positive on document section headers
|
|
138
|
+
if (hasExplicitEpicMatches && (pattern.name === 'N: Title' || pattern.name === 'N. Title')) {
|
|
139
|
+
this.logger.debug({ patternName: pattern.name }, 'Skipping generic pattern - explicit Epic patterns already matched');
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
115
142
|
const matches = this.matchPattern(pattern, lines, prdContent);
|
|
116
143
|
if (matches.length > 0) {
|
|
117
144
|
allMatches.push(...matches);
|
|
118
145
|
this.logger.debug({ matches: matches.length, patternName: pattern.name }, 'Pattern matched epics');
|
|
146
|
+
// Track if we found explicit "Epic N" patterns (any pattern with "Epic" in the name)
|
|
147
|
+
if (pattern.name.includes('Epic')) {
|
|
148
|
+
hasExplicitEpicMatches = true;
|
|
149
|
+
}
|
|
119
150
|
}
|
|
120
151
|
}
|
|
121
152
|
// Check if any epics were found
|
|
@@ -212,12 +243,8 @@ export class PrdParser {
|
|
|
212
243
|
const position = this.findLineNumber(lines, fullMatch);
|
|
213
244
|
// Extract fullNumber based on pattern type
|
|
214
245
|
let fullNumber;
|
|
215
|
-
if (pattern.name.includes('Nested')) {
|
|
216
|
-
// For
|
|
217
|
-
fullNumber = `Epic ${epicNumber}`;
|
|
218
|
-
}
|
|
219
|
-
else if (pattern.name.startsWith('Epic N')) {
|
|
220
|
-
// For Epic patterns, use "Epic N"
|
|
246
|
+
if (pattern.name.includes('Nested') || pattern.name.includes('Epic N') || pattern.name.includes('H3 Epic')) {
|
|
247
|
+
// For explicit Epic patterns (H2, H3, nested), use "Epic N"
|
|
221
248
|
fullNumber = `Epic ${epicNumber}`;
|
|
222
249
|
}
|
|
223
250
|
else {
|
package/package.json
CHANGED