@basou/cli 0.29.0 → 0.30.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
@@ -2114,16 +2114,20 @@ function registerHookCommand(program2) {
2114
2114
  ).option(
2115
2115
  "--block",
2116
2116
  "Opt-in enforcement: hold the agent in-turn (decision:block) instead of a non-blocking reminder"
2117
+ ).option(
2118
+ "--require-review",
2119
+ "Opt-in review gate: also remind when a session shipped substantive code (push / PR / merge) without recording a review"
2117
2120
  ).addHelpText("after", HOOK_STOP_HELP).action(async (options) => {
2118
2121
  const minEdits = parseMinEdits(options.minEdits);
2119
2122
  await runHookStop({
2120
2123
  ...minEdits !== void 0 ? { minEdits } : {},
2121
- ...options.block === true ? { block: true } : {}
2124
+ ...options.block === true ? { block: true } : {},
2125
+ ...options.requireReview === true ? { requireReview: true } : {}
2122
2126
  });
2123
2127
  });
2124
2128
  hook.command("install").description(
2125
- "Register the Stop hook in ~/.claude/settings.json (reproducible, idempotent). Default is advisory; --block opts into in-turn enforcement."
2126
- ).option("--block", "Register the blocking (opt-in enforcement) form instead of advisory").option("--min-edits <n>", "Pass a custom file-edit threshold to the registered hook").option("--settings <path>", "Override the settings.json path (intended for tests)").option("--dry-run", "Print what would change without writing").option("-v, --verbose", "Show error causes").action(async (opts) => {
2129
+ "Register the Stop hook in ~/.claude/settings.json (reproducible, idempotent). Default is advisory capture-only; --block opts into in-turn enforcement, --require-review opts into the review gate."
2130
+ ).option("--block", "Register the blocking (opt-in enforcement) form instead of advisory").option("--require-review", "Register with the opt-in review gate enabled").option("--min-edits <n>", "Pass a custom file-edit threshold to the registered hook").option("--settings <path>", "Override the settings.json path (intended for tests)").option("--dry-run", "Print what would change without writing").option("-v, --verbose", "Show error causes").action(async (opts) => {
2127
2131
  await runHookInstall(opts);
2128
2132
  });
2129
2133
  hook.command("uninstall").description(
@@ -2147,6 +2151,12 @@ Substantive = EITHER >= ${DEFAULT_STOP_HOOK_MIN_EDITS} file edits (default) OR a
2147
2151
  answer (an uncaptured conversational decision). Read-only Bash (ls / grep /
2148
2152
  git status) does NOT count.
2149
2153
 
2154
+ With --require-review (opt-in, 'basou hook install --require-review') it also
2155
+ reminds when the session SHIPPED substantive code (git push / git merge /
2156
+ gh pr create|merge) without recording a review ('basou review record'). This
2157
+ gate is off by default; when on, its reminder is composed into the same
2158
+ envelope as the capture reminder.
2159
+
2150
2160
  By default the reminder is non-blocking: Claude sees it and may act on it or
2151
2161
  stop. With --block (opt-in enforcement, 'basou hook install --block') it instead
2152
2162
  returns decision:block, holding the agent in-turn to act on the reminder; the
@@ -2190,11 +2200,17 @@ async function doRunHookStop(options, ctx) {
2190
2200
  stopHookActive: false,
2191
2201
  ...options.minEdits !== void 0 ? { minEdits: options.minEdits } : {}
2192
2202
  });
2193
- if (evaluation.kind !== "nudge") return;
2194
- const payloadJson = options.block === true ? JSON.stringify({ decision: "block", reason: evaluation.additionalContext }) : JSON.stringify({
2203
+ const parts = [];
2204
+ if (evaluation.kind === "nudge") parts.push(evaluation.additionalContext);
2205
+ if (options.requireReview === true && evaluation.review.fires) {
2206
+ parts.push(evaluation.review.additionalContext);
2207
+ }
2208
+ if (parts.length === 0) return;
2209
+ const reason = parts.join("\n\n");
2210
+ const payloadJson = options.block === true ? JSON.stringify({ decision: "block", reason }) : JSON.stringify({
2195
2211
  hookSpecificOutput: {
2196
2212
  hookEventName: "Stop",
2197
- additionalContext: evaluation.additionalContext
2213
+ additionalContext: reason
2198
2214
  }
2199
2215
  });
2200
2216
  write(`${payloadJson}
@@ -2247,6 +2263,7 @@ function resolveCliEntry() {
2247
2263
  function normalizeInstallOptions(raw) {
2248
2264
  const out = {};
2249
2265
  if (raw.block === true) out.block = true;
2266
+ if (raw.requireReview === true) out.requireReview = true;
2250
2267
  if (raw.settings !== void 0) out.settings = raw.settings;
2251
2268
  if (raw.dryRun === true) out.dryRun = true;
2252
2269
  if (raw.verbose === true) out.verbose = true;
@@ -2306,9 +2323,13 @@ async function doRunHookInstall(options, ctx = {}) {
2306
2323
  const command = buildStopHookCommand({
2307
2324
  cliEntry,
2308
2325
  ...options.block === true ? { block: true } : {},
2326
+ ...options.requireReview === true ? { requireReview: true } : {},
2309
2327
  ...options.minEdits !== void 0 ? { minEdits: options.minEdits } : {}
2310
2328
  });
2311
- const mode = options.block === true ? "blocking" : "advisory";
2329
+ const mode = describeHookMode({
2330
+ block: options.block === true,
2331
+ review: options.requireReview === true
2332
+ });
2312
2333
  await assertNotSymlink(settingsPath);
2313
2334
  const { raw, parsed } = await readSettings(settingsPath);
2314
2335
  const { settings, action } = upsertStopHook(parsed, command);
@@ -2385,9 +2406,17 @@ async function doRunHookStatus(options) {
2385
2406
  console.log("basou Stop hook: not registered. Run 'basou hook install' to register it.");
2386
2407
  return;
2387
2408
  }
2388
- const mode = / --block\b/.test(command) ? "blocking (opt-in enforcement)" : "advisory (non-blocking)";
2409
+ const mode = describeHookMode({
2410
+ block: / --block\b/.test(command),
2411
+ review: / --require-review\b/.test(command)
2412
+ });
2389
2413
  console.log(`basou Stop hook: registered, ${mode}.`);
2390
2414
  }
2415
+ function describeHookMode(tiers) {
2416
+ const enforcement = tiers.block ? "blocking (opt-in enforcement)" : "advisory (non-blocking)";
2417
+ const gates = tiers.review ? "capture + review" : "capture";
2418
+ return `${enforcement}, ${gates}`;
2419
+ }
2391
2420
 
2392
2421
  // src/commands/import.ts
2393
2422
  import { createReadStream } from "fs";
@@ -6220,25 +6249,131 @@ function renderProjectRetrofit(result) {
6220
6249
 
6221
6250
  // src/commands/protocol.ts
6222
6251
  import { readFile as readFile4 } from "fs/promises";
6252
+ import { PROTOCOL_END, PROTOCOL_START, parseMarkers as parseMarkers3, readMarkdownFile as readMarkdownFile6 } from "@basou/core";
6253
+
6254
+ // src/lib/context-channel.ts
6255
+ import { homedir as homedir7 } from "os";
6256
+ import { join as join10 } from "path";
6223
6257
  import {
6224
- PROTOCOL_END,
6225
- PROTOCOL_START,
6258
+ ORIENTATION_END,
6259
+ ORIENTATION_START,
6226
6260
  parseMarkers as parseMarkers2,
6227
6261
  readMarkdownFile as readMarkdownFile5,
6228
6262
  removeMarkerSection as removeMarkerSection2
6229
6263
  } from "@basou/core";
6264
+ var CODEX_TARGET_PATH = join10(homedir7(), ".codex", "AGENTS.md");
6265
+ var ORIENTATION_MARKERS = { start: ORIENTATION_START, end: ORIENTATION_END };
6266
+ var ORIENTATION_MANAGED_NOTE = "<!-- Managed by basou: 'basou refresh' regenerates everything between the BASOU:ORIENTATION markers with the workspace's current position. This block is transient \u2014 it changes every refresh; do not edit it. -->";
6267
+ function buildTargetBody(existing, block, markers) {
6268
+ const wrapped = `${markers.start}
6269
+ ${block}${markers.end}
6270
+ `;
6271
+ if (existing === null || existing === "") return wrapped;
6272
+ const section = parseMarkers2(existing, markers);
6273
+ switch (section.kind) {
6274
+ case "ok":
6275
+ return `${section.before}${markers.start}
6276
+ ${block}${markers.end}${section.after}`;
6277
+ case "no_markers": {
6278
+ const sep = existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
6279
+ return `${existing}${sep}${wrapped}`;
6280
+ }
6281
+ default:
6282
+ throw new Error(
6283
+ "The basou-managed markers in the target are malformed (a marker is missing, duplicated, or out of order). Fix or remove them, then retry."
6284
+ );
6285
+ }
6286
+ }
6287
+ async function backupOnce(target, existing) {
6288
+ if (existing === null) return;
6289
+ const bak = `${target}.basou-bak`;
6290
+ const already = await readMarkdownFile5(bak);
6291
+ if (already !== null) return;
6292
+ await writeFileDurable(bak, existing);
6293
+ }
6294
+ async function syncMarkerBlock(opts) {
6295
+ const { target, markers, block } = opts;
6296
+ await assertNotSymlink(target);
6297
+ const existing = await readMarkdownFile5(target);
6298
+ const newBody = buildTargetBody(existing, block, markers);
6299
+ if (newBody === existing) return { action: "unchanged" };
6300
+ const hadBlock = existing !== null && parseMarkers2(existing, markers).kind === "ok";
6301
+ const action = hadBlock ? "updated" : "installed";
6302
+ if (opts.dryRun === true) return { action };
6303
+ const recheck = await readMarkdownFile5(target);
6304
+ if (recheck !== existing) {
6305
+ throw new Error(
6306
+ "The target changed during sync; aborting so a concurrent edit is not overwritten. Re-run the command."
6307
+ );
6308
+ }
6309
+ await backupOnce(target, existing);
6310
+ await writeFileDurable(target, newBody);
6311
+ return { action };
6312
+ }
6313
+ function assertNoMarkerLine(body, markers) {
6314
+ for (const line of body.split(/\r?\n/)) {
6315
+ if (line === markers.start || line === markers.end) {
6316
+ throw new Error(
6317
+ "The content contains a basou marker line, which would corrupt the managed block. Remove that line from the source."
6318
+ );
6319
+ }
6320
+ }
6321
+ }
6322
+ async function removeMarkerBlock(opts) {
6323
+ const { target, markers, fileLabel } = opts;
6324
+ await assertNotSymlink(target);
6325
+ const existing = await readMarkdownFile5(target);
6326
+ if (existing === null) return { removed: false };
6327
+ const newBody = removeMarkerSection2(existing, fileLabel, markers);
6328
+ if (newBody === existing) return { removed: false };
6329
+ if (opts.dryRun === true) return { removed: true };
6330
+ const recheck = await readMarkdownFile5(target);
6331
+ if (recheck !== existing) {
6332
+ throw new Error(
6333
+ "The target changed during unsync; aborting so a concurrent edit is not overwritten. Re-run the command."
6334
+ );
6335
+ }
6336
+ await backupOnce(target, existing);
6337
+ await writeFileDurable(target, newBody);
6338
+ return { removed: true };
6339
+ }
6340
+ async function syncOrientationChannel(opts) {
6341
+ assertNoMarkerLine(opts.body, ORIENTATION_MARKERS);
6342
+ const block = `${ORIENTATION_MANAGED_NOTE}
6343
+
6344
+ ${opts.body.replace(/\s+$/, "")}
6345
+ `;
6346
+ return syncMarkerBlock({
6347
+ target: opts.target ?? CODEX_TARGET_PATH,
6348
+ markers: ORIENTATION_MARKERS,
6349
+ block,
6350
+ ...opts.dryRun === true ? { dryRun: true } : {}
6351
+ });
6352
+ }
6353
+ async function renderOrientationToCodexChannel(opts) {
6354
+ const body = await readMarkdownFile5(opts.orientationPath);
6355
+ if (body === null) return null;
6356
+ const { action } = await syncOrientationChannel({
6357
+ body,
6358
+ ...opts.channelPath !== void 0 ? { target: opts.channelPath } : {}
6359
+ });
6360
+ return {
6361
+ action,
6362
+ line: `codex channel: orientation ${action} in ${opts.channelPath ?? "~/.codex/AGENTS.md"}`
6363
+ };
6364
+ }
6230
6365
 
6231
6366
  // src/lib/protocols-config.ts
6232
- import { homedir as homedir7 } from "os";
6233
- import { isAbsolute as isAbsolute4, join as join10, resolve as resolve8 } from "path";
6367
+ import { homedir as homedir8 } from "os";
6368
+ import { isAbsolute as isAbsolute4, join as join11, resolve as resolve8 } from "path";
6234
6369
  import { readYamlFile as readYamlFile5 } from "@basou/core";
6235
- var DEFAULT_PROTOCOLS_CONFIG_PATH = join10(homedir7(), ".basou", "protocols.yaml");
6236
- var DEFAULT_TARGET_PATH = join10(homedir7(), ".claude", "CLAUDE.md");
6370
+ var DEFAULT_PROTOCOLS_CONFIG_PATH = join11(homedir8(), ".basou", "protocols.yaml");
6371
+ var DEFAULT_TARGET_PATH = join11(homedir8(), ".claude", "CLAUDE.md");
6237
6372
  var ALLOWED_TOP_KEYS = /* @__PURE__ */ new Set(["version", "protocols"]);
6238
6373
  var ALLOWED_ENTRY_KEYS = /* @__PURE__ */ new Set(["source", "title"]);
6239
6374
  function expandTilde3(p) {
6240
- if (p === "~") return homedir7();
6241
- if (p.startsWith("~/")) return join10(homedir7(), p.slice(2));
6375
+ if (p === "~") return homedir8();
6376
+ if (p.startsWith("~/")) return join11(homedir8(), p.slice(2));
6242
6377
  return p;
6243
6378
  }
6244
6379
  function isRecord3(value) {
@@ -6359,13 +6494,7 @@ async function readProtocolSources(entries) {
6359
6494
  }
6360
6495
  throw new Error("Failed to read a protocol source file.", { cause: error });
6361
6496
  }
6362
- for (const line of content.split(/\r?\n/)) {
6363
- if (line === PROTOCOL_START || line === PROTOCOL_END) {
6364
- throw new Error(
6365
- "A protocol source contains a BASOU:PROTOCOLS marker line, which would corrupt the managed block. Remove that line from the source."
6366
- );
6367
- }
6368
- }
6497
+ assertNoMarkerLine(content, PROTOCOL_MARKERS);
6369
6498
  out.push({ entry, content });
6370
6499
  }
6371
6500
  return out;
@@ -6382,74 +6511,41 @@ ${body}` : body;
6382
6511
  ${sections.join("\n\n")}
6383
6512
  `;
6384
6513
  }
6385
- function buildTargetBody(existing, block) {
6386
- const wrapped = `${PROTOCOL_START}
6387
- ${block}${PROTOCOL_END}
6388
- `;
6389
- if (existing === null || existing === "") return wrapped;
6390
- const section = parseMarkers2(existing, PROTOCOL_MARKERS);
6391
- switch (section.kind) {
6392
- case "ok":
6393
- return `${section.before}${PROTOCOL_START}
6394
- ${block}${PROTOCOL_END}${section.after}`;
6395
- case "no_markers": {
6396
- const sep = existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
6397
- return `${existing}${sep}${wrapped}`;
6398
- }
6399
- default:
6400
- throw new Error(
6401
- "The BASOU:PROTOCOLS markers in the target are malformed (a marker is missing, duplicated, or out of order). Fix or remove them, then retry."
6402
- );
6403
- }
6404
- }
6405
- async function backupOnce(target, existing) {
6406
- if (existing === null) return;
6407
- const bak = `${target}.basou-bak`;
6408
- const already = await readMarkdownFile5(bak);
6409
- if (already !== null) return;
6410
- await writeFileDurable(bak, existing);
6411
- }
6412
6514
  async function doRunProtocolSync(options) {
6413
6515
  const configPath = options.config ?? DEFAULT_PROTOCOLS_CONFIG_PATH;
6414
6516
  const target = options.target ?? DEFAULT_TARGET_PATH;
6415
6517
  const entries = await loadProtocolsConfig(configPath);
6416
6518
  const sources = await readProtocolSources(entries);
6417
6519
  const block = buildBlock(sources);
6418
- await assertNotSymlink(target);
6419
- const existing = await readMarkdownFile5(target);
6420
- const newBody = buildTargetBody(existing, block);
6421
- if (newBody === existing) {
6520
+ const result = await syncMarkerBlock({
6521
+ target,
6522
+ markers: PROTOCOL_MARKERS,
6523
+ block,
6524
+ ...options.dryRun === true ? { dryRun: true } : {}
6525
+ });
6526
+ if (result.action === "unchanged") {
6422
6527
  console.log(`The basou:protocols block is already up to date (${entries.length} protocol(s)).`);
6423
6528
  return;
6424
6529
  }
6425
- const hadBlock = existing !== null && parseMarkers2(existing, PROTOCOL_MARKERS).kind === "ok";
6426
6530
  if (options.dryRun === true) {
6427
6531
  console.log(
6428
- `[dry-run] Would ${hadBlock ? "update" : "install"} the basou:protocols block (${entries.length} protocol(s)).`
6532
+ `[dry-run] Would ${result.action === "updated" ? "update" : "install"} the basou:protocols block (${entries.length} protocol(s)).`
6429
6533
  );
6430
6534
  for (const { entry } of sources) {
6431
6535
  console.log(` - ${entry.title ?? entry.source}`);
6432
6536
  }
6433
6537
  return;
6434
6538
  }
6435
- const recheck = await readMarkdownFile5(target);
6436
- if (recheck !== existing) {
6437
- throw new Error(
6438
- "The target changed during sync; aborting so a concurrent edit is not overwritten. Re-run 'basou protocol sync'."
6439
- );
6440
- }
6441
- await backupOnce(target, existing);
6442
- await writeFileDurable(target, newBody);
6443
6539
  console.log(
6444
- `${hadBlock ? "Updated" : "Installed"} the basou:protocols block in the global CLAUDE.md (${entries.length} protocol(s)).`
6540
+ `${result.action === "updated" ? "Updated" : "Installed"} the basou:protocols block in the global CLAUDE.md (${entries.length} protocol(s)).`
6445
6541
  );
6446
6542
  }
6447
6543
  async function doRunProtocolList(options) {
6448
6544
  const configPath = options.config ?? DEFAULT_PROTOCOLS_CONFIG_PATH;
6449
6545
  const target = options.target ?? DEFAULT_TARGET_PATH;
6450
6546
  const entries = await loadProtocolsConfig(configPath);
6451
- const existing = await readMarkdownFile5(target);
6452
- const installed = existing !== null && parseMarkers2(existing, PROTOCOL_MARKERS).kind === "ok";
6547
+ const existing = await readMarkdownFile6(target);
6548
+ const installed = existing !== null && parseMarkers3(existing, PROTOCOL_MARKERS).kind === "ok";
6453
6549
  console.log(`Declared protocols (${entries.length}):`);
6454
6550
  for (const entry of entries) {
6455
6551
  console.log(` - ${entry.title ?? entry.source}`);
@@ -6458,14 +6554,13 @@ async function doRunProtocolList(options) {
6458
6554
  }
6459
6555
  async function doRunProtocolUnsync(options) {
6460
6556
  const target = options.target ?? DEFAULT_TARGET_PATH;
6461
- await assertNotSymlink(target);
6462
- const existing = await readMarkdownFile5(target);
6463
- if (existing === null) {
6464
- console.log("No target file; nothing to remove.");
6465
- return;
6466
- }
6467
- const newBody = removeMarkerSection2(existing, "CLAUDE.md", PROTOCOL_MARKERS);
6468
- if (newBody === existing) {
6557
+ const result = await removeMarkerBlock({
6558
+ target,
6559
+ markers: PROTOCOL_MARKERS,
6560
+ fileLabel: "CLAUDE.md",
6561
+ ...options.dryRun === true ? { dryRun: true } : {}
6562
+ });
6563
+ if (!result.removed) {
6469
6564
  console.log("No basou:protocols block found; nothing removed.");
6470
6565
  return;
6471
6566
  }
@@ -6473,14 +6568,6 @@ async function doRunProtocolUnsync(options) {
6473
6568
  console.log("[dry-run] Would remove the basou:protocols block from the global CLAUDE.md.");
6474
6569
  return;
6475
6570
  }
6476
- const recheck = await readMarkdownFile5(target);
6477
- if (recheck !== existing) {
6478
- throw new Error(
6479
- "The target changed during unsync; aborting so a concurrent edit is not overwritten. Re-run 'basou protocol unsync'."
6480
- );
6481
- }
6482
- await backupOnce(target, existing);
6483
- await writeFileDurable(target, newBody);
6484
6571
  console.log("Removed the basou:protocols block from the global CLAUDE.md.");
6485
6572
  }
6486
6573
 
@@ -6490,16 +6577,16 @@ import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
6490
6577
 
6491
6578
  // src/commands/refresh-watch.ts
6492
6579
  import { readdir as readdir2, stat as stat5 } from "fs/promises";
6493
- import { homedir as homedir8 } from "os";
6494
- import { join as join11 } from "path";
6580
+ import { homedir as homedir9 } from "os";
6581
+ import { join as join12 } from "path";
6495
6582
  import { findErrorCode as findErrorCode8 } from "@basou/core";
6496
6583
  var DEFAULT_WATCH_INTERVAL_SEC = 30;
6497
6584
  var MIN_WATCH_INTERVAL_SEC = 5;
6498
6585
  var MAX_WATCH_INTERVAL_SEC = 86400;
6499
6586
  function watchedRoots(ctx) {
6500
6587
  return [
6501
- ctx.codexSessionsDir ?? join11(homedir8(), ".codex", "sessions"),
6502
- ctx.claudeProjectsDir ?? join11(homedir8(), ".claude", "projects")
6588
+ ctx.codexSessionsDir ?? join12(homedir9(), ".codex", "sessions"),
6589
+ ctx.claudeProjectsDir ?? join12(homedir9(), ".claude", "projects")
6503
6590
  ];
6504
6591
  }
6505
6592
  async function scanSourceLogs(roots) {
@@ -6513,7 +6600,7 @@ async function scanSourceLogs(roots) {
6513
6600
  throw new Error("Failed to read a source log directory", { cause: error });
6514
6601
  }
6515
6602
  for (const entry of entries) {
6516
- const full = join11(dir, entry.name);
6603
+ const full = join12(dir, entry.name);
6517
6604
  if (entry.isDirectory()) {
6518
6605
  await walk(full);
6519
6606
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
@@ -6620,19 +6707,19 @@ function parseInterval(value) {
6620
6707
  return seconds;
6621
6708
  }
6622
6709
  function abortableSleep(ms, signal) {
6623
- return new Promise((resolve12) => {
6710
+ return new Promise((resolve13) => {
6624
6711
  if (signal.aborted) {
6625
- resolve12();
6712
+ resolve13();
6626
6713
  return;
6627
6714
  }
6628
6715
  let timer;
6629
6716
  const onAbort = () => {
6630
6717
  clearTimeout(timer);
6631
- resolve12();
6718
+ resolve13();
6632
6719
  };
6633
6720
  timer = setTimeout(() => {
6634
6721
  signal.removeEventListener("abort", onAbort);
6635
- resolve12();
6722
+ resolve13();
6636
6723
  }, ms);
6637
6724
  signal.addEventListener("abort", onAbort, { once: true });
6638
6725
  });
@@ -6685,7 +6772,7 @@ async function doRunRefreshPortfolio(options, ctx) {
6685
6772
  for (const ws of workspaces) {
6686
6773
  const label = ws.label ?? ws.path;
6687
6774
  try {
6688
- const result = await computeRefresh(
6775
+ const { result } = await computeRefresh(
6689
6776
  { ...options, portfolio: false },
6690
6777
  { ...ctx, cwd: ws.path }
6691
6778
  );
@@ -6754,7 +6841,7 @@ async function computeRefresh(options, ctx) {
6754
6841
  const paths = basouPaths11(repositoryRoot);
6755
6842
  await assertWorkspaceInitialized8(paths.root);
6756
6843
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
6757
- return refreshAll({
6844
+ const result = await refreshAll({
6758
6845
  options: {
6759
6846
  ...options.project !== void 0 && options.project.length > 0 ? { project: options.project } : {},
6760
6847
  ...options.force === true ? { force: true } : {},
@@ -6766,16 +6853,30 @@ async function computeRefresh(options, ctx) {
6766
6853
  paths,
6767
6854
  nowIso
6768
6855
  });
6856
+ return { result, paths };
6769
6857
  }
6770
6858
  async function doRunRefresh(options, ctx) {
6771
- const result = await computeRefresh(options, ctx);
6859
+ const { result, paths } = await computeRefresh(options, ctx);
6860
+ const channelLine = options.dryRun === true ? null : await syncCodexOrientationChannel(paths, ctx.codexChannelPath);
6772
6861
  if (options.json === true) {
6773
6862
  console.log(JSON.stringify(result));
6774
6863
  } else {
6775
6864
  printRefreshSummary(result);
6865
+ if (channelLine !== null) console.log(channelLine);
6776
6866
  }
6777
6867
  return result;
6778
6868
  }
6869
+ async function syncCodexOrientationChannel(paths, channelPath) {
6870
+ try {
6871
+ const rendered = await renderOrientationToCodexChannel({
6872
+ orientationPath: paths.files.orientation,
6873
+ ...channelPath !== void 0 ? { channelPath } : {}
6874
+ });
6875
+ return rendered === null ? null : rendered.line;
6876
+ } catch (error) {
6877
+ return `codex channel skipped: ${error instanceof Error ? error.message : String(error)}`;
6878
+ }
6879
+ }
6779
6880
  function describeImport(outcome) {
6780
6881
  if (outcome.status === "skipped") {
6781
6882
  return `${outcome.adapter}: skipped (${outcome.reason})`;
@@ -6908,9 +7009,195 @@ async function assertWorkspaceInitialized9(basouRoot) {
6908
7009
  }
6909
7010
  }
6910
7011
 
6911
- // src/commands/review-gaps.ts
7012
+ // src/commands/review.ts
7013
+ import { readFile as readFile5 } from "fs/promises";
7014
+ import { homedir as homedir10 } from "os";
7015
+ import { resolve as resolve10 } from "path";
6912
7016
  import {
7017
+ assertBasouRootSafe as assertBasouRootSafe11,
6913
7018
  basouPaths as basouPaths13,
7019
+ buildReviewRecordedEvent,
7020
+ buildReviewRecordLabel,
7021
+ createAdHocSessionWithEvent as createAdHocSessionWithEvent3,
7022
+ findErrorCode as findErrorCode11,
7023
+ parseReviewRecordInput,
7024
+ readManifest as readManifest7,
7025
+ sanitizePath as sanitizePath2
7026
+ } from "@basou/core";
7027
+ function registerReviewCommand(program2) {
7028
+ const review = program2.command("review").description("Record reviews that ran (the durable signal a review happened)");
7029
+ review.command("record").description(
7030
+ "Record that a review ran, from a JSON object (stdin or --file). The in-loop agent runs an adversarial / second-opinion review and pipes a description -- reviewer, target, optional verdict/findings/blocked -- and basou writes one review_recorded event deterministically."
7031
+ ).option("--file <path>", "Read the JSON object from a file instead of stdin").option("--dry-run", "Validate and preview the review without writing it").option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").addHelpText("after", REVIEW_RECORD_HELP).action(async (options) => {
7032
+ await runReviewRecord(options);
7033
+ });
7034
+ }
7035
+ var REVIEW_RECORD_HELP = `
7036
+ Input format (a single JSON object describing one review):
7037
+ {
7038
+ "reviewer": "codex",
7039
+ "target": "working-tree",
7040
+ "verdict": "needs-attention",
7041
+ "findings": [
7042
+ { "title": "Off-by-one in pager", "severity": "medium", "location": "src/page.ts:42", "summary": "..." }
7043
+ ],
7044
+ "blocked": [
7045
+ { "title": "Reviewer wanted to drop the singleton", "reason": "design-reversal", "why": "Settled in decision_X" }
7046
+ ]
7047
+ }
7048
+
7049
+ Only "reviewer" and "target" are required; verdict / findings / blocked are
7050
+ optional. Record blocked findings (spec-deviation / design-reversal) here so the
7051
+ adversarial-review protocol's "always report what you blocked" becomes a durable
7052
+ trail artifact -- an explicit empty "blocked": [] is encouraged to record that
7053
+ you blocked nothing. The review is written into one ad-hoc session timestamped
7054
+ now. Run from a workspace-view directory and it resolves to the planning repo,
7055
+ like 'basou decision capture' / 'basou note'.
7056
+
7057
+ Example (heredoc on stdin):
7058
+ basou review record <<'JSON'
7059
+ { "reviewer": "codex", "target": "working-tree", "verdict": "pass", "blocked": [] }
7060
+ JSON
7061
+ `;
7062
+ async function runReviewRecord(options, ctx = {}) {
7063
+ try {
7064
+ await doRunReviewRecord(options, ctx);
7065
+ } catch (error) {
7066
+ renderCliError(error, {
7067
+ verbose: isVerbose(options),
7068
+ classifiers: [failedToFinalizeClassifier]
7069
+ });
7070
+ process.exitCode = 1;
7071
+ }
7072
+ }
7073
+ async function doRunReviewRecord(options, ctx) {
7074
+ const cwd = ctx.cwd ?? process.cwd();
7075
+ const repositoryRoot = await resolveBasouRootForCommand(cwd, "review record");
7076
+ const paths = basouPaths13(repositoryRoot);
7077
+ await assertWorkspaceInitialized10(paths.root);
7078
+ const raw = await readReviewInput(options, ctx);
7079
+ const review = parseReviewRecordInput(raw);
7080
+ if (options.dryRun === true) {
7081
+ printReviewPreview(options, review);
7082
+ return;
7083
+ }
7084
+ const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
7085
+ const occurredAt = now.toISOString();
7086
+ const manifest = await readManifest7(paths);
7087
+ const invocationArgs = options.file !== void 0 ? [
7088
+ "--file",
7089
+ sanitizePath2(resolve10(cwd, options.file), {
7090
+ workingDirectory: repositoryRoot,
7091
+ homedir: homedir10()
7092
+ })
7093
+ ] : [];
7094
+ const adHoc = await createAdHocSessionWithEvent3({
7095
+ paths,
7096
+ manifest,
7097
+ label: buildReviewRecordLabel(review),
7098
+ occurredAt,
7099
+ sessionSource: "human",
7100
+ workingDirectory: repositoryRoot,
7101
+ invocation: { command: "basou review record", args: invocationArgs },
7102
+ targetEventBuilders: [
7103
+ (sessionId, eventId) => buildReviewRecordedEvent({ eventId, sessionId, occurredAt, review })
7104
+ ]
7105
+ });
7106
+ printReviewResult(options, {
7107
+ sessionId: adHoc.sessionId,
7108
+ eventId: adHoc.targetEventIds[0],
7109
+ review
7110
+ });
7111
+ }
7112
+ async function readReviewInput(options, ctx) {
7113
+ if (options.file !== void 0) {
7114
+ try {
7115
+ return await readFile5(options.file, "utf8");
7116
+ } catch (error) {
7117
+ if (findErrorCode11(error, "ENOENT")) {
7118
+ throw new Error(`Input file not found: ${options.file}`);
7119
+ }
7120
+ throw error;
7121
+ }
7122
+ }
7123
+ if (ctx.readInput !== void 0) {
7124
+ return await ctx.readInput();
7125
+ }
7126
+ if (process.stdin.isTTY === true) {
7127
+ throw new Error(NO_INPUT_HINT2);
7128
+ }
7129
+ return await readStdinToEnd2();
7130
+ }
7131
+ async function readStdinToEnd2() {
7132
+ const chunks = [];
7133
+ for await (const chunk of process.stdin) {
7134
+ chunks.push(Buffer.from(chunk));
7135
+ }
7136
+ return Buffer.concat(chunks).toString("utf8");
7137
+ }
7138
+ var NO_INPUT_HINT2 = "No input: pipe a JSON object describing the review to stdin or pass --file <path>.";
7139
+ function reviewToPayload(review) {
7140
+ const payload = {
7141
+ reviewer: review.reviewer,
7142
+ target: review.target
7143
+ };
7144
+ if (review.verdict !== void 0) payload.verdict = review.verdict;
7145
+ if (review.findings !== void 0) payload.findings = review.findings;
7146
+ if (review.blocked !== void 0) payload.blocked = review.blocked;
7147
+ return payload;
7148
+ }
7149
+ function reviewSummaryLine(review) {
7150
+ const parts = [];
7151
+ if (review.verdict !== void 0) parts.push(`verdict: ${review.verdict}`);
7152
+ if (review.findings !== void 0) {
7153
+ parts.push(`${review.findings.length} finding${review.findings.length === 1 ? "" : "s"}`);
7154
+ }
7155
+ if (review.blocked !== void 0) {
7156
+ parts.push(`${review.blocked.length} blocked`);
7157
+ }
7158
+ return parts.length > 0 ? ` (${parts.join(", ")})` : "";
7159
+ }
7160
+ function printReviewPreview(options, review) {
7161
+ if (options.json === true) {
7162
+ console.log(JSON.stringify({ dry_run: true, review: reviewToPayload(review) }));
7163
+ return;
7164
+ }
7165
+ console.log(
7166
+ `Would record review by ${review.reviewer} of ${review.target}${reviewSummaryLine(review)} (dry run; nothing written).`
7167
+ );
7168
+ }
7169
+ function printReviewResult(options, result) {
7170
+ const sid = shortSessionId(result.sessionId);
7171
+ if (options.json === true) {
7172
+ console.log(
7173
+ JSON.stringify({
7174
+ mode: "ad-hoc",
7175
+ session_id: result.sessionId,
7176
+ session_status: "completed",
7177
+ event_id: result.eventId,
7178
+ review: reviewToPayload(result.review)
7179
+ })
7180
+ );
7181
+ return;
7182
+ }
7183
+ console.log(
7184
+ `Recorded review by ${result.review.reviewer} of ${result.review.target}${reviewSummaryLine(result.review)} in ad-hoc session ${sid}.`
7185
+ );
7186
+ }
7187
+ async function assertWorkspaceInitialized10(basouRoot) {
7188
+ try {
7189
+ await assertBasouRootSafe11(basouRoot);
7190
+ } catch (error) {
7191
+ if (findErrorCode11(error, "ENOENT")) {
7192
+ throw new Error("Workspace not initialized. Run 'basou init' first.");
7193
+ }
7194
+ throw error;
7195
+ }
7196
+ }
7197
+
7198
+ // src/commands/review-gaps.ts
7199
+ import {
7200
+ basouPaths as basouPaths14,
6914
7201
  findReviewGaps
6915
7202
  } from "@basou/core";
6916
7203
  import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
@@ -6951,7 +7238,7 @@ async function runReviewGaps(options, ctx = {}) {
6951
7238
  async function doRunReviewGaps(options, ctx) {
6952
7239
  const cwd = ctx.cwd ?? process.cwd();
6953
7240
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "review-gaps");
6954
- const paths = basouPaths13(repositoryRoot);
7241
+ const paths = basouPaths14(repositoryRoot);
6955
7242
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
6956
7243
  const summary = await findReviewGaps({
6957
7244
  paths,
@@ -7034,23 +7321,25 @@ function renderReviewGaps(summary) {
7034
7321
 
7035
7322
  // src/commands/run.ts
7036
7323
  import { mkdir as mkdir2 } from "fs/promises";
7037
- import { homedir as homedir9 } from "os";
7038
- import { join as join12 } from "path";
7324
+ import { homedir as homedir11 } from "os";
7325
+ import { join as join13 } from "path";
7039
7326
  import {
7040
7327
  acquireLock as acquireLock5,
7041
- assertBasouRootSafe as assertBasouRootSafe11,
7042
- basouPaths as basouPaths14,
7328
+ assertBasouRootSafe as assertBasouRootSafe12,
7329
+ basouPaths as basouPaths15,
7043
7330
  ChildProcessRunner as ChildProcessRunner2,
7044
7331
  claudeCodeAdapterMetadata,
7332
+ codexAdapterMetadata,
7045
7333
  appendChainedEvent as coreAppendChainedEvent2,
7046
7334
  finalizeSessionYaml as finalizeSessionYaml2,
7047
7335
  getDiff,
7048
7336
  getSnapshot as getSnapshot2,
7049
7337
  overwriteYamlFile as overwriteYamlFile2,
7050
7338
  prefixedUlid as prefixedUlid4,
7051
- readManifest as readManifest7,
7339
+ readManifest as readManifest8,
7052
7340
  readYamlFile as readYamlFile6,
7053
7341
  resolveClaudeCodeCommand,
7342
+ resolveCodexCommand,
7054
7343
  resolveRepositoryRoot as resolveRepositoryRoot10,
7055
7344
  SessionSchema as SessionSchema2,
7056
7345
  sanitizeRelatedFiles,
@@ -7058,50 +7347,72 @@ import {
7058
7347
  writeYamlFile as writeYamlFile2
7059
7348
  } from "@basou/core";
7060
7349
  function registerRunCommand(program2, ctx = {}) {
7061
- const runCommand = program2.command("run").description("Run an AI coding tool through Basou as a tracked session").enablePositionalOptions().option("--no-snapshot", "Skip git_snapshot before/after the session").option("--cwd <path>", "Run from a Basou root other than process.cwd()").option("-v, --verbose", "Show error causes");
7062
- runCommand.command("claude-code [args...]").description("Run Claude Code CLI as a Basou-tracked session").option("--no-snapshot", "Skip git_snapshot before/after the session").option("--cwd <path>", "Run from a Basou root other than process.cwd()").option("-v, --verbose", "Show error causes").passThroughOptions().action(async (args, options, command) => {
7350
+ const runCommand = addRunOptions(
7351
+ program2.command("run").description("Run an AI coding tool through Basou as a tracked session").enablePositionalOptions()
7352
+ );
7353
+ const dispatch = async (args, options, command, run) => {
7063
7354
  const parentOptions = command.parent?.opts() ?? {};
7064
7355
  const snapshotOn = parentOptions.snapshot !== false && options.snapshot !== false;
7065
- const merged = {
7066
- ...parentOptions,
7067
- ...options,
7068
- snapshot: snapshotOn
7069
- };
7356
+ const merged = { ...parentOptions, ...options, snapshot: snapshotOn };
7070
7357
  try {
7071
- const exitCode = await runClaudeCode(args, merged, ctx);
7358
+ const exitCode = await run(args, merged, ctx);
7072
7359
  process.exit(exitCode);
7073
7360
  } catch (error) {
7074
7361
  renderCliError(error, { verbose: isVerbose(merged) });
7075
7362
  process.exit(1);
7076
7363
  }
7364
+ };
7365
+ addRunOptions(runCommand.command("claude-code [args...]")).description("Run Claude Code CLI as a Basou-tracked session").passThroughOptions().action(
7366
+ (args, options, command) => dispatch(args, options, command, runClaudeCode)
7367
+ );
7368
+ addRunOptions(runCommand.command("codex [args...]")).description("Run the Codex CLI as a Basou-tracked session").passThroughOptions().action(
7369
+ (args, options, command) => dispatch(args, options, command, runCodex)
7370
+ );
7371
+ }
7372
+ function addRunOptions(command) {
7373
+ return command.option("--no-snapshot", "Skip git_snapshot before/after the session").option("--cwd <path>", "Run from a Basou root other than process.cwd()").option("-v, --verbose", "Show error causes");
7374
+ }
7375
+ function runClaudeCode(args, options, ctx = {}) {
7376
+ return runTrackedTool(args, options, ctx, {
7377
+ resolveCommand: ctx.resolveCommand ?? resolveClaudeCodeCommand,
7378
+ metadata: claudeCodeAdapterMetadata
7379
+ });
7380
+ }
7381
+ function runCodex(args, options, ctx = {}) {
7382
+ return runTrackedTool(args, options, ctx, {
7383
+ resolveCommand: ctx.resolveCodexCommand ?? resolveCodexCommand,
7384
+ metadata: codexAdapterMetadata,
7385
+ transformArgs: (a) => ["-c", "shell_environment_policy.inherit=all", ...a],
7386
+ preSpawn: syncCodexOrientationChannelPreSpawn
7077
7387
  });
7078
7388
  }
7079
- async function runClaudeCode(args, options, ctx = {}) {
7389
+ async function runTrackedTool(args, options, ctx, adapter) {
7080
7390
  const runner = ctx.runner ?? new ChildProcessRunner2();
7081
7391
  const now = ctx.now ?? (() => /* @__PURE__ */ new Date());
7082
- const resolveCommand = ctx.resolveCommand ?? resolveClaudeCodeCommand;
7083
7392
  const getDiffFn = ctx.getDiff ?? getDiff;
7084
- const { command } = await resolveCommand();
7393
+ const { command } = await adapter.resolveCommand();
7394
+ const childArgs = adapter.transformArgs ? adapter.transformArgs(args) : args;
7085
7395
  const cwd = options.cwd ?? process.cwd();
7086
7396
  const repoRoot = await resolveRepositoryRootForRun(cwd);
7087
- const paths = basouPaths14(repoRoot);
7088
- await assertBasouRootSafe11(paths.root);
7089
- const manifest = await readManifest7(paths);
7397
+ const paths = basouPaths15(repoRoot);
7398
+ await assertBasouRootSafe12(paths.root);
7399
+ const manifest = await readManifest8(paths);
7090
7400
  const sessionId = prefixedUlid4("ses");
7091
- const sessionDir = join12(paths.sessions, sessionId);
7401
+ const sessionDir = join13(paths.sessions, sessionId);
7092
7402
  await mkdir2(sessionDir, { recursive: true });
7093
7403
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
7094
7404
  await coreAppendChainedEvent2(paths, sessionId, event);
7095
7405
  });
7096
7406
  const startedAt = now().toISOString();
7097
- const sessionYamlPath = join12(sessionDir, "session.yaml");
7407
+ const sessionYamlPath = join13(sessionDir, "session.yaml");
7098
7408
  const session = buildInitialSession2({
7099
7409
  id: sessionId,
7100
7410
  command,
7101
- args,
7411
+ args: childArgs,
7102
7412
  cwd: repoRoot,
7103
7413
  workspaceId: manifest.workspace.id,
7104
- startedAt
7414
+ startedAt,
7415
+ source: adapter.metadata
7105
7416
  });
7106
7417
  await writeYamlFile2(sessionYamlPath, session);
7107
7418
  await appendEvent(sessionDir, {
@@ -7110,7 +7421,7 @@ async function runClaudeCode(args, options, ctx = {}) {
7110
7421
  id: prefixedUlid4("evt"),
7111
7422
  session_id: sessionId,
7112
7423
  occurred_at: startedAt,
7113
- source: claudeCodeAdapterMetadata.kind
7424
+ source: adapter.metadata.kind
7114
7425
  });
7115
7426
  let preSnapshot = null;
7116
7427
  if (options.snapshot !== false) {
@@ -7123,7 +7434,7 @@ async function runClaudeCode(args, options, ctx = {}) {
7123
7434
  id: prefixedUlid4("evt"),
7124
7435
  session_id: sessionId,
7125
7436
  occurred_at: runningAt,
7126
- source: claudeCodeAdapterMetadata.kind,
7437
+ source: adapter.metadata.kind,
7127
7438
  from: "initialized",
7128
7439
  to: "running"
7129
7440
  });
@@ -7157,10 +7468,14 @@ async function runClaudeCode(args, options, ctx = {}) {
7157
7468
  process.on("SIGTERM", onSigTerm);
7158
7469
  process.on("exit", exitHandler);
7159
7470
  ctx.onExitHookInstalled?.(exitHandler);
7471
+ if (adapter.preSpawn !== void 0) {
7472
+ const line = await adapter.preSpawn(cwd, ctx);
7473
+ if (line !== null) console.log(line);
7474
+ }
7160
7475
  let result;
7161
7476
  try {
7162
7477
  try {
7163
- result = await runner.run(command, args, {
7478
+ result = await runner.run(command, childArgs, {
7164
7479
  cwd: repoRoot,
7165
7480
  capture: "none",
7166
7481
  signal: controller.signal,
@@ -7171,10 +7486,11 @@ async function runClaudeCode(args, options, ctx = {}) {
7171
7486
  } catch (spawnError) {
7172
7487
  await finalizeSessionAsFailed2(paths, sessionDir, sessionId, appendEvent, {
7173
7488
  command,
7174
- args,
7489
+ args: childArgs,
7175
7490
  cwd: repoRoot,
7176
7491
  occurredAt: now().toISOString(),
7177
- signalReceived
7492
+ signalReceived,
7493
+ sourceKind: adapter.metadata.kind
7178
7494
  });
7179
7495
  throw spawnError;
7180
7496
  }
@@ -7193,7 +7509,7 @@ async function runClaudeCode(args, options, ctx = {}) {
7193
7509
  occurred_at: endedAt,
7194
7510
  source: "terminal-recording",
7195
7511
  command,
7196
- args,
7512
+ args: childArgs,
7197
7513
  cwd: repoRoot,
7198
7514
  exit_code: result.exit_code,
7199
7515
  ...result.signal !== null ? { signal: result.signal } : {},
@@ -7220,7 +7536,7 @@ async function runClaudeCode(args, options, ctx = {}) {
7220
7536
  const rawRelated = computeRelatedFiles(preSnapshot, postSnapshot, diff);
7221
7537
  const relatedFiles = sanitizeRelatedFiles(rawRelated, {
7222
7538
  workingDirectory: repoRoot,
7223
- homedir: homedir9()
7539
+ homedir: homedir11()
7224
7540
  }).sanitized;
7225
7541
  const finalStatus = decideFinalStatus2(result, signalReceived);
7226
7542
  await appendEvent(sessionDir, {
@@ -7229,7 +7545,7 @@ async function runClaudeCode(args, options, ctx = {}) {
7229
7545
  id: prefixedUlid4("evt"),
7230
7546
  session_id: sessionId,
7231
7547
  occurred_at: endedAt,
7232
- source: claudeCodeAdapterMetadata.kind,
7548
+ source: adapter.metadata.kind,
7233
7549
  from: "running",
7234
7550
  to: finalStatus
7235
7551
  });
@@ -7239,7 +7555,7 @@ async function runClaudeCode(args, options, ctx = {}) {
7239
7555
  id: prefixedUlid4("evt"),
7240
7556
  session_id: sessionId,
7241
7557
  occurred_at: endedAt,
7242
- source: claudeCodeAdapterMetadata.kind,
7558
+ source: adapter.metadata.kind,
7243
7559
  ...result.exit_code !== null ? { exit_code: result.exit_code } : {}
7244
7560
  });
7245
7561
  await finalizeSessionYaml2(paths, sessionId, (s) => {
@@ -7361,10 +7677,10 @@ function buildInitialSession2(input) {
7361
7677
  label: `basou run ${cmdline} (${input.startedAt})`,
7362
7678
  task_id: null,
7363
7679
  workspace_id: input.workspaceId,
7364
- source: { ...claudeCodeAdapterMetadata },
7680
+ source: { ...input.source },
7365
7681
  started_at: input.startedAt,
7366
7682
  status: "initialized",
7367
- working_directory: sanitizeWorkingDirectory2(input.cwd, { homedir: homedir9() }),
7683
+ working_directory: sanitizeWorkingDirectory2(input.cwd, { homedir: homedir11() }),
7368
7684
  invocation: {
7369
7685
  command: input.command,
7370
7686
  args: [...input.args],
@@ -7404,7 +7720,7 @@ async function finalizeSessionAsFailed2(paths, sessionDir, sessionId, appendEven
7404
7720
  id: prefixedUlid4("evt"),
7405
7721
  session_id: sessionId,
7406
7722
  occurred_at: ctx.occurredAt,
7407
- source: claudeCodeAdapterMetadata.kind,
7723
+ source: ctx.sourceKind,
7408
7724
  from: "running",
7409
7725
  to: "failed"
7410
7726
  });
@@ -7414,7 +7730,7 @@ async function finalizeSessionAsFailed2(paths, sessionDir, sessionId, appendEven
7414
7730
  id: prefixedUlid4("evt"),
7415
7731
  session_id: sessionId,
7416
7732
  occurred_at: ctx.occurredAt,
7417
- source: claudeCodeAdapterMetadata.kind
7733
+ source: ctx.sourceKind
7418
7734
  });
7419
7735
  await finalizeSessionYaml2(paths, sessionId, (s) => {
7420
7736
  s.session.status = "failed";
@@ -7434,21 +7750,34 @@ async function resolveRepositoryRootForRun(cwd) {
7434
7750
  throw error;
7435
7751
  }
7436
7752
  }
7753
+ async function syncCodexOrientationChannelPreSpawn(cwd, ctx) {
7754
+ try {
7755
+ const root = await resolveBasouRootForCommand(cwd, "run");
7756
+ const paths = basouPaths15(root);
7757
+ const rendered = await renderOrientationToCodexChannel({
7758
+ orientationPath: paths.files.orientation,
7759
+ ...ctx.codexChannelPath !== void 0 ? { channelPath: ctx.codexChannelPath } : {}
7760
+ });
7761
+ return rendered === null ? null : rendered.line;
7762
+ } catch {
7763
+ return null;
7764
+ }
7765
+ }
7437
7766
 
7438
7767
  // src/commands/session.ts
7439
- import { readFile as readFile5 } from "fs/promises";
7440
- import { basename as basename6, isAbsolute as isAbsolute6, join as join13, relative as relative3 } from "path";
7768
+ import { readFile as readFile6 } from "fs/promises";
7769
+ import { basename as basename6, isAbsolute as isAbsolute6, join as join14, relative as relative3 } from "path";
7441
7770
  import {
7442
7771
  acquireLock as acquireLock6,
7443
7772
  appendEventToExistingSession as appendEventToExistingSession3,
7444
- assertBasouRootSafe as assertBasouRootSafe12,
7445
- basouPaths as basouPaths15,
7773
+ assertBasouRootSafe as assertBasouRootSafe13,
7774
+ basouPaths as basouPaths16,
7446
7775
  enumerateSessionDirs as enumerateSessionDirs2,
7447
- findErrorCode as findErrorCode11,
7776
+ findErrorCode as findErrorCode12,
7448
7777
  importSessionFromJson as importSessionFromJson2,
7449
7778
  loadSessionEntries as loadSessionEntries2,
7450
7779
  readAllEvents,
7451
- readManifest as readManifest8,
7780
+ readManifest as readManifest9,
7452
7781
  readYamlFile as readYamlFile7,
7453
7782
  rechainSessionInPlace,
7454
7783
  resolveSessionId as resolveSessionId3,
@@ -7507,8 +7836,8 @@ async function runSessionList(options, ctx = {}) {
7507
7836
  async function doRunSessionList(options, ctx) {
7508
7837
  const cwd = ctx.cwd ?? process.cwd();
7509
7838
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "list");
7510
- const paths = basouPaths15(repositoryRoot);
7511
- await assertWorkspaceInitialized10(paths.root);
7839
+ const paths = basouPaths16(repositoryRoot);
7840
+ await assertWorkspaceInitialized11(paths.root);
7512
7841
  const now = /* @__PURE__ */ new Date();
7513
7842
  const records = (await loadSessionEntries2(paths, {
7514
7843
  now,
@@ -7559,17 +7888,17 @@ async function runSessionShow(idInput, options, ctx = {}) {
7559
7888
  async function doRunSessionShow(idInput, options, ctx) {
7560
7889
  const cwd = ctx.cwd ?? process.cwd();
7561
7890
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "show");
7562
- const paths = basouPaths15(repositoryRoot);
7563
- await assertWorkspaceInitialized10(paths.root);
7891
+ const paths = basouPaths16(repositoryRoot);
7892
+ await assertWorkspaceInitialized11(paths.root);
7564
7893
  const sessionId = await resolveSessionId3(paths, idInput);
7565
- const sessionDir = join13(paths.sessions, sessionId);
7566
- const sessionYamlPath = join13(sessionDir, "session.yaml");
7894
+ const sessionDir = join14(paths.sessions, sessionId);
7895
+ const sessionYamlPath = join14(sessionDir, "session.yaml");
7567
7896
  let session;
7568
7897
  try {
7569
7898
  const raw = await readYamlFile7(sessionYamlPath);
7570
7899
  session = SessionSchema3.parse(raw);
7571
7900
  } catch (error) {
7572
- if (findErrorCode11(error, "ENOENT")) {
7901
+ if (findErrorCode12(error, "ENOENT")) {
7573
7902
  throw new Error(`Session not found: ${idInput}`);
7574
7903
  }
7575
7904
  throw new Error("Failed to read session", { cause: error });
@@ -7763,6 +8092,11 @@ function eventVariantSummary(ev) {
7763
8092
  return `task ${shortTaskId2(ev.task_id)}: ${ev.title} (archived)`;
7764
8093
  case "note_added":
7765
8094
  return ev.body.length > 80 ? `${ev.body.slice(0, 77)}...` : ev.body;
8095
+ case "review_recorded": {
8096
+ const verdict = ev.verdict !== void 0 ? ` (${ev.verdict})` : "";
8097
+ const blocked = ev.blocked !== void 0 && ev.blocked.length > 0 ? ` ${ev.blocked.length} blocked` : "";
8098
+ return `${ev.reviewer} -> ${ev.target}${verdict}${blocked}`;
8099
+ }
7766
8100
  case "adapter_output":
7767
8101
  return `${ev.stream} "${ev.summary}" raw_ref=${ev.raw_ref}`;
7768
8102
  }
@@ -7810,11 +8144,11 @@ function maxLen2(values, floor) {
7810
8144
  async function resolveRepositoryRootForSession(cwd, subcmd) {
7811
8145
  return resolveBasouRootForCommand(cwd, `session ${subcmd}`);
7812
8146
  }
7813
- async function assertWorkspaceInitialized10(basouRoot) {
8147
+ async function assertWorkspaceInitialized11(basouRoot) {
7814
8148
  try {
7815
- await assertBasouRootSafe12(basouRoot);
8149
+ await assertBasouRootSafe13(basouRoot);
7816
8150
  } catch (error) {
7817
- if (findErrorCode11(error, "ENOENT")) {
8151
+ if (findErrorCode12(error, "ENOENT")) {
7818
8152
  throw new Error("Workspace not initialized. Run 'basou init' first.");
7819
8153
  }
7820
8154
  throw error;
@@ -7852,9 +8186,9 @@ async function runSessionImport(options, ctx = {}) {
7852
8186
  async function doRunSessionImport(options, ctx) {
7853
8187
  const cwd = ctx.cwd ?? process.cwd();
7854
8188
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "import");
7855
- const paths = basouPaths15(repositoryRoot);
7856
- await assertWorkspaceInitialized10(paths.root);
7857
- const manifest = await readManifest8(paths);
8189
+ const paths = basouPaths16(repositoryRoot);
8190
+ await assertWorkspaceInitialized11(paths.root);
8191
+ const manifest = await readManifest9(paths);
7858
8192
  const rawBody = await readInputFile(options.from);
7859
8193
  const json = parseJsonStrict(rawBody);
7860
8194
  const parsed = SessionImportPayloadSchema2.safeParse(json);
@@ -7881,12 +8215,12 @@ async function doRunSessionImport(options, ctx) {
7881
8215
  }
7882
8216
  async function readInputFile(path) {
7883
8217
  try {
7884
- return await readFile5(path, "utf8");
8218
+ return await readFile6(path, "utf8");
7885
8219
  } catch (error) {
7886
- if (findErrorCode11(error, "ENOENT")) {
8220
+ if (findErrorCode12(error, "ENOENT")) {
7887
8221
  throw new Error("Import source not found", { cause: error });
7888
8222
  }
7889
- if (findErrorCode11(error, "EISDIR")) {
8223
+ if (findErrorCode12(error, "EISDIR")) {
7890
8224
  throw new Error("Import source is not a file", { cause: error });
7891
8225
  }
7892
8226
  throw new Error("Failed to read import source", { cause: error });
@@ -7966,8 +8300,8 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
7966
8300
  }
7967
8301
  const cwd = ctx.cwd ?? process.cwd();
7968
8302
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "note");
7969
- const paths = basouPaths15(repositoryRoot);
7970
- await assertWorkspaceInitialized10(paths.root);
8303
+ const paths = basouPaths16(repositoryRoot);
8304
+ await assertWorkspaceInitialized11(paths.root);
7971
8305
  const sessionId = await resolveSessionId3(paths, sessionIdInput);
7972
8306
  const body = hasBody ? options.body : await readNoteFile(options.fromFile);
7973
8307
  if (body.length === 0) {
@@ -7998,12 +8332,12 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
7998
8332
  }
7999
8333
  async function readNoteFile(path) {
8000
8334
  try {
8001
- return await readFile5(path, "utf8");
8335
+ return await readFile6(path, "utf8");
8002
8336
  } catch (error) {
8003
- if (findErrorCode11(error, "ENOENT")) {
8337
+ if (findErrorCode12(error, "ENOENT")) {
8004
8338
  throw new Error("Note source not found", { cause: error });
8005
8339
  }
8006
- if (findErrorCode11(error, "EISDIR")) {
8340
+ if (findErrorCode12(error, "EISDIR")) {
8007
8341
  throw new Error("Note source is not a file", { cause: error });
8008
8342
  }
8009
8343
  throw new Error("Failed to read note source", { cause: error });
@@ -8048,8 +8382,8 @@ async function doRunSessionRechain(options, ctx) {
8048
8382
  }
8049
8383
  const cwd = ctx.cwd ?? process.cwd();
8050
8384
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "rechain");
8051
- const paths = basouPaths15(repositoryRoot);
8052
- await assertWorkspaceInitialized10(paths.root);
8385
+ const paths = basouPaths16(repositoryRoot);
8386
+ await assertWorkspaceInitialized11(paths.root);
8053
8387
  const sessionIds = options.session !== void 0 ? [await resolveSessionId3(paths, options.session)] : await enumerateSessionDirs2(paths);
8054
8388
  const dryRun = options.dryRun === true;
8055
8389
  const rows = [];
@@ -8102,10 +8436,10 @@ function renderRechainRow(row, dryRun) {
8102
8436
 
8103
8437
  // src/commands/stats.ts
8104
8438
  import {
8105
- assertBasouRootSafe as assertBasouRootSafe13,
8106
- basouPaths as basouPaths16,
8439
+ assertBasouRootSafe as assertBasouRootSafe14,
8440
+ basouPaths as basouPaths17,
8107
8441
  computeWorkStats,
8108
- findErrorCode as findErrorCode12,
8442
+ findErrorCode as findErrorCode13,
8109
8443
  resolveRepositoryRoot as resolveRepositoryRoot11
8110
8444
  } from "@basou/core";
8111
8445
  function registerStatsCommand(program2) {
@@ -8124,8 +8458,8 @@ async function runStats(options, ctx = {}) {
8124
8458
  async function doRunStats(options, ctx) {
8125
8459
  const cwd = ctx.cwd ?? process.cwd();
8126
8460
  const repositoryRoot = await resolveRepositoryRootForStats(cwd);
8127
- const paths = basouPaths16(repositoryRoot);
8128
- await assertWorkspaceInitialized11(paths.root);
8461
+ const paths = basouPaths17(repositoryRoot);
8462
+ await assertWorkspaceInitialized12(paths.root);
8129
8463
  const now = ctx.nowProvider?.() ?? /* @__PURE__ */ new Date();
8130
8464
  const result = await computeWorkStats({
8131
8465
  paths,
@@ -8219,11 +8553,11 @@ async function resolveRepositoryRootForStats(cwd) {
8219
8553
  throw error;
8220
8554
  }
8221
8555
  }
8222
- async function assertWorkspaceInitialized11(basouRoot) {
8556
+ async function assertWorkspaceInitialized12(basouRoot) {
8223
8557
  try {
8224
- await assertBasouRootSafe13(basouRoot);
8558
+ await assertBasouRootSafe14(basouRoot);
8225
8559
  } catch (error) {
8226
- if (findErrorCode12(error, "ENOENT")) {
8560
+ if (findErrorCode13(error, "ENOENT")) {
8227
8561
  throw new Error("Workspace not initialized. Run 'basou init' first.");
8228
8562
  }
8229
8563
  throw error;
@@ -8232,11 +8566,11 @@ async function assertWorkspaceInitialized11(basouRoot) {
8232
8566
 
8233
8567
  // src/commands/status.ts
8234
8568
  import {
8235
- assertBasouRootSafe as assertBasouRootSafe14,
8236
- basouPaths as basouPaths17,
8569
+ assertBasouRootSafe as assertBasouRootSafe15,
8570
+ basouPaths as basouPaths18,
8237
8571
  buildStatusSnapshot,
8238
- findErrorCode as findErrorCode13,
8239
- readManifest as readManifest9,
8572
+ findErrorCode as findErrorCode14,
8573
+ readManifest as readManifest10,
8240
8574
  resolveRepositoryRoot as resolveRepositoryRoot12,
8241
8575
  writeStatus
8242
8576
  } from "@basou/core";
@@ -8256,20 +8590,20 @@ async function runStatus(options, ctx = {}) {
8256
8590
  async function doRunStatus(options, ctx) {
8257
8591
  const cwd = ctx.cwd ?? process.cwd();
8258
8592
  const repositoryRoot = await resolveRepositoryRootForStatus(cwd);
8259
- const paths = basouPaths17(repositoryRoot);
8593
+ const paths = basouPaths18(repositoryRoot);
8260
8594
  try {
8261
- await assertBasouRootSafe14(paths.root);
8595
+ await assertBasouRootSafe15(paths.root);
8262
8596
  } catch (error) {
8263
- if (findErrorCode13(error, "ENOENT")) {
8597
+ if (findErrorCode14(error, "ENOENT")) {
8264
8598
  throw new Error("Workspace not initialized. Run 'basou init' first.");
8265
8599
  }
8266
8600
  throw error;
8267
8601
  }
8268
8602
  let manifest;
8269
8603
  try {
8270
- manifest = await readManifest9(paths);
8604
+ manifest = await readManifest10(paths);
8271
8605
  } catch (error) {
8272
- if (findErrorCode13(error, "ENOENT")) {
8606
+ if (findErrorCode14(error, "ENOENT")) {
8273
8607
  throw new Error("Workspace not initialized. Run 'basou init' first.");
8274
8608
  }
8275
8609
  throw new Error("Failed to read workspace manifest", { cause: error });
@@ -8305,21 +8639,21 @@ async function resolveRepositoryRootForStatus(cwd) {
8305
8639
  }
8306
8640
 
8307
8641
  // src/commands/task.ts
8308
- import { readFile as readFile6 } from "fs/promises";
8309
- import { join as join14 } from "path";
8642
+ import { readFile as readFile7 } from "fs/promises";
8643
+ import { join as join15 } from "path";
8310
8644
  import {
8311
8645
  archiveTask,
8312
- assertBasouRootSafe as assertBasouRootSafe15,
8313
- basouPaths as basouPaths18,
8646
+ assertBasouRootSafe as assertBasouRootSafe16,
8647
+ basouPaths as basouPaths19,
8314
8648
  createTaskWithEvent,
8315
8649
  deleteTask,
8316
8650
  editTask,
8317
8651
  enumerateArchivedTaskIds,
8318
- findErrorCode as findErrorCode14,
8652
+ findErrorCode as findErrorCode15,
8319
8653
  loadSessionEntries as loadSessionEntries3,
8320
8654
  loadTaskEntries,
8321
8655
  prefixedUlid as prefixedUlid5,
8322
- readManifest as readManifest10,
8656
+ readManifest as readManifest11,
8323
8657
  readTaskFile,
8324
8658
  readTaskFileWithArchiveFallback,
8325
8659
  reconcileAllTasks,
@@ -8411,8 +8745,8 @@ async function doRunTaskNew(options, ctx) {
8411
8745
  }
8412
8746
  const cwd = ctx.cwd ?? process.cwd();
8413
8747
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "new");
8414
- const paths = basouPaths18(repositoryRoot);
8415
- await assertWorkspaceInitialized12(paths.root);
8748
+ const paths = basouPaths19(repositoryRoot);
8749
+ await assertWorkspaceInitialized13(paths.root);
8416
8750
  const description = options.description !== void 0 ? options.description : options.fromFile !== void 0 ? await readDescriptionFile(options.fromFile) : "";
8417
8751
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
8418
8752
  const occurredAt = now.toISOString();
@@ -8446,7 +8780,7 @@ async function doRunTaskNew(options, ctx) {
8446
8780
  });
8447
8781
  return;
8448
8782
  }
8449
- const manifest = await readManifest10(paths);
8783
+ const manifest = await readManifest11(paths);
8450
8784
  const result = await createTaskWithEvent({
8451
8785
  mode: "ad-hoc",
8452
8786
  paths,
@@ -8520,8 +8854,8 @@ async function runTaskList(options, ctx = {}) {
8520
8854
  async function doRunTaskList(options, ctx) {
8521
8855
  const cwd = ctx.cwd ?? process.cwd();
8522
8856
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "list");
8523
- const paths = basouPaths18(repositoryRoot);
8524
- await assertWorkspaceInitialized12(paths.root);
8857
+ const paths = basouPaths19(repositoryRoot);
8858
+ await assertWorkspaceInitialized13(paths.root);
8525
8859
  const entries = await loadTaskEntries(paths, {
8526
8860
  onSkip: (id, reason) => printTaskSkip(id, reason)
8527
8861
  });
@@ -8624,15 +8958,15 @@ async function runTaskShow(idInput, options, ctx = {}) {
8624
8958
  async function doRunTaskShow(idInput, options, ctx) {
8625
8959
  const cwd = ctx.cwd ?? process.cwd();
8626
8960
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "show");
8627
- const paths = basouPaths18(repositoryRoot);
8628
- await assertWorkspaceInitialized12(paths.root);
8961
+ const paths = basouPaths19(repositoryRoot);
8962
+ await assertWorkspaceInitialized13(paths.root);
8629
8963
  const taskId = await resolveTaskId2(paths, idInput, { includeArchived: true });
8630
8964
  const { doc, archived } = await readTaskFileWithArchiveFallback(paths, taskId);
8631
8965
  const sessions = await loadSessionEntries3(paths, { now: /* @__PURE__ */ new Date() });
8632
8966
  const events = [];
8633
8967
  const linkedSessionIds = new Set(doc.task.task.linked_sessions);
8634
8968
  for (const s of sessions) {
8635
- const sessionDir = join14(paths.sessions, s.sessionId);
8969
+ const sessionDir = join15(paths.sessions, s.sessionId);
8636
8970
  try {
8637
8971
  for await (const ev of replayEvents3(sessionDir, {
8638
8972
  onWarning: (w) => printReplayWarning(w, s.sessionId)
@@ -8768,8 +9102,8 @@ async function doRunTaskStatus(taskIdInput, newStatusInput, options, ctx) {
8768
9102
  const newStatus = parseTaskStatusPositional(newStatusInput);
8769
9103
  const cwd = ctx.cwd ?? process.cwd();
8770
9104
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "status");
8771
- const paths = basouPaths18(repositoryRoot);
8772
- await assertWorkspaceInitialized12(paths.root);
9105
+ const paths = basouPaths19(repositoryRoot);
9106
+ await assertWorkspaceInitialized13(paths.root);
8773
9107
  const taskId = await resolveTaskId2(paths, taskIdInput);
8774
9108
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
8775
9109
  const occurredAt = now.toISOString();
@@ -8794,7 +9128,7 @@ async function doRunTaskStatus(taskIdInput, newStatusInput, options, ctx) {
8794
9128
  });
8795
9129
  return;
8796
9130
  }
8797
- const manifest = await readManifest10(paths);
9131
+ const manifest = await readManifest11(paths);
8798
9132
  const result = await updateTaskStatusWithEvent({
8799
9133
  mode: "ad-hoc",
8800
9134
  paths,
@@ -8845,9 +9179,9 @@ async function runTaskReconcile(options, ctx = {}) {
8845
9179
  async function doRunTaskReconcile(options, ctx) {
8846
9180
  const cwd = ctx.cwd ?? process.cwd();
8847
9181
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "reconcile");
8848
- const paths = basouPaths18(repositoryRoot);
8849
- await assertWorkspaceInitialized12(paths.root);
8850
- const manifest = await readManifest10(paths);
9182
+ const paths = basouPaths19(repositoryRoot);
9183
+ await assertWorkspaceInitialized13(paths.root);
9184
+ const manifest = await readManifest11(paths);
8851
9185
  const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
8852
9186
  const write = options.write === true;
8853
9187
  const verbose = isVerbose(options);
@@ -9025,9 +9359,9 @@ async function doRunTaskRefreshLinkage(taskIdInput, options, ctx) {
9025
9359
  }
9026
9360
  const cwd = ctx.cwd ?? process.cwd();
9027
9361
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "refresh-linkage");
9028
- const paths = basouPaths18(repositoryRoot);
9029
- await assertWorkspaceInitialized12(paths.root);
9030
- const manifest = await readManifest10(paths);
9362
+ const paths = basouPaths19(repositoryRoot);
9363
+ await assertWorkspaceInitialized13(paths.root);
9364
+ const manifest = await readManifest11(paths);
9031
9365
  const taskId = await resolveTaskId2(paths, taskIdInput);
9032
9366
  const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
9033
9367
  const write = options.write === true;
@@ -9105,9 +9439,9 @@ async function doRunTaskEdit(taskIdInput, options, ctx) {
9105
9439
  }
9106
9440
  const cwd = ctx.cwd ?? process.cwd();
9107
9441
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "edit");
9108
- const paths = basouPaths18(repositoryRoot);
9109
- await assertWorkspaceInitialized12(paths.root);
9110
- const manifest = await readManifest10(paths);
9442
+ const paths = basouPaths19(repositoryRoot);
9443
+ await assertWorkspaceInitialized13(paths.root);
9444
+ const manifest = await readManifest11(paths);
9111
9445
  const taskId = await resolveTaskId2(paths, taskIdInput);
9112
9446
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
9113
9447
  const occurredAt = now.toISOString();
@@ -9161,9 +9495,9 @@ async function doRunTaskDelete(taskIdInput, options, ctx) {
9161
9495
  }
9162
9496
  const cwd = ctx.cwd ?? process.cwd();
9163
9497
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "delete");
9164
- const paths = basouPaths18(repositoryRoot);
9165
- await assertWorkspaceInitialized12(paths.root);
9166
- const manifest = await readManifest10(paths);
9498
+ const paths = basouPaths19(repositoryRoot);
9499
+ await assertWorkspaceInitialized13(paths.root);
9500
+ const manifest = await readManifest11(paths);
9167
9501
  const taskId = await resolveTaskId2(paths, taskIdInput);
9168
9502
  if (options.yes !== true) {
9169
9503
  await confirmDestructiveAction("delete", taskId);
@@ -9206,9 +9540,9 @@ async function doRunTaskArchive(taskIdInput, options, ctx) {
9206
9540
  }
9207
9541
  const cwd = ctx.cwd ?? process.cwd();
9208
9542
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "archive");
9209
- const paths = basouPaths18(repositoryRoot);
9210
- await assertWorkspaceInitialized12(paths.root);
9211
- const manifest = await readManifest10(paths);
9543
+ const paths = basouPaths19(repositoryRoot);
9544
+ await assertWorkspaceInitialized13(paths.root);
9545
+ const manifest = await readManifest11(paths);
9212
9546
  const taskId = await resolveTaskId2(paths, taskIdInput);
9213
9547
  if (options.yes !== true) {
9214
9548
  await confirmDestructiveAction("archive", taskId);
@@ -9320,12 +9654,12 @@ function parsePositiveInt2(raw) {
9320
9654
  }
9321
9655
  async function readDescriptionFile(path) {
9322
9656
  try {
9323
- return await readFile6(path, "utf8");
9657
+ return await readFile7(path, "utf8");
9324
9658
  } catch (error) {
9325
- if (findErrorCode14(error, "ENOENT")) {
9659
+ if (findErrorCode15(error, "ENOENT")) {
9326
9660
  throw new Error("Description source not found", { cause: error });
9327
9661
  }
9328
- if (findErrorCode14(error, "EISDIR")) {
9662
+ if (findErrorCode15(error, "EISDIR")) {
9329
9663
  throw new Error("Description source is not a file", { cause: error });
9330
9664
  }
9331
9665
  throw new Error("Failed to read description source", { cause: error });
@@ -9334,11 +9668,11 @@ async function readDescriptionFile(path) {
9334
9668
  async function resolveRepositoryRootForTask(cwd, subcmd) {
9335
9669
  return resolveBasouRootForCommand(cwd, `task ${subcmd}`);
9336
9670
  }
9337
- async function assertWorkspaceInitialized12(basouRoot) {
9671
+ async function assertWorkspaceInitialized13(basouRoot) {
9338
9672
  try {
9339
- await assertBasouRootSafe15(basouRoot);
9673
+ await assertBasouRootSafe16(basouRoot);
9340
9674
  } catch (error) {
9341
- if (findErrorCode14(error, "ENOENT")) {
9675
+ if (findErrorCode15(error, "ENOENT")) {
9342
9676
  throw new Error("Workspace not initialized. Run 'basou init' first.");
9343
9677
  }
9344
9678
  throw error;
@@ -9426,10 +9760,10 @@ function maxLen3(values, floor) {
9426
9760
 
9427
9761
  // src/commands/verify.ts
9428
9762
  import {
9429
- assertBasouRootSafe as assertBasouRootSafe16,
9430
- basouPaths as basouPaths19,
9763
+ assertBasouRootSafe as assertBasouRootSafe17,
9764
+ basouPaths as basouPaths20,
9431
9765
  enumerateSessionDirs as enumerateSessionDirs3,
9432
- findErrorCode as findErrorCode15,
9766
+ findErrorCode as findErrorCode16,
9433
9767
  resolveRepositoryRoot as resolveRepositoryRoot13,
9434
9768
  resolveSessionId as resolveSessionId5,
9435
9769
  verifyEventsChain
@@ -9453,8 +9787,8 @@ async function doRunVerify(options, ctx) {
9453
9787
  }
9454
9788
  const cwd = ctx.cwd ?? process.cwd();
9455
9789
  const repositoryRoot = await resolveRepositoryRootForVerify(cwd);
9456
- const paths = basouPaths19(repositoryRoot);
9457
- await assertWorkspaceInitialized13(paths.root);
9790
+ const paths = basouPaths20(repositoryRoot);
9791
+ await assertWorkspaceInitialized14(paths.root);
9458
9792
  const sessionIds = options.session !== void 0 ? [await resolveSessionId5(paths, options.session)] : await enumerateSessionDirs3(paths);
9459
9793
  const rows = [];
9460
9794
  for (const sessionId of sessionIds) {
@@ -9511,11 +9845,11 @@ async function resolveRepositoryRootForVerify(cwd) {
9511
9845
  throw error;
9512
9846
  }
9513
9847
  }
9514
- async function assertWorkspaceInitialized13(basouRoot) {
9848
+ async function assertWorkspaceInitialized14(basouRoot) {
9515
9849
  try {
9516
- await assertBasouRootSafe16(basouRoot);
9850
+ await assertBasouRootSafe17(basouRoot);
9517
9851
  } catch (error) {
9518
- if (findErrorCode15(error, "ENOENT")) {
9852
+ if (findErrorCode16(error, "ENOENT")) {
9519
9853
  throw new Error("Workspace not initialized. Run 'basou init' first.");
9520
9854
  }
9521
9855
  throw error;
@@ -9525,12 +9859,12 @@ async function assertWorkspaceInitialized13(basouRoot) {
9525
9859
  // src/commands/view.ts
9526
9860
  import { spawn } from "child_process";
9527
9861
  import { createHash } from "crypto";
9528
- import { basename as basename7, resolve as resolve11 } from "path";
9862
+ import { basename as basename7, resolve as resolve12 } from "path";
9529
9863
  import {
9530
- assertBasouRootSafe as assertBasouRootSafe17,
9531
- basouPaths as basouPaths20,
9532
- findErrorCode as findErrorCode17,
9533
- readManifest as readManifest13,
9864
+ assertBasouRootSafe as assertBasouRootSafe18,
9865
+ basouPaths as basouPaths21,
9866
+ findErrorCode as findErrorCode18,
9867
+ readManifest as readManifest14,
9534
9868
  resolveRepositoryRoot as resolveRepositoryRoot14
9535
9869
  } from "@basou/core";
9536
9870
  import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
@@ -9538,9 +9872,9 @@ import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
9538
9872
  // src/lib/portfolio-safety.ts
9539
9873
  import { execFile } from "child_process";
9540
9874
  import { lstat as lstat2, realpath as realpath2 } from "fs/promises";
9541
- import { isAbsolute as isAbsolute7, join as join15, relative as relative4, resolve as resolve10 } from "path";
9875
+ import { isAbsolute as isAbsolute7, join as join16, relative as relative4, resolve as resolve11 } from "path";
9542
9876
  import { promisify } from "util";
9543
- import { readManifest as readManifest11 } from "@basou/core";
9877
+ import { readManifest as readManifest12 } from "@basou/core";
9544
9878
  var execFileAsync = promisify(execFile);
9545
9879
  function errorCode(error) {
9546
9880
  return error instanceof Error ? error.code : void 0;
@@ -9549,7 +9883,7 @@ async function canonical(p) {
9549
9883
  try {
9550
9884
  return await realpath2(p);
9551
9885
  } catch {
9552
- return resolve10(p);
9886
+ return resolve11(p);
9553
9887
  }
9554
9888
  }
9555
9889
  function isInside(child, parent) {
@@ -9562,7 +9896,7 @@ function isBasouPath(p) {
9562
9896
  async function inspectRepo(repoPath) {
9563
9897
  let hasEntry = false;
9564
9898
  try {
9565
- await lstat2(join15(repoPath, ".basou"));
9899
+ await lstat2(join16(repoPath, ".basou"));
9566
9900
  hasEntry = true;
9567
9901
  } catch (error) {
9568
9902
  if (errorCode(error) !== "ENOENT") {
@@ -9593,7 +9927,7 @@ async function checkPortfolioSafety(workspaces) {
9593
9927
  const wsReal = await canonical(ws.repoRoot);
9594
9928
  let sourceRoots = [];
9595
9929
  try {
9596
- const manifest = await readManifest11(ws.paths);
9930
+ const manifest = await readManifest12(ws.paths);
9597
9931
  sourceRoots = manifest.import?.source_roots ?? [];
9598
9932
  } catch (error) {
9599
9933
  if (error instanceof Error && error.message === "YAML file not found") {
@@ -9611,7 +9945,7 @@ async function checkPortfolioSafety(workspaces) {
9611
9945
  }
9612
9946
  const monitored = /* @__PURE__ */ new Map();
9613
9947
  for (const root of sourceRoots) {
9614
- const display = resolve10(ws.repoRoot, root);
9948
+ const display = resolve11(ws.repoRoot, root);
9615
9949
  const real = await canonical(display);
9616
9950
  if (real !== wsReal) monitored.set(real, display);
9617
9951
  }
@@ -9663,18 +9997,18 @@ function formatSafetyReport(result) {
9663
9997
 
9664
9998
  // src/lib/view-server.ts
9665
9999
  import { createServer } from "http";
9666
- import { join as join16 } from "path";
10000
+ import { join as join17 } from "path";
9667
10001
  import {
9668
10002
  computeWorkStats as computeWorkStats2,
9669
10003
  enumerateApprovals as enumerateApprovals2,
9670
- findErrorCode as findErrorCode16,
10004
+ findErrorCode as findErrorCode17,
9671
10005
  isLazyExpired as isLazyExpired2,
9672
10006
  loadApproval as loadApproval2,
9673
10007
  loadSessionEntries as loadSessionEntries4,
9674
10008
  loadTaskEntries as loadTaskEntries2,
9675
10009
  readAllEvents as readAllEvents2,
9676
- readManifest as readManifest12,
9677
- readMarkdownFile as readMarkdownFile6,
10010
+ readManifest as readManifest13,
10011
+ readMarkdownFile as readMarkdownFile7,
9678
10012
  readSessionYaml as readSessionYaml3,
9679
10013
  readTaskFile as readTaskFile2,
9680
10014
  renderDecisions as renderDecisions3,
@@ -10324,7 +10658,7 @@ function startViewServer(opts) {
10324
10658
  };
10325
10659
  let boundPort = port;
10326
10660
  const getPort = () => boundPort;
10327
- return new Promise((resolve12, reject) => {
10661
+ return new Promise((resolve13, reject) => {
10328
10662
  const server = createServer((req, res) => {
10329
10663
  handleRequest(req, res, deps, getPort, runExclusive).catch((error) => {
10330
10664
  sendError(res, error instanceof HttpError ? error.status : 500, pathlessMessage(error));
@@ -10335,7 +10669,7 @@ function startViewServer(opts) {
10335
10669
  const address = server.address();
10336
10670
  boundPort = isAddressInfo(address) ? address.port : port;
10337
10671
  server.off("error", reject);
10338
- resolve12({
10672
+ resolve13({
10339
10673
  url: `http://${host}:${boundPort}`,
10340
10674
  port: boundPort,
10341
10675
  close: () => closeServer(server)
@@ -10347,8 +10681,8 @@ function isAddressInfo(value) {
10347
10681
  return value !== null && typeof value === "object";
10348
10682
  }
10349
10683
  function closeServer(server) {
10350
- return new Promise((resolve12) => {
10351
- server.close(() => resolve12());
10684
+ return new Promise((resolve13) => {
10685
+ server.close(() => resolve13());
10352
10686
  server.closeAllConnections();
10353
10687
  });
10354
10688
  }
@@ -10565,9 +10899,9 @@ async function captureStaleness(ws, nowIso) {
10565
10899
  async function overview(ws, nowProvider) {
10566
10900
  let manifest;
10567
10901
  try {
10568
- manifest = await readManifest12(ws.paths);
10902
+ manifest = await readManifest13(ws.paths);
10569
10903
  } catch (error) {
10570
- if (findErrorCode16(error, "ENOENT")) {
10904
+ if (findErrorCode17(error, "ENOENT")) {
10571
10905
  return { initialized: false, repoRoot: ws.repoRoot };
10572
10906
  }
10573
10907
  throw error;
@@ -10622,7 +10956,7 @@ async function sessionDetail(ws, sessionId) {
10622
10956
  throw error;
10623
10957
  }
10624
10958
  try {
10625
- const events = await readAllEvents2(join16(ws.paths.sessions, sessionId));
10959
+ const events = await readAllEvents2(join17(ws.paths.sessions, sessionId));
10626
10960
  return { session, events };
10627
10961
  } catch {
10628
10962
  return { session, events: [], degraded: true };
@@ -10644,7 +10978,7 @@ async function taskDetail(ws, taskId) {
10644
10978
  }
10645
10979
  }
10646
10980
  async function decisionsView(ws, nowProvider) {
10647
- const fromDisk = await readMarkdownFile6(ws.paths.files.decisions);
10981
+ const fromDisk = await readMarkdownFile7(ws.paths.files.decisions);
10648
10982
  if (fromDisk !== null) {
10649
10983
  return { body: fromDisk, fromDisk: true };
10650
10984
  }
@@ -10667,7 +11001,7 @@ async function approvalsView(ws, nowProvider) {
10667
11001
  return { pending: await toViews(ids.pending), resolved: await toViews(ids.resolved) };
10668
11002
  }
10669
11003
  async function handoffView(ws, nowProvider) {
10670
- const fromDisk = await readMarkdownFile6(ws.paths.files.handoff);
11004
+ const fromDisk = await readMarkdownFile7(ws.paths.files.handoff);
10671
11005
  if (fromDisk !== null) {
10672
11006
  return { body: fromDisk, fromDisk: true };
10673
11007
  }
@@ -10836,18 +11170,18 @@ async function doRunView(options, ctx) {
10836
11170
  }
10837
11171
  async function buildSingleDeps(ctx, cwd) {
10838
11172
  const repositoryRoot = await resolveRepositoryRootForView(cwd);
10839
- const paths = basouPaths20(repositoryRoot);
10840
- await assertWorkspaceInitialized14(paths.root);
11173
+ const paths = basouPaths21(repositoryRoot);
11174
+ await assertWorkspaceInitialized15(paths.root);
10841
11175
  const entry = await buildWorkspaceEntry(repositoryRoot, ctx);
10842
11176
  return { workspaces: [entry], mode: "single", nowProvider: nowProviderOf(ctx) };
10843
11177
  }
10844
11178
  async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
10845
- const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve11(cwd, p) })) : await loadPortfolioConfig(ctx.portfolioConfigPath);
11179
+ const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve12(cwd, p) })) : await loadPortfolioConfig(ctx.portfolioConfigPath);
10846
11180
  const entries = [];
10847
11181
  const seenPath = /* @__PURE__ */ new Set();
10848
11182
  const seenKey = /* @__PURE__ */ new Set();
10849
11183
  for (const spec of specs) {
10850
- const repoRoot = resolve11(spec.path);
11184
+ const repoRoot = resolve12(spec.path);
10851
11185
  if (seenPath.has(repoRoot)) continue;
10852
11186
  seenPath.add(repoRoot);
10853
11187
  const entry = await buildWorkspaceEntry(repoRoot, ctx, spec.label);
@@ -10860,14 +11194,14 @@ async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
10860
11194
  return { workspaces: entries, mode: "portfolio", nowProvider: nowProviderOf(ctx) };
10861
11195
  }
10862
11196
  async function buildWorkspaceEntry(repoRoot, ctx, labelOverride) {
10863
- const paths = basouPaths20(repoRoot);
11197
+ const paths = basouPaths21(repoRoot);
10864
11198
  const importCtx = {
10865
11199
  cwd: repoRoot,
10866
11200
  ...ctx.claudeProjectsDir !== void 0 ? { claudeProjectsDir: ctx.claudeProjectsDir } : {},
10867
11201
  ...ctx.codexSessionsDir !== void 0 ? { codexSessionsDir: ctx.codexSessionsDir } : {}
10868
11202
  };
10869
11203
  try {
10870
- const manifest = await readManifest13(paths);
11204
+ const manifest = await readManifest14(paths);
10871
11205
  return {
10872
11206
  key: manifest.workspace.id,
10873
11207
  label: labelOverride ?? manifest.workspace.name,
@@ -10896,7 +11230,7 @@ async function startListening(port, deps) {
10896
11230
  try {
10897
11231
  return await startViewServer({ port, deps });
10898
11232
  } catch (error) {
10899
- if (findErrorCode17(error, "EADDRINUSE")) {
11233
+ if (findErrorCode18(error, "EADDRINUSE")) {
10900
11234
  throw new Error(`Port ${port} is already in use. Pass --port <n> to choose another.`, {
10901
11235
  cause: error
10902
11236
  });
@@ -10919,7 +11253,7 @@ function openInBrowser(url, override) {
10919
11253
  }
10920
11254
  }
10921
11255
  function waitForShutdown(signal) {
10922
- return new Promise((resolve12) => {
11256
+ return new Promise((resolve13) => {
10923
11257
  const cleanup = () => {
10924
11258
  process.off("SIGINT", onSignal);
10925
11259
  process.off("SIGTERM", onSignal);
@@ -10927,18 +11261,18 @@ function waitForShutdown(signal) {
10927
11261
  };
10928
11262
  const onSignal = () => {
10929
11263
  cleanup();
10930
- resolve12();
11264
+ resolve13();
10931
11265
  };
10932
11266
  const onAbort = () => {
10933
11267
  cleanup();
10934
- resolve12();
11268
+ resolve13();
10935
11269
  };
10936
11270
  process.on("SIGINT", onSignal);
10937
11271
  process.on("SIGTERM", onSignal);
10938
11272
  if (signal !== void 0) {
10939
11273
  if (signal.aborted) {
10940
11274
  cleanup();
10941
- resolve12();
11275
+ resolve13();
10942
11276
  return;
10943
11277
  }
10944
11278
  signal.addEventListener("abort", onAbort);
@@ -10957,11 +11291,11 @@ async function resolveRepositoryRootForView(cwd) {
10957
11291
  throw error;
10958
11292
  }
10959
11293
  }
10960
- async function assertWorkspaceInitialized14(basouRoot) {
11294
+ async function assertWorkspaceInitialized15(basouRoot) {
10961
11295
  try {
10962
- await assertBasouRootSafe17(basouRoot);
11296
+ await assertBasouRootSafe18(basouRoot);
10963
11297
  } catch (error) {
10964
- if (findErrorCode17(error, "ENOENT")) {
11298
+ if (findErrorCode18(error, "ENOENT")) {
10965
11299
  throw new Error("Workspace not initialized. Run 'basou init' first.");
10966
11300
  }
10967
11301
  throw error;
@@ -10993,6 +11327,7 @@ function buildProgram() {
10993
11327
  registerDecisionsCommand(program2);
10994
11328
  registerReportCommand(program2);
10995
11329
  registerOrientCommand(program2);
11330
+ registerReviewCommand(program2);
10996
11331
  registerReviewGapsCommand(program2);
10997
11332
  registerProjectCommand(program2);
10998
11333
  registerProtocolCommand(program2);