bmad-dashboard 1.0.14 → 1.0.16
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/package.json +1 -1
- package/server/artifactDetails.js +18 -7
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
|
|
12
12
|
const ARCH_STEP_IDS = [1, 2, 3, 4, 5, 6, 7, 8]
|
|
13
13
|
|
|
14
|
-
/** Match
|
|
14
|
+
/** Match AC line: bold or plain Given/When/Then/And; support - and * bullets */
|
|
15
15
|
const AC_LINE_BOLD = /^[-*]\s*\*\*(Given|When|Then|And)\s*:?\s*(?:\*\*\s*)?(.+)$/
|
|
16
16
|
const AC_LINE_PLAIN = /^[-*]\s*(Given|When|Then|And)\s*:?\s*(.+)$/
|
|
17
17
|
|
|
@@ -20,7 +20,6 @@ function parseStoryBody(body, num, title, statusByKey) {
|
|
|
20
20
|
const rest = (body || '').replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim()
|
|
21
21
|
let userStory = ''
|
|
22
22
|
const acceptanceCriteria = []
|
|
23
|
-
// Multiple possible markers for start of acceptance criteria block (case-insensitive)
|
|
24
23
|
const acMarkers = [
|
|
25
24
|
/\*\*Acceptance Criteria\s*:?\s*\*\*/i,
|
|
26
25
|
/\*\*Acceptance criteria\s*:?\s*\*\*/i,
|
|
@@ -28,6 +27,8 @@ function parseStoryBody(body, num, title, statusByKey) {
|
|
|
28
27
|
/^Acceptance criteria\s*:?\s*$/im,
|
|
29
28
|
/^#+\s*Acceptance Criteria\s*:?\s*$/im,
|
|
30
29
|
/^#+\s*Acceptance criteria\s*:?\s*$/im,
|
|
30
|
+
/^Acceptance Criteria\s*:?\s*\n/im,
|
|
31
|
+
/\bAC\s*:?\s*\n/im,
|
|
31
32
|
]
|
|
32
33
|
let acIndex = -1
|
|
33
34
|
let acBlockStart = 0
|
|
@@ -47,14 +48,18 @@ function parseStoryBody(body, num, title, statusByKey) {
|
|
|
47
48
|
const acBlock = rest.slice(acBlockStart).trim()
|
|
48
49
|
const acLines = acBlock.split(/\n/).map((line) => line.trim()).filter(Boolean)
|
|
49
50
|
for (const line of acLines) {
|
|
50
|
-
if (line.startsWith('###')) break
|
|
51
|
+
if (line.startsWith('###')) break
|
|
51
52
|
let m = line.match(AC_LINE_BOLD)
|
|
52
53
|
if (!m) m = line.match(AC_LINE_PLAIN)
|
|
53
54
|
if (m) acceptanceCriteria.push({ type: m[1], text: m[2].trim() })
|
|
54
55
|
else if ((line.startsWith('- ') || line.startsWith('* ')) && line.length > 2)
|
|
55
56
|
acceptanceCriteria.push({ type: null, text: line.slice(2).trim() })
|
|
57
|
+
else {
|
|
58
|
+
const numBullet = line.match(/^\d+\.\s+(.+)$/)
|
|
59
|
+
if (numBullet && numBullet[1].trim().length > 0)
|
|
60
|
+
acceptanceCriteria.push({ type: null, text: numBullet[1].trim() })
|
|
61
|
+
}
|
|
56
62
|
}
|
|
57
|
-
// If we found AC header but no criteria matched, take any list items in the block
|
|
58
63
|
if (acceptanceCriteria.length === 0 && acBlock.length > 0) {
|
|
59
64
|
for (const line of acLines) {
|
|
60
65
|
if (line.startsWith('###')) break
|
|
@@ -64,18 +69,24 @@ function parseStoryBody(body, num, title, statusByKey) {
|
|
|
64
69
|
}
|
|
65
70
|
}
|
|
66
71
|
}
|
|
67
|
-
// Fallback: no AC header but body has bullet list that looks like criteria (Given/When/Then/And)
|
|
68
72
|
if (acceptanceCriteria.length === 0 && rest) {
|
|
69
73
|
const lines = rest.split(/\n/)
|
|
70
74
|
for (const line of lines) {
|
|
71
75
|
const t = line.trim()
|
|
76
|
+
if (t.startsWith('###')) continue
|
|
72
77
|
let m = t.match(AC_LINE_BOLD)
|
|
73
78
|
if (!m) m = t.match(AC_LINE_PLAIN)
|
|
74
79
|
if (m) acceptanceCriteria.push({ type: m[1], text: m[2].trim() })
|
|
75
80
|
else if ((t.startsWith('- ') || t.startsWith('* ')) && t.length > 2)
|
|
76
81
|
acceptanceCriteria.push({ type: null, text: t.slice(2).trim() })
|
|
82
|
+
else {
|
|
83
|
+
const numBullet = t.match(/^\d+\.\s+(.+)$/)
|
|
84
|
+
if (numBullet && numBullet[1].trim().length > 0)
|
|
85
|
+
acceptanceCriteria.push({ type: null, text: numBullet[1].trim() })
|
|
86
|
+
}
|
|
77
87
|
}
|
|
78
88
|
}
|
|
89
|
+
|
|
79
90
|
if (!userStory && rest) {
|
|
80
91
|
const first = rest.split(/\n\n+/)[0]
|
|
81
92
|
if (first && first.length > 10) userStory = first.replace(/\n/g, ' ').trim()
|
|
@@ -218,9 +229,9 @@ export function getArtifactDetails(projectRootParam, planPath, implPath, sprintD
|
|
|
218
229
|
|
|
219
230
|
for (let i = 0; i < epicTitles.length; i++) {
|
|
220
231
|
const block = epicBlocks[i] || ''
|
|
221
|
-
const storyRe = /^### Story (\d+)\.(\d+)
|
|
232
|
+
const storyRe = /^### Story (\d+)\.(\d+)\s*[:-]\s*(.+)$/gm
|
|
222
233
|
const storyMatches = [...block.matchAll(storyRe)]
|
|
223
|
-
const rawBodies = block.split(/^### Story \d+\.\d
|
|
234
|
+
const rawBodies = block.split(/^### Story \d+\.\d+\s*[:-]\s*/m).slice(1)
|
|
224
235
|
const stories = storyMatches.map((m, j) => {
|
|
225
236
|
const num = `${m[1]}.${m[2]}`
|
|
226
237
|
const title = m[3].trim()
|