@basou/cli 0.58.0 → 0.59.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
@@ -12,10 +12,12 @@ import {
12
12
  acquireLock,
13
13
  appendChainedEventLocked,
14
14
  assertBasouRootSafe,
15
+ assertSessionDirSafe,
15
16
  basouPaths,
16
17
  EVENT_SCHEMA_VERSION,
17
18
  enumerateApprovals,
18
19
  findErrorCode,
20
+ inspectSessionEntry,
19
21
  isLazyExpired,
20
22
  LOCAL_CLI_EVENT_SOURCE,
21
23
  linkYamlFile,
@@ -262,13 +264,20 @@ async function doRunApprovalShow(idInput, options, ctx) {
262
264
  if (loaded === null) {
263
265
  throw new Error(`Approval not found: ${idInput}`);
264
266
  }
265
- const sessionDir = join(paths.sessions, loaded.approval.session_id);
267
+ const sessionId = loaded.approval.session_id;
268
+ const entry = await inspectSessionEntry(paths, sessionId);
266
269
  const relatedEvents = [];
267
- for await (const ev of replayEvents(sessionDir, {
268
- onWarning: (w) => printReplayWarning(w, loaded.approval.session_id)
269
- })) {
270
- if (isApprovalEvent(ev) && ev.approval_id === id) {
271
- relatedEvents.push(ev);
270
+ if (entry === "symlink" || entry === "not_a_directory") {
271
+ console.error(
272
+ `Warning: session ${sessionId} is not a directory (a symlink or a file is not followed); its events are not shown`
273
+ );
274
+ } else {
275
+ for await (const ev of replayEvents(join(paths.sessions, sessionId), {
276
+ onWarning: (w) => printReplayWarning(w, sessionId)
277
+ })) {
278
+ if (isApprovalEvent(ev) && ev.approval_id === id) {
279
+ relatedEvents.push(ev);
280
+ }
272
281
  }
273
282
  }
274
283
  const now = /* @__PURE__ */ new Date();
@@ -347,6 +356,7 @@ async function doRunApprovalResolve(idInput, options, ctx, decision) {
347
356
  const eventId = prefixedUlid("evt");
348
357
  const sessionLock = await acquireLock(paths, "session", approval.session_id);
349
358
  try {
359
+ await assertSessionDirSafe(paths, approval.session_id);
350
360
  const sessionDir = join(paths.sessions, approval.session_id);
351
361
  for await (const ev of replayEvents(sessionDir, {
352
362
  onWarning: (w) => printReplayWarning(w, approval.session_id)
@@ -2067,6 +2077,7 @@ import { join as join6 } from "path";
2067
2077
  import {
2068
2078
  acquireLock as acquireLock3,
2069
2079
  assertBasouRootSafe as assertBasouRootSafe4,
2080
+ assertSessionStoreSafe,
2070
2081
  basouPaths as basouPaths6,
2071
2082
  ChildProcessRunner,
2072
2083
  appendChainedEvent as coreAppendChainedEvent,
@@ -2105,6 +2116,7 @@ async function runExec(command, args, options, ctx = {}) {
2105
2116
  const paths = basouPaths6(repoRoot);
2106
2117
  await assertBasouRootSafe4(paths.root);
2107
2118
  const manifest = await readManifest3(paths);
2119
+ await assertSessionStoreSafe(paths);
2108
2120
  const sessionId = prefixedUlid3("ses");
2109
2121
  const sessionDir = join6(paths.sessions, sessionId);
2110
2122
  await mkdir(sessionDir, { recursive: true });
@@ -2919,6 +2931,7 @@ import { createInterface } from "readline";
2919
2931
  import {
2920
2932
  AGENT_INFRA_DIRS as AGENT_INFRA_DIRS2,
2921
2933
  assertBasouRootSafe as assertBasouRootSafe6,
2934
+ assertSessionStoreSafe as assertSessionStoreSafe2,
2922
2935
  basouPaths as basouPaths8,
2923
2936
  CLAUDE_IMPORT_SOURCE,
2924
2937
  CODEX_IMPORT_SOURCE,
@@ -2926,7 +2939,7 @@ import {
2926
2939
  claudeTranscriptToImportPayload,
2927
2940
  codexRolloutToImportPayload,
2928
2941
  displayPath as displayPath2,
2929
- enumerateSessionDirs,
2942
+ enumerateSessionEntries,
2930
2943
  findErrorCode as findErrorCode5,
2931
2944
  importSessionFromJson,
2932
2945
  observedFilesOf,
@@ -3090,7 +3103,12 @@ async function resolveImportTarget(ctx) {
3090
3103
  return { repositoryRoot, paths, manifest };
3091
3104
  }
3092
3105
  async function importDerivedSessions(paths, manifest, options, sourceKind, candidates, projectPaths, boundaryDeclared) {
3093
- const existingByExternalId = await loadExistingByExternalId(paths, sourceKind);
3106
+ await assertSessionStoreSafe2(paths);
3107
+ const { byExternalId: existingByExternalId, notDirectories } = await loadExistingByExternalId(
3108
+ paths,
3109
+ sourceKind
3110
+ );
3111
+ let notDirectoriesWarned = false;
3094
3112
  const seenThisRun = /* @__PURE__ */ new Set();
3095
3113
  const crossProjectCheck = boundaryDeclared;
3096
3114
  const crossProject = [];
@@ -3182,6 +3200,13 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
3182
3200
  }
3183
3201
  counts.replaced++;
3184
3202
  }
3203
+ if (priors.length === 0 && notDirectories.length > 0 && !notDirectoriesWarned) {
3204
+ notDirectoriesWarned = true;
3205
+ const one = notDirectories.length === 1;
3206
+ console.error(
3207
+ `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`
3208
+ );
3209
+ }
3185
3210
  const result = await importSessionFromJson(paths, manifest, payload, {
3186
3211
  dryRun: options.dryRun === true
3187
3212
  });
@@ -3258,10 +3283,11 @@ async function loadExistingByExternalId(paths, sourceKind) {
3258
3283
  else list.push(prior);
3259
3284
  };
3260
3285
  let sessionIds;
3286
+ let notDirectories;
3261
3287
  try {
3262
- sessionIds = await enumerateSessionDirs(paths);
3288
+ ({ dirs: sessionIds, notDirectories } = await enumerateSessionEntries(paths));
3263
3289
  } catch {
3264
- return byExternalId;
3290
+ return { byExternalId, notDirectories: [] };
3265
3291
  }
3266
3292
  for (const sessionId of sessionIds) {
3267
3293
  let session;
@@ -3282,7 +3308,7 @@ async function loadExistingByExternalId(paths, sourceKind) {
3282
3308
  const match = typeof label === "string" ? label.match(/^claude-code import (\S+)$/) : null;
3283
3309
  if (match?.[1] !== void 0) add(match[1], prior);
3284
3310
  }
3285
- return byExternalId;
3311
+ return { byExternalId, notDirectories };
3286
3312
  }
3287
3313
  async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
3288
3314
  if (options.session !== void 0) {
@@ -9562,6 +9588,7 @@ import { join as join14 } from "path";
9562
9588
  import {
9563
9589
  acquireLock as acquireLock5,
9564
9590
  assertBasouRootSafe as assertBasouRootSafe12,
9591
+ assertSessionStoreSafe as assertSessionStoreSafe3,
9565
9592
  basouPaths as basouPaths17,
9566
9593
  ChildProcessRunner as ChildProcessRunner2,
9567
9594
  claudeCodeAdapterMetadata,
@@ -9637,6 +9664,7 @@ async function runTrackedTool(args, options, ctx, adapter) {
9637
9664
  const paths = basouPaths17(repoRoot);
9638
9665
  await assertBasouRootSafe12(paths.root);
9639
9666
  const manifest = await readManifest10(paths);
9667
+ await assertSessionStoreSafe3(paths);
9640
9668
  const sessionId = prefixedUlid4("ses");
9641
9669
  const sessionDir = join14(paths.sessions, sessionId);
9642
9670
  await mkdir2(sessionDir, { recursive: true });
@@ -10026,7 +10054,7 @@ import {
10026
10054
  basouPaths as basouPaths18,
10027
10055
  displayPath as displayPath3,
10028
10056
  EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION6,
10029
- enumerateSessionDirs as enumerateSessionDirs2,
10057
+ enumerateSessionEntries as enumerateSessionEntries2,
10030
10058
  findErrorCode as findErrorCode12,
10031
10059
  importSessionFromJson as importSessionFromJson2,
10032
10060
  LOCAL_CLI_EVENT_SOURCE as LOCAL_CLI_EVENT_SOURCE4,
@@ -10658,7 +10686,7 @@ async function doRunSessionRechain(options, ctx) {
10658
10686
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "rechain");
10659
10687
  const paths = basouPaths18(repositoryRoot);
10660
10688
  await assertWorkspaceInitialized11(paths.root);
10661
- const sessionIds = options.session !== void 0 ? [await resolveSessionId3(paths, options.session)] : await enumerateSessionDirs2(paths);
10689
+ const sessionIds = options.session !== void 0 ? [await resolveSessionId3(paths, options.session)] : await enumerateAllSessionNames(paths);
10662
10690
  const dryRun = options.dryRun === true;
10663
10691
  const rows = [];
10664
10692
  for (const sessionId of sessionIds) {
@@ -10679,7 +10707,9 @@ async function doRunSessionRechain(options, ctx) {
10679
10707
  rows.push({ session_id: sessionId, status: "skipped", reason: outcome.reason });
10680
10708
  }
10681
10709
  }
10682
- const tamperedCount = rows.filter((r) => r.reason === "tampered").length;
10710
+ const tamperedCount = rows.filter(
10711
+ (r) => r.reason === "tampered" || r.reason === "symlink" || r.reason === "not_a_directory"
10712
+ ).length;
10683
10713
  const errorCount = rows.filter((r) => r.status === "error").length;
10684
10714
  if (options.json === true) {
10685
10715
  console.log(JSON.stringify(rows, null, 2));
@@ -10702,11 +10732,19 @@ function renderRechainRow(row, dryRun) {
10702
10732
  case "rechained":
10703
10733
  return `${dryRun ? "would rechain" : "rechained"} (${row.event_count} events)`;
10704
10734
  case "skipped":
10705
- return row.reason === "tampered" ? "skipped (TAMPERED \u2014 inspect with 'basou verify')" : `skipped (${row.reason})`;
10735
+ if (row.reason === "tampered") return "skipped (TAMPERED \u2014 inspect with 'basou verify')";
10736
+ if (row.reason === "symlink" || row.reason === "not_a_directory") {
10737
+ return `skipped (${row.reason === "symlink" ? "SYMLINK" : "NOT A DIRECTORY"} \u2014 not followed; inspect with 'basou verify')`;
10738
+ }
10739
+ return `skipped (${row.reason})`;
10706
10740
  case "error":
10707
10741
  return `error (${row.message})`;
10708
10742
  }
10709
10743
  }
10744
+ async function enumerateAllSessionNames(paths) {
10745
+ const { dirs, notDirectories } = await enumerateSessionEntries2(paths);
10746
+ return [...dirs, ...notDirectories].sort();
10747
+ }
10710
10748
 
10711
10749
  // src/commands/stats.ts
10712
10750
  import {
@@ -12054,7 +12092,7 @@ function maxLen3(values, floor) {
12054
12092
  import {
12055
12093
  assertBasouRootSafe as assertBasouRootSafe17,
12056
12094
  basouPaths as basouPaths22,
12057
- enumerateSessionEntries,
12095
+ enumerateSessionEntries as enumerateSessionEntries3,
12058
12096
  findErrorCode as findErrorCode16,
12059
12097
  resolveRepositoryRoot as resolveRepositoryRoot12,
12060
12098
  resolveSessionId as resolveSessionId5,
@@ -12088,7 +12126,7 @@ async function doRunVerify(options, ctx) {
12088
12126
  const repositoryRoot = await resolveRepositoryRootForVerify(cwd);
12089
12127
  const paths = basouPaths22(repositoryRoot);
12090
12128
  await assertWorkspaceInitialized14(paths.root);
12091
- const sessionIds = options.session !== void 0 ? [await resolveSessionId5(paths, options.session, { allowNotDirectory: true })] : await enumerateAllSessionNames(paths);
12129
+ const sessionIds = options.session !== void 0 ? [await resolveSessionId5(paths, options.session, { allowNotDirectory: true })] : await enumerateAllSessionNames2(paths);
12092
12130
  const rows = [];
12093
12131
  for (const sessionId of sessionIds) {
12094
12132
  const verdict = await verifyEventsChain(paths, sessionId);
@@ -12135,8 +12173,8 @@ function renderVerdict(row) {
12135
12173
  return "empty";
12136
12174
  }
12137
12175
  }
12138
- async function enumerateAllSessionNames(paths) {
12139
- const { dirs, notDirectories } = await enumerateSessionEntries(paths);
12176
+ async function enumerateAllSessionNames2(paths) {
12177
+ const { dirs, notDirectories } = await enumerateSessionEntries3(paths);
12140
12178
  return [...dirs, ...notDirectories].sort();
12141
12179
  }
12142
12180
  async function resolveRepositoryRootForVerify(cwd) {
@@ -12676,6 +12714,7 @@ import {
12676
12714
  computeWorkStats as computeWorkStats2,
12677
12715
  enumerateApprovals as enumerateApprovals2,
12678
12716
  findErrorCode as findErrorCode17,
12717
+ inspectSessionEntry as inspectSessionEntry2,
12679
12718
  isLazyExpired as isLazyExpired2,
12680
12719
  loadApproval as loadApproval2,
12681
12720
  loadSessionEntries as loadSessionEntries4,
@@ -13754,6 +13793,13 @@ async function sessionDetail(ws, sessionId) {
13754
13793
  if (!SessionIdSchema.safeParse(sessionId).success) {
13755
13794
  throw new HttpError(404, "Session not found");
13756
13795
  }
13796
+ const entry = await inspectSessionEntry2(ws.paths, sessionId);
13797
+ if (entry === "symlink" || entry === "not_a_directory") {
13798
+ throw new HttpError(
13799
+ 404,
13800
+ `Session ${sessionId} is not a directory; a symlink or a file there is not followed`
13801
+ );
13802
+ }
13757
13803
  let session;
13758
13804
  try {
13759
13805
  session = await readSessionYaml3(ws.paths, sessionId);
@@ -14136,7 +14182,7 @@ async function assertWorkspaceInitialized15(basouRoot) {
14136
14182
  function readBuildStamp() {
14137
14183
  if (false) return void 0;
14138
14184
  try {
14139
- return JSON.parse('{"version":"0.58.0","commit":"4026254","committedAt":"2026-10-01T14:04:07+09:00"}');
14185
+ return JSON.parse('{"version":"0.59.0","commit":"431fe03","committedAt":"2026-10-01T20:32:13+09:00"}');
14140
14186
  } catch {
14141
14187
  return void 0;
14142
14188
  }