@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/TASK_QUEUE.md, find ${nextTask.id}, and implement it"
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/TASK_QUEUE.md, implement ${nextTask.id}
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/TASK_QUEUE.md, find ${currentTask?.id || 'current task'}
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/TASK_QUEUE.md`');
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('| `TASK_QUEUE.md` | Ordered task queue and acceptance criteria |');
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 section in `TASK_QUEUE.md` for acceptance criteria');
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 section in \`TASK_QUEUE.md\` for acceptance criteria
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
- | \`TASK_QUEUE.md\` | Ordered task queue and acceptance criteria |
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
- > Checklist for autonomous build completion
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
- content += `- ${checkbox} ${task.title}\n`;
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.slice(0, 3)) {
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 += `## Progress Summary
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
- const queuePath = path.join(planningDir, 'TASK_QUEUE.md');
647
- const existingQueue = fs.existsSync(queuePath)
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
- // Regenerate TASK_QUEUE.md
661
- const queue = generateImplementationQueue(tasks, {
662
- projectName: state.projectName,
663
- lastUpdated: new Date().toISOString().split('T')[0],
664
- queueVersion
665
- });
666
- fs.writeFileSync(queuePath, queue);
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/TASK_QUEUE.md` - Acceptance criteria for that task ID');
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('| `TASK_QUEUE.md` | All tasks with acceptance criteria (look up current task ID here) | **Before each task** |');
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/TASK_QUEUE.md for acceptance criteria │');
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('│ ↓ │');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@girardmedia/bootspring",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "Thin client for Bootspring cloud MCP, hosted agents, and paywalled workflow intelligence",
5
5
  "keywords": [
6
6
  "ai",