@basou/cli 0.17.0 → 0.19.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
@@ -635,17 +635,21 @@ function printNoApprovals(options) {
635
635
  // src/commands/decision.ts
636
636
  import { readFile } from "fs/promises";
637
637
  import { homedir as homedir2 } from "os";
638
- import { resolve as resolve3 } from "path";
638
+ import { join as join3, resolve as resolve3 } from "path";
639
639
  import {
640
+ AGENT_INFRA_DIRS,
640
641
  acquireLock as acquireLock2,
641
642
  appendEventToExistingSession,
642
643
  assertBasouRootSafe as assertBasouRootSafe2,
643
644
  basouPaths as basouPaths3,
645
+ classifyFilesBySourceRoot,
644
646
  createAdHocSessionWithEvent,
645
647
  findErrorCode as findErrorCode2,
646
648
  isValidPrefixedId,
649
+ loadSessionEntries,
647
650
  prefixedUlid as prefixedUlid2,
648
651
  readManifest as readManifest2,
652
+ replayEvents as replayEvents2,
649
653
  resolveRepositoryRoot as resolveRepositoryRoot2,
650
654
  resolveSessionId,
651
655
  sanitizePath
@@ -834,6 +838,9 @@ function registerDecisionCommand(program2) {
834
838
  "Related file path (repeatable). Path is opaque; existence is verified at render time.",
835
839
  collectLinkedFile,
836
840
  []
841
+ ).option(
842
+ "--track",
843
+ "Record as a strategic track (an unfinished direction + why). orientation/handoff keep resurfacing open tracks until you close one with 'basou decision void'."
837
844
  ).option(
838
845
  "--session <session_id>",
839
846
  "Attach to an existing session; otherwise an ad-hoc session is created"
@@ -845,6 +852,17 @@ function registerDecisionCommand(program2) {
845
852
  ).option("--file <path>", "Read the JSON array from a file instead of stdin").option("--dry-run", "Validate and preview the decisions without writing them").option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").addHelpText("after", CAPTURE_HELP).action(async (options) => {
846
853
  await runDecisionCapture(options);
847
854
  });
855
+ decision.command("void").description(
856
+ "Void (or supersede) a recorded decision. Append-only: the original is kept but struck in decisions.md and skipped as orientation's latest direction. Use when a decision was wrong or recorded in the wrong project."
857
+ ).argument("<decision_id>", "The decision to void (its decision_ ULID)").option("--reason <text>", "Why the decision is voided", parseReason).option(
858
+ "--superseded-by <decision_id>",
859
+ "The decision that replaces this one (records a supersede rather than a plain void)"
860
+ ).option(
861
+ "--session <session_id>",
862
+ "Attach to an existing session; otherwise an ad-hoc session is created"
863
+ ).option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").action(async (decisionId, options) => {
864
+ await runDecisionVoid(decisionId, options);
865
+ });
848
866
  }
849
867
  var CAPTURE_HELP = `
850
868
  Input format (a JSON array; one object per decision):
@@ -855,13 +873,22 @@ Input format (a JSON array; one object per decision):
855
873
  "alternatives": ["npm workspaces", "yarn"],
856
874
  "rejected_reason": "npm hoisting caused phantom-dependency bugs",
857
875
  "linked_files": ["pnpm-workspace.yaml"]
876
+ },
877
+ {
878
+ "title": "Form-based admin editing is the next track (only 6/19 sections done)",
879
+ "rationale": "Raw-JSON editing is a stopgap, not the final shape; cover the rest.",
880
+ "kind": "track"
858
881
  }
859
882
  ]
860
883
 
861
- Only "title" is required; every other field is optional. All decisions are
862
- written into one ad-hoc session timestamped now, so orientation surfaces them
863
- as the latest decisions. Run from a workspace-view directory and it resolves to
864
- the planning repo, like 'basou orient' / 'basou refresh' / 'basou note'.
884
+ Only "title" is required; every other field is optional. Set "kind": "track" to
885
+ record a strategic, UNFINISHED direction (+ why): orientation/handoff resurface
886
+ open tracks every session until you close one with 'basou decision void <id>'.
887
+ Absent / "decision" is a point-in-time decision (surfaced only as the latest).
888
+ All decisions are written into one ad-hoc session timestamped now, so
889
+ orientation surfaces them as the latest decisions. Run from a workspace-view
890
+ directory and it resolves to the planning repo, like 'basou orient' /
891
+ 'basou refresh' / 'basou note'.
865
892
 
866
893
  Example (heredoc on stdin):
867
894
  basou decision capture <<'JSON'
@@ -879,6 +906,28 @@ async function runDecisionRecord(options, ctx = {}) {
879
906
  process.exitCode = 1;
880
907
  }
881
908
  }
909
+ async function warnLinkedFilesOutsideRoots(input) {
910
+ if (input.linkedFiles.length === 0) return;
911
+ try {
912
+ const manifest = await readManifest2(input.paths);
913
+ if ((manifest.import?.source_roots?.length ?? 0) === 0) return;
914
+ const scope = await classifyFilesBySourceRoot({
915
+ files: input.linkedFiles,
916
+ workingDirectory: input.cwd,
917
+ sourceRoots: manifest.import?.source_roots,
918
+ masterRoot: input.repositoryRoot,
919
+ extraInRoot: AGENT_INFRA_DIRS
920
+ });
921
+ if (scope.outOfRoot.length === 0) return;
922
+ const PATH_SAMPLE = 5;
923
+ const sample = scope.outOfRoot.slice(0, PATH_SAMPLE).join(", ");
924
+ const more = scope.outOfRoot.length > PATH_SAMPLE ? ` (... +${scope.outOfRoot.length - PATH_SAMPLE} more)` : "";
925
+ console.error(
926
+ `basou: ${scope.outOfRoot.length} linked file(s) resolve outside this project's source_roots: ${sample}${more} \u2014 this decision may belong to another project.`
927
+ );
928
+ } catch {
929
+ }
930
+ }
882
931
  async function doRunDecisionRecord(options, ctx) {
883
932
  const cwd = ctx.cwd ?? process.cwd();
884
933
  const repositoryRoot = await resolveRepositoryRootForDecision(cwd);
@@ -888,6 +937,12 @@ async function doRunDecisionRecord(options, ctx) {
888
937
  const occurredAt = now.toISOString();
889
938
  const decisionId = prefixedUlid2("decision");
890
939
  const rich = pickRichFields(options);
940
+ await warnLinkedFilesOutsideRoots({
941
+ linkedFiles: rich.linked_files ?? [],
942
+ cwd,
943
+ paths,
944
+ repositoryRoot
945
+ });
891
946
  if (options.session !== void 0) {
892
947
  const sessionId = await resolveSessionId(paths, options.session);
893
948
  const sesId = sessionId;
@@ -930,7 +985,7 @@ async function doRunDecisionRecord(options, ctx) {
930
985
  workingDirectory: repositoryRoot,
931
986
  invocation: {
932
987
  command: "basou decision record",
933
- args: ["--title", options.title]
988
+ args: options.track === true ? ["--title", options.title, "--track"] : ["--title", options.title]
934
989
  },
935
990
  targetEventBuilders: [
936
991
  (sessionId, eventId) => buildDecisionEvent({
@@ -971,6 +1026,12 @@ async function doRunDecisionCapture(options, ctx) {
971
1026
  await assertWorkspaceInitialized2(paths.root);
972
1027
  const raw = await readCaptureInput(options, ctx);
973
1028
  const decisions = parseCaptureInput(raw);
1029
+ await warnLinkedFilesOutsideRoots({
1030
+ linkedFiles: decisions.flatMap((d) => d.linked_files ?? []),
1031
+ cwd,
1032
+ paths,
1033
+ repositoryRoot
1034
+ });
974
1035
  if (options.dryRun === true) {
975
1036
  printCapturePreview(options, decisions);
976
1037
  return;
@@ -1017,6 +1078,161 @@ async function doRunDecisionCapture(options, ctx) {
1017
1078
  }))
1018
1079
  });
1019
1080
  }
1081
+ async function runDecisionVoid(decisionId, options, ctx = {}) {
1082
+ try {
1083
+ await doRunDecisionVoid(decisionId, options, ctx);
1084
+ } catch (error) {
1085
+ renderCliError(error, {
1086
+ verbose: isVerbose(options),
1087
+ classifiers: [failedToFinalizeClassifier]
1088
+ });
1089
+ process.exitCode = 1;
1090
+ }
1091
+ }
1092
+ async function doRunDecisionVoid(decisionId, options, ctx) {
1093
+ if (!isDecisionId(decisionId)) {
1094
+ throw new Error(`Invalid decision id: ${decisionId} (expected a decision_<ULID>).`);
1095
+ }
1096
+ if (options.supersededBy !== void 0 && !isDecisionId(options.supersededBy)) {
1097
+ throw new Error(
1098
+ `Invalid --superseded-by id: ${options.supersededBy} (expected a decision_<ULID>).`
1099
+ );
1100
+ }
1101
+ if (options.supersededBy === decisionId) {
1102
+ throw new Error("A decision cannot supersede itself.");
1103
+ }
1104
+ const cwd = ctx.cwd ?? process.cwd();
1105
+ const repositoryRoot = await resolveBasouRootForCommand(cwd, "decision void");
1106
+ const paths = basouPaths3(repositoryRoot);
1107
+ await assertWorkspaceInitialized2(paths.root);
1108
+ if (!await decisionExists(paths, decisionId)) {
1109
+ throw new Error(
1110
+ `Decision ${decisionId} not found in this workspace. Run 'basou decisions generate' or check the id.`
1111
+ );
1112
+ }
1113
+ const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
1114
+ const occurredAt = now.toISOString();
1115
+ const reason = options.reason;
1116
+ const supersededBy = options.supersededBy;
1117
+ if (options.session !== void 0) {
1118
+ const sessionId = await resolveSessionId(paths, options.session);
1119
+ const sessionLock = await acquireLock2(paths, "session", sessionId);
1120
+ let result;
1121
+ try {
1122
+ result = await appendEventToExistingSession({
1123
+ paths,
1124
+ sessionId,
1125
+ eventBuilder: (eventId) => buildDecisionVoidedEvent({
1126
+ eventId,
1127
+ sessionId,
1128
+ decisionId,
1129
+ occurredAt,
1130
+ reason,
1131
+ supersededBy
1132
+ })
1133
+ });
1134
+ } finally {
1135
+ await sessionLock.release();
1136
+ }
1137
+ printVoidResult(options, {
1138
+ mode: "attached",
1139
+ sessionId,
1140
+ decisionId,
1141
+ eventId: result.eventId,
1142
+ sessionStatus: result.sessionStatus,
1143
+ reason,
1144
+ supersededBy
1145
+ });
1146
+ return;
1147
+ }
1148
+ const manifest = await readManifest2(paths);
1149
+ const adHoc = await createAdHocSessionWithEvent({
1150
+ paths,
1151
+ manifest,
1152
+ label: `Ad-hoc decision void: ${decisionId}`,
1153
+ occurredAt,
1154
+ sessionSource: "human",
1155
+ workingDirectory: repositoryRoot,
1156
+ invocation: { command: "basou decision void", args: [decisionId] },
1157
+ targetEventBuilders: [
1158
+ (sessionId, eventId) => buildDecisionVoidedEvent({
1159
+ eventId,
1160
+ sessionId,
1161
+ decisionId,
1162
+ occurredAt,
1163
+ reason,
1164
+ supersededBy
1165
+ })
1166
+ ]
1167
+ });
1168
+ printVoidResult(options, {
1169
+ mode: "ad-hoc",
1170
+ sessionId: adHoc.sessionId,
1171
+ decisionId,
1172
+ eventId: adHoc.targetEventIds[0],
1173
+ sessionStatus: "completed",
1174
+ reason,
1175
+ supersededBy
1176
+ });
1177
+ }
1178
+ function isDecisionId(value) {
1179
+ return value.startsWith("decision_") && isValidPrefixedId(value);
1180
+ }
1181
+ async function decisionExists(paths, decisionId) {
1182
+ const entries = await loadSessionEntries(paths, { now: /* @__PURE__ */ new Date() });
1183
+ for (const entry of entries) {
1184
+ const sessionDir = join3(paths.sessions, entry.sessionId);
1185
+ try {
1186
+ for await (const ev of replayEvents2(sessionDir, {})) {
1187
+ if (ev.type === "decision_recorded" && ev.decision_id === decisionId) return true;
1188
+ }
1189
+ } catch {
1190
+ }
1191
+ }
1192
+ return false;
1193
+ }
1194
+ function buildDecisionVoidedEvent(input) {
1195
+ return {
1196
+ schema_version: "0.1.0",
1197
+ id: input.eventId,
1198
+ session_id: input.sessionId,
1199
+ occurred_at: input.occurredAt,
1200
+ source: "local-cli",
1201
+ type: "decision_voided",
1202
+ decision_id: input.decisionId,
1203
+ ...input.reason !== void 0 ? { reason: input.reason } : {},
1204
+ ...input.supersededBy !== void 0 ? { superseded_by: input.supersededBy } : {}
1205
+ };
1206
+ }
1207
+ function printVoidResult(options, result) {
1208
+ if (options.json === true) {
1209
+ console.log(
1210
+ JSON.stringify({
1211
+ event_id: result.eventId,
1212
+ session_id: result.sessionId,
1213
+ decision_id: result.decisionId,
1214
+ session_status: result.sessionStatus,
1215
+ mode: result.mode,
1216
+ ...result.reason !== void 0 ? { reason: result.reason } : {},
1217
+ ...result.supersededBy !== void 0 ? { superseded_by: result.supersededBy } : {}
1218
+ })
1219
+ );
1220
+ return;
1221
+ }
1222
+ const sid = shortSessionId(result.sessionId);
1223
+ const tail = result.supersededBy !== void 0 ? ` (superseded by ${result.supersededBy})` : "";
1224
+ if (result.mode === "ad-hoc") {
1225
+ console.log(`Voided ${result.decisionId} in ad-hoc session ${sid}${tail}`);
1226
+ } else {
1227
+ console.log(`Voided ${result.decisionId} in session ${sid} (${result.sessionStatus})${tail}`);
1228
+ }
1229
+ }
1230
+ function parseReason(raw) {
1231
+ if (raw.trim().length === 0) {
1232
+ throw new InvalidArgumentError("--reason must not be empty");
1233
+ }
1234
+ return raw;
1235
+ }
1020
1236
  async function readCaptureInput(options, ctx) {
1021
1237
  if (options.file !== void 0) {
1022
1238
  try {
@@ -1050,7 +1266,8 @@ var CAPTURE_ALLOWED_KEYS = /* @__PURE__ */ new Set([
1050
1266
  "rejected_reason",
1051
1267
  "alternatives",
1052
1268
  "linked_events",
1053
- "linked_files"
1269
+ "linked_files",
1270
+ "kind"
1054
1271
  ]);
1055
1272
  function parseCaptureInput(raw) {
1056
1273
  if (raw.trim().length === 0) {
@@ -1079,7 +1296,7 @@ function validateCaptureItem(item, index) {
1079
1296
  for (const key of Object.keys(obj)) {
1080
1297
  if (!CAPTURE_ALLOWED_KEYS.has(key)) {
1081
1298
  throw new Error(
1082
- `decision[${index}]: unknown field '${key}'. Allowed: title, rationale, rejected_reason, alternatives, linked_events, linked_files.`
1299
+ `decision[${index}]: unknown field '${key}'. Allowed: title, rationale, rejected_reason, alternatives, linked_events, linked_files, kind.`
1083
1300
  );
1084
1301
  }
1085
1302
  }
@@ -1087,6 +1304,12 @@ function validateCaptureItem(item, index) {
1087
1304
  throw new Error(`decision[${index}].title must be a non-empty string.`);
1088
1305
  }
1089
1306
  const out = { title: obj.title };
1307
+ if (obj.kind !== void 0) {
1308
+ if (obj.kind !== "decision" && obj.kind !== "track") {
1309
+ throw new Error(`decision[${index}].kind must be "decision" or "track", got '${obj.kind}'.`);
1310
+ }
1311
+ if (obj.kind === "track") out.kind = "track";
1312
+ }
1090
1313
  if (obj.rationale !== void 0) {
1091
1314
  out.rationale = requireNonEmptyString(obj.rationale, index, "rationale");
1092
1315
  }
@@ -1154,6 +1377,7 @@ function toRichFields(decision) {
1154
1377
  if (decision.alternatives !== void 0) out.alternatives = [...decision.alternatives];
1155
1378
  if (decision.linked_events !== void 0) out.linked_events = [...decision.linked_events];
1156
1379
  if (decision.linked_files !== void 0) out.linked_files = [...decision.linked_files];
1380
+ if (decision.kind !== void 0) out.kind = decision.kind;
1157
1381
  return out;
1158
1382
  }
1159
1383
  function buildCaptureLabel(count) {
@@ -1171,6 +1395,7 @@ function captureItemToPayload(item) {
1171
1395
  payload.rejected_reason = item.input.rejected_reason;
1172
1396
  if (item.input.linked_events !== void 0) payload.linked_events = item.input.linked_events;
1173
1397
  if (item.input.linked_files !== void 0) payload.linked_files = item.input.linked_files;
1398
+ if (item.input.kind !== void 0) payload.kind = item.input.kind;
1174
1399
  return payload;
1175
1400
  }
1176
1401
  function printCapturePreview(options, decisions) {
@@ -1182,7 +1407,7 @@ function printCapturePreview(options, decisions) {
1182
1407
  `Would capture ${decisions.length} decision${decisions.length === 1 ? "" : "s"} (dry run; nothing written):`
1183
1408
  );
1184
1409
  for (const decision of decisions) {
1185
- console.log(`- ${decision.title}`);
1410
+ console.log(`- ${decision.title}${decision.kind === "track" ? " [TRACK]" : ""}`);
1186
1411
  }
1187
1412
  }
1188
1413
  function printCaptureResult(options, result) {
@@ -1203,7 +1428,9 @@ function printCaptureResult(options, result) {
1203
1428
  `Captured ${result.items.length} decision${result.items.length === 1 ? "" : "s"} in ad-hoc session ${sid}:`
1204
1429
  );
1205
1430
  for (const item of result.items) {
1206
- console.log(`- ${item.decisionId}: ${item.input.title}`);
1431
+ console.log(
1432
+ `- ${item.decisionId}: ${item.input.title}${item.input.kind === "track" ? " [TRACK]" : ""}`
1433
+ );
1207
1434
  }
1208
1435
  }
1209
1436
  function pickRichFields(options) {
@@ -1219,6 +1446,7 @@ function pickRichFields(options) {
1219
1446
  if (options.linkedFile !== void 0 && options.linkedFile.length > 0) {
1220
1447
  out.linked_files = [...options.linkedFile];
1221
1448
  }
1449
+ if (options.track === true) out.kind = "track";
1222
1450
  return out;
1223
1451
  }
1224
1452
  function buildDecisionEvent(input) {
@@ -1235,7 +1463,8 @@ function buildDecisionEvent(input) {
1235
1463
  ...input.rich.alternatives !== void 0 ? { alternatives: input.rich.alternatives } : {},
1236
1464
  ...input.rich.rejected_reason !== void 0 ? { rejected_reason: input.rich.rejected_reason } : {},
1237
1465
  ...input.rich.linked_events !== void 0 ? { linked_events: input.rich.linked_events } : {},
1238
- ...input.rich.linked_files !== void 0 ? { linked_files: input.rich.linked_files } : {}
1466
+ ...input.rich.linked_files !== void 0 ? { linked_files: input.rich.linked_files } : {},
1467
+ ...input.rich.kind !== void 0 ? { kind: input.rich.kind } : {}
1239
1468
  };
1240
1469
  }
1241
1470
  function buildAdHocLabel(title) {
@@ -1302,15 +1531,19 @@ function printDecisionResult(options, result) {
1302
1531
  }
1303
1532
  if (result.rich.linked_events !== void 0) payload.linked_events = result.rich.linked_events;
1304
1533
  if (result.rich.linked_files !== void 0) payload.linked_files = result.rich.linked_files;
1534
+ if (result.rich.kind !== void 0) payload.kind = result.rich.kind;
1305
1535
  console.log(JSON.stringify(payload));
1306
1536
  return;
1307
1537
  }
1538
+ const trackPrefix = result.rich.kind === "track" ? "track " : "";
1308
1539
  const rationaleSuffix = result.rich.rationale !== void 0 ? ` (rationale: ${result.rich.rationale})` : "";
1309
1540
  if (result.mode === "ad-hoc") {
1310
- console.log(`Recorded ${result.decisionId} in ad-hoc session ${sid}${rationaleSuffix}`);
1541
+ console.log(
1542
+ `Recorded ${trackPrefix}${result.decisionId} in ad-hoc session ${sid}${rationaleSuffix}`
1543
+ );
1311
1544
  } else {
1312
1545
  console.log(
1313
- `Recorded ${result.decisionId} in session ${sid} (${result.sessionStatus})${rationaleSuffix}`
1546
+ `Recorded ${trackPrefix}${result.decisionId} in session ${sid} (${result.sessionStatus})${rationaleSuffix}`
1314
1547
  );
1315
1548
  }
1316
1549
  }
@@ -1408,7 +1641,7 @@ async function assertWorkspaceInitialized3(basouRoot) {
1408
1641
  // src/commands/exec.ts
1409
1642
  import { mkdir } from "fs/promises";
1410
1643
  import { homedir as homedir3 } from "os";
1411
- import { join as join3 } from "path";
1644
+ import { join as join4 } from "path";
1412
1645
  import {
1413
1646
  acquireLock as acquireLock3,
1414
1647
  assertBasouRootSafe as assertBasouRootSafe4,
@@ -1448,13 +1681,13 @@ async function runExec(command, args, options, ctx = {}) {
1448
1681
  await assertBasouRootSafe4(paths.root);
1449
1682
  const manifest = await readManifest3(paths);
1450
1683
  const sessionId = prefixedUlid3("ses");
1451
- const sessionDir = join3(paths.sessions, sessionId);
1684
+ const sessionDir = join4(paths.sessions, sessionId);
1452
1685
  await mkdir(sessionDir, { recursive: true });
1453
1686
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
1454
1687
  await coreAppendChainedEvent(paths, sessionId, event);
1455
1688
  });
1456
1689
  const startedAt = now().toISOString();
1457
- const sessionYamlPath = join3(sessionDir, "session.yaml");
1690
+ const sessionYamlPath = join4(sessionDir, "session.yaml");
1458
1691
  const session = buildInitialSession({
1459
1692
  id: sessionId,
1460
1693
  command,
@@ -1804,15 +2037,15 @@ async function assertWorkspaceInitialized4(basouRoot) {
1804
2037
  import { createReadStream } from "fs";
1805
2038
  import { readdir, readFile as readFile2, rm, stat as stat2 } from "fs/promises";
1806
2039
  import { homedir as homedir4 } from "os";
1807
- import { basename as basename2, dirname, join as join4, resolve as resolve4 } from "path";
2040
+ import { basename as basename2, dirname, join as join5, resolve as resolve4 } from "path";
1808
2041
  import { createInterface } from "readline";
1809
2042
  import {
1810
- AGENT_INFRA_DIRS,
2043
+ AGENT_INFRA_DIRS as AGENT_INFRA_DIRS2,
1811
2044
  assertBasouRootSafe as assertBasouRootSafe6,
1812
2045
  basouPaths as basouPaths7,
1813
2046
  CLAUDE_IMPORT_SOURCE,
1814
2047
  CODEX_IMPORT_SOURCE,
1815
- classifyFilesBySourceRoot,
2048
+ classifyFilesBySourceRoot as classifyFilesBySourceRoot2,
1816
2049
  claudeTranscriptToImportPayload,
1817
2050
  codexRolloutToImportPayload,
1818
2051
  enumerateSessionDirs,
@@ -1890,7 +2123,7 @@ async function doRunImportClaudeCode(options, ctx) {
1890
2123
  repoRoot: repositoryRoot,
1891
2124
  cwd: ctx.cwd ?? process.cwd()
1892
2125
  });
1893
- const projectsRoot = ctx.claudeProjectsDir ?? join4(homedir4(), ".claude", "projects");
2126
+ const projectsRoot = ctx.claudeProjectsDir ?? join5(homedir4(), ".claude", "projects");
1894
2127
  const files = await selectTranscriptFiles(projectsRoot, projectPaths, options);
1895
2128
  const projectSet = new Set(projectPaths);
1896
2129
  const candidates = files.map((file) => {
@@ -1929,7 +2162,7 @@ async function doRunImportCodex(options, ctx) {
1929
2162
  repoRoot: repositoryRoot,
1930
2163
  cwd: ctx.cwd ?? process.cwd()
1931
2164
  });
1932
- const sessionsRoot = ctx.codexSessionsDir ?? join4(homedir4(), ".codex", "sessions");
2165
+ const sessionsRoot = ctx.codexSessionsDir ?? join5(homedir4(), ".codex", "sessions");
1933
2166
  const rollouts = await discoverCodexRollouts(sessionsRoot, projectPaths, options);
1934
2167
  const candidates = rollouts.map(({ file, externalId }) => ({
1935
2168
  externalId,
@@ -1980,12 +2213,12 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
1980
2213
  const noteCrossProject = async (externalId, payload) => {
1981
2214
  if (!crossProjectCheck) return;
1982
2215
  try {
1983
- const scope = await classifyFilesBySourceRoot({
2216
+ const scope = await classifyFilesBySourceRoot2({
1984
2217
  files: payload.session.related_files ?? [],
1985
2218
  workingDirectory: payload.session.working_directory,
1986
2219
  sourceRoots: projectPaths,
1987
2220
  masterRoot: dirname(paths.root),
1988
- extraInRoot: AGENT_INFRA_DIRS
2221
+ extraInRoot: AGENT_INFRA_DIRS2
1989
2222
  });
1990
2223
  if (scope.outOfRoot.length > 0) crossProject.push({ externalId, outOfRoot: scope.outOfRoot });
1991
2224
  } catch {
@@ -2058,7 +2291,7 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
2058
2291
  if (priors.length > 0 && options.force === true) {
2059
2292
  if (options.dryRun !== true) {
2060
2293
  for (const { sessionId } of priors) {
2061
- await rm(join4(paths.sessions, sessionId), { recursive: true, force: true });
2294
+ await rm(join5(paths.sessions, sessionId), { recursive: true, force: true });
2062
2295
  }
2063
2296
  }
2064
2297
  counts.replaced++;
@@ -2169,7 +2402,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
2169
2402
  if (options.session !== void 0) {
2170
2403
  const matches = [];
2171
2404
  for (const projectPath of projectPaths) {
2172
- const file = join4(projectsRoot, encodeProjectDir(projectPath), `${options.session}.jsonl`);
2405
+ const file = join5(projectsRoot, encodeProjectDir(projectPath), `${options.session}.jsonl`);
2173
2406
  if (await pathExists(file)) matches.push(file);
2174
2407
  }
2175
2408
  if (matches.length === 0) {
@@ -2180,7 +2413,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
2180
2413
  const files = [];
2181
2414
  let anyDirFound = false;
2182
2415
  for (const projectPath of projectPaths) {
2183
- const transcriptDir = join4(projectsRoot, encodeProjectDir(projectPath));
2416
+ const transcriptDir = join5(projectsRoot, encodeProjectDir(projectPath));
2184
2417
  let entries;
2185
2418
  try {
2186
2419
  entries = await readdir(transcriptDir);
@@ -2190,7 +2423,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
2190
2423
  }
2191
2424
  anyDirFound = true;
2192
2425
  for (const name of entries) {
2193
- if (name.endsWith(".jsonl")) files.push(join4(transcriptDir, name));
2426
+ if (name.endsWith(".jsonl")) files.push(join5(transcriptDir, name));
2194
2427
  }
2195
2428
  }
2196
2429
  if (!anyDirFound) {
@@ -2247,7 +2480,7 @@ async function findRolloutFiles(sessionsRoot) {
2247
2480
  throw new Error("Failed to read Codex sessions directory", { cause: error });
2248
2481
  }
2249
2482
  for (const entry of entries) {
2250
- const full = join4(dir, entry.name);
2483
+ const full = join5(dir, entry.name);
2251
2484
  if (entry.isDirectory()) {
2252
2485
  await walk(full, false);
2253
2486
  } else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) {
@@ -2671,12 +2904,12 @@ import {
2671
2904
 
2672
2905
  // src/lib/hosts-config.ts
2673
2906
  import { homedir as homedir5 } from "os";
2674
- import { isAbsolute as isAbsolute2, join as join5, resolve as resolve6 } from "path";
2907
+ import { isAbsolute as isAbsolute2, join as join6, resolve as resolve6 } from "path";
2675
2908
  import { readYamlFile as readYamlFile4 } from "@basou/core";
2676
- var DEFAULT_HOSTS_CONFIG_PATH = join5(homedir5(), ".basou", "hosts.yaml");
2909
+ var DEFAULT_HOSTS_CONFIG_PATH = join6(homedir5(), ".basou", "hosts.yaml");
2677
2910
  function expandTilde2(p) {
2678
2911
  if (p === "~") return homedir5();
2679
- if (p.startsWith("~/")) return join5(homedir5(), p.slice(2));
2912
+ if (p.startsWith("~/")) return join6(homedir5(), p.slice(2));
2680
2913
  return p;
2681
2914
  }
2682
2915
  function isRecord2(value) {
@@ -2990,7 +3223,7 @@ import {
2990
3223
  unlinkSync,
2991
3224
  writeFileSync
2992
3225
  } from "fs";
2993
- import { basename as basename4, dirname as dirname2, isAbsolute as isAbsolute3, join as join6, relative as relative2, resolve as resolve7 } from "path";
3226
+ import { basename as basename4, dirname as dirname2, isAbsolute as isAbsolute3, join as join7, relative as relative2, resolve as resolve7 } from "path";
2994
3227
  import {
2995
3228
  basouPaths as basouPaths10,
2996
3229
  GENERATED_END,
@@ -3249,7 +3482,7 @@ function classifySourceRoot(repositoryRoot, declaredPath) {
3249
3482
  } catch {
3250
3483
  return { path: declaredPath, kind: "unresolved" };
3251
3484
  }
3252
- return { path: declaredPath, kind: existsSync(join6(real, ".git")) ? "repo" : "non-repo" };
3485
+ return { path: declaredPath, kind: existsSync(join7(real, ".git")) ? "repo" : "non-repo" };
3253
3486
  }
3254
3487
  async function doRunProjectAdopt(options, ctx) {
3255
3488
  const cwd = ctx.cwd ?? process.cwd();
@@ -3348,7 +3581,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
3348
3581
  } catch {
3349
3582
  return { ...base, reachable: false, instructionFiles: [] };
3350
3583
  }
3351
- if (!existsSync(join6(real, ".git"))) {
3584
+ if (!existsSync(join7(real, ".git"))) {
3352
3585
  return { ...base, reachable: false, instructionFiles: [] };
3353
3586
  }
3354
3587
  try {
@@ -3356,7 +3589,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
3356
3589
  for (const name of INSTRUCTION_FILES) {
3357
3590
  let present = true;
3358
3591
  try {
3359
- lstatSync(join6(real, name));
3592
+ lstatSync(join7(real, name));
3360
3593
  } catch {
3361
3594
  present = false;
3362
3595
  }
@@ -3453,10 +3686,10 @@ function gatherRepoGitignore(repositoryRoot, entry) {
3453
3686
  } catch {
3454
3687
  return { ...base, reachable: false, currentLines: [] };
3455
3688
  }
3456
- if (!existsSync(join6(real, ".git"))) {
3689
+ if (!existsSync(join7(real, ".git"))) {
3457
3690
  return { ...base, reachable: false, currentLines: [] };
3458
3691
  }
3459
- return { ...base, reachable: true, currentLines: readGitignoreLines(join6(real, ".gitignore")) };
3692
+ return { ...base, reachable: true, currentLines: readGitignoreLines(join7(real, ".gitignore")) };
3460
3693
  }
3461
3694
  function hasErrorCode(error) {
3462
3695
  return error instanceof Error && typeof error.code === "string";
@@ -3470,7 +3703,7 @@ function readGitignoreLines(file) {
3470
3703
  }
3471
3704
  }
3472
3705
  function applyGitignorePlan(repositoryRoot, plan) {
3473
- const file = join6(realpathSync(resolve7(repositoryRoot, plan.path)), ".gitignore");
3706
+ const file = join7(realpathSync(resolve7(repositoryRoot, plan.path)), ".gitignore");
3474
3707
  let existing = "";
3475
3708
  try {
3476
3709
  existing = readFileSync(file, "utf8");
@@ -3589,16 +3822,16 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
3589
3822
  if (real === anchorReal) {
3590
3823
  return { ...base, isAnchor: true, reachable: true, canonicalPresent: false, files: [] };
3591
3824
  }
3592
- if (!existsSync(join6(real, ".git"))) {
3825
+ if (!existsSync(join7(real, ".git"))) {
3593
3826
  return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
3594
3827
  }
3595
- const canonicalFile = join6(anchorReal, "agents", basename4(real), CANONICAL_FILE);
3828
+ const canonicalFile = join7(anchorReal, "agents", basename4(real), CANONICAL_FILE);
3596
3829
  if (!existsSync(canonicalFile)) {
3597
3830
  return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
3598
3831
  }
3599
3832
  const files = expectedSymlinkTargets(real, canonicalFile).map(
3600
3833
  (spec) => {
3601
- const { state, actualTarget } = inspectSymlink(join6(real, spec.name), spec.target);
3834
+ const { state, actualTarget } = inspectSymlink(join7(real, spec.name), spec.target);
3602
3835
  return {
3603
3836
  name: spec.name,
3604
3837
  expectedTarget: spec.target,
@@ -3627,7 +3860,7 @@ function applySymlinkPlan(repositoryRoot, plan) {
3627
3860
  const created = [];
3628
3861
  const failed = [];
3629
3862
  for (const { name, target } of plan.toCreate) {
3630
- const filePath = join6(real, name);
3863
+ const filePath = join7(real, name);
3631
3864
  try {
3632
3865
  mkdirSync(dirname2(filePath), { recursive: true });
3633
3866
  symlinkSync(target, filePath);
@@ -3769,7 +4002,7 @@ function resolveViewDir(repositoryRoot, viewPath) {
3769
4002
  return realpathSync(abs);
3770
4003
  } catch {
3771
4004
  try {
3772
- return join6(realpathSync(dirname2(abs)), basename4(abs));
4005
+ return join7(realpathSync(dirname2(abs)), basename4(abs));
3773
4006
  } catch {
3774
4007
  return abs;
3775
4008
  }
@@ -3787,7 +4020,7 @@ function gatherViewRepo(repositoryRoot, viewDir, entry) {
3787
4020
  return { path: entry.path, reachable: false };
3788
4021
  }
3789
4022
  const linkName = basename4(repoReal);
3790
- const { state, actualTarget } = inspectSymlink(join6(viewDir, linkName), expectedTarget);
4023
+ const { state, actualTarget } = inspectSymlink(join7(viewDir, linkName), expectedTarget);
3791
4024
  return {
3792
4025
  path: entry.path,
3793
4026
  reachable: true,
@@ -3801,7 +4034,7 @@ function applyViewPlan(viewDir, toCreate) {
3801
4034
  const created = [];
3802
4035
  const failed = [];
3803
4036
  for (const { name, target } of toCreate) {
3804
- const filePath = join6(viewDir, name);
4037
+ const filePath = join7(viewDir, name);
3805
4038
  try {
3806
4039
  mkdirSync(dirname2(filePath), { recursive: true });
3807
4040
  symlinkSync(target, filePath);
@@ -3816,7 +4049,7 @@ var TOP_LEVEL_INSTRUCTION_FILES_LOWER = new Set(
3816
4049
  INSTRUCTION_FILES.filter((f) => !f.includes("/")).map((f) => f.toLowerCase())
3817
4050
  );
3818
4051
  function classifyViewLink(viewDir, name, rosterRealpaths) {
3819
- const filePath = join6(viewDir, name);
4052
+ const filePath = join7(viewDir, name);
3820
4053
  let isLink;
3821
4054
  try {
3822
4055
  isLink = lstatSync(filePath).isSymbolicLink();
@@ -3845,7 +4078,7 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
3845
4078
  if (!isDir) {
3846
4079
  return { target, kind: existsSync(resolved) ? "non-repo" : "broken" };
3847
4080
  }
3848
- return { target, kind: existsSync(join6(resolved, ".git")) ? "repo" : "non-repo" };
4081
+ return { target, kind: existsSync(join7(resolved, ".git")) ? "repo" : "non-repo" };
3849
4082
  }
3850
4083
  function gatherExistingViewLinks(viewDir, rosterRealpaths) {
3851
4084
  let names;
@@ -3870,7 +4103,7 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
3870
4103
  const pruned = [];
3871
4104
  const failed = [];
3872
4105
  for (const { name } of toPrune) {
3873
- const filePath = join6(viewDir, name);
4106
+ const filePath = join7(viewDir, name);
3874
4107
  const c = classifyViewLink(viewDir, name, rosterRealpaths);
3875
4108
  if (c === null || c.kind !== "repo") {
3876
4109
  failed.push({
@@ -4081,10 +4314,10 @@ async function runProjectPreset(options, ctx = {}) {
4081
4314
  }
4082
4315
  }
4083
4316
  function canonicalFileFor(anchorReal, canonicalName) {
4084
- return join6(anchorReal, "agents", canonicalName, CANONICAL_FILE);
4317
+ return join7(anchorReal, "agents", canonicalName, CANONICAL_FILE);
4085
4318
  }
4086
4319
  function canonicalLabelFor(canonicalName) {
4087
- return join6("agents", canonicalName, CANONICAL_FILE);
4320
+ return join7("agents", canonicalName, CANONICAL_FILE);
4088
4321
  }
4089
4322
  async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
4090
4323
  const declared = {
@@ -4102,7 +4335,7 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
4102
4335
  if (real === anchorReal) {
4103
4336
  return { ...declared, isAnchor: true, reachable: true, canonicalPresent: false };
4104
4337
  }
4105
- if (!existsSync(join6(real, ".git"))) {
4338
+ if (!existsSync(join7(real, ".git"))) {
4106
4339
  return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
4107
4340
  }
4108
4341
  const canonicalName = basename4(real);
@@ -4333,24 +4566,24 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
4333
4566
  const instructionFiles = [];
4334
4567
  for (const name of INSTRUCTION_FILES) {
4335
4568
  try {
4336
- lstatSync(join6(real, name));
4569
+ lstatSync(join7(real, name));
4337
4570
  instructionFiles.push(name);
4338
4571
  } catch {
4339
4572
  }
4340
4573
  }
4341
4574
  let ignored;
4342
4575
  try {
4343
- ignored = new Set(readGitignoreLines(join6(real, ".gitignore")).map((l) => l.trim()));
4576
+ ignored = new Set(readGitignoreLines(join7(real, ".gitignore")).map((l) => l.trim()));
4344
4577
  } catch {
4345
4578
  ignored = /* @__PURE__ */ new Set();
4346
4579
  }
4347
4580
  const gitignorePatterns = INSTRUCTION_FILES.filter((p) => ignored.has(p) || ignored.has(`/${p}`));
4348
- const canonical2 = existsSync(join6(anchorReal, "agents", canonicalName, CANONICAL_FILE));
4581
+ const canonical2 = existsSync(join7(anchorReal, "agents", canonicalName, CANONICAL_FILE));
4349
4582
  let viewLink = false;
4350
4583
  const viewPath = manifest.workspace.view;
4351
4584
  if (viewPath !== void 0) {
4352
4585
  try {
4353
- lstatSync(join6(resolveViewDir(repositoryRoot, viewPath), canonicalName));
4586
+ lstatSync(join7(resolveViewDir(repositoryRoot, viewPath), canonicalName));
4354
4587
  viewLink = true;
4355
4588
  } catch {
4356
4589
  }
@@ -4512,12 +4745,12 @@ function gatherRenameWiring(repositoryRoot, manifest, oldBasename) {
4512
4745
  } catch {
4513
4746
  return { canonicalDirOld: false, viewLinkOld: false };
4514
4747
  }
4515
- const canonicalDirOld = existsSync(join6(anchorReal, "agents", oldBasename));
4748
+ const canonicalDirOld = existsSync(join7(anchorReal, "agents", oldBasename));
4516
4749
  let viewLinkOld = false;
4517
4750
  const viewPath = manifest.workspace.view;
4518
4751
  if (viewPath !== void 0) {
4519
4752
  try {
4520
- lstatSync(join6(resolveViewDir(repositoryRoot, viewPath), oldBasename));
4753
+ lstatSync(join7(resolveViewDir(repositoryRoot, viewPath), oldBasename));
4521
4754
  viewLinkOld = true;
4522
4755
  } catch {
4523
4756
  }
@@ -4666,7 +4899,7 @@ import {
4666
4899
  // src/lib/durable-write.ts
4667
4900
  import { randomUUID } from "crypto";
4668
4901
  import { lstat, open, rename, stat as stat3, unlink as unlink2 } from "fs/promises";
4669
- import { basename as basename5, dirname as dirname3, join as join7 } from "path";
4902
+ import { basename as basename5, dirname as dirname3, join as join8 } from "path";
4670
4903
  async function assertNotSymlink(targetPath) {
4671
4904
  try {
4672
4905
  const st = await lstat(targetPath);
@@ -4682,7 +4915,7 @@ async function assertNotSymlink(targetPath) {
4682
4915
  }
4683
4916
  async function writeFileDurable(targetPath, content) {
4684
4917
  const dir = dirname3(targetPath);
4685
- const tmpPath = join7(dir, `.${basename5(targetPath)}.tmp.${randomUUID()}`);
4918
+ const tmpPath = join8(dir, `.${basename5(targetPath)}.tmp.${randomUUID()}`);
4686
4919
  let mode = 420;
4687
4920
  try {
4688
4921
  mode = (await stat3(targetPath)).mode & 511;
@@ -4718,15 +4951,15 @@ async function writeFileDurable(targetPath, content) {
4718
4951
 
4719
4952
  // src/lib/protocols-config.ts
4720
4953
  import { homedir as homedir6 } from "os";
4721
- import { isAbsolute as isAbsolute4, join as join8, resolve as resolve8 } from "path";
4954
+ import { isAbsolute as isAbsolute4, join as join9, resolve as resolve8 } from "path";
4722
4955
  import { readYamlFile as readYamlFile5 } from "@basou/core";
4723
- var DEFAULT_PROTOCOLS_CONFIG_PATH = join8(homedir6(), ".basou", "protocols.yaml");
4724
- var DEFAULT_TARGET_PATH = join8(homedir6(), ".claude", "CLAUDE.md");
4956
+ var DEFAULT_PROTOCOLS_CONFIG_PATH = join9(homedir6(), ".basou", "protocols.yaml");
4957
+ var DEFAULT_TARGET_PATH = join9(homedir6(), ".claude", "CLAUDE.md");
4725
4958
  var ALLOWED_TOP_KEYS = /* @__PURE__ */ new Set(["version", "protocols"]);
4726
4959
  var ALLOWED_ENTRY_KEYS = /* @__PURE__ */ new Set(["source", "title"]);
4727
4960
  function expandTilde3(p) {
4728
4961
  if (p === "~") return homedir6();
4729
- if (p.startsWith("~/")) return join8(homedir6(), p.slice(2));
4962
+ if (p.startsWith("~/")) return join9(homedir6(), p.slice(2));
4730
4963
  return p;
4731
4964
  }
4732
4965
  function isRecord3(value) {
@@ -4979,15 +5212,15 @@ import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
4979
5212
  // src/commands/refresh-watch.ts
4980
5213
  import { readdir as readdir2, stat as stat4 } from "fs/promises";
4981
5214
  import { homedir as homedir7 } from "os";
4982
- import { join as join9 } from "path";
5215
+ import { join as join10 } from "path";
4983
5216
  import { findErrorCode as findErrorCode8 } from "@basou/core";
4984
5217
  var DEFAULT_WATCH_INTERVAL_SEC = 30;
4985
5218
  var MIN_WATCH_INTERVAL_SEC = 5;
4986
5219
  var MAX_WATCH_INTERVAL_SEC = 86400;
4987
5220
  function watchedRoots(ctx) {
4988
5221
  return [
4989
- ctx.codexSessionsDir ?? join9(homedir7(), ".codex", "sessions"),
4990
- ctx.claudeProjectsDir ?? join9(homedir7(), ".claude", "projects")
5222
+ ctx.codexSessionsDir ?? join10(homedir7(), ".codex", "sessions"),
5223
+ ctx.claudeProjectsDir ?? join10(homedir7(), ".claude", "projects")
4991
5224
  ];
4992
5225
  }
4993
5226
  async function scanSourceLogs(roots) {
@@ -5001,7 +5234,7 @@ async function scanSourceLogs(roots) {
5001
5234
  throw new Error("Failed to read a source log directory", { cause: error });
5002
5235
  }
5003
5236
  for (const entry of entries) {
5004
- const full = join9(dir, entry.name);
5237
+ const full = join10(dir, entry.name);
5005
5238
  if (entry.isDirectory()) {
5006
5239
  await walk(full);
5007
5240
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
@@ -5523,7 +5756,7 @@ function renderReviewGaps(summary) {
5523
5756
  // src/commands/run.ts
5524
5757
  import { mkdir as mkdir2 } from "fs/promises";
5525
5758
  import { homedir as homedir8 } from "os";
5526
- import { join as join10 } from "path";
5759
+ import { join as join11 } from "path";
5527
5760
  import {
5528
5761
  acquireLock as acquireLock5,
5529
5762
  assertBasouRootSafe as assertBasouRootSafe11,
@@ -5576,13 +5809,13 @@ async function runClaudeCode(args, options, ctx = {}) {
5576
5809
  await assertBasouRootSafe11(paths.root);
5577
5810
  const manifest = await readManifest7(paths);
5578
5811
  const sessionId = prefixedUlid4("ses");
5579
- const sessionDir = join10(paths.sessions, sessionId);
5812
+ const sessionDir = join11(paths.sessions, sessionId);
5580
5813
  await mkdir2(sessionDir, { recursive: true });
5581
5814
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
5582
5815
  await coreAppendChainedEvent2(paths, sessionId, event);
5583
5816
  });
5584
5817
  const startedAt = now().toISOString();
5585
- const sessionYamlPath = join10(sessionDir, "session.yaml");
5818
+ const sessionYamlPath = join11(sessionDir, "session.yaml");
5586
5819
  const session = buildInitialSession2({
5587
5820
  id: sessionId,
5588
5821
  command,
@@ -5925,7 +6158,7 @@ async function resolveRepositoryRootForRun(cwd) {
5925
6158
 
5926
6159
  // src/commands/session.ts
5927
6160
  import { readFile as readFile4 } from "fs/promises";
5928
- import { basename as basename6, isAbsolute as isAbsolute6, join as join11, relative as relative3 } from "path";
6161
+ import { basename as basename6, isAbsolute as isAbsolute6, join as join12, relative as relative3 } from "path";
5929
6162
  import {
5930
6163
  acquireLock as acquireLock6,
5931
6164
  appendEventToExistingSession as appendEventToExistingSession3,
@@ -5934,7 +6167,7 @@ import {
5934
6167
  enumerateSessionDirs as enumerateSessionDirs2,
5935
6168
  findErrorCode as findErrorCode11,
5936
6169
  importSessionFromJson as importSessionFromJson2,
5937
- loadSessionEntries,
6170
+ loadSessionEntries as loadSessionEntries2,
5938
6171
  readAllEvents,
5939
6172
  readManifest as readManifest8,
5940
6173
  readYamlFile as readYamlFile7,
@@ -5998,7 +6231,7 @@ async function doRunSessionList(options, ctx) {
5998
6231
  const paths = basouPaths15(repositoryRoot);
5999
6232
  await assertWorkspaceInitialized10(paths.root);
6000
6233
  const now = /* @__PURE__ */ new Date();
6001
- const records = (await loadSessionEntries(paths, {
6234
+ const records = (await loadSessionEntries2(paths, {
6002
6235
  now,
6003
6236
  onWarning: (w, sid) => printReplayWarning(w, sid),
6004
6237
  onSkip: (sid, reason) => printSessionListSkip(sid, reason)
@@ -6050,8 +6283,8 @@ async function doRunSessionShow(idInput, options, ctx) {
6050
6283
  const paths = basouPaths15(repositoryRoot);
6051
6284
  await assertWorkspaceInitialized10(paths.root);
6052
6285
  const sessionId = await resolveSessionId3(paths, idInput);
6053
- const sessionDir = join11(paths.sessions, sessionId);
6054
- const sessionYamlPath = join11(sessionDir, "session.yaml");
6286
+ const sessionDir = join12(paths.sessions, sessionId);
6287
+ const sessionYamlPath = join12(sessionDir, "session.yaml");
6055
6288
  let session;
6056
6289
  try {
6057
6290
  const raw = await readYamlFile7(sessionYamlPath);
@@ -6226,6 +6459,11 @@ function eventVariantSummary(ev) {
6226
6459
  return `approval=${ev.approval_id}`;
6227
6460
  case "decision_recorded":
6228
6461
  return ev.title;
6462
+ case "decision_voided": {
6463
+ const sup = ev.superseded_by !== void 0 ? ` superseded by ${ev.superseded_by}` : "";
6464
+ const reason = typeof ev.reason === "string" && ev.reason.length > 0 ? `: ${ev.reason}` : "";
6465
+ return `voided ${ev.decision_id}${reason}${sup}`;
6466
+ }
6229
6467
  case "task_created":
6230
6468
  return ev.title;
6231
6469
  case "task_status_changed":
@@ -6789,7 +7027,7 @@ async function resolveRepositoryRootForStatus(cwd) {
6789
7027
 
6790
7028
  // src/commands/task.ts
6791
7029
  import { readFile as readFile5 } from "fs/promises";
6792
- import { join as join12 } from "path";
7030
+ import { join as join13 } from "path";
6793
7031
  import {
6794
7032
  archiveTask,
6795
7033
  assertBasouRootSafe as assertBasouRootSafe15,
@@ -6799,7 +7037,7 @@ import {
6799
7037
  editTask,
6800
7038
  enumerateArchivedTaskIds,
6801
7039
  findErrorCode as findErrorCode14,
6802
- loadSessionEntries as loadSessionEntries2,
7040
+ loadSessionEntries as loadSessionEntries3,
6803
7041
  loadTaskEntries,
6804
7042
  prefixedUlid as prefixedUlid5,
6805
7043
  readManifest as readManifest10,
@@ -6808,7 +7046,7 @@ import {
6808
7046
  reconcileAllTasks,
6809
7047
  reconcileTask,
6810
7048
  refreshTaskLinkedSessions,
6811
- replayEvents as replayEvents2,
7049
+ replayEvents as replayEvents3,
6812
7050
  resolveRepositoryRoot as resolveRepositoryRoot12,
6813
7051
  resolveSessionId as resolveSessionId4,
6814
7052
  resolveTaskId as resolveTaskId2,
@@ -7112,13 +7350,13 @@ async function doRunTaskShow(idInput, options, ctx) {
7112
7350
  await assertWorkspaceInitialized12(paths.root);
7113
7351
  const taskId = await resolveTaskId2(paths, idInput, { includeArchived: true });
7114
7352
  const { doc, archived } = await readTaskFileWithArchiveFallback(paths, taskId);
7115
- const sessions = await loadSessionEntries2(paths, { now: /* @__PURE__ */ new Date() });
7353
+ const sessions = await loadSessionEntries3(paths, { now: /* @__PURE__ */ new Date() });
7116
7354
  const events = [];
7117
7355
  const linkedSessionIds = new Set(doc.task.task.linked_sessions);
7118
7356
  for (const s of sessions) {
7119
- const sessionDir = join12(paths.sessions, s.sessionId);
7357
+ const sessionDir = join13(paths.sessions, s.sessionId);
7120
7358
  try {
7121
- for await (const ev of replayEvents2(sessionDir, {
7359
+ for await (const ev of replayEvents3(sessionDir, {
7122
7360
  onWarning: (w) => printReplayWarning(w, s.sessionId)
7123
7361
  })) {
7124
7362
  if ((ev.type === "task_created" || ev.type === "task_status_changed" || ev.type === "task_reconciled" || ev.type === "task_linkage_refreshed" || ev.type === "task_deleted" || ev.type === "task_archived") && ev.task_id === taskId) {
@@ -8032,7 +8270,7 @@ import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
8032
8270
  // src/lib/portfolio-safety.ts
8033
8271
  import { execFile } from "child_process";
8034
8272
  import { lstat as lstat2, realpath as realpath2 } from "fs/promises";
8035
- import { isAbsolute as isAbsolute7, join as join13, relative as relative4, resolve as resolve10 } from "path";
8273
+ import { isAbsolute as isAbsolute7, join as join14, relative as relative4, resolve as resolve10 } from "path";
8036
8274
  import { promisify } from "util";
8037
8275
  import { readManifest as readManifest11 } from "@basou/core";
8038
8276
  var execFileAsync = promisify(execFile);
@@ -8056,7 +8294,7 @@ function isBasouPath(p) {
8056
8294
  async function inspectRepo(repoPath) {
8057
8295
  let hasEntry = false;
8058
8296
  try {
8059
- await lstat2(join13(repoPath, ".basou"));
8297
+ await lstat2(join14(repoPath, ".basou"));
8060
8298
  hasEntry = true;
8061
8299
  } catch (error) {
8062
8300
  if (errorCode(error) !== "ENOENT") {
@@ -8157,14 +8395,14 @@ function formatSafetyReport(result) {
8157
8395
 
8158
8396
  // src/lib/view-server.ts
8159
8397
  import { createServer } from "http";
8160
- import { join as join14 } from "path";
8398
+ import { join as join15 } from "path";
8161
8399
  import {
8162
8400
  computeWorkStats as computeWorkStats2,
8163
8401
  enumerateApprovals as enumerateApprovals2,
8164
8402
  findErrorCode as findErrorCode16,
8165
8403
  isLazyExpired as isLazyExpired2,
8166
8404
  loadApproval as loadApproval2,
8167
- loadSessionEntries as loadSessionEntries3,
8405
+ loadSessionEntries as loadSessionEntries4,
8168
8406
  loadTaskEntries as loadTaskEntries2,
8169
8407
  readAllEvents as readAllEvents2,
8170
8408
  readManifest as readManifest12,
@@ -8709,6 +8947,10 @@ var VIEW_HTML = `<!doctype html>
8709
8947
  }
8710
8948
  if (ev.type === 'file_changed') return ev.path + ' [' + ev.change_type + ']';
8711
8949
  if (ev.type === 'decision_recorded') return ev.title || '';
8950
+ if (ev.type === 'decision_voided') {
8951
+ var vs = ev.superseded_by ? ' superseded by ' + ev.superseded_by : '';
8952
+ return 'voided ' + ev.decision_id + (ev.reason ? ': ' + ev.reason : '') + vs;
8953
+ }
8712
8954
  return '';
8713
8955
  }
8714
8956
 
@@ -9086,7 +9328,7 @@ async function overview(ws, nowProvider) {
9086
9328
  };
9087
9329
  }
9088
9330
  async function sessionsList(ws, nowProvider) {
9089
- const entries = await loadSessionEntries3(ws.paths, { now: nowProvider() });
9331
+ const entries = await loadSessionEntries4(ws.paths, { now: nowProvider() });
9090
9332
  const sessions = entries.map((entry) => ({
9091
9333
  sessionId: entry.sessionId,
9092
9334
  label: entry.session.session.label ?? null,
@@ -9112,7 +9354,7 @@ async function sessionDetail(ws, sessionId) {
9112
9354
  throw error;
9113
9355
  }
9114
9356
  try {
9115
- const events = await readAllEvents2(join14(ws.paths.sessions, sessionId));
9357
+ const events = await readAllEvents2(join15(ws.paths.sessions, sessionId));
9116
9358
  return { session, events };
9117
9359
  } catch {
9118
9360
  return { session, events: [], degraded: true };