@girardmedia/bootspring 2.0.18 → 2.0.21
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/bin/bootspring.js +12 -0
- package/cli/generate.js +22 -22
- package/cli/seed.js +24 -24
- package/core/context.js +1 -2
- package/core/task-extractor.js +13 -13
- package/mcp/contracts/mcp-contract.v1.json +1 -1
- package/package.json +1 -1
package/bin/bootspring.js
CHANGED
|
@@ -467,6 +467,18 @@ async function main() {
|
|
|
467
467
|
try {
|
|
468
468
|
const commandModule = require(scriptPath);
|
|
469
469
|
|
|
470
|
+
// Track command usage (silent, best-effort)
|
|
471
|
+
try {
|
|
472
|
+
const telemetry = require('../core/telemetry');
|
|
473
|
+
telemetry.emitEvent('cli.command', {
|
|
474
|
+
command,
|
|
475
|
+
subcommand: commandArgs[0] || null,
|
|
476
|
+
hasFlags: commandArgs.some(arg => arg.startsWith('-'))
|
|
477
|
+
});
|
|
478
|
+
} catch {
|
|
479
|
+
// Telemetry is optional - don't fail commands if it errors
|
|
480
|
+
}
|
|
481
|
+
|
|
470
482
|
// Execute command
|
|
471
483
|
if (typeof commandModule.run === 'function') {
|
|
472
484
|
await commandModule.run(commandArgs);
|
package/cli/generate.js
CHANGED
|
@@ -218,6 +218,28 @@ ${utils.COLORS.dim}Regenerating AI context...${utils.COLORS.reset}
|
|
|
218
218
|
return;
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
+
// Generate todo.md if missing (required for 100% score)
|
|
222
|
+
const todoPath = path.join(cfg._projectRoot, cfg.paths?.todo || 'todo.md');
|
|
223
|
+
if (!utils.fileExists(todoPath)) {
|
|
224
|
+
const todoSpinner = utils.createSpinner('Generating todo.md').start();
|
|
225
|
+
const todoContent = `# ${cfg.project.name} - Todo List
|
|
226
|
+
|
|
227
|
+
> Last updated: ${utils.formatDate()}
|
|
228
|
+
|
|
229
|
+
## In Progress
|
|
230
|
+
|
|
231
|
+
## Pending
|
|
232
|
+
|
|
233
|
+
- [ ] Review generated context
|
|
234
|
+
- [ ] Configure environment variables
|
|
235
|
+
|
|
236
|
+
## Completed
|
|
237
|
+
|
|
238
|
+
`;
|
|
239
|
+
utils.writeFile(todoPath, todoContent);
|
|
240
|
+
todoSpinner.succeed('Generated todo.md');
|
|
241
|
+
}
|
|
242
|
+
|
|
221
243
|
// Validate context
|
|
222
244
|
const validation = context.validate({ config: cfg });
|
|
223
245
|
|
|
@@ -264,28 +286,6 @@ ${utils.COLORS.bold}Score:${utils.COLORS.reset} ${validation.score}/${validation
|
|
|
264
286
|
mcpSpinner.succeed('Generated .mcp.json');
|
|
265
287
|
}
|
|
266
288
|
|
|
267
|
-
// Generate todo.md if missing
|
|
268
|
-
const todoPath = path.join(cfg._projectRoot, cfg.paths?.todo || 'todo.md');
|
|
269
|
-
if (!utils.fileExists(todoPath)) {
|
|
270
|
-
const todoSpinner = utils.createSpinner('Generating todo.md').start();
|
|
271
|
-
const todoContent = `# ${cfg.project.name} - Todo List
|
|
272
|
-
|
|
273
|
-
> Last updated: ${utils.formatDate()}
|
|
274
|
-
|
|
275
|
-
## In Progress
|
|
276
|
-
|
|
277
|
-
## Pending
|
|
278
|
-
|
|
279
|
-
- [ ] Review generated context
|
|
280
|
-
- [ ] Configure environment variables
|
|
281
|
-
|
|
282
|
-
## Completed
|
|
283
|
-
|
|
284
|
-
`;
|
|
285
|
-
utils.writeFile(todoPath, todoContent);
|
|
286
|
-
todoSpinner.succeed('Generated todo.md');
|
|
287
|
-
}
|
|
288
|
-
|
|
289
289
|
// Generate AGENTS.md for multi-tool compatibility
|
|
290
290
|
const agentsPath = path.join(cfg._projectRoot, 'AGENTS.md');
|
|
291
291
|
const agentsSpinner = utils.createSpinner('Generating AGENTS.md').start();
|
package/cli/seed.js
CHANGED
|
@@ -1116,7 +1116,7 @@ function extractProblem(docs) {
|
|
|
1116
1116
|
// Remove section number prefixes like "2.1 " at start of content
|
|
1117
1117
|
.replace(/^[\d.]+\s+[A-Z][a-z]+\s+[a-z]+\n/m, '')
|
|
1118
1118
|
// Convert bullet points to flowing text
|
|
1119
|
-
.replace(/^[
|
|
1119
|
+
.replace(/^[-*]\s+\*{0,2}([^*\n:]+):?\*{0,2}\s*/gm, '$1: ')
|
|
1120
1120
|
// Remove remaining markdown
|
|
1121
1121
|
.replace(/\*+/g, '')
|
|
1122
1122
|
.trim();
|
|
@@ -1139,7 +1139,7 @@ function extractProblem(docs) {
|
|
|
1139
1139
|
const cleaned = problemSection
|
|
1140
1140
|
.replace(/^#{1,4}\s+[\d.]+\s+[^\n]+\n/gm, '')
|
|
1141
1141
|
.replace(/^[\d.]+\s+[A-Z][a-z]+\s+[a-z]+\n/m, '')
|
|
1142
|
-
.replace(/^[
|
|
1142
|
+
.replace(/^[-*]\s+\*{0,2}([^*\n:]+):?\*{0,2}\s*/gm, '$1: ')
|
|
1143
1143
|
.replace(/\*+/g, '')
|
|
1144
1144
|
.trim();
|
|
1145
1145
|
|
|
@@ -1167,7 +1167,7 @@ function extractSolution(docs) {
|
|
|
1167
1167
|
if (solutionSection) {
|
|
1168
1168
|
const paragraphs = solutionSection.split(/\n\n/).filter(p => p.trim().length > 50);
|
|
1169
1169
|
if (paragraphs.length > 0) {
|
|
1170
|
-
return paragraphs[0].replace(/^[
|
|
1170
|
+
return paragraphs[0].replace(/^[#*\d.-]+\s*/gm, '').replace(/\*+/g, '').trim();
|
|
1171
1171
|
}
|
|
1172
1172
|
}
|
|
1173
1173
|
|
|
@@ -1303,10 +1303,10 @@ function extractMVPFeatures(docs) {
|
|
|
1303
1303
|
if (features.length === 0) {
|
|
1304
1304
|
const journeySection = extractSection(prdDoc, 'Core\\s+User\\s+Journeys|User\\s+Journeys', { maxLength: 3000 });
|
|
1305
1305
|
if (journeySection) {
|
|
1306
|
-
const journeyHeaders = journeySection.match(/^#{1,4}\s+\d+\.\d+\s+Journey\s+\d+\s*[
|
|
1306
|
+
const journeyHeaders = journeySection.match(/^#{1,4}\s+\d+\.\d+\s+Journey\s+\d+\s*[—-]\s*(.+)$/gm);
|
|
1307
1307
|
if (journeyHeaders) {
|
|
1308
1308
|
journeyHeaders.forEach(h => {
|
|
1309
|
-
const match = h.match(/Journey\s+\d+\s*[
|
|
1309
|
+
const match = h.match(/Journey\s+\d+\s*[—-]\s*(.+)/);
|
|
1310
1310
|
if (match) features.push(match[1].trim());
|
|
1311
1311
|
});
|
|
1312
1312
|
}
|
|
@@ -1329,10 +1329,10 @@ function extractTargetUsers(docs) {
|
|
|
1329
1329
|
|
|
1330
1330
|
// Try AUDIENCE doc first - look for "## X.X Persona PX — Name" pattern
|
|
1331
1331
|
if (audienceDoc) {
|
|
1332
|
-
const personaMatches = audienceDoc.match(/^#{1,4}\s+\d+\.\d+\s+Persona\s+P?\d+\s*[
|
|
1332
|
+
const personaMatches = audienceDoc.match(/^#{1,4}\s+\d+\.\d+\s+Persona\s+P?\d+\s*[—-]\s*(.+)$/gm);
|
|
1333
1333
|
if (personaMatches) {
|
|
1334
1334
|
personaMatches.forEach(m => {
|
|
1335
|
-
const match = m.match(/[
|
|
1335
|
+
const match = m.match(/[—-]\s*(.+)/);
|
|
1336
1336
|
if (match && match[1]) personas.push(match[1].trim());
|
|
1337
1337
|
});
|
|
1338
1338
|
}
|
|
@@ -1353,7 +1353,7 @@ function extractTargetUsers(docs) {
|
|
|
1353
1353
|
const targetSection = extractSection(visionDoc, 'Target\\s+Users|Who\\s+This\\s+Is\\s+For', { maxLength: 2000 });
|
|
1354
1354
|
if (targetSection) {
|
|
1355
1355
|
// Look for bold text patterns like "- **AI Agencies & AI Arbitrage Specialists**"
|
|
1356
|
-
const boldMatches = targetSection.match(/^\s*[
|
|
1356
|
+
const boldMatches = targetSection.match(/^\s*[-*]\s*\*{2}([^*]+)\*{2}/gm);
|
|
1357
1357
|
if (boldMatches) {
|
|
1358
1358
|
boldMatches.forEach(m => {
|
|
1359
1359
|
const match = m.match(/\*{2}([^*]+)\*{2}/);
|
|
@@ -1367,10 +1367,10 @@ function extractTargetUsers(docs) {
|
|
|
1367
1367
|
if (prdDoc && personas.length === 0) {
|
|
1368
1368
|
const personaSection = extractSection(prdDoc, 'Personas|Who\\s+We\\s+Serve', { maxLength: 2000 });
|
|
1369
1369
|
if (personaSection) {
|
|
1370
|
-
const personaMatches = personaSection.match(/^#{1,4}\s+\d+\.\d+\s+Persona\s+[A-Z]\s*[
|
|
1370
|
+
const personaMatches = personaSection.match(/^#{1,4}\s+\d+\.\d+\s+Persona\s+[A-Z]\s*[—-]\s*(.+)$/gm);
|
|
1371
1371
|
if (personaMatches) {
|
|
1372
1372
|
personaMatches.forEach(m => {
|
|
1373
|
-
const match = m.match(/[
|
|
1373
|
+
const match = m.match(/[—-]\s*(.+)/);
|
|
1374
1374
|
if (match && match[1]) personas.push(match[1].trim());
|
|
1375
1375
|
});
|
|
1376
1376
|
}
|
|
@@ -1394,7 +1394,7 @@ function extractDatabaseEntities(docs) {
|
|
|
1394
1394
|
const tableSection = extractSection(techDoc, 'Key\\s+tables|Data\\s+Model|Database\\s+Schema', { maxLength: 3000 });
|
|
1395
1395
|
if (tableSection) {
|
|
1396
1396
|
// Extract backticked table names from bullet points in this specific section
|
|
1397
|
-
const bulletTableMatches = tableSection.match(/^\s*[
|
|
1397
|
+
const bulletTableMatches = tableSection.match(/^\s*[-*]\s*`(\w+)`/gm);
|
|
1398
1398
|
if (bulletTableMatches) {
|
|
1399
1399
|
bulletTableMatches.forEach(m => {
|
|
1400
1400
|
const match = m.match(/`(\w+)`/);
|
|
@@ -1461,11 +1461,11 @@ function extractImplementationPhases(docs) {
|
|
|
1461
1461
|
|
|
1462
1462
|
// Prioritize full phase sections: "## X. Phase N —" (with section number)
|
|
1463
1463
|
// These have the actual deliverables, unlike brief summaries
|
|
1464
|
-
const fullPhaseMatches = roadmapDoc.match(/^##\s+\d+\.\s+Phase\s+\d+\s*[
|
|
1464
|
+
const fullPhaseMatches = roadmapDoc.match(/^##\s+\d+\.\s+Phase\s+\d+\s*[—-]+\s*(.+)$/gm);
|
|
1465
1465
|
|
|
1466
1466
|
if (fullPhaseMatches) {
|
|
1467
1467
|
fullPhaseMatches.forEach(m => {
|
|
1468
|
-
const match = m.match(/Phase\s+(\d+)\s*[
|
|
1468
|
+
const match = m.match(/Phase\s+(\d+)\s*[—-]+\s*(.+)$/);
|
|
1469
1469
|
if (match && match[1] && match[2]) {
|
|
1470
1470
|
const phaseNum = match[1];
|
|
1471
1471
|
if (seenPhases.has(phaseNum)) return;
|
|
@@ -1478,7 +1478,7 @@ function extractImplementationPhases(docs) {
|
|
|
1478
1478
|
const deliverables = [];
|
|
1479
1479
|
|
|
1480
1480
|
// Find the full section for this specific phase (## X. Phase N — until next ## X. Phase)
|
|
1481
|
-
const phaseRegex = new RegExp(`##\\s+\\d+\\.\\s+Phase\\s+${phaseNum}\\s*[
|
|
1481
|
+
const phaseRegex = new RegExp(`##\\s+\\d+\\.\\s+Phase\\s+${phaseNum}\\s*[—-][\\s\\S]*?(?=##\\s+\\d+\\.\\s+Phase\\s+\\d+|$)`, 'i');
|
|
1482
1482
|
const phaseSectionMatch = roadmapDoc.match(phaseRegex);
|
|
1483
1483
|
|
|
1484
1484
|
if (phaseSectionMatch) {
|
|
@@ -1490,10 +1490,10 @@ function extractImplementationPhases(docs) {
|
|
|
1490
1490
|
|
|
1491
1491
|
if (deliverablesMatch) {
|
|
1492
1492
|
// Extract bullet points
|
|
1493
|
-
const bullets = deliverablesMatch[0].match(/^[
|
|
1493
|
+
const bullets = deliverablesMatch[0].match(/^[-*]\s+(.+)$/gm);
|
|
1494
1494
|
if (bullets) {
|
|
1495
1495
|
bullets.slice(0, 6).forEach(b => {
|
|
1496
|
-
const text = b.replace(/^[
|
|
1496
|
+
const text = b.replace(/^[-*]\s+/, '').trim();
|
|
1497
1497
|
if (text.length > 3) deliverables.push(text);
|
|
1498
1498
|
});
|
|
1499
1499
|
}
|
|
@@ -1521,10 +1521,10 @@ function extractImplementationPhases(docs) {
|
|
|
1521
1521
|
const exitMatch = phaseContent.match(exitCriteriaRegex);
|
|
1522
1522
|
if (exitMatch) {
|
|
1523
1523
|
// Extract checkmark items like "- ✅ Description"
|
|
1524
|
-
const checkItems = exitMatch[0].match(/^[
|
|
1524
|
+
const checkItems = exitMatch[0].match(/^[-*]\s+[✅✓]\s*(.+)$/gm);
|
|
1525
1525
|
if (checkItems) {
|
|
1526
1526
|
checkItems.slice(0, 5).forEach(item => {
|
|
1527
|
-
const text = item.replace(/^[
|
|
1527
|
+
const text = item.replace(/^[-*]\s+[✅✓]\s*/, '').trim();
|
|
1528
1528
|
if (text.length > 5) deliverables.push(text);
|
|
1529
1529
|
});
|
|
1530
1530
|
}
|
|
@@ -1537,10 +1537,10 @@ function extractImplementationPhases(docs) {
|
|
|
1537
1537
|
const goalMatch = phaseContent.match(goalRegex);
|
|
1538
1538
|
if (goalMatch) {
|
|
1539
1539
|
// Extract bullet points or bold items
|
|
1540
|
-
const bullets = goalMatch[0].match(/^[
|
|
1540
|
+
const bullets = goalMatch[0].match(/^[-*]\s+(.+)$/gm);
|
|
1541
1541
|
if (bullets) {
|
|
1542
1542
|
bullets.slice(0, 4).forEach(b => {
|
|
1543
|
-
const text = b.replace(/^[
|
|
1543
|
+
const text = b.replace(/^[-*]\s+/, '').trim();
|
|
1544
1544
|
if (text.length > 3) deliverables.push(text);
|
|
1545
1545
|
});
|
|
1546
1546
|
}
|
|
@@ -1639,10 +1639,10 @@ function extractAPIRoutes(docs) {
|
|
|
1639
1639
|
|
|
1640
1640
|
if (appStructureSection) {
|
|
1641
1641
|
// Extract page routes like "- `/dashboard`" or "- `/agents`" or "- `/agents/[id]`"
|
|
1642
|
-
const pageRoutes = appStructureSection.match(/[-*]\s*`\/([\w
|
|
1642
|
+
const pageRoutes = appStructureSection.match(/[-*]\s*`\/([\w/[\]]+)`/g);
|
|
1643
1643
|
if (pageRoutes) {
|
|
1644
1644
|
pageRoutes.forEach(r => {
|
|
1645
|
-
const match = r.match(/`(\/[\w
|
|
1645
|
+
const match = r.match(/`(\/[\w/[\]]+)`/);
|
|
1646
1646
|
if (match && match[1]) {
|
|
1647
1647
|
const route = match[1];
|
|
1648
1648
|
// Skip if it looks like a citation URL
|
|
@@ -1673,7 +1673,7 @@ function extractAPIRoutes(docs) {
|
|
|
1673
1673
|
}
|
|
1674
1674
|
|
|
1675
1675
|
// Also look for explicit /api/ routes in backticks
|
|
1676
|
-
const explicitRoutes = techDoc.match(/`\/api\/[\w
|
|
1676
|
+
const explicitRoutes = techDoc.match(/`\/api\/[\w/[\]-]+`/g);
|
|
1677
1677
|
if (explicitRoutes) {
|
|
1678
1678
|
explicitRoutes.forEach(r => {
|
|
1679
1679
|
const route = r.replace(/`/g, '');
|
|
@@ -1735,7 +1735,7 @@ function extractArchitecture(docs) {
|
|
|
1735
1735
|
const archSection = extractSection(techDoc, 'Architecture|System\\s+Components', { maxLength: 2000 });
|
|
1736
1736
|
if (archSection) {
|
|
1737
1737
|
// Look for bold component names like "- **Component Name**"
|
|
1738
|
-
const boldMatches = archSection.match(/^\s*[
|
|
1738
|
+
const boldMatches = archSection.match(/^\s*[-*]\s+\*{2}([^*]+)\*{2}/gm);
|
|
1739
1739
|
if (boldMatches) {
|
|
1740
1740
|
boldMatches.forEach(m => {
|
|
1741
1741
|
const match = m.match(/\*{2}([^*]+)\*{2}/);
|
package/core/context.js
CHANGED
|
@@ -251,8 +251,7 @@ function validate(options = {}) {
|
|
|
251
251
|
checks.push({ name: 'Todo Tracking', status: 'pass', message: 'todo.md exists' });
|
|
252
252
|
score += 1;
|
|
253
253
|
} else {
|
|
254
|
-
checks.push({ name: 'Todo Tracking', status: '
|
|
255
|
-
score += 0.5;
|
|
254
|
+
checks.push({ name: 'Todo Tracking', status: 'fail', message: 'todo.md not found' });
|
|
256
255
|
}
|
|
257
256
|
|
|
258
257
|
return {
|
package/core/task-extractor.js
CHANGED
|
@@ -157,7 +157,7 @@ function extractFromPrd(content) {
|
|
|
157
157
|
// Extract from Core User Journeys
|
|
158
158
|
const journeySection = extractSection(content, 'Core\\s+User\\s+Journeys|User\\s+Journeys', { maxLength: 5000 });
|
|
159
159
|
if (journeySection) {
|
|
160
|
-
const journeyMatches = journeySection.matchAll(/^#{1,4}\s+\d+\.\d+\s+Journey\s+\d+\s*[
|
|
160
|
+
const journeyMatches = journeySection.matchAll(/^#{1,4}\s+\d+\.\d+\s+Journey\s+\d+\s*[—-]\s*(.+)$/gm);
|
|
161
161
|
for (const match of journeyMatches) {
|
|
162
162
|
const journeyName = match[1].trim();
|
|
163
163
|
|
|
@@ -189,7 +189,7 @@ function extractFromRoadmap(content) {
|
|
|
189
189
|
const seenPhases = new Set();
|
|
190
190
|
|
|
191
191
|
// Extract phase headers: "## X. Phase N — Name"
|
|
192
|
-
const phaseMatches = content.matchAll(/^##\s+\d+\.\s+Phase\s+(\d+)\s*[
|
|
192
|
+
const phaseMatches = content.matchAll(/^##\s+\d+\.\s+Phase\s+(\d+)\s*[—-]+\s*(.+)$/gm);
|
|
193
193
|
|
|
194
194
|
for (const match of phaseMatches) {
|
|
195
195
|
const phaseNum = match[1];
|
|
@@ -205,7 +205,7 @@ function extractFromRoadmap(content) {
|
|
|
205
205
|
|
|
206
206
|
// Extract deliverables for this phase
|
|
207
207
|
const phaseRegex = new RegExp(
|
|
208
|
-
`##\\s+\\d+\\.\\s+Phase\\s+${phaseNum}\\s*[
|
|
208
|
+
`##\\s+\\d+\\.\\s+Phase\\s+${phaseNum}\\s*[—-][\\s\\S]*?(?=##\\s+\\d+\\.\\s+Phase\\s+\\d+|$)`,
|
|
209
209
|
'i'
|
|
210
210
|
);
|
|
211
211
|
const phaseSectionMatch = content.match(phaseRegex);
|
|
@@ -219,10 +219,10 @@ function extractFromRoadmap(content) {
|
|
|
219
219
|
const deliverablesMatch = phaseContent.match(deliverablesRegex);
|
|
220
220
|
|
|
221
221
|
if (deliverablesMatch) {
|
|
222
|
-
const bullets = deliverablesMatch[0].match(/^[
|
|
222
|
+
const bullets = deliverablesMatch[0].match(/^[-*]\s+(.+)$/gm);
|
|
223
223
|
if (bullets) {
|
|
224
224
|
bullets.slice(0, 8).forEach(b => {
|
|
225
|
-
const text = b.replace(/^[
|
|
225
|
+
const text = b.replace(/^[-*]\s+/, '').trim();
|
|
226
226
|
if (text.length > 3) {
|
|
227
227
|
deliverables.push(text);
|
|
228
228
|
|
|
@@ -269,10 +269,10 @@ function extractFromRoadmap(content) {
|
|
|
269
269
|
const exitCriteriaRegex = /###\s+[\d.]+\s*Exit\s+Criteria[\s\S]*?(?=###|##|$)/i;
|
|
270
270
|
const exitMatch = phaseContent.match(exitCriteriaRegex);
|
|
271
271
|
if (exitMatch) {
|
|
272
|
-
const checkItems = exitMatch[0].match(/^[
|
|
272
|
+
const checkItems = exitMatch[0].match(/^[-*]\s+[✅✓⬜]\s*(.+)$/gm);
|
|
273
273
|
if (checkItems) {
|
|
274
274
|
checkItems.forEach(item => {
|
|
275
|
-
const text = item.replace(/^[
|
|
275
|
+
const text = item.replace(/^[-*]\s+[✅✓⬜]\s*/, '').trim();
|
|
276
276
|
if (text.length > 5 && !deliverables.includes(text)) {
|
|
277
277
|
deliverables.push(text);
|
|
278
278
|
}
|
|
@@ -304,7 +304,7 @@ function extractFromTechnicalSpec(content) {
|
|
|
304
304
|
const dbSection = extractSection(content, 'Data\\s+Model|Database|Key\\s+tables', { maxLength: 3000 });
|
|
305
305
|
if (dbSection) {
|
|
306
306
|
// Look for table names
|
|
307
|
-
const tableMatches = dbSection.match(/^\s*[
|
|
307
|
+
const tableMatches = dbSection.match(/^\s*[-*]\s*`(\w+)`/gm);
|
|
308
308
|
if (tableMatches && tableMatches.length > 0) {
|
|
309
309
|
tasks.push({
|
|
310
310
|
title: 'Set up database schema with Prisma',
|
|
@@ -328,7 +328,7 @@ function extractFromTechnicalSpec(content) {
|
|
|
328
328
|
if (apiSection) {
|
|
329
329
|
// Group related API routes
|
|
330
330
|
const apiGroups = new Map();
|
|
331
|
-
const apiMatches = apiSection.matchAll(/`\/api\/([
|
|
331
|
+
const apiMatches = apiSection.matchAll(/`\/api\/([^`/]+)/g);
|
|
332
332
|
|
|
333
333
|
for (const match of apiMatches) {
|
|
334
334
|
const group = match[1];
|
|
@@ -446,10 +446,10 @@ function extractMvpCriteria(content) {
|
|
|
446
446
|
const successSection = extractSection(content, 'MVP\\s+Success|Success\\s+Criteria|Definition\\s+of\\s+Done', { maxLength: 3000 });
|
|
447
447
|
|
|
448
448
|
if (successSection) {
|
|
449
|
-
const bulletMatches = successSection.match(/^[
|
|
449
|
+
const bulletMatches = successSection.match(/^[-*]\s+(.+)$/gm);
|
|
450
450
|
if (bulletMatches) {
|
|
451
451
|
bulletMatches.forEach(b => {
|
|
452
|
-
const text = b.replace(/^[
|
|
452
|
+
const text = b.replace(/^[-*]\s+/, '').replace(/\*+/g, '').trim();
|
|
453
453
|
if (text.length > 5) {
|
|
454
454
|
criteria.push({
|
|
455
455
|
name: text,
|
|
@@ -572,10 +572,10 @@ function extractAcceptanceCriteria(content, featureId) {
|
|
|
572
572
|
const featureContent = featureMatch[0];
|
|
573
573
|
|
|
574
574
|
// Look for bullet points
|
|
575
|
-
const bulletMatches = featureContent.match(/^[
|
|
575
|
+
const bulletMatches = featureContent.match(/^[-*]\s+(.+)$/gm);
|
|
576
576
|
if (bulletMatches) {
|
|
577
577
|
bulletMatches.slice(0, 5).forEach(b => {
|
|
578
|
-
const text = b.replace(/^[
|
|
578
|
+
const text = b.replace(/^[-*]\s+/, '').replace(/\*+/g, '').trim();
|
|
579
579
|
if (text.length > 5 && text.length < 200) {
|
|
580
580
|
criteria.push(text);
|
|
581
581
|
}
|
package/package.json
CHANGED