@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/index.js CHANGED
@@ -95,7 +95,7 @@ function printSessionSkip(sid, reason) {
95
95
  const short = shortSessionId(sid);
96
96
  if (reason === "events_jsonl_unreadable") {
97
97
  console.error(`Warning: skipped suspect check for ${short}: events.jsonl unreadable`);
98
- } else if (reason === "session_dir_not_directory") {
98
+ } else if (reason === "session_dir_not_directory" || reason === "session_yaml_not_a_file") {
99
99
  console.error(`Skipped ${sid}: ${reason}`);
100
100
  } else {
101
101
  console.error(`Skipped ${short}: ${reason}`);
@@ -116,6 +116,11 @@ function printSessionListSkip(sid, reason) {
116
116
  case "session_dir_not_directory":
117
117
  console.error(`Skipped ${sid}: not a directory (a symlink or a file is not followed)`);
118
118
  break;
119
+ case "session_yaml_not_a_file":
120
+ console.error(
121
+ `Skipped ${sid}: session.yaml is not a regular file (a symlink or anything else is not followed)`
122
+ );
123
+ break;
119
124
  }
120
125
  }
121
126
  function printTaskSkip(taskId, reason) {
@@ -152,7 +157,8 @@ import {
152
157
  prefixedUlid,
153
158
  readSessionYaml,
154
159
  readYamlFile,
155
- replayEvents
160
+ replayEvents,
161
+ SessionFileNotAFileError
156
162
  } from "@basou/core";
157
163
 
158
164
  // src/lib/repo-root.ts
@@ -425,7 +431,7 @@ async function doRunApprovalShow(idInput, options, ctx) {
425
431
  `Warning: session ${sessionId} is not a directory (a symlink or a file is not followed); its events are not shown`
426
432
  );
427
433
  } else {
428
- for await (const ev of replayEvents(join(paths.sessions, sessionId), {
434
+ for await (const ev of replayEvents(paths, sessionId, {
429
435
  onWarning: (w) => printReplayWarning(w, sessionId)
430
436
  })) {
431
437
  if (isApprovalEvent(ev) && ev.approval_id === id) {
@@ -525,8 +531,7 @@ async function doRunApprovalResolve(idInput, options, ctx, decision) {
525
531
  const sessionLock = await acquireLock(paths, "session", approval.session_id);
526
532
  try {
527
533
  await assertSessionDirSafe(paths, approval.session_id);
528
- const sessionDir = join(paths.sessions, approval.session_id);
529
- for await (const ev of replayEvents(sessionDir, {
534
+ for await (const ev of replayEvents(paths, approval.session_id, {
530
535
  onWarning: (w) => printReplayWarning(w, approval.session_id)
531
536
  })) {
532
537
  if (isApprovalEvent(ev) && ev.approval_id === approval.id && (ev.type === "approval_approved" || ev.type === "approval_rejected" || ev.type === "approval_expired")) {
@@ -539,7 +544,8 @@ async function doRunApprovalResolve(idInput, options, ctx, decision) {
539
544
  let sessionStatus = null;
540
545
  try {
541
546
  sessionStatus = (await readSessionYaml(paths, approval.session_id)).session.status;
542
- } catch {
547
+ } catch (error) {
548
+ if (error instanceof SessionFileNotAFileError) throw error;
543
549
  sessionStatus = null;
544
550
  }
545
551
  const attachable = sessionStatus === "initialized" || sessionStatus === "running" || sessionStatus === "waiting_approval";
@@ -1048,6 +1054,7 @@ import {
1048
1054
  readSessionYaml as readSessionYaml2,
1049
1055
  reimportPreservingId,
1050
1056
  SESSION_IMPORT_SCHEMA_VERSION,
1057
+ SessionFileNotAFileError as SessionFileNotAFileError2,
1051
1058
  SessionImportPayloadSchema
1052
1059
  } from "@basou/core";
1053
1060
 
@@ -1221,10 +1228,11 @@ async function resolveImportTarget(ctx) {
1221
1228
  async function importDerivedSessions(paths, manifest, options, sourceKind, candidates, projectPaths, boundaryDeclared) {
1222
1229
  await assertSessionStoreSafe(paths);
1223
1230
  if (options.dryRun !== true) await assertLockStoreSafe2(paths);
1224
- const { byExternalId: existingByExternalId, notDirectories } = await loadExistingByExternalId(
1225
- paths,
1226
- sourceKind
1227
- );
1231
+ const {
1232
+ byExternalId: existingByExternalId,
1233
+ notDirectories,
1234
+ yamlNotFiles
1235
+ } = await loadExistingByExternalId(paths, sourceKind);
1228
1236
  let notDirectoriesWarned = false;
1229
1237
  const seenThisRun = /* @__PURE__ */ new Set();
1230
1238
  const crossProjectCheck = boundaryDeclared;
@@ -1317,12 +1325,20 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
1317
1325
  }
1318
1326
  counts.replaced++;
1319
1327
  }
1320
- if (priors.length === 0 && notDirectories.length > 0 && !notDirectoriesWarned) {
1328
+ if (priors.length === 0 && (notDirectories.length > 0 || yamlNotFiles.length > 0) && !notDirectoriesWarned) {
1321
1329
  notDirectoriesWarned = true;
1322
- const one = notDirectories.length === 1;
1323
- console.error(
1324
- `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`
1325
- );
1330
+ if (notDirectories.length > 0) {
1331
+ const one = notDirectories.length === 1;
1332
+ console.error(
1333
+ `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`
1334
+ );
1335
+ }
1336
+ if (yamlNotFiles.length > 0) {
1337
+ const one = yamlNotFiles.length === 1;
1338
+ console.error(
1339
+ `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`
1340
+ );
1341
+ }
1326
1342
  }
1327
1343
  const result = await importSessionFromJson(paths, manifest, payload, {
1328
1344
  dryRun: options.dryRun === true
@@ -1404,13 +1420,15 @@ async function loadExistingByExternalId(paths, sourceKind) {
1404
1420
  try {
1405
1421
  ({ dirs: sessionIds, notDirectories } = await enumerateSessionEntries(paths));
1406
1422
  } catch {
1407
- return { byExternalId, notDirectories: [] };
1423
+ return { byExternalId, notDirectories: [], yamlNotFiles: [] };
1408
1424
  }
1425
+ const yamlNotFiles = [];
1409
1426
  for (const sessionId of sessionIds) {
1410
1427
  let session;
1411
1428
  try {
1412
1429
  session = await readSessionYaml2(paths, sessionId);
1413
- } catch {
1430
+ } catch (error) {
1431
+ if (error instanceof SessionFileNotAFileError2) yamlNotFiles.push(sessionId);
1414
1432
  continue;
1415
1433
  }
1416
1434
  if (session.session.source.kind !== sourceKind) continue;
@@ -1425,7 +1443,7 @@ async function loadExistingByExternalId(paths, sourceKind) {
1425
1443
  const match = typeof label === "string" ? label.match(/^claude-code import (\S+)$/) : null;
1426
1444
  if (match?.[1] !== void 0) add(match[1], prior);
1427
1445
  }
1428
- return { byExternalId, notDirectories };
1446
+ return { byExternalId, notDirectories, yamlNotFiles };
1429
1447
  }
1430
1448
  async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
1431
1449
  if (options.session !== void 0) {
@@ -2958,9 +2976,8 @@ async function doRunChannelClear(face, options) {
2958
2976
  }
2959
2977
 
2960
2978
  // src/commands/decision.ts
2961
- import { readFile as readFile3 } from "fs/promises";
2962
2979
  import { homedir as homedir3 } from "os";
2963
- import { join as join7, resolve as resolve4 } from "path";
2980
+ import { resolve as resolve4 } from "path";
2964
2981
  import {
2965
2982
  AGENT_INFRA_DIRS as AGENT_INFRA_DIRS2,
2966
2983
  acquireLock as acquireLock2,
@@ -2971,7 +2988,7 @@ import {
2971
2988
  createAdHocSessionWithEvent,
2972
2989
  displayPath as displayPath4,
2973
2990
  EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION2,
2974
- findErrorCode as findErrorCode4,
2991
+ findErrorCode as findErrorCode5,
2975
2992
  isValidPrefixedId,
2976
2993
  LOCAL_CLI_EVENT_SOURCE as LOCAL_CLI_EVENT_SOURCE2,
2977
2994
  loadSessionEntries,
@@ -2983,6 +3000,35 @@ import {
2983
3000
  } from "@basou/core";
2984
3001
  import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
2985
3002
 
3003
+ // src/lib/input-file.ts
3004
+ import { readFile as readFile3 } from "fs/promises";
3005
+ import { findErrorCode as findErrorCode4 } from "@basou/core";
3006
+ function refuseDashInputFile(flag, value, alternative) {
3007
+ if (value !== "-") return;
3008
+ if (alternative === "stdin") {
3009
+ throw new Error(
3010
+ `${flag} - is refused: - is reserved for standard input. Omit ${flag} to read stdin, or pass ${flag} ./- to read a file named -.`
3011
+ );
3012
+ }
3013
+ 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}`;
3014
+ throw new Error(
3015
+ `${flag} - is refused: - is reserved for standard input, which this command does not read. ${toFile}; a file named - is read with ${flag} ./-.`
3016
+ );
3017
+ }
3018
+ async function readInputFileText(flag, path) {
3019
+ try {
3020
+ return await readFile3(path, "utf8");
3021
+ } catch (error) {
3022
+ if (findErrorCode4(error, "ENOENT")) {
3023
+ throw new Error(`${flag} names a file that does not exist`, { cause: error });
3024
+ }
3025
+ if (findErrorCode4(error, "EISDIR")) {
3026
+ throw new Error(`${flag} names a directory, not a file`, { cause: error });
3027
+ }
3028
+ throw new Error(`Could not read the file ${flag} names`, { cause: error });
3029
+ }
3030
+ }
3031
+
2986
3032
  // src/commands/decision-gaps.ts
2987
3033
  import {
2988
3034
  basouPaths as basouPaths5,
@@ -3350,6 +3396,7 @@ async function runDecisionCapture(options, ctx = {}) {
3350
3396
  }
3351
3397
  }
3352
3398
  async function doRunDecisionCapture(options, ctx) {
3399
+ refuseDashInputFile("--file", options.file, "stdin");
3353
3400
  const cwd = ctx.cwd ?? process.cwd();
3354
3401
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "decision capture");
3355
3402
  const paths = basouPaths6(repositoryRoot);
@@ -3511,9 +3558,8 @@ function isDecisionId(value) {
3511
3558
  async function decisionExists(paths, decisionId) {
3512
3559
  const entries = await loadSessionEntries(paths, { now: /* @__PURE__ */ new Date() });
3513
3560
  for (const entry of entries) {
3514
- const sessionDir = join7(paths.sessions, entry.sessionId);
3515
3561
  try {
3516
- for await (const ev of replayEvents2(sessionDir, {})) {
3562
+ for await (const ev of replayEvents2(paths, entry.sessionId, {})) {
3517
3563
  if (ev.type === "decision_recorded" && ev.decision_id === decisionId) return true;
3518
3564
  }
3519
3565
  } catch {
@@ -3565,14 +3611,7 @@ function parseReason(raw) {
3565
3611
  }
3566
3612
  async function readCaptureInput(options, ctx) {
3567
3613
  if (options.file !== void 0) {
3568
- try {
3569
- return await readFile3(options.file, "utf8");
3570
- } catch (error) {
3571
- if (findErrorCode4(error, "ENOENT")) {
3572
- throw new Error(`Input file not found: ${options.file}`);
3573
- }
3574
- throw error;
3575
- }
3614
+ return await readInputFileText("--file", options.file);
3576
3615
  }
3577
3616
  if (ctx.readInput !== void 0) {
3578
3617
  return await ctx.readInput();
@@ -3911,7 +3950,7 @@ async function assertWorkspaceInitialized4(basouRoot) {
3911
3950
  try {
3912
3951
  await assertBasouRootSafe4(basouRoot);
3913
3952
  } catch (error) {
3914
- if (findErrorCode4(error, "ENOENT")) {
3953
+ if (findErrorCode5(error, "ENOENT")) {
3915
3954
  throw new Error("Workspace not initialized. Run 'basou init' first.");
3916
3955
  }
3917
3956
  throw error;
@@ -3922,7 +3961,7 @@ async function assertWorkspaceInitialized4(basouRoot) {
3922
3961
  import {
3923
3962
  assertBasouRootSafe as assertBasouRootSafe5,
3924
3963
  basouPaths as basouPaths7,
3925
- findErrorCode as findErrorCode5,
3964
+ findErrorCode as findErrorCode6,
3926
3965
  readMarkdownFile as readMarkdownFile4,
3927
3966
  renderDecisions as renderDecisions2,
3928
3967
  renderWithMarkers as renderWithMarkers2,
@@ -3964,7 +4003,7 @@ async function assertWorkspaceInitialized5(basouRoot) {
3964
4003
  try {
3965
4004
  await assertBasouRootSafe5(basouRoot);
3966
4005
  } catch (error) {
3967
- if (findErrorCode5(error, "ENOENT")) {
4006
+ if (findErrorCode6(error, "ENOENT")) {
3968
4007
  throw new Error("Workspace not initialized. Run 'basou init' first.");
3969
4008
  }
3970
4009
  throw error;
@@ -3974,7 +4013,7 @@ async function assertWorkspaceInitialized5(basouRoot) {
3974
4013
  // src/commands/exec.ts
3975
4014
  import { mkdir as mkdir2, rm as rm2 } from "fs/promises";
3976
4015
  import { homedir as homedir4 } from "os";
3977
- import { join as join8 } from "path";
4016
+ import { join as join7 } from "path";
3978
4017
  import {
3979
4018
  acquireLock as acquireLock3,
3980
4019
  assertBasouRootSafe as assertBasouRootSafe6,
@@ -3985,17 +4024,37 @@ import {
3985
4024
  EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION3,
3986
4025
  finalizeSessionYaml,
3987
4026
  getSnapshot,
4027
+ lstatSessionFile,
3988
4028
  overwriteYamlFile,
3989
4029
  parseDuration,
3990
4030
  prefixedUlid as prefixedUlid3,
3991
4031
  readManifest as readManifest6,
3992
4032
  readYamlFile as readYamlFile2,
3993
4033
  SESSION_SCHEMA_VERSION,
4034
+ SessionFileNotAFileError as SessionFileNotAFileError3,
3994
4035
  SessionSchema,
3995
4036
  sanitizeWorkingDirectory,
3996
4037
  writeObservedDuration,
3997
4038
  writeYamlFile
3998
4039
  } from "@basou/core";
4040
+
4041
+ // src/lib/signal-exit-code.ts
4042
+ import { constants } from "os";
4043
+ var PROMISED_SIGNAL_NUMBERS = {
4044
+ SIGHUP: 1,
4045
+ SIGINT: 2,
4046
+ SIGQUIT: 3,
4047
+ SIGKILL: 9,
4048
+ SIGTERM: 15
4049
+ };
4050
+ var UNNUMBERED_SIGNAL_EXIT_CODE = 255;
4051
+ function signalToExitCode(sig) {
4052
+ if (sig === null) return 1;
4053
+ const num = PROMISED_SIGNAL_NUMBERS[sig] ?? constants.signals[sig];
4054
+ return num === void 0 ? UNNUMBERED_SIGNAL_EXIT_CODE : 128 + num;
4055
+ }
4056
+
4057
+ // src/commands/exec.ts
3999
4058
  function registerExecCommand(program2) {
4000
4059
  program2.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) => {
4001
4060
  try {
@@ -4018,12 +4077,12 @@ async function runExec(command, args, options, ctx = {}) {
4018
4077
  const manifest = await readManifest6(paths);
4019
4078
  await assertSessionStoreSafe2(paths);
4020
4079
  const sessionId = prefixedUlid3("ses");
4021
- const sessionDir = join8(paths.sessions, sessionId);
4080
+ const sessionDir = join7(paths.sessions, sessionId);
4022
4081
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
4023
4082
  await coreAppendChainedEvent(paths, sessionId, event);
4024
4083
  });
4025
4084
  const startedAt = now().toISOString();
4026
- const sessionYamlPath = join8(sessionDir, "session.yaml");
4085
+ const sessionYamlPath = join7(sessionDir, "session.yaml");
4027
4086
  const session = buildInitialSession({
4028
4087
  id: sessionId,
4029
4088
  command,
@@ -4072,7 +4131,7 @@ async function runExec(command, args, options, ctx = {}) {
4072
4131
  });
4073
4132
  const runningLock = await acquireLock3(paths, "session", sessionId);
4074
4133
  try {
4075
- await mutateSessionYaml(sessionYamlPath, (s) => {
4134
+ await mutateSessionYaml(paths, sessionId, (s) => {
4076
4135
  s.session.status = "running";
4077
4136
  });
4078
4137
  } finally {
@@ -4192,18 +4251,6 @@ function decideFinalStatus(result, signalReceived) {
4192
4251
  if (result.exit_code === 0) return "completed";
4193
4252
  return "failed";
4194
4253
  }
4195
- var SIGNUM_MAP = {
4196
- SIGHUP: 1,
4197
- SIGINT: 2,
4198
- SIGQUIT: 3,
4199
- SIGKILL: 9,
4200
- SIGTERM: 15
4201
- };
4202
- function signalToExitCode(sig) {
4203
- if (sig === null) return 1;
4204
- const num = SIGNUM_MAP[sig] ?? 1;
4205
- return 128 + num;
4206
- }
4207
4254
  async function tryAppendGitSnapshot(sessionDir, sessionId, repoRoot, now, appendEvent) {
4208
4255
  let snapshot;
4209
4256
  try {
@@ -4261,7 +4308,14 @@ function buildInitialSession(input) {
4261
4308
  }
4262
4309
  };
4263
4310
  }
4264
- async function mutateSessionYaml(filePath, mutator) {
4311
+ async function mutateSessionYaml(paths, sessionId, mutator) {
4312
+ const kind = await lstatSessionFile(paths, sessionId, "session.yaml").catch((error) => {
4313
+ throw new Error("Failed to read YAML file", { cause: error });
4314
+ });
4315
+ if (kind === "symlink" || kind === "not_a_file") {
4316
+ throw new SessionFileNotAFileError3(sessionId, "session.yaml", kind);
4317
+ }
4318
+ const filePath = join7(paths.sessions, sessionId, "session.yaml");
4265
4319
  const raw = await readYamlFile2(filePath);
4266
4320
  const parsed = SessionSchema.parse(raw);
4267
4321
  mutator(parsed);
@@ -4315,7 +4369,7 @@ async function finalizeSessionAsFailed(paths, sessionDir, sessionId, appendEvent
4315
4369
  import {
4316
4370
  assertBasouRootSafe as assertBasouRootSafe7,
4317
4371
  basouPaths as basouPaths9,
4318
- findErrorCode as findErrorCode6,
4372
+ findErrorCode as findErrorCode7,
4319
4373
  readMarkdownFile as readMarkdownFile5,
4320
4374
  renderHandoff as renderHandoff2,
4321
4375
  renderWithMarkers as renderWithMarkers3,
@@ -4373,7 +4427,7 @@ async function assertWorkspaceInitialized6(basouRoot) {
4373
4427
  try {
4374
4428
  await assertBasouRootSafe7(basouRoot);
4375
4429
  } catch (error) {
4376
- if (findErrorCode6(error, "ENOENT")) {
4430
+ if (findErrorCode7(error, "ENOENT")) {
4377
4431
  throw new Error("Workspace not initialized. Run 'basou init' first.");
4378
4432
  }
4379
4433
  throw error;
@@ -4384,7 +4438,7 @@ async function assertWorkspaceInitialized6(basouRoot) {
4384
4438
  import { execFile } from "child_process";
4385
4439
  import { open as open2, readFile as readFile4, realpath as realpath4, stat as stat4 } from "fs/promises";
4386
4440
  import { homedir as homedir7 } from "os";
4387
- import { join as join11 } from "path";
4441
+ import { join as join10 } from "path";
4388
4442
  import { fileURLToPath } from "url";
4389
4443
  import { promisify } from "util";
4390
4444
  import {
@@ -4545,15 +4599,15 @@ function describeCodexHookTrust(trust) {
4545
4599
 
4546
4600
  // src/lib/protocols-config.ts
4547
4601
  import { homedir as homedir5 } from "os";
4548
- import { isAbsolute, join as join9, resolve as resolve5 } from "path";
4602
+ import { isAbsolute, join as join8, resolve as resolve5 } from "path";
4549
4603
  import { readYamlFile as readYamlFile3 } from "@basou/core";
4550
- var DEFAULT_PROTOCOLS_CONFIG_PATH = join9(homedir5(), ".basou", "protocols.yaml");
4551
- var DEFAULT_TARGET_PATH = join9(homedir5(), ".claude", "CLAUDE.md");
4604
+ var DEFAULT_PROTOCOLS_CONFIG_PATH = join8(homedir5(), ".basou", "protocols.yaml");
4605
+ var DEFAULT_TARGET_PATH = join8(homedir5(), ".claude", "CLAUDE.md");
4552
4606
  var ALLOWED_TOP_KEYS = /* @__PURE__ */ new Set(["version", "protocols"]);
4553
4607
  var ALLOWED_ENTRY_KEYS = /* @__PURE__ */ new Set(["source", "title"]);
4554
4608
  function expandTilde(p) {
4555
4609
  if (p === "~") return homedir5();
4556
- if (p.startsWith("~/")) return join9(homedir5(), p.slice(2));
4610
+ if (p.startsWith("~/")) return join8(homedir5(), p.slice(2));
4557
4611
  return p;
4558
4612
  }
4559
4613
  function isRecord(value) {
@@ -4624,19 +4678,19 @@ async function loadProtocolsConfig(configPath = DEFAULT_PROTOCOLS_CONFIG_PATH) {
4624
4678
  import {
4625
4679
  assertBasouRootSafe as assertBasouRootSafe8,
4626
4680
  basouPaths as basouPaths10,
4627
- findErrorCode as findErrorCode7,
4681
+ findErrorCode as findErrorCode8,
4628
4682
  renderOrientation as renderOrientation2,
4629
4683
  writeMarkdownFile as writeMarkdownFile4
4630
4684
  } from "@basou/core";
4631
4685
 
4632
4686
  // src/lib/hosts-config.ts
4633
4687
  import { homedir as homedir6 } from "os";
4634
- import { isAbsolute as isAbsolute2, join as join10, resolve as resolve6 } from "path";
4688
+ import { isAbsolute as isAbsolute2, join as join9, resolve as resolve6 } from "path";
4635
4689
  import { readYamlFile as readYamlFile4 } from "@basou/core";
4636
- var DEFAULT_HOSTS_CONFIG_PATH = join10(homedir6(), ".basou", "hosts.yaml");
4690
+ var DEFAULT_HOSTS_CONFIG_PATH = join9(homedir6(), ".basou", "hosts.yaml");
4637
4691
  function expandTilde2(p) {
4638
4692
  if (p === "~") return homedir6();
4639
- if (p.startsWith("~/")) return join10(homedir6(), p.slice(2));
4693
+ if (p.startsWith("~/")) return join9(homedir6(), p.slice(2));
4640
4694
  return p;
4641
4695
  }
4642
4696
  function isRecord2(value) {
@@ -4781,7 +4835,7 @@ async function assertWorkspaceInitialized7(basouRoot) {
4781
4835
  try {
4782
4836
  await assertBasouRootSafe8(basouRoot);
4783
4837
  } catch (error) {
4784
- if (findErrorCode7(error, "ENOENT")) {
4838
+ if (findErrorCode8(error, "ENOENT")) {
4785
4839
  throw new Error("Workspace not initialized. Run 'basou init' first.");
4786
4840
  }
4787
4841
  throw error;
@@ -5220,7 +5274,7 @@ function parseMinEdits(raw) {
5220
5274
  if (raw === void 0 || !/^\d+$/.test(raw)) return void 0;
5221
5275
  return Number(raw);
5222
5276
  }
5223
- var DEFAULT_CLAUDE_SETTINGS_PATH = join11(homedir7(), ".claude", "settings.json");
5277
+ var DEFAULT_CLAUDE_SETTINGS_PATH = join10(homedir7(), ".claude", "settings.json");
5224
5278
  function resolveCliEntry() {
5225
5279
  return fileURLToPath(import.meta.url);
5226
5280
  }
@@ -5577,7 +5631,7 @@ function describeHookMode(tiers) {
5577
5631
  const gates = tiers.review ? "capture + review" : "capture";
5578
5632
  return `${enforcement}, ${gates}`;
5579
5633
  }
5580
- var DEFAULT_CODEX_FACE_PATH = join11(homedir7(), ".codex", "AGENTS.md");
5634
+ var DEFAULT_CODEX_FACE_PATH = join10(homedir7(), ".codex", "AGENTS.md");
5581
5635
  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.`;
5582
5636
  async function faceHasLeftoverOrientationBlock(facePath) {
5583
5637
  try {
@@ -5589,8 +5643,8 @@ async function faceHasLeftoverOrientationBlock(facePath) {
5589
5643
  return false;
5590
5644
  }
5591
5645
  }
5592
- var DEFAULT_CODEX_HOOKS_PATH = join11(homedir7(), ".codex", "hooks.json");
5593
- var DEFAULT_CODEX_CONFIG_PATH = join11(homedir7(), ".codex", "config.toml");
5646
+ var DEFAULT_CODEX_HOOKS_PATH = join10(homedir7(), ".codex", "hooks.json");
5647
+ var DEFAULT_CODEX_CONFIG_PATH = join10(homedir7(), ".codex", "config.toml");
5594
5648
  async function readHooksFile(path) {
5595
5649
  let raw;
5596
5650
  try {
@@ -5836,7 +5890,6 @@ function renderGitignoreWarning(error, verbose) {
5836
5890
  }
5837
5891
 
5838
5892
  // src/commands/note.ts
5839
- import { readFile as readFile5 } from "fs/promises";
5840
5893
  import { homedir as homedir8 } from "os";
5841
5894
  import { resolve as resolve8 } from "path";
5842
5895
  import {
@@ -5846,7 +5899,7 @@ import {
5846
5899
  basouPaths as basouPaths12,
5847
5900
  createAdHocSessionWithEvent as createAdHocSessionWithEvent2,
5848
5901
  EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION4,
5849
- findErrorCode as findErrorCode8,
5902
+ findErrorCode as findErrorCode9,
5850
5903
  LOCAL_CLI_EVENT_SOURCE as LOCAL_CLI_EVENT_SOURCE3,
5851
5904
  readManifest as readManifest8,
5852
5905
  resolveSessionId as resolveSessionId2,
@@ -6007,22 +6060,12 @@ async function readNoteBody(bodyArgument, options, ctx, cwd) {
6007
6060
  if (bodyArgument !== void 0 && options.file !== void 0) {
6008
6061
  throw new Error("Pass the note text either as an argument or with --file, not both.");
6009
6062
  }
6063
+ refuseDashInputFile("--file", options.file, "stdin");
6010
6064
  if (bodyArgument !== void 0) {
6011
6065
  return { body: bodyArgument, source: "argument" };
6012
6066
  }
6013
6067
  if (options.file !== void 0) {
6014
- let raw;
6015
- try {
6016
- raw = await readFile5(resolve8(cwd, options.file), "utf8");
6017
- } catch (error) {
6018
- if (findErrorCode8(error, "ENOENT")) {
6019
- throw new Error("--file names a file that does not exist", { cause: error });
6020
- }
6021
- if (findErrorCode8(error, "EISDIR")) {
6022
- throw new Error("--file names a directory, not a file", { cause: error });
6023
- }
6024
- throw new Error("Could not read the file --file names", { cause: error });
6025
- }
6068
+ const raw = await readInputFileText("--file", resolve8(cwd, options.file));
6026
6069
  return { body: dropTrailingNewlines(raw), source: "file" };
6027
6070
  }
6028
6071
  if (ctx.readInput !== void 0) {
@@ -6091,7 +6134,7 @@ async function assertWorkspaceInitialized8(basouRoot) {
6091
6134
  try {
6092
6135
  await assertBasouRootSafe9(basouRoot);
6093
6136
  } catch (error) {
6094
- if (findErrorCode8(error, "ENOENT")) {
6137
+ if (findErrorCode9(error, "ENOENT")) {
6095
6138
  throw new Error("Workspace not initialized. Run 'basou init' first.");
6096
6139
  }
6097
6140
  throw error;
@@ -6208,7 +6251,7 @@ import {
6208
6251
  writeFileSync,
6209
6252
  writeSync
6210
6253
  } from "fs";
6211
- import { basename as basename7, dirname as dirname7, isAbsolute as isAbsolute3, join as join12, relative as relative3, resolve as resolve9 } from "path";
6254
+ import { basename as basename7, dirname as dirname7, isAbsolute as isAbsolute3, join as join11, relative as relative3, resolve as resolve9 } from "path";
6212
6255
  import {
6213
6256
  appendBasouGitignore as appendBasouGitignore2,
6214
6257
  basouPaths as basouPaths13,
@@ -6589,7 +6632,7 @@ function classifySourceRoot(repositoryRoot, declaredPath) {
6589
6632
  } catch {
6590
6633
  return { path: declaredPath, kind: "unresolved" };
6591
6634
  }
6592
- return { path: declaredPath, kind: existsSync(join12(real, ".git")) ? "repo" : "non-repo" };
6635
+ return { path: declaredPath, kind: existsSync(join11(real, ".git")) ? "repo" : "non-repo" };
6593
6636
  }
6594
6637
  async function doRunProjectAdopt(options, ctx) {
6595
6638
  const cwd = ctx.cwd ?? process.cwd();
@@ -6693,7 +6736,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
6693
6736
  } catch {
6694
6737
  return { ...base, reachable: false, instructionFiles: [] };
6695
6738
  }
6696
- if (!existsSync(join12(real, ".git"))) {
6739
+ if (!existsSync(join11(real, ".git"))) {
6697
6740
  return { ...base, reachable: false, instructionFiles: [] };
6698
6741
  }
6699
6742
  try {
@@ -6701,7 +6744,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
6701
6744
  for (const name of INSTRUCTION_FILES) {
6702
6745
  let present = true;
6703
6746
  try {
6704
- lstatSync(join12(real, name));
6747
+ lstatSync(join11(real, name));
6705
6748
  } catch {
6706
6749
  present = false;
6707
6750
  }
@@ -6812,10 +6855,10 @@ function gatherRepoGitignore(repositoryRoot, entry) {
6812
6855
  } catch {
6813
6856
  return { ...base, reachable: false, currentLines: [] };
6814
6857
  }
6815
- if (!existsSync(join12(real, ".git"))) {
6858
+ if (!existsSync(join11(real, ".git"))) {
6816
6859
  return { ...base, reachable: false, currentLines: [] };
6817
6860
  }
6818
- return { ...base, reachable: true, currentLines: readGitignoreLines(join12(real, ".gitignore")) };
6861
+ return { ...base, reachable: true, currentLines: readGitignoreLines(join11(real, ".gitignore")) };
6819
6862
  }
6820
6863
  function hasErrorCode(error) {
6821
6864
  return error instanceof Error && typeof error.code === "string";
@@ -6829,7 +6872,7 @@ function readGitignoreLines(file) {
6829
6872
  }
6830
6873
  }
6831
6874
  function applyGitignorePlan(repositoryRoot, plan) {
6832
- const file = join12(realpathSync(resolve9(repositoryRoot, plan.path)), ".gitignore");
6875
+ const file = join11(realpathSync(resolve9(repositoryRoot, plan.path)), ".gitignore");
6833
6876
  let existing = "";
6834
6877
  try {
6835
6878
  existing = readFileSync(file, "utf8");
@@ -6969,7 +7012,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
6969
7012
  return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
6970
7013
  }
6971
7014
  if (real === anchorReal) {
6972
- const anchorCanonical = join12(real, CANONICAL_FILE);
7015
+ const anchorCanonical = join11(real, CANONICAL_FILE);
6973
7016
  const anchorState = anchorCanonicalState(anchorCanonical);
6974
7017
  if (anchorState === "absent") {
6975
7018
  return { ...base, isAnchor: true, reachable: true, canonicalPresent: false, files: [] };
@@ -6989,7 +7032,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
6989
7032
  anchorCanonical,
6990
7033
  "self"
6991
7034
  ).map((spec) => {
6992
- const { state, actualTarget } = inspectSymlink(join12(real, spec.name), spec.target);
7035
+ const { state, actualTarget } = inspectSymlink(join11(real, spec.name), spec.target);
6993
7036
  return {
6994
7037
  name: spec.name,
6995
7038
  expectedTarget: spec.target,
@@ -7006,16 +7049,16 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
7006
7049
  files: anchorFiles
7007
7050
  };
7008
7051
  }
7009
- if (!existsSync(join12(real, ".git"))) {
7052
+ if (!existsSync(join11(real, ".git"))) {
7010
7053
  return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
7011
7054
  }
7012
- const canonicalFile = isSelf ? join12(real, CANONICAL_FILE) : join12(anchorReal, "agents", basename7(real), CANONICAL_FILE);
7055
+ const canonicalFile = isSelf ? join11(real, CANONICAL_FILE) : join11(anchorReal, "agents", basename7(real), CANONICAL_FILE);
7013
7056
  if (!existsSync(canonicalFile)) {
7014
7057
  return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
7015
7058
  }
7016
7059
  const files = expectedSymlinkTargets(real, canonicalFile, mode).map(
7017
7060
  (spec) => {
7018
- const { state, actualTarget } = inspectSymlink(join12(real, spec.name), spec.target);
7061
+ const { state, actualTarget } = inspectSymlink(join11(real, spec.name), spec.target);
7019
7062
  return {
7020
7063
  name: spec.name,
7021
7064
  expectedTarget: spec.target,
@@ -7044,7 +7087,7 @@ function applySymlinkPlan(repositoryRoot, plan) {
7044
7087
  const created = [];
7045
7088
  const failed = [];
7046
7089
  for (const { name, target } of plan.toCreate) {
7047
- const filePath = join12(real, name);
7090
+ const filePath = join11(real, name);
7048
7091
  try {
7049
7092
  mkdirSync(dirname7(filePath), { recursive: true });
7050
7093
  symlinkSync(target, filePath);
@@ -7078,7 +7121,7 @@ function gatherViewSymlinks(repositoryRoot, anchorReal, roster, viewDir) {
7078
7121
  if (!existsSync(canonicalFile)) return { kind: "missing-canonical", viewName };
7079
7122
  const files = expectedSymlinkTargets(viewDir, canonicalFile, "hub").map(
7080
7123
  (spec) => {
7081
- const { state, actualTarget } = inspectSymlink(join12(viewDir, spec.name), spec.target);
7124
+ const { state, actualTarget } = inspectSymlink(join11(viewDir, spec.name), spec.target);
7082
7125
  return {
7083
7126
  name: spec.name,
7084
7127
  expectedTarget: spec.target,
@@ -7094,7 +7137,7 @@ function applyViewSymlinks(viewDir, files) {
7094
7137
  const failed = [];
7095
7138
  for (const f of files) {
7096
7139
  if (f.state !== "missing") continue;
7097
- const filePath = join12(viewDir, f.name);
7140
+ const filePath = join11(viewDir, f.name);
7098
7141
  try {
7099
7142
  mkdirSync(dirname7(filePath), { recursive: true });
7100
7143
  symlinkSync(f.expectedTarget, filePath);
@@ -7316,7 +7359,7 @@ function resolveViewDir(repositoryRoot, viewPath) {
7316
7359
  return realpathSync(abs);
7317
7360
  } catch {
7318
7361
  try {
7319
- return join12(realpathSync(dirname7(abs)), basename7(abs));
7362
+ return join11(realpathSync(dirname7(abs)), basename7(abs));
7320
7363
  } catch {
7321
7364
  return abs;
7322
7365
  }
@@ -7334,7 +7377,7 @@ function gatherViewRepo(repositoryRoot, viewDir, entry) {
7334
7377
  return { path: entry.path, reachable: false };
7335
7378
  }
7336
7379
  const linkName = basename7(repoReal);
7337
- const { state, actualTarget } = inspectSymlink(join12(viewDir, linkName), expectedTarget);
7380
+ const { state, actualTarget } = inspectSymlink(join11(viewDir, linkName), expectedTarget);
7338
7381
  return {
7339
7382
  path: entry.path,
7340
7383
  reachable: true,
@@ -7348,7 +7391,7 @@ function applyViewPlan(viewDir, toCreate) {
7348
7391
  const created = [];
7349
7392
  const failed = [];
7350
7393
  for (const { name, target } of toCreate) {
7351
- const filePath = join12(viewDir, name);
7394
+ const filePath = join11(viewDir, name);
7352
7395
  try {
7353
7396
  mkdirSync(dirname7(filePath), { recursive: true });
7354
7397
  symlinkSync(target, filePath);
@@ -7363,7 +7406,7 @@ var TOP_LEVEL_INSTRUCTION_FILES_LOWER = new Set(
7363
7406
  INSTRUCTION_FILES.filter((f) => !f.includes("/")).map((f) => f.toLowerCase())
7364
7407
  );
7365
7408
  function classifyViewLink(viewDir, name, rosterRealpaths) {
7366
- const filePath = join12(viewDir, name);
7409
+ const filePath = join11(viewDir, name);
7367
7410
  let isLink;
7368
7411
  try {
7369
7412
  isLink = lstatSync(filePath).isSymbolicLink();
@@ -7392,7 +7435,7 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
7392
7435
  if (!isDir) {
7393
7436
  return { target, kind: existsSync(resolved) ? "non-repo" : "broken" };
7394
7437
  }
7395
- return { target, kind: existsSync(join12(resolved, ".git")) ? "repo" : "non-repo" };
7438
+ return { target, kind: existsSync(join11(resolved, ".git")) ? "repo" : "non-repo" };
7396
7439
  }
7397
7440
  function gatherExistingViewLinks(viewDir, rosterRealpaths) {
7398
7441
  let names;
@@ -7417,7 +7460,7 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
7417
7460
  const pruned = [];
7418
7461
  const failed = [];
7419
7462
  for (const { name } of toPrune) {
7420
- const filePath = join12(viewDir, name);
7463
+ const filePath = join11(viewDir, name);
7421
7464
  const c = classifyViewLink(viewDir, name, rosterRealpaths);
7422
7465
  if (c === null || c.kind !== "repo") {
7423
7466
  failed.push({
@@ -7630,10 +7673,10 @@ async function runProjectPreset(options, ctx = {}) {
7630
7673
  }
7631
7674
  }
7632
7675
  function canonicalFileFor(anchorReal, canonicalName) {
7633
- return join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
7676
+ return join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
7634
7677
  }
7635
7678
  function canonicalLabelFor(canonicalName) {
7636
- return join12("agents", canonicalName, CANONICAL_FILE);
7679
+ return join11("agents", canonicalName, CANONICAL_FILE);
7637
7680
  }
7638
7681
  async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
7639
7682
  const declared = {
@@ -7654,7 +7697,7 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
7654
7697
  if (real === anchorReal) {
7655
7698
  return { ...declared, isAnchor: true, reachable: true, canonicalPresent: false };
7656
7699
  }
7657
- if (!existsSync(join12(real, ".git"))) {
7700
+ if (!existsSync(join11(real, ".git"))) {
7658
7701
  return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
7659
7702
  }
7660
7703
  const canonicalName = basename7(real);
@@ -8036,24 +8079,24 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
8036
8079
  const instructionFiles = [];
8037
8080
  for (const name of INSTRUCTION_FILES) {
8038
8081
  try {
8039
- lstatSync(join12(real, name));
8082
+ lstatSync(join11(real, name));
8040
8083
  instructionFiles.push(name);
8041
8084
  } catch {
8042
8085
  }
8043
8086
  }
8044
8087
  let ignored;
8045
8088
  try {
8046
- ignored = new Set(readGitignoreLines(join12(real, ".gitignore")).map((l) => l.trim()));
8089
+ ignored = new Set(readGitignoreLines(join11(real, ".gitignore")).map((l) => l.trim()));
8047
8090
  } catch {
8048
8091
  ignored = /* @__PURE__ */ new Set();
8049
8092
  }
8050
8093
  const gitignorePatterns = INSTRUCTION_FILES.filter((p) => ignored.has(p) || ignored.has(`/${p}`));
8051
- const canonical2 = existsSync(join12(anchorReal, "agents", canonicalName, CANONICAL_FILE));
8094
+ const canonical2 = existsSync(join11(anchorReal, "agents", canonicalName, CANONICAL_FILE));
8052
8095
  let viewLink = false;
8053
8096
  const viewPath = manifest.workspace.view;
8054
8097
  if (viewPath !== void 0) {
8055
8098
  try {
8056
- lstatSync(join12(resolveViewDir(repositoryRoot, viewPath), canonicalName));
8099
+ lstatSync(join11(resolveViewDir(repositoryRoot, viewPath), canonicalName));
8057
8100
  viewLink = true;
8058
8101
  } catch {
8059
8102
  }
@@ -8067,11 +8110,11 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
8067
8110
  };
8068
8111
  }
8069
8112
  function teardownExpectedTargets(repoReal, anchorReal, canonicalName) {
8070
- const canonicalFile = join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8113
+ const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8071
8114
  return expectedSymlinkTargets(repoReal, canonicalFile);
8072
8115
  }
8073
8116
  function viewLinkPointsAt(viewDir, name, repoReal) {
8074
- const filePath = join12(viewDir, name);
8117
+ const filePath = join11(viewDir, name);
8075
8118
  try {
8076
8119
  if (!lstatSync(filePath).isSymbolicLink()) return false;
8077
8120
  const target = readlinkSync(filePath);
@@ -8082,7 +8125,7 @@ function viewLinkPointsAt(viewDir, name, repoReal) {
8082
8125
  }
8083
8126
  }
8084
8127
  function viewLinkPointsAtPath(viewDir, name, expectedRepoPath) {
8085
- const filePath = join12(viewDir, name);
8128
+ const filePath = join11(viewDir, name);
8086
8129
  try {
8087
8130
  if (!lstatSync(filePath).isSymbolicLink()) return false;
8088
8131
  const target = readlinkSync(filePath);
@@ -8133,7 +8176,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
8133
8176
  if (!isAnchor) {
8134
8177
  if (repoReal !== void 0) {
8135
8178
  for (const spec of teardownExpectedTargets(repoReal, anchorReal, canonicalName)) {
8136
- const { state, actualTarget } = inspectSymlink(join12(repoReal, spec.name), spec.target);
8179
+ const { state, actualTarget } = inspectSymlink(join11(repoReal, spec.name), spec.target);
8137
8180
  if (isSelf) {
8138
8181
  if (state !== "missing")
8139
8182
  items.push({
@@ -8170,7 +8213,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
8170
8213
  }
8171
8214
  let ignored;
8172
8215
  try {
8173
- ignored = new Set(readGitignoreLines(join12(repoReal, ".gitignore")).map((l) => l.trim()));
8216
+ ignored = new Set(readGitignoreLines(join11(repoReal, ".gitignore")).map((l) => l.trim()));
8174
8217
  for (const p of INSTRUCTION_FILES) {
8175
8218
  if (ignored.has(p) || ignored.has(`/${p}`)) {
8176
8219
  items.push({
@@ -8193,7 +8236,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
8193
8236
  const viewPath = manifest.workspace.view;
8194
8237
  if (viewPath !== void 0) {
8195
8238
  const viewDir = resolveViewDir(repositoryRoot, viewPath);
8196
- const linkPath = join12(viewDir, canonicalName);
8239
+ const linkPath = join11(viewDir, canonicalName);
8197
8240
  let isLink = false;
8198
8241
  try {
8199
8242
  isLink = lstatSync(linkPath).isSymbolicLink();
@@ -8219,8 +8262,8 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
8219
8262
  else items.push({ kind: "view-symlink", label: canonicalName, state: "removable" });
8220
8263
  }
8221
8264
  }
8222
- const canonicalFile = join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8223
- const canonicalLabel = join12("agents", canonicalName, CANONICAL_FILE);
8265
+ const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8266
+ const canonicalLabel = join11("agents", canonicalName, CANONICAL_FILE);
8224
8267
  let canonicalIsLink = false;
8225
8268
  try {
8226
8269
  canonicalIsLink = lstatSync(canonicalFile).isSymbolicLink();
@@ -8324,12 +8367,12 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
8324
8367
  );
8325
8368
  for (const item of removable.filter((i) => i.kind === "instruction-symlink")) {
8326
8369
  const expected = expectedByName.get(item.label);
8327
- if (repoReal === null || expected === void 0 || inspectSymlink(join12(repoReal, item.label), expected).state !== "correct") {
8370
+ if (repoReal === null || expected === void 0 || inspectSymlink(join11(repoReal, item.label), expected).state !== "correct") {
8328
8371
  changed(item.label);
8329
8372
  continue;
8330
8373
  }
8331
8374
  try {
8332
- unlinkSync(join12(repoReal, item.label));
8375
+ unlinkSync(join11(repoReal, item.label));
8333
8376
  removed.push(item.label);
8334
8377
  } catch (error) {
8335
8378
  failed.push({ label: item.label, message: failureReason(error) });
@@ -8348,7 +8391,7 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
8348
8391
  continue;
8349
8392
  }
8350
8393
  try {
8351
- unlinkSync(join12(viewDir, item.label));
8394
+ unlinkSync(join11(viewDir, item.label));
8352
8395
  removed.push(`view/${item.label}`);
8353
8396
  } catch (error) {
8354
8397
  failed.push({ label: `view/${item.label}`, message: failureReason(error) });
@@ -8356,7 +8399,7 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
8356
8399
  }
8357
8400
  const NOFOLLOW = fsConstants.O_NOFOLLOW ?? 0;
8358
8401
  for (const item of removable.filter((i) => i.kind === "canonical-block")) {
8359
- const canonicalFile = join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8402
+ const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8360
8403
  try {
8361
8404
  if (lstatSync(canonicalFile).isSymbolicLink()) {
8362
8405
  changed(item.label);
@@ -8630,12 +8673,12 @@ function gatherRenameWiring(repositoryRoot, manifest, oldBasename) {
8630
8673
  } catch {
8631
8674
  return { canonicalDirOld: false, viewLinkOld: false };
8632
8675
  }
8633
- const canonicalDirOld = existsSync(join12(anchorReal, "agents", oldBasename));
8676
+ const canonicalDirOld = existsSync(join11(anchorReal, "agents", oldBasename));
8634
8677
  let viewLinkOld = false;
8635
8678
  const viewPath = manifest.workspace.view;
8636
8679
  if (viewPath !== void 0) {
8637
8680
  try {
8638
- lstatSync(join12(resolveViewDir(repositoryRoot, viewPath), oldBasename));
8681
+ lstatSync(join11(resolveViewDir(repositoryRoot, viewPath), oldBasename));
8639
8682
  viewLinkOld = true;
8640
8683
  } catch {
8641
8684
  }
@@ -8980,7 +9023,7 @@ async function doRunProjectSeedAnchor(options, ctx) {
8980
9023
  console.log("\u2139\uFE0F No repo roster declared \u2014 nothing to seed.");
8981
9024
  return;
8982
9025
  }
8983
- const anchorDoc = join12(repositoryRoot, CANONICAL_FILE);
9026
+ const anchorDoc = join11(repositoryRoot, CANONICAL_FILE);
8984
9027
  if (pathPresent(anchorDoc)) {
8985
9028
  console.log(
8986
9029
  `\u2705 The anchor's own \`${CANONICAL_FILE}\` already exists \u2014 hand-maintained, left untouched.`
@@ -9042,7 +9085,7 @@ function regularFileSpokes(repoReal) {
9042
9085
  const out = [];
9043
9086
  for (const spoke of ["CLAUDE.md", ".github/copilot-instructions.md"]) {
9044
9087
  try {
9045
- const st = lstatSync(join12(repoReal, spoke));
9088
+ const st = lstatSync(join11(repoReal, spoke));
9046
9089
  if (!st.isSymbolicLink() && st.isFile()) out.push(spoke);
9047
9090
  } catch {
9048
9091
  }
@@ -9088,8 +9131,8 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argAbs, argReal, vie
9088
9131
  };
9089
9132
  }
9090
9133
  const isAnchor = argReal === anchorReal;
9091
- const reachable = existsSync(join12(argReal, ".git"));
9092
- const canonicalFile = join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
9134
+ const reachable = existsSync(join11(argReal, ".git"));
9135
+ const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
9093
9136
  return {
9094
9137
  path,
9095
9138
  declared,
@@ -9098,13 +9141,13 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argAbs, argReal, vie
9098
9141
  reachable,
9099
9142
  canonicalName,
9100
9143
  ...viewCanonicalName !== void 0 ? { viewCanonicalName } : {},
9101
- agentsState: inspectAgentsState(join12(argReal, CANONICAL_FILE)),
9144
+ agentsState: inspectAgentsState(join11(argReal, CANONICAL_FILE)),
9102
9145
  canonicalExists: pathPresent(canonicalFile),
9103
9146
  regularSpokes: regularFileSpokes(argReal)
9104
9147
  };
9105
9148
  }
9106
9149
  function relocateAgentsFile(repoReal, canonicalFile) {
9107
- const agentsFile = join12(repoReal, CANONICAL_FILE);
9150
+ const agentsFile = join11(repoReal, CANONICAL_FILE);
9108
9151
  try {
9109
9152
  mkdirSync(dirname7(canonicalFile), { recursive: true });
9110
9153
  } catch (error) {
@@ -9220,7 +9263,7 @@ async function doRunProjectRetrofit(repo, options, ctx) {
9220
9263
  let failure;
9221
9264
  let partial = false;
9222
9265
  if (options.apply === true && plan.action === "relocate" && argReal !== void 0) {
9223
- const canonicalFile = join12(anchorReal, "agents", plan.canonicalName, CANONICAL_FILE);
9266
+ const canonicalFile = join11(anchorReal, "agents", plan.canonicalName, CANONICAL_FILE);
9224
9267
  const res = relocateAgentsFile(argReal, canonicalFile);
9225
9268
  if (res.ok) {
9226
9269
  applied = true;
@@ -9420,7 +9463,7 @@ function renderProjectRetrofit(result) {
9420
9463
  }
9421
9464
 
9422
9465
  // src/commands/protocol.ts
9423
- import { readFile as readFile6, stat as stat5 } from "fs/promises";
9466
+ import { readFile as readFile5, stat as stat5 } from "fs/promises";
9424
9467
  import {
9425
9468
  carryForwardProtocolStamp,
9426
9469
  PROTOCOL_END as PROTOCOL_END2,
@@ -9477,7 +9520,7 @@ async function readProtocolSources(entries) {
9477
9520
  for (const entry of entries) {
9478
9521
  let content;
9479
9522
  try {
9480
- content = await readFile6(entry.source, "utf8");
9523
+ content = await readFile5(entry.source, "utf8");
9481
9524
  } catch (error) {
9482
9525
  if (error instanceof Error && error.code === "ENOENT") {
9483
9526
  throw new Error(
@@ -9603,7 +9646,7 @@ async function doRunProtocolUnsync(options) {
9603
9646
  import {
9604
9647
  assertBasouRootSafe as assertBasouRootSafe10,
9605
9648
  basouPaths as basouPaths14,
9606
- findErrorCode as findErrorCode10,
9649
+ findErrorCode as findErrorCode11,
9607
9650
  loadPortfolioConfig as loadPortfolioConfig5,
9608
9651
  readManifest as readManifest10
9609
9652
  } from "@basou/core";
@@ -9612,20 +9655,20 @@ import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
9612
9655
  // src/commands/refresh-watch.ts
9613
9656
  import { readdir as readdir3, stat as stat6 } from "fs/promises";
9614
9657
  import { homedir as homedir9 } from "os";
9615
- import { join as join13 } from "path";
9658
+ import { join as join12 } from "path";
9616
9659
  import {
9617
9660
  assertApprovalStoreSafe as assertApprovalStoreSafe2,
9618
9661
  assertLockStoreSafe as assertLockStoreSafe4,
9619
9662
  assertTaskStoreSafe as assertTaskStoreSafe2,
9620
- findErrorCode as findErrorCode9
9663
+ findErrorCode as findErrorCode10
9621
9664
  } from "@basou/core";
9622
9665
  var DEFAULT_WATCH_INTERVAL_SEC = 30;
9623
9666
  var MIN_WATCH_INTERVAL_SEC = 5;
9624
9667
  var MAX_WATCH_INTERVAL_SEC = 86400;
9625
9668
  function watchedRoots(ctx) {
9626
9669
  return [
9627
- ctx.codexSessionsDir ?? join13(homedir9(), ".codex", "sessions"),
9628
- ctx.claudeProjectsDir ?? join13(homedir9(), ".claude", "projects")
9670
+ ctx.codexSessionsDir ?? join12(homedir9(), ".codex", "sessions"),
9671
+ ctx.claudeProjectsDir ?? join12(homedir9(), ".claude", "projects")
9629
9672
  ];
9630
9673
  }
9631
9674
  async function scanSourceLogs(roots) {
@@ -9635,11 +9678,11 @@ async function scanSourceLogs(roots) {
9635
9678
  try {
9636
9679
  entries = await readdir3(dir, { withFileTypes: true });
9637
9680
  } catch (error) {
9638
- if (findErrorCode9(error, "ENOENT") || findErrorCode9(error, "ENOTDIR")) return;
9681
+ if (findErrorCode10(error, "ENOENT") || findErrorCode10(error, "ENOTDIR")) return;
9639
9682
  throw new Error("Failed to read a source log directory", { cause: error });
9640
9683
  }
9641
9684
  for (const entry of entries) {
9642
- const full = join13(dir, entry.name);
9685
+ const full = join12(dir, entry.name);
9643
9686
  if (entry.isDirectory()) {
9644
9687
  await walk(full);
9645
9688
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
@@ -9647,7 +9690,7 @@ async function scanSourceLogs(roots) {
9647
9690
  const info = await stat6(full);
9648
9691
  out.set(full, { mtimeMs: info.mtimeMs, size: info.size });
9649
9692
  } catch (error) {
9650
- if (findErrorCode9(error, "ENOENT")) continue;
9693
+ if (findErrorCode10(error, "ENOENT")) continue;
9651
9694
  throw new Error("Failed to stat a source log file", { cause: error });
9652
9695
  }
9653
9696
  }
@@ -9981,7 +10024,7 @@ async function assertWorkspaceInitialized9(basouRoot) {
9981
10024
  try {
9982
10025
  await assertBasouRootSafe10(basouRoot);
9983
10026
  } catch (error) {
9984
- if (findErrorCode10(error, "ENOENT")) {
10027
+ if (findErrorCode11(error, "ENOENT")) {
9985
10028
  throw new Error("Workspace not initialized. Run 'basou init' first.");
9986
10029
  }
9987
10030
  throw error;
@@ -9993,7 +10036,7 @@ import { isAbsolute as isAbsolute4, resolve as resolve10 } from "path";
9993
10036
  import {
9994
10037
  assertBasouRootSafe as assertBasouRootSafe11,
9995
10038
  basouPaths as basouPaths15,
9996
- findErrorCode as findErrorCode11,
10039
+ findErrorCode as findErrorCode12,
9997
10040
  renderReport,
9998
10041
  writeMarkdownFile as writeMarkdownFile6
9999
10042
  } from "@basou/core";
@@ -10045,7 +10088,7 @@ async function assertWorkspaceInitialized10(basouRoot) {
10045
10088
  try {
10046
10089
  await assertBasouRootSafe11(basouRoot);
10047
10090
  } catch (error) {
10048
- if (findErrorCode11(error, "ENOENT")) {
10091
+ if (findErrorCode12(error, "ENOENT")) {
10049
10092
  throw new Error("Workspace not initialized. Run 'basou init' first.");
10050
10093
  }
10051
10094
  throw error;
@@ -10053,7 +10096,6 @@ async function assertWorkspaceInitialized10(basouRoot) {
10053
10096
  }
10054
10097
 
10055
10098
  // src/commands/review.ts
10056
- import { readFile as readFile7 } from "fs/promises";
10057
10099
  import { homedir as homedir10 } from "os";
10058
10100
  import { resolve as resolve11 } from "path";
10059
10101
  import {
@@ -10062,7 +10104,7 @@ import {
10062
10104
  buildReviewRecordedEvent,
10063
10105
  buildReviewRecordLabel,
10064
10106
  createAdHocSessionWithEvent as createAdHocSessionWithEvent3,
10065
- findErrorCode as findErrorCode12,
10107
+ findErrorCode as findErrorCode13,
10066
10108
  findUnbindableRepos,
10067
10109
  parseReviewRecordInput,
10068
10110
  readManifest as readManifest11,
@@ -10138,6 +10180,7 @@ async function runReviewRecord(options, ctx = {}) {
10138
10180
  }
10139
10181
  }
10140
10182
  async function doRunReviewRecord(options, ctx) {
10183
+ refuseDashInputFile("--file", options.file, "stdin");
10141
10184
  const cwd = ctx.cwd ?? process.cwd();
10142
10185
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "review record");
10143
10186
  const paths = basouPaths16(repositoryRoot);
@@ -10196,14 +10239,7 @@ ${detail}
10196
10239
  }
10197
10240
  async function readReviewInput(options, ctx) {
10198
10241
  if (options.file !== void 0) {
10199
- try {
10200
- return await readFile7(options.file, "utf8");
10201
- } catch (error) {
10202
- if (findErrorCode12(error, "ENOENT")) {
10203
- throw new Error(`Input file not found: ${options.file}`);
10204
- }
10205
- throw error;
10206
- }
10242
+ return await readInputFileText("--file", options.file);
10207
10243
  }
10208
10244
  if (ctx.readInput !== void 0) {
10209
10245
  return await ctx.readInput();
@@ -10278,7 +10314,7 @@ async function assertWorkspaceInitialized11(basouRoot) {
10278
10314
  try {
10279
10315
  await assertBasouRootSafe12(basouRoot);
10280
10316
  } catch (error) {
10281
- if (findErrorCode12(error, "ENOENT")) {
10317
+ if (findErrorCode13(error, "ENOENT")) {
10282
10318
  throw new Error("Workspace not initialized. Run 'basou init' first.");
10283
10319
  }
10284
10320
  throw error;
@@ -10490,9 +10526,9 @@ function unattachedLines(u) {
10490
10526
  }
10491
10527
 
10492
10528
  // src/commands/run.ts
10493
- import { mkdir as mkdir3, readFile as readFile8, rm as rm3 } from "fs/promises";
10529
+ import { mkdir as mkdir3, readFile as readFile6, rm as rm3 } from "fs/promises";
10494
10530
  import { homedir as homedir11 } from "os";
10495
- import { join as join14 } from "path";
10531
+ import { join as join13 } from "path";
10496
10532
  import {
10497
10533
  acquireLock as acquireLock5,
10498
10534
  assertBasouRootSafe as assertBasouRootSafe13,
@@ -10507,6 +10543,7 @@ import {
10507
10543
  findBasouSessionStartHook as findBasouSessionStartHook2,
10508
10544
  getDiff,
10509
10545
  getSnapshot as getSnapshot2,
10546
+ lstatSessionFile as lstatSessionFile2,
10510
10547
  overwriteYamlFile as overwriteYamlFile2,
10511
10548
  prefixedUlid as prefixedUlid4,
10512
10549
  readManifest as readManifest12,
@@ -10514,6 +10551,7 @@ import {
10514
10551
  resolveClaudeCodeCommand,
10515
10552
  resolveCodexCommand,
10516
10553
  SESSION_SCHEMA_VERSION as SESSION_SCHEMA_VERSION2,
10554
+ SessionFileNotAFileError as SessionFileNotAFileError4,
10517
10555
  SessionSchema as SessionSchema2,
10518
10556
  sanitizeRelatedFiles,
10519
10557
  sanitizeWorkingDirectory as sanitizeWorkingDirectory2,
@@ -10573,12 +10611,12 @@ async function runTrackedTool(args, options, ctx, adapter) {
10573
10611
  const manifest = await readManifest12(paths);
10574
10612
  await assertSessionStoreSafe3(paths);
10575
10613
  const sessionId = prefixedUlid4("ses");
10576
- const sessionDir = join14(paths.sessions, sessionId);
10614
+ const sessionDir = join13(paths.sessions, sessionId);
10577
10615
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
10578
10616
  await coreAppendChainedEvent2(paths, sessionId, event);
10579
10617
  });
10580
10618
  const startedAt = now().toISOString();
10581
- const sessionYamlPath = join14(sessionDir, "session.yaml");
10619
+ const sessionYamlPath = join13(sessionDir, "session.yaml");
10582
10620
  const session = buildInitialSession2({
10583
10621
  id: sessionId,
10584
10622
  command,
@@ -10629,7 +10667,7 @@ async function runTrackedTool(args, options, ctx, adapter) {
10629
10667
  });
10630
10668
  const runningLock = await acquireLock5(paths, "session", sessionId);
10631
10669
  try {
10632
- await mutateSessionYaml2(sessionYamlPath, (s) => {
10670
+ await mutateSessionYaml2(paths, sessionId, (s) => {
10633
10671
  s.session.status = "running";
10634
10672
  });
10635
10673
  } finally {
@@ -10761,7 +10799,7 @@ async function runTrackedTool(args, options, ctx, adapter) {
10761
10799
  s.session.related_files = relatedFiles;
10762
10800
  });
10763
10801
  if (result.exit_code !== null) return result.exit_code;
10764
- return signalToExitCode2(signalReceived ?? result.signal);
10802
+ return signalToExitCode(signalReceived ?? result.signal);
10765
10803
  }
10766
10804
  function decideFinalStatus2(result, signalReceived) {
10767
10805
  if (signalReceived === "SIGINT" || signalReceived === "SIGTERM") return "interrupted";
@@ -10771,18 +10809,6 @@ function decideFinalStatus2(result, signalReceived) {
10771
10809
  if (result.exit_code === 0) return "completed";
10772
10810
  return "failed";
10773
10811
  }
10774
- var SIGNUM_MAP2 = {
10775
- SIGHUP: 1,
10776
- SIGINT: 2,
10777
- SIGQUIT: 3,
10778
- SIGKILL: 9,
10779
- SIGTERM: 15
10780
- };
10781
- function signalToExitCode2(sig) {
10782
- if (sig === null) return 1;
10783
- const num = SIGNUM_MAP2[sig] ?? 1;
10784
- return 128 + num;
10785
- }
10786
10812
  async function tryAppendGitSnapshot2(sessionDir, sessionId, repoRoot, now, appendEvent) {
10787
10813
  let snapshot;
10788
10814
  try {
@@ -10887,7 +10913,14 @@ function buildInitialSession2(input) {
10887
10913
  }
10888
10914
  };
10889
10915
  }
10890
- async function mutateSessionYaml2(filePath, mutator) {
10916
+ async function mutateSessionYaml2(paths, sessionId, mutator) {
10917
+ const kind = await lstatSessionFile2(paths, sessionId, "session.yaml").catch((error) => {
10918
+ throw new Error("Failed to read YAML file", { cause: error });
10919
+ });
10920
+ if (kind === "symlink" || kind === "not_a_file") {
10921
+ throw new SessionFileNotAFileError4(sessionId, "session.yaml", kind);
10922
+ }
10923
+ const filePath = join13(paths.sessions, sessionId, "session.yaml");
10891
10924
  const raw = await readYamlFile5(filePath);
10892
10925
  const parsed = SessionSchema2.parse(raw);
10893
10926
  mutator(parsed);
@@ -10940,7 +10973,7 @@ async function noteCodexHookStatusPreSpawn(_cwd, ctx) {
10940
10973
  const hooksPath = ctx.codexHooksPath ?? DEFAULT_CODEX_HOOKS_PATH;
10941
10974
  let location;
10942
10975
  try {
10943
- location = findBasouSessionStartHook2(JSON.parse(await readFile8(hooksPath, "utf8")));
10976
+ location = findBasouSessionStartHook2(JSON.parse(await readFile6(hooksPath, "utf8")));
10944
10977
  } catch (error) {
10945
10978
  if (!(error instanceof Error && error.code === "ENOENT")) return null;
10946
10979
  location = null;
@@ -10954,8 +10987,8 @@ async function noteCodexHookStatusPreSpawn(_cwd, ctx) {
10954
10987
  }
10955
10988
 
10956
10989
  // src/commands/session.ts
10957
- import { readFile as readFile9 } from "fs/promises";
10958
- import { basename as basename8, isAbsolute as isAbsolute5, join as join15, relative as relative4, sep as sep3 } from "path";
10990
+ import { readFile as readFile7 } from "fs/promises";
10991
+ import { basename as basename8, isAbsolute as isAbsolute5, join as join14, relative as relative4, sep as sep3 } from "path";
10959
10992
  import {
10960
10993
  acquireLock as acquireLock6,
10961
10994
  appendEventToExistingSession as appendEventToExistingSession3,
@@ -10965,10 +10998,11 @@ import {
10965
10998
  displayPath as displayPath5,
10966
10999
  EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION6,
10967
11000
  enumerateSessionEntries as enumerateSessionEntries2,
10968
- findErrorCode as findErrorCode13,
11001
+ findErrorCode as findErrorCode14,
10969
11002
  importSessionFromJson as importSessionFromJson2,
10970
11003
  LOCAL_CLI_EVENT_SOURCE as LOCAL_CLI_EVENT_SOURCE4,
10971
11004
  loadSessionEntries as loadSessionEntries2,
11005
+ lstatSessionFile as lstatSessionFile3,
10972
11006
  normalizeSessionTimestamps,
10973
11007
  readAllEvents,
10974
11008
  readManifest as readManifest13,
@@ -10978,6 +11012,7 @@ import {
10978
11012
  resolveSessionId as resolveSessionId3,
10979
11013
  resolveTaskId,
10980
11014
  SESSION_IMPORT_SCHEMA_VERSION as SESSION_IMPORT_SCHEMA_VERSION2,
11015
+ SessionFileNotAFileError as SessionFileNotAFileError5,
10981
11016
  SessionImportPayloadSchema as SessionImportPayloadSchema2,
10982
11017
  SessionSchema as SessionSchema3,
10983
11018
  SessionStatusSchema,
@@ -11087,20 +11122,28 @@ async function doRunSessionShow(idInput, options, ctx) {
11087
11122
  const paths = basouPaths19(repositoryRoot);
11088
11123
  await assertWorkspaceInitialized12(paths.root);
11089
11124
  const sessionId = await resolveSessionId3(paths, idInput);
11090
- const sessionDir = join15(paths.sessions, sessionId);
11091
- const sessionYamlPath = join15(sessionDir, "session.yaml");
11125
+ const sessionDir = join14(paths.sessions, sessionId);
11126
+ const sessionYamlPath = join14(sessionDir, "session.yaml");
11127
+ const yamlKind = await lstatSessionFile3(paths, sessionId, "session.yaml").catch(
11128
+ (error) => {
11129
+ throw new Error("Failed to read session", { cause: error });
11130
+ }
11131
+ );
11132
+ if (yamlKind === "symlink" || yamlKind === "not_a_file") {
11133
+ throw new SessionFileNotAFileError5(sessionId, "session.yaml", yamlKind);
11134
+ }
11092
11135
  let session;
11093
11136
  try {
11094
11137
  const raw = await readYamlFile6(sessionYamlPath);
11095
11138
  session = SessionSchema3.parse(normalizeSessionTimestamps(raw));
11096
11139
  } catch (error) {
11097
- if (findErrorCode13(error, "ENOENT")) {
11140
+ if (findErrorCode14(error, "ENOENT")) {
11098
11141
  throw new Error(`Session not found: ${idInput}`);
11099
11142
  }
11100
11143
  throw new Error("Failed to read session", { cause: error });
11101
11144
  }
11102
11145
  let eventsLostLines = 0;
11103
- const events = await readAllEvents(sessionDir, {
11146
+ const events = await readAllEvents(paths, sessionId, {
11104
11147
  onWarning: (w) => {
11105
11148
  if (w.kind === "malformed_json" || w.kind === "schema_violation") eventsLostLines++;
11106
11149
  printReplayWarning(w, sessionId);
@@ -11358,7 +11401,7 @@ async function assertWorkspaceInitialized12(basouRoot) {
11358
11401
  try {
11359
11402
  await assertBasouRootSafe14(basouRoot);
11360
11403
  } catch (error) {
11361
- if (findErrorCode13(error, "ENOENT")) {
11404
+ if (findErrorCode14(error, "ENOENT")) {
11362
11405
  throw new Error("Workspace not initialized. Run 'basou init' first.");
11363
11406
  }
11364
11407
  throw error;
@@ -11394,6 +11437,7 @@ async function runSessionImport(options, ctx = {}) {
11394
11437
  }
11395
11438
  }
11396
11439
  async function doRunSessionImport(options, ctx) {
11440
+ refuseDashInputFile("--from", options.from, { fileOnly: "the JSON" });
11397
11441
  const cwd = ctx.cwd ?? process.cwd();
11398
11442
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "import");
11399
11443
  const paths = basouPaths19(repositoryRoot);
@@ -11427,12 +11471,12 @@ async function doRunSessionImport(options, ctx) {
11427
11471
  }
11428
11472
  async function readInputFile(path) {
11429
11473
  try {
11430
- return await readFile9(path, "utf8");
11474
+ return await readFile7(path, "utf8");
11431
11475
  } catch (error) {
11432
- if (findErrorCode13(error, "ENOENT")) {
11476
+ if (findErrorCode14(error, "ENOENT")) {
11433
11477
  throw new Error("Import source not found", { cause: error });
11434
11478
  }
11435
- if (findErrorCode13(error, "EISDIR")) {
11479
+ if (findErrorCode14(error, "EISDIR")) {
11436
11480
  throw new Error("Import source is not a file", { cause: error });
11437
11481
  }
11438
11482
  throw new Error("Failed to read import source", { cause: error });
@@ -11507,9 +11551,7 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
11507
11551
  if (hasBody && hasFromFile) {
11508
11552
  throw new Error("--body and --from-file are mutually exclusive");
11509
11553
  }
11510
- if (hasFromFile && options.fromFile === "-") {
11511
- throw new Error("--from-file - (stdin) is not supported in v0.1");
11512
- }
11554
+ refuseDashInputFile("--from-file", options.fromFile, { textFlag: "--body" });
11513
11555
  const cwd = ctx.cwd ?? process.cwd();
11514
11556
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "note");
11515
11557
  const paths = basouPaths19(repositoryRoot);
@@ -11544,12 +11586,12 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
11544
11586
  }
11545
11587
  async function readNoteFile(path) {
11546
11588
  try {
11547
- return await readFile9(path, "utf8");
11589
+ return await readFile7(path, "utf8");
11548
11590
  } catch (error) {
11549
- if (findErrorCode13(error, "ENOENT")) {
11591
+ if (findErrorCode14(error, "ENOENT")) {
11550
11592
  throw new Error("Note source not found", { cause: error });
11551
11593
  }
11552
- if (findErrorCode13(error, "EISDIR")) {
11594
+ if (findErrorCode14(error, "EISDIR")) {
11553
11595
  throw new Error("Note source is not a file", { cause: error });
11554
11596
  }
11555
11597
  throw new Error("Failed to read note source", { cause: error });
@@ -11619,7 +11661,7 @@ async function doRunSessionRechain(options, ctx) {
11619
11661
  }
11620
11662
  }
11621
11663
  const tamperedCount = rows.filter(
11622
- (r) => r.reason === "tampered" || r.reason === "symlink" || r.reason === "not_a_directory"
11664
+ (r) => r.reason === "tampered" || r.reason === "symlink" || r.reason === "not_a_directory" || r.reason === "events_not_a_file" || r.reason === "yaml_not_a_file"
11623
11665
  ).length;
11624
11666
  const errorCount = rows.filter((r) => r.status === "error").length;
11625
11667
  if (options.json === true) {
@@ -11647,6 +11689,10 @@ function renderRechainRow(row, dryRun) {
11647
11689
  if (row.reason === "symlink" || row.reason === "not_a_directory") {
11648
11690
  return `skipped (${row.reason === "symlink" ? "SYMLINK" : "NOT A DIRECTORY"} \u2014 not followed; inspect with 'basou verify')`;
11649
11691
  }
11692
+ if (row.reason === "events_not_a_file" || row.reason === "yaml_not_a_file") {
11693
+ const file = row.reason === "events_not_a_file" ? "events.jsonl" : "session.yaml";
11694
+ return `skipped (${file} NOT A REGULAR FILE \u2014 not followed; inspect with 'basou verify')`;
11695
+ }
11650
11696
  return `skipped (${row.reason})`;
11651
11697
  case "error":
11652
11698
  return `error (${row.message})`;
@@ -11662,7 +11708,7 @@ import {
11662
11708
  assertBasouRootSafe as assertBasouRootSafe15,
11663
11709
  basouPaths as basouPaths20,
11664
11710
  computeWorkStats,
11665
- findErrorCode as findErrorCode14
11711
+ findErrorCode as findErrorCode15
11666
11712
  } from "@basou/core";
11667
11713
  function registerStatsCommand(program2) {
11668
11714
  program2.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) => {
@@ -11771,7 +11817,7 @@ async function assertWorkspaceInitialized13(basouRoot) {
11771
11817
  try {
11772
11818
  await assertBasouRootSafe15(basouRoot);
11773
11819
  } catch (error) {
11774
- if (findErrorCode14(error, "ENOENT")) {
11820
+ if (findErrorCode15(error, "ENOENT")) {
11775
11821
  throw new Error("Workspace not initialized. Run 'basou init' first.");
11776
11822
  }
11777
11823
  throw error;
@@ -11783,7 +11829,7 @@ import {
11783
11829
  assertBasouRootSafe as assertBasouRootSafe16,
11784
11830
  basouPaths as basouPaths21,
11785
11831
  buildStatusSnapshot,
11786
- findErrorCode as findErrorCode15,
11832
+ findErrorCode as findErrorCode16,
11787
11833
  readManifest as readManifest14,
11788
11834
  writeStatus
11789
11835
  } from "@basou/core";
@@ -11807,7 +11853,7 @@ async function doRunStatus(options, ctx) {
11807
11853
  try {
11808
11854
  await assertBasouRootSafe16(paths.root);
11809
11855
  } catch (error) {
11810
- if (findErrorCode15(error, "ENOENT")) {
11856
+ if (findErrorCode16(error, "ENOENT")) {
11811
11857
  throw new Error("Workspace not initialized. Run 'basou init' first.");
11812
11858
  }
11813
11859
  throw error;
@@ -11816,7 +11862,7 @@ async function doRunStatus(options, ctx) {
11816
11862
  try {
11817
11863
  manifest = await readManifest14(paths);
11818
11864
  } catch (error) {
11819
- if (findErrorCode15(error, "ENOENT")) {
11865
+ if (findErrorCode16(error, "ENOENT")) {
11820
11866
  throw new Error("Workspace not initialized. Run 'basou init' first.");
11821
11867
  }
11822
11868
  const gateMessage = formatVersionGateMessage(error);
@@ -11854,8 +11900,7 @@ function formatVersionGateMessage(error) {
11854
11900
  }
11855
11901
 
11856
11902
  // src/commands/task.ts
11857
- import { readFile as readFile10 } from "fs/promises";
11858
- import { join as join16 } from "path";
11903
+ import { readFile as readFile8 } from "fs/promises";
11859
11904
  import {
11860
11905
  archiveTask,
11861
11906
  assertBasouRootSafe as assertBasouRootSafe17,
@@ -11865,7 +11910,7 @@ import {
11865
11910
  deleteTask,
11866
11911
  editTask,
11867
11912
  enumerateArchivedTaskIds,
11868
- findErrorCode as findErrorCode16,
11913
+ findErrorCode as findErrorCode17,
11869
11914
  loadSessionEntries as loadSessionEntries3,
11870
11915
  loadTaskEntries,
11871
11916
  prefixedUlid as prefixedUlid5,
@@ -11952,9 +11997,7 @@ async function doRunTaskNew(options, ctx) {
11952
11997
  if (options.description !== void 0 && options.fromFile !== void 0) {
11953
11998
  throw new Error("--description and --from-file are mutually exclusive");
11954
11999
  }
11955
- if (options.fromFile === "-") {
11956
- throw new Error("--from-file - (stdin) is not supported in v0.1");
11957
- }
12000
+ refuseDashInputFile("--from-file", options.fromFile, { textFlag: "--description" });
11958
12001
  const initialStatus = options.status ?? "planned";
11959
12002
  if (options.completedAt !== void 0 && !isTerminalStatusForCli(initialStatus)) {
11960
12003
  throw new Error("--completed-at requires --status done or cancelled");
@@ -12182,9 +12225,8 @@ async function doRunTaskShow(idInput, options, ctx) {
12182
12225
  const events = [];
12183
12226
  const linkedSessionIds = new Set(doc.task.task.linked_sessions);
12184
12227
  for (const s of sessions) {
12185
- const sessionDir = join16(paths.sessions, s.sessionId);
12186
12228
  try {
12187
- for await (const ev of replayEvents3(sessionDir, {
12229
+ for await (const ev of replayEvents3(paths, s.sessionId, {
12188
12230
  onWarning: (w) => printReplayWarning(w, s.sessionId)
12189
12231
  })) {
12190
12232
  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) {
@@ -12871,12 +12913,12 @@ function parsePositiveInt2(raw) {
12871
12913
  }
12872
12914
  async function readDescriptionFile(path) {
12873
12915
  try {
12874
- return await readFile10(path, "utf8");
12916
+ return await readFile8(path, "utf8");
12875
12917
  } catch (error) {
12876
- if (findErrorCode16(error, "ENOENT")) {
12918
+ if (findErrorCode17(error, "ENOENT")) {
12877
12919
  throw new Error("Description source not found", { cause: error });
12878
12920
  }
12879
- if (findErrorCode16(error, "EISDIR")) {
12921
+ if (findErrorCode17(error, "EISDIR")) {
12880
12922
  throw new Error("Description source is not a file", { cause: error });
12881
12923
  }
12882
12924
  throw new Error("Failed to read description source", { cause: error });
@@ -12889,7 +12931,7 @@ async function assertWorkspaceInitialized14(basouRoot) {
12889
12931
  try {
12890
12932
  await assertBasouRootSafe17(basouRoot);
12891
12933
  } catch (error) {
12892
- if (findErrorCode16(error, "ENOENT")) {
12934
+ if (findErrorCode17(error, "ENOENT")) {
12893
12935
  throw new Error("Workspace not initialized. Run 'basou init' first.");
12894
12936
  }
12895
12937
  throw error;
@@ -12980,7 +13022,7 @@ import {
12980
13022
  assertBasouRootSafe as assertBasouRootSafe18,
12981
13023
  basouPaths as basouPaths23,
12982
13024
  enumerateSessionEntries as enumerateSessionEntries3,
12983
- findErrorCode as findErrorCode17,
13025
+ findErrorCode as findErrorCode18,
12984
13026
  resolveSessionId as resolveSessionId5,
12985
13027
  verifyEventsChain
12986
13028
  } from "@basou/core";
@@ -13067,7 +13109,7 @@ async function assertWorkspaceInitialized15(basouRoot) {
13067
13109
  try {
13068
13110
  await assertBasouRootSafe18(basouRoot);
13069
13111
  } catch (error) {
13070
- if (findErrorCode17(error, "ENOENT")) {
13112
+ if (findErrorCode18(error, "ENOENT")) {
13071
13113
  throw new Error("Workspace not initialized. Run 'basou init' first.");
13072
13114
  }
13073
13115
  throw error;
@@ -13077,11 +13119,11 @@ async function assertWorkspaceInitialized15(basouRoot) {
13077
13119
  // src/commands/view.ts
13078
13120
  import { spawn } from "child_process";
13079
13121
  import { createHash as createHash2 } from "crypto";
13080
- import { basename as basename11, dirname as dirname9, join as join20, resolve as resolve14 } from "path";
13122
+ import { basename as basename11, dirname as dirname9, join as join17, resolve as resolve14 } from "path";
13081
13123
  import {
13082
13124
  assertBasouRootSafe as assertBasouRootSafe19,
13083
13125
  basouPaths as basouPaths25,
13084
- findErrorCode as findErrorCode19,
13126
+ findErrorCode as findErrorCode20,
13085
13127
  loadPortfolioConfig as loadPortfolioConfig6,
13086
13128
  readManifest as readManifest19
13087
13129
  } from "@basou/core";
@@ -13091,7 +13133,7 @@ import { InvalidArgumentError as InvalidArgumentError8 } from "commander";
13091
13133
  import { createReadStream as createReadStream2 } from "fs";
13092
13134
  import { readdir as readdir4, stat as stat7 } from "fs/promises";
13093
13135
  import { homedir as homedir12 } from "os";
13094
- import { basename as basename9, dirname as dirname8, join as join17 } from "path";
13136
+ import { basename as basename9, dirname as dirname8, join as join15 } from "path";
13095
13137
  import { createInterface as createInterface2 } from "readline";
13096
13138
  import { basouPaths as basouPaths24, readManifest as readManifest16 } from "@basou/core";
13097
13139
  function uncapturedTotal(result) {
@@ -13103,8 +13145,8 @@ async function checkPortfolioCoverage(workspaces, ctx = {}) {
13103
13145
  ctx.portfolioConfigPath
13104
13146
  );
13105
13147
  const listedDirs = new Set([...roots].map((root) => encodeProjectDir(root)));
13106
- const claudeProjectsDir = ctx.claudeProjectsDir ?? join17(homedir12(), ".claude", "projects");
13107
- const codexSessionsDir = ctx.codexSessionsDir ?? join17(homedir12(), ".codex", "sessions");
13148
+ const claudeProjectsDir = ctx.claudeProjectsDir ?? join15(homedir12(), ".claude", "projects");
13149
+ const codexSessionsDir = ctx.codexSessionsDir ?? join15(homedir12(), ".codex", "sessions");
13108
13150
  const tally = new Tally(roots);
13109
13151
  const absentTrees = [];
13110
13152
  const claudeFiles = await listClaudeTranscripts(claudeProjectsDir);
@@ -13242,7 +13284,7 @@ async function isDirEntry(parent, entry) {
13242
13284
  if (entry.isDirectory()) return true;
13243
13285
  if (!entry.isSymbolicLink()) return false;
13244
13286
  try {
13245
- return (await stat7(join17(parent, entry.name))).isDirectory();
13287
+ return (await stat7(join15(parent, entry.name))).isDirectory();
13246
13288
  } catch {
13247
13289
  return false;
13248
13290
  }
@@ -13257,7 +13299,7 @@ async function listClaudeTranscripts(projectsRoot) {
13257
13299
  const files = [];
13258
13300
  for (const entry of entries) {
13259
13301
  if (!await isDirEntry(projectsRoot, entry)) continue;
13260
- const full = join17(projectsRoot, entry.name);
13302
+ const full = join15(projectsRoot, entry.name);
13261
13303
  let names;
13262
13304
  try {
13263
13305
  names = await readdir4(full);
@@ -13265,7 +13307,7 @@ async function listClaudeTranscripts(projectsRoot) {
13265
13307
  continue;
13266
13308
  }
13267
13309
  for (const name of names) {
13268
- if (name.endsWith(".jsonl")) files.push(join17(full, name));
13310
+ if (name.endsWith(".jsonl")) files.push(join15(full, name));
13269
13311
  }
13270
13312
  }
13271
13313
  return files.sort();
@@ -13280,7 +13322,7 @@ async function listCodexRollouts(sessionsRoot) {
13280
13322
  return;
13281
13323
  }
13282
13324
  for (const entry of entries) {
13283
- const full = join17(dir, entry.name);
13325
+ const full = join15(dir, entry.name);
13284
13326
  if (entry.isDirectory()) await walk(full);
13285
13327
  else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) {
13286
13328
  found.push(full);
@@ -13411,7 +13453,7 @@ function inertLines(result) {
13411
13453
  // src/lib/portfolio-safety.ts
13412
13454
  import { execFile as execFile2 } from "child_process";
13413
13455
  import { lstat as lstat3, realpath as realpath5 } from "fs/promises";
13414
- import { isAbsolute as isAbsolute6, join as join18, relative as relative5, resolve as resolve12, sep as sep4 } from "path";
13456
+ import { isAbsolute as isAbsolute6, join as join16, relative as relative5, resolve as resolve12, sep as sep4 } from "path";
13415
13457
  import { promisify as promisify2 } from "util";
13416
13458
  import { readManifest as readManifest17 } from "@basou/core";
13417
13459
  var execFileAsync2 = promisify2(execFile2);
@@ -13435,7 +13477,7 @@ function isBasouPath(p) {
13435
13477
  async function inspectRepo(repoPath) {
13436
13478
  let hasEntry = false;
13437
13479
  try {
13438
- await lstat3(join18(repoPath, ".basou"));
13480
+ await lstat3(join16(repoPath, ".basou"));
13439
13481
  hasEntry = true;
13440
13482
  } catch (error) {
13441
13483
  if (errorCode2(error) !== "ENOENT") {
@@ -13589,7 +13631,7 @@ function formatSafetyReport(result) {
13589
13631
 
13590
13632
  // src/lib/view-server.ts
13591
13633
  import { createServer } from "http";
13592
- import { basename as basename10, join as join19, resolve as resolve13 } from "path";
13634
+ import { basename as basename10, resolve as resolve13 } from "path";
13593
13635
  import {
13594
13636
  boardLivePage,
13595
13637
  boardPage,
@@ -13597,7 +13639,7 @@ import {
13597
13639
  boardPageUnavailable,
13598
13640
  computeWorkStats as computeWorkStats2,
13599
13641
  enumerateApprovals as enumerateApprovals2,
13600
- findErrorCode as findErrorCode18,
13642
+ findErrorCode as findErrorCode19,
13601
13643
  inspectSessionEntry as inspectSessionEntry2,
13602
13644
  isLazyExpired as isLazyExpired2,
13603
13645
  loadApproval as loadApproval2,
@@ -13611,7 +13653,9 @@ import {
13611
13653
  renderDecisions as renderDecisions3,
13612
13654
  renderHandoff as renderHandoff3,
13613
13655
  resolveViewLanguageFromPaths,
13656
+ SessionFileNotAFileError as SessionFileNotAFileError6,
13614
13657
  SessionIdSchema,
13658
+ SessionNotADirectoryError,
13615
13659
  summarizeOrientation,
13616
13660
  tryRemoteUrl
13617
13661
  } from "@basou/core";
@@ -15546,7 +15590,7 @@ async function overview(ws, nowProvider, resolveRemoteUrl) {
15546
15590
  try {
15547
15591
  manifest = await readManifest18(ws.paths);
15548
15592
  } catch (error) {
15549
- if (findErrorCode18(error, "ENOENT")) {
15593
+ if (findErrorCode19(error, "ENOENT")) {
15550
15594
  return { initialized: false, repoRoot: ws.repoRoot };
15551
15595
  }
15552
15596
  throw error;
@@ -15614,10 +15658,7 @@ async function sessionDetail(ws, sessionId) {
15614
15658
  }
15615
15659
  const entry = await inspectSessionEntry2(ws.paths, sessionId);
15616
15660
  if (entry === "symlink" || entry === "not_a_directory") {
15617
- throw new HttpError(
15618
- 404,
15619
- `Session ${sessionId} is not a directory; a symlink or a file there is not followed`
15620
- );
15661
+ throw new HttpError(404, new SessionNotADirectoryError(sessionId, entry).message);
15621
15662
  }
15622
15663
  let session;
15623
15664
  try {
@@ -15626,10 +15667,11 @@ async function sessionDetail(ws, sessionId) {
15626
15667
  if (error instanceof Error && error.message === "YAML file not found") {
15627
15668
  throw new HttpError(404, "Session not found");
15628
15669
  }
15670
+ if (error instanceof SessionFileNotAFileError6) throw new HttpError(404, error.message);
15629
15671
  throw error;
15630
15672
  }
15631
15673
  try {
15632
- const events = await readAllEvents2(join19(ws.paths.sessions, sessionId));
15674
+ const events = await readAllEvents2(ws.paths, sessionId);
15633
15675
  return { session, events };
15634
15676
  } catch {
15635
15677
  return { session, events: [], degraded: true };
@@ -15894,7 +15936,7 @@ async function viewBoardOf(root, paths) {
15894
15936
  own = void 0;
15895
15937
  }
15896
15938
  if (own?.visibility !== "private") return { why: "no_board" };
15897
- return { recordsDir: join20(root, dirname9(DEFAULT_BOARD_PATH), "records") };
15939
+ return { recordsDir: join17(root, dirname9(DEFAULT_BOARD_PATH), "records") };
15898
15940
  }
15899
15941
  async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
15900
15942
  const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve14(cwd, p) })) : await loadPortfolioConfig6(ctx.portfolioConfigPath);
@@ -15956,7 +15998,7 @@ async function startListening(port, deps) {
15956
15998
  try {
15957
15999
  return await startViewServer({ port, deps });
15958
16000
  } catch (error) {
15959
- if (findErrorCode19(error, "EADDRINUSE")) {
16001
+ if (findErrorCode20(error, "EADDRINUSE")) {
15960
16002
  throw new Error(`Port ${port} is already in use. Pass --port <n> to choose another.`, {
15961
16003
  cause: error
15962
16004
  });
@@ -16009,7 +16051,7 @@ async function assertWorkspaceInitialized16(basouRoot) {
16009
16051
  try {
16010
16052
  await assertBasouRootSafe19(basouRoot);
16011
16053
  } catch (error) {
16012
- if (findErrorCode19(error, "ENOENT")) {
16054
+ if (findErrorCode20(error, "ENOENT")) {
16013
16055
  throw new Error("Workspace not initialized. Run 'basou init' first.");
16014
16056
  }
16015
16057
  throw error;
@@ -16048,7 +16090,7 @@ function installExperimentalNotice(program2, names = EXPERIMENTAL_COMMANDS, writ
16048
16090
  function readBuildStamp() {
16049
16091
  if (false) return void 0;
16050
16092
  try {
16051
- return JSON.parse('{"version":"0.68.4","commit":"89725f6","committedAt":"2026-10-15T20:46:14+09:00"}');
16093
+ return JSON.parse('{"version":"0.70.0","commit":"60d302a","committedAt":"2026-10-16T17:39:30+09:00"}');
16052
16094
  } catch {
16053
16095
  return void 0;
16054
16096
  }