@girardmedia/bootspring 3.0.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.
package/dist/cli/index.js CHANGED
@@ -33,9 +33,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
33
33
  ));
34
34
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
35
35
 
36
- // ../../node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.8_tsx@4.21.0_typescript@5.9.3_yaml@2.8.3/node_modules/tsup/assets/cjs_shims.js
36
+ // ../../node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.14_tsx@4.21.0_typescript@5.9.3_yaml@2.8.3/node_modules/tsup/assets/cjs_shims.js
37
37
  var init_cjs_shims = __esm({
38
- "../../node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.8_tsx@4.21.0_typescript@5.9.3_yaml@2.8.3/node_modules/tsup/assets/cjs_shims.js"() {
38
+ "../../node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.14_tsx@4.21.0_typescript@5.9.3_yaml@2.8.3/node_modules/tsup/assets/cjs_shims.js"() {
39
39
  "use strict";
40
40
  }
41
41
  });
@@ -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.0.0";
3389
+ BOOTSPRING_VERSION = "3.2.0";
3390
3390
  BOOTSPRING_PACKAGE_NAME = "@girardmedia/bootspring";
3391
3391
  }
3392
3392
  });
@@ -32899,13 +32899,16 @@ var require_data = __commonJS({
32899
32899
  }
32900
32900
  });
32901
32901
 
32902
- // ../../node_modules/.pnpm/fast-uri@3.1.0/node_modules/fast-uri/lib/utils.js
32902
+ // ../../node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/lib/utils.js
32903
32903
  var require_utils = __commonJS({
32904
- "../../node_modules/.pnpm/fast-uri@3.1.0/node_modules/fast-uri/lib/utils.js"(exports2, module2) {
32904
+ "../../node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/lib/utils.js"(exports2, module2) {
32905
32905
  "use strict";
32906
32906
  init_cjs_shims();
32907
32907
  var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
32908
32908
  var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
32909
+ var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
32910
+ var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
32911
+ var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
32909
32912
  function stringArrayToHexStripped(input) {
32910
32913
  let acc = "";
32911
32914
  let code = 0;
@@ -33098,27 +33101,77 @@ var require_utils = __commonJS({
33098
33101
  }
33099
33102
  return output.join("");
33100
33103
  }
33101
- function normalizeComponentEncoding(component, esc4) {
33102
- const func = esc4 !== true ? escape : unescape;
33103
- if (component.scheme !== void 0) {
33104
- component.scheme = func(component.scheme);
33105
- }
33106
- if (component.userinfo !== void 0) {
33107
- component.userinfo = func(component.userinfo);
33108
- }
33109
- if (component.host !== void 0) {
33110
- component.host = func(component.host);
33104
+ var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
33105
+ var HOST_DELIM_RE = /[@/?#:]/g;
33106
+ var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
33107
+ function reescapeHostDelimiters(host, isIP) {
33108
+ const re = isIP ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
33109
+ re.lastIndex = 0;
33110
+ return host.replace(re, (ch) => HOST_DELIMS[ch]);
33111
+ }
33112
+ function normalizePercentEncoding(input, decodeUnreserved = false) {
33113
+ if (input.indexOf("%") === -1) {
33114
+ return input;
33111
33115
  }
33112
- if (component.path !== void 0) {
33113
- component.path = func(component.path);
33116
+ let output = "";
33117
+ for (let i = 0; i < input.length; i++) {
33118
+ if (input[i] === "%" && i + 2 < input.length) {
33119
+ const hex3 = input.slice(i + 1, i + 3);
33120
+ if (isHexPair(hex3)) {
33121
+ const normalizedHex = hex3.toUpperCase();
33122
+ const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
33123
+ if (decodeUnreserved && isUnreserved(decoded)) {
33124
+ output += decoded;
33125
+ } else {
33126
+ output += "%" + normalizedHex;
33127
+ }
33128
+ i += 2;
33129
+ continue;
33130
+ }
33131
+ }
33132
+ output += input[i];
33114
33133
  }
33115
- if (component.query !== void 0) {
33116
- component.query = func(component.query);
33134
+ return output;
33135
+ }
33136
+ function normalizePathEncoding(input) {
33137
+ let output = "";
33138
+ for (let i = 0; i < input.length; i++) {
33139
+ if (input[i] === "%" && i + 2 < input.length) {
33140
+ const hex3 = input.slice(i + 1, i + 3);
33141
+ if (isHexPair(hex3)) {
33142
+ const normalizedHex = hex3.toUpperCase();
33143
+ const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
33144
+ if (decoded !== "." && isUnreserved(decoded)) {
33145
+ output += decoded;
33146
+ } else {
33147
+ output += "%" + normalizedHex;
33148
+ }
33149
+ i += 2;
33150
+ continue;
33151
+ }
33152
+ }
33153
+ if (isPathCharacter(input[i])) {
33154
+ output += input[i];
33155
+ } else {
33156
+ output += escape(input[i]);
33157
+ }
33117
33158
  }
33118
- if (component.fragment !== void 0) {
33119
- component.fragment = func(component.fragment);
33159
+ return output;
33160
+ }
33161
+ function escapePreservingEscapes(input) {
33162
+ let output = "";
33163
+ for (let i = 0; i < input.length; i++) {
33164
+ if (input[i] === "%" && i + 2 < input.length) {
33165
+ const hex3 = input.slice(i + 1, i + 3);
33166
+ if (isHexPair(hex3)) {
33167
+ output += "%" + hex3.toUpperCase();
33168
+ i += 2;
33169
+ continue;
33170
+ }
33171
+ }
33172
+ output += escape(input[i]);
33120
33173
  }
33121
- return component;
33174
+ return output;
33122
33175
  }
33123
33176
  function recomposeAuthority(component) {
33124
33177
  const uriTokens = [];
@@ -33133,7 +33186,7 @@ var require_utils = __commonJS({
33133
33186
  if (ipV6res.isIPV6 === true) {
33134
33187
  host = `[${ipV6res.escapedHost}]`;
33135
33188
  } else {
33136
- host = component.host;
33189
+ host = reescapeHostDelimiters(host, false);
33137
33190
  }
33138
33191
  }
33139
33192
  uriTokens.push(host);
@@ -33147,7 +33200,10 @@ var require_utils = __commonJS({
33147
33200
  module2.exports = {
33148
33201
  nonSimpleDomain,
33149
33202
  recomposeAuthority,
33150
- normalizeComponentEncoding,
33203
+ reescapeHostDelimiters,
33204
+ normalizePercentEncoding,
33205
+ normalizePathEncoding,
33206
+ escapePreservingEscapes,
33151
33207
  removeDotSegments,
33152
33208
  isIPv4,
33153
33209
  isUUID,
@@ -33157,9 +33213,9 @@ var require_utils = __commonJS({
33157
33213
  }
33158
33214
  });
33159
33215
 
33160
- // ../../node_modules/.pnpm/fast-uri@3.1.0/node_modules/fast-uri/lib/schemes.js
33216
+ // ../../node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/lib/schemes.js
33161
33217
  var require_schemes = __commonJS({
33162
- "../../node_modules/.pnpm/fast-uri@3.1.0/node_modules/fast-uri/lib/schemes.js"(exports2, module2) {
33218
+ "../../node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/lib/schemes.js"(exports2, module2) {
33163
33219
  "use strict";
33164
33220
  init_cjs_shims();
33165
33221
  var { isUUID } = require_utils();
@@ -33368,17 +33424,17 @@ var require_schemes = __commonJS({
33368
33424
  }
33369
33425
  });
33370
33426
 
33371
- // ../../node_modules/.pnpm/fast-uri@3.1.0/node_modules/fast-uri/index.js
33427
+ // ../../node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/index.js
33372
33428
  var require_fast_uri = __commonJS({
33373
- "../../node_modules/.pnpm/fast-uri@3.1.0/node_modules/fast-uri/index.js"(exports2, module2) {
33429
+ "../../node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/index.js"(exports2, module2) {
33374
33430
  "use strict";
33375
33431
  init_cjs_shims();
33376
- var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils();
33432
+ var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
33377
33433
  var { SCHEMES, getSchemeHandler } = require_schemes();
33378
33434
  function normalize2(uri, options) {
33379
33435
  if (typeof uri === "string") {
33380
33436
  uri = /** @type {T} */
33381
- serialize(parse7(uri, options), options);
33437
+ normalizeString(uri, options);
33382
33438
  } else if (typeof uri === "object") {
33383
33439
  uri = /** @type {T} */
33384
33440
  parse7(serialize(uri, options), options);
@@ -33445,19 +33501,9 @@ var require_fast_uri = __commonJS({
33445
33501
  return target;
33446
33502
  }
33447
33503
  function equal(uriA, uriB, options) {
33448
- if (typeof uriA === "string") {
33449
- uriA = unescape(uriA);
33450
- uriA = serialize(normalizeComponentEncoding(parse7(uriA, options), true), { ...options, skipEscape: true });
33451
- } else if (typeof uriA === "object") {
33452
- uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options, skipEscape: true });
33453
- }
33454
- if (typeof uriB === "string") {
33455
- uriB = unescape(uriB);
33456
- uriB = serialize(normalizeComponentEncoding(parse7(uriB, options), true), { ...options, skipEscape: true });
33457
- } else if (typeof uriB === "object") {
33458
- uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options, skipEscape: true });
33459
- }
33460
- return uriA.toLowerCase() === uriB.toLowerCase();
33504
+ const normalizedA = normalizeComparableURI(uriA, options);
33505
+ const normalizedB = normalizeComparableURI(uriB, options);
33506
+ return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA.toLowerCase() === normalizedB.toLowerCase();
33461
33507
  }
33462
33508
  function serialize(cmpts, opts) {
33463
33509
  const component = {
@@ -33482,12 +33528,12 @@ var require_fast_uri = __commonJS({
33482
33528
  if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
33483
33529
  if (component.path !== void 0) {
33484
33530
  if (!options.skipEscape) {
33485
- component.path = escape(component.path);
33531
+ component.path = escapePreservingEscapes(component.path);
33486
33532
  if (component.scheme !== void 0) {
33487
33533
  component.path = component.path.split("%3A").join(":");
33488
33534
  }
33489
33535
  } else {
33490
- component.path = unescape(component.path);
33536
+ component.path = normalizePercentEncoding(component.path);
33491
33537
  }
33492
33538
  }
33493
33539
  if (options.reference !== "suffix" && component.scheme) {
@@ -33522,7 +33568,16 @@ var require_fast_uri = __commonJS({
33522
33568
  return uriTokens.join("");
33523
33569
  }
33524
33570
  var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
33525
- function parse7(uri, opts) {
33571
+ function getParseError(parsed, matches) {
33572
+ if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
33573
+ return 'URI path must start with "/" when authority is present.';
33574
+ }
33575
+ if (typeof parsed.port === "number" && (parsed.port < 0 || parsed.port > 65535)) {
33576
+ return "URI port is malformed.";
33577
+ }
33578
+ return void 0;
33579
+ }
33580
+ function parseWithStatus(uri, opts) {
33526
33581
  const options = Object.assign({}, opts);
33527
33582
  const parsed = {
33528
33583
  scheme: void 0,
@@ -33533,6 +33588,7 @@ var require_fast_uri = __commonJS({
33533
33588
  query: void 0,
33534
33589
  fragment: void 0
33535
33590
  };
33591
+ let malformedAuthorityOrPort = false;
33536
33592
  let isIP = false;
33537
33593
  if (options.reference === "suffix") {
33538
33594
  if (options.scheme) {
@@ -33553,6 +33609,11 @@ var require_fast_uri = __commonJS({
33553
33609
  if (isNaN(parsed.port)) {
33554
33610
  parsed.port = matches[5];
33555
33611
  }
33612
+ const parseError = getParseError(parsed, matches);
33613
+ if (parseError !== void 0) {
33614
+ parsed.error = parsed.error || parseError;
33615
+ malformedAuthorityOrPort = true;
33616
+ }
33556
33617
  if (parsed.host) {
33557
33618
  const ipv4result = isIPv4(parsed.host);
33558
33619
  if (ipv4result === false) {
@@ -33591,14 +33652,18 @@ var require_fast_uri = __commonJS({
33591
33652
  parsed.scheme = unescape(parsed.scheme);
33592
33653
  }
33593
33654
  if (parsed.host !== void 0) {
33594
- parsed.host = unescape(parsed.host);
33655
+ parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
33595
33656
  }
33596
33657
  }
33597
33658
  if (parsed.path) {
33598
- parsed.path = escape(unescape(parsed.path));
33659
+ parsed.path = normalizePathEncoding(parsed.path);
33599
33660
  }
33600
33661
  if (parsed.fragment) {
33601
- parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
33662
+ try {
33663
+ parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
33664
+ } catch {
33665
+ parsed.error = parsed.error || "URI malformed";
33666
+ }
33602
33667
  }
33603
33668
  }
33604
33669
  if (schemeHandler && schemeHandler.parse) {
@@ -33607,7 +33672,29 @@ var require_fast_uri = __commonJS({
33607
33672
  } else {
33608
33673
  parsed.error = parsed.error || "URI can not be parsed.";
33609
33674
  }
33610
- return parsed;
33675
+ return { parsed, malformedAuthorityOrPort };
33676
+ }
33677
+ function parse7(uri, opts) {
33678
+ return parseWithStatus(uri, opts).parsed;
33679
+ }
33680
+ function normalizeString(uri, opts) {
33681
+ return normalizeStringWithStatus(uri, opts).normalized;
33682
+ }
33683
+ function normalizeStringWithStatus(uri, opts) {
33684
+ const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
33685
+ return {
33686
+ normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
33687
+ malformedAuthorityOrPort
33688
+ };
33689
+ }
33690
+ function normalizeComparableURI(uri, opts) {
33691
+ if (typeof uri === "string") {
33692
+ const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
33693
+ return malformedAuthorityOrPort ? void 0 : normalized;
33694
+ }
33695
+ if (typeof uri === "object") {
33696
+ return serialize(uri, opts);
33697
+ }
33611
33698
  }
33612
33699
  var fastUri = {
33613
33700
  SCHEMES,
@@ -56350,6 +56437,15 @@ init_cjs_shims();
56350
56437
  var fs51 = __toESM(require("fs"));
56351
56438
  var path52 = __toESM(require("path"));
56352
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
+ ];
56353
56449
  function getPlanningSurfaceStatus() {
56354
56450
  const planningDir = path52.join(process.cwd(), "planning");
56355
56451
  return {
@@ -56681,6 +56777,97 @@ function getTaskEntriesFromState(state) {
56681
56777
  estimatedTurns: task.estimatedTurns
56682
56778
  }));
56683
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
+ }
56684
56871
  function extractSupplementaryContent(queueContent) {
56685
56872
  const lines = queueContent.split("\n");
56686
56873
  const sections = [];
@@ -56854,7 +57041,8 @@ function registerBuildCommand(program3) {
56854
57041
  build.command("status").description("Check build progress").option("--json", "Output as JSON").action((opts) => {
56855
57042
  const state = loadBuildStateWithAutoSync(opts.json);
56856
57043
  const planningTasks = loadPlanningTasks();
56857
- const tasks = planningTasks.tasks.length > 0 ? planningTasks.tasks : getTaskEntriesFromState(state);
57044
+ const stateTasks = getTaskEntriesFromState(state);
57045
+ const tasks = stateTasks.length > 0 ? stateTasks : planningTasks.tasks;
56858
57046
  if (!state) {
56859
57047
  if (opts.json) {
56860
57048
  console.log(JSON.stringify({ error: "no_build_state", tasks: [] }));
@@ -56873,14 +57061,8 @@ function registerBuildCommand(program3) {
56873
57061
  }
56874
57062
  return;
56875
57063
  }
56876
- const completed = tasks.filter((t) => {
56877
- const s = t.status.toLowerCase();
56878
- return s === "completed" || s === "done";
56879
- }).length;
56880
- const pending = tasks.filter((t) => t.status.toLowerCase() === "pending").length;
56881
- const inProgress = tasks.filter((t) => t.status.toLowerCase() === "in_progress").length;
56882
- const total = tasks.length;
56883
- 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 });
56884
57066
  const iteration = state.loopSession?.currentIteration ?? 0;
56885
57067
  const maxIter = state.loopSession?.maxIterations ?? 50;
56886
57068
  const sessionId = state.loopSession?.sessionId ?? "N/A";
@@ -56888,12 +57070,15 @@ function registerBuildCommand(program3) {
56888
57070
  const currentTask2 = tasks.find((t) => t.status.toLowerCase() === "in_progress");
56889
57071
  console.log(JSON.stringify({
56890
57072
  project: state.projectName ?? "Unknown",
56891
- status: state.status,
57073
+ status: derivedStatus,
56892
57074
  phase: formatPhaseName(state.currentPhase ?? getCurrentPhase(state.implementationQueue)),
56893
57075
  planningSource: formatPlanningSourceLabel(planningTasks.source ?? (state ? "BUILD_STATE.json" : null)),
56894
57076
  completed,
57077
+ skipped,
57078
+ blocked,
56895
57079
  pending,
56896
57080
  inProgress,
57081
+ closed,
56897
57082
  total,
56898
57083
  pct: pct2,
56899
57084
  iteration,
@@ -56906,10 +57091,12 @@ function registerBuildCommand(program3) {
56906
57091
  print.header("Build Status");
56907
57092
  printLegacyQueueWarningIfNeeded();
56908
57093
  print.info(`Project: ${state.projectName ?? "Unknown"}`);
56909
- print.info(`Status: ${state.status}`);
57094
+ print.info(`Status: ${derivedStatus}`);
56910
57095
  print.info(`Phase: ${formatPhaseName(state.currentPhase ?? getCurrentPhase(state.implementationQueue))}`);
56911
57096
  print.info(`Planning Source: ${formatPlanningSourceLabel(planningTasks.source ?? (state ? "BUILD_STATE.json" : null))}`);
56912
- 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}`);
56913
57100
  print.info(`Pending: ${pending}`);
56914
57101
  if (inProgress > 0) print.info(`In Progress: ${inProgress}`);
56915
57102
  if (iteration > maxIter) {
@@ -56926,6 +57113,40 @@ function registerBuildCommand(program3) {
56926
57113
  const filled = Math.round(pct2 / 100 * barWidth);
56927
57114
  const bar = "#".repeat(filled) + ".".repeat(barWidth - filled);
56928
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"));
56929
57150
  });
56930
57151
  build.command("task").description("Show the current task").action(() => {
56931
57152
  printLegacyQueueWarningIfNeeded();
@@ -56944,7 +57165,7 @@ function registerBuildCommand(program3) {
56944
57165
  print.info(`Complexity: ${next.complexity ?? "Unknown"}`);
56945
57166
  if (current) print.info(`Status: ${next.status}`);
56946
57167
  });
56947
- build.command("done").alias("complete").description("Mark the current task as done (trusts caller \u2014 does not verify execution)").action(() => {
57168
+ build.command("done").alias("complete").description("Mark the current task as done, auto-commit, and show next task").option("--no-commit", "Skip auto-commit").action((opts) => {
56948
57169
  const state = loadBuildStateWithAutoSync();
56949
57170
  if (!state) {
56950
57171
  print.error("No build state found");
@@ -56958,18 +57179,59 @@ function registerBuildCommand(program3) {
56958
57179
  return;
56959
57180
  }
56960
57181
  const currentId = queue[idx].id;
57182
+ const currentTitle = queue[idx].title;
57183
+ if (opts.commit !== false) {
57184
+ try {
57185
+ const { execSync: execSync15 } = require("child_process");
57186
+ const cwd = process.cwd();
57187
+ const status = execSync15("git status --porcelain", { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
57188
+ if (status) {
57189
+ execSync15("git add -A", { cwd, stdio: ["ignore", "ignore", "ignore"] });
57190
+ const msg = `feat(${currentId}): ${currentTitle}`;
57191
+ execSync15(`git commit -m ${JSON.stringify(msg)}`, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
57192
+ print.success(`Committed: ${msg}`);
57193
+ }
57194
+ } catch {
57195
+ }
57196
+ }
56961
57197
  queue[idx].status = "completed";
56962
57198
  if (state.loopSession) {
56963
57199
  state.loopSession.currentIteration = (state.loopSession.currentIteration ?? 0) + 1;
56964
57200
  }
56965
57201
  saveBuildState(state);
56966
- const nextTask = queue.find((t) => t.status === "pending");
57202
+ const nextTask = (() => {
57203
+ for (const task of queue) {
57204
+ if (task.status !== "pending") continue;
57205
+ if (task.dependencies?.length) {
57206
+ const allDone = task.dependencies.every((depId) => {
57207
+ const dep = queue.find((t) => t.id === depId);
57208
+ return dep && dep.status === "completed";
57209
+ });
57210
+ if (!allDone) continue;
57211
+ }
57212
+ return task;
57213
+ }
57214
+ return null;
57215
+ })();
56967
57216
  print.success(`Task ${currentId} marked as done`);
56968
57217
  if (nextTask) {
56969
- print.info(`Next: ${nextTask.id} - ${nextTask.title}`);
57218
+ console.log("");
57219
+ print.header("Next Task");
57220
+ print.info(`ID: ${nextTask.id}`);
57221
+ print.info(`Title: ${nextTask.title}`);
57222
+ if (nextTask.phase) print.info(`Phase: ${nextTask.phase}`);
57223
+ if (nextTask.description) print.info(`Description: ${nextTask.description}`);
57224
+ if (nextTask.acceptanceCriteria && nextTask.acceptanceCriteria.length > 0) {
57225
+ print.info("Acceptance criteria:");
57226
+ for (const ac of nextTask.acceptanceCriteria) {
57227
+ console.log(` [ ] ${ac}`);
57228
+ }
57229
+ }
56970
57230
  } else {
56971
57231
  print.success("No more pending tasks!");
56972
57232
  }
57233
+ const artifactPath = writeBuildProgressArtifact(state, nextTask ?? queue[idx], nextTask ? "task_completed_next_available" : "task_completed_all_done");
57234
+ printVisibleWorkNotice(artifactPath);
56973
57235
  });
56974
57236
  build.command("plan").description("View the full build plan").action(() => {
56975
57237
  printLegacyQueueWarningIfNeeded();
@@ -57041,6 +57303,8 @@ function registerBuildCommand(program3) {
57041
57303
  const inProgress = queue.find((t2) => t2.status === "in_progress");
57042
57304
  if (inProgress) {
57043
57305
  print.warning(`Task ${inProgress.id} is already in progress: ${inProgress.title}`);
57306
+ const artifactPath2 = writeBuildProgressArtifact(state, inProgress, "already_in_progress");
57307
+ printVisibleWorkNotice(artifactPath2);
57044
57308
  return;
57045
57309
  }
57046
57310
  const idx = queue.findIndex((t2) => t2.status === "pending");
@@ -57068,6 +57332,8 @@ function registerBuildCommand(program3) {
57068
57332
  console.log(` [ ] ${ac}`);
57069
57333
  }
57070
57334
  }
57335
+ const artifactPath = writeBuildProgressArtifact(state, t, "task_started");
57336
+ printVisibleWorkNotice(artifactPath);
57071
57337
  });
57072
57338
  build.command("pause").description("Pause the build loop").action(() => {
57073
57339
  const state = loadBuildState();
@@ -57852,6 +58118,71 @@ ${supplementaryContent}
57852
58118
  print.dim(`${snapshot.completedCount} completed, ${snapshot.pendingCount} pending`);
57853
58119
  }
57854
58120
  });
58121
+ build.command("handoff").description("Save or load session handoff state for cross-session continuity").option("--save", "Save current session state").option("--notes <notes>", "Notes for the next session").option("--json", "Output as JSON").action((opts) => {
58122
+ const handoffPath = path52.join(process.cwd(), "planning", ".handoff.json");
58123
+ if (opts.save) {
58124
+ const state = loadBuildStateWithAutoSync(true);
58125
+ const queue = state?.implementationQueue ?? [];
58126
+ const inProgress = queue.find((t) => t.status === "in_progress");
58127
+ const lastCompleted = [...queue].reverse().find((t) => t.status === "completed");
58128
+ let filesModified = [];
58129
+ try {
58130
+ const { execSync: execSync15 } = require("child_process");
58131
+ const gitDiff = execSync15("git diff --name-only HEAD~1 2>/dev/null || true", { encoding: "utf8" }).trim();
58132
+ if (gitDiff) filesModified = gitDiff.split("\n").filter(Boolean);
58133
+ } catch {
58134
+ }
58135
+ const handoff = {
58136
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
58137
+ lastTaskId: lastCompleted?.id ?? null,
58138
+ inProgressTask: inProgress?.id ?? null,
58139
+ inProgressTitle: inProgress?.title ?? null,
58140
+ filesModified,
58141
+ notes: opts.notes ?? "",
58142
+ blockers: [],
58143
+ progress: {
58144
+ completed: queue.filter((t) => t.status === "completed").length,
58145
+ pending: queue.filter((t) => t.status === "pending").length,
58146
+ total: queue.length
58147
+ }
58148
+ };
58149
+ const dir = path52.dirname(handoffPath);
58150
+ if (!fs51.existsSync(dir)) fs51.mkdirSync(dir, { recursive: true });
58151
+ fs51.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
58152
+ if (opts.json) {
58153
+ console.log(JSON.stringify({ saved: true, handoff }));
58154
+ } else {
58155
+ print.success("Session handoff saved");
58156
+ if (inProgress) print.info(`In-progress: ${inProgress.id} \u2014 ${inProgress.title}`);
58157
+ if (opts.notes) print.info(`Notes: ${opts.notes}`);
58158
+ }
58159
+ return;
58160
+ }
58161
+ if (!fs51.existsSync(handoffPath)) {
58162
+ if (opts.json) {
58163
+ console.log(JSON.stringify({ hasHandoff: false }));
58164
+ } else {
58165
+ print.info("No previous session handoff found.");
58166
+ }
58167
+ return;
58168
+ }
58169
+ try {
58170
+ const handoff = JSON.parse(fs51.readFileSync(handoffPath, "utf-8"));
58171
+ if (opts.json) {
58172
+ console.log(JSON.stringify({ hasHandoff: true, handoff }));
58173
+ } else {
58174
+ print.header("Session Handoff");
58175
+ print.info(`Saved: ${handoff.timestamp}`);
58176
+ if (handoff.inProgressTask) print.info(`In-progress: ${handoff.inProgressTask} \u2014 ${handoff.inProgressTitle ?? ""}`);
58177
+ if (handoff.lastTaskId) print.info(`Last completed: ${handoff.lastTaskId}`);
58178
+ if (handoff.notes) print.info(`Notes: ${handoff.notes}`);
58179
+ if (handoff.progress) print.info(`Progress: ${handoff.progress.completed}/${handoff.progress.total}`);
58180
+ if (handoff.filesModified?.length) print.dim(`Files: ${handoff.filesModified.slice(0, 5).join(", ")}${handoff.filesModified.length > 5 ? ` +${handoff.filesModified.length - 5} more` : ""}`);
58181
+ }
58182
+ } catch {
58183
+ print.error("Failed to read handoff file");
58184
+ }
58185
+ });
57855
58186
  build.action(() => {
57856
58187
  build.outputHelp();
57857
58188
  });
@@ -61679,7 +62010,7 @@ function generateDocTemplate(docType, projectName) {
61679
62010
  `;
61680
62011
  }
61681
62012
  function registerGoCommand(program3) {
61682
- 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) => {
61683
62014
  console.log("");
61684
62015
  console.log(`${COLORS.cyan}${COLORS.bold}\u26A1 Bootspring Go${COLORS.reset}`);
61685
62016
  console.log(`${COLORS.dim}Setting up your project in one shot...${COLORS.reset}`);
@@ -61699,26 +62030,56 @@ function registerGoCommand(program3) {
61699
62030
  print.dim(" No codebase detected (starting fresh)");
61700
62031
  }
61701
62032
  console.log("");
61702
- console.log(`${COLORS.bold}[2/5] Seed templates${COLORS.reset}`);
61703
- if (!fs64.existsSync(contextDir)) {
61704
- fs64.mkdirSync(contextDir, { recursive: true });
61705
- }
61706
- const docs = PRESETS[opts.preset] || PRESETS.startup;
61707
- let generated = 0;
61708
- let skipped = 0;
61709
- for (const docType of docs) {
61710
- const meta3 = CONTEXT_DOCS[docType];
61711
- if (!meta3) continue;
61712
- const filePath = path65.join(contextDir, meta3.name);
61713
- if (fs64.existsSync(filePath) && !opts.force) {
61714
- skipped++;
61715
- 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;
61716
62044
  }
61717
- fs64.writeFileSync(filePath, generateDocTemplate(docType, projectName));
61718
- 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);
61719
62082
  }
61720
- if (generated > 0) print.success(` Created ${generated} template(s) in ${CONTEXT_DIR}/`);
61721
- if (skipped > 0) print.dim(` Skipped ${skipped} (already exist)`);
61722
62083
  console.log("");
61723
62084
  if (opts.editFirst) {
61724
62085
  print.info("Templates created. Edit them, then run:");
@@ -61771,13 +62132,39 @@ function registerGoCommand(program3) {
61771
62132
  console.log("");
61772
62133
  console.log(`${COLORS.green}${COLORS.bold}Setup complete!${COLORS.reset}`);
61773
62134
  console.log("");
61774
- console.log(`${COLORS.bold}Next:${COLORS.reset}`);
61775
- console.log(" 1. Edit planning/TODO.md with your real tasks");
61776
- console.log(" 2. bootspring build next # Start the first task");
61777
- 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
+ }
61778
62146
  console.log("");
61779
62147
  });
61780
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
+ }
61781
62168
 
61782
62169
  // src/commands/seed.ts
61783
62170
  var CONTEXT_DOCS2 = {
@@ -62287,7 +62674,7 @@ ${COLORS.bold}Detected Features:${COLORS.reset}`);
62287
62674
  console.log("");
62288
62675
  print.info(`${generated} generated, ${skipped} skipped in ${CONTEXT_DIR2}/`);
62289
62676
  });
62290
- 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) => {
62291
62678
  print.header("Seed Merge");
62292
62679
  const cwd = process.cwd();
62293
62680
  const contextDir = getContextDir();
@@ -62553,10 +62940,122 @@ ${COLORS.bold}Detected Features:${COLORS.reset}`);
62553
62940
  print.success("Document looks good!");
62554
62941
  }
62555
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
+ });
62556
63052
  seed.action(() => {
62557
63053
  seed.outputHelp();
62558
63054
  console.log("");
62559
- 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}`);
62560
63059
  console.log(" bootspring seed init # Create .bootspring/context/ + templates");
62561
63060
  console.log(" (edit/drop files) # Add your project docs");
62562
63061
  console.log(" bootspring seed merge # Combine into planning/SEED.md");
@@ -73378,6 +73877,29 @@ ${COLORS.green}Task submitted:${COLORS.reset} ${res.id}`);
73378
73877
  handleError(error50);
73379
73878
  }
73380
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
+ });
73381
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) => {
73382
73904
  if (!requireAuth4()) return;
73383
73905
  try {
@@ -77851,40 +78373,45 @@ var import_child_process18 = require("child_process");
77851
78373
  init_src2();
77852
78374
  var HOOK_REGISTRY = {
77853
78375
  SessionStart: [
77854
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/stats/hooks/session-start.ts" },
77855
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/suggest/hooks/session-start.ts" },
77856
- { 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" }
77857
78379
  ],
77858
78380
  UserPromptSubmit: [
77859
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/stats/hooks/user-prompt-submit.ts" },
77860
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/suggest/hooks/user-prompt-submit.ts" },
77861
- { 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" }
77862
78384
  ],
77863
78385
  PreToolUse: [
77864
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/suggest/hooks/pre-tool-use.ts" },
77865
- { 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" }
77866
78388
  ],
77867
78389
  PostToolUse: [
77868
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/stats/hooks/post-tool-use.ts" },
77869
- { 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" }
77870
78392
  ],
77871
78393
  PreCompact: [
77872
- { 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" }
77873
78395
  ],
77874
78396
  SubagentStart: [
77875
- { 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" }
77876
78398
  ],
77877
78399
  SubagentStop: [
77878
- { 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" }
77879
78401
  ],
77880
78402
  SessionEnd: [
77881
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/stats/hooks/session-end.ts" },
77882
- { package: "session-intelligence", script: "packages/session-intelligence/src/features/suggest/hooks/session-end.ts" },
77883
- { 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" }
77884
78406
  ]
77885
78407
  };
77886
78408
  var ALL_EVENTS = Object.keys(HOOK_REGISTRY);
77887
- 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
+ ];
77888
78415
  function findMonorepoRoot(cwd) {
77889
78416
  let dir = cwd;
77890
78417
  for (let i = 0; i < 10; i++) {
@@ -77895,19 +78422,27 @@ function findMonorepoRoot(cwd) {
77895
78422
  }
77896
78423
  return null;
77897
78424
  }
77898
- function readSettings(cwd) {
77899
- const settingsPath = path107.join(cwd, SETTINGS_FILE);
77900
- if (!fs106.existsSync(settingsPath)) return {};
78425
+ function readJsonFile(cwd, relPath) {
78426
+ const filePath = path107.join(cwd, relPath);
78427
+ if (!fs106.existsSync(filePath)) return {};
77901
78428
  try {
77902
- return JSON.parse(fs106.readFileSync(settingsPath, "utf8"));
78429
+ return JSON.parse(fs106.readFileSync(filePath, "utf8"));
77903
78430
  } catch {
77904
78431
  return {};
77905
78432
  }
77906
78433
  }
77907
- function writeSettings(cwd, settings) {
77908
- const dir = path107.join(cwd, ".claude");
78434
+ function writeJsonFile(cwd, relPath, data) {
78435
+ const dir = path107.dirname(path107.join(cwd, relPath));
77909
78436
  if (!fs106.existsSync(dir)) fs106.mkdirSync(dir, { recursive: true });
77910
- 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");
77911
78446
  }
77912
78447
  function buildDispatcherCommand(monorepoRoot) {
77913
78448
  const cliEntry = path107.join(monorepoRoot, "monorepo/apps/cli/bin/bootspring.js");
@@ -77961,7 +78496,7 @@ async function dispatchEvent(event) {
77961
78496
  });
77962
78497
  } catch (err) {
77963
78498
  const msg = err.stderr?.toString?.().slice(0, 200) || err.message || "unknown error";
77964
- 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}
77965
78500
  `);
77966
78501
  }
77967
78502
  }
@@ -77990,8 +78525,8 @@ function readStdinPayload() {
77990
78525
  });
77991
78526
  }
77992
78527
  function registerHookCommand(program3) {
77993
- const hook = program3.command("hook").description("Manage Claude Code hook dispatcher (observer \u2192 autopilot \u2192 memory)");
77994
- 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) => {
77995
78530
  const cwd = process.cwd();
77996
78531
  const monorepoRoot = findMonorepoRoot(cwd);
77997
78532
  if (!monorepoRoot) {
@@ -78005,17 +78540,19 @@ function registerHookCommand(program3) {
78005
78540
  console.log(`
78006
78541
  ${COLORS.bold}Hook config (dry run):${COLORS.reset}
78007
78542
  `);
78008
- 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));
78009
78546
  console.log(`
78010
- ${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}
78011
78548
  `);
78012
78549
  return;
78013
78550
  }
78014
- const settings = readSettings(monorepoRoot);
78015
- settings.hooks = hooksConfig;
78016
- writeSettings(monorepoRoot, settings);
78017
- print.success(`Wrote ${ALL_EVENTS.length} hook events (${totalHandlers} handlers) to ${SETTINGS_FILE}`);
78018
- 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}`);
78019
78556
  console.log(` ${COLORS.dim}Error isolation: each handler runs in its own subprocess${COLORS.reset}`);
78020
78557
  console.log(` ${COLORS.dim}Events: ${ALL_EVENTS.join(", ")}${COLORS.reset}
78021
78558
  `);
@@ -78023,45 +78560,54 @@ ${COLORS.dim}${ALL_EVENTS.length} events, ${totalHandlers} handlers, ordering: o
78023
78560
  hook.command("status").description("Show installed hook configuration").action(() => {
78024
78561
  const cwd = process.cwd();
78025
78562
  const monorepoRoot = findMonorepoRoot(cwd) || cwd;
78026
- const settings = readSettings(monorepoRoot);
78027
- const hooks = settings.hooks || {};
78563
+ const targetStates = HOOK_TARGETS.map((target) => ({
78564
+ ...target,
78565
+ hooks: readJsonFile(monorepoRoot, target.file).hooks || {}
78566
+ }));
78028
78567
  console.log(`
78029
78568
  ${COLORS.bold}Hook Status${COLORS.reset}
78030
78569
  `);
78031
- if (Object.keys(hooks).length === 0) {
78032
- 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 ")}`);
78033
78572
  print.dim('Run "bootspring hook install" to set up the deterministic dispatcher.');
78034
78573
  console.log();
78035
78574
  return;
78036
78575
  }
78037
- const isDispatcher = JSON.stringify(hooks).includes("hook dispatch");
78038
78576
  for (const event of ALL_EVENTS) {
78039
- const entries = hooks[event];
78577
+ const installedTargets = targetStates.filter((target) => hookEntriesUseDispatcher(target.hooks[event]));
78040
78578
  const registry4 = HOOK_REGISTRY[event];
78041
- const mark = entries ? `${COLORS.green}\u2713${COLORS.reset}` : `${COLORS.dim}\xB7${COLORS.reset}`;
78042
- const handlers = registry4.map((h) => `${COLORS.cyan}${h.package}${COLORS.reset}`).join(" \u2192 ");
78043
- 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}`);
78044
78583
  }
78045
78584
  console.log();
78046
- if (isDispatcher) {
78047
- print.success("Using centralized dispatcher with deterministic ordering.");
78048
- } else {
78049
- print.warning("Hooks are configured but not using the centralized dispatcher.");
78050
- 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
+ }
78051
78592
  }
78052
78593
  console.log();
78053
78594
  });
78054
- 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(() => {
78055
78596
  const cwd = process.cwd();
78056
78597
  const monorepoRoot = findMonorepoRoot(cwd) || cwd;
78057
- const settings = readSettings(monorepoRoot);
78058
- 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) {
78059
78607
  print.info("No hooks configured \u2014 nothing to remove.");
78060
78608
  return;
78061
78609
  }
78062
- delete settings.hooks;
78063
- writeSettings(monorepoRoot, settings);
78064
- print.success("Removed all hook entries from .claude/settings.local.json");
78610
+ print.success(`Removed hook entries from ${removed} assistant config file(s).`);
78065
78611
  });
78066
78612
  hook.command("dispatch <event>").description("(internal) Dispatch a hook event to handlers in order").action(async (event) => {
78067
78613
  await dispatchEvent(event);
@@ -78081,6 +78627,7 @@ var SUBCOMMANDS = {
78081
78627
  auth: ["login", "logout", "whoami", "status", "clear", "register", "switch", "reauth"],
78082
78628
  build: [
78083
78629
  "status",
78630
+ "progress",
78084
78631
  "task",
78085
78632
  "done",
78086
78633
  "plan",