@basou/cli 0.49.0 → 0.51.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/index.js CHANGED
@@ -866,6 +866,7 @@ import {
866
866
  basouPaths as basouPaths4,
867
867
  classifyFilesBySourceRoot,
868
868
  createAdHocSessionWithEvent,
869
+ displayPath,
869
870
  EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION2,
870
871
  findErrorCode as findErrorCode2,
871
872
  isValidPrefixedId,
@@ -1305,7 +1306,7 @@ async function warnLinkedFilesOutsideRoots(input) {
1305
1306
  });
1306
1307
  if (scope.outOfRoot.length === 0) return;
1307
1308
  const PATH_SAMPLE = 5;
1308
- const sample = scope.outOfRoot.slice(0, PATH_SAMPLE).join(", ");
1309
+ const sample = scope.outOfRoot.slice(0, PATH_SAMPLE).map(displayPath).join(", ");
1309
1310
  const more = scope.outOfRoot.length > PATH_SAMPLE ? ` (... +${scope.outOfRoot.length - PATH_SAMPLE} more)` : "";
1310
1311
  console.error(
1311
1312
  `basou: ${scope.outOfRoot.length} linked file(s) resolve outside this project's source_roots: ${sample}${more} \u2014 this decision may belong to another project.`
@@ -2474,6 +2475,9 @@ import {
2474
2475
  evaluateStopHook,
2475
2476
  findBasouSessionStartHook,
2476
2477
  findBasouStopHookCommand,
2478
+ findClaudeSessionStartHooks,
2479
+ findUnrecognizedSessionStart,
2480
+ isClaudeSessionStartMalformed,
2477
2481
  isProtocolUpdateDue,
2478
2482
  ORIENTATION_END as ORIENTATION_END2,
2479
2483
  ORIENTATION_START as ORIENTATION_START2,
@@ -2488,10 +2492,12 @@ import {
2488
2492
  readManifest as readManifest5,
2489
2493
  readMarkdownFile as readMarkdownFile6,
2490
2494
  recordSessionBaseline,
2495
+ removeClaudeSessionStartHook,
2491
2496
  removeSessionStartHook,
2492
2497
  removeStopHook,
2493
2498
  renderProtocolUpdate,
2494
2499
  transcriptStartedAt,
2500
+ upsertClaudeSessionStartHook,
2495
2501
  upsertSessionStartHook,
2496
2502
  upsertStopHook
2497
2503
  } from "@basou/core";
@@ -2913,6 +2919,7 @@ import {
2913
2919
  classifyFilesBySourceRoot as classifyFilesBySourceRoot2,
2914
2920
  claudeTranscriptToImportPayload,
2915
2921
  codexRolloutToImportPayload,
2922
+ displayPath as displayPath2,
2916
2923
  enumerateSessionDirs,
2917
2924
  findErrorCode as findErrorCode5,
2918
2925
  importSessionFromJson,
@@ -3183,7 +3190,7 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
3183
3190
  if (crossProject.length > 0) {
3184
3191
  const PATH_SAMPLE = 5;
3185
3192
  for (const { externalId, outOfRoot } of crossProject) {
3186
- const sample = outOfRoot.slice(0, PATH_SAMPLE).join(", ");
3193
+ const sample = outOfRoot.slice(0, PATH_SAMPLE).map(displayPath2).join(", ");
3187
3194
  const more = outOfRoot.length > PATH_SAMPLE ? ` (... +${outOfRoot.length - PATH_SAMPLE} more)` : "";
3188
3195
  console.error(
3189
3196
  `basou: session ${externalId} edited ${outOfRoot.length} file(s) outside this project's source_roots: ${sample}${more} \u2014 they may belong to another project.`
@@ -3842,11 +3849,14 @@ function registerHookCommand(program2) {
3842
3849
  });
3843
3850
  });
3844
3851
  hook.command("install [target]").description(
3845
- "Register a basou hook (reproducible, idempotent). Target `claude` (default): the Stop hook in ~/.claude/settings.json \u2014 advisory capture-only by default; --block opts into in-turn enforcement, --require-review into the review gate. Target `codex`: the SessionStart hook in ~/.codex/hooks.json, which hands each Codex session the position of the workspace it was opened in. Codex asks you to review and trust a new hook once before it runs."
3852
+ "Register a basou hook (reproducible, idempotent). Target `claude` (default): the Stop hook and the SessionStart hook in ~/.claude/settings.json. The Stop hook is advisory capture-only by default; --block opts into in-turn enforcement, --require-review into the review gate. The SessionStart hook hands each session the workspace's position and records the git baseline the Stop hook measures the session's file changes against; --no-session-start leaves it out. Target `codex`: the SessionStart hook in ~/.codex/hooks.json, which hands each Codex session the position of the workspace it was opened in. Codex asks you to review and trust a new hook once before it runs."
3846
3853
  ).option(
3847
3854
  "--block",
3848
3855
  "claude: register the blocking (opt-in enforcement) form instead of advisory"
3849
- ).option("--require-review", "claude: register with the opt-in review gate enabled").option("--min-edits <n>", "claude: pass a custom file-edit threshold to the registered hook").option("--settings <path>", "claude: override the settings.json path (intended for tests)").option("--hooks <path>", "codex: override the hooks.json path (intended for tests)").option(
3856
+ ).option("--require-review", "claude: register with the opt-in review gate enabled").option("--min-edits <n>", "claude: pass a custom file-edit threshold to the registered hook").option(
3857
+ "--no-session-start",
3858
+ "claude: register the Stop hook only, and leave any SessionStart hook as it is"
3859
+ ).option("--settings <path>", "claude: override the settings.json path (intended for tests)").option("--hooks <path>", "codex: override the hooks.json path (intended for tests)").option(
3850
3860
  "--codex-config <path>",
3851
3861
  "codex: override the Codex config.toml path (intended for tests)"
3852
3862
  ).option(
@@ -3855,11 +3865,23 @@ function registerHookCommand(program2) {
3855
3865
  ).option("--dry-run", "Print what would change without writing").option("-v, --verbose", "Show error causes").action(async (target, opts) => {
3856
3866
  await dispatchHookTarget(target, opts, {
3857
3867
  claude: () => runHookInstall(opts),
3858
- codex: () => runCodexHookInstall(opts)
3868
+ codex: async () => {
3869
+ if (opts.sessionStart === false) {
3870
+ renderCliError(
3871
+ new Error(
3872
+ "--no-session-start applies to target claude only; the Codex hook is itself a SessionStart hook."
3873
+ ),
3874
+ { verbose: isVerbose(opts) }
3875
+ );
3876
+ process.exitCode = 1;
3877
+ return;
3878
+ }
3879
+ await runCodexHookInstall(opts);
3880
+ }
3859
3881
  });
3860
3882
  });
3861
3883
  hook.command("uninstall [target]").description(
3862
- "Remove a basou hook, leaving other hooks intact. Target `claude` (default): the Stop hook in ~/.claude/settings.json. Target `codex`: the SessionStart hook in ~/.codex/hooks.json."
3884
+ "Remove a basou hook, leaving other hooks intact. Target `claude` (default): the Stop hook and the SessionStart hook in ~/.claude/settings.json. Target `codex`: the SessionStart hook in ~/.codex/hooks.json."
3863
3885
  ).option("--settings <path>", "claude: override the settings.json path (intended for tests)").option("--hooks <path>", "codex: override the hooks.json path (intended for tests)").option("--dry-run", "Print what would change without writing").option("-v, --verbose", "Show error causes").action(async (target, opts) => {
3864
3886
  await dispatchHookTarget(target, opts, {
3865
3887
  claude: () => runHookUninstall(opts),
@@ -3867,7 +3889,7 @@ function registerHookCommand(program2) {
3867
3889
  });
3868
3890
  });
3869
3891
  hook.command("status [target]").description(
3870
- "Report whether a basou hook is registered. Target `claude` (default): the Stop hook and its mode. Target `codex`: the SessionStart hook, and whether Codex has trusted it yet."
3892
+ "Report whether a basou hook is registered. Target `claude` (default): the Stop hook and its mode, and the SessionStart hook. Target `codex`: the SessionStart hook, and whether Codex has trusted it yet."
3871
3893
  ).option("--settings <path>", "claude: override the settings.json path (intended for tests)").option("--hooks <path>", "codex: override the hooks.json path (intended for tests)").option(
3872
3894
  "--codex-config <path>",
3873
3895
  "codex: override the Codex config.toml path (intended for tests)"
@@ -3915,9 +3937,11 @@ becomes the session's trusted context, so it withholds the position instead.
3915
3937
  Run 'basou refresh' to see which lines are responsible.
3916
3938
 
3917
3939
  Claude Code's SessionStart hook sends the same kind of payload (a JSON object
3918
- with 'cwd') and adds stdout to context the same way, so a Claude Code user may
3919
- register this command in ~/.claude/settings.json in place of 'basou orient' to
3920
- get both gates. basou does not install that one.
3940
+ with 'cwd') and adds stdout to context the same way. 'basou hook install'
3941
+ registers this command there too, in ~/.claude/settings.json, and replaces a
3942
+ hand-registered 'basou orient' SessionStart hook with it. In Claude Code it also
3943
+ records where each declared repository stood when the session started, which is
3944
+ what the Stop hook measures the session's file changes against.
3921
3945
 
3922
3946
  Codex trusts hooks by hash. A newly installed or changed hook is skipped until
3923
3947
  you review it: the interactive CLI asks at startup ("Hooks need review"), the
@@ -4231,6 +4255,7 @@ function normalizeInstallOptions(raw) {
4231
4255
  const out = {};
4232
4256
  if (raw.block === true) out.block = true;
4233
4257
  if (raw.requireReview === true) out.requireReview = true;
4258
+ if (raw.sessionStart === false) out.noSessionStart = true;
4234
4259
  if (raw.settings !== void 0) out.settings = raw.settings;
4235
4260
  if (raw.hooks !== void 0) out.hooks = raw.hooks;
4236
4261
  if (raw.codexConfig !== void 0) out.codexConfig = raw.codexConfig;
@@ -4302,15 +4327,32 @@ async function doRunHookInstall(options, ctx = {}) {
4302
4327
  });
4303
4328
  await assertNotSymlink(settingsPath);
4304
4329
  const { raw, parsed } = await readSettings(settingsPath);
4305
- const { settings, action } = upsertStopHook(parsed, command);
4306
- const newBody = `${JSON.stringify(settings, null, 2)}
4307
- `;
4308
- if (raw !== null && newBody === raw) {
4309
- console.log(`The basou Stop hook is already registered (${mode}); no change.`);
4310
- return;
4330
+ const stop = upsertStopHook(parsed, command);
4331
+ let sessionStart;
4332
+ if (options.noSessionStart === true) {
4333
+ sessionStart = { kind: "skipped" };
4334
+ } else {
4335
+ try {
4336
+ const upsert = upsertClaudeSessionStartHook(
4337
+ stop.settings,
4338
+ buildSessionStartHookCommand({ cliEntry })
4339
+ );
4340
+ sessionStart = { kind: "done", action: upsert.action, settings: upsert.settings };
4341
+ } catch (error) {
4342
+ if (!isClaudeSessionStartMalformed(stop.settings)) throw error;
4343
+ sessionStart = { kind: "malformed" };
4344
+ }
4311
4345
  }
4312
- if (options.dryRun === true) {
4313
- console.log(`[dry-run] Would ${action} the basou Stop hook (${mode}).`);
4346
+ const settings = sessionStart.kind === "done" ? sessionStart.settings : stop.settings;
4347
+ const changed = stop.action !== "unchanged" || sessionStart.kind === "done" && sessionStart.action !== "unchanged";
4348
+ const dryRun = options.dryRun === true;
4349
+ const lines = [
4350
+ describeStopInstall(stop.action, mode, dryRun),
4351
+ describeSessionStartInstall(sessionStart, dryRun),
4352
+ ...describeSessionStartLeftovers(settings)
4353
+ ];
4354
+ if (!changed || dryRun) {
4355
+ for (const line of lines) console.log(line);
4314
4356
  return;
4315
4357
  }
4316
4358
  const recheck = await readSettings(settingsPath);
@@ -4320,8 +4362,55 @@ async function doRunHookInstall(options, ctx = {}) {
4320
4362
  );
4321
4363
  }
4322
4364
  await backupSettingsOnce(settingsPath, raw);
4323
- await writeFileDurable(settingsPath, newBody);
4324
- console.log(`${action === "installed" ? "Installed" : "Updated"} the basou Stop hook (${mode}).`);
4365
+ await writeFileDurable(settingsPath, `${JSON.stringify(settings, null, 2)}
4366
+ `);
4367
+ for (const line of lines) console.log(line);
4368
+ }
4369
+ function describeStopInstall(action, mode, dryRun) {
4370
+ if (action === "unchanged") {
4371
+ return `The basou Stop hook is already registered (${mode}); no change.`;
4372
+ }
4373
+ if (dryRun) {
4374
+ return `[dry-run] Would ${action === "installed" ? "install" : "update"} the basou Stop hook (${mode}).`;
4375
+ }
4376
+ return `${action === "installed" ? "Installed" : "Updated"} the basou Stop hook (${mode}).`;
4377
+ }
4378
+ function describeSessionStartInstall(outcome, dryRun) {
4379
+ if (outcome.kind === "skipped") {
4380
+ return "The SessionStart hook was left as it is (--no-session-start).";
4381
+ }
4382
+ if (outcome.kind === "malformed") {
4383
+ return "The basou SessionStart hook was NOT installed: 'hooks.SessionStart' in settings.json is not a list of hook groups. Fix it and re-run 'basou hook install'.";
4384
+ }
4385
+ const action = outcome.action;
4386
+ if (action === "unchanged") {
4387
+ return "The basou SessionStart hook is already registered; no change.";
4388
+ }
4389
+ const prefix = dryRun ? "[dry-run] Would " : "";
4390
+ if (action === "replaced") {
4391
+ return `${prefix}${dryRun ? "replace" : "Replaced"} the hand-registered 'basou orient' SessionStart hook with 'basou hook session-start': the same position, plus the baseline the Stop hook measures the session's file changes against. It differs in three ways: it speaks only for a workspace registered in ~/.basou/portfolio.yaml, it does not rewrite .basou/orientation.md at session start, and it stays silent when the position names another registered workspace.`;
4392
+ }
4393
+ if (action === "updated") {
4394
+ return `${prefix}${dryRun ? "update" : "Updated"} the basou SessionStart hook.`;
4395
+ }
4396
+ return `${prefix}${dryRun ? "install" : "Installed"} the basou SessionStart hook: each session gets the workspace's position, and the Stop hook can see the files the session changes through the shell.`;
4397
+ }
4398
+ function describeSessionStartLeftovers(settings) {
4399
+ const own = findClaudeSessionStartHooks(settings);
4400
+ if (own.length === 0) return [];
4401
+ const lines = [];
4402
+ for (const other of findUnrecognizedSessionStart(settings)) {
4403
+ lines.push(
4404
+ `note: this SessionStart hook runs basou '${other.runs === "orient" ? "orient" : "hook session-start"}' inside a longer command, so it was left as written, and a session may receive the position twice: ${other.command}`
4405
+ );
4406
+ }
4407
+ if (own.length > 1) {
4408
+ const matchers = own.map((o) => o.matcher ?? "(every source)").join(", ");
4409
+ lines.push(
4410
+ `note: ${own.length} basou SessionStart hooks remain, under different matchers (${matchers}). They were kept so that no session source stops firing; a source that more than one matches receives the position twice. Remove the one you do not want by hand.`
4411
+ );
4412
+ }
4413
+ return lines;
4325
4414
  }
4326
4415
  async function runHookUninstall(options) {
4327
4416
  try {
@@ -4339,15 +4428,22 @@ async function doRunHookUninstall(options) {
4339
4428
  console.log("No settings.json; nothing to remove.");
4340
4429
  return;
4341
4430
  }
4342
- const { settings, action } = removeStopHook(parsed);
4343
- if (action === "absent") {
4344
- console.log("No basou Stop hook found; nothing removed.");
4431
+ const stop = removeStopHook(parsed);
4432
+ const sessionStart = removeClaudeSessionStartHook(stop.settings);
4433
+ if (stop.action === "absent" && sessionStart.action === "absent") {
4434
+ console.log("No basou Stop hook or SessionStart hook found; nothing removed.");
4345
4435
  return;
4346
4436
  }
4347
- const newBody = `${JSON.stringify(settings, null, 2)}
4437
+ const newBody = `${JSON.stringify(sessionStart.settings, null, 2)}
4348
4438
  `;
4439
+ const removedNames = [
4440
+ ...stop.action === "removed" ? ["Stop hook"] : [],
4441
+ ...sessionStart.action === "removed" ? ["SessionStart hook"] : []
4442
+ ];
4349
4443
  if (options.dryRun === true) {
4350
- console.log("[dry-run] Would remove the basou Stop hook from settings.json.");
4444
+ for (const name of removedNames) {
4445
+ console.log(`[dry-run] Would remove the basou ${name} from settings.json.`);
4446
+ }
4351
4447
  return;
4352
4448
  }
4353
4449
  const recheck = await readSettings(settingsPath);
@@ -4358,7 +4454,9 @@ async function doRunHookUninstall(options) {
4358
4454
  }
4359
4455
  await backupSettingsOnce(settingsPath, raw);
4360
4456
  await writeFileDurable(settingsPath, newBody);
4361
- console.log("Removed the basou Stop hook from settings.json.");
4457
+ for (const name of removedNames) {
4458
+ console.log(`Removed the basou ${name} from settings.json.`);
4459
+ }
4362
4460
  }
4363
4461
  async function runHookStatus(options) {
4364
4462
  try {
@@ -4374,14 +4472,49 @@ async function doRunHookStatus(options) {
4374
4472
  const command = findBasouStopHookCommand(parsed);
4375
4473
  if (command === null) {
4376
4474
  console.log("basou Stop hook: not registered. Run 'basou hook install' to register it.");
4475
+ } else {
4476
+ const mode = describeHookMode({
4477
+ block: / --block\b/.test(command),
4478
+ review: / --require-review\b/.test(command)
4479
+ });
4480
+ console.log(`basou Stop hook: registered, ${mode}.`);
4481
+ await reportHookEntryBuild(command);
4482
+ }
4483
+ reportSessionStartStatus(parsed);
4484
+ }
4485
+ function reportSessionStartStatus(parsed) {
4486
+ if (isClaudeSessionStartMalformed(parsed)) {
4487
+ console.log(
4488
+ "basou SessionStart hook: cannot tell -- 'hooks.SessionStart' in settings.json is not a list of hook groups."
4489
+ );
4377
4490
  return;
4378
4491
  }
4379
- const mode = describeHookMode({
4380
- block: / --block\b/.test(command),
4381
- review: / --require-review\b/.test(command)
4382
- });
4383
- console.log(`basou Stop hook: registered, ${mode}.`);
4384
- await reportHookEntryBuild(command);
4492
+ const own = findClaudeSessionStartHooks(parsed);
4493
+ const others = findUnrecognizedSessionStart(parsed);
4494
+ for (const hook of own.filter((o) => o.kind === "session-start")) {
4495
+ console.log(`basou SessionStart hook: registered, fires ${describeFiring(hook.matcher)}.`);
4496
+ }
4497
+ for (const hook of own.filter((o) => o.kind === "orient")) {
4498
+ console.log(
4499
+ `basou SessionStart hook: 'basou orient' registered by hand, fires ${describeFiring(hook.matcher)}. It delivers the position but records no git baseline, so the files a session changes through the shell are not observed; 'basou hook install' replaces it with 'basou hook session-start'.`
4500
+ );
4501
+ }
4502
+ if (own.length === 0) {
4503
+ if (others.length === 0) {
4504
+ console.log(
4505
+ "basou SessionStart hook: not registered. Sessions get no position at start, and the files a session changes through the shell are not observed. Run 'basou hook install' to register it."
4506
+ );
4507
+ }
4508
+ for (const other of others) {
4509
+ console.log(
4510
+ other.runs === "orient" ? `basou SessionStart hook: not registered by basou. A SessionStart hook runs 'basou orient' inside a longer command, so sessions may get the position, but no git baseline is recorded: ${other.command}` : `basou SessionStart hook: not registered by basou. A SessionStart hook runs 'basou hook session-start' inside a longer command, which basou leaves as written: ${other.command}`
4511
+ );
4512
+ }
4513
+ }
4514
+ for (const line of describeSessionStartLeftovers(parsed)) console.log(line);
4515
+ }
4516
+ function describeFiring(matcher) {
4517
+ return matcher === void 0 || matcher === "" || matcher === "*" ? "on every session source" : `on ${matcher}`;
4385
4518
  }
4386
4519
  async function reportHookEntryBuild(command) {
4387
4520
  console.log(` this basou is: ${BASOU_VERSION_LINE}`);
@@ -9789,6 +9922,7 @@ import {
9789
9922
  appendEventToExistingSession as appendEventToExistingSession3,
9790
9923
  assertBasouRootSafe as assertBasouRootSafe13,
9791
9924
  basouPaths as basouPaths18,
9925
+ displayPath as displayPath3,
9792
9926
  EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION6,
9793
9927
  enumerateSessionDirs as enumerateSessionDirs2,
9794
9928
  findErrorCode as findErrorCode12,
@@ -10057,7 +10191,7 @@ function formatWorkingDir(workingDir, repositoryRoot, options) {
10057
10191
  }
10058
10192
  function formatRelatedFiles(files) {
10059
10193
  if (files.length === 0) return "0 paths";
10060
- const head = files.slice(0, 3).join(", ");
10194
+ const head = files.slice(0, 3).map(displayPath3).join(", ");
10061
10195
  const remaining = files.length - 3;
10062
10196
  if (remaining <= 0) return `${files.length} paths (${head})`;
10063
10197
  return `${files.length} paths (${head}, ... +${remaining} more)`;
@@ -10085,7 +10219,7 @@ function eventVariantSummary(ev) {
10085
10219
  case "git_snapshot":
10086
10220
  return `branch=${ev.branch} dirty=${ev.dirty}`;
10087
10221
  case "file_changed":
10088
- return `${ev.change_type} ${ev.path}`;
10222
+ return `${ev.change_type} ${displayPath3(ev.path)}`;
10089
10223
  case "session_status_changed":
10090
10224
  return `${ev.from} -> ${ev.to}`;
10091
10225
  case "session_started":
@@ -13037,6 +13171,25 @@ var VIEW_HTML = `<!doctype html>
13037
13171
  detail.appendChild(tl);
13038
13172
  }).catch(fail);
13039
13173
  }
13174
+ // The page cannot import @basou/core, so this mirrors its displayPath: a
13175
+ // recorded name is shown with its control characters as visible escapes, so a
13176
+ // newline in it does not read as a space and an invisible one stays visible.
13177
+ // A test lifts this function and checks it agrees with displayPath.
13178
+ function showPath(p) {
13179
+ var out = '';
13180
+ for (var i = 0; i < p.length; i++) {
13181
+ var c = p.charCodeAt(i);
13182
+ var acting = c <= 0x1f || (c >= 0x7f && c <= 0x9f) || c === 0x2028 || c === 0x2029 ||
13183
+ (c >= 0x202a && c <= 0x202e) || (c >= 0x2066 && c <= 0x2069);
13184
+ if (!acting) { out += p.charAt(i); continue; }
13185
+ if (c === 10) out += '\\\\n';
13186
+ else if (c === 13) out += '\\\\r';
13187
+ else if (c === 9) out += '\\\\t';
13188
+ else if (c <= 0xff) out += '\\\\x' + ('0' + c.toString(16)).slice(-2);
13189
+ else out += '\\\\u' + ('000' + c.toString(16)).slice(-4);
13190
+ }
13191
+ return out;
13192
+ }
13040
13193
  function eventSummary(ev) {
13041
13194
  if (ev.type === 'command_executed') {
13042
13195
  var cmd = (ev.args && ev.args.length) ? ev.args.join(' ') : ev.command;
@@ -13044,7 +13197,7 @@ var VIEW_HTML = `<!doctype html>
13044
13197
  var ex = (ev.exit_code === null || ev.exit_code === undefined) ? '' : ' (exit ' + ev.exit_code + ')';
13045
13198
  return cmd + ex;
13046
13199
  }
13047
- if (ev.type === 'file_changed') return ev.path + ' [' + ev.change_type + ']';
13200
+ if (ev.type === 'file_changed') return showPath(ev.path) + ' [' + ev.change_type + ']';
13048
13201
  if (ev.type === 'decision_recorded') return ev.title || '';
13049
13202
  if (ev.type === 'decision_voided') {
13050
13203
  var vs = ev.superseded_by ? ' superseded by ' + ev.superseded_by : '';
@@ -13862,7 +14015,7 @@ async function assertWorkspaceInitialized15(basouRoot) {
13862
14015
  function readBuildStamp() {
13863
14016
  if (false) return void 0;
13864
14017
  try {
13865
- return JSON.parse('{"version":"0.49.0","commit":"6c4704a","committedAt":"2026-09-23T11:46:39+09:00"}');
14018
+ return JSON.parse('{"version":"0.51.0","commit":"22ef44f","committedAt":"2026-09-23T22:27:59+09:00"}');
13866
14019
  } catch {
13867
14020
  return void 0;
13868
14021
  }