@girardmedia/bootspring 2.0.6 → 2.0.7

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
@@ -56,9 +56,11 @@ async function run(args) {
56
56
  return showBuildStatus(projectRoot, parsedArgs);
57
57
 
58
58
  case 'pause':
59
- case 'stop':
60
59
  return pauseBuildLoop(projectRoot);
61
60
 
61
+ case 'stop':
62
+ return gracefulStop(projectRoot);
63
+
62
64
  case 'resume':
63
65
  case 'continue':
64
66
  return resumeBuildLoop(projectRoot);
@@ -189,13 +191,14 @@ Some tasks may be blocked. Run ${c.cyan}bootspring build status${c.reset} for de
189
191
  console.log(`${c.bold}What would you like to do?${c.reset}
190
192
 
191
193
  ${c.cyan}1${c.reset} Start this task (shows prompt for Claude Code)
192
- ${c.cyan}2${c.reset} View task details
193
- ${c.cyan}3${c.reset} Skip this task
194
- ${c.cyan}4${c.reset} View full plan
194
+ ${c.cyan}2${c.reset} Loop all tasks (autonomous - runs Claude until complete)
195
+ ${c.cyan}3${c.reset} View task details
196
+ ${c.cyan}4${c.reset} Skip this task
197
+ ${c.cyan}5${c.reset} View full plan
195
198
  ${c.cyan}q${c.reset} Quit
196
199
  `);
197
200
 
198
- const choice = await askQuestion('Choose [1-4, q]: ');
201
+ const choice = await askQuestion('Choose [1-5, q]: ');
199
202
 
200
203
  switch (choice.trim()) {
201
204
  case '1':
@@ -203,17 +206,20 @@ Some tasks may be blocked. Run ${c.cyan}bootspring build status${c.reset} for de
203
206
  return buildNextTask(projectRoot, args);
204
207
 
205
208
  case '2':
209
+ return buildAll(projectRoot, args);
210
+
211
+ case '3':
206
212
  showCurrentTask(projectRoot, { prompt: true });
207
213
  console.log('');
208
214
  return interactiveBuild(projectRoot, args);
209
215
 
210
- case '3': {
216
+ case '4': {
211
217
  const reason = await askQuestion('Skip reason (optional): ');
212
218
  skipCurrentTask(projectRoot, { reason: reason || 'Skipped by user' });
213
219
  return interactiveBuild(projectRoot, args);
214
220
  }
215
221
 
216
- case '4':
222
+ case '5':
217
223
  showPlan(projectRoot);
218
224
  return interactiveBuild(projectRoot, args);
219
225
 
@@ -547,6 +553,24 @@ Or start fresh:
547
553
  `);
548
554
  }
549
555
 
556
+ /**
557
+ * Graceful stop - finish current task then pause
558
+ */
559
+ function gracefulStop(projectRoot) {
560
+ const loop = require('./loop');
561
+
562
+ console.log(`
563
+ ${c.yellow}${c.bold}Graceful stop requested${c.reset}
564
+
565
+ The loop will pause after the current task completes.
566
+ This ensures no work is interrupted mid-task.
567
+
568
+ ${c.dim}Waiting for current task to finish...${c.reset}
569
+ `);
570
+
571
+ loop.setStopSignal(projectRoot);
572
+ }
573
+
550
574
  /**
551
575
  * Show current task
552
576
  */
@@ -745,6 +769,7 @@ ${c.bold}Commands:${c.reset}
745
769
  ${c.cyan}bootspring build next${c.reset} Show next task prompt
746
770
  ${c.cyan}bootspring build done${c.reset} Mark current task complete
747
771
  ${c.cyan}bootspring build skip${c.reset} Skip current task
772
+ ${c.cyan}bootspring build stop${c.reset} Graceful stop (finish current task, then pause)
748
773
  ${c.cyan}bootspring build status${c.reset} View detailed progress
749
774
  ${c.cyan}bootspring build task${c.reset} View current task details
750
775
  ${c.cyan}bootspring build plan${c.reset} View the full master plan
package/cli/loop.js CHANGED
@@ -457,17 +457,25 @@ ${task.acceptance ? `\n### Acceptance Criteria\n${task.acceptance.map(a => `- ${
457
457
  - No TypeScript/lint errors
458
458
  - No security vulnerabilities introduced
459
459
 
460
- ## Exit Signals
461
- When you successfully complete the task, output exactly:
462
- <loop-status>TASK_COMPLETE</loop-status>
460
+ ## Status Reporting
461
+ When you complete work, include a status block at the end of your response:
463
462
 
464
- If you cannot complete the task, output exactly:
465
- <loop-status>TASK_BLOCKED</loop-status>
466
- Reason: [explanation]
463
+ \`\`\`
464
+ BOOTSPRING_STATUS: COMPLETE
465
+ \`\`\`
466
+
467
+ If you cannot proceed (missing info, blocked, need human input):
468
+ \`\`\`
469
+ BOOTSPRING_STATUS: BLOCKED
470
+ BOOTSPRING_REASON: [brief explanation]
471
+ \`\`\`
467
472
 
468
- If all tasks are done, output exactly:
469
- <loop-status>ALL_COMPLETE</loop-status>
470
- EXIT_SIGNAL
473
+ If ALL tasks in the build are finished:
474
+ \`\`\`
475
+ BOOTSPRING_STATUS: EXIT
476
+ \`\`\`
477
+
478
+ IMPORTANT: Always include one of these status blocks at the end of your work.
471
479
 
472
480
  ## Session Info
473
481
  - Session: ${session.state.sessionId}
@@ -543,7 +551,17 @@ async function runIteration(session, taskInfo, options = {}) {
543
551
 
544
552
  if (session.state.tool === 'claude') {
545
553
  aiCmd = 'claude';
546
- aiArgs = ['--print'];
554
+ aiArgs = ['-p']; // Print mode for non-interactive
555
+
556
+ // Continue session if not first iteration (maintains context)
557
+ if (session.state.iteration > 0) {
558
+ aiArgs.push('-c'); // Continue most recent conversation
559
+ }
560
+
561
+ // Use JSON output for structured parsing when not in live mode
562
+ if (!options.live) {
563
+ aiArgs.push('--output-format', 'json');
564
+ }
547
565
  } else if (session.state.tool === 'amp') {
548
566
  aiCmd = 'amp';
549
567
  aiArgs = [];
@@ -552,9 +570,12 @@ async function runIteration(session, taskInfo, options = {}) {
552
570
  return;
553
571
  }
554
572
 
573
+ // For live mode, inherit stdout/stderr so user sees Claude working
574
+ // For non-live mode, capture output for parsing
555
575
  const child = spawn(aiCmd, aiArgs, {
556
576
  cwd: session.projectRoot,
557
- stdio: options.live ? ['pipe', 'inherit', 'inherit'] : ['pipe', 'pipe', 'pipe']
577
+ stdio: options.live ? ['pipe', 'inherit', 'inherit'] : ['pipe', 'pipe', 'pipe'],
578
+ env: { ...process.env }
558
579
  });
559
580
 
560
581
  // Send prompt via stdin
@@ -577,6 +598,28 @@ async function runIteration(session, taskInfo, options = {}) {
577
598
  }
578
599
 
579
600
  child.on('close', (code) => {
601
+ // For live mode, we assume success if exit code is 0
602
+ // Claude will have output directly to terminal
603
+ if (options.live) {
604
+ if (code !== 0) {
605
+ session.recordTaskEnd('error', { exitCode: code });
606
+ resolve({ status: 'error', exitCode: code });
607
+ } else {
608
+ session.recordTaskEnd('complete');
609
+ resolve({ status: 'complete', output: '[live mode - output shown above]' });
610
+ }
611
+ return;
612
+ }
613
+
614
+ // Parse JSON output if available
615
+ let parsedOutput = null;
616
+ try {
617
+ parsedOutput = JSON.parse(output);
618
+ output = parsedOutput.result || parsedOutput.content || output;
619
+ } catch {
620
+ // Not JSON, use raw output
621
+ }
622
+
580
623
  // Check exit conditions
581
624
  const exitCheck = session.checkExitConditions(output);
582
625
 
@@ -586,7 +629,27 @@ async function runIteration(session, taskInfo, options = {}) {
586
629
  return;
587
630
  }
588
631
 
589
- // Check for task status in output
632
+ // Check for BOOTSPRING_STATUS block (like Ralph's RALPH_STATUS)
633
+ const statusMatch = output.match(/BOOTSPRING_STATUS:\s*(\w+)/i);
634
+ if (statusMatch) {
635
+ const status = statusMatch[1].toUpperCase();
636
+ if (status === 'COMPLETE' || status === 'DONE') {
637
+ session.recordTaskEnd('complete');
638
+ resolve({ status: 'complete', output });
639
+ return;
640
+ } else if (status === 'BLOCKED' || status === 'STUCK') {
641
+ const reasonMatch = output.match(/BOOTSPRING_REASON:\s*(.+)/i);
642
+ session.recordTaskEnd('blocked', { reason: reasonMatch?.[1] || 'Unknown' });
643
+ resolve({ status: 'blocked', reason: reasonMatch?.[1], output });
644
+ return;
645
+ } else if (status === 'EXIT' || status === 'FINISHED') {
646
+ session.recordTaskEnd('complete', { exitReason: 'All tasks complete' });
647
+ resolve({ status: 'all_complete', reason: 'All tasks complete', output });
648
+ return;
649
+ }
650
+ }
651
+
652
+ // Legacy status tags
590
653
  if (output.includes('<loop-status>TASK_COMPLETE</loop-status>')) {
591
654
  session.recordTaskEnd('complete');
592
655
  resolve({ status: 'complete', output });
@@ -618,6 +681,37 @@ function sleep(ms) {
618
681
  return new Promise(resolve => setTimeout(resolve, ms));
619
682
  }
620
683
 
684
+ /**
685
+ * Check if graceful stop signal is set
686
+ */
687
+ function checkStopSignal(projectRoot) {
688
+ const stopFile = path.join(projectRoot, '.bootspring', 'STOP_AFTER_TASK');
689
+ return fs.existsSync(stopFile);
690
+ }
691
+
692
+ /**
693
+ * Set graceful stop signal (stop after current task completes)
694
+ */
695
+ function setStopSignal(projectRoot) {
696
+ const bootspringDir = path.join(projectRoot, '.bootspring');
697
+ if (!fs.existsSync(bootspringDir)) {
698
+ fs.mkdirSync(bootspringDir, { recursive: true });
699
+ }
700
+ const stopFile = path.join(bootspringDir, 'STOP_AFTER_TASK');
701
+ fs.writeFileSync(stopFile, new Date().toISOString());
702
+ return stopFile;
703
+ }
704
+
705
+ /**
706
+ * Clear graceful stop signal
707
+ */
708
+ function clearStopSignal(projectRoot) {
709
+ const stopFile = path.join(projectRoot, '.bootspring', 'STOP_AFTER_TASK');
710
+ if (fs.existsSync(stopFile)) {
711
+ fs.unlinkSync(stopFile);
712
+ }
713
+ }
714
+
621
715
  /**
622
716
  * Run the main loop
623
717
  */
@@ -729,6 +823,16 @@ ${c.bold}Configuration${c.reset}
729
823
  }
730
824
  }
731
825
 
826
+ // Check for graceful stop signal (stop after completing current task)
827
+ if (checkStopSignal(projectRoot)) {
828
+ console.log(`\n${c.yellow}${c.bold}⏸ Graceful stop requested. Pausing after this task.${c.reset}`);
829
+ session.state.status = 'paused';
830
+ session.save();
831
+ clearStopSignal(projectRoot);
832
+ console.log(`${c.dim}Resume with: bootspring build${c.reset}\n`);
833
+ break;
834
+ }
835
+
732
836
  // Delay between iterations
733
837
  await sleep(LOOP_CONFIG.minDelayBetweenCalls);
734
838
  }
@@ -1331,5 +1435,7 @@ module.exports = {
1331
1435
  showStatus,
1332
1436
  showPRDStatus,
1333
1437
  getTasks,
1438
+ setStopSignal,
1439
+ clearStopSignal,
1334
1440
  LOOP_CONFIG
1335
1441
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "contractVersion": "v1",
3
3
  "packageName": "@girardmedia/bootspring",
4
- "packageVersion": "2.0.6",
4
+ "packageVersion": "2.0.7",
5
5
  "tools": [
6
6
  {
7
7
  "name": "bootspring_assist",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@girardmedia/bootspring",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "description": "Development scaffolding with intelligence - AI-powered context, agents, and workflows for any MCP-compatible assistant",
5
5
  "keywords": [
6
6
  "ai",