@basou/cli 0.62.0 → 0.64.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
@@ -136,11 +136,13 @@ import {
136
136
  acquireLock,
137
137
  appendChainedEventLocked,
138
138
  assertBasouRootSafe,
139
+ assertLockStoreSafe,
139
140
  assertSessionDirSafe,
140
141
  basouPaths,
141
142
  EVENT_SCHEMA_VERSION,
142
143
  enumerateApprovals,
143
144
  findErrorCode,
145
+ inspectApprovalEntry,
144
146
  inspectSessionEntry,
145
147
  isLazyExpired,
146
148
  LOCAL_CLI_EVENT_SOURCE,
@@ -192,6 +194,11 @@ async function doRunApprovalList(options, ctx) {
192
194
  const paths = basouPaths(repositoryRoot);
193
195
  await assertWorkspaceInitialized(paths.root);
194
196
  const ids = await enumerateApprovals(paths);
197
+ for (const entry of ids.unfollowed) {
198
+ console.error(
199
+ `Skipped ${entry.id} in ${entry.location}: not a file (a symlink or a directory is not followed)`
200
+ );
201
+ }
195
202
  const now = /* @__PURE__ */ new Date();
196
203
  const records = [];
197
204
  const resolvedSet = new Set(ids.resolved);
@@ -259,7 +266,8 @@ async function doRunApprovalShow(idInput, options, ctx) {
259
266
  const repositoryRoot = await resolveRepositoryRootForApproval(cwd, "show");
260
267
  const paths = basouPaths(repositoryRoot);
261
268
  await assertWorkspaceInitialized(paths.root);
262
- const { id } = await resolveApprovalId(paths, idInput);
269
+ const { id, unfollowed } = await resolveApprovalId(paths, idInput);
270
+ for (const entry2 of unfollowed) console.error(`Warning: ${describeUnfollowed(entry2)}`);
263
271
  const loaded = await loadApproval(paths, id);
264
272
  if (loaded === null) {
265
273
  throw new Error(`Approval not found: ${idInput}`);
@@ -328,6 +336,15 @@ async function doRunApprovalResolve(idInput, options, ctx, decision) {
328
336
  if (location === "resolved") {
329
337
  throw new Error(`Approval already resolved: ${idInput}`);
330
338
  }
339
+ const resolvedEntry = await inspectApprovalEntry(paths, "resolved", id);
340
+ if (resolvedEntry === "file") {
341
+ throw new Error(`Approval already resolved: ${idInput}`);
342
+ }
343
+ if (resolvedEntry !== "missing") {
344
+ throw new Error(
345
+ `Approval ${id} cannot be resolved: its entry in resolved is not a file (a symlink or a directory is not followed)`
346
+ );
347
+ }
331
348
  const pendingPath = join(paths.approvals.pending, `${id}.yaml`);
332
349
  let pendingRaw;
333
350
  try {
@@ -354,6 +371,7 @@ async function doRunApprovalResolve(idInput, options, ctx, decision) {
354
371
  const now = /* @__PURE__ */ new Date();
355
372
  const occurredAt = now.toISOString();
356
373
  const eventId = prefixedUlid("evt");
374
+ await assertLockStoreSafe(paths);
357
375
  try {
358
376
  await mkdir(paths.approvals.resolved, { recursive: true });
359
377
  } catch (error) {
@@ -469,20 +487,31 @@ async function resolveApprovalId(paths, input) {
469
487
  }
470
488
  byId.set(id2, "resolved");
471
489
  }
472
- if (byId.size === 0) {
490
+ const matched = new Set(byId.keys());
491
+ for (const entry of enumeration.unfollowed) {
492
+ if (entry.id.startsWith(normalized)) matched.add(entry.id);
493
+ }
494
+ if (matched.size === 0) {
473
495
  throw new Error(`Approval not found: ${input}`);
474
496
  }
475
- if (byId.size > 1) {
497
+ if (matched.size > 1) {
476
498
  throw new Error(
477
- `Ambiguous approval id '${input}': matched ${byId.size} approvals. Disambiguate with a longer prefix.`
499
+ `Ambiguous approval id '${input}': matched ${matched.size} approvals. Disambiguate with a longer prefix.`
478
500
  );
479
501
  }
480
- const first = byId.entries().next().value;
481
- if (first === void 0) {
502
+ const id = matched.values().next().value;
503
+ if (id === void 0) {
482
504
  throw new Error(`Approval not found: ${input}`);
483
505
  }
484
- const [id, location] = first;
485
- return { id, location };
506
+ const location = byId.get(id);
507
+ if (location === void 0) {
508
+ throw new Error(`Approval ${id} is not a file; a symlink or a directory there is not followed`);
509
+ }
510
+ const unfollowed = enumeration.unfollowed.filter((entry) => entry.id === id);
511
+ return { id, location, unfollowed };
512
+ }
513
+ function describeUnfollowed(entry) {
514
+ return `${entry.id} in ${entry.location} is not a file (a symlink or a directory is not followed)`;
486
515
  }
487
516
  function isApprovalEvent(ev) {
488
517
  return ev.type === "approval_requested" || ev.type === "approval_approved" || ev.type === "approval_rejected" || ev.type === "approval_expired";
@@ -2076,7 +2105,7 @@ async function assertWorkspaceInitialized3(basouRoot) {
2076
2105
  }
2077
2106
 
2078
2107
  // src/commands/exec.ts
2079
- import { mkdir as mkdir2 } from "fs/promises";
2108
+ import { mkdir as mkdir2, rm } from "fs/promises";
2080
2109
  import { homedir as homedir4 } from "os";
2081
2110
  import { join as join6 } from "path";
2082
2111
  import {
@@ -2124,7 +2153,6 @@ async function runExec(command, args, options, ctx = {}) {
2124
2153
  await assertSessionStoreSafe(paths);
2125
2154
  const sessionId = prefixedUlid3("ses");
2126
2155
  const sessionDir = join6(paths.sessions, sessionId);
2127
- await mkdir2(sessionDir, { recursive: true });
2128
2156
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
2129
2157
  await coreAppendChainedEvent(paths, sessionId, event);
2130
2158
  });
@@ -2138,7 +2166,22 @@ async function runExec(command, args, options, ctx = {}) {
2138
2166
  workspaceId: manifest.workspace.id,
2139
2167
  startedAt
2140
2168
  });
2141
- await writeYamlFile(sessionYamlPath, session);
2169
+ const initialLock = await acquireLock3(paths, "session", sessionId);
2170
+ try {
2171
+ try {
2172
+ await mkdir2(sessionDir, { recursive: true });
2173
+ } catch (error) {
2174
+ throw new Error("Failed to create session directory", { cause: error });
2175
+ }
2176
+ try {
2177
+ await writeYamlFile(sessionYamlPath, session);
2178
+ } catch (error) {
2179
+ await rm(sessionDir, { recursive: true, force: true }).catch(() => void 0);
2180
+ throw error;
2181
+ }
2182
+ } finally {
2183
+ await initialLock.release();
2184
+ }
2142
2185
  await appendEvent(sessionDir, {
2143
2186
  schema_version: EVENT_SCHEMA_VERSION3,
2144
2187
  type: "session_started",
@@ -2920,6 +2963,7 @@ async function loadHostsConfig(configPath = DEFAULT_HOSTS_CONFIG_PATH) {
2920
2963
  // src/lib/provenance-actions.ts
2921
2964
  import {
2922
2965
  assertApprovalStoreSafe,
2966
+ assertLockStoreSafe as assertLockStoreSafe3,
2923
2967
  assertTaskStoreSafe,
2924
2968
  readMarkdownFile as readMarkdownFile5,
2925
2969
  renderDecisions as renderDecisions2,
@@ -2931,13 +2975,14 @@ import {
2931
2975
 
2932
2976
  // src/commands/import.ts
2933
2977
  import { createReadStream } from "fs";
2934
- import { readdir, readFile as readFile2, rm, stat as stat3 } from "fs/promises";
2978
+ import { readdir, readFile as readFile2, rm as rm2, stat as stat3 } from "fs/promises";
2935
2979
  import { homedir as homedir7 } from "os";
2936
2980
  import { basename as basename4, dirname as dirname3, join as join9, resolve as resolve6 } from "path";
2937
2981
  import { createInterface } from "readline";
2938
2982
  import {
2939
2983
  AGENT_INFRA_DIRS as AGENT_INFRA_DIRS2,
2940
2984
  assertBasouRootSafe as assertBasouRootSafe6,
2985
+ assertLockStoreSafe as assertLockStoreSafe2,
2941
2986
  assertSessionStoreSafe as assertSessionStoreSafe2,
2942
2987
  basouPaths as basouPaths8,
2943
2988
  CLAUDE_IMPORT_SOURCE,
@@ -3111,6 +3156,7 @@ async function resolveImportTarget(ctx) {
3111
3156
  }
3112
3157
  async function importDerivedSessions(paths, manifest, options, sourceKind, candidates, projectPaths, boundaryDeclared) {
3113
3158
  await assertSessionStoreSafe2(paths);
3159
+ if (options.dryRun !== true) await assertLockStoreSafe2(paths);
3114
3160
  const { byExternalId: existingByExternalId, notDirectories } = await loadExistingByExternalId(
3115
3161
  paths,
3116
3162
  sourceKind
@@ -3202,7 +3248,7 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
3202
3248
  if (priors.length > 0 && options.force === true) {
3203
3249
  if (options.dryRun !== true) {
3204
3250
  for (const { sessionId } of priors) {
3205
- await rm(join9(paths.sessions, sessionId), { recursive: true, force: true });
3251
+ await rm2(join9(paths.sessions, sessionId), { recursive: true, force: true });
3206
3252
  }
3207
3253
  }
3208
3254
  counts.replaced++;
@@ -3692,6 +3738,7 @@ async function refreshAll(args) {
3692
3738
  if (!dryRun) {
3693
3739
  await assertTaskStoreSafe(paths);
3694
3740
  await assertApprovalStoreSafe(paths);
3741
+ await assertLockStoreSafe3(paths);
3695
3742
  }
3696
3743
  const claudeCode = await importClaudeCode(options, ctx);
3697
3744
  const codex = await importCodex(options, ctx);
@@ -8714,6 +8761,7 @@ import { homedir as homedir10 } from "os";
8714
8761
  import { join as join13 } from "path";
8715
8762
  import {
8716
8763
  assertApprovalStoreSafe as assertApprovalStoreSafe2,
8764
+ assertLockStoreSafe as assertLockStoreSafe4,
8717
8765
  assertTaskStoreSafe as assertTaskStoreSafe2,
8718
8766
  findErrorCode as findErrorCode8
8719
8767
  } from "@basou/core";
@@ -8778,6 +8826,7 @@ function hms(date) {
8778
8826
  async function runImports(deps) {
8779
8827
  await assertTaskStoreSafe2(deps.paths);
8780
8828
  await assertApprovalStoreSafe2(deps.paths);
8829
+ await assertLockStoreSafe4(deps.paths);
8781
8830
  const claude = await importClaudeCode(deps.importOptions, deps.ctx);
8782
8831
  const codex = await importCodex(deps.importOptions, deps.ctx);
8783
8832
  return { claude, codex, changed: changedCount(claude) + changedCount(codex) };
@@ -9599,7 +9648,7 @@ function unattachedLines(u) {
9599
9648
  }
9600
9649
 
9601
9650
  // src/commands/run.ts
9602
- import { mkdir as mkdir3, readFile as readFile7 } from "fs/promises";
9651
+ import { mkdir as mkdir3, readFile as readFile7, rm as rm3 } from "fs/promises";
9603
9652
  import { homedir as homedir12 } from "os";
9604
9653
  import { join as join14 } from "path";
9605
9654
  import {
@@ -9684,7 +9733,6 @@ async function runTrackedTool(args, options, ctx, adapter) {
9684
9733
  await assertSessionStoreSafe3(paths);
9685
9734
  const sessionId = prefixedUlid4("ses");
9686
9735
  const sessionDir = join14(paths.sessions, sessionId);
9687
- await mkdir3(sessionDir, { recursive: true });
9688
9736
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
9689
9737
  await coreAppendChainedEvent2(paths, sessionId, event);
9690
9738
  });
@@ -9699,7 +9747,22 @@ async function runTrackedTool(args, options, ctx, adapter) {
9699
9747
  startedAt,
9700
9748
  source: adapter.metadata
9701
9749
  });
9702
- await writeYamlFile2(sessionYamlPath, session);
9750
+ const initialLock = await acquireLock5(paths, "session", sessionId);
9751
+ try {
9752
+ try {
9753
+ await mkdir3(sessionDir, { recursive: true });
9754
+ } catch (error) {
9755
+ throw new Error("Failed to create session directory", { cause: error });
9756
+ }
9757
+ try {
9758
+ await writeYamlFile2(sessionYamlPath, session);
9759
+ } catch (error) {
9760
+ await rm3(sessionDir, { recursive: true, force: true }).catch(() => void 0);
9761
+ throw error;
9762
+ }
9763
+ } finally {
9764
+ await initialLock.release();
9765
+ }
9703
9766
  await appendEvent(sessionDir, {
9704
9767
  schema_version: EVENT_SCHEMA_VERSION5,
9705
9768
  type: "session_started",
@@ -10068,6 +10131,7 @@ import {
10068
10131
  acquireLock as acquireLock6,
10069
10132
  appendEventToExistingSession as appendEventToExistingSession3,
10070
10133
  assertBasouRootSafe as assertBasouRootSafe13,
10134
+ assertLockStoreSafe as assertLockStoreSafe5,
10071
10135
  basouPaths as basouPaths18,
10072
10136
  displayPath as displayPath3,
10073
10137
  EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION6,
@@ -10703,6 +10767,7 @@ async function doRunSessionRechain(options, ctx) {
10703
10767
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "rechain");
10704
10768
  const paths = basouPaths18(repositoryRoot);
10705
10769
  await assertWorkspaceInitialized11(paths.root);
10770
+ await assertLockStoreSafe5(paths);
10706
10771
  const sessionIds = options.session !== void 0 ? [await resolveSessionId3(paths, options.session)] : await enumerateAllSessionNames(paths);
10707
10772
  const dryRun = options.dryRun === true;
10708
10773
  const rows = [];
@@ -10991,6 +11056,7 @@ import { join as join16 } from "path";
10991
11056
  import {
10992
11057
  archiveTask,
10993
11058
  assertBasouRootSafe as assertBasouRootSafe16,
11059
+ assertLockStoreSafe as assertLockStoreSafe6,
10994
11060
  basouPaths as basouPaths21,
10995
11061
  createTaskWithEvent,
10996
11062
  deleteTask,
@@ -11528,6 +11594,7 @@ async function doRunTaskReconcile(options, ctx) {
11528
11594
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "reconcile");
11529
11595
  const paths = basouPaths21(repositoryRoot);
11530
11596
  await assertWorkspaceInitialized13(paths.root);
11597
+ await assertLockStoreSafe6(paths);
11531
11598
  const manifest = await readManifest13(paths);
11532
11599
  const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
11533
11600
  const write = options.write === true;
@@ -14199,7 +14266,7 @@ async function assertWorkspaceInitialized15(basouRoot) {
14199
14266
  function readBuildStamp() {
14200
14267
  if (false) return void 0;
14201
14268
  try {
14202
- return JSON.parse('{"version":"0.62.0","commit":"5ce774d","committedAt":"2026-10-02T23:27:57+09:00"}');
14269
+ return JSON.parse('{"version":"0.64.0","commit":"1caea85","committedAt":"2026-10-04T11:34:39+09:00"}');
14203
14270
  } catch {
14204
14271
  return void 0;
14205
14272
  }