@basou/cli 0.68.4 → 0.70.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/program.js CHANGED
@@ -28,7 +28,8 @@ import {
28
28
  prefixedUlid,
29
29
  readSessionYaml,
30
30
  readYamlFile,
31
- replayEvents
31
+ replayEvents,
32
+ SessionFileNotAFileError
32
33
  } from "@basou/core";
33
34
 
34
35
  // src/lib/error-render.ts
@@ -126,7 +127,7 @@ function printSessionSkip(sid, reason) {
126
127
  const short = shortSessionId(sid);
127
128
  if (reason === "events_jsonl_unreadable") {
128
129
  console.error(`Warning: skipped suspect check for ${short}: events.jsonl unreadable`);
129
- } else if (reason === "session_dir_not_directory") {
130
+ } else if (reason === "session_dir_not_directory" || reason === "session_yaml_not_a_file") {
130
131
  console.error(`Skipped ${sid}: ${reason}`);
131
132
  } else {
132
133
  console.error(`Skipped ${short}: ${reason}`);
@@ -147,6 +148,11 @@ function printSessionListSkip(sid, reason) {
147
148
  case "session_dir_not_directory":
148
149
  console.error(`Skipped ${sid}: not a directory (a symlink or a file is not followed)`);
149
150
  break;
151
+ case "session_yaml_not_a_file":
152
+ console.error(
153
+ `Skipped ${sid}: session.yaml is not a regular file (a symlink or anything else is not followed)`
154
+ );
155
+ break;
150
156
  }
151
157
  }
152
158
  function printTaskSkip(taskId, reason) {
@@ -423,7 +429,7 @@ async function doRunApprovalShow(idInput, options, ctx) {
423
429
  `Warning: session ${sessionId} is not a directory (a symlink or a file is not followed); its events are not shown`
424
430
  );
425
431
  } else {
426
- for await (const ev of replayEvents(join(paths.sessions, sessionId), {
432
+ for await (const ev of replayEvents(paths, sessionId, {
427
433
  onWarning: (w) => printReplayWarning(w, sessionId)
428
434
  })) {
429
435
  if (isApprovalEvent(ev) && ev.approval_id === id) {
@@ -523,8 +529,7 @@ async function doRunApprovalResolve(idInput, options, ctx, decision) {
523
529
  const sessionLock = await acquireLock(paths, "session", approval.session_id);
524
530
  try {
525
531
  await assertSessionDirSafe(paths, approval.session_id);
526
- const sessionDir = join(paths.sessions, approval.session_id);
527
- for await (const ev of replayEvents(sessionDir, {
532
+ for await (const ev of replayEvents(paths, approval.session_id, {
528
533
  onWarning: (w) => printReplayWarning(w, approval.session_id)
529
534
  })) {
530
535
  if (isApprovalEvent(ev) && ev.approval_id === approval.id && (ev.type === "approval_approved" || ev.type === "approval_rejected" || ev.type === "approval_expired")) {
@@ -537,7 +542,8 @@ async function doRunApprovalResolve(idInput, options, ctx, decision) {
537
542
  let sessionStatus = null;
538
543
  try {
539
544
  sessionStatus = (await readSessionYaml(paths, approval.session_id)).session.status;
540
- } catch {
545
+ } catch (error) {
546
+ if (error instanceof SessionFileNotAFileError) throw error;
541
547
  sessionStatus = null;
542
548
  }
543
549
  const attachable = sessionStatus === "initialized" || sessionStatus === "running" || sessionStatus === "waiting_approval";
@@ -1046,6 +1052,7 @@ import {
1046
1052
  readSessionYaml as readSessionYaml2,
1047
1053
  reimportPreservingId,
1048
1054
  SESSION_IMPORT_SCHEMA_VERSION,
1055
+ SessionFileNotAFileError as SessionFileNotAFileError2,
1049
1056
  SessionImportPayloadSchema
1050
1057
  } from "@basou/core";
1051
1058
 
@@ -1219,10 +1226,11 @@ async function resolveImportTarget(ctx) {
1219
1226
  async function importDerivedSessions(paths, manifest, options, sourceKind, candidates, projectPaths, boundaryDeclared) {
1220
1227
  await assertSessionStoreSafe(paths);
1221
1228
  if (options.dryRun !== true) await assertLockStoreSafe2(paths);
1222
- const { byExternalId: existingByExternalId, notDirectories } = await loadExistingByExternalId(
1223
- paths,
1224
- sourceKind
1225
- );
1229
+ const {
1230
+ byExternalId: existingByExternalId,
1231
+ notDirectories,
1232
+ yamlNotFiles
1233
+ } = await loadExistingByExternalId(paths, sourceKind);
1226
1234
  let notDirectoriesWarned = false;
1227
1235
  const seenThisRun = /* @__PURE__ */ new Set();
1228
1236
  const crossProjectCheck = boundaryDeclared;
@@ -1315,12 +1323,20 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
1315
1323
  }
1316
1324
  counts.replaced++;
1317
1325
  }
1318
- if (priors.length === 0 && notDirectories.length > 0 && !notDirectoriesWarned) {
1326
+ if (priors.length === 0 && (notDirectories.length > 0 || yamlNotFiles.length > 0) && !notDirectoriesWarned) {
1319
1327
  notDirectoriesWarned = true;
1320
- const one = notDirectories.length === 1;
1321
- console.error(
1322
- `Import: ${notDirectories.join(", ")} ${one ? "is not a directory" : "are not directories"} (a symlink or a file is not followed), so ${one ? "it was" : "they were"} not checked for an earlier import; a session imported there may be imported again`
1323
- );
1328
+ if (notDirectories.length > 0) {
1329
+ const one = notDirectories.length === 1;
1330
+ console.error(
1331
+ `Import: ${notDirectories.join(", ")} ${one ? "is not a directory" : "are not directories"} (a symlink or a file is not followed), so ${one ? "it was" : "they were"} not checked for an earlier import; a session imported there may be imported again`
1332
+ );
1333
+ }
1334
+ if (yamlNotFiles.length > 0) {
1335
+ const one = yamlNotFiles.length === 1;
1336
+ console.error(
1337
+ `Import: the session.yaml of ${yamlNotFiles.join(", ")} ${one ? "is" : "are"} not a regular file (a symlink or anything else is not followed), so ${one ? "it was" : "they were"} not checked for an earlier import; a session imported there may be imported again`
1338
+ );
1339
+ }
1324
1340
  }
1325
1341
  const result = await importSessionFromJson(paths, manifest, payload, {
1326
1342
  dryRun: options.dryRun === true
@@ -1402,13 +1418,15 @@ async function loadExistingByExternalId(paths, sourceKind) {
1402
1418
  try {
1403
1419
  ({ dirs: sessionIds, notDirectories } = await enumerateSessionEntries(paths));
1404
1420
  } catch {
1405
- return { byExternalId, notDirectories: [] };
1421
+ return { byExternalId, notDirectories: [], yamlNotFiles: [] };
1406
1422
  }
1423
+ const yamlNotFiles = [];
1407
1424
  for (const sessionId of sessionIds) {
1408
1425
  let session;
1409
1426
  try {
1410
1427
  session = await readSessionYaml2(paths, sessionId);
1411
- } catch {
1428
+ } catch (error) {
1429
+ if (error instanceof SessionFileNotAFileError2) yamlNotFiles.push(sessionId);
1412
1430
  continue;
1413
1431
  }
1414
1432
  if (session.session.source.kind !== sourceKind) continue;
@@ -1423,7 +1441,7 @@ async function loadExistingByExternalId(paths, sourceKind) {
1423
1441
  const match = typeof label === "string" ? label.match(/^claude-code import (\S+)$/) : null;
1424
1442
  if (match?.[1] !== void 0) add(match[1], prior);
1425
1443
  }
1426
- return { byExternalId, notDirectories };
1444
+ return { byExternalId, notDirectories, yamlNotFiles };
1427
1445
  }
1428
1446
  async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
1429
1447
  if (options.session !== void 0) {
@@ -2956,9 +2974,8 @@ async function doRunChannelClear(face, options) {
2956
2974
  }
2957
2975
 
2958
2976
  // src/commands/decision.ts
2959
- import { readFile as readFile3 } from "fs/promises";
2960
2977
  import { homedir as homedir3 } from "os";
2961
- import { join as join7, resolve as resolve4 } from "path";
2978
+ import { resolve as resolve4 } from "path";
2962
2979
  import {
2963
2980
  AGENT_INFRA_DIRS as AGENT_INFRA_DIRS2,
2964
2981
  acquireLock as acquireLock2,
@@ -2969,7 +2986,7 @@ import {
2969
2986
  createAdHocSessionWithEvent,
2970
2987
  displayPath as displayPath4,
2971
2988
  EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION2,
2972
- findErrorCode as findErrorCode4,
2989
+ findErrorCode as findErrorCode5,
2973
2990
  isValidPrefixedId,
2974
2991
  LOCAL_CLI_EVENT_SOURCE as LOCAL_CLI_EVENT_SOURCE2,
2975
2992
  loadSessionEntries,
@@ -2981,6 +2998,35 @@ import {
2981
2998
  } from "@basou/core";
2982
2999
  import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
2983
3000
 
3001
+ // src/lib/input-file.ts
3002
+ import { readFile as readFile3 } from "fs/promises";
3003
+ import { findErrorCode as findErrorCode4 } from "@basou/core";
3004
+ function refuseDashInputFile(flag, value, alternative) {
3005
+ if (value !== "-") return;
3006
+ if (alternative === "stdin") {
3007
+ throw new Error(
3008
+ `${flag} - is refused: - is reserved for standard input. Omit ${flag} to read stdin, or pass ${flag} ./- to read a file named -.`
3009
+ );
3010
+ }
3011
+ const toFile = "textFlag" in alternative ? `Write the text to a file and pass ${flag} <path>, or pass it with ${alternative.textFlag}` : `Write ${alternative.fileOnly} to a file and pass its path with ${flag}`;
3012
+ throw new Error(
3013
+ `${flag} - is refused: - is reserved for standard input, which this command does not read. ${toFile}; a file named - is read with ${flag} ./-.`
3014
+ );
3015
+ }
3016
+ async function readInputFileText(flag, path) {
3017
+ try {
3018
+ return await readFile3(path, "utf8");
3019
+ } catch (error) {
3020
+ if (findErrorCode4(error, "ENOENT")) {
3021
+ throw new Error(`${flag} names a file that does not exist`, { cause: error });
3022
+ }
3023
+ if (findErrorCode4(error, "EISDIR")) {
3024
+ throw new Error(`${flag} names a directory, not a file`, { cause: error });
3025
+ }
3026
+ throw new Error(`Could not read the file ${flag} names`, { cause: error });
3027
+ }
3028
+ }
3029
+
2984
3030
  // src/commands/decision-gaps.ts
2985
3031
  import {
2986
3032
  basouPaths as basouPaths5,
@@ -3348,6 +3394,7 @@ async function runDecisionCapture(options, ctx = {}) {
3348
3394
  }
3349
3395
  }
3350
3396
  async function doRunDecisionCapture(options, ctx) {
3397
+ refuseDashInputFile("--file", options.file, "stdin");
3351
3398
  const cwd = ctx.cwd ?? process.cwd();
3352
3399
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "decision capture");
3353
3400
  const paths = basouPaths6(repositoryRoot);
@@ -3509,9 +3556,8 @@ function isDecisionId(value) {
3509
3556
  async function decisionExists(paths, decisionId) {
3510
3557
  const entries = await loadSessionEntries(paths, { now: /* @__PURE__ */ new Date() });
3511
3558
  for (const entry of entries) {
3512
- const sessionDir = join7(paths.sessions, entry.sessionId);
3513
3559
  try {
3514
- for await (const ev of replayEvents2(sessionDir, {})) {
3560
+ for await (const ev of replayEvents2(paths, entry.sessionId, {})) {
3515
3561
  if (ev.type === "decision_recorded" && ev.decision_id === decisionId) return true;
3516
3562
  }
3517
3563
  } catch {
@@ -3563,14 +3609,7 @@ function parseReason(raw) {
3563
3609
  }
3564
3610
  async function readCaptureInput(options, ctx) {
3565
3611
  if (options.file !== void 0) {
3566
- try {
3567
- return await readFile3(options.file, "utf8");
3568
- } catch (error) {
3569
- if (findErrorCode4(error, "ENOENT")) {
3570
- throw new Error(`Input file not found: ${options.file}`);
3571
- }
3572
- throw error;
3573
- }
3612
+ return await readInputFileText("--file", options.file);
3574
3613
  }
3575
3614
  if (ctx.readInput !== void 0) {
3576
3615
  return await ctx.readInput();
@@ -3909,7 +3948,7 @@ async function assertWorkspaceInitialized4(basouRoot) {
3909
3948
  try {
3910
3949
  await assertBasouRootSafe4(basouRoot);
3911
3950
  } catch (error) {
3912
- if (findErrorCode4(error, "ENOENT")) {
3951
+ if (findErrorCode5(error, "ENOENT")) {
3913
3952
  throw new Error("Workspace not initialized. Run 'basou init' first.");
3914
3953
  }
3915
3954
  throw error;
@@ -3920,7 +3959,7 @@ async function assertWorkspaceInitialized4(basouRoot) {
3920
3959
  import {
3921
3960
  assertBasouRootSafe as assertBasouRootSafe5,
3922
3961
  basouPaths as basouPaths7,
3923
- findErrorCode as findErrorCode5,
3962
+ findErrorCode as findErrorCode6,
3924
3963
  readMarkdownFile as readMarkdownFile4,
3925
3964
  renderDecisions as renderDecisions2,
3926
3965
  renderWithMarkers as renderWithMarkers2,
@@ -3962,7 +4001,7 @@ async function assertWorkspaceInitialized5(basouRoot) {
3962
4001
  try {
3963
4002
  await assertBasouRootSafe5(basouRoot);
3964
4003
  } catch (error) {
3965
- if (findErrorCode5(error, "ENOENT")) {
4004
+ if (findErrorCode6(error, "ENOENT")) {
3966
4005
  throw new Error("Workspace not initialized. Run 'basou init' first.");
3967
4006
  }
3968
4007
  throw error;
@@ -3972,7 +4011,7 @@ async function assertWorkspaceInitialized5(basouRoot) {
3972
4011
  // src/commands/exec.ts
3973
4012
  import { mkdir as mkdir2, rm as rm2 } from "fs/promises";
3974
4013
  import { homedir as homedir4 } from "os";
3975
- import { join as join8 } from "path";
4014
+ import { join as join7 } from "path";
3976
4015
  import {
3977
4016
  acquireLock as acquireLock3,
3978
4017
  assertBasouRootSafe as assertBasouRootSafe6,
@@ -3983,17 +4022,37 @@ import {
3983
4022
  EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION3,
3984
4023
  finalizeSessionYaml,
3985
4024
  getSnapshot,
4025
+ lstatSessionFile,
3986
4026
  overwriteYamlFile,
3987
4027
  parseDuration,
3988
4028
  prefixedUlid as prefixedUlid3,
3989
4029
  readManifest as readManifest6,
3990
4030
  readYamlFile as readYamlFile2,
3991
4031
  SESSION_SCHEMA_VERSION,
4032
+ SessionFileNotAFileError as SessionFileNotAFileError3,
3992
4033
  SessionSchema,
3993
4034
  sanitizeWorkingDirectory,
3994
4035
  writeObservedDuration,
3995
4036
  writeYamlFile
3996
4037
  } from "@basou/core";
4038
+
4039
+ // src/lib/signal-exit-code.ts
4040
+ import { constants } from "os";
4041
+ var PROMISED_SIGNAL_NUMBERS = {
4042
+ SIGHUP: 1,
4043
+ SIGINT: 2,
4044
+ SIGQUIT: 3,
4045
+ SIGKILL: 9,
4046
+ SIGTERM: 15
4047
+ };
4048
+ var UNNUMBERED_SIGNAL_EXIT_CODE = 255;
4049
+ function signalToExitCode(sig) {
4050
+ if (sig === null) return 1;
4051
+ const num = PROMISED_SIGNAL_NUMBERS[sig] ?? constants.signals[sig];
4052
+ return num === void 0 ? UNNUMBERED_SIGNAL_EXIT_CODE : 128 + num;
4053
+ }
4054
+
4055
+ // src/commands/exec.ts
3997
4056
  function registerExecCommand(program) {
3998
4057
  program.command("exec <command> [args...]").description("Execute a command and record it as a Basou session").passThroughOptions().option("--timeout <duration>", "Kill the child after this duration (e.g. 30s, 5m, 1h)").option("--no-snapshot", "Skip git_snapshot before/after the command").option("--cwd <path>", "Run from a Basou root other than process.cwd()").option("-v, --verbose", "Show error causes").action(async (command, args, options) => {
3999
4058
  try {
@@ -4016,12 +4075,12 @@ async function runExec(command, args, options, ctx = {}) {
4016
4075
  const manifest = await readManifest6(paths);
4017
4076
  await assertSessionStoreSafe2(paths);
4018
4077
  const sessionId = prefixedUlid3("ses");
4019
- const sessionDir = join8(paths.sessions, sessionId);
4078
+ const sessionDir = join7(paths.sessions, sessionId);
4020
4079
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
4021
4080
  await coreAppendChainedEvent(paths, sessionId, event);
4022
4081
  });
4023
4082
  const startedAt = now().toISOString();
4024
- const sessionYamlPath = join8(sessionDir, "session.yaml");
4083
+ const sessionYamlPath = join7(sessionDir, "session.yaml");
4025
4084
  const session = buildInitialSession({
4026
4085
  id: sessionId,
4027
4086
  command,
@@ -4070,7 +4129,7 @@ async function runExec(command, args, options, ctx = {}) {
4070
4129
  });
4071
4130
  const runningLock = await acquireLock3(paths, "session", sessionId);
4072
4131
  try {
4073
- await mutateSessionYaml(sessionYamlPath, (s) => {
4132
+ await mutateSessionYaml(paths, sessionId, (s) => {
4074
4133
  s.session.status = "running";
4075
4134
  });
4076
4135
  } finally {
@@ -4190,18 +4249,6 @@ function decideFinalStatus(result, signalReceived) {
4190
4249
  if (result.exit_code === 0) return "completed";
4191
4250
  return "failed";
4192
4251
  }
4193
- var SIGNUM_MAP = {
4194
- SIGHUP: 1,
4195
- SIGINT: 2,
4196
- SIGQUIT: 3,
4197
- SIGKILL: 9,
4198
- SIGTERM: 15
4199
- };
4200
- function signalToExitCode(sig) {
4201
- if (sig === null) return 1;
4202
- const num = SIGNUM_MAP[sig] ?? 1;
4203
- return 128 + num;
4204
- }
4205
4252
  async function tryAppendGitSnapshot(sessionDir, sessionId, repoRoot, now, appendEvent) {
4206
4253
  let snapshot;
4207
4254
  try {
@@ -4259,7 +4306,14 @@ function buildInitialSession(input) {
4259
4306
  }
4260
4307
  };
4261
4308
  }
4262
- async function mutateSessionYaml(filePath, mutator) {
4309
+ async function mutateSessionYaml(paths, sessionId, mutator) {
4310
+ const kind = await lstatSessionFile(paths, sessionId, "session.yaml").catch((error) => {
4311
+ throw new Error("Failed to read YAML file", { cause: error });
4312
+ });
4313
+ if (kind === "symlink" || kind === "not_a_file") {
4314
+ throw new SessionFileNotAFileError3(sessionId, "session.yaml", kind);
4315
+ }
4316
+ const filePath = join7(paths.sessions, sessionId, "session.yaml");
4263
4317
  const raw = await readYamlFile2(filePath);
4264
4318
  const parsed = SessionSchema.parse(raw);
4265
4319
  mutator(parsed);
@@ -4313,7 +4367,7 @@ async function finalizeSessionAsFailed(paths, sessionDir, sessionId, appendEvent
4313
4367
  import {
4314
4368
  assertBasouRootSafe as assertBasouRootSafe7,
4315
4369
  basouPaths as basouPaths9,
4316
- findErrorCode as findErrorCode6,
4370
+ findErrorCode as findErrorCode7,
4317
4371
  readMarkdownFile as readMarkdownFile5,
4318
4372
  renderHandoff as renderHandoff2,
4319
4373
  renderWithMarkers as renderWithMarkers3,
@@ -4371,7 +4425,7 @@ async function assertWorkspaceInitialized6(basouRoot) {
4371
4425
  try {
4372
4426
  await assertBasouRootSafe7(basouRoot);
4373
4427
  } catch (error) {
4374
- if (findErrorCode6(error, "ENOENT")) {
4428
+ if (findErrorCode7(error, "ENOENT")) {
4375
4429
  throw new Error("Workspace not initialized. Run 'basou init' first.");
4376
4430
  }
4377
4431
  throw error;
@@ -4382,7 +4436,7 @@ async function assertWorkspaceInitialized6(basouRoot) {
4382
4436
  import { execFile } from "child_process";
4383
4437
  import { open as open2, readFile as readFile4, realpath as realpath4, stat as stat4 } from "fs/promises";
4384
4438
  import { homedir as homedir7 } from "os";
4385
- import { join as join11 } from "path";
4439
+ import { join as join10 } from "path";
4386
4440
  import { fileURLToPath } from "url";
4387
4441
  import { promisify } from "util";
4388
4442
  import {
@@ -4543,15 +4597,15 @@ function describeCodexHookTrust(trust) {
4543
4597
 
4544
4598
  // src/lib/protocols-config.ts
4545
4599
  import { homedir as homedir5 } from "os";
4546
- import { isAbsolute, join as join9, resolve as resolve5 } from "path";
4600
+ import { isAbsolute, join as join8, resolve as resolve5 } from "path";
4547
4601
  import { readYamlFile as readYamlFile3 } from "@basou/core";
4548
- var DEFAULT_PROTOCOLS_CONFIG_PATH = join9(homedir5(), ".basou", "protocols.yaml");
4549
- var DEFAULT_TARGET_PATH = join9(homedir5(), ".claude", "CLAUDE.md");
4602
+ var DEFAULT_PROTOCOLS_CONFIG_PATH = join8(homedir5(), ".basou", "protocols.yaml");
4603
+ var DEFAULT_TARGET_PATH = join8(homedir5(), ".claude", "CLAUDE.md");
4550
4604
  var ALLOWED_TOP_KEYS = /* @__PURE__ */ new Set(["version", "protocols"]);
4551
4605
  var ALLOWED_ENTRY_KEYS = /* @__PURE__ */ new Set(["source", "title"]);
4552
4606
  function expandTilde(p) {
4553
4607
  if (p === "~") return homedir5();
4554
- if (p.startsWith("~/")) return join9(homedir5(), p.slice(2));
4608
+ if (p.startsWith("~/")) return join8(homedir5(), p.slice(2));
4555
4609
  return p;
4556
4610
  }
4557
4611
  function isRecord(value) {
@@ -4622,19 +4676,19 @@ async function loadProtocolsConfig(configPath = DEFAULT_PROTOCOLS_CONFIG_PATH) {
4622
4676
  import {
4623
4677
  assertBasouRootSafe as assertBasouRootSafe8,
4624
4678
  basouPaths as basouPaths10,
4625
- findErrorCode as findErrorCode7,
4679
+ findErrorCode as findErrorCode8,
4626
4680
  renderOrientation as renderOrientation2,
4627
4681
  writeMarkdownFile as writeMarkdownFile4
4628
4682
  } from "@basou/core";
4629
4683
 
4630
4684
  // src/lib/hosts-config.ts
4631
4685
  import { homedir as homedir6 } from "os";
4632
- import { isAbsolute as isAbsolute2, join as join10, resolve as resolve6 } from "path";
4686
+ import { isAbsolute as isAbsolute2, join as join9, resolve as resolve6 } from "path";
4633
4687
  import { readYamlFile as readYamlFile4 } from "@basou/core";
4634
- var DEFAULT_HOSTS_CONFIG_PATH = join10(homedir6(), ".basou", "hosts.yaml");
4688
+ var DEFAULT_HOSTS_CONFIG_PATH = join9(homedir6(), ".basou", "hosts.yaml");
4635
4689
  function expandTilde2(p) {
4636
4690
  if (p === "~") return homedir6();
4637
- if (p.startsWith("~/")) return join10(homedir6(), p.slice(2));
4691
+ if (p.startsWith("~/")) return join9(homedir6(), p.slice(2));
4638
4692
  return p;
4639
4693
  }
4640
4694
  function isRecord2(value) {
@@ -4779,7 +4833,7 @@ async function assertWorkspaceInitialized7(basouRoot) {
4779
4833
  try {
4780
4834
  await assertBasouRootSafe8(basouRoot);
4781
4835
  } catch (error) {
4782
- if (findErrorCode7(error, "ENOENT")) {
4836
+ if (findErrorCode8(error, "ENOENT")) {
4783
4837
  throw new Error("Workspace not initialized. Run 'basou init' first.");
4784
4838
  }
4785
4839
  throw error;
@@ -5218,7 +5272,7 @@ function parseMinEdits(raw) {
5218
5272
  if (raw === void 0 || !/^\d+$/.test(raw)) return void 0;
5219
5273
  return Number(raw);
5220
5274
  }
5221
- var DEFAULT_CLAUDE_SETTINGS_PATH = join11(homedir7(), ".claude", "settings.json");
5275
+ var DEFAULT_CLAUDE_SETTINGS_PATH = join10(homedir7(), ".claude", "settings.json");
5222
5276
  function resolveCliEntry() {
5223
5277
  return fileURLToPath(import.meta.url);
5224
5278
  }
@@ -5575,7 +5629,7 @@ function describeHookMode(tiers) {
5575
5629
  const gates = tiers.review ? "capture + review" : "capture";
5576
5630
  return `${enforcement}, ${gates}`;
5577
5631
  }
5578
- var DEFAULT_CODEX_FACE_PATH = join11(homedir7(), ".codex", "AGENTS.md");
5632
+ var DEFAULT_CODEX_FACE_PATH = join10(homedir7(), ".codex", "AGENTS.md");
5579
5633
  var LEFTOVER_FACE_NOTE = (label) => `${label} still carries an orientation block rendered by an earlier basou (0.39 or before); every Codex session on this machine reads it. \`basou channel clear codex\` removes it.`;
5580
5634
  async function faceHasLeftoverOrientationBlock(facePath) {
5581
5635
  try {
@@ -5587,8 +5641,8 @@ async function faceHasLeftoverOrientationBlock(facePath) {
5587
5641
  return false;
5588
5642
  }
5589
5643
  }
5590
- var DEFAULT_CODEX_HOOKS_PATH = join11(homedir7(), ".codex", "hooks.json");
5591
- var DEFAULT_CODEX_CONFIG_PATH = join11(homedir7(), ".codex", "config.toml");
5644
+ var DEFAULT_CODEX_HOOKS_PATH = join10(homedir7(), ".codex", "hooks.json");
5645
+ var DEFAULT_CODEX_CONFIG_PATH = join10(homedir7(), ".codex", "config.toml");
5592
5646
  async function readHooksFile(path) {
5593
5647
  let raw;
5594
5648
  try {
@@ -5834,7 +5888,6 @@ function renderGitignoreWarning(error, verbose) {
5834
5888
  }
5835
5889
 
5836
5890
  // src/commands/note.ts
5837
- import { readFile as readFile5 } from "fs/promises";
5838
5891
  import { homedir as homedir8 } from "os";
5839
5892
  import { resolve as resolve8 } from "path";
5840
5893
  import {
@@ -5844,7 +5897,7 @@ import {
5844
5897
  basouPaths as basouPaths12,
5845
5898
  createAdHocSessionWithEvent as createAdHocSessionWithEvent2,
5846
5899
  EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION4,
5847
- findErrorCode as findErrorCode8,
5900
+ findErrorCode as findErrorCode9,
5848
5901
  LOCAL_CLI_EVENT_SOURCE as LOCAL_CLI_EVENT_SOURCE3,
5849
5902
  readManifest as readManifest8,
5850
5903
  resolveSessionId as resolveSessionId2,
@@ -6005,22 +6058,12 @@ async function readNoteBody(bodyArgument, options, ctx, cwd) {
6005
6058
  if (bodyArgument !== void 0 && options.file !== void 0) {
6006
6059
  throw new Error("Pass the note text either as an argument or with --file, not both.");
6007
6060
  }
6061
+ refuseDashInputFile("--file", options.file, "stdin");
6008
6062
  if (bodyArgument !== void 0) {
6009
6063
  return { body: bodyArgument, source: "argument" };
6010
6064
  }
6011
6065
  if (options.file !== void 0) {
6012
- let raw;
6013
- try {
6014
- raw = await readFile5(resolve8(cwd, options.file), "utf8");
6015
- } catch (error) {
6016
- if (findErrorCode8(error, "ENOENT")) {
6017
- throw new Error("--file names a file that does not exist", { cause: error });
6018
- }
6019
- if (findErrorCode8(error, "EISDIR")) {
6020
- throw new Error("--file names a directory, not a file", { cause: error });
6021
- }
6022
- throw new Error("Could not read the file --file names", { cause: error });
6023
- }
6066
+ const raw = await readInputFileText("--file", resolve8(cwd, options.file));
6024
6067
  return { body: dropTrailingNewlines(raw), source: "file" };
6025
6068
  }
6026
6069
  if (ctx.readInput !== void 0) {
@@ -6089,7 +6132,7 @@ async function assertWorkspaceInitialized8(basouRoot) {
6089
6132
  try {
6090
6133
  await assertBasouRootSafe9(basouRoot);
6091
6134
  } catch (error) {
6092
- if (findErrorCode8(error, "ENOENT")) {
6135
+ if (findErrorCode9(error, "ENOENT")) {
6093
6136
  throw new Error("Workspace not initialized. Run 'basou init' first.");
6094
6137
  }
6095
6138
  throw error;
@@ -6206,7 +6249,7 @@ import {
6206
6249
  writeFileSync,
6207
6250
  writeSync
6208
6251
  } from "fs";
6209
- import { basename as basename7, dirname as dirname7, isAbsolute as isAbsolute3, join as join12, relative as relative3, resolve as resolve9 } from "path";
6252
+ import { basename as basename7, dirname as dirname7, isAbsolute as isAbsolute3, join as join11, relative as relative3, resolve as resolve9 } from "path";
6210
6253
  import {
6211
6254
  appendBasouGitignore as appendBasouGitignore2,
6212
6255
  basouPaths as basouPaths13,
@@ -6587,7 +6630,7 @@ function classifySourceRoot(repositoryRoot, declaredPath) {
6587
6630
  } catch {
6588
6631
  return { path: declaredPath, kind: "unresolved" };
6589
6632
  }
6590
- return { path: declaredPath, kind: existsSync(join12(real, ".git")) ? "repo" : "non-repo" };
6633
+ return { path: declaredPath, kind: existsSync(join11(real, ".git")) ? "repo" : "non-repo" };
6591
6634
  }
6592
6635
  async function doRunProjectAdopt(options, ctx) {
6593
6636
  const cwd = ctx.cwd ?? process.cwd();
@@ -6691,7 +6734,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
6691
6734
  } catch {
6692
6735
  return { ...base, reachable: false, instructionFiles: [] };
6693
6736
  }
6694
- if (!existsSync(join12(real, ".git"))) {
6737
+ if (!existsSync(join11(real, ".git"))) {
6695
6738
  return { ...base, reachable: false, instructionFiles: [] };
6696
6739
  }
6697
6740
  try {
@@ -6699,7 +6742,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
6699
6742
  for (const name of INSTRUCTION_FILES) {
6700
6743
  let present = true;
6701
6744
  try {
6702
- lstatSync(join12(real, name));
6745
+ lstatSync(join11(real, name));
6703
6746
  } catch {
6704
6747
  present = false;
6705
6748
  }
@@ -6810,10 +6853,10 @@ function gatherRepoGitignore(repositoryRoot, entry) {
6810
6853
  } catch {
6811
6854
  return { ...base, reachable: false, currentLines: [] };
6812
6855
  }
6813
- if (!existsSync(join12(real, ".git"))) {
6856
+ if (!existsSync(join11(real, ".git"))) {
6814
6857
  return { ...base, reachable: false, currentLines: [] };
6815
6858
  }
6816
- return { ...base, reachable: true, currentLines: readGitignoreLines(join12(real, ".gitignore")) };
6859
+ return { ...base, reachable: true, currentLines: readGitignoreLines(join11(real, ".gitignore")) };
6817
6860
  }
6818
6861
  function hasErrorCode(error) {
6819
6862
  return error instanceof Error && typeof error.code === "string";
@@ -6827,7 +6870,7 @@ function readGitignoreLines(file) {
6827
6870
  }
6828
6871
  }
6829
6872
  function applyGitignorePlan(repositoryRoot, plan) {
6830
- const file = join12(realpathSync(resolve9(repositoryRoot, plan.path)), ".gitignore");
6873
+ const file = join11(realpathSync(resolve9(repositoryRoot, plan.path)), ".gitignore");
6831
6874
  let existing = "";
6832
6875
  try {
6833
6876
  existing = readFileSync(file, "utf8");
@@ -6967,7 +7010,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
6967
7010
  return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
6968
7011
  }
6969
7012
  if (real === anchorReal) {
6970
- const anchorCanonical = join12(real, CANONICAL_FILE);
7013
+ const anchorCanonical = join11(real, CANONICAL_FILE);
6971
7014
  const anchorState = anchorCanonicalState(anchorCanonical);
6972
7015
  if (anchorState === "absent") {
6973
7016
  return { ...base, isAnchor: true, reachable: true, canonicalPresent: false, files: [] };
@@ -6987,7 +7030,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
6987
7030
  anchorCanonical,
6988
7031
  "self"
6989
7032
  ).map((spec) => {
6990
- const { state, actualTarget } = inspectSymlink(join12(real, spec.name), spec.target);
7033
+ const { state, actualTarget } = inspectSymlink(join11(real, spec.name), spec.target);
6991
7034
  return {
6992
7035
  name: spec.name,
6993
7036
  expectedTarget: spec.target,
@@ -7004,16 +7047,16 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
7004
7047
  files: anchorFiles
7005
7048
  };
7006
7049
  }
7007
- if (!existsSync(join12(real, ".git"))) {
7050
+ if (!existsSync(join11(real, ".git"))) {
7008
7051
  return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
7009
7052
  }
7010
- const canonicalFile = isSelf ? join12(real, CANONICAL_FILE) : join12(anchorReal, "agents", basename7(real), CANONICAL_FILE);
7053
+ const canonicalFile = isSelf ? join11(real, CANONICAL_FILE) : join11(anchorReal, "agents", basename7(real), CANONICAL_FILE);
7011
7054
  if (!existsSync(canonicalFile)) {
7012
7055
  return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
7013
7056
  }
7014
7057
  const files = expectedSymlinkTargets(real, canonicalFile, mode).map(
7015
7058
  (spec) => {
7016
- const { state, actualTarget } = inspectSymlink(join12(real, spec.name), spec.target);
7059
+ const { state, actualTarget } = inspectSymlink(join11(real, spec.name), spec.target);
7017
7060
  return {
7018
7061
  name: spec.name,
7019
7062
  expectedTarget: spec.target,
@@ -7042,7 +7085,7 @@ function applySymlinkPlan(repositoryRoot, plan) {
7042
7085
  const created = [];
7043
7086
  const failed = [];
7044
7087
  for (const { name, target } of plan.toCreate) {
7045
- const filePath = join12(real, name);
7088
+ const filePath = join11(real, name);
7046
7089
  try {
7047
7090
  mkdirSync(dirname7(filePath), { recursive: true });
7048
7091
  symlinkSync(target, filePath);
@@ -7076,7 +7119,7 @@ function gatherViewSymlinks(repositoryRoot, anchorReal, roster, viewDir) {
7076
7119
  if (!existsSync(canonicalFile)) return { kind: "missing-canonical", viewName };
7077
7120
  const files = expectedSymlinkTargets(viewDir, canonicalFile, "hub").map(
7078
7121
  (spec) => {
7079
- const { state, actualTarget } = inspectSymlink(join12(viewDir, spec.name), spec.target);
7122
+ const { state, actualTarget } = inspectSymlink(join11(viewDir, spec.name), spec.target);
7080
7123
  return {
7081
7124
  name: spec.name,
7082
7125
  expectedTarget: spec.target,
@@ -7092,7 +7135,7 @@ function applyViewSymlinks(viewDir, files) {
7092
7135
  const failed = [];
7093
7136
  for (const f of files) {
7094
7137
  if (f.state !== "missing") continue;
7095
- const filePath = join12(viewDir, f.name);
7138
+ const filePath = join11(viewDir, f.name);
7096
7139
  try {
7097
7140
  mkdirSync(dirname7(filePath), { recursive: true });
7098
7141
  symlinkSync(f.expectedTarget, filePath);
@@ -7314,7 +7357,7 @@ function resolveViewDir(repositoryRoot, viewPath) {
7314
7357
  return realpathSync(abs);
7315
7358
  } catch {
7316
7359
  try {
7317
- return join12(realpathSync(dirname7(abs)), basename7(abs));
7360
+ return join11(realpathSync(dirname7(abs)), basename7(abs));
7318
7361
  } catch {
7319
7362
  return abs;
7320
7363
  }
@@ -7332,7 +7375,7 @@ function gatherViewRepo(repositoryRoot, viewDir, entry) {
7332
7375
  return { path: entry.path, reachable: false };
7333
7376
  }
7334
7377
  const linkName = basename7(repoReal);
7335
- const { state, actualTarget } = inspectSymlink(join12(viewDir, linkName), expectedTarget);
7378
+ const { state, actualTarget } = inspectSymlink(join11(viewDir, linkName), expectedTarget);
7336
7379
  return {
7337
7380
  path: entry.path,
7338
7381
  reachable: true,
@@ -7346,7 +7389,7 @@ function applyViewPlan(viewDir, toCreate) {
7346
7389
  const created = [];
7347
7390
  const failed = [];
7348
7391
  for (const { name, target } of toCreate) {
7349
- const filePath = join12(viewDir, name);
7392
+ const filePath = join11(viewDir, name);
7350
7393
  try {
7351
7394
  mkdirSync(dirname7(filePath), { recursive: true });
7352
7395
  symlinkSync(target, filePath);
@@ -7361,7 +7404,7 @@ var TOP_LEVEL_INSTRUCTION_FILES_LOWER = new Set(
7361
7404
  INSTRUCTION_FILES.filter((f) => !f.includes("/")).map((f) => f.toLowerCase())
7362
7405
  );
7363
7406
  function classifyViewLink(viewDir, name, rosterRealpaths) {
7364
- const filePath = join12(viewDir, name);
7407
+ const filePath = join11(viewDir, name);
7365
7408
  let isLink;
7366
7409
  try {
7367
7410
  isLink = lstatSync(filePath).isSymbolicLink();
@@ -7390,7 +7433,7 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
7390
7433
  if (!isDir) {
7391
7434
  return { target, kind: existsSync(resolved) ? "non-repo" : "broken" };
7392
7435
  }
7393
- return { target, kind: existsSync(join12(resolved, ".git")) ? "repo" : "non-repo" };
7436
+ return { target, kind: existsSync(join11(resolved, ".git")) ? "repo" : "non-repo" };
7394
7437
  }
7395
7438
  function gatherExistingViewLinks(viewDir, rosterRealpaths) {
7396
7439
  let names;
@@ -7415,7 +7458,7 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
7415
7458
  const pruned = [];
7416
7459
  const failed = [];
7417
7460
  for (const { name } of toPrune) {
7418
- const filePath = join12(viewDir, name);
7461
+ const filePath = join11(viewDir, name);
7419
7462
  const c = classifyViewLink(viewDir, name, rosterRealpaths);
7420
7463
  if (c === null || c.kind !== "repo") {
7421
7464
  failed.push({
@@ -7628,10 +7671,10 @@ async function runProjectPreset(options, ctx = {}) {
7628
7671
  }
7629
7672
  }
7630
7673
  function canonicalFileFor(anchorReal, canonicalName) {
7631
- return join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
7674
+ return join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
7632
7675
  }
7633
7676
  function canonicalLabelFor(canonicalName) {
7634
- return join12("agents", canonicalName, CANONICAL_FILE);
7677
+ return join11("agents", canonicalName, CANONICAL_FILE);
7635
7678
  }
7636
7679
  async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
7637
7680
  const declared = {
@@ -7652,7 +7695,7 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
7652
7695
  if (real === anchorReal) {
7653
7696
  return { ...declared, isAnchor: true, reachable: true, canonicalPresent: false };
7654
7697
  }
7655
- if (!existsSync(join12(real, ".git"))) {
7698
+ if (!existsSync(join11(real, ".git"))) {
7656
7699
  return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
7657
7700
  }
7658
7701
  const canonicalName = basename7(real);
@@ -8034,24 +8077,24 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
8034
8077
  const instructionFiles = [];
8035
8078
  for (const name of INSTRUCTION_FILES) {
8036
8079
  try {
8037
- lstatSync(join12(real, name));
8080
+ lstatSync(join11(real, name));
8038
8081
  instructionFiles.push(name);
8039
8082
  } catch {
8040
8083
  }
8041
8084
  }
8042
8085
  let ignored;
8043
8086
  try {
8044
- ignored = new Set(readGitignoreLines(join12(real, ".gitignore")).map((l) => l.trim()));
8087
+ ignored = new Set(readGitignoreLines(join11(real, ".gitignore")).map((l) => l.trim()));
8045
8088
  } catch {
8046
8089
  ignored = /* @__PURE__ */ new Set();
8047
8090
  }
8048
8091
  const gitignorePatterns = INSTRUCTION_FILES.filter((p) => ignored.has(p) || ignored.has(`/${p}`));
8049
- const canonical2 = existsSync(join12(anchorReal, "agents", canonicalName, CANONICAL_FILE));
8092
+ const canonical2 = existsSync(join11(anchorReal, "agents", canonicalName, CANONICAL_FILE));
8050
8093
  let viewLink = false;
8051
8094
  const viewPath = manifest.workspace.view;
8052
8095
  if (viewPath !== void 0) {
8053
8096
  try {
8054
- lstatSync(join12(resolveViewDir(repositoryRoot, viewPath), canonicalName));
8097
+ lstatSync(join11(resolveViewDir(repositoryRoot, viewPath), canonicalName));
8055
8098
  viewLink = true;
8056
8099
  } catch {
8057
8100
  }
@@ -8065,11 +8108,11 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
8065
8108
  };
8066
8109
  }
8067
8110
  function teardownExpectedTargets(repoReal, anchorReal, canonicalName) {
8068
- const canonicalFile = join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8111
+ const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8069
8112
  return expectedSymlinkTargets(repoReal, canonicalFile);
8070
8113
  }
8071
8114
  function viewLinkPointsAt(viewDir, name, repoReal) {
8072
- const filePath = join12(viewDir, name);
8115
+ const filePath = join11(viewDir, name);
8073
8116
  try {
8074
8117
  if (!lstatSync(filePath).isSymbolicLink()) return false;
8075
8118
  const target = readlinkSync(filePath);
@@ -8080,7 +8123,7 @@ function viewLinkPointsAt(viewDir, name, repoReal) {
8080
8123
  }
8081
8124
  }
8082
8125
  function viewLinkPointsAtPath(viewDir, name, expectedRepoPath) {
8083
- const filePath = join12(viewDir, name);
8126
+ const filePath = join11(viewDir, name);
8084
8127
  try {
8085
8128
  if (!lstatSync(filePath).isSymbolicLink()) return false;
8086
8129
  const target = readlinkSync(filePath);
@@ -8131,7 +8174,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
8131
8174
  if (!isAnchor) {
8132
8175
  if (repoReal !== void 0) {
8133
8176
  for (const spec of teardownExpectedTargets(repoReal, anchorReal, canonicalName)) {
8134
- const { state, actualTarget } = inspectSymlink(join12(repoReal, spec.name), spec.target);
8177
+ const { state, actualTarget } = inspectSymlink(join11(repoReal, spec.name), spec.target);
8135
8178
  if (isSelf) {
8136
8179
  if (state !== "missing")
8137
8180
  items.push({
@@ -8168,7 +8211,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
8168
8211
  }
8169
8212
  let ignored;
8170
8213
  try {
8171
- ignored = new Set(readGitignoreLines(join12(repoReal, ".gitignore")).map((l) => l.trim()));
8214
+ ignored = new Set(readGitignoreLines(join11(repoReal, ".gitignore")).map((l) => l.trim()));
8172
8215
  for (const p of INSTRUCTION_FILES) {
8173
8216
  if (ignored.has(p) || ignored.has(`/${p}`)) {
8174
8217
  items.push({
@@ -8191,7 +8234,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
8191
8234
  const viewPath = manifest.workspace.view;
8192
8235
  if (viewPath !== void 0) {
8193
8236
  const viewDir = resolveViewDir(repositoryRoot, viewPath);
8194
- const linkPath = join12(viewDir, canonicalName);
8237
+ const linkPath = join11(viewDir, canonicalName);
8195
8238
  let isLink = false;
8196
8239
  try {
8197
8240
  isLink = lstatSync(linkPath).isSymbolicLink();
@@ -8217,8 +8260,8 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
8217
8260
  else items.push({ kind: "view-symlink", label: canonicalName, state: "removable" });
8218
8261
  }
8219
8262
  }
8220
- const canonicalFile = join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8221
- const canonicalLabel = join12("agents", canonicalName, CANONICAL_FILE);
8263
+ const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8264
+ const canonicalLabel = join11("agents", canonicalName, CANONICAL_FILE);
8222
8265
  let canonicalIsLink = false;
8223
8266
  try {
8224
8267
  canonicalIsLink = lstatSync(canonicalFile).isSymbolicLink();
@@ -8322,12 +8365,12 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
8322
8365
  );
8323
8366
  for (const item of removable.filter((i) => i.kind === "instruction-symlink")) {
8324
8367
  const expected = expectedByName.get(item.label);
8325
- if (repoReal === null || expected === void 0 || inspectSymlink(join12(repoReal, item.label), expected).state !== "correct") {
8368
+ if (repoReal === null || expected === void 0 || inspectSymlink(join11(repoReal, item.label), expected).state !== "correct") {
8326
8369
  changed(item.label);
8327
8370
  continue;
8328
8371
  }
8329
8372
  try {
8330
- unlinkSync(join12(repoReal, item.label));
8373
+ unlinkSync(join11(repoReal, item.label));
8331
8374
  removed.push(item.label);
8332
8375
  } catch (error) {
8333
8376
  failed.push({ label: item.label, message: failureReason(error) });
@@ -8346,7 +8389,7 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
8346
8389
  continue;
8347
8390
  }
8348
8391
  try {
8349
- unlinkSync(join12(viewDir, item.label));
8392
+ unlinkSync(join11(viewDir, item.label));
8350
8393
  removed.push(`view/${item.label}`);
8351
8394
  } catch (error) {
8352
8395
  failed.push({ label: `view/${item.label}`, message: failureReason(error) });
@@ -8354,7 +8397,7 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
8354
8397
  }
8355
8398
  const NOFOLLOW = fsConstants.O_NOFOLLOW ?? 0;
8356
8399
  for (const item of removable.filter((i) => i.kind === "canonical-block")) {
8357
- const canonicalFile = join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8400
+ const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8358
8401
  try {
8359
8402
  if (lstatSync(canonicalFile).isSymbolicLink()) {
8360
8403
  changed(item.label);
@@ -8628,12 +8671,12 @@ function gatherRenameWiring(repositoryRoot, manifest, oldBasename) {
8628
8671
  } catch {
8629
8672
  return { canonicalDirOld: false, viewLinkOld: false };
8630
8673
  }
8631
- const canonicalDirOld = existsSync(join12(anchorReal, "agents", oldBasename));
8674
+ const canonicalDirOld = existsSync(join11(anchorReal, "agents", oldBasename));
8632
8675
  let viewLinkOld = false;
8633
8676
  const viewPath = manifest.workspace.view;
8634
8677
  if (viewPath !== void 0) {
8635
8678
  try {
8636
- lstatSync(join12(resolveViewDir(repositoryRoot, viewPath), oldBasename));
8679
+ lstatSync(join11(resolveViewDir(repositoryRoot, viewPath), oldBasename));
8637
8680
  viewLinkOld = true;
8638
8681
  } catch {
8639
8682
  }
@@ -8978,7 +9021,7 @@ async function doRunProjectSeedAnchor(options, ctx) {
8978
9021
  console.log("\u2139\uFE0F No repo roster declared \u2014 nothing to seed.");
8979
9022
  return;
8980
9023
  }
8981
- const anchorDoc = join12(repositoryRoot, CANONICAL_FILE);
9024
+ const anchorDoc = join11(repositoryRoot, CANONICAL_FILE);
8982
9025
  if (pathPresent(anchorDoc)) {
8983
9026
  console.log(
8984
9027
  `\u2705 The anchor's own \`${CANONICAL_FILE}\` already exists \u2014 hand-maintained, left untouched.`
@@ -9040,7 +9083,7 @@ function regularFileSpokes(repoReal) {
9040
9083
  const out = [];
9041
9084
  for (const spoke of ["CLAUDE.md", ".github/copilot-instructions.md"]) {
9042
9085
  try {
9043
- const st = lstatSync(join12(repoReal, spoke));
9086
+ const st = lstatSync(join11(repoReal, spoke));
9044
9087
  if (!st.isSymbolicLink() && st.isFile()) out.push(spoke);
9045
9088
  } catch {
9046
9089
  }
@@ -9086,8 +9129,8 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argAbs, argReal, vie
9086
9129
  };
9087
9130
  }
9088
9131
  const isAnchor = argReal === anchorReal;
9089
- const reachable = existsSync(join12(argReal, ".git"));
9090
- const canonicalFile = join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
9132
+ const reachable = existsSync(join11(argReal, ".git"));
9133
+ const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
9091
9134
  return {
9092
9135
  path,
9093
9136
  declared,
@@ -9096,13 +9139,13 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argAbs, argReal, vie
9096
9139
  reachable,
9097
9140
  canonicalName,
9098
9141
  ...viewCanonicalName !== void 0 ? { viewCanonicalName } : {},
9099
- agentsState: inspectAgentsState(join12(argReal, CANONICAL_FILE)),
9142
+ agentsState: inspectAgentsState(join11(argReal, CANONICAL_FILE)),
9100
9143
  canonicalExists: pathPresent(canonicalFile),
9101
9144
  regularSpokes: regularFileSpokes(argReal)
9102
9145
  };
9103
9146
  }
9104
9147
  function relocateAgentsFile(repoReal, canonicalFile) {
9105
- const agentsFile = join12(repoReal, CANONICAL_FILE);
9148
+ const agentsFile = join11(repoReal, CANONICAL_FILE);
9106
9149
  try {
9107
9150
  mkdirSync(dirname7(canonicalFile), { recursive: true });
9108
9151
  } catch (error) {
@@ -9218,7 +9261,7 @@ async function doRunProjectRetrofit(repo, options, ctx) {
9218
9261
  let failure;
9219
9262
  let partial = false;
9220
9263
  if (options.apply === true && plan.action === "relocate" && argReal !== void 0) {
9221
- const canonicalFile = join12(anchorReal, "agents", plan.canonicalName, CANONICAL_FILE);
9264
+ const canonicalFile = join11(anchorReal, "agents", plan.canonicalName, CANONICAL_FILE);
9222
9265
  const res = relocateAgentsFile(argReal, canonicalFile);
9223
9266
  if (res.ok) {
9224
9267
  applied = true;
@@ -9418,7 +9461,7 @@ function renderProjectRetrofit(result) {
9418
9461
  }
9419
9462
 
9420
9463
  // src/commands/protocol.ts
9421
- import { readFile as readFile6, stat as stat5 } from "fs/promises";
9464
+ import { readFile as readFile5, stat as stat5 } from "fs/promises";
9422
9465
  import {
9423
9466
  carryForwardProtocolStamp,
9424
9467
  PROTOCOL_END as PROTOCOL_END2,
@@ -9475,7 +9518,7 @@ async function readProtocolSources(entries) {
9475
9518
  for (const entry of entries) {
9476
9519
  let content;
9477
9520
  try {
9478
- content = await readFile6(entry.source, "utf8");
9521
+ content = await readFile5(entry.source, "utf8");
9479
9522
  } catch (error) {
9480
9523
  if (error instanceof Error && error.code === "ENOENT") {
9481
9524
  throw new Error(
@@ -9601,7 +9644,7 @@ async function doRunProtocolUnsync(options) {
9601
9644
  import {
9602
9645
  assertBasouRootSafe as assertBasouRootSafe10,
9603
9646
  basouPaths as basouPaths14,
9604
- findErrorCode as findErrorCode10,
9647
+ findErrorCode as findErrorCode11,
9605
9648
  loadPortfolioConfig as loadPortfolioConfig5,
9606
9649
  readManifest as readManifest10
9607
9650
  } from "@basou/core";
@@ -9610,20 +9653,20 @@ import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
9610
9653
  // src/commands/refresh-watch.ts
9611
9654
  import { readdir as readdir3, stat as stat6 } from "fs/promises";
9612
9655
  import { homedir as homedir9 } from "os";
9613
- import { join as join13 } from "path";
9656
+ import { join as join12 } from "path";
9614
9657
  import {
9615
9658
  assertApprovalStoreSafe as assertApprovalStoreSafe2,
9616
9659
  assertLockStoreSafe as assertLockStoreSafe4,
9617
9660
  assertTaskStoreSafe as assertTaskStoreSafe2,
9618
- findErrorCode as findErrorCode9
9661
+ findErrorCode as findErrorCode10
9619
9662
  } from "@basou/core";
9620
9663
  var DEFAULT_WATCH_INTERVAL_SEC = 30;
9621
9664
  var MIN_WATCH_INTERVAL_SEC = 5;
9622
9665
  var MAX_WATCH_INTERVAL_SEC = 86400;
9623
9666
  function watchedRoots(ctx) {
9624
9667
  return [
9625
- ctx.codexSessionsDir ?? join13(homedir9(), ".codex", "sessions"),
9626
- ctx.claudeProjectsDir ?? join13(homedir9(), ".claude", "projects")
9668
+ ctx.codexSessionsDir ?? join12(homedir9(), ".codex", "sessions"),
9669
+ ctx.claudeProjectsDir ?? join12(homedir9(), ".claude", "projects")
9627
9670
  ];
9628
9671
  }
9629
9672
  async function scanSourceLogs(roots) {
@@ -9633,11 +9676,11 @@ async function scanSourceLogs(roots) {
9633
9676
  try {
9634
9677
  entries = await readdir3(dir, { withFileTypes: true });
9635
9678
  } catch (error) {
9636
- if (findErrorCode9(error, "ENOENT") || findErrorCode9(error, "ENOTDIR")) return;
9679
+ if (findErrorCode10(error, "ENOENT") || findErrorCode10(error, "ENOTDIR")) return;
9637
9680
  throw new Error("Failed to read a source log directory", { cause: error });
9638
9681
  }
9639
9682
  for (const entry of entries) {
9640
- const full = join13(dir, entry.name);
9683
+ const full = join12(dir, entry.name);
9641
9684
  if (entry.isDirectory()) {
9642
9685
  await walk(full);
9643
9686
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
@@ -9645,7 +9688,7 @@ async function scanSourceLogs(roots) {
9645
9688
  const info = await stat6(full);
9646
9689
  out.set(full, { mtimeMs: info.mtimeMs, size: info.size });
9647
9690
  } catch (error) {
9648
- if (findErrorCode9(error, "ENOENT")) continue;
9691
+ if (findErrorCode10(error, "ENOENT")) continue;
9649
9692
  throw new Error("Failed to stat a source log file", { cause: error });
9650
9693
  }
9651
9694
  }
@@ -9979,7 +10022,7 @@ async function assertWorkspaceInitialized9(basouRoot) {
9979
10022
  try {
9980
10023
  await assertBasouRootSafe10(basouRoot);
9981
10024
  } catch (error) {
9982
- if (findErrorCode10(error, "ENOENT")) {
10025
+ if (findErrorCode11(error, "ENOENT")) {
9983
10026
  throw new Error("Workspace not initialized. Run 'basou init' first.");
9984
10027
  }
9985
10028
  throw error;
@@ -9991,7 +10034,7 @@ import { isAbsolute as isAbsolute4, resolve as resolve10 } from "path";
9991
10034
  import {
9992
10035
  assertBasouRootSafe as assertBasouRootSafe11,
9993
10036
  basouPaths as basouPaths15,
9994
- findErrorCode as findErrorCode11,
10037
+ findErrorCode as findErrorCode12,
9995
10038
  renderReport,
9996
10039
  writeMarkdownFile as writeMarkdownFile6
9997
10040
  } from "@basou/core";
@@ -10043,7 +10086,7 @@ async function assertWorkspaceInitialized10(basouRoot) {
10043
10086
  try {
10044
10087
  await assertBasouRootSafe11(basouRoot);
10045
10088
  } catch (error) {
10046
- if (findErrorCode11(error, "ENOENT")) {
10089
+ if (findErrorCode12(error, "ENOENT")) {
10047
10090
  throw new Error("Workspace not initialized. Run 'basou init' first.");
10048
10091
  }
10049
10092
  throw error;
@@ -10051,7 +10094,6 @@ async function assertWorkspaceInitialized10(basouRoot) {
10051
10094
  }
10052
10095
 
10053
10096
  // src/commands/review.ts
10054
- import { readFile as readFile7 } from "fs/promises";
10055
10097
  import { homedir as homedir10 } from "os";
10056
10098
  import { resolve as resolve11 } from "path";
10057
10099
  import {
@@ -10060,7 +10102,7 @@ import {
10060
10102
  buildReviewRecordedEvent,
10061
10103
  buildReviewRecordLabel,
10062
10104
  createAdHocSessionWithEvent as createAdHocSessionWithEvent3,
10063
- findErrorCode as findErrorCode12,
10105
+ findErrorCode as findErrorCode13,
10064
10106
  findUnbindableRepos,
10065
10107
  parseReviewRecordInput,
10066
10108
  readManifest as readManifest11,
@@ -10136,6 +10178,7 @@ async function runReviewRecord(options, ctx = {}) {
10136
10178
  }
10137
10179
  }
10138
10180
  async function doRunReviewRecord(options, ctx) {
10181
+ refuseDashInputFile("--file", options.file, "stdin");
10139
10182
  const cwd = ctx.cwd ?? process.cwd();
10140
10183
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "review record");
10141
10184
  const paths = basouPaths16(repositoryRoot);
@@ -10194,14 +10237,7 @@ ${detail}
10194
10237
  }
10195
10238
  async function readReviewInput(options, ctx) {
10196
10239
  if (options.file !== void 0) {
10197
- try {
10198
- return await readFile7(options.file, "utf8");
10199
- } catch (error) {
10200
- if (findErrorCode12(error, "ENOENT")) {
10201
- throw new Error(`Input file not found: ${options.file}`);
10202
- }
10203
- throw error;
10204
- }
10240
+ return await readInputFileText("--file", options.file);
10205
10241
  }
10206
10242
  if (ctx.readInput !== void 0) {
10207
10243
  return await ctx.readInput();
@@ -10276,7 +10312,7 @@ async function assertWorkspaceInitialized11(basouRoot) {
10276
10312
  try {
10277
10313
  await assertBasouRootSafe12(basouRoot);
10278
10314
  } catch (error) {
10279
- if (findErrorCode12(error, "ENOENT")) {
10315
+ if (findErrorCode13(error, "ENOENT")) {
10280
10316
  throw new Error("Workspace not initialized. Run 'basou init' first.");
10281
10317
  }
10282
10318
  throw error;
@@ -10488,9 +10524,9 @@ function unattachedLines(u) {
10488
10524
  }
10489
10525
 
10490
10526
  // src/commands/run.ts
10491
- import { mkdir as mkdir3, readFile as readFile8, rm as rm3 } from "fs/promises";
10527
+ import { mkdir as mkdir3, readFile as readFile6, rm as rm3 } from "fs/promises";
10492
10528
  import { homedir as homedir11 } from "os";
10493
- import { join as join14 } from "path";
10529
+ import { join as join13 } from "path";
10494
10530
  import {
10495
10531
  acquireLock as acquireLock5,
10496
10532
  assertBasouRootSafe as assertBasouRootSafe13,
@@ -10505,6 +10541,7 @@ import {
10505
10541
  findBasouSessionStartHook as findBasouSessionStartHook2,
10506
10542
  getDiff,
10507
10543
  getSnapshot as getSnapshot2,
10544
+ lstatSessionFile as lstatSessionFile2,
10508
10545
  overwriteYamlFile as overwriteYamlFile2,
10509
10546
  prefixedUlid as prefixedUlid4,
10510
10547
  readManifest as readManifest12,
@@ -10512,6 +10549,7 @@ import {
10512
10549
  resolveClaudeCodeCommand,
10513
10550
  resolveCodexCommand,
10514
10551
  SESSION_SCHEMA_VERSION as SESSION_SCHEMA_VERSION2,
10552
+ SessionFileNotAFileError as SessionFileNotAFileError4,
10515
10553
  SessionSchema as SessionSchema2,
10516
10554
  sanitizeRelatedFiles,
10517
10555
  sanitizeWorkingDirectory as sanitizeWorkingDirectory2,
@@ -10571,12 +10609,12 @@ async function runTrackedTool(args, options, ctx, adapter) {
10571
10609
  const manifest = await readManifest12(paths);
10572
10610
  await assertSessionStoreSafe3(paths);
10573
10611
  const sessionId = prefixedUlid4("ses");
10574
- const sessionDir = join14(paths.sessions, sessionId);
10612
+ const sessionDir = join13(paths.sessions, sessionId);
10575
10613
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
10576
10614
  await coreAppendChainedEvent2(paths, sessionId, event);
10577
10615
  });
10578
10616
  const startedAt = now().toISOString();
10579
- const sessionYamlPath = join14(sessionDir, "session.yaml");
10617
+ const sessionYamlPath = join13(sessionDir, "session.yaml");
10580
10618
  const session = buildInitialSession2({
10581
10619
  id: sessionId,
10582
10620
  command,
@@ -10627,7 +10665,7 @@ async function runTrackedTool(args, options, ctx, adapter) {
10627
10665
  });
10628
10666
  const runningLock = await acquireLock5(paths, "session", sessionId);
10629
10667
  try {
10630
- await mutateSessionYaml2(sessionYamlPath, (s) => {
10668
+ await mutateSessionYaml2(paths, sessionId, (s) => {
10631
10669
  s.session.status = "running";
10632
10670
  });
10633
10671
  } finally {
@@ -10759,7 +10797,7 @@ async function runTrackedTool(args, options, ctx, adapter) {
10759
10797
  s.session.related_files = relatedFiles;
10760
10798
  });
10761
10799
  if (result.exit_code !== null) return result.exit_code;
10762
- return signalToExitCode2(signalReceived ?? result.signal);
10800
+ return signalToExitCode(signalReceived ?? result.signal);
10763
10801
  }
10764
10802
  function decideFinalStatus2(result, signalReceived) {
10765
10803
  if (signalReceived === "SIGINT" || signalReceived === "SIGTERM") return "interrupted";
@@ -10769,18 +10807,6 @@ function decideFinalStatus2(result, signalReceived) {
10769
10807
  if (result.exit_code === 0) return "completed";
10770
10808
  return "failed";
10771
10809
  }
10772
- var SIGNUM_MAP2 = {
10773
- SIGHUP: 1,
10774
- SIGINT: 2,
10775
- SIGQUIT: 3,
10776
- SIGKILL: 9,
10777
- SIGTERM: 15
10778
- };
10779
- function signalToExitCode2(sig) {
10780
- if (sig === null) return 1;
10781
- const num = SIGNUM_MAP2[sig] ?? 1;
10782
- return 128 + num;
10783
- }
10784
10810
  async function tryAppendGitSnapshot2(sessionDir, sessionId, repoRoot, now, appendEvent) {
10785
10811
  let snapshot;
10786
10812
  try {
@@ -10885,7 +10911,14 @@ function buildInitialSession2(input) {
10885
10911
  }
10886
10912
  };
10887
10913
  }
10888
- async function mutateSessionYaml2(filePath, mutator) {
10914
+ async function mutateSessionYaml2(paths, sessionId, mutator) {
10915
+ const kind = await lstatSessionFile2(paths, sessionId, "session.yaml").catch((error) => {
10916
+ throw new Error("Failed to read YAML file", { cause: error });
10917
+ });
10918
+ if (kind === "symlink" || kind === "not_a_file") {
10919
+ throw new SessionFileNotAFileError4(sessionId, "session.yaml", kind);
10920
+ }
10921
+ const filePath = join13(paths.sessions, sessionId, "session.yaml");
10889
10922
  const raw = await readYamlFile5(filePath);
10890
10923
  const parsed = SessionSchema2.parse(raw);
10891
10924
  mutator(parsed);
@@ -10938,7 +10971,7 @@ async function noteCodexHookStatusPreSpawn(_cwd, ctx) {
10938
10971
  const hooksPath = ctx.codexHooksPath ?? DEFAULT_CODEX_HOOKS_PATH;
10939
10972
  let location;
10940
10973
  try {
10941
- location = findBasouSessionStartHook2(JSON.parse(await readFile8(hooksPath, "utf8")));
10974
+ location = findBasouSessionStartHook2(JSON.parse(await readFile6(hooksPath, "utf8")));
10942
10975
  } catch (error) {
10943
10976
  if (!(error instanceof Error && error.code === "ENOENT")) return null;
10944
10977
  location = null;
@@ -10952,8 +10985,8 @@ async function noteCodexHookStatusPreSpawn(_cwd, ctx) {
10952
10985
  }
10953
10986
 
10954
10987
  // src/commands/session.ts
10955
- import { readFile as readFile9 } from "fs/promises";
10956
- import { basename as basename8, isAbsolute as isAbsolute5, join as join15, relative as relative4, sep as sep3 } from "path";
10988
+ import { readFile as readFile7 } from "fs/promises";
10989
+ import { basename as basename8, isAbsolute as isAbsolute5, join as join14, relative as relative4, sep as sep3 } from "path";
10957
10990
  import {
10958
10991
  acquireLock as acquireLock6,
10959
10992
  appendEventToExistingSession as appendEventToExistingSession3,
@@ -10963,10 +10996,11 @@ import {
10963
10996
  displayPath as displayPath5,
10964
10997
  EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION6,
10965
10998
  enumerateSessionEntries as enumerateSessionEntries2,
10966
- findErrorCode as findErrorCode13,
10999
+ findErrorCode as findErrorCode14,
10967
11000
  importSessionFromJson as importSessionFromJson2,
10968
11001
  LOCAL_CLI_EVENT_SOURCE as LOCAL_CLI_EVENT_SOURCE4,
10969
11002
  loadSessionEntries as loadSessionEntries2,
11003
+ lstatSessionFile as lstatSessionFile3,
10970
11004
  normalizeSessionTimestamps,
10971
11005
  readAllEvents,
10972
11006
  readManifest as readManifest13,
@@ -10976,6 +11010,7 @@ import {
10976
11010
  resolveSessionId as resolveSessionId3,
10977
11011
  resolveTaskId,
10978
11012
  SESSION_IMPORT_SCHEMA_VERSION as SESSION_IMPORT_SCHEMA_VERSION2,
11013
+ SessionFileNotAFileError as SessionFileNotAFileError5,
10979
11014
  SessionImportPayloadSchema as SessionImportPayloadSchema2,
10980
11015
  SessionSchema as SessionSchema3,
10981
11016
  SessionStatusSchema,
@@ -11085,20 +11120,28 @@ async function doRunSessionShow(idInput, options, ctx) {
11085
11120
  const paths = basouPaths19(repositoryRoot);
11086
11121
  await assertWorkspaceInitialized12(paths.root);
11087
11122
  const sessionId = await resolveSessionId3(paths, idInput);
11088
- const sessionDir = join15(paths.sessions, sessionId);
11089
- const sessionYamlPath = join15(sessionDir, "session.yaml");
11123
+ const sessionDir = join14(paths.sessions, sessionId);
11124
+ const sessionYamlPath = join14(sessionDir, "session.yaml");
11125
+ const yamlKind = await lstatSessionFile3(paths, sessionId, "session.yaml").catch(
11126
+ (error) => {
11127
+ throw new Error("Failed to read session", { cause: error });
11128
+ }
11129
+ );
11130
+ if (yamlKind === "symlink" || yamlKind === "not_a_file") {
11131
+ throw new SessionFileNotAFileError5(sessionId, "session.yaml", yamlKind);
11132
+ }
11090
11133
  let session;
11091
11134
  try {
11092
11135
  const raw = await readYamlFile6(sessionYamlPath);
11093
11136
  session = SessionSchema3.parse(normalizeSessionTimestamps(raw));
11094
11137
  } catch (error) {
11095
- if (findErrorCode13(error, "ENOENT")) {
11138
+ if (findErrorCode14(error, "ENOENT")) {
11096
11139
  throw new Error(`Session not found: ${idInput}`);
11097
11140
  }
11098
11141
  throw new Error("Failed to read session", { cause: error });
11099
11142
  }
11100
11143
  let eventsLostLines = 0;
11101
- const events = await readAllEvents(sessionDir, {
11144
+ const events = await readAllEvents(paths, sessionId, {
11102
11145
  onWarning: (w) => {
11103
11146
  if (w.kind === "malformed_json" || w.kind === "schema_violation") eventsLostLines++;
11104
11147
  printReplayWarning(w, sessionId);
@@ -11356,7 +11399,7 @@ async function assertWorkspaceInitialized12(basouRoot) {
11356
11399
  try {
11357
11400
  await assertBasouRootSafe14(basouRoot);
11358
11401
  } catch (error) {
11359
- if (findErrorCode13(error, "ENOENT")) {
11402
+ if (findErrorCode14(error, "ENOENT")) {
11360
11403
  throw new Error("Workspace not initialized. Run 'basou init' first.");
11361
11404
  }
11362
11405
  throw error;
@@ -11392,6 +11435,7 @@ async function runSessionImport(options, ctx = {}) {
11392
11435
  }
11393
11436
  }
11394
11437
  async function doRunSessionImport(options, ctx) {
11438
+ refuseDashInputFile("--from", options.from, { fileOnly: "the JSON" });
11395
11439
  const cwd = ctx.cwd ?? process.cwd();
11396
11440
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "import");
11397
11441
  const paths = basouPaths19(repositoryRoot);
@@ -11425,12 +11469,12 @@ async function doRunSessionImport(options, ctx) {
11425
11469
  }
11426
11470
  async function readInputFile(path) {
11427
11471
  try {
11428
- return await readFile9(path, "utf8");
11472
+ return await readFile7(path, "utf8");
11429
11473
  } catch (error) {
11430
- if (findErrorCode13(error, "ENOENT")) {
11474
+ if (findErrorCode14(error, "ENOENT")) {
11431
11475
  throw new Error("Import source not found", { cause: error });
11432
11476
  }
11433
- if (findErrorCode13(error, "EISDIR")) {
11477
+ if (findErrorCode14(error, "EISDIR")) {
11434
11478
  throw new Error("Import source is not a file", { cause: error });
11435
11479
  }
11436
11480
  throw new Error("Failed to read import source", { cause: error });
@@ -11505,9 +11549,7 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
11505
11549
  if (hasBody && hasFromFile) {
11506
11550
  throw new Error("--body and --from-file are mutually exclusive");
11507
11551
  }
11508
- if (hasFromFile && options.fromFile === "-") {
11509
- throw new Error("--from-file - (stdin) is not supported in v0.1");
11510
- }
11552
+ refuseDashInputFile("--from-file", options.fromFile, { textFlag: "--body" });
11511
11553
  const cwd = ctx.cwd ?? process.cwd();
11512
11554
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "note");
11513
11555
  const paths = basouPaths19(repositoryRoot);
@@ -11542,12 +11584,12 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
11542
11584
  }
11543
11585
  async function readNoteFile(path) {
11544
11586
  try {
11545
- return await readFile9(path, "utf8");
11587
+ return await readFile7(path, "utf8");
11546
11588
  } catch (error) {
11547
- if (findErrorCode13(error, "ENOENT")) {
11589
+ if (findErrorCode14(error, "ENOENT")) {
11548
11590
  throw new Error("Note source not found", { cause: error });
11549
11591
  }
11550
- if (findErrorCode13(error, "EISDIR")) {
11592
+ if (findErrorCode14(error, "EISDIR")) {
11551
11593
  throw new Error("Note source is not a file", { cause: error });
11552
11594
  }
11553
11595
  throw new Error("Failed to read note source", { cause: error });
@@ -11617,7 +11659,7 @@ async function doRunSessionRechain(options, ctx) {
11617
11659
  }
11618
11660
  }
11619
11661
  const tamperedCount = rows.filter(
11620
- (r) => r.reason === "tampered" || r.reason === "symlink" || r.reason === "not_a_directory"
11662
+ (r) => r.reason === "tampered" || r.reason === "symlink" || r.reason === "not_a_directory" || r.reason === "events_not_a_file" || r.reason === "yaml_not_a_file"
11621
11663
  ).length;
11622
11664
  const errorCount = rows.filter((r) => r.status === "error").length;
11623
11665
  if (options.json === true) {
@@ -11645,6 +11687,10 @@ function renderRechainRow(row, dryRun) {
11645
11687
  if (row.reason === "symlink" || row.reason === "not_a_directory") {
11646
11688
  return `skipped (${row.reason === "symlink" ? "SYMLINK" : "NOT A DIRECTORY"} \u2014 not followed; inspect with 'basou verify')`;
11647
11689
  }
11690
+ if (row.reason === "events_not_a_file" || row.reason === "yaml_not_a_file") {
11691
+ const file = row.reason === "events_not_a_file" ? "events.jsonl" : "session.yaml";
11692
+ return `skipped (${file} NOT A REGULAR FILE \u2014 not followed; inspect with 'basou verify')`;
11693
+ }
11648
11694
  return `skipped (${row.reason})`;
11649
11695
  case "error":
11650
11696
  return `error (${row.message})`;
@@ -11660,7 +11706,7 @@ import {
11660
11706
  assertBasouRootSafe as assertBasouRootSafe15,
11661
11707
  basouPaths as basouPaths20,
11662
11708
  computeWorkStats,
11663
- findErrorCode as findErrorCode14
11709
+ findErrorCode as findErrorCode15
11664
11710
  } from "@basou/core";
11665
11711
  function registerStatsCommand(program) {
11666
11712
  program.command("stats").description("Report how much the AI worked (output volume + time proxies) across sessions").option("--by-source", "Break the totals down by session source kind").option("--by-day", "Break billable time and volume down by calendar day").option("--json", "Output the full stats as JSON").option("-v, --verbose", "Show error causes").action(async (options) => {
@@ -11769,7 +11815,7 @@ async function assertWorkspaceInitialized13(basouRoot) {
11769
11815
  try {
11770
11816
  await assertBasouRootSafe15(basouRoot);
11771
11817
  } catch (error) {
11772
- if (findErrorCode14(error, "ENOENT")) {
11818
+ if (findErrorCode15(error, "ENOENT")) {
11773
11819
  throw new Error("Workspace not initialized. Run 'basou init' first.");
11774
11820
  }
11775
11821
  throw error;
@@ -11781,7 +11827,7 @@ import {
11781
11827
  assertBasouRootSafe as assertBasouRootSafe16,
11782
11828
  basouPaths as basouPaths21,
11783
11829
  buildStatusSnapshot,
11784
- findErrorCode as findErrorCode15,
11830
+ findErrorCode as findErrorCode16,
11785
11831
  readManifest as readManifest14,
11786
11832
  writeStatus
11787
11833
  } from "@basou/core";
@@ -11805,7 +11851,7 @@ async function doRunStatus(options, ctx) {
11805
11851
  try {
11806
11852
  await assertBasouRootSafe16(paths.root);
11807
11853
  } catch (error) {
11808
- if (findErrorCode15(error, "ENOENT")) {
11854
+ if (findErrorCode16(error, "ENOENT")) {
11809
11855
  throw new Error("Workspace not initialized. Run 'basou init' first.");
11810
11856
  }
11811
11857
  throw error;
@@ -11814,7 +11860,7 @@ async function doRunStatus(options, ctx) {
11814
11860
  try {
11815
11861
  manifest = await readManifest14(paths);
11816
11862
  } catch (error) {
11817
- if (findErrorCode15(error, "ENOENT")) {
11863
+ if (findErrorCode16(error, "ENOENT")) {
11818
11864
  throw new Error("Workspace not initialized. Run 'basou init' first.");
11819
11865
  }
11820
11866
  const gateMessage = formatVersionGateMessage(error);
@@ -11852,8 +11898,7 @@ function formatVersionGateMessage(error) {
11852
11898
  }
11853
11899
 
11854
11900
  // src/commands/task.ts
11855
- import { readFile as readFile10 } from "fs/promises";
11856
- import { join as join16 } from "path";
11901
+ import { readFile as readFile8 } from "fs/promises";
11857
11902
  import {
11858
11903
  archiveTask,
11859
11904
  assertBasouRootSafe as assertBasouRootSafe17,
@@ -11863,7 +11908,7 @@ import {
11863
11908
  deleteTask,
11864
11909
  editTask,
11865
11910
  enumerateArchivedTaskIds,
11866
- findErrorCode as findErrorCode16,
11911
+ findErrorCode as findErrorCode17,
11867
11912
  loadSessionEntries as loadSessionEntries3,
11868
11913
  loadTaskEntries,
11869
11914
  prefixedUlid as prefixedUlid5,
@@ -11950,9 +11995,7 @@ async function doRunTaskNew(options, ctx) {
11950
11995
  if (options.description !== void 0 && options.fromFile !== void 0) {
11951
11996
  throw new Error("--description and --from-file are mutually exclusive");
11952
11997
  }
11953
- if (options.fromFile === "-") {
11954
- throw new Error("--from-file - (stdin) is not supported in v0.1");
11955
- }
11998
+ refuseDashInputFile("--from-file", options.fromFile, { textFlag: "--description" });
11956
11999
  const initialStatus = options.status ?? "planned";
11957
12000
  if (options.completedAt !== void 0 && !isTerminalStatusForCli(initialStatus)) {
11958
12001
  throw new Error("--completed-at requires --status done or cancelled");
@@ -12180,9 +12223,8 @@ async function doRunTaskShow(idInput, options, ctx) {
12180
12223
  const events = [];
12181
12224
  const linkedSessionIds = new Set(doc.task.task.linked_sessions);
12182
12225
  for (const s of sessions) {
12183
- const sessionDir = join16(paths.sessions, s.sessionId);
12184
12226
  try {
12185
- for await (const ev of replayEvents3(sessionDir, {
12227
+ for await (const ev of replayEvents3(paths, s.sessionId, {
12186
12228
  onWarning: (w) => printReplayWarning(w, s.sessionId)
12187
12229
  })) {
12188
12230
  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) {
@@ -12869,12 +12911,12 @@ function parsePositiveInt2(raw) {
12869
12911
  }
12870
12912
  async function readDescriptionFile(path) {
12871
12913
  try {
12872
- return await readFile10(path, "utf8");
12914
+ return await readFile8(path, "utf8");
12873
12915
  } catch (error) {
12874
- if (findErrorCode16(error, "ENOENT")) {
12916
+ if (findErrorCode17(error, "ENOENT")) {
12875
12917
  throw new Error("Description source not found", { cause: error });
12876
12918
  }
12877
- if (findErrorCode16(error, "EISDIR")) {
12919
+ if (findErrorCode17(error, "EISDIR")) {
12878
12920
  throw new Error("Description source is not a file", { cause: error });
12879
12921
  }
12880
12922
  throw new Error("Failed to read description source", { cause: error });
@@ -12887,7 +12929,7 @@ async function assertWorkspaceInitialized14(basouRoot) {
12887
12929
  try {
12888
12930
  await assertBasouRootSafe17(basouRoot);
12889
12931
  } catch (error) {
12890
- if (findErrorCode16(error, "ENOENT")) {
12932
+ if (findErrorCode17(error, "ENOENT")) {
12891
12933
  throw new Error("Workspace not initialized. Run 'basou init' first.");
12892
12934
  }
12893
12935
  throw error;
@@ -12978,7 +13020,7 @@ import {
12978
13020
  assertBasouRootSafe as assertBasouRootSafe18,
12979
13021
  basouPaths as basouPaths23,
12980
13022
  enumerateSessionEntries as enumerateSessionEntries3,
12981
- findErrorCode as findErrorCode17,
13023
+ findErrorCode as findErrorCode18,
12982
13024
  resolveSessionId as resolveSessionId5,
12983
13025
  verifyEventsChain
12984
13026
  } from "@basou/core";
@@ -13065,7 +13107,7 @@ async function assertWorkspaceInitialized15(basouRoot) {
13065
13107
  try {
13066
13108
  await assertBasouRootSafe18(basouRoot);
13067
13109
  } catch (error) {
13068
- if (findErrorCode17(error, "ENOENT")) {
13110
+ if (findErrorCode18(error, "ENOENT")) {
13069
13111
  throw new Error("Workspace not initialized. Run 'basou init' first.");
13070
13112
  }
13071
13113
  throw error;
@@ -13075,11 +13117,11 @@ async function assertWorkspaceInitialized15(basouRoot) {
13075
13117
  // src/commands/view.ts
13076
13118
  import { spawn } from "child_process";
13077
13119
  import { createHash as createHash2 } from "crypto";
13078
- import { basename as basename11, dirname as dirname9, join as join20, resolve as resolve14 } from "path";
13120
+ import { basename as basename11, dirname as dirname9, join as join17, resolve as resolve14 } from "path";
13079
13121
  import {
13080
13122
  assertBasouRootSafe as assertBasouRootSafe19,
13081
13123
  basouPaths as basouPaths25,
13082
- findErrorCode as findErrorCode19,
13124
+ findErrorCode as findErrorCode20,
13083
13125
  loadPortfolioConfig as loadPortfolioConfig6,
13084
13126
  readManifest as readManifest19
13085
13127
  } from "@basou/core";
@@ -13089,7 +13131,7 @@ import { InvalidArgumentError as InvalidArgumentError8 } from "commander";
13089
13131
  import { createReadStream as createReadStream2 } from "fs";
13090
13132
  import { readdir as readdir4, stat as stat7 } from "fs/promises";
13091
13133
  import { homedir as homedir12 } from "os";
13092
- import { basename as basename9, dirname as dirname8, join as join17 } from "path";
13134
+ import { basename as basename9, dirname as dirname8, join as join15 } from "path";
13093
13135
  import { createInterface as createInterface2 } from "readline";
13094
13136
  import { basouPaths as basouPaths24, readManifest as readManifest16 } from "@basou/core";
13095
13137
  function uncapturedTotal(result) {
@@ -13101,8 +13143,8 @@ async function checkPortfolioCoverage(workspaces, ctx = {}) {
13101
13143
  ctx.portfolioConfigPath
13102
13144
  );
13103
13145
  const listedDirs = new Set([...roots].map((root) => encodeProjectDir(root)));
13104
- const claudeProjectsDir = ctx.claudeProjectsDir ?? join17(homedir12(), ".claude", "projects");
13105
- const codexSessionsDir = ctx.codexSessionsDir ?? join17(homedir12(), ".codex", "sessions");
13146
+ const claudeProjectsDir = ctx.claudeProjectsDir ?? join15(homedir12(), ".claude", "projects");
13147
+ const codexSessionsDir = ctx.codexSessionsDir ?? join15(homedir12(), ".codex", "sessions");
13106
13148
  const tally = new Tally(roots);
13107
13149
  const absentTrees = [];
13108
13150
  const claudeFiles = await listClaudeTranscripts(claudeProjectsDir);
@@ -13240,7 +13282,7 @@ async function isDirEntry(parent, entry) {
13240
13282
  if (entry.isDirectory()) return true;
13241
13283
  if (!entry.isSymbolicLink()) return false;
13242
13284
  try {
13243
- return (await stat7(join17(parent, entry.name))).isDirectory();
13285
+ return (await stat7(join15(parent, entry.name))).isDirectory();
13244
13286
  } catch {
13245
13287
  return false;
13246
13288
  }
@@ -13255,7 +13297,7 @@ async function listClaudeTranscripts(projectsRoot) {
13255
13297
  const files = [];
13256
13298
  for (const entry of entries) {
13257
13299
  if (!await isDirEntry(projectsRoot, entry)) continue;
13258
- const full = join17(projectsRoot, entry.name);
13300
+ const full = join15(projectsRoot, entry.name);
13259
13301
  let names;
13260
13302
  try {
13261
13303
  names = await readdir4(full);
@@ -13263,7 +13305,7 @@ async function listClaudeTranscripts(projectsRoot) {
13263
13305
  continue;
13264
13306
  }
13265
13307
  for (const name of names) {
13266
- if (name.endsWith(".jsonl")) files.push(join17(full, name));
13308
+ if (name.endsWith(".jsonl")) files.push(join15(full, name));
13267
13309
  }
13268
13310
  }
13269
13311
  return files.sort();
@@ -13278,7 +13320,7 @@ async function listCodexRollouts(sessionsRoot) {
13278
13320
  return;
13279
13321
  }
13280
13322
  for (const entry of entries) {
13281
- const full = join17(dir, entry.name);
13323
+ const full = join15(dir, entry.name);
13282
13324
  if (entry.isDirectory()) await walk(full);
13283
13325
  else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) {
13284
13326
  found.push(full);
@@ -13409,7 +13451,7 @@ function inertLines(result) {
13409
13451
  // src/lib/portfolio-safety.ts
13410
13452
  import { execFile as execFile2 } from "child_process";
13411
13453
  import { lstat as lstat3, realpath as realpath5 } from "fs/promises";
13412
- import { isAbsolute as isAbsolute6, join as join18, relative as relative5, resolve as resolve12, sep as sep4 } from "path";
13454
+ import { isAbsolute as isAbsolute6, join as join16, relative as relative5, resolve as resolve12, sep as sep4 } from "path";
13413
13455
  import { promisify as promisify2 } from "util";
13414
13456
  import { readManifest as readManifest17 } from "@basou/core";
13415
13457
  var execFileAsync2 = promisify2(execFile2);
@@ -13433,7 +13475,7 @@ function isBasouPath(p) {
13433
13475
  async function inspectRepo(repoPath) {
13434
13476
  let hasEntry = false;
13435
13477
  try {
13436
- await lstat3(join18(repoPath, ".basou"));
13478
+ await lstat3(join16(repoPath, ".basou"));
13437
13479
  hasEntry = true;
13438
13480
  } catch (error) {
13439
13481
  if (errorCode2(error) !== "ENOENT") {
@@ -13587,7 +13629,7 @@ function formatSafetyReport(result) {
13587
13629
 
13588
13630
  // src/lib/view-server.ts
13589
13631
  import { createServer } from "http";
13590
- import { basename as basename10, join as join19, resolve as resolve13 } from "path";
13632
+ import { basename as basename10, resolve as resolve13 } from "path";
13591
13633
  import {
13592
13634
  boardLivePage,
13593
13635
  boardPage,
@@ -13595,7 +13637,7 @@ import {
13595
13637
  boardPageUnavailable,
13596
13638
  computeWorkStats as computeWorkStats2,
13597
13639
  enumerateApprovals as enumerateApprovals2,
13598
- findErrorCode as findErrorCode18,
13640
+ findErrorCode as findErrorCode19,
13599
13641
  inspectSessionEntry as inspectSessionEntry2,
13600
13642
  isLazyExpired as isLazyExpired2,
13601
13643
  loadApproval as loadApproval2,
@@ -13609,7 +13651,9 @@ import {
13609
13651
  renderDecisions as renderDecisions3,
13610
13652
  renderHandoff as renderHandoff3,
13611
13653
  resolveViewLanguageFromPaths,
13654
+ SessionFileNotAFileError as SessionFileNotAFileError6,
13612
13655
  SessionIdSchema,
13656
+ SessionNotADirectoryError,
13613
13657
  summarizeOrientation,
13614
13658
  tryRemoteUrl
13615
13659
  } from "@basou/core";
@@ -15544,7 +15588,7 @@ async function overview(ws, nowProvider, resolveRemoteUrl) {
15544
15588
  try {
15545
15589
  manifest = await readManifest18(ws.paths);
15546
15590
  } catch (error) {
15547
- if (findErrorCode18(error, "ENOENT")) {
15591
+ if (findErrorCode19(error, "ENOENT")) {
15548
15592
  return { initialized: false, repoRoot: ws.repoRoot };
15549
15593
  }
15550
15594
  throw error;
@@ -15612,10 +15656,7 @@ async function sessionDetail(ws, sessionId) {
15612
15656
  }
15613
15657
  const entry = await inspectSessionEntry2(ws.paths, sessionId);
15614
15658
  if (entry === "symlink" || entry === "not_a_directory") {
15615
- throw new HttpError(
15616
- 404,
15617
- `Session ${sessionId} is not a directory; a symlink or a file there is not followed`
15618
- );
15659
+ throw new HttpError(404, new SessionNotADirectoryError(sessionId, entry).message);
15619
15660
  }
15620
15661
  let session;
15621
15662
  try {
@@ -15624,10 +15665,11 @@ async function sessionDetail(ws, sessionId) {
15624
15665
  if (error instanceof Error && error.message === "YAML file not found") {
15625
15666
  throw new HttpError(404, "Session not found");
15626
15667
  }
15668
+ if (error instanceof SessionFileNotAFileError6) throw new HttpError(404, error.message);
15627
15669
  throw error;
15628
15670
  }
15629
15671
  try {
15630
- const events = await readAllEvents2(join19(ws.paths.sessions, sessionId));
15672
+ const events = await readAllEvents2(ws.paths, sessionId);
15631
15673
  return { session, events };
15632
15674
  } catch {
15633
15675
  return { session, events: [], degraded: true };
@@ -15892,7 +15934,7 @@ async function viewBoardOf(root, paths) {
15892
15934
  own = void 0;
15893
15935
  }
15894
15936
  if (own?.visibility !== "private") return { why: "no_board" };
15895
- return { recordsDir: join20(root, dirname9(DEFAULT_BOARD_PATH), "records") };
15937
+ return { recordsDir: join17(root, dirname9(DEFAULT_BOARD_PATH), "records") };
15896
15938
  }
15897
15939
  async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
15898
15940
  const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve14(cwd, p) })) : await loadPortfolioConfig6(ctx.portfolioConfigPath);
@@ -15954,7 +15996,7 @@ async function startListening(port, deps) {
15954
15996
  try {
15955
15997
  return await startViewServer({ port, deps });
15956
15998
  } catch (error) {
15957
- if (findErrorCode19(error, "EADDRINUSE")) {
15999
+ if (findErrorCode20(error, "EADDRINUSE")) {
15958
16000
  throw new Error(`Port ${port} is already in use. Pass --port <n> to choose another.`, {
15959
16001
  cause: error
15960
16002
  });
@@ -16007,7 +16049,7 @@ async function assertWorkspaceInitialized16(basouRoot) {
16007
16049
  try {
16008
16050
  await assertBasouRootSafe19(basouRoot);
16009
16051
  } catch (error) {
16010
- if (findErrorCode19(error, "ENOENT")) {
16052
+ if (findErrorCode20(error, "ENOENT")) {
16011
16053
  throw new Error("Workspace not initialized. Run 'basou init' first.");
16012
16054
  }
16013
16055
  throw error;
@@ -16046,7 +16088,7 @@ function installExperimentalNotice(program, names = EXPERIMENTAL_COMMANDS, write
16046
16088
  function readBuildStamp() {
16047
16089
  if (false) return void 0;
16048
16090
  try {
16049
- return JSON.parse('{"version":"0.68.4","commit":"89725f6","committedAt":"2026-10-15T20:46:14+09:00"}');
16091
+ return JSON.parse('{"version":"0.70.0","commit":"60d302a","committedAt":"2026-10-16T17:39:30+09:00"}');
16050
16092
  } catch {
16051
16093
  return void 0;
16052
16094
  }