@girardmedia/bootspring 2.1.0 → 2.1.1
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/cli/build.js
CHANGED
|
@@ -165,6 +165,10 @@ async function run(args) {
|
|
|
165
165
|
case 'all':
|
|
166
166
|
return buildLoop(projectRoot, parsedArgs);
|
|
167
167
|
|
|
168
|
+
case 'migrate-todo':
|
|
169
|
+
case 'migrate':
|
|
170
|
+
return migrateTodoFormat(projectRoot, parsedArgs);
|
|
171
|
+
|
|
168
172
|
case 'backfill':
|
|
169
173
|
case 'recover':
|
|
170
174
|
case 'hydrate':
|
|
@@ -350,7 +354,7 @@ ${c.bold}Phase:${c.reset} ${nextTask.phase || 'MVP'}
|
|
|
350
354
|
${c.bold}Source:${c.reset} ${nextTask.source || 'PRD.md'}
|
|
351
355
|
|
|
352
356
|
${c.cyan}${c.bold}Tell Claude Code:${c.reset}
|
|
353
|
-
"Read planning/
|
|
357
|
+
"Read planning/TODO.md, find ${nextTask.id}, and implement it"
|
|
354
358
|
|
|
355
359
|
${c.dim}When done, run: bootspring build done${c.reset}
|
|
356
360
|
`);
|
|
@@ -462,7 +466,7 @@ async function markTaskDone(projectRoot, _args = {}) {
|
|
|
462
466
|
console.log(`
|
|
463
467
|
${c.cyan}${c.bold}▶ Next: ${nextTask.title}${c.reset} (${nextTask.id})
|
|
464
468
|
|
|
465
|
-
${c.bold}Continue building:${c.reset} Read planning/
|
|
469
|
+
${c.bold}Continue building:${c.reset} Read planning/TODO.md, implement ${nextTask.id}
|
|
466
470
|
${c.dim}Then run: bootspring build done${c.reset}
|
|
467
471
|
`);
|
|
468
472
|
} else {
|
|
@@ -545,7 +549,7 @@ ${c.bold}Progress:${c.reset} ${stats.completed}/${stats.total} tasks (${Math.rou
|
|
|
545
549
|
${c.bold}Git Mode:${c.reset} ${noCommit ? 'Disabled' : noPush ? 'Commit only' : 'Commit + Push'}
|
|
546
550
|
|
|
547
551
|
${c.bold}Workflow:${c.reset}
|
|
548
|
-
1. Read planning/
|
|
552
|
+
1. Read planning/TODO.md, find ${currentTask?.id || 'current task'}
|
|
549
553
|
2. Implement the task
|
|
550
554
|
3. Run: ${c.cyan}bootspring build done${c.reset}${!noCommit ? `
|
|
551
555
|
(Auto-commits: "feat(${currentTask?.id}): ${currentTask?.title?.slice(0, 40) || 'task'}...")${!noPush ? `
|
|
@@ -1225,6 +1229,77 @@ ${c.bold}Next steps:${c.reset}
|
|
|
1225
1229
|
`);
|
|
1226
1230
|
}
|
|
1227
1231
|
|
|
1232
|
+
/**
|
|
1233
|
+
* Migrate from TASK_QUEUE.md to TODO.md as single source of truth.
|
|
1234
|
+
*/
|
|
1235
|
+
async function migrateTodoFormat(projectRoot, args = {}) {
|
|
1236
|
+
const planningDir = path.join(projectRoot, 'planning');
|
|
1237
|
+
const queuePath = path.join(planningDir, 'TASK_QUEUE.md');
|
|
1238
|
+
const todoPath = path.join(planningDir, 'TODO.md');
|
|
1239
|
+
const keepQueue = Boolean(args['keep-queue'] || args.keepQueue);
|
|
1240
|
+
|
|
1241
|
+
console.log(`
|
|
1242
|
+
${c.cyan}${c.bold}Migrating to TODO.md as Single Source of Truth${c.reset}
|
|
1243
|
+
`);
|
|
1244
|
+
|
|
1245
|
+
// Step 1: Ensure we have build state with tasks
|
|
1246
|
+
let state = buildState.load(projectRoot);
|
|
1247
|
+
|
|
1248
|
+
if (!state || !state.implementationQueue || state.implementationQueue.length === 0) {
|
|
1249
|
+
if (fs.existsSync(queuePath)) {
|
|
1250
|
+
console.log(`${c.dim}No build state — syncing tasks from TASK_QUEUE.md first...${c.reset}`);
|
|
1251
|
+
try {
|
|
1252
|
+
const taskExtractor = require('../core/task-extractor');
|
|
1253
|
+
const result = taskExtractor.syncFromTaskQueue(projectRoot);
|
|
1254
|
+
if (result.total > 0) {
|
|
1255
|
+
console.log(`${c.green}✓${c.reset} Synced ${result.total} tasks from TASK_QUEUE.md`);
|
|
1256
|
+
state = buildState.load(projectRoot);
|
|
1257
|
+
}
|
|
1258
|
+
} catch {
|
|
1259
|
+
// Fall through
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
if (!state || !state.implementationQueue || state.implementationQueue.length === 0) {
|
|
1264
|
+
console.log(`${c.red}No tasks found. Run 'bootspring seed build' first or ensure TASK_QUEUE.md exists.${c.reset}`);
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// Step 2: Generate enriched TODO.md with full acceptance criteria
|
|
1270
|
+
const planningTemplate = require('../generators/templates/build-planning.template');
|
|
1271
|
+
const todo = planningTemplate.generateTodo(state.implementationQueue, {
|
|
1272
|
+
projectName: state.projectName
|
|
1273
|
+
});
|
|
1274
|
+
fs.writeFileSync(todoPath, todo);
|
|
1275
|
+
|
|
1276
|
+
const stats = buildState.getStats(projectRoot);
|
|
1277
|
+
console.log(`${c.green}✓${c.reset} Generated TODO.md with ${state.implementationQueue.length} tasks (${stats?.completed || 0} completed)`);
|
|
1278
|
+
|
|
1279
|
+
// Step 3: Archive TASK_QUEUE.md unless --keep-queue
|
|
1280
|
+
if (fs.existsSync(queuePath) && !keepQueue) {
|
|
1281
|
+
const archivePath = path.join(planningDir, 'TASK_QUEUE.md.bak');
|
|
1282
|
+
fs.renameSync(queuePath, archivePath);
|
|
1283
|
+
console.log(`${c.green}✓${c.reset} Archived TASK_QUEUE.md → TASK_QUEUE.md.bak`);
|
|
1284
|
+
} else if (fs.existsSync(queuePath) && keepQueue) {
|
|
1285
|
+
console.log(`${c.dim}Kept TASK_QUEUE.md (--keep-queue)${c.reset}`);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
console.log(`
|
|
1289
|
+
${c.green}${c.bold}Migration complete${c.reset}
|
|
1290
|
+
|
|
1291
|
+
${c.bold}What changed:${c.reset}
|
|
1292
|
+
- ${c.cyan}planning/TODO.md${c.reset} is now the single source of truth
|
|
1293
|
+
- Task IDs, acceptance criteria, and status are all inline
|
|
1294
|
+
- Build loop reads TODO.md instead of TASK_QUEUE.md
|
|
1295
|
+
${!keepQueue && fs.existsSync(path.join(planningDir, 'TASK_QUEUE.md.bak')) ? ` - Old TASK_QUEUE.md archived as TASK_QUEUE.md.bak
|
|
1296
|
+
` : ''}
|
|
1297
|
+
${c.bold}Next steps:${c.reset}
|
|
1298
|
+
Run ${c.cyan}bootspring build status${c.reset} to verify
|
|
1299
|
+
Run ${c.cyan}bootspring build next${c.reset} to continue building
|
|
1300
|
+
`);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1228
1303
|
/**
|
|
1229
1304
|
* Show help
|
|
1230
1305
|
*/
|
|
@@ -1252,6 +1327,7 @@ ${c.bold}Commands:${c.reset}
|
|
|
1252
1327
|
${c.cyan}bootspring build status${c.reset} View detailed progress
|
|
1253
1328
|
${c.cyan}bootspring build task${c.reset} View current task details
|
|
1254
1329
|
${c.cyan}bootspring build plan${c.reset} View the full master plan
|
|
1330
|
+
${c.cyan}bootspring build migrate-todo${c.reset} Migrate TASK_QUEUE.md → TODO.md (single source of truth)
|
|
1255
1331
|
${c.cyan}bootspring build backfill${c.reset} Recover preseed + SEED + TASK_QUEUE artifacts
|
|
1256
1332
|
${c.cyan}bootspring build reset${c.reset} Start over
|
|
1257
1333
|
|
|
@@ -52,7 +52,7 @@ function generate(config) {
|
|
|
52
52
|
sections.push('');
|
|
53
53
|
sections.push('This loops through all tasks:');
|
|
54
54
|
sections.push('1. Run `bootspring build task` (current task comes from `planning/BUILD_STATE.json`)');
|
|
55
|
-
sections.push('2. Read that task\'s acceptance criteria in `planning/
|
|
55
|
+
sections.push('2. Read that task\'s acceptance criteria in `planning/TODO.md`');
|
|
56
56
|
sections.push('3. Implement and validate the task');
|
|
57
57
|
sections.push('4. Run `bootspring build done` (auto-queues next)');
|
|
58
58
|
sections.push('');
|
|
@@ -153,8 +153,7 @@ function generatePlanningAgents(_config) {
|
|
|
153
153
|
sections.push('');
|
|
154
154
|
sections.push('| File | Purpose |');
|
|
155
155
|
sections.push('|------|---------|');
|
|
156
|
-
sections.push('| `
|
|
157
|
-
sections.push('| `TODO.md` | Full task checklist |');
|
|
156
|
+
sections.push('| `TODO.md` | **Source of truth** — task checklist with acceptance criteria |');
|
|
158
157
|
sections.push('| `BUILD_STATE.json` | Build state and current task ID (do not edit) |');
|
|
159
158
|
sections.push('| `CONTEXT.md` | Build context summary |');
|
|
160
159
|
sections.push('');
|
|
@@ -162,7 +161,7 @@ function generatePlanningAgents(_config) {
|
|
|
162
161
|
sections.push('## Workflow');
|
|
163
162
|
sections.push('');
|
|
164
163
|
sections.push('1. Run `bootspring build task` to identify the current task ID');
|
|
165
|
-
sections.push('2. Read the matching
|
|
164
|
+
sections.push('2. Read the matching task in `TODO.md` for acceptance criteria');
|
|
166
165
|
sections.push('3. Implement in the main codebase (not here)');
|
|
167
166
|
sections.push('4. Ensure acceptance criteria are met');
|
|
168
167
|
sections.push('5. Run `npm run lint && npm run test`');
|
|
@@ -93,7 +93,7 @@ Run \`bootspring build task\` to get the current \`in_progress\` task from \`BUI
|
|
|
93
93
|
## Workflow
|
|
94
94
|
|
|
95
95
|
1. Run \`bootspring build task\` to identify the current task ID
|
|
96
|
-
2. Read the matching
|
|
96
|
+
2. Read the matching task in \`TODO.md\` for acceptance criteria
|
|
97
97
|
3. Implement in the main codebase (not in this folder)
|
|
98
98
|
4. Ensure acceptance criteria are met
|
|
99
99
|
5. Run quality checks: \`npm run lint && npm run test\`
|
|
@@ -104,8 +104,7 @@ Run \`bootspring build task\` to get the current \`in_progress\` task from \`BUI
|
|
|
104
104
|
|
|
105
105
|
| File | Purpose |
|
|
106
106
|
|------|---------|
|
|
107
|
-
| \`
|
|
108
|
-
| \`TODO.md\` | Full task checklist |
|
|
107
|
+
| \`TODO.md\` | **Source of truth** — task checklist with acceptance criteria |
|
|
109
108
|
| \`BUILD_STATE.json\` | Build state and current task ID (do not edit directly) |
|
|
110
109
|
| \`CONTEXT.md\` | Build context summary |
|
|
111
110
|
|
|
@@ -254,7 +253,19 @@ function generateTodo(tasks, options = {}) {
|
|
|
254
253
|
|
|
255
254
|
let content = `# ${projectName} - Build Todo
|
|
256
255
|
|
|
257
|
-
>
|
|
256
|
+
> Single source of truth for autonomous build execution
|
|
257
|
+
> Updated: ${new Date().toISOString().split('T')[0]}
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Program Status
|
|
262
|
+
|
|
263
|
+
| Metric | Value |
|
|
264
|
+
|---|---:|
|
|
265
|
+
| Total Tasks | ${tasks.length} |
|
|
266
|
+
| Completed | ${tasks.filter(t => t.status === 'completed').length} |
|
|
267
|
+
| Remaining | ${tasks.filter(t => t.status === 'pending').length} |
|
|
268
|
+
| In Progress | ${tasks.filter(t => t.status === 'in_progress').length} |
|
|
258
269
|
|
|
259
270
|
---
|
|
260
271
|
|
|
@@ -265,17 +276,21 @@ function generateTodo(tasks, options = {}) {
|
|
|
265
276
|
const total = phaseTasks.length;
|
|
266
277
|
const percent = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
267
278
|
|
|
268
|
-
content += `## ${formatPhaseName(phaseName)} (${percent}%)
|
|
279
|
+
content += `## ${formatPhaseName(phaseName)} (${completed}/${total} — ${percent}%)
|
|
269
280
|
|
|
270
281
|
`;
|
|
271
282
|
|
|
272
283
|
for (const task of phaseTasks) {
|
|
273
284
|
const checkbox = task.status === 'completed' ? '[x]' : '[ ]';
|
|
274
|
-
|
|
285
|
+
const statusTag = task.status === 'completed' ? ' (`completed`)' :
|
|
286
|
+
task.status === 'in_progress' ? ' (`in_progress`)' :
|
|
287
|
+
task.status === 'blocked' ? ' (`blocked`)' :
|
|
288
|
+
task.status === 'skipped' ? ' (`skipped`)' : '';
|
|
289
|
+
content += `- ${checkbox} \`${task.id}\` ${task.title}${statusTag}\n`;
|
|
275
290
|
|
|
276
|
-
// Add acceptance criteria as sub-items
|
|
291
|
+
// Add acceptance criteria as sub-items (all of them — this is the source of truth)
|
|
277
292
|
if (task.acceptanceCriteria && task.acceptanceCriteria.length > 0) {
|
|
278
|
-
for (const criteria of task.acceptanceCriteria
|
|
293
|
+
for (const criteria of task.acceptanceCriteria) {
|
|
279
294
|
const subCheckbox = task.status === 'completed' ? '[x]' : '[ ]';
|
|
280
295
|
content += ` - ${subCheckbox} ${criteria}\n`;
|
|
281
296
|
}
|
|
@@ -285,18 +300,7 @@ function generateTodo(tasks, options = {}) {
|
|
|
285
300
|
content += '\n---\n\n';
|
|
286
301
|
}
|
|
287
302
|
|
|
288
|
-
content +=
|
|
289
|
-
|
|
290
|
-
| Metric | Value |
|
|
291
|
-
|--------|-------|
|
|
292
|
-
| Total Tasks | ${tasks.length} |
|
|
293
|
-
| Completed | ${tasks.filter(t => t.status === 'completed').length} |
|
|
294
|
-
| Remaining | ${tasks.filter(t => t.status === 'pending').length} |
|
|
295
|
-
| In Progress | ${tasks.filter(t => t.status === 'in_progress').length} |
|
|
296
|
-
|
|
297
|
-
---
|
|
298
|
-
|
|
299
|
-
*Updated: ${new Date().toISOString()}*
|
|
303
|
+
content += `*Updated: ${new Date().toISOString()}*
|
|
300
304
|
`;
|
|
301
305
|
|
|
302
306
|
return content;
|
|
@@ -643,27 +647,28 @@ function updateFromState(projectRoot, state) {
|
|
|
643
647
|
|
|
644
648
|
const tasks = state.implementationQueue;
|
|
645
649
|
const planningDir = path.join(projectRoot, 'planning');
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
? fs.readFileSync(queuePath, 'utf-8')
|
|
649
|
-
: '';
|
|
650
|
-
const existingVersionMatch = existingQueue.match(/\*\*Current Version:\*\*\s*([^\n]+)/i)
|
|
651
|
-
|| existingQueue.match(/>\s*Current Version:\s*([^\n]+)/i);
|
|
652
|
-
const queueVersion = existingVersionMatch
|
|
653
|
-
? existingVersionMatch[1].trim()
|
|
654
|
-
: DEFAULT_QUEUE_VERSION;
|
|
655
|
-
|
|
656
|
-
// Regenerate TODO.md with current status
|
|
650
|
+
|
|
651
|
+
// Regenerate TODO.md — the single source of truth
|
|
657
652
|
const todo = generateTodo(tasks, { projectName: state.projectName });
|
|
658
653
|
fs.writeFileSync(path.join(planningDir, 'TODO.md'), todo);
|
|
659
654
|
|
|
660
|
-
//
|
|
661
|
-
const
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
655
|
+
// Legacy: also regenerate TASK_QUEUE.md if it already exists (backward compat)
|
|
656
|
+
const queuePath = path.join(planningDir, 'TASK_QUEUE.md');
|
|
657
|
+
if (fs.existsSync(queuePath)) {
|
|
658
|
+
const existingQueue = fs.readFileSync(queuePath, 'utf-8');
|
|
659
|
+
const existingVersionMatch = existingQueue.match(/\*\*Current Version:\*\*\s*([^\n]+)/i)
|
|
660
|
+
|| existingQueue.match(/>\s*Current Version:\s*([^\n]+)/i);
|
|
661
|
+
const queueVersion = existingVersionMatch
|
|
662
|
+
? existingVersionMatch[1].trim()
|
|
663
|
+
: DEFAULT_QUEUE_VERSION;
|
|
664
|
+
|
|
665
|
+
const queue = generateImplementationQueue(tasks, {
|
|
666
|
+
projectName: state.projectName,
|
|
667
|
+
lastUpdated: new Date().toISOString().split('T')[0],
|
|
668
|
+
queueVersion
|
|
669
|
+
});
|
|
670
|
+
fs.writeFileSync(queuePath, queue);
|
|
671
|
+
}
|
|
667
672
|
}
|
|
668
673
|
|
|
669
674
|
module.exports = {
|
|
@@ -33,7 +33,7 @@ function generate(config) {
|
|
|
33
33
|
sections.push('### Before Writing ANY Code');
|
|
34
34
|
sections.push('1. Read `planning/MASTER_PLAN.md` - Overall vision and phases');
|
|
35
35
|
sections.push('2. Run `bootspring build task` - Current task ID comes from `planning/BUILD_STATE.json`');
|
|
36
|
-
sections.push('3. Read `planning/
|
|
36
|
+
sections.push('3. Read `planning/TODO.md` - Find the task ID and its acceptance criteria');
|
|
37
37
|
sections.push('4. Read `planning/CONTEXT.md` - Current build context');
|
|
38
38
|
sections.push('5. Understand the acceptance criteria before implementing');
|
|
39
39
|
sections.push('');
|
|
@@ -53,9 +53,8 @@ function generate(config) {
|
|
|
53
53
|
sections.push('| File | Purpose | When to Read |');
|
|
54
54
|
sections.push('|------|---------|--------------|');
|
|
55
55
|
sections.push('| `MASTER_PLAN.md` | Overall vision, phases, success criteria | **Always read first** |');
|
|
56
|
-
sections.push('| `
|
|
56
|
+
sections.push('| `TODO.md` | **Source of truth** — all tasks with acceptance criteria | **Before each task** |');
|
|
57
57
|
sections.push('| `CONTEXT.md` | Current build context and decisions | Before each task |');
|
|
58
|
-
sections.push('| `TODO.md` | Checkbox task list | Update after completing |');
|
|
59
58
|
sections.push('| `BUILD_STATE.json` | Machine state - source of current task ID | Reference only |');
|
|
60
59
|
sections.push('');
|
|
61
60
|
sections.push('---');
|
|
@@ -231,7 +230,7 @@ function generate(config) {
|
|
|
231
230
|
sections.push('│ ↓ │');
|
|
232
231
|
sections.push('│ bootspring build task → get in_progress task ID │');
|
|
233
232
|
sections.push('│ ↓ │');
|
|
234
|
-
sections.push('│ Read planning/
|
|
233
|
+
sections.push('│ Read planning/TODO.md for acceptance criteria │');
|
|
235
234
|
sections.push('│ ↓ │');
|
|
236
235
|
sections.push('│ Implement task (meet acceptance criteria) │');
|
|
237
236
|
sections.push('│ ↓ │');
|