@basou/cli 0.16.0 → 0.18.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
@@ -845,6 +849,17 @@ function registerDecisionCommand(program2) {
845
849
  ).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
850
  await runDecisionCapture(options);
847
851
  });
852
+ decision.command("void").description(
853
+ "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."
854
+ ).argument("<decision_id>", "The decision to void (its decision_ ULID)").option("--reason <text>", "Why the decision is voided", parseReason).option(
855
+ "--superseded-by <decision_id>",
856
+ "The decision that replaces this one (records a supersede rather than a plain void)"
857
+ ).option(
858
+ "--session <session_id>",
859
+ "Attach to an existing session; otherwise an ad-hoc session is created"
860
+ ).option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").action(async (decisionId, options) => {
861
+ await runDecisionVoid(decisionId, options);
862
+ });
848
863
  }
849
864
  var CAPTURE_HELP = `
850
865
  Input format (a JSON array; one object per decision):
@@ -879,6 +894,28 @@ async function runDecisionRecord(options, ctx = {}) {
879
894
  process.exitCode = 1;
880
895
  }
881
896
  }
897
+ async function warnLinkedFilesOutsideRoots(input) {
898
+ if (input.linkedFiles.length === 0) return;
899
+ try {
900
+ const manifest = await readManifest2(input.paths);
901
+ if ((manifest.import?.source_roots?.length ?? 0) === 0) return;
902
+ const scope = await classifyFilesBySourceRoot({
903
+ files: input.linkedFiles,
904
+ workingDirectory: input.cwd,
905
+ sourceRoots: manifest.import?.source_roots,
906
+ masterRoot: input.repositoryRoot,
907
+ extraInRoot: AGENT_INFRA_DIRS
908
+ });
909
+ if (scope.outOfRoot.length === 0) return;
910
+ const PATH_SAMPLE = 5;
911
+ const sample = scope.outOfRoot.slice(0, PATH_SAMPLE).join(", ");
912
+ const more = scope.outOfRoot.length > PATH_SAMPLE ? ` (... +${scope.outOfRoot.length - PATH_SAMPLE} more)` : "";
913
+ console.error(
914
+ `basou: ${scope.outOfRoot.length} linked file(s) resolve outside this project's source_roots: ${sample}${more} \u2014 this decision may belong to another project.`
915
+ );
916
+ } catch {
917
+ }
918
+ }
882
919
  async function doRunDecisionRecord(options, ctx) {
883
920
  const cwd = ctx.cwd ?? process.cwd();
884
921
  const repositoryRoot = await resolveRepositoryRootForDecision(cwd);
@@ -888,6 +925,12 @@ async function doRunDecisionRecord(options, ctx) {
888
925
  const occurredAt = now.toISOString();
889
926
  const decisionId = prefixedUlid2("decision");
890
927
  const rich = pickRichFields(options);
928
+ await warnLinkedFilesOutsideRoots({
929
+ linkedFiles: rich.linked_files ?? [],
930
+ cwd,
931
+ paths,
932
+ repositoryRoot
933
+ });
891
934
  if (options.session !== void 0) {
892
935
  const sessionId = await resolveSessionId(paths, options.session);
893
936
  const sesId = sessionId;
@@ -971,6 +1014,12 @@ async function doRunDecisionCapture(options, ctx) {
971
1014
  await assertWorkspaceInitialized2(paths.root);
972
1015
  const raw = await readCaptureInput(options, ctx);
973
1016
  const decisions = parseCaptureInput(raw);
1017
+ await warnLinkedFilesOutsideRoots({
1018
+ linkedFiles: decisions.flatMap((d) => d.linked_files ?? []),
1019
+ cwd,
1020
+ paths,
1021
+ repositoryRoot
1022
+ });
974
1023
  if (options.dryRun === true) {
975
1024
  printCapturePreview(options, decisions);
976
1025
  return;
@@ -1017,6 +1066,161 @@ async function doRunDecisionCapture(options, ctx) {
1017
1066
  }))
1018
1067
  });
1019
1068
  }
1069
+ async function runDecisionVoid(decisionId, options, ctx = {}) {
1070
+ try {
1071
+ await doRunDecisionVoid(decisionId, options, ctx);
1072
+ } catch (error) {
1073
+ renderCliError(error, {
1074
+ verbose: isVerbose(options),
1075
+ classifiers: [failedToFinalizeClassifier]
1076
+ });
1077
+ process.exitCode = 1;
1078
+ }
1079
+ }
1080
+ async function doRunDecisionVoid(decisionId, options, ctx) {
1081
+ if (!isDecisionId(decisionId)) {
1082
+ throw new Error(`Invalid decision id: ${decisionId} (expected a decision_<ULID>).`);
1083
+ }
1084
+ if (options.supersededBy !== void 0 && !isDecisionId(options.supersededBy)) {
1085
+ throw new Error(
1086
+ `Invalid --superseded-by id: ${options.supersededBy} (expected a decision_<ULID>).`
1087
+ );
1088
+ }
1089
+ if (options.supersededBy === decisionId) {
1090
+ throw new Error("A decision cannot supersede itself.");
1091
+ }
1092
+ const cwd = ctx.cwd ?? process.cwd();
1093
+ const repositoryRoot = await resolveBasouRootForCommand(cwd, "decision void");
1094
+ const paths = basouPaths3(repositoryRoot);
1095
+ await assertWorkspaceInitialized2(paths.root);
1096
+ if (!await decisionExists(paths, decisionId)) {
1097
+ throw new Error(
1098
+ `Decision ${decisionId} not found in this workspace. Run 'basou decisions generate' or check the id.`
1099
+ );
1100
+ }
1101
+ const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
1102
+ const occurredAt = now.toISOString();
1103
+ const reason = options.reason;
1104
+ const supersededBy = options.supersededBy;
1105
+ if (options.session !== void 0) {
1106
+ const sessionId = await resolveSessionId(paths, options.session);
1107
+ const sessionLock = await acquireLock2(paths, "session", sessionId);
1108
+ let result;
1109
+ try {
1110
+ result = await appendEventToExistingSession({
1111
+ paths,
1112
+ sessionId,
1113
+ eventBuilder: (eventId) => buildDecisionVoidedEvent({
1114
+ eventId,
1115
+ sessionId,
1116
+ decisionId,
1117
+ occurredAt,
1118
+ reason,
1119
+ supersededBy
1120
+ })
1121
+ });
1122
+ } finally {
1123
+ await sessionLock.release();
1124
+ }
1125
+ printVoidResult(options, {
1126
+ mode: "attached",
1127
+ sessionId,
1128
+ decisionId,
1129
+ eventId: result.eventId,
1130
+ sessionStatus: result.sessionStatus,
1131
+ reason,
1132
+ supersededBy
1133
+ });
1134
+ return;
1135
+ }
1136
+ const manifest = await readManifest2(paths);
1137
+ const adHoc = await createAdHocSessionWithEvent({
1138
+ paths,
1139
+ manifest,
1140
+ label: `Ad-hoc decision void: ${decisionId}`,
1141
+ occurredAt,
1142
+ sessionSource: "human",
1143
+ workingDirectory: repositoryRoot,
1144
+ invocation: { command: "basou decision void", args: [decisionId] },
1145
+ targetEventBuilders: [
1146
+ (sessionId, eventId) => buildDecisionVoidedEvent({
1147
+ eventId,
1148
+ sessionId,
1149
+ decisionId,
1150
+ occurredAt,
1151
+ reason,
1152
+ supersededBy
1153
+ })
1154
+ ]
1155
+ });
1156
+ printVoidResult(options, {
1157
+ mode: "ad-hoc",
1158
+ sessionId: adHoc.sessionId,
1159
+ decisionId,
1160
+ eventId: adHoc.targetEventIds[0],
1161
+ sessionStatus: "completed",
1162
+ reason,
1163
+ supersededBy
1164
+ });
1165
+ }
1166
+ function isDecisionId(value) {
1167
+ return value.startsWith("decision_") && isValidPrefixedId(value);
1168
+ }
1169
+ async function decisionExists(paths, decisionId) {
1170
+ const entries = await loadSessionEntries(paths, { now: /* @__PURE__ */ new Date() });
1171
+ for (const entry of entries) {
1172
+ const sessionDir = join3(paths.sessions, entry.sessionId);
1173
+ try {
1174
+ for await (const ev of replayEvents2(sessionDir, {})) {
1175
+ if (ev.type === "decision_recorded" && ev.decision_id === decisionId) return true;
1176
+ }
1177
+ } catch {
1178
+ }
1179
+ }
1180
+ return false;
1181
+ }
1182
+ function buildDecisionVoidedEvent(input) {
1183
+ return {
1184
+ schema_version: "0.1.0",
1185
+ id: input.eventId,
1186
+ session_id: input.sessionId,
1187
+ occurred_at: input.occurredAt,
1188
+ source: "local-cli",
1189
+ type: "decision_voided",
1190
+ decision_id: input.decisionId,
1191
+ ...input.reason !== void 0 ? { reason: input.reason } : {},
1192
+ ...input.supersededBy !== void 0 ? { superseded_by: input.supersededBy } : {}
1193
+ };
1194
+ }
1195
+ function printVoidResult(options, result) {
1196
+ if (options.json === true) {
1197
+ console.log(
1198
+ JSON.stringify({
1199
+ event_id: result.eventId,
1200
+ session_id: result.sessionId,
1201
+ decision_id: result.decisionId,
1202
+ session_status: result.sessionStatus,
1203
+ mode: result.mode,
1204
+ ...result.reason !== void 0 ? { reason: result.reason } : {},
1205
+ ...result.supersededBy !== void 0 ? { superseded_by: result.supersededBy } : {}
1206
+ })
1207
+ );
1208
+ return;
1209
+ }
1210
+ const sid = shortSessionId(result.sessionId);
1211
+ const tail = result.supersededBy !== void 0 ? ` (superseded by ${result.supersededBy})` : "";
1212
+ if (result.mode === "ad-hoc") {
1213
+ console.log(`Voided ${result.decisionId} in ad-hoc session ${sid}${tail}`);
1214
+ } else {
1215
+ console.log(`Voided ${result.decisionId} in session ${sid} (${result.sessionStatus})${tail}`);
1216
+ }
1217
+ }
1218
+ function parseReason(raw) {
1219
+ if (raw.trim().length === 0) {
1220
+ throw new InvalidArgumentError("--reason must not be empty");
1221
+ }
1222
+ return raw;
1223
+ }
1020
1224
  async function readCaptureInput(options, ctx) {
1021
1225
  if (options.file !== void 0) {
1022
1226
  try {
@@ -1408,7 +1612,7 @@ async function assertWorkspaceInitialized3(basouRoot) {
1408
1612
  // src/commands/exec.ts
1409
1613
  import { mkdir } from "fs/promises";
1410
1614
  import { homedir as homedir3 } from "os";
1411
- import { join as join3 } from "path";
1615
+ import { join as join4 } from "path";
1412
1616
  import {
1413
1617
  acquireLock as acquireLock3,
1414
1618
  assertBasouRootSafe as assertBasouRootSafe4,
@@ -1448,13 +1652,13 @@ async function runExec(command, args, options, ctx = {}) {
1448
1652
  await assertBasouRootSafe4(paths.root);
1449
1653
  const manifest = await readManifest3(paths);
1450
1654
  const sessionId = prefixedUlid3("ses");
1451
- const sessionDir = join3(paths.sessions, sessionId);
1655
+ const sessionDir = join4(paths.sessions, sessionId);
1452
1656
  await mkdir(sessionDir, { recursive: true });
1453
1657
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
1454
1658
  await coreAppendChainedEvent(paths, sessionId, event);
1455
1659
  });
1456
1660
  const startedAt = now().toISOString();
1457
- const sessionYamlPath = join3(sessionDir, "session.yaml");
1661
+ const sessionYamlPath = join4(sessionDir, "session.yaml");
1458
1662
  const session = buildInitialSession({
1459
1663
  id: sessionId,
1460
1664
  command,
@@ -1804,13 +2008,15 @@ async function assertWorkspaceInitialized4(basouRoot) {
1804
2008
  import { createReadStream } from "fs";
1805
2009
  import { readdir, readFile as readFile2, rm, stat as stat2 } from "fs/promises";
1806
2010
  import { homedir as homedir4 } from "os";
1807
- import { basename as basename2, join as join4, resolve as resolve4 } from "path";
2011
+ import { basename as basename2, dirname, join as join5, resolve as resolve4 } from "path";
1808
2012
  import { createInterface } from "readline";
1809
2013
  import {
2014
+ AGENT_INFRA_DIRS as AGENT_INFRA_DIRS2,
1810
2015
  assertBasouRootSafe as assertBasouRootSafe6,
1811
2016
  basouPaths as basouPaths7,
1812
2017
  CLAUDE_IMPORT_SOURCE,
1813
2018
  CODEX_IMPORT_SOURCE,
2019
+ classifyFilesBySourceRoot as classifyFilesBySourceRoot2,
1814
2020
  claudeTranscriptToImportPayload,
1815
2021
  codexRolloutToImportPayload,
1816
2022
  enumerateSessionDirs,
@@ -1888,7 +2094,7 @@ async function doRunImportClaudeCode(options, ctx) {
1888
2094
  repoRoot: repositoryRoot,
1889
2095
  cwd: ctx.cwd ?? process.cwd()
1890
2096
  });
1891
- const projectsRoot = ctx.claudeProjectsDir ?? join4(homedir4(), ".claude", "projects");
2097
+ const projectsRoot = ctx.claudeProjectsDir ?? join5(homedir4(), ".claude", "projects");
1892
2098
  const files = await selectTranscriptFiles(projectsRoot, projectPaths, options);
1893
2099
  const projectSet = new Set(projectPaths);
1894
2100
  const candidates = files.map((file) => {
@@ -1908,7 +2114,15 @@ async function doRunImportClaudeCode(options, ctx) {
1908
2114
  }
1909
2115
  };
1910
2116
  });
1911
- await importDerivedSessions(paths, manifest, options, CLAUDE_IMPORT_SOURCE, candidates);
2117
+ await importDerivedSessions(
2118
+ paths,
2119
+ manifest,
2120
+ options,
2121
+ CLAUDE_IMPORT_SOURCE,
2122
+ candidates,
2123
+ projectPaths,
2124
+ hasDeclaredBoundary(options, manifest)
2125
+ );
1912
2126
  }
1913
2127
  async function doRunImportCodex(options, ctx) {
1914
2128
  assertSelector(options);
@@ -1919,7 +2133,7 @@ async function doRunImportCodex(options, ctx) {
1919
2133
  repoRoot: repositoryRoot,
1920
2134
  cwd: ctx.cwd ?? process.cwd()
1921
2135
  });
1922
- const sessionsRoot = ctx.codexSessionsDir ?? join4(homedir4(), ".codex", "sessions");
2136
+ const sessionsRoot = ctx.codexSessionsDir ?? join5(homedir4(), ".codex", "sessions");
1923
2137
  const rollouts = await discoverCodexRollouts(sessionsRoot, projectPaths, options);
1924
2138
  const candidates = rollouts.map(({ file, externalId }) => ({
1925
2139
  externalId,
@@ -1933,7 +2147,18 @@ async function doRunImportCodex(options, ctx) {
1933
2147
  });
1934
2148
  }
1935
2149
  }));
1936
- await importDerivedSessions(paths, manifest, options, CODEX_IMPORT_SOURCE, candidates);
2150
+ await importDerivedSessions(
2151
+ paths,
2152
+ manifest,
2153
+ options,
2154
+ CODEX_IMPORT_SOURCE,
2155
+ candidates,
2156
+ projectPaths,
2157
+ hasDeclaredBoundary(options, manifest)
2158
+ );
2159
+ }
2160
+ function hasDeclaredBoundary(options, manifest) {
2161
+ return (options.project?.length ?? 0) > 0 || (manifest.import?.source_roots?.length ?? 0) > 0;
1937
2162
  }
1938
2163
  function assertSelector(options) {
1939
2164
  if (options.session !== void 0 && options.all === true) {
@@ -1951,9 +2176,25 @@ async function resolveImportTarget(ctx) {
1951
2176
  const manifest = await readManifest4(paths);
1952
2177
  return { repositoryRoot, paths, manifest };
1953
2178
  }
1954
- async function importDerivedSessions(paths, manifest, options, sourceKind, candidates) {
2179
+ async function importDerivedSessions(paths, manifest, options, sourceKind, candidates, projectPaths, boundaryDeclared) {
1955
2180
  const existingByExternalId = await loadExistingByExternalId(paths, sourceKind);
1956
2181
  const seenThisRun = /* @__PURE__ */ new Set();
2182
+ const crossProjectCheck = boundaryDeclared;
2183
+ const crossProject = [];
2184
+ const noteCrossProject = async (externalId, payload) => {
2185
+ if (!crossProjectCheck) return;
2186
+ try {
2187
+ const scope = await classifyFilesBySourceRoot2({
2188
+ files: payload.session.related_files ?? [],
2189
+ workingDirectory: payload.session.working_directory,
2190
+ sourceRoots: projectPaths,
2191
+ masterRoot: dirname(paths.root),
2192
+ extraInRoot: AGENT_INFRA_DIRS2
2193
+ });
2194
+ if (scope.outOfRoot.length > 0) crossProject.push({ externalId, outOfRoot: scope.outOfRoot });
2195
+ } catch {
2196
+ }
2197
+ };
1957
2198
  const results = [];
1958
2199
  const counts = {
1959
2200
  skippedNoAction: 0,
@@ -2010,6 +2251,7 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
2010
2251
  }
2011
2252
  counts.reimported++;
2012
2253
  seenThisRun.add(externalId);
2254
+ await noteCrossProject(externalId, payload2);
2013
2255
  continue;
2014
2256
  }
2015
2257
  const payload = validate(await toPayload());
@@ -2020,7 +2262,7 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
2020
2262
  if (priors.length > 0 && options.force === true) {
2021
2263
  if (options.dryRun !== true) {
2022
2264
  for (const { sessionId } of priors) {
2023
- await rm(join4(paths.sessions, sessionId), { recursive: true, force: true });
2265
+ await rm(join5(paths.sessions, sessionId), { recursive: true, force: true });
2024
2266
  }
2025
2267
  }
2026
2268
  counts.replaced++;
@@ -2031,10 +2273,21 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
2031
2273
  results.push(result);
2032
2274
  seenThisRun.add(externalId);
2033
2275
  sanitizedPaths += result.pathSanitizeReport.relatedFiles + (result.pathSanitizeReport.workingDirectoryRewritten ? 1 : 0);
2276
+ await noteCrossProject(externalId, payload);
2034
2277
  }
2035
2278
  if (sanitizedPaths > 0) {
2036
2279
  console.error(`Imported sessions: ${sanitizedPaths} path(s) sanitized`);
2037
2280
  }
2281
+ if (crossProject.length > 0) {
2282
+ const PATH_SAMPLE = 5;
2283
+ for (const { externalId, outOfRoot } of crossProject) {
2284
+ const sample = outOfRoot.slice(0, PATH_SAMPLE).join(", ");
2285
+ const more = outOfRoot.length > PATH_SAMPLE ? ` (... +${outOfRoot.length - PATH_SAMPLE} more)` : "";
2286
+ console.error(
2287
+ `basou: session ${externalId} edited ${outOfRoot.length} file(s) outside this project's source_roots: ${sample}${more} \u2014 they may belong to another project.`
2288
+ );
2289
+ }
2290
+ }
2038
2291
  printImportResult(options, results, counts);
2039
2292
  }
2040
2293
  async function classifyReimport(priors, sourcePath, externalId, counts) {
@@ -2120,7 +2373,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
2120
2373
  if (options.session !== void 0) {
2121
2374
  const matches = [];
2122
2375
  for (const projectPath of projectPaths) {
2123
- const file = join4(projectsRoot, encodeProjectDir(projectPath), `${options.session}.jsonl`);
2376
+ const file = join5(projectsRoot, encodeProjectDir(projectPath), `${options.session}.jsonl`);
2124
2377
  if (await pathExists(file)) matches.push(file);
2125
2378
  }
2126
2379
  if (matches.length === 0) {
@@ -2131,7 +2384,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
2131
2384
  const files = [];
2132
2385
  let anyDirFound = false;
2133
2386
  for (const projectPath of projectPaths) {
2134
- const transcriptDir = join4(projectsRoot, encodeProjectDir(projectPath));
2387
+ const transcriptDir = join5(projectsRoot, encodeProjectDir(projectPath));
2135
2388
  let entries;
2136
2389
  try {
2137
2390
  entries = await readdir(transcriptDir);
@@ -2141,7 +2394,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
2141
2394
  }
2142
2395
  anyDirFound = true;
2143
2396
  for (const name of entries) {
2144
- if (name.endsWith(".jsonl")) files.push(join4(transcriptDir, name));
2397
+ if (name.endsWith(".jsonl")) files.push(join5(transcriptDir, name));
2145
2398
  }
2146
2399
  }
2147
2400
  if (!anyDirFound) {
@@ -2198,7 +2451,7 @@ async function findRolloutFiles(sessionsRoot) {
2198
2451
  throw new Error("Failed to read Codex sessions directory", { cause: error });
2199
2452
  }
2200
2453
  for (const entry of entries) {
2201
- const full = join4(dir, entry.name);
2454
+ const full = join5(dir, entry.name);
2202
2455
  if (entry.isDirectory()) {
2203
2456
  await walk(full, false);
2204
2457
  } else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) {
@@ -2622,12 +2875,12 @@ import {
2622
2875
 
2623
2876
  // src/lib/hosts-config.ts
2624
2877
  import { homedir as homedir5 } from "os";
2625
- import { isAbsolute as isAbsolute2, join as join5, resolve as resolve6 } from "path";
2878
+ import { isAbsolute as isAbsolute2, join as join6, resolve as resolve6 } from "path";
2626
2879
  import { readYamlFile as readYamlFile4 } from "@basou/core";
2627
- var DEFAULT_HOSTS_CONFIG_PATH = join5(homedir5(), ".basou", "hosts.yaml");
2880
+ var DEFAULT_HOSTS_CONFIG_PATH = join6(homedir5(), ".basou", "hosts.yaml");
2628
2881
  function expandTilde2(p) {
2629
2882
  if (p === "~") return homedir5();
2630
- if (p.startsWith("~/")) return join5(homedir5(), p.slice(2));
2883
+ if (p.startsWith("~/")) return join6(homedir5(), p.slice(2));
2631
2884
  return p;
2632
2885
  }
2633
2886
  function isRecord2(value) {
@@ -2941,7 +3194,7 @@ import {
2941
3194
  unlinkSync,
2942
3195
  writeFileSync
2943
3196
  } from "fs";
2944
- import { basename as basename4, dirname, isAbsolute as isAbsolute3, join as join6, relative as relative2, resolve as resolve7 } from "path";
3197
+ import { basename as basename4, dirname as dirname2, isAbsolute as isAbsolute3, join as join7, relative as relative2, resolve as resolve7 } from "path";
2945
3198
  import {
2946
3199
  basouPaths as basouPaths10,
2947
3200
  GENERATED_END,
@@ -3200,7 +3453,7 @@ function classifySourceRoot(repositoryRoot, declaredPath) {
3200
3453
  } catch {
3201
3454
  return { path: declaredPath, kind: "unresolved" };
3202
3455
  }
3203
- return { path: declaredPath, kind: existsSync(join6(real, ".git")) ? "repo" : "non-repo" };
3456
+ return { path: declaredPath, kind: existsSync(join7(real, ".git")) ? "repo" : "non-repo" };
3204
3457
  }
3205
3458
  async function doRunProjectAdopt(options, ctx) {
3206
3459
  const cwd = ctx.cwd ?? process.cwd();
@@ -3299,7 +3552,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
3299
3552
  } catch {
3300
3553
  return { ...base, reachable: false, instructionFiles: [] };
3301
3554
  }
3302
- if (!existsSync(join6(real, ".git"))) {
3555
+ if (!existsSync(join7(real, ".git"))) {
3303
3556
  return { ...base, reachable: false, instructionFiles: [] };
3304
3557
  }
3305
3558
  try {
@@ -3307,7 +3560,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
3307
3560
  for (const name of INSTRUCTION_FILES) {
3308
3561
  let present = true;
3309
3562
  try {
3310
- lstatSync(join6(real, name));
3563
+ lstatSync(join7(real, name));
3311
3564
  } catch {
3312
3565
  present = false;
3313
3566
  }
@@ -3404,10 +3657,10 @@ function gatherRepoGitignore(repositoryRoot, entry) {
3404
3657
  } catch {
3405
3658
  return { ...base, reachable: false, currentLines: [] };
3406
3659
  }
3407
- if (!existsSync(join6(real, ".git"))) {
3660
+ if (!existsSync(join7(real, ".git"))) {
3408
3661
  return { ...base, reachable: false, currentLines: [] };
3409
3662
  }
3410
- return { ...base, reachable: true, currentLines: readGitignoreLines(join6(real, ".gitignore")) };
3663
+ return { ...base, reachable: true, currentLines: readGitignoreLines(join7(real, ".gitignore")) };
3411
3664
  }
3412
3665
  function hasErrorCode(error) {
3413
3666
  return error instanceof Error && typeof error.code === "string";
@@ -3421,7 +3674,7 @@ function readGitignoreLines(file) {
3421
3674
  }
3422
3675
  }
3423
3676
  function applyGitignorePlan(repositoryRoot, plan) {
3424
- const file = join6(realpathSync(resolve7(repositoryRoot, plan.path)), ".gitignore");
3677
+ const file = join7(realpathSync(resolve7(repositoryRoot, plan.path)), ".gitignore");
3425
3678
  let existing = "";
3426
3679
  try {
3427
3680
  existing = readFileSync(file, "utf8");
@@ -3540,16 +3793,16 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
3540
3793
  if (real === anchorReal) {
3541
3794
  return { ...base, isAnchor: true, reachable: true, canonicalPresent: false, files: [] };
3542
3795
  }
3543
- if (!existsSync(join6(real, ".git"))) {
3796
+ if (!existsSync(join7(real, ".git"))) {
3544
3797
  return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
3545
3798
  }
3546
- const canonicalFile = join6(anchorReal, "agents", basename4(real), CANONICAL_FILE);
3799
+ const canonicalFile = join7(anchorReal, "agents", basename4(real), CANONICAL_FILE);
3547
3800
  if (!existsSync(canonicalFile)) {
3548
3801
  return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
3549
3802
  }
3550
3803
  const files = expectedSymlinkTargets(real, canonicalFile).map(
3551
3804
  (spec) => {
3552
- const { state, actualTarget } = inspectSymlink(join6(real, spec.name), spec.target);
3805
+ const { state, actualTarget } = inspectSymlink(join7(real, spec.name), spec.target);
3553
3806
  return {
3554
3807
  name: spec.name,
3555
3808
  expectedTarget: spec.target,
@@ -3578,9 +3831,9 @@ function applySymlinkPlan(repositoryRoot, plan) {
3578
3831
  const created = [];
3579
3832
  const failed = [];
3580
3833
  for (const { name, target } of plan.toCreate) {
3581
- const filePath = join6(real, name);
3834
+ const filePath = join7(real, name);
3582
3835
  try {
3583
- mkdirSync(dirname(filePath), { recursive: true });
3836
+ mkdirSync(dirname2(filePath), { recursive: true });
3584
3837
  symlinkSync(target, filePath);
3585
3838
  created.push(name);
3586
3839
  } catch (error) {
@@ -3720,7 +3973,7 @@ function resolveViewDir(repositoryRoot, viewPath) {
3720
3973
  return realpathSync(abs);
3721
3974
  } catch {
3722
3975
  try {
3723
- return join6(realpathSync(dirname(abs)), basename4(abs));
3976
+ return join7(realpathSync(dirname2(abs)), basename4(abs));
3724
3977
  } catch {
3725
3978
  return abs;
3726
3979
  }
@@ -3738,7 +3991,7 @@ function gatherViewRepo(repositoryRoot, viewDir, entry) {
3738
3991
  return { path: entry.path, reachable: false };
3739
3992
  }
3740
3993
  const linkName = basename4(repoReal);
3741
- const { state, actualTarget } = inspectSymlink(join6(viewDir, linkName), expectedTarget);
3994
+ const { state, actualTarget } = inspectSymlink(join7(viewDir, linkName), expectedTarget);
3742
3995
  return {
3743
3996
  path: entry.path,
3744
3997
  reachable: true,
@@ -3752,9 +4005,9 @@ function applyViewPlan(viewDir, toCreate) {
3752
4005
  const created = [];
3753
4006
  const failed = [];
3754
4007
  for (const { name, target } of toCreate) {
3755
- const filePath = join6(viewDir, name);
4008
+ const filePath = join7(viewDir, name);
3756
4009
  try {
3757
- mkdirSync(dirname(filePath), { recursive: true });
4010
+ mkdirSync(dirname2(filePath), { recursive: true });
3758
4011
  symlinkSync(target, filePath);
3759
4012
  created.push(name);
3760
4013
  } catch (error) {
@@ -3767,7 +4020,7 @@ var TOP_LEVEL_INSTRUCTION_FILES_LOWER = new Set(
3767
4020
  INSTRUCTION_FILES.filter((f) => !f.includes("/")).map((f) => f.toLowerCase())
3768
4021
  );
3769
4022
  function classifyViewLink(viewDir, name, rosterRealpaths) {
3770
- const filePath = join6(viewDir, name);
4023
+ const filePath = join7(viewDir, name);
3771
4024
  let isLink;
3772
4025
  try {
3773
4026
  isLink = lstatSync(filePath).isSymbolicLink();
@@ -3796,7 +4049,7 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
3796
4049
  if (!isDir) {
3797
4050
  return { target, kind: existsSync(resolved) ? "non-repo" : "broken" };
3798
4051
  }
3799
- return { target, kind: existsSync(join6(resolved, ".git")) ? "repo" : "non-repo" };
4052
+ return { target, kind: existsSync(join7(resolved, ".git")) ? "repo" : "non-repo" };
3800
4053
  }
3801
4054
  function gatherExistingViewLinks(viewDir, rosterRealpaths) {
3802
4055
  let names;
@@ -3821,7 +4074,7 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
3821
4074
  const pruned = [];
3822
4075
  const failed = [];
3823
4076
  for (const { name } of toPrune) {
3824
- const filePath = join6(viewDir, name);
4077
+ const filePath = join7(viewDir, name);
3825
4078
  const c = classifyViewLink(viewDir, name, rosterRealpaths);
3826
4079
  if (c === null || c.kind !== "repo") {
3827
4080
  failed.push({
@@ -4032,10 +4285,10 @@ async function runProjectPreset(options, ctx = {}) {
4032
4285
  }
4033
4286
  }
4034
4287
  function canonicalFileFor(anchorReal, canonicalName) {
4035
- return join6(anchorReal, "agents", canonicalName, CANONICAL_FILE);
4288
+ return join7(anchorReal, "agents", canonicalName, CANONICAL_FILE);
4036
4289
  }
4037
4290
  function canonicalLabelFor(canonicalName) {
4038
- return join6("agents", canonicalName, CANONICAL_FILE);
4291
+ return join7("agents", canonicalName, CANONICAL_FILE);
4039
4292
  }
4040
4293
  async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
4041
4294
  const declared = {
@@ -4053,7 +4306,7 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
4053
4306
  if (real === anchorReal) {
4054
4307
  return { ...declared, isAnchor: true, reachable: true, canonicalPresent: false };
4055
4308
  }
4056
- if (!existsSync(join6(real, ".git"))) {
4309
+ if (!existsSync(join7(real, ".git"))) {
4057
4310
  return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
4058
4311
  }
4059
4312
  const canonicalName = basename4(real);
@@ -4101,7 +4354,7 @@ async function applyPresetPlan(anchorReal, plan) {
4101
4354
  isLink = false;
4102
4355
  }
4103
4356
  if (isLink) throw new Error(`Canonical is a symlink in ${label}`);
4104
- if (plan.action === "create") mkdirSync(dirname(file), { recursive: true });
4357
+ if (plan.action === "create") mkdirSync(dirname2(file), { recursive: true });
4105
4358
  const existing = await readMarkdownFile4(file);
4106
4359
  await writeMarkdownFile5(file, renderWithMarkers4(existing, plan.desiredBlock, label));
4107
4360
  }
@@ -4284,24 +4537,24 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
4284
4537
  const instructionFiles = [];
4285
4538
  for (const name of INSTRUCTION_FILES) {
4286
4539
  try {
4287
- lstatSync(join6(real, name));
4540
+ lstatSync(join7(real, name));
4288
4541
  instructionFiles.push(name);
4289
4542
  } catch {
4290
4543
  }
4291
4544
  }
4292
4545
  let ignored;
4293
4546
  try {
4294
- ignored = new Set(readGitignoreLines(join6(real, ".gitignore")).map((l) => l.trim()));
4547
+ ignored = new Set(readGitignoreLines(join7(real, ".gitignore")).map((l) => l.trim()));
4295
4548
  } catch {
4296
4549
  ignored = /* @__PURE__ */ new Set();
4297
4550
  }
4298
4551
  const gitignorePatterns = INSTRUCTION_FILES.filter((p) => ignored.has(p) || ignored.has(`/${p}`));
4299
- const canonical2 = existsSync(join6(anchorReal, "agents", canonicalName, CANONICAL_FILE));
4552
+ const canonical2 = existsSync(join7(anchorReal, "agents", canonicalName, CANONICAL_FILE));
4300
4553
  let viewLink = false;
4301
4554
  const viewPath = manifest.workspace.view;
4302
4555
  if (viewPath !== void 0) {
4303
4556
  try {
4304
- lstatSync(join6(resolveViewDir(repositoryRoot, viewPath), canonicalName));
4557
+ lstatSync(join7(resolveViewDir(repositoryRoot, viewPath), canonicalName));
4305
4558
  viewLink = true;
4306
4559
  } catch {
4307
4560
  }
@@ -4463,12 +4716,12 @@ function gatherRenameWiring(repositoryRoot, manifest, oldBasename) {
4463
4716
  } catch {
4464
4717
  return { canonicalDirOld: false, viewLinkOld: false };
4465
4718
  }
4466
- const canonicalDirOld = existsSync(join6(anchorReal, "agents", oldBasename));
4719
+ const canonicalDirOld = existsSync(join7(anchorReal, "agents", oldBasename));
4467
4720
  let viewLinkOld = false;
4468
4721
  const viewPath = manifest.workspace.view;
4469
4722
  if (viewPath !== void 0) {
4470
4723
  try {
4471
- lstatSync(join6(resolveViewDir(repositoryRoot, viewPath), oldBasename));
4724
+ lstatSync(join7(resolveViewDir(repositoryRoot, viewPath), oldBasename));
4472
4725
  viewLinkOld = true;
4473
4726
  } catch {
4474
4727
  }
@@ -4617,7 +4870,7 @@ import {
4617
4870
  // src/lib/durable-write.ts
4618
4871
  import { randomUUID } from "crypto";
4619
4872
  import { lstat, open, rename, stat as stat3, unlink as unlink2 } from "fs/promises";
4620
- import { basename as basename5, dirname as dirname2, join as join7 } from "path";
4873
+ import { basename as basename5, dirname as dirname3, join as join8 } from "path";
4621
4874
  async function assertNotSymlink(targetPath) {
4622
4875
  try {
4623
4876
  const st = await lstat(targetPath);
@@ -4632,8 +4885,8 @@ async function assertNotSymlink(targetPath) {
4632
4885
  }
4633
4886
  }
4634
4887
  async function writeFileDurable(targetPath, content) {
4635
- const dir = dirname2(targetPath);
4636
- const tmpPath = join7(dir, `.${basename5(targetPath)}.tmp.${randomUUID()}`);
4888
+ const dir = dirname3(targetPath);
4889
+ const tmpPath = join8(dir, `.${basename5(targetPath)}.tmp.${randomUUID()}`);
4637
4890
  let mode = 420;
4638
4891
  try {
4639
4892
  mode = (await stat3(targetPath)).mode & 511;
@@ -4669,15 +4922,15 @@ async function writeFileDurable(targetPath, content) {
4669
4922
 
4670
4923
  // src/lib/protocols-config.ts
4671
4924
  import { homedir as homedir6 } from "os";
4672
- import { isAbsolute as isAbsolute4, join as join8, resolve as resolve8 } from "path";
4925
+ import { isAbsolute as isAbsolute4, join as join9, resolve as resolve8 } from "path";
4673
4926
  import { readYamlFile as readYamlFile5 } from "@basou/core";
4674
- var DEFAULT_PROTOCOLS_CONFIG_PATH = join8(homedir6(), ".basou", "protocols.yaml");
4675
- var DEFAULT_TARGET_PATH = join8(homedir6(), ".claude", "CLAUDE.md");
4927
+ var DEFAULT_PROTOCOLS_CONFIG_PATH = join9(homedir6(), ".basou", "protocols.yaml");
4928
+ var DEFAULT_TARGET_PATH = join9(homedir6(), ".claude", "CLAUDE.md");
4676
4929
  var ALLOWED_TOP_KEYS = /* @__PURE__ */ new Set(["version", "protocols"]);
4677
4930
  var ALLOWED_ENTRY_KEYS = /* @__PURE__ */ new Set(["source", "title"]);
4678
4931
  function expandTilde3(p) {
4679
4932
  if (p === "~") return homedir6();
4680
- if (p.startsWith("~/")) return join8(homedir6(), p.slice(2));
4933
+ if (p.startsWith("~/")) return join9(homedir6(), p.slice(2));
4681
4934
  return p;
4682
4935
  }
4683
4936
  function isRecord3(value) {
@@ -4930,15 +5183,15 @@ import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
4930
5183
  // src/commands/refresh-watch.ts
4931
5184
  import { readdir as readdir2, stat as stat4 } from "fs/promises";
4932
5185
  import { homedir as homedir7 } from "os";
4933
- import { join as join9 } from "path";
5186
+ import { join as join10 } from "path";
4934
5187
  import { findErrorCode as findErrorCode8 } from "@basou/core";
4935
5188
  var DEFAULT_WATCH_INTERVAL_SEC = 30;
4936
5189
  var MIN_WATCH_INTERVAL_SEC = 5;
4937
5190
  var MAX_WATCH_INTERVAL_SEC = 86400;
4938
5191
  function watchedRoots(ctx) {
4939
5192
  return [
4940
- ctx.codexSessionsDir ?? join9(homedir7(), ".codex", "sessions"),
4941
- ctx.claudeProjectsDir ?? join9(homedir7(), ".claude", "projects")
5193
+ ctx.codexSessionsDir ?? join10(homedir7(), ".codex", "sessions"),
5194
+ ctx.claudeProjectsDir ?? join10(homedir7(), ".claude", "projects")
4942
5195
  ];
4943
5196
  }
4944
5197
  async function scanSourceLogs(roots) {
@@ -4952,7 +5205,7 @@ async function scanSourceLogs(roots) {
4952
5205
  throw new Error("Failed to read a source log directory", { cause: error });
4953
5206
  }
4954
5207
  for (const entry of entries) {
4955
- const full = join9(dir, entry.name);
5208
+ const full = join10(dir, entry.name);
4956
5209
  if (entry.isDirectory()) {
4957
5210
  await walk(full);
4958
5211
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
@@ -5474,7 +5727,7 @@ function renderReviewGaps(summary) {
5474
5727
  // src/commands/run.ts
5475
5728
  import { mkdir as mkdir2 } from "fs/promises";
5476
5729
  import { homedir as homedir8 } from "os";
5477
- import { join as join10 } from "path";
5730
+ import { join as join11 } from "path";
5478
5731
  import {
5479
5732
  acquireLock as acquireLock5,
5480
5733
  assertBasouRootSafe as assertBasouRootSafe11,
@@ -5527,13 +5780,13 @@ async function runClaudeCode(args, options, ctx = {}) {
5527
5780
  await assertBasouRootSafe11(paths.root);
5528
5781
  const manifest = await readManifest7(paths);
5529
5782
  const sessionId = prefixedUlid4("ses");
5530
- const sessionDir = join10(paths.sessions, sessionId);
5783
+ const sessionDir = join11(paths.sessions, sessionId);
5531
5784
  await mkdir2(sessionDir, { recursive: true });
5532
5785
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
5533
5786
  await coreAppendChainedEvent2(paths, sessionId, event);
5534
5787
  });
5535
5788
  const startedAt = now().toISOString();
5536
- const sessionYamlPath = join10(sessionDir, "session.yaml");
5789
+ const sessionYamlPath = join11(sessionDir, "session.yaml");
5537
5790
  const session = buildInitialSession2({
5538
5791
  id: sessionId,
5539
5792
  command,
@@ -5876,7 +6129,7 @@ async function resolveRepositoryRootForRun(cwd) {
5876
6129
 
5877
6130
  // src/commands/session.ts
5878
6131
  import { readFile as readFile4 } from "fs/promises";
5879
- import { basename as basename6, isAbsolute as isAbsolute6, join as join11, relative as relative3 } from "path";
6132
+ import { basename as basename6, isAbsolute as isAbsolute6, join as join12, relative as relative3 } from "path";
5880
6133
  import {
5881
6134
  acquireLock as acquireLock6,
5882
6135
  appendEventToExistingSession as appendEventToExistingSession3,
@@ -5885,7 +6138,7 @@ import {
5885
6138
  enumerateSessionDirs as enumerateSessionDirs2,
5886
6139
  findErrorCode as findErrorCode11,
5887
6140
  importSessionFromJson as importSessionFromJson2,
5888
- loadSessionEntries,
6141
+ loadSessionEntries as loadSessionEntries2,
5889
6142
  readAllEvents,
5890
6143
  readManifest as readManifest8,
5891
6144
  readYamlFile as readYamlFile7,
@@ -5949,7 +6202,7 @@ async function doRunSessionList(options, ctx) {
5949
6202
  const paths = basouPaths15(repositoryRoot);
5950
6203
  await assertWorkspaceInitialized10(paths.root);
5951
6204
  const now = /* @__PURE__ */ new Date();
5952
- const records = (await loadSessionEntries(paths, {
6205
+ const records = (await loadSessionEntries2(paths, {
5953
6206
  now,
5954
6207
  onWarning: (w, sid) => printReplayWarning(w, sid),
5955
6208
  onSkip: (sid, reason) => printSessionListSkip(sid, reason)
@@ -6001,8 +6254,8 @@ async function doRunSessionShow(idInput, options, ctx) {
6001
6254
  const paths = basouPaths15(repositoryRoot);
6002
6255
  await assertWorkspaceInitialized10(paths.root);
6003
6256
  const sessionId = await resolveSessionId3(paths, idInput);
6004
- const sessionDir = join11(paths.sessions, sessionId);
6005
- const sessionYamlPath = join11(sessionDir, "session.yaml");
6257
+ const sessionDir = join12(paths.sessions, sessionId);
6258
+ const sessionYamlPath = join12(sessionDir, "session.yaml");
6006
6259
  let session;
6007
6260
  try {
6008
6261
  const raw = await readYamlFile7(sessionYamlPath);
@@ -6177,6 +6430,11 @@ function eventVariantSummary(ev) {
6177
6430
  return `approval=${ev.approval_id}`;
6178
6431
  case "decision_recorded":
6179
6432
  return ev.title;
6433
+ case "decision_voided": {
6434
+ const sup = ev.superseded_by !== void 0 ? ` superseded by ${ev.superseded_by}` : "";
6435
+ const reason = typeof ev.reason === "string" && ev.reason.length > 0 ? `: ${ev.reason}` : "";
6436
+ return `voided ${ev.decision_id}${reason}${sup}`;
6437
+ }
6180
6438
  case "task_created":
6181
6439
  return ev.title;
6182
6440
  case "task_status_changed":
@@ -6740,7 +6998,7 @@ async function resolveRepositoryRootForStatus(cwd) {
6740
6998
 
6741
6999
  // src/commands/task.ts
6742
7000
  import { readFile as readFile5 } from "fs/promises";
6743
- import { join as join12 } from "path";
7001
+ import { join as join13 } from "path";
6744
7002
  import {
6745
7003
  archiveTask,
6746
7004
  assertBasouRootSafe as assertBasouRootSafe15,
@@ -6750,7 +7008,7 @@ import {
6750
7008
  editTask,
6751
7009
  enumerateArchivedTaskIds,
6752
7010
  findErrorCode as findErrorCode14,
6753
- loadSessionEntries as loadSessionEntries2,
7011
+ loadSessionEntries as loadSessionEntries3,
6754
7012
  loadTaskEntries,
6755
7013
  prefixedUlid as prefixedUlid5,
6756
7014
  readManifest as readManifest10,
@@ -6759,7 +7017,7 @@ import {
6759
7017
  reconcileAllTasks,
6760
7018
  reconcileTask,
6761
7019
  refreshTaskLinkedSessions,
6762
- replayEvents as replayEvents2,
7020
+ replayEvents as replayEvents3,
6763
7021
  resolveRepositoryRoot as resolveRepositoryRoot12,
6764
7022
  resolveSessionId as resolveSessionId4,
6765
7023
  resolveTaskId as resolveTaskId2,
@@ -7063,13 +7321,13 @@ async function doRunTaskShow(idInput, options, ctx) {
7063
7321
  await assertWorkspaceInitialized12(paths.root);
7064
7322
  const taskId = await resolveTaskId2(paths, idInput, { includeArchived: true });
7065
7323
  const { doc, archived } = await readTaskFileWithArchiveFallback(paths, taskId);
7066
- const sessions = await loadSessionEntries2(paths, { now: /* @__PURE__ */ new Date() });
7324
+ const sessions = await loadSessionEntries3(paths, { now: /* @__PURE__ */ new Date() });
7067
7325
  const events = [];
7068
7326
  const linkedSessionIds = new Set(doc.task.task.linked_sessions);
7069
7327
  for (const s of sessions) {
7070
- const sessionDir = join12(paths.sessions, s.sessionId);
7328
+ const sessionDir = join13(paths.sessions, s.sessionId);
7071
7329
  try {
7072
- for await (const ev of replayEvents2(sessionDir, {
7330
+ for await (const ev of replayEvents3(sessionDir, {
7073
7331
  onWarning: (w) => printReplayWarning(w, s.sessionId)
7074
7332
  })) {
7075
7333
  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) {
@@ -7983,7 +8241,7 @@ import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
7983
8241
  // src/lib/portfolio-safety.ts
7984
8242
  import { execFile } from "child_process";
7985
8243
  import { lstat as lstat2, realpath as realpath2 } from "fs/promises";
7986
- import { isAbsolute as isAbsolute7, join as join13, relative as relative4, resolve as resolve10 } from "path";
8244
+ import { isAbsolute as isAbsolute7, join as join14, relative as relative4, resolve as resolve10 } from "path";
7987
8245
  import { promisify } from "util";
7988
8246
  import { readManifest as readManifest11 } from "@basou/core";
7989
8247
  var execFileAsync = promisify(execFile);
@@ -8007,7 +8265,7 @@ function isBasouPath(p) {
8007
8265
  async function inspectRepo(repoPath) {
8008
8266
  let hasEntry = false;
8009
8267
  try {
8010
- await lstat2(join13(repoPath, ".basou"));
8268
+ await lstat2(join14(repoPath, ".basou"));
8011
8269
  hasEntry = true;
8012
8270
  } catch (error) {
8013
8271
  if (errorCode(error) !== "ENOENT") {
@@ -8108,14 +8366,14 @@ function formatSafetyReport(result) {
8108
8366
 
8109
8367
  // src/lib/view-server.ts
8110
8368
  import { createServer } from "http";
8111
- import { join as join14 } from "path";
8369
+ import { join as join15 } from "path";
8112
8370
  import {
8113
8371
  computeWorkStats as computeWorkStats2,
8114
8372
  enumerateApprovals as enumerateApprovals2,
8115
8373
  findErrorCode as findErrorCode16,
8116
8374
  isLazyExpired as isLazyExpired2,
8117
8375
  loadApproval as loadApproval2,
8118
- loadSessionEntries as loadSessionEntries3,
8376
+ loadSessionEntries as loadSessionEntries4,
8119
8377
  loadTaskEntries as loadTaskEntries2,
8120
8378
  readAllEvents as readAllEvents2,
8121
8379
  readManifest as readManifest12,
@@ -8660,6 +8918,10 @@ var VIEW_HTML = `<!doctype html>
8660
8918
  }
8661
8919
  if (ev.type === 'file_changed') return ev.path + ' [' + ev.change_type + ']';
8662
8920
  if (ev.type === 'decision_recorded') return ev.title || '';
8921
+ if (ev.type === 'decision_voided') {
8922
+ var vs = ev.superseded_by ? ' superseded by ' + ev.superseded_by : '';
8923
+ return 'voided ' + ev.decision_id + (ev.reason ? ': ' + ev.reason : '') + vs;
8924
+ }
8663
8925
  return '';
8664
8926
  }
8665
8927
 
@@ -9037,7 +9299,7 @@ async function overview(ws, nowProvider) {
9037
9299
  };
9038
9300
  }
9039
9301
  async function sessionsList(ws, nowProvider) {
9040
- const entries = await loadSessionEntries3(ws.paths, { now: nowProvider() });
9302
+ const entries = await loadSessionEntries4(ws.paths, { now: nowProvider() });
9041
9303
  const sessions = entries.map((entry) => ({
9042
9304
  sessionId: entry.sessionId,
9043
9305
  label: entry.session.session.label ?? null,
@@ -9063,7 +9325,7 @@ async function sessionDetail(ws, sessionId) {
9063
9325
  throw error;
9064
9326
  }
9065
9327
  try {
9066
- const events = await readAllEvents2(join14(ws.paths.sessions, sessionId));
9328
+ const events = await readAllEvents2(join15(ws.paths.sessions, sessionId));
9067
9329
  return { session, events };
9068
9330
  } catch {
9069
9331
  return { session, events: [], degraded: true };