@girardmedia/bootspring 3.1.0 → 3.2.0

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.
@@ -21,6 +21,8 @@ When user invokes `/build` or says "build next", "build loop", "build done", use
21
21
  - `/build list` → `bootspring_build` with action: "list"
22
22
  - `/build current` → `bootspring_build` with action: "current"
23
23
 
24
+ After `action: "next"` or `action: "current"`, surface the returned `visibility` contract: show a visible checklist in the host UI when available and keep `planning/BUILD_PROGRESS.md` updated after inspection, edits, verification, and completion.
25
+
24
26
  ## Important
25
27
 
26
28
  - "build" in Bootspring context means **task execution**, not `npm run build`
package/dist/cli/index.js CHANGED
@@ -3386,7 +3386,7 @@ var init_release = __esm({
3386
3386
  "../../packages/shared/src/release.ts"() {
3387
3387
  "use strict";
3388
3388
  init_cjs_shims();
3389
- BOOTSPRING_VERSION = "3.1.0";
3389
+ BOOTSPRING_VERSION = "3.2.0";
3390
3390
  BOOTSPRING_PACKAGE_NAME = "@girardmedia/bootspring";
3391
3391
  }
3392
3392
  });
@@ -56437,6 +56437,15 @@ init_cjs_shims();
56437
56437
  var fs51 = __toESM(require("fs"));
56438
56438
  var path52 = __toESM(require("path"));
56439
56439
  init_src();
56440
+ var BUILD_PROGRESS_ARTIFACT = path52.join("planning", "BUILD_PROGRESS.md");
56441
+ var VISIBLE_WORK_CHECKPOINTS = [
56442
+ "Announce the active task and intended checkpoints in the chat or terminal.",
56443
+ "Inspect the relevant files and summarize what changed before editing.",
56444
+ "Patch the implementation and keep unrelated files untouched.",
56445
+ "Add or update focused tests when the task changes behavior.",
56446
+ "Run the relevant verification commands and report pass/fail output.",
56447
+ "Update the build state with `bootspring build done` only after verification."
56448
+ ];
56440
56449
  function getPlanningSurfaceStatus() {
56441
56450
  const planningDir = path52.join(process.cwd(), "planning");
56442
56451
  return {
@@ -56768,6 +56777,97 @@ function getTaskEntriesFromState(state) {
56768
56777
  estimatedTurns: task.estimatedTurns
56769
56778
  }));
56770
56779
  }
56780
+ function getStatusCounts(tasks) {
56781
+ const completed = tasks.filter((t) => normalizeStatus(t.status) === "completed").length;
56782
+ const skipped = tasks.filter((t) => normalizeStatus(t.status) === "skipped").length;
56783
+ const blocked = tasks.filter((t) => normalizeStatus(t.status) === "blocked").length;
56784
+ const pending = tasks.filter((t) => normalizeStatus(t.status) === "pending").length;
56785
+ const inProgress = tasks.filter((t) => normalizeStatus(t.status) === "in_progress").length;
56786
+ const total = tasks.length;
56787
+ const closed = completed + skipped;
56788
+ const pct2 = total > 0 ? Math.round(closed / total * 100) : 0;
56789
+ return { completed, skipped, blocked, pending, inProgress, total, closed, pct: pct2 };
56790
+ }
56791
+ function renderProgressBar(pct2, width = 30) {
56792
+ const clamped = Math.max(0, Math.min(100, pct2));
56793
+ const filled = Math.round(clamped / 100 * width);
56794
+ return `[${"#".repeat(filled)}${".".repeat(width - filled)}] ${clamped}%`;
56795
+ }
56796
+ function getVisibleTask(task) {
56797
+ if (!task?.id || !task?.title) return null;
56798
+ return {
56799
+ id: task.id,
56800
+ title: task.title,
56801
+ phase: task.phase,
56802
+ complexity: task.complexity,
56803
+ status: normalizeStatus(task.status),
56804
+ description: task.description,
56805
+ source: task.source,
56806
+ sourceSection: task.sourceSection,
56807
+ acceptanceCriteria: task.acceptanceCriteria ?? [],
56808
+ dependencies: task.dependencies ?? [],
56809
+ estimatedTokens: task.estimatedTokens,
56810
+ estimatedTurns: task.estimatedTurns
56811
+ };
56812
+ }
56813
+ function writeBuildProgressArtifact(state, task, event) {
56814
+ const dir = path52.join(process.cwd(), "planning");
56815
+ if (!fs51.existsSync(dir)) fs51.mkdirSync(dir, { recursive: true });
56816
+ const tasks = getTaskEntriesFromState(state);
56817
+ const counts = getStatusCounts(tasks);
56818
+ const visibleTask = getVisibleTask(task) ?? tasks.find((t) => normalizeStatus(t.status) === "in_progress") ?? tasks.find((t) => normalizeStatus(t.status) === "pending") ?? null;
56819
+ const progressPath = path52.join(process.cwd(), BUILD_PROGRESS_ARTIFACT);
56820
+ const updated = (/* @__PURE__ */ new Date()).toISOString();
56821
+ const artifact = [
56822
+ "# Build Progress",
56823
+ "",
56824
+ `Updated: ${updated}`,
56825
+ `Event: ${event}`,
56826
+ `Project: ${state.projectName ?? path52.basename(process.cwd())}`,
56827
+ `Status: ${deriveBuildStatus(state.status, counts)}`,
56828
+ `Overall: ${counts.closed}/${counts.total} ${renderProgressBar(counts.pct)}`,
56829
+ "",
56830
+ "## Active Task",
56831
+ "",
56832
+ visibleTask ? [
56833
+ `- ID: ${visibleTask.id}`,
56834
+ `- Title: ${visibleTask.title}`,
56835
+ `- Status: ${normalizeStatus(visibleTask.status)}`,
56836
+ `- Phase: ${visibleTask.phase ?? "Unknown"}`,
56837
+ visibleTask.description ? `- Description: ${visibleTask.description}` : null,
56838
+ visibleTask.dependencies?.length ? `- Dependencies: ${visibleTask.dependencies.join(", ")}` : null
56839
+ ].filter(Boolean).join("\n") : "No active or pending task.",
56840
+ "",
56841
+ "## Acceptance Criteria",
56842
+ "",
56843
+ visibleTask?.acceptanceCriteria?.length ? visibleTask.acceptanceCriteria.map((item) => `- [ ] ${item}`).join("\n") : "- [ ] No task-specific acceptance criteria recorded.",
56844
+ "",
56845
+ "## Visible Work Contract",
56846
+ "",
56847
+ VISIBLE_WORK_CHECKPOINTS.map((item) => `- [ ] ${item}`).join("\n"),
56848
+ "",
56849
+ "## Host Notes",
56850
+ "",
56851
+ "- Claude Code and Codex CLI can stream terminal output directly.",
56852
+ "- Codex Desktop, Claude Desktop, and other MCP hosts should keep a visible plan/checklist updated while the terminal is hidden.",
56853
+ "- Update this file after inspection, edits, verification, and completion so the workspace has a durable progress surface.",
56854
+ ""
56855
+ ].join("\n");
56856
+ fs51.writeFileSync(progressPath, artifact);
56857
+ return BUILD_PROGRESS_ARTIFACT;
56858
+ }
56859
+ function printVisibleWorkNotice(artifactPath) {
56860
+ print.header("Visible Work");
56861
+ print.info(`Progress artifact: ${artifactPath}`);
56862
+ print.info("Desktop agents should keep a visible plan/checklist updated while they inspect, edit, verify, and mark the task done.");
56863
+ print.info("This mirrors Claude Code/Codex CLI-style streaming even when the host hides terminal output.");
56864
+ }
56865
+ function deriveBuildStatus(stateStatus, counts) {
56866
+ if (counts.total > 0 && counts.pending === 0 && counts.inProgress === 0) {
56867
+ return counts.blocked > 0 ? "blocked" : "completed";
56868
+ }
56869
+ return stateStatus ?? "unknown";
56870
+ }
56771
56871
  function extractSupplementaryContent(queueContent) {
56772
56872
  const lines = queueContent.split("\n");
56773
56873
  const sections = [];
@@ -56941,7 +57041,8 @@ function registerBuildCommand(program3) {
56941
57041
  build.command("status").description("Check build progress").option("--json", "Output as JSON").action((opts) => {
56942
57042
  const state = loadBuildStateWithAutoSync(opts.json);
56943
57043
  const planningTasks = loadPlanningTasks();
56944
- const tasks = planningTasks.tasks.length > 0 ? planningTasks.tasks : getTaskEntriesFromState(state);
57044
+ const stateTasks = getTaskEntriesFromState(state);
57045
+ const tasks = stateTasks.length > 0 ? stateTasks : planningTasks.tasks;
56945
57046
  if (!state) {
56946
57047
  if (opts.json) {
56947
57048
  console.log(JSON.stringify({ error: "no_build_state", tasks: [] }));
@@ -56960,14 +57061,8 @@ function registerBuildCommand(program3) {
56960
57061
  }
56961
57062
  return;
56962
57063
  }
56963
- const completed = tasks.filter((t) => {
56964
- const s = t.status.toLowerCase();
56965
- return s === "completed" || s === "done";
56966
- }).length;
56967
- const pending = tasks.filter((t) => t.status.toLowerCase() === "pending").length;
56968
- const inProgress = tasks.filter((t) => t.status.toLowerCase() === "in_progress").length;
56969
- const total = tasks.length;
56970
- const pct2 = total > 0 ? Math.round(completed / total * 100) : 0;
57064
+ const { completed, skipped, blocked, pending, inProgress, total, closed, pct: pct2 } = getStatusCounts(tasks);
57065
+ const derivedStatus = deriveBuildStatus(state.status, { completed, skipped, blocked, pending, inProgress, total, closed, pct: pct2 });
56971
57066
  const iteration = state.loopSession?.currentIteration ?? 0;
56972
57067
  const maxIter = state.loopSession?.maxIterations ?? 50;
56973
57068
  const sessionId = state.loopSession?.sessionId ?? "N/A";
@@ -56975,12 +57070,15 @@ function registerBuildCommand(program3) {
56975
57070
  const currentTask2 = tasks.find((t) => t.status.toLowerCase() === "in_progress");
56976
57071
  console.log(JSON.stringify({
56977
57072
  project: state.projectName ?? "Unknown",
56978
- status: state.status,
57073
+ status: derivedStatus,
56979
57074
  phase: formatPhaseName(state.currentPhase ?? getCurrentPhase(state.implementationQueue)),
56980
57075
  planningSource: formatPlanningSourceLabel(planningTasks.source ?? (state ? "BUILD_STATE.json" : null)),
56981
57076
  completed,
57077
+ skipped,
57078
+ blocked,
56982
57079
  pending,
56983
57080
  inProgress,
57081
+ closed,
56984
57082
  total,
56985
57083
  pct: pct2,
56986
57084
  iteration,
@@ -56993,10 +57091,12 @@ function registerBuildCommand(program3) {
56993
57091
  print.header("Build Status");
56994
57092
  printLegacyQueueWarningIfNeeded();
56995
57093
  print.info(`Project: ${state.projectName ?? "Unknown"}`);
56996
- print.info(`Status: ${state.status}`);
57094
+ print.info(`Status: ${derivedStatus}`);
56997
57095
  print.info(`Phase: ${formatPhaseName(state.currentPhase ?? getCurrentPhase(state.implementationQueue))}`);
56998
57096
  print.info(`Planning Source: ${formatPlanningSourceLabel(planningTasks.source ?? (state ? "BUILD_STATE.json" : null))}`);
56999
- print.info(`Progress: ${completed}/${total} (${pct2}%)`);
57097
+ print.info(`Progress: ${closed}/${total} (${pct2}%)`);
57098
+ if (skipped > 0) print.info(`Skipped: ${skipped}`);
57099
+ if (blocked > 0) print.info(`Blocked: ${blocked}`);
57000
57100
  print.info(`Pending: ${pending}`);
57001
57101
  if (inProgress > 0) print.info(`In Progress: ${inProgress}`);
57002
57102
  if (iteration > maxIter) {
@@ -57013,6 +57113,40 @@ function registerBuildCommand(program3) {
57013
57113
  const filled = Math.round(pct2 / 100 * barWidth);
57014
57114
  const bar = "#".repeat(filled) + ".".repeat(barWidth - filled);
57015
57115
  console.log(` [${bar}] ${pct2}%`);
57116
+ const artifactPath = writeBuildProgressArtifact(state, currentTask, "status");
57117
+ print.info(`Progress artifact: ${artifactPath}`);
57118
+ });
57119
+ build.command("progress").description("Show the visible build progress artifact for desktop/agent hosts").option("--json", "Output artifact metadata as JSON").action((opts) => {
57120
+ const state = loadBuildStateWithAutoSync(opts.json);
57121
+ if (!state) {
57122
+ if (opts.json) {
57123
+ console.log(JSON.stringify({ error: "no_build_state", artifact: null }));
57124
+ } else {
57125
+ print.error("No build state found");
57126
+ }
57127
+ return;
57128
+ }
57129
+ const tasks = getTaskEntriesFromState(state);
57130
+ const currentTask = tasks.find((t) => normalizeStatus(t.status) === "in_progress") ?? tasks.find((t) => normalizeStatus(t.status) === "pending") ?? null;
57131
+ const artifactPath = writeBuildProgressArtifact(state, currentTask, "progress");
57132
+ if (opts.json) {
57133
+ const counts = getStatusCounts(tasks);
57134
+ console.log(JSON.stringify({
57135
+ artifact: artifactPath,
57136
+ progress: {
57137
+ completed: counts.completed,
57138
+ pending: counts.pending,
57139
+ inProgress: counts.inProgress,
57140
+ total: counts.total,
57141
+ percent: counts.pct
57142
+ },
57143
+ currentTask: currentTask ? { id: currentTask.id, title: currentTask.title, status: currentTask.status } : null,
57144
+ visibleWorkCheckpoints: VISIBLE_WORK_CHECKPOINTS
57145
+ }, null, 2));
57146
+ return;
57147
+ }
57148
+ print.header("Build Progress");
57149
+ console.log(fs51.readFileSync(path52.join(process.cwd(), artifactPath), "utf-8"));
57016
57150
  });
57017
57151
  build.command("task").description("Show the current task").action(() => {
57018
57152
  printLegacyQueueWarningIfNeeded();
@@ -57096,6 +57230,8 @@ function registerBuildCommand(program3) {
57096
57230
  } else {
57097
57231
  print.success("No more pending tasks!");
57098
57232
  }
57233
+ const artifactPath = writeBuildProgressArtifact(state, nextTask ?? queue[idx], nextTask ? "task_completed_next_available" : "task_completed_all_done");
57234
+ printVisibleWorkNotice(artifactPath);
57099
57235
  });
57100
57236
  build.command("plan").description("View the full build plan").action(() => {
57101
57237
  printLegacyQueueWarningIfNeeded();
@@ -57167,6 +57303,8 @@ function registerBuildCommand(program3) {
57167
57303
  const inProgress = queue.find((t2) => t2.status === "in_progress");
57168
57304
  if (inProgress) {
57169
57305
  print.warning(`Task ${inProgress.id} is already in progress: ${inProgress.title}`);
57306
+ const artifactPath2 = writeBuildProgressArtifact(state, inProgress, "already_in_progress");
57307
+ printVisibleWorkNotice(artifactPath2);
57170
57308
  return;
57171
57309
  }
57172
57310
  const idx = queue.findIndex((t2) => t2.status === "pending");
@@ -57194,6 +57332,8 @@ function registerBuildCommand(program3) {
57194
57332
  console.log(` [ ] ${ac}`);
57195
57333
  }
57196
57334
  }
57335
+ const artifactPath = writeBuildProgressArtifact(state, t, "task_started");
57336
+ printVisibleWorkNotice(artifactPath);
57197
57337
  });
57198
57338
  build.command("pause").description("Pause the build loop").action(() => {
57199
57339
  const state = loadBuildState();
@@ -61870,7 +62010,7 @@ function generateDocTemplate(docType, projectName) {
61870
62010
  `;
61871
62011
  }
61872
62012
  function registerGoCommand(program3) {
61873
- program3.command("go").description("One-command setup: detect \u2192 seed \u2192 generate \u2192 build init").option("--preset <preset>", "Document set (essential, startup, full, technical)", "startup").option("--force", "Overwrite existing files").option("--edit-first", "Stop after creating templates for manual editing").action((opts) => {
62013
+ program3.command("go").description("One-command setup: detect \u2192 seed \u2192 generate \u2192 build init").argument("[pitch]", "Optional project pitch \u2014 uses AI to fill all context docs").option("--preset <preset>", "Document set (essential, startup, full, technical)", "startup").option("--provider <provider>", "AI provider: anthropic, openai, or gemini", "anthropic").option("--force", "Overwrite existing files").option("--edit-first", "Stop after creating templates for manual editing").action(async (pitch, opts) => {
61874
62014
  console.log("");
61875
62015
  console.log(`${COLORS.cyan}${COLORS.bold}\u26A1 Bootspring Go${COLORS.reset}`);
61876
62016
  console.log(`${COLORS.dim}Setting up your project in one shot...${COLORS.reset}`);
@@ -61890,26 +62030,56 @@ function registerGoCommand(program3) {
61890
62030
  print.dim(" No codebase detected (starting fresh)");
61891
62031
  }
61892
62032
  console.log("");
61893
- console.log(`${COLORS.bold}[2/5] Seed templates${COLORS.reset}`);
61894
- if (!fs64.existsSync(contextDir)) {
61895
- fs64.mkdirSync(contextDir, { recursive: true });
61896
- }
61897
- const docs = PRESETS[opts.preset] || PRESETS.startup;
61898
- let generated = 0;
61899
- let skipped = 0;
61900
- for (const docType of docs) {
61901
- const meta3 = CONTEXT_DOCS[docType];
61902
- if (!meta3) continue;
61903
- const filePath = path65.join(contextDir, meta3.name);
61904
- if (fs64.existsSync(filePath) && !opts.force) {
61905
- skipped++;
61906
- continue;
62033
+ if (pitch) {
62034
+ console.log(`${COLORS.bold}[2/5] AI Synthesize${COLORS.reset}`);
62035
+ print.info(` Pitch: "${pitch}"`);
62036
+ let api;
62037
+ try {
62038
+ const core = await Promise.resolve().then(() => (init_src2(), src_exports2));
62039
+ api = core.api;
62040
+ } catch {
62041
+ print.error(" Failed to load API client. Run `bootspring auth login` first.");
62042
+ print.info(" Falling back to empty templates...");
62043
+ api = null;
61907
62044
  }
61908
- fs64.writeFileSync(filePath, generateDocTemplate(docType, projectName));
61909
- generated++;
62045
+ if (api) {
62046
+ try {
62047
+ const result = await api.request("POST", "/seed/synthesize", {
62048
+ pitch,
62049
+ projectName,
62050
+ stack,
62051
+ features,
62052
+ preset: opts.preset,
62053
+ provider: opts.provider
62054
+ }, { timeout: 12e4 });
62055
+ if (result?.documents && Object.keys(result.documents).length > 0) {
62056
+ if (!fs64.existsSync(contextDir)) fs64.mkdirSync(contextDir, { recursive: true });
62057
+ let written = 0;
62058
+ for (const [key, content] of Object.entries(result.documents)) {
62059
+ if (typeof content !== "string") continue;
62060
+ const filePath = path65.join(contextDir, `${key}.md`);
62061
+ if (fs64.existsSync(filePath) && !opts.force) continue;
62062
+ fs64.writeFileSync(filePath, content);
62063
+ written++;
62064
+ }
62065
+ print.success(` Generated ${written} context doc(s) with AI (${result.metadata?.durationMs || "?"}ms)`);
62066
+ } else {
62067
+ print.warning(" AI returned no documents \u2014 falling back to templates");
62068
+ generateFallbackTemplates(contextDir, opts.preset, projectName, opts.force);
62069
+ }
62070
+ } catch (err) {
62071
+ const msg = err instanceof Error ? err.message : String(err);
62072
+ print.warning(` AI generation failed: ${msg}`);
62073
+ print.info(" Falling back to templates...");
62074
+ generateFallbackTemplates(contextDir, opts.preset, projectName, opts.force);
62075
+ }
62076
+ } else {
62077
+ generateFallbackTemplates(contextDir, opts.preset, projectName, opts.force);
62078
+ }
62079
+ } else {
62080
+ console.log(`${COLORS.bold}[2/5] Seed templates${COLORS.reset}`);
62081
+ generateFallbackTemplates(contextDir, opts.preset, projectName, opts.force);
61910
62082
  }
61911
- if (generated > 0) print.success(` Created ${generated} template(s) in ${CONTEXT_DIR}/`);
61912
- if (skipped > 0) print.dim(` Skipped ${skipped} (already exist)`);
61913
62083
  console.log("");
61914
62084
  if (opts.editFirst) {
61915
62085
  print.info("Templates created. Edit them, then run:");
@@ -61962,13 +62132,39 @@ function registerGoCommand(program3) {
61962
62132
  console.log("");
61963
62133
  console.log(`${COLORS.green}${COLORS.bold}Setup complete!${COLORS.reset}`);
61964
62134
  console.log("");
61965
- console.log(`${COLORS.bold}Next:${COLORS.reset}`);
61966
- console.log(" 1. Edit planning/TODO.md with your real tasks");
61967
- console.log(" 2. bootspring build next # Start the first task");
61968
- console.log(" 3. bootspring build done # Mark it complete");
62135
+ if (pitch) {
62136
+ console.log(`${COLORS.bold}Next:${COLORS.reset}`);
62137
+ console.log(" 1. Review .bootspring/context/ \u2014 tweak anything the AI got wrong");
62138
+ console.log(" 2. Edit planning/TODO.md with your real tasks");
62139
+ console.log(" 3. bootspring build next # Start the first task");
62140
+ } else {
62141
+ console.log(`${COLORS.bold}Next:${COLORS.reset}`);
62142
+ console.log(' 1. Edit the docs in .bootspring/context/ (or run: bootspring seed synthesize "your idea")');
62143
+ console.log(" 2. bootspring seed merge # Re-merge after edits");
62144
+ console.log(" 3. bootspring build next # Start the first task");
62145
+ }
61969
62146
  console.log("");
61970
62147
  });
61971
62148
  }
62149
+ function generateFallbackTemplates(contextDir, preset, projectName, force) {
62150
+ if (!fs64.existsSync(contextDir)) fs64.mkdirSync(contextDir, { recursive: true });
62151
+ const docs = PRESETS[preset] || PRESETS.startup;
62152
+ let generated = 0;
62153
+ let skipped = 0;
62154
+ for (const docType of docs) {
62155
+ const meta3 = CONTEXT_DOCS[docType];
62156
+ if (!meta3) continue;
62157
+ const filePath = path65.join(contextDir, meta3.name);
62158
+ if (fs64.existsSync(filePath) && !force) {
62159
+ skipped++;
62160
+ continue;
62161
+ }
62162
+ fs64.writeFileSync(filePath, generateDocTemplate(docType, projectName));
62163
+ generated++;
62164
+ }
62165
+ if (generated > 0) print.success(` Created ${generated} template(s) in .bootspring/context/`);
62166
+ if (skipped > 0) print.dim(` Skipped ${skipped} (already exist)`);
62167
+ }
61972
62168
 
61973
62169
  // src/commands/seed.ts
61974
62170
  var CONTEXT_DOCS2 = {
@@ -62478,7 +62674,7 @@ ${COLORS.bold}Detected Features:${COLORS.reset}`);
62478
62674
  console.log("");
62479
62675
  print.info(`${generated} generated, ${skipped} skipped in ${CONTEXT_DIR2}/`);
62480
62676
  });
62481
- seed.command("merge").alias("synthesize").description("Merge context documents into planning/SEED.md").option("--output <path>", "Output file", "planning/SEED.md").option("--force", "Overwrite existing seed file").action((opts) => {
62677
+ seed.command("merge").description("Merge context documents into planning/SEED.md").option("--output <path>", "Output file", "planning/SEED.md").option("--force", "Overwrite existing seed file").action((opts) => {
62482
62678
  print.header("Seed Merge");
62483
62679
  const cwd = process.cwd();
62484
62680
  const contextDir = getContextDir();
@@ -62744,10 +62940,122 @@ ${COLORS.bold}Detected Features:${COLORS.reset}`);
62744
62940
  print.success("Document looks good!");
62745
62941
  }
62746
62942
  });
62943
+ seed.command("synthesize").alias("ai").description("Generate all context docs from a one-liner pitch using AI").argument("<pitch>", "Your project idea in one sentence").option("--preset <preset>", "Document set (essential, startup, full, technical)", "startup").option("--provider <provider>", "AI provider: anthropic, openai, or gemini", "anthropic").option("--force", "Overwrite existing context docs").option("--json", "Output as JSON").action(async (pitch, opts) => {
62944
+ let api;
62945
+ try {
62946
+ const core = await Promise.resolve().then(() => (init_src2(), src_exports2));
62947
+ api = core.api;
62948
+ } catch {
62949
+ print.error("Failed to load API client. Run `bootspring auth login` first.");
62950
+ return;
62951
+ }
62952
+ const cwd = process.cwd();
62953
+ const contextDir = path66.join(cwd, CONTEXT_DIR2);
62954
+ const { name: projectName, stack, features } = detectStack2(cwd);
62955
+ if (!opts.json) {
62956
+ print.header("Seed Synthesize");
62957
+ print.info(`Pitch: "${pitch}"`);
62958
+ if (stack.length > 0) print.dim(`Stack: ${stack.join(", ")}`);
62959
+ console.log("");
62960
+ }
62961
+ const spinner = opts.json ? null : createSpinner("Generating context docs with AI...").start();
62962
+ try {
62963
+ const result = await api.request("POST", "/seed/synthesize", {
62964
+ pitch,
62965
+ projectName,
62966
+ stack,
62967
+ features,
62968
+ preset: opts.preset,
62969
+ provider: opts.provider
62970
+ }, { timeout: 12e4 });
62971
+ if (!result?.documents || Object.keys(result.documents).length === 0) {
62972
+ spinner?.fail("AI returned no documents");
62973
+ print.error("No documents were generated. Try rephrasing your pitch.");
62974
+ return;
62975
+ }
62976
+ if (!fs65.existsSync(contextDir)) {
62977
+ fs65.mkdirSync(contextDir, { recursive: true });
62978
+ }
62979
+ let written = 0;
62980
+ const writtenFiles = [];
62981
+ for (const [key, content] of Object.entries(result.documents)) {
62982
+ if (typeof content !== "string") continue;
62983
+ const fileName = `${key}.md`;
62984
+ const filePath = path66.join(contextDir, fileName);
62985
+ if (fs65.existsSync(filePath) && !opts.force) {
62986
+ if (!opts.json) print.dim(` Skipped ${fileName} (exists, use --force)`);
62987
+ continue;
62988
+ }
62989
+ fs65.writeFileSync(filePath, content);
62990
+ written++;
62991
+ writtenFiles.push(fileName);
62992
+ }
62993
+ spinner?.succeed(`Generated ${written} document(s) in ${result.metadata?.durationMs || "?"}ms`);
62994
+ if (opts.json) {
62995
+ console.log(JSON.stringify({
62996
+ documents: result.documents,
62997
+ files: writtenFiles,
62998
+ metadata: result.metadata
62999
+ }, null, 2));
63000
+ return;
63001
+ }
63002
+ console.log("");
63003
+ for (const f of writtenFiles) {
63004
+ print.success(` ${CONTEXT_DIR2}/${f}`);
63005
+ }
63006
+ if (written > 0) {
63007
+ console.log("");
63008
+ print.info("Merging into planning/SEED.md...");
63009
+ const validDocs = Object.values(CONTEXT_DOCS2).map((d) => d.name);
63010
+ const files = fs65.readdirSync(contextDir).filter((f) => f.endsWith(".md") && f !== "README.md").sort((a, b) => {
63011
+ const ai = validDocs.indexOf(a);
63012
+ const bi = validDocs.indexOf(b);
63013
+ return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
63014
+ });
63015
+ if (files.length > 0) {
63016
+ const sections = files.map((f) => fs65.readFileSync(path66.join(contextDir, f), "utf-8"));
63017
+ const planningDir = path66.join(cwd, "planning");
63018
+ if (!fs65.existsSync(planningDir)) fs65.mkdirSync(planningDir, { recursive: true });
63019
+ fs65.writeFileSync(path66.join(planningDir, "SEED.md"), sections.join("\n\n---\n\n"));
63020
+ print.success(` planning/SEED.md (${files.length} docs merged)`);
63021
+ }
63022
+ const genFiles = runGenerate(cwd, { force: false, full: true });
63023
+ if (genFiles.length > 0) {
63024
+ for (const f of genFiles) {
63025
+ print.success(` ${f}`);
63026
+ }
63027
+ }
63028
+ const buildResult = initBuildFromSeed(cwd);
63029
+ if (buildResult.initialized) {
63030
+ print.success(` Build initialized with ${buildResult.taskCount} tasks`);
63031
+ }
63032
+ }
63033
+ console.log("");
63034
+ console.log(`${COLORS.green}${COLORS.bold}Done!${COLORS.reset} Your project context is ready.`);
63035
+ console.log("");
63036
+ console.log(`${COLORS.bold}Next:${COLORS.reset}`);
63037
+ console.log(" 1. Review the docs in .bootspring/context/");
63038
+ console.log(" 2. Edit planning/TODO.md with your real tasks");
63039
+ console.log(" 3. bootspring build next # Start building");
63040
+ } catch (err) {
63041
+ spinner?.fail("AI generation failed");
63042
+ const message = err instanceof Error ? err.message : String(err);
63043
+ if (message.includes("Cannot connect") || message.includes("ECONNREFUSED")) {
63044
+ print.error("Cannot reach Bootspring API. Check your internet connection.");
63045
+ } else if (message.includes("Authentication") || message.includes("401")) {
63046
+ print.error("Not authenticated. Run `bootspring auth login` first.");
63047
+ } else {
63048
+ print.error(message);
63049
+ }
63050
+ }
63051
+ });
62747
63052
  seed.action(() => {
62748
63053
  seed.outputHelp();
62749
63054
  console.log("");
62750
- console.log(`${COLORS.bold}Workflow:${COLORS.reset}`);
63055
+ console.log(`${COLORS.bold}Quick start (AI-powered):${COLORS.reset}`);
63056
+ console.log(' bootspring seed synthesize "your project idea" # AI fills all context docs');
63057
+ console.log("");
63058
+ console.log(`${COLORS.bold}Manual workflow:${COLORS.reset}`);
62751
63059
  console.log(" bootspring seed init # Create .bootspring/context/ + templates");
62752
63060
  console.log(" (edit/drop files) # Add your project docs");
62753
63061
  console.log(" bootspring seed merge # Combine into planning/SEED.md");
@@ -73569,6 +73877,29 @@ ${COLORS.green}Task submitted:${COLORS.reset} ${res.id}`);
73569
73877
  handleError(error50);
73570
73878
  }
73571
73879
  });
73880
+ cmd.command("priority <taskId> <level>").description("Update queued swarm task priority").option("--json", "Output as JSON").action(async (taskId, level, opts) => {
73881
+ if (!requireAuth4()) return;
73882
+ const priority = level.toLowerCase();
73883
+ const valid = ["critical", "high", "normal", "low"];
73884
+ if (!valid.includes(priority)) {
73885
+ print(`${COLORS.red}Invalid priority:${COLORS.reset} ${level}. Must be one of: ${valid.join(", ")}`);
73886
+ return;
73887
+ }
73888
+ try {
73889
+ const res = await api_client_exports.request("PATCH", `/swarm/tasks/${taskId}/priority`, { priority });
73890
+ if (opts.json) {
73891
+ print(JSON.stringify(res, null, 2));
73892
+ return;
73893
+ }
73894
+ print(`
73895
+ ${COLORS.green}Task priority updated:${COLORS.reset} ${taskId}`);
73896
+ print(` Priority: ${priority}`);
73897
+ print(` Status: ${res.task?.status ?? "queued"}
73898
+ `);
73899
+ } catch (error50) {
73900
+ handleError(error50);
73901
+ }
73902
+ });
73572
73903
  cmd.command("plan <goal>").description("Generate a GOAP execution plan from a goal").option("--parallel", "Enable parallel task groups").option("--json", "Output as JSON").action(async (goal, opts) => {
73573
73904
  if (!requireAuth4()) return;
73574
73905
  try {
@@ -78042,40 +78373,45 @@ var import_child_process18 = require("child_process");
78042
78373
  init_src2();
78043
78374
  var HOOK_REGISTRY = {
78044
78375
  SessionStart: [
78045
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/stats/hooks/session-start.ts" },
78046
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/suggest/hooks/session-start.ts" },
78047
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/recall/hooks/session-start.ts" }
78376
+ { feature: "stats", script: "packages/session-intelligence/src/features/stats/hooks/session-start.ts" },
78377
+ { feature: "suggest", script: "packages/session-intelligence/src/features/suggest/hooks/session-start.ts" },
78378
+ { feature: "recall", script: "packages/session-intelligence/src/features/recall/hooks/session-start.ts" }
78048
78379
  ],
78049
78380
  UserPromptSubmit: [
78050
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/stats/hooks/user-prompt-submit.ts" },
78051
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/suggest/hooks/user-prompt-submit.ts" },
78052
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/recall/hooks/prompt-submit.ts" }
78381
+ { feature: "stats", script: "packages/session-intelligence/src/features/stats/hooks/user-prompt-submit.ts" },
78382
+ { feature: "suggest", script: "packages/session-intelligence/src/features/suggest/hooks/user-prompt-submit.ts" },
78383
+ { feature: "recall", script: "packages/session-intelligence/src/features/recall/hooks/prompt-submit.ts" }
78053
78384
  ],
78054
78385
  PreToolUse: [
78055
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/suggest/hooks/pre-tool-use.ts" },
78056
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/recall/hooks/pre-tool-use.ts" }
78386
+ { feature: "suggest", script: "packages/session-intelligence/src/features/suggest/hooks/pre-tool-use.ts" },
78387
+ { feature: "recall", script: "packages/session-intelligence/src/features/recall/hooks/pre-tool-use.ts" }
78057
78388
  ],
78058
78389
  PostToolUse: [
78059
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/stats/hooks/post-tool-use.ts" },
78060
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/suggest/hooks/post-tool-use.ts" }
78390
+ { feature: "stats", script: "packages/session-intelligence/src/features/stats/hooks/post-tool-use.ts" },
78391
+ { feature: "suggest", script: "packages/session-intelligence/src/features/suggest/hooks/post-tool-use.ts" }
78061
78392
  ],
78062
78393
  PreCompact: [
78063
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/stats/hooks/pre-compact.ts" }
78394
+ { feature: "stats", script: "packages/session-intelligence/src/features/stats/hooks/pre-compact.ts" }
78064
78395
  ],
78065
78396
  SubagentStart: [
78066
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/stats/hooks/subagent-start.ts" }
78397
+ { feature: "stats", script: "packages/session-intelligence/src/features/stats/hooks/subagent-start.ts" }
78067
78398
  ],
78068
78399
  SubagentStop: [
78069
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/stats/hooks/subagent-stop.ts" }
78400
+ { feature: "stats", script: "packages/session-intelligence/src/features/stats/hooks/subagent-stop.ts" }
78070
78401
  ],
78071
78402
  SessionEnd: [
78072
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/stats/hooks/session-end.ts" },
78073
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/suggest/hooks/session-end.ts" },
78074
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/recall/hooks/stop.ts" }
78403
+ { feature: "stats", script: "packages/session-intelligence/src/features/stats/hooks/session-end.ts" },
78404
+ { feature: "suggest", script: "packages/session-intelligence/src/features/suggest/hooks/session-end.ts" },
78405
+ { feature: "recall", script: "packages/session-intelligence/src/features/recall/hooks/stop.ts" }
78075
78406
  ]
78076
78407
  };
78077
78408
  var ALL_EVENTS = Object.keys(HOOK_REGISTRY);
78078
- var SETTINGS_FILE = ".claude/settings.local.json";
78409
+ var CLAUDE_SETTINGS_FILE = ".claude/settings.local.json";
78410
+ var CODEX_HOOKS_FILE = ".codex/hooks.json";
78411
+ var HOOK_TARGETS = [
78412
+ { id: "claude", label: "Claude", file: CLAUDE_SETTINGS_FILE },
78413
+ { id: "codex", label: "Codex", file: CODEX_HOOKS_FILE }
78414
+ ];
78079
78415
  function findMonorepoRoot(cwd) {
78080
78416
  let dir = cwd;
78081
78417
  for (let i = 0; i < 10; i++) {
@@ -78086,19 +78422,27 @@ function findMonorepoRoot(cwd) {
78086
78422
  }
78087
78423
  return null;
78088
78424
  }
78089
- function readSettings(cwd) {
78090
- const settingsPath = path107.join(cwd, SETTINGS_FILE);
78091
- if (!fs106.existsSync(settingsPath)) return {};
78425
+ function readJsonFile(cwd, relPath) {
78426
+ const filePath = path107.join(cwd, relPath);
78427
+ if (!fs106.existsSync(filePath)) return {};
78092
78428
  try {
78093
- return JSON.parse(fs106.readFileSync(settingsPath, "utf8"));
78429
+ return JSON.parse(fs106.readFileSync(filePath, "utf8"));
78094
78430
  } catch {
78095
78431
  return {};
78096
78432
  }
78097
78433
  }
78098
- function writeSettings(cwd, settings) {
78099
- const dir = path107.join(cwd, ".claude");
78434
+ function writeJsonFile(cwd, relPath, data) {
78435
+ const dir = path107.dirname(path107.join(cwd, relPath));
78100
78436
  if (!fs106.existsSync(dir)) fs106.mkdirSync(dir, { recursive: true });
78101
- fs106.writeFileSync(path107.join(cwd, SETTINGS_FILE), JSON.stringify(settings, null, 2) + "\n");
78437
+ fs106.writeFileSync(path107.join(cwd, relPath), JSON.stringify(data, null, 2) + "\n");
78438
+ }
78439
+ function installHooksForTarget(cwd, relPath, hooksConfig) {
78440
+ const settings = readJsonFile(cwd, relPath);
78441
+ settings.hooks = hooksConfig;
78442
+ writeJsonFile(cwd, relPath, settings);
78443
+ }
78444
+ function hookEntriesUseDispatcher(entries) {
78445
+ return JSON.stringify(entries || "").includes("hook dispatch");
78102
78446
  }
78103
78447
  function buildDispatcherCommand(monorepoRoot) {
78104
78448
  const cliEntry = path107.join(monorepoRoot, "monorepo/apps/cli/bin/bootspring.js");
@@ -78152,7 +78496,7 @@ async function dispatchEvent(event) {
78152
78496
  });
78153
78497
  } catch (err) {
78154
78498
  const msg = err.stderr?.toString?.().slice(0, 200) || err.message || "unknown error";
78155
- process.stderr.write(`[hook-dispatcher] ${handler.package}/${path107.basename(handler.script)} failed: ${msg}
78499
+ process.stderr.write(`[hook-dispatcher] ${handler.feature}/${path107.basename(handler.script)} failed: ${msg}
78156
78500
  `);
78157
78501
  }
78158
78502
  }
@@ -78181,8 +78525,8 @@ function readStdinPayload() {
78181
78525
  });
78182
78526
  }
78183
78527
  function registerHookCommand(program3) {
78184
- const hook = program3.command("hook").description("Manage Claude Code hook dispatcher (observer \u2192 autopilot \u2192 memory)");
78185
- hook.command("install").description("Write deterministic hook config to .claude/settings.local.json").option("--dry-run", "Show what would be written without modifying files").action((opts) => {
78528
+ const hook = program3.command("hook").description("Manage Bootspring hook dispatcher for Claude Code and Codex (stats \u2192 suggest \u2192 recall)");
78529
+ hook.command("install").description("Write deterministic hook config to Claude and Codex settings").option("--dry-run", "Show what would be written without modifying files").action((opts) => {
78186
78530
  const cwd = process.cwd();
78187
78531
  const monorepoRoot = findMonorepoRoot(cwd);
78188
78532
  if (!monorepoRoot) {
@@ -78196,17 +78540,19 @@ function registerHookCommand(program3) {
78196
78540
  console.log(`
78197
78541
  ${COLORS.bold}Hook config (dry run):${COLORS.reset}
78198
78542
  `);
78199
- console.log(JSON.stringify({ hooks: hooksConfig }, null, 2));
78543
+ console.log(JSON.stringify(Object.fromEntries(
78544
+ HOOK_TARGETS.map((target) => [target.file, { hooks: hooksConfig }])
78545
+ ), null, 2));
78200
78546
  console.log(`
78201
- ${COLORS.dim}${ALL_EVENTS.length} events, ${totalHandlers} handlers, ordering: observer \u2192 autopilot \u2192 memory${COLORS.reset}
78547
+ ${COLORS.dim}${ALL_EVENTS.length} events, ${totalHandlers} handlers, ordering: stats \u2192 suggest \u2192 recall${COLORS.reset}
78202
78548
  `);
78203
78549
  return;
78204
78550
  }
78205
- const settings = readSettings(monorepoRoot);
78206
- settings.hooks = hooksConfig;
78207
- writeSettings(monorepoRoot, settings);
78208
- print.success(`Wrote ${ALL_EVENTS.length} hook events (${totalHandlers} handlers) to ${SETTINGS_FILE}`);
78209
- console.log(` ${COLORS.dim}Ordering: observer \u2192 autopilot \u2192 memory (deterministic)${COLORS.reset}`);
78551
+ for (const target of HOOK_TARGETS) {
78552
+ installHooksForTarget(monorepoRoot, target.file, hooksConfig);
78553
+ }
78554
+ print.success(`Wrote ${ALL_EVENTS.length} hook events (${totalHandlers} handlers) to ${HOOK_TARGETS.map((t) => t.file).join(" and ")}`);
78555
+ console.log(` ${COLORS.dim}Ordering: stats \u2192 suggest \u2192 recall (deterministic)${COLORS.reset}`);
78210
78556
  console.log(` ${COLORS.dim}Error isolation: each handler runs in its own subprocess${COLORS.reset}`);
78211
78557
  console.log(` ${COLORS.dim}Events: ${ALL_EVENTS.join(", ")}${COLORS.reset}
78212
78558
  `);
@@ -78214,45 +78560,54 @@ ${COLORS.dim}${ALL_EVENTS.length} events, ${totalHandlers} handlers, ordering: o
78214
78560
  hook.command("status").description("Show installed hook configuration").action(() => {
78215
78561
  const cwd = process.cwd();
78216
78562
  const monorepoRoot = findMonorepoRoot(cwd) || cwd;
78217
- const settings = readSettings(monorepoRoot);
78218
- const hooks = settings.hooks || {};
78563
+ const targetStates = HOOK_TARGETS.map((target) => ({
78564
+ ...target,
78565
+ hooks: readJsonFile(monorepoRoot, target.file).hooks || {}
78566
+ }));
78219
78567
  console.log(`
78220
78568
  ${COLORS.bold}Hook Status${COLORS.reset}
78221
78569
  `);
78222
- if (Object.keys(hooks).length === 0) {
78223
- print.warning("No hooks configured in .claude/settings.local.json");
78570
+ if (targetStates.every((target) => Object.keys(target.hooks).length === 0)) {
78571
+ print.warning(`No hooks configured in ${HOOK_TARGETS.map((t) => t.file).join(" or ")}`);
78224
78572
  print.dim('Run "bootspring hook install" to set up the deterministic dispatcher.');
78225
78573
  console.log();
78226
78574
  return;
78227
78575
  }
78228
- const isDispatcher = JSON.stringify(hooks).includes("hook dispatch");
78229
78576
  for (const event of ALL_EVENTS) {
78230
- const entries = hooks[event];
78577
+ const installedTargets = targetStates.filter((target) => hookEntriesUseDispatcher(target.hooks[event]));
78231
78578
  const registry4 = HOOK_REGISTRY[event];
78232
- const mark = entries ? `${COLORS.green}\u2713${COLORS.reset}` : `${COLORS.dim}\xB7${COLORS.reset}`;
78233
- const handlers = registry4.map((h) => `${COLORS.cyan}${h.package}${COLORS.reset}`).join(" \u2192 ");
78234
- console.log(` ${mark} ${COLORS.bold}${event.padEnd(20)}${COLORS.reset} ${handlers}`);
78579
+ const mark = installedTargets.length === HOOK_TARGETS.length ? `${COLORS.green}\u2713${COLORS.reset}` : `${COLORS.yellow}!${COLORS.reset}`;
78580
+ const handlers = registry4.map((h) => `${COLORS.cyan}${h.feature}${COLORS.reset}`).join(" \u2192 ");
78581
+ const targetLabels = installedTargets.map((t) => t.label).join(", ") || "none";
78582
+ console.log(` ${mark} ${COLORS.bold}${event.padEnd(20)}${COLORS.reset} ${handlers} ${COLORS.dim}(${targetLabels})${COLORS.reset}`);
78235
78583
  }
78236
78584
  console.log();
78237
- if (isDispatcher) {
78238
- print.success("Using centralized dispatcher with deterministic ordering.");
78239
- } else {
78240
- print.warning("Hooks are configured but not using the centralized dispatcher.");
78241
- print.dim('Run "bootspring hook install" to switch to the deterministic dispatcher.');
78585
+ for (const target of targetStates) {
78586
+ const isDispatcher = ALL_EVENTS.every((event) => hookEntriesUseDispatcher(target.hooks[event]));
78587
+ if (isDispatcher) {
78588
+ print.success(`${target.label}: using centralized dispatcher (${target.file}).`);
78589
+ } else {
78590
+ print.warning(`${target.label}: hooks are missing or not using the centralized dispatcher (${target.file}).`);
78591
+ }
78242
78592
  }
78243
78593
  console.log();
78244
78594
  });
78245
- hook.command("uninstall").description("Remove all hook entries from .claude/settings.local.json").action(() => {
78595
+ hook.command("uninstall").description("Remove all hook entries from Claude and Codex settings").action(() => {
78246
78596
  const cwd = process.cwd();
78247
78597
  const monorepoRoot = findMonorepoRoot(cwd) || cwd;
78248
- const settings = readSettings(monorepoRoot);
78249
- if (!settings.hooks) {
78598
+ let removed = 0;
78599
+ for (const target of HOOK_TARGETS) {
78600
+ const settings = readJsonFile(monorepoRoot, target.file);
78601
+ if (!settings.hooks) continue;
78602
+ delete settings.hooks;
78603
+ writeJsonFile(monorepoRoot, target.file, settings);
78604
+ removed++;
78605
+ }
78606
+ if (removed === 0) {
78250
78607
  print.info("No hooks configured \u2014 nothing to remove.");
78251
78608
  return;
78252
78609
  }
78253
- delete settings.hooks;
78254
- writeSettings(monorepoRoot, settings);
78255
- print.success("Removed all hook entries from .claude/settings.local.json");
78610
+ print.success(`Removed hook entries from ${removed} assistant config file(s).`);
78256
78611
  });
78257
78612
  hook.command("dispatch <event>").description("(internal) Dispatch a hook event to handlers in order").action(async (event) => {
78258
78613
  await dispatchEvent(event);
@@ -78272,6 +78627,7 @@ var SUBCOMMANDS = {
78272
78627
  auth: ["login", "logout", "whoami", "status", "clear", "register", "switch", "reauth"],
78273
78628
  build: [
78274
78629
  "status",
78630
+ "progress",
78275
78631
  "task",
78276
78632
  "done",
78277
78633
  "plan",
@@ -477,7 +477,7 @@ interface InstallContext {
477
477
  scriptPath: string;
478
478
  }
479
479
  declare const PACKAGE_NAME = "@girardmedia/bootspring";
480
- declare const CURRENT_VERSION = "3.1.0";
480
+ declare const CURRENT_VERSION = "3.2.0";
481
481
  declare const DEFAULT_INTERVAL_MS: number;
482
482
  declare const STATE_PATH: string;
483
483
  declare function compareVersions(a: string, b: string): number;
package/dist/core.js CHANGED
@@ -379,7 +379,7 @@ var init_release = __esm({
379
379
  "../../packages/shared/src/release.ts"() {
380
380
  "use strict";
381
381
  init_cjs_shims();
382
- BOOTSPRING_VERSION = "3.1.0";
382
+ BOOTSPRING_VERSION = "3.2.0";
383
383
  BOOTSPRING_PACKAGE_NAME = "@girardmedia/bootspring";
384
384
  }
385
385
  });
@@ -21643,7 +21643,7 @@ ${COLORS2.dim}Run "bootspring mcp" for server options${COLORS2.reset}
21643
21643
  console.log(`${COLORS2.dim}Run "bootspring mcp" for setup instructions.${COLORS2.reset}
21644
21644
  `);
21645
21645
  }
21646
- var BOOTSPRING_VERSION2 = "3.1.0";
21646
+ var BOOTSPRING_VERSION2 = "3.2.0";
21647
21647
  var BOOTSPRING_PACKAGE_NAME2 = "@girardmedia/bootspring";
21648
21648
  var REDACTED2 = "[REDACTED]";
21649
21649
  var SENSITIVE_KEY_PATTERN2 = /(?:^|[_-])(api[_-]?key|token|refresh[_-]?token|authorization|x[_-]?api[_-]?key|project[_-]?id)$/i;
@@ -21691,7 +21691,7 @@ var require_package = __commonJS({
21691
21691
  "../../../package.json"(exports2, module2) {
21692
21692
  module2.exports = {
21693
21693
  name: "bootspring-workspace",
21694
- version: "3.1.0",
21694
+ version: "3.2.0",
21695
21695
  private: true,
21696
21696
  description: "Workspace tooling for the Bootspring monorepo",
21697
21697
  keywords: [
@@ -31464,7 +31464,7 @@ var init_release = __esm({
31464
31464
  "../../packages/shared/src/release.ts"() {
31465
31465
  "use strict";
31466
31466
  init_cjs_shims();
31467
- BOOTSPRING_VERSION = "3.1.0";
31467
+ BOOTSPRING_VERSION = "3.2.0";
31468
31468
  BOOTSPRING_PACKAGE_NAME = "@girardmedia/bootspring";
31469
31469
  }
31470
31470
  });
@@ -52365,7 +52365,7 @@ var require_package = __commonJS({
52365
52365
  "../../../package.json"(exports2, module2) {
52366
52366
  module2.exports = {
52367
52367
  name: "bootspring-workspace",
52368
- version: "3.1.0",
52368
+ version: "3.2.0",
52369
52369
  private: true,
52370
52370
  description: "Workspace tooling for the Bootspring monorepo",
52371
52371
  keywords: [
@@ -52503,6 +52503,15 @@ var core = require_dist2();
52503
52503
  var api2 = core.api;
52504
52504
  var auth2 = core.auth;
52505
52505
  var VERSION = require_package().version;
52506
+ var BUILD_PROGRESS_ARTIFACT = "planning/BUILD_PROGRESS.md";
52507
+ var VISIBLE_WORK_CHECKPOINTS = [
52508
+ "Announce the active task and intended checkpoints in the chat or terminal.",
52509
+ "Inspect the relevant files and summarize what changed before editing.",
52510
+ "Patch the implementation and keep unrelated files untouched.",
52511
+ "Add or update focused tests when the task changes behavior.",
52512
+ "Run the relevant verification commands and report pass/fail output.",
52513
+ "Update the build state with action=done only after verification."
52514
+ ];
52506
52515
  function getProjectRoot() {
52507
52516
  return process.cwd();
52508
52517
  }
@@ -52579,6 +52588,70 @@ function formatBuildTask(task) {
52579
52588
  dependencies: task.dependencies || []
52580
52589
  };
52581
52590
  }
52591
+ function renderVisibleProgressBar(percent, width = 30) {
52592
+ const clamped = Math.max(0, Math.min(100, Math.round(percent || 0)));
52593
+ const filled = Math.round(clamped / 100 * width);
52594
+ return `[${"#".repeat(filled)}${".".repeat(width - filled)}] ${clamped}%`;
52595
+ }
52596
+ function writeVisibleProgressArtifact(task, stats, event) {
52597
+ const fs3 = require("fs");
52598
+ const path3 = require("path");
52599
+ const artifactPath = path3.join(getProjectRoot(), BUILD_PROGRESS_ARTIFACT);
52600
+ const progress = {
52601
+ completed: stats?.completed || 0,
52602
+ pending: stats?.pending || 0,
52603
+ inProgress: stats?.inProgress || 0,
52604
+ total: stats?.total || 0,
52605
+ percent: stats?.percent || 0
52606
+ };
52607
+ const taskData = formatBuildTask(task);
52608
+ fs3.mkdirSync(path3.dirname(artifactPath), { recursive: true });
52609
+ fs3.writeFileSync(artifactPath, [
52610
+ "# Build Progress",
52611
+ "",
52612
+ `Updated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
52613
+ `Event: ${event}`,
52614
+ `Overall: ${progress.completed}/${progress.total} ${renderVisibleProgressBar(progress.percent)}`,
52615
+ "",
52616
+ "## Active Task",
52617
+ "",
52618
+ taskData ? [
52619
+ `- ID: ${taskData.id}`,
52620
+ `- Title: ${taskData.title}`,
52621
+ `- Phase: ${taskData.phase || "Unknown"}`,
52622
+ taskData.description ? `- Description: ${taskData.description}` : null,
52623
+ taskData.dependencies?.length ? `- Dependencies: ${taskData.dependencies.join(", ")}` : null
52624
+ ].filter(Boolean).join("\n") : "No active or pending task.",
52625
+ "",
52626
+ "## Acceptance Criteria",
52627
+ "",
52628
+ taskData?.acceptanceCriteria?.length ? taskData.acceptanceCriteria.map((item) => `- [ ] ${item}`).join("\n") : "- [ ] No task-specific acceptance criteria recorded.",
52629
+ "",
52630
+ "## Visible Work Contract",
52631
+ "",
52632
+ VISIBLE_WORK_CHECKPOINTS.map((item) => `- [ ] ${item}`).join("\n"),
52633
+ "",
52634
+ "## Host Notes",
52635
+ "",
52636
+ "- Claude Code and Codex CLI can stream terminal output directly.",
52637
+ "- Codex Desktop, Claude Desktop, and other MCP hosts should keep a visible plan/checklist updated while the terminal is hidden.",
52638
+ "- Update this file after inspection, edits, verification, and completion so the workspace has a durable progress surface.",
52639
+ ""
52640
+ ].join("\n"));
52641
+ return BUILD_PROGRESS_ARTIFACT;
52642
+ }
52643
+ function buildVisibilityContract(task, stats, event) {
52644
+ const artifact = writeVisibleProgressArtifact(task, stats, event);
52645
+ return {
52646
+ artifact,
52647
+ checkpoints: VISIBLE_WORK_CHECKPOINTS,
52648
+ hostInstructions: [
52649
+ "Immediately show the active task and a short visible checklist to the user.",
52650
+ "Post visible updates after inspection, implementation, verification, and completion.",
52651
+ `Keep ${artifact} current when the host hides terminal output.`
52652
+ ]
52653
+ };
52654
+ }
52582
52655
  function autoCommitTask(taskId, taskTitle) {
52583
52656
  try {
52584
52657
  const { execSync } = require("child_process");
@@ -52685,6 +52758,7 @@ async function executeLocalBuild(args) {
52685
52758
  status: state.status,
52686
52759
  progress: { completed: stats.completed, pending: stats.pending, inProgress: stats.inProgress, total: stats.total, percent: stats.percent },
52687
52760
  currentTask: currentTask ? { id: currentTask.id, title: currentTask.title } : null,
52761
+ visibility: buildVisibilityContract(currentTask, stats, "status"),
52688
52762
  nextAction: currentTask ? "Complete the current task, then use action=done" : "Use action=next to get the next task"
52689
52763
  }, null, 2) }] };
52690
52764
  }
@@ -52698,6 +52772,7 @@ async function executeLocalBuild(args) {
52698
52772
  message: "Task already in progress",
52699
52773
  state: "in_progress",
52700
52774
  task: formatBuildTask(inProgress),
52775
+ visibility: buildVisibilityContract(inProgress, buildGetStats(state), "already_in_progress"),
52701
52776
  hint: "Complete this task with action=done, or use action=skip to skip it"
52702
52777
  }, null, 2) }] };
52703
52778
  }
@@ -52714,6 +52789,7 @@ async function executeLocalBuild(args) {
52714
52789
  state: "task_ready",
52715
52790
  file: "planning/TODO.md",
52716
52791
  progress: { completed: stats.completed, total: stats.total, percent: stats.percent },
52792
+ visibility: buildVisibilityContract(nextTask, stats, "task_ready"),
52717
52793
  instructions: [
52718
52794
  `Find ${nextTask.id} in planning/TODO.md for full details`,
52719
52795
  "Implement the task in the codebase",
@@ -52734,6 +52810,7 @@ async function executeLocalBuild(args) {
52734
52810
  }
52735
52811
  return { content: [{ type: "text", text: JSON.stringify({
52736
52812
  task: formatBuildTask(currentTask),
52813
+ visibility: buildVisibilityContract(currentTask, buildGetStats(state), "current"),
52737
52814
  instructions: ["Implement this task in the codebase", "Run quality checks (lint, test)", "Commit your changes", "Use action=done to mark complete"]
52738
52815
  }, null, 2) }] };
52739
52816
  }
@@ -52762,6 +52839,7 @@ async function executeLocalBuild(args) {
52762
52839
  nextTask: nextTask ? { ...formatBuildTask(nextTask), file: "planning/TODO.md" } : null,
52763
52840
  allComplete: !nextTask,
52764
52841
  state: nextTask ? "advanced" : "all_complete",
52842
+ visibility: buildVisibilityContract(nextTask, stats, nextTask ? "advanced" : "all_complete"),
52765
52843
  message: nextTask ? `Done! Committed ${inProgress.id}. Next: ${nextTask.id} \u2014 ${nextTask.title}` : "Build complete! All tasks finished."
52766
52844
  }, null, 2) }] };
52767
52845
  }
@@ -52782,6 +52860,7 @@ async function executeLocalBuild(args) {
52782
52860
  return { content: [{ type: "text", text: JSON.stringify({
52783
52861
  skipped: { id: inProgress.id, title: inProgress.title, reason: args?.reason || "Skipped" },
52784
52862
  nextTask: nextTask ? formatBuildTask(nextTask) : null,
52863
+ visibility: buildVisibilityContract(nextTask, buildGetStats(state), nextTask ? "skipped_next_ready" : "skipped_all_done"),
52785
52864
  message: nextTask ? `Skipped. Next: ${nextTask.title}` : "No more tasks available"
52786
52865
  }, null, 2) }] };
52787
52866
  }
@@ -52807,13 +52886,14 @@ async function executeLocalBuild(args) {
52807
52886
  const nextTask2 = buildGetNextTask(state);
52808
52887
  if (nextTask2) buildUpdateTaskStatus(state, nextTask2.id, "in_progress");
52809
52888
  saveBuildState(state);
52810
- const stats = buildGetStats(state);
52889
+ const stats2 = buildGetStats(state);
52811
52890
  return { content: [{ type: "text", text: JSON.stringify({
52812
52891
  state: nextTask2 ? "advanced" : "all_complete",
52813
52892
  autoCompleted: true,
52814
52893
  completed: { id: activeTask.id, title: activeTask.title },
52815
52894
  nextTask: nextTask2 ? formatBuildTask(nextTask2) : null,
52816
- progress: { completed: stats.completed, total: stats.total, percent: stats.percent }
52895
+ progress: { completed: stats2.completed, total: stats2.total, percent: stats2.percent },
52896
+ visibility: buildVisibilityContract(nextTask2, stats2, nextTask2 ? "advanced" : "all_complete")
52817
52897
  }, null, 2) }] };
52818
52898
  }
52819
52899
  } catch {
@@ -52822,20 +52902,23 @@ async function executeLocalBuild(args) {
52822
52902
  return { content: [{ type: "text", text: JSON.stringify({
52823
52903
  state: "in_progress",
52824
52904
  task: formatBuildTask(activeTask),
52905
+ visibility: buildVisibilityContract(activeTask, buildGetStats(state), "in_progress"),
52825
52906
  hint: "Finish implementation and use action=done, or call action=advance with autoDone=true after commit"
52826
52907
  }, null, 2) }] };
52827
52908
  }
52828
52909
  const nextTask = buildGetNextTask(state);
52829
52910
  if (!nextTask) {
52830
- const stats = buildGetStats(state);
52831
- return { content: [{ type: "text", text: JSON.stringify({ state: "all_complete", message: "All tasks complete!", progress: { completed: stats.completed, total: stats.total, percent: stats.percent } }, null, 2) }] };
52911
+ const stats2 = buildGetStats(state);
52912
+ return { content: [{ type: "text", text: JSON.stringify({ state: "all_complete", message: "All tasks complete!", progress: { completed: stats2.completed, total: stats2.total, percent: stats2.percent }, visibility: buildVisibilityContract(null, stats2, "all_complete") }, null, 2) }] };
52832
52913
  }
52833
52914
  buildUpdateTaskStatus(state, nextTask.id, "in_progress");
52834
52915
  saveBuildState(state);
52916
+ const stats = buildGetStats(state);
52835
52917
  return { content: [{ type: "text", text: JSON.stringify({
52836
52918
  state: "task_ready",
52837
52919
  task: formatBuildTask(nextTask),
52838
52920
  file: "planning/TODO.md",
52921
+ visibility: buildVisibilityContract(nextTask, stats, "task_ready"),
52839
52922
  hint: "Implement task, then call action=advance (or action=done) to continue loop"
52840
52923
  }, null, 2) }] };
52841
52924
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@girardmedia/bootspring",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Thin client for Bootspring cloud MCP, hosted agents, and paywalled workflow intelligence",
5
5
  "keywords": [
6
6
  "ai",