@basou/cli 0.15.0 → 0.16.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
@@ -2620,6 +2620,62 @@ import {
2620
2620
  writeMarkdownFile as writeMarkdownFile4
2621
2621
  } from "@basou/core";
2622
2622
 
2623
+ // src/lib/hosts-config.ts
2624
+ import { homedir as homedir5 } from "os";
2625
+ import { isAbsolute as isAbsolute2, join as join5, resolve as resolve6 } from "path";
2626
+ import { readYamlFile as readYamlFile4 } from "@basou/core";
2627
+ var DEFAULT_HOSTS_CONFIG_PATH = join5(homedir5(), ".basou", "hosts.yaml");
2628
+ function expandTilde2(p) {
2629
+ if (p === "~") return homedir5();
2630
+ if (p.startsWith("~/")) return join5(homedir5(), p.slice(2));
2631
+ return p;
2632
+ }
2633
+ function isRecord2(value) {
2634
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2635
+ }
2636
+ async function loadHostsConfig(configPath = DEFAULT_HOSTS_CONFIG_PATH) {
2637
+ let raw;
2638
+ try {
2639
+ raw = await readYamlFile4(configPath);
2640
+ } catch (error) {
2641
+ if (error instanceof Error && error.message === "YAML file not found") {
2642
+ return null;
2643
+ }
2644
+ if (error instanceof Error && error.message === "Failed to parse YAML content") {
2645
+ throw new Error("~/.basou/hosts.yaml is not valid YAML.");
2646
+ }
2647
+ throw error;
2648
+ }
2649
+ if (!isRecord2(raw) || !Array.isArray(raw.hosts)) {
2650
+ throw new Error("~/.basou/hosts.yaml must contain a 'hosts:' list.");
2651
+ }
2652
+ const seenPaths = /* @__PURE__ */ new Set();
2653
+ const seenLabels = /* @__PURE__ */ new Set();
2654
+ const result = [];
2655
+ for (const entry of raw.hosts) {
2656
+ if (!isRecord2(entry) || typeof entry.label !== "string" || entry.label.trim().length === 0) {
2657
+ throw new Error("Each host needs a non-empty string 'label'.");
2658
+ }
2659
+ const label = entry.label.trim();
2660
+ if (typeof entry.path !== "string" || entry.path.trim().length === 0) {
2661
+ throw new Error("Each host needs a non-empty string 'path'.");
2662
+ }
2663
+ const expanded = expandTilde2(entry.path.trim());
2664
+ if (!isAbsolute2(expanded)) {
2665
+ throw new Error("Host paths must be absolute (or start with '~').");
2666
+ }
2667
+ const abs = resolve6(expanded);
2668
+ if (seenPaths.has(abs)) continue;
2669
+ if (seenLabels.has(label)) {
2670
+ throw new Error(`Duplicate host label '${label}'; each host needs a distinct label.`);
2671
+ }
2672
+ seenPaths.add(abs);
2673
+ seenLabels.add(label);
2674
+ result.push({ label, path: abs });
2675
+ }
2676
+ return result;
2677
+ }
2678
+
2623
2679
  // src/lib/provenance-actions.ts
2624
2680
  import {
2625
2681
  readMarkdownFile as readMarkdownFile3,
@@ -2826,14 +2882,29 @@ async function doRunOrient(options, ctx) {
2826
2882
  if (ctx.claudeProjectsDir !== void 0) probeCtx.claudeProjectsDir = ctx.claudeProjectsDir;
2827
2883
  if (ctx.codexSessionsDir !== void 0) probeCtx.codexSessionsDir = ctx.codexSessionsDir;
2828
2884
  const staleness = await probeStaleness({ ctx: probeCtx, paths, nowIso });
2885
+ let federatedRoots = [];
2886
+ try {
2887
+ const hosts = await loadHostsConfig(ctx.hostsConfigPath);
2888
+ if (hosts !== null) {
2889
+ federatedRoots = hosts.map((h) => ({ paths: basouPaths9(h.path), host: h.label }));
2890
+ }
2891
+ } catch (error) {
2892
+ console.error(
2893
+ `basou: ignoring ~/.basou/hosts.yaml (${error instanceof Error ? error.message : String(error)}); showing local sessions only.`
2894
+ );
2895
+ }
2829
2896
  const result = await renderOrientation2({
2830
2897
  paths,
2831
2898
  nowIso,
2832
2899
  staleness,
2833
2900
  verbose: options.verbose === true,
2901
+ federatedRoots,
2834
2902
  onWarning: (w, sid) => printReplayWarning(w, sid),
2835
2903
  onSessionSkip: (sid, reason) => printSessionSkip(sid, reason),
2836
- onTaskSkip: (taskId, reason) => printTaskSkip(taskId, reason)
2904
+ onTaskSkip: (taskId, reason) => printTaskSkip(taskId, reason),
2905
+ onHostUnavailable: (host, error) => console.error(
2906
+ `basou: host '${host}' mirror unreadable (${error instanceof Error ? error.message : String(error)}); skipping it.`
2907
+ )
2837
2908
  });
2838
2909
  await writeMarkdownFile4(paths.files.orientation, `${result.body}
2839
2910
  `);
@@ -2870,7 +2941,7 @@ import {
2870
2941
  unlinkSync,
2871
2942
  writeFileSync
2872
2943
  } from "fs";
2873
- import { basename as basename4, dirname, isAbsolute as isAbsolute2, join as join5, relative as relative2, resolve as resolve6 } from "path";
2944
+ import { basename as basename4, dirname, isAbsolute as isAbsolute3, join as join6, relative as relative2, resolve as resolve7 } from "path";
2874
2945
  import {
2875
2946
  basouPaths as basouPaths10,
2876
2947
  GENERATED_END,
@@ -3122,14 +3193,14 @@ async function runProjectAdopt(options, ctx = {}) {
3122
3193
  }
3123
3194
  }
3124
3195
  function classifySourceRoot(repositoryRoot, declaredPath) {
3125
- const absolute = resolve6(repositoryRoot, declaredPath);
3196
+ const absolute = resolve7(repositoryRoot, declaredPath);
3126
3197
  let real;
3127
3198
  try {
3128
3199
  real = realpathSync(absolute);
3129
3200
  } catch {
3130
3201
  return { path: declaredPath, kind: "unresolved" };
3131
3202
  }
3132
- return { path: declaredPath, kind: existsSync(join5(real, ".git")) ? "repo" : "non-repo" };
3203
+ return { path: declaredPath, kind: existsSync(join6(real, ".git")) ? "repo" : "non-repo" };
3133
3204
  }
3134
3205
  async function doRunProjectAdopt(options, ctx) {
3135
3206
  const cwd = ctx.cwd ?? process.cwd();
@@ -3224,11 +3295,11 @@ async function gatherRepoWiring(repositoryRoot, entry) {
3224
3295
  };
3225
3296
  let real;
3226
3297
  try {
3227
- real = realpathSync(resolve6(repositoryRoot, entry.path));
3298
+ real = realpathSync(resolve7(repositoryRoot, entry.path));
3228
3299
  } catch {
3229
3300
  return { ...base, reachable: false, instructionFiles: [] };
3230
3301
  }
3231
- if (!existsSync(join5(real, ".git"))) {
3302
+ if (!existsSync(join6(real, ".git"))) {
3232
3303
  return { ...base, reachable: false, instructionFiles: [] };
3233
3304
  }
3234
3305
  try {
@@ -3236,7 +3307,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
3236
3307
  for (const name of INSTRUCTION_FILES) {
3237
3308
  let present = true;
3238
3309
  try {
3239
- lstatSync(join5(real, name));
3310
+ lstatSync(join6(real, name));
3240
3311
  } catch {
3241
3312
  present = false;
3242
3313
  }
@@ -3329,14 +3400,14 @@ function gatherRepoGitignore(repositoryRoot, entry) {
3329
3400
  };
3330
3401
  let real;
3331
3402
  try {
3332
- real = realpathSync(resolve6(repositoryRoot, entry.path));
3403
+ real = realpathSync(resolve7(repositoryRoot, entry.path));
3333
3404
  } catch {
3334
3405
  return { ...base, reachable: false, currentLines: [] };
3335
3406
  }
3336
- if (!existsSync(join5(real, ".git"))) {
3407
+ if (!existsSync(join6(real, ".git"))) {
3337
3408
  return { ...base, reachable: false, currentLines: [] };
3338
3409
  }
3339
- return { ...base, reachable: true, currentLines: readGitignoreLines(join5(real, ".gitignore")) };
3410
+ return { ...base, reachable: true, currentLines: readGitignoreLines(join6(real, ".gitignore")) };
3340
3411
  }
3341
3412
  function hasErrorCode(error) {
3342
3413
  return error instanceof Error && typeof error.code === "string";
@@ -3350,7 +3421,7 @@ function readGitignoreLines(file) {
3350
3421
  }
3351
3422
  }
3352
3423
  function applyGitignorePlan(repositoryRoot, plan) {
3353
- const file = join5(realpathSync(resolve6(repositoryRoot, plan.path)), ".gitignore");
3424
+ const file = join6(realpathSync(resolve7(repositoryRoot, plan.path)), ".gitignore");
3354
3425
  let existing = "";
3355
3426
  try {
3356
3427
  existing = readFileSync(file, "utf8");
@@ -3462,23 +3533,23 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
3462
3533
  const base = { path: entry.path };
3463
3534
  let real;
3464
3535
  try {
3465
- real = realpathSync(resolve6(repositoryRoot, entry.path));
3536
+ real = realpathSync(resolve7(repositoryRoot, entry.path));
3466
3537
  } catch {
3467
3538
  return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
3468
3539
  }
3469
3540
  if (real === anchorReal) {
3470
3541
  return { ...base, isAnchor: true, reachable: true, canonicalPresent: false, files: [] };
3471
3542
  }
3472
- if (!existsSync(join5(real, ".git"))) {
3543
+ if (!existsSync(join6(real, ".git"))) {
3473
3544
  return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
3474
3545
  }
3475
- const canonicalFile = join5(anchorReal, "agents", basename4(real), CANONICAL_FILE);
3546
+ const canonicalFile = join6(anchorReal, "agents", basename4(real), CANONICAL_FILE);
3476
3547
  if (!existsSync(canonicalFile)) {
3477
3548
  return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
3478
3549
  }
3479
3550
  const files = expectedSymlinkTargets(real, canonicalFile).map(
3480
3551
  (spec) => {
3481
- const { state, actualTarget } = inspectSymlink(join5(real, spec.name), spec.target);
3552
+ const { state, actualTarget } = inspectSymlink(join6(real, spec.name), spec.target);
3482
3553
  return {
3483
3554
  name: spec.name,
3484
3555
  expectedTarget: spec.target,
@@ -3499,7 +3570,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
3499
3570
  function applySymlinkPlan(repositoryRoot, plan) {
3500
3571
  let real;
3501
3572
  try {
3502
- real = realpathSync(resolve6(repositoryRoot, plan.path));
3573
+ real = realpathSync(resolve7(repositoryRoot, plan.path));
3503
3574
  } catch (error) {
3504
3575
  const message = failureReason(error);
3505
3576
  return { created: [], failed: plan.toCreate.map((c) => ({ file: c.name, message })) };
@@ -3507,7 +3578,7 @@ function applySymlinkPlan(repositoryRoot, plan) {
3507
3578
  const created = [];
3508
3579
  const failed = [];
3509
3580
  for (const { name, target } of plan.toCreate) {
3510
- const filePath = join5(real, name);
3581
+ const filePath = join6(real, name);
3511
3582
  try {
3512
3583
  mkdirSync(dirname(filePath), { recursive: true });
3513
3584
  symlinkSync(target, filePath);
@@ -3644,12 +3715,12 @@ async function runProjectWorkspace(options, ctx = {}) {
3644
3715
  }
3645
3716
  }
3646
3717
  function resolveViewDir(repositoryRoot, viewPath) {
3647
- const abs = resolve6(repositoryRoot, viewPath);
3718
+ const abs = resolve7(repositoryRoot, viewPath);
3648
3719
  try {
3649
3720
  return realpathSync(abs);
3650
3721
  } catch {
3651
3722
  try {
3652
- return join5(realpathSync(dirname(abs)), basename4(abs));
3723
+ return join6(realpathSync(dirname(abs)), basename4(abs));
3653
3724
  } catch {
3654
3725
  return abs;
3655
3726
  }
@@ -3658,7 +3729,7 @@ function resolveViewDir(repositoryRoot, viewPath) {
3658
3729
  function gatherViewRepo(repositoryRoot, viewDir, entry) {
3659
3730
  let repoReal;
3660
3731
  try {
3661
- repoReal = realpathSync(resolve6(repositoryRoot, entry.path));
3732
+ repoReal = realpathSync(resolve7(repositoryRoot, entry.path));
3662
3733
  } catch {
3663
3734
  return { path: entry.path, reachable: false };
3664
3735
  }
@@ -3667,7 +3738,7 @@ function gatherViewRepo(repositoryRoot, viewDir, entry) {
3667
3738
  return { path: entry.path, reachable: false };
3668
3739
  }
3669
3740
  const linkName = basename4(repoReal);
3670
- const { state, actualTarget } = inspectSymlink(join5(viewDir, linkName), expectedTarget);
3741
+ const { state, actualTarget } = inspectSymlink(join6(viewDir, linkName), expectedTarget);
3671
3742
  return {
3672
3743
  path: entry.path,
3673
3744
  reachable: true,
@@ -3681,7 +3752,7 @@ function applyViewPlan(viewDir, toCreate) {
3681
3752
  const created = [];
3682
3753
  const failed = [];
3683
3754
  for (const { name, target } of toCreate) {
3684
- const filePath = join5(viewDir, name);
3755
+ const filePath = join6(viewDir, name);
3685
3756
  try {
3686
3757
  mkdirSync(dirname(filePath), { recursive: true });
3687
3758
  symlinkSync(target, filePath);
@@ -3696,7 +3767,7 @@ var TOP_LEVEL_INSTRUCTION_FILES_LOWER = new Set(
3696
3767
  INSTRUCTION_FILES.filter((f) => !f.includes("/")).map((f) => f.toLowerCase())
3697
3768
  );
3698
3769
  function classifyViewLink(viewDir, name, rosterRealpaths) {
3699
- const filePath = join5(viewDir, name);
3770
+ const filePath = join6(viewDir, name);
3700
3771
  let isLink;
3701
3772
  try {
3702
3773
  isLink = lstatSync(filePath).isSymbolicLink();
@@ -3710,12 +3781,12 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
3710
3781
  } catch {
3711
3782
  return null;
3712
3783
  }
3713
- const resolved = isAbsolute2(target) ? target : resolve6(viewDir, target);
3784
+ const resolved = isAbsolute3(target) ? target : resolve7(viewDir, target);
3714
3785
  try {
3715
3786
  if (rosterRealpaths.has(realpathSync(resolved))) return null;
3716
3787
  } catch {
3717
3788
  }
3718
- if (isAbsolute2(target)) return { target, kind: "absolute" };
3789
+ if (isAbsolute3(target)) return { target, kind: "absolute" };
3719
3790
  let isDir = false;
3720
3791
  try {
3721
3792
  isDir = statSync(resolved).isDirectory();
@@ -3725,7 +3796,7 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
3725
3796
  if (!isDir) {
3726
3797
  return { target, kind: existsSync(resolved) ? "non-repo" : "broken" };
3727
3798
  }
3728
- return { target, kind: existsSync(join5(resolved, ".git")) ? "repo" : "non-repo" };
3799
+ return { target, kind: existsSync(join6(resolved, ".git")) ? "repo" : "non-repo" };
3729
3800
  }
3730
3801
  function gatherExistingViewLinks(viewDir, rosterRealpaths) {
3731
3802
  let names;
@@ -3750,7 +3821,7 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
3750
3821
  const pruned = [];
3751
3822
  const failed = [];
3752
3823
  for (const { name } of toPrune) {
3753
- const filePath = join5(viewDir, name);
3824
+ const filePath = join6(viewDir, name);
3754
3825
  const c = classifyViewLink(viewDir, name, rosterRealpaths);
3755
3826
  if (c === null || c.kind !== "repo") {
3756
3827
  failed.push({
@@ -3796,11 +3867,11 @@ async function doRunProjectWorkspace(options, ctx) {
3796
3867
  } else {
3797
3868
  const viewDir = resolveViewDir(repositoryRoot, viewPath);
3798
3869
  const facts = roster.map((entry) => gatherViewRepo(repositoryRoot, viewDir, entry));
3799
- const rosterNames = roster.map((entry) => basename4(resolve6(repositoryRoot, entry.path)));
3870
+ const rosterNames = roster.map((entry) => basename4(resolve7(repositoryRoot, entry.path)));
3800
3871
  const rosterRealpaths = /* @__PURE__ */ new Set();
3801
3872
  for (const entry of roster) {
3802
3873
  try {
3803
- rosterRealpaths.add(realpathSync(resolve6(repositoryRoot, entry.path)));
3874
+ rosterRealpaths.add(realpathSync(resolve7(repositoryRoot, entry.path)));
3804
3875
  } catch {
3805
3876
  }
3806
3877
  }
@@ -3961,10 +4032,10 @@ async function runProjectPreset(options, ctx = {}) {
3961
4032
  }
3962
4033
  }
3963
4034
  function canonicalFileFor(anchorReal, canonicalName) {
3964
- return join5(anchorReal, "agents", canonicalName, CANONICAL_FILE);
4035
+ return join6(anchorReal, "agents", canonicalName, CANONICAL_FILE);
3965
4036
  }
3966
4037
  function canonicalLabelFor(canonicalName) {
3967
- return join5("agents", canonicalName, CANONICAL_FILE);
4038
+ return join6("agents", canonicalName, CANONICAL_FILE);
3968
4039
  }
3969
4040
  async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
3970
4041
  const declared = {
@@ -3975,14 +4046,14 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
3975
4046
  };
3976
4047
  let real;
3977
4048
  try {
3978
- real = realpathSync(resolve6(repositoryRoot, entry.path));
4049
+ real = realpathSync(resolve7(repositoryRoot, entry.path));
3979
4050
  } catch {
3980
4051
  return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
3981
4052
  }
3982
4053
  if (real === anchorReal) {
3983
4054
  return { ...declared, isAnchor: true, reachable: true, canonicalPresent: false };
3984
4055
  }
3985
- if (!existsSync(join5(real, ".git"))) {
4056
+ if (!existsSync(join6(real, ".git"))) {
3986
4057
  return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
3987
4058
  }
3988
4059
  const canonicalName = basename4(real);
@@ -4204,7 +4275,7 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
4204
4275
  };
4205
4276
  let real;
4206
4277
  try {
4207
- real = realpathSync(resolve6(repositoryRoot, target));
4278
+ real = realpathSync(resolve7(repositoryRoot, target));
4208
4279
  } catch {
4209
4280
  return empty;
4210
4281
  }
@@ -4213,24 +4284,24 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
4213
4284
  const instructionFiles = [];
4214
4285
  for (const name of INSTRUCTION_FILES) {
4215
4286
  try {
4216
- lstatSync(join5(real, name));
4287
+ lstatSync(join6(real, name));
4217
4288
  instructionFiles.push(name);
4218
4289
  } catch {
4219
4290
  }
4220
4291
  }
4221
4292
  let ignored;
4222
4293
  try {
4223
- ignored = new Set(readGitignoreLines(join5(real, ".gitignore")).map((l) => l.trim()));
4294
+ ignored = new Set(readGitignoreLines(join6(real, ".gitignore")).map((l) => l.trim()));
4224
4295
  } catch {
4225
4296
  ignored = /* @__PURE__ */ new Set();
4226
4297
  }
4227
4298
  const gitignorePatterns = INSTRUCTION_FILES.filter((p) => ignored.has(p) || ignored.has(`/${p}`));
4228
- const canonical2 = existsSync(join5(anchorReal, "agents", canonicalName, CANONICAL_FILE));
4299
+ const canonical2 = existsSync(join6(anchorReal, "agents", canonicalName, CANONICAL_FILE));
4229
4300
  let viewLink = false;
4230
4301
  const viewPath = manifest.workspace.view;
4231
4302
  if (viewPath !== void 0) {
4232
4303
  try {
4233
- lstatSync(join5(resolveViewDir(repositoryRoot, viewPath), canonicalName));
4304
+ lstatSync(join6(resolveViewDir(repositoryRoot, viewPath), canonicalName));
4234
4305
  viewLink = true;
4235
4306
  } catch {
4236
4307
  }
@@ -4272,7 +4343,7 @@ async function doRunProjectArchive(target, options, ctx) {
4272
4343
  const roster = manifest.repos ?? [];
4273
4344
  let targetIsAnchor = false;
4274
4345
  try {
4275
- targetIsAnchor = realpathSync(resolve6(repositoryRoot, target)) === realpathSync(repositoryRoot);
4346
+ targetIsAnchor = realpathSync(resolve7(repositoryRoot, target)) === realpathSync(repositoryRoot);
4276
4347
  } catch {
4277
4348
  targetIsAnchor = false;
4278
4349
  }
@@ -4392,12 +4463,12 @@ function gatherRenameWiring(repositoryRoot, manifest, oldBasename) {
4392
4463
  } catch {
4393
4464
  return { canonicalDirOld: false, viewLinkOld: false };
4394
4465
  }
4395
- const canonicalDirOld = existsSync(join5(anchorReal, "agents", oldBasename));
4466
+ const canonicalDirOld = existsSync(join6(anchorReal, "agents", oldBasename));
4396
4467
  let viewLinkOld = false;
4397
4468
  const viewPath = manifest.workspace.view;
4398
4469
  if (viewPath !== void 0) {
4399
4470
  try {
4400
- lstatSync(join5(resolveViewDir(repositoryRoot, viewPath), oldBasename));
4471
+ lstatSync(join6(resolveViewDir(repositoryRoot, viewPath), oldBasename));
4401
4472
  viewLinkOld = true;
4402
4473
  } catch {
4403
4474
  }
@@ -4423,7 +4494,7 @@ async function doRunProjectRename(oldPath, newPath, options, ctx) {
4423
4494
  const roster = manifest.repos ?? [];
4424
4495
  let oldIsAnchor = false;
4425
4496
  try {
4426
- oldIsAnchor = realpathSync(resolve6(repositoryRoot, oldPath)) === realpathSync(repositoryRoot);
4497
+ oldIsAnchor = realpathSync(resolve7(repositoryRoot, oldPath)) === realpathSync(repositoryRoot);
4427
4498
  } catch {
4428
4499
  oldIsAnchor = false;
4429
4500
  }
@@ -4546,7 +4617,7 @@ import {
4546
4617
  // src/lib/durable-write.ts
4547
4618
  import { randomUUID } from "crypto";
4548
4619
  import { lstat, open, rename, stat as stat3, unlink as unlink2 } from "fs/promises";
4549
- import { basename as basename5, dirname as dirname2, join as join6 } from "path";
4620
+ import { basename as basename5, dirname as dirname2, join as join7 } from "path";
4550
4621
  async function assertNotSymlink(targetPath) {
4551
4622
  try {
4552
4623
  const st = await lstat(targetPath);
@@ -4562,7 +4633,7 @@ async function assertNotSymlink(targetPath) {
4562
4633
  }
4563
4634
  async function writeFileDurable(targetPath, content) {
4564
4635
  const dir = dirname2(targetPath);
4565
- const tmpPath = join6(dir, `.${basename5(targetPath)}.tmp.${randomUUID()}`);
4636
+ const tmpPath = join7(dir, `.${basename5(targetPath)}.tmp.${randomUUID()}`);
4566
4637
  let mode = 420;
4567
4638
  try {
4568
4639
  mode = (await stat3(targetPath)).mode & 511;
@@ -4597,25 +4668,25 @@ async function writeFileDurable(targetPath, content) {
4597
4668
  }
4598
4669
 
4599
4670
  // src/lib/protocols-config.ts
4600
- import { homedir as homedir5 } from "os";
4601
- import { isAbsolute as isAbsolute3, join as join7, resolve as resolve7 } from "path";
4602
- import { readYamlFile as readYamlFile4 } from "@basou/core";
4603
- var DEFAULT_PROTOCOLS_CONFIG_PATH = join7(homedir5(), ".basou", "protocols.yaml");
4604
- var DEFAULT_TARGET_PATH = join7(homedir5(), ".claude", "CLAUDE.md");
4671
+ import { homedir as homedir6 } from "os";
4672
+ import { isAbsolute as isAbsolute4, join as join8, resolve as resolve8 } from "path";
4673
+ import { readYamlFile as readYamlFile5 } from "@basou/core";
4674
+ var DEFAULT_PROTOCOLS_CONFIG_PATH = join8(homedir6(), ".basou", "protocols.yaml");
4675
+ var DEFAULT_TARGET_PATH = join8(homedir6(), ".claude", "CLAUDE.md");
4605
4676
  var ALLOWED_TOP_KEYS = /* @__PURE__ */ new Set(["version", "protocols"]);
4606
4677
  var ALLOWED_ENTRY_KEYS = /* @__PURE__ */ new Set(["source", "title"]);
4607
- function expandTilde2(p) {
4608
- if (p === "~") return homedir5();
4609
- if (p.startsWith("~/")) return join7(homedir5(), p.slice(2));
4678
+ function expandTilde3(p) {
4679
+ if (p === "~") return homedir6();
4680
+ if (p.startsWith("~/")) return join8(homedir6(), p.slice(2));
4610
4681
  return p;
4611
4682
  }
4612
- function isRecord2(value) {
4683
+ function isRecord3(value) {
4613
4684
  return typeof value === "object" && value !== null && !Array.isArray(value);
4614
4685
  }
4615
4686
  async function loadProtocolsConfig(configPath = DEFAULT_PROTOCOLS_CONFIG_PATH) {
4616
4687
  let raw;
4617
4688
  try {
4618
- raw = await readYamlFile4(configPath);
4689
+ raw = await readYamlFile5(configPath);
4619
4690
  } catch (error) {
4620
4691
  if (error instanceof Error && error.message === "YAML file not found") {
4621
4692
  throw new Error(
@@ -4627,7 +4698,7 @@ async function loadProtocolsConfig(configPath = DEFAULT_PROTOCOLS_CONFIG_PATH) {
4627
4698
  }
4628
4699
  throw error;
4629
4700
  }
4630
- if (!isRecord2(raw) || !Array.isArray(raw.protocols)) {
4701
+ if (!isRecord3(raw) || !Array.isArray(raw.protocols)) {
4631
4702
  throw new Error("~/.basou/protocols.yaml must contain a 'protocols:' list.");
4632
4703
  }
4633
4704
  for (const key of Object.keys(raw)) {
@@ -4640,7 +4711,7 @@ async function loadProtocolsConfig(configPath = DEFAULT_PROTOCOLS_CONFIG_PATH) {
4640
4711
  const seen = /* @__PURE__ */ new Set();
4641
4712
  const result = [];
4642
4713
  for (const entry of raw.protocols) {
4643
- if (!isRecord2(entry)) {
4714
+ if (!isRecord3(entry)) {
4644
4715
  throw new Error("Each protocol entry must be a mapping with a 'source' key.");
4645
4716
  }
4646
4717
  for (const key of Object.keys(entry)) {
@@ -4654,11 +4725,11 @@ async function loadProtocolsConfig(configPath = DEFAULT_PROTOCOLS_CONFIG_PATH) {
4654
4725
  if (entry.title !== void 0 && (typeof entry.title !== "string" || entry.title.trim().length === 0)) {
4655
4726
  throw new Error("A protocol entry 'title' must be a non-empty string when present.");
4656
4727
  }
4657
- const expanded = expandTilde2(entry.source.trim());
4658
- if (!isAbsolute3(expanded)) {
4728
+ const expanded = expandTilde3(entry.source.trim());
4729
+ if (!isAbsolute4(expanded)) {
4659
4730
  throw new Error("Protocol 'source' paths must be absolute (or start with '~').");
4660
4731
  }
4661
- const abs = resolve7(expanded);
4732
+ const abs = resolve8(expanded);
4662
4733
  if (seen.has(abs)) {
4663
4734
  throw new Error("Duplicate protocol source (each source path may appear only once).");
4664
4735
  }
@@ -4858,16 +4929,16 @@ import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
4858
4929
 
4859
4930
  // src/commands/refresh-watch.ts
4860
4931
  import { readdir as readdir2, stat as stat4 } from "fs/promises";
4861
- import { homedir as homedir6 } from "os";
4862
- import { join as join8 } from "path";
4932
+ import { homedir as homedir7 } from "os";
4933
+ import { join as join9 } from "path";
4863
4934
  import { findErrorCode as findErrorCode8 } from "@basou/core";
4864
4935
  var DEFAULT_WATCH_INTERVAL_SEC = 30;
4865
4936
  var MIN_WATCH_INTERVAL_SEC = 5;
4866
4937
  var MAX_WATCH_INTERVAL_SEC = 86400;
4867
4938
  function watchedRoots(ctx) {
4868
4939
  return [
4869
- ctx.codexSessionsDir ?? join8(homedir6(), ".codex", "sessions"),
4870
- ctx.claudeProjectsDir ?? join8(homedir6(), ".claude", "projects")
4940
+ ctx.codexSessionsDir ?? join9(homedir7(), ".codex", "sessions"),
4941
+ ctx.claudeProjectsDir ?? join9(homedir7(), ".claude", "projects")
4871
4942
  ];
4872
4943
  }
4873
4944
  async function scanSourceLogs(roots) {
@@ -4881,7 +4952,7 @@ async function scanSourceLogs(roots) {
4881
4952
  throw new Error("Failed to read a source log directory", { cause: error });
4882
4953
  }
4883
4954
  for (const entry of entries) {
4884
- const full = join8(dir, entry.name);
4955
+ const full = join9(dir, entry.name);
4885
4956
  if (entry.isDirectory()) {
4886
4957
  await walk(full);
4887
4958
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
@@ -4988,19 +5059,19 @@ function parseInterval(value) {
4988
5059
  return seconds;
4989
5060
  }
4990
5061
  function abortableSleep(ms, signal) {
4991
- return new Promise((resolve11) => {
5062
+ return new Promise((resolve12) => {
4992
5063
  if (signal.aborted) {
4993
- resolve11();
5064
+ resolve12();
4994
5065
  return;
4995
5066
  }
4996
5067
  let timer;
4997
5068
  const onAbort = () => {
4998
5069
  clearTimeout(timer);
4999
- resolve11();
5070
+ resolve12();
5000
5071
  };
5001
5072
  timer = setTimeout(() => {
5002
5073
  signal.removeEventListener("abort", onAbort);
5003
- resolve11();
5074
+ resolve12();
5004
5075
  }, ms);
5005
5076
  signal.addEventListener("abort", onAbort, { once: true });
5006
5077
  });
@@ -5199,7 +5270,7 @@ async function assertWorkspaceInitialized8(basouRoot) {
5199
5270
  }
5200
5271
 
5201
5272
  // src/commands/report.ts
5202
- import { isAbsolute as isAbsolute4, resolve as resolve8 } from "path";
5273
+ import { isAbsolute as isAbsolute5, resolve as resolve9 } from "path";
5203
5274
  import {
5204
5275
  assertBasouRootSafe as assertBasouRootSafe10,
5205
5276
  basouPaths as basouPaths12,
@@ -5239,7 +5310,7 @@ async function doRunReportGenerate(options, ctx) {
5239
5310
  onTaskSkip: (taskId, reason) => printTaskSkip(taskId, reason)
5240
5311
  });
5241
5312
  if (options.out !== void 0) {
5242
- const outPath = isAbsolute4(options.out) ? options.out : resolve8(cwd, options.out);
5313
+ const outPath = isAbsolute5(options.out) ? options.out : resolve9(cwd, options.out);
5243
5314
  await writeMarkdownFile6(outPath, result.body);
5244
5315
  const { sessions, decisions, tasks } = result.data;
5245
5316
  console.error(
@@ -5402,8 +5473,8 @@ function renderReviewGaps(summary) {
5402
5473
 
5403
5474
  // src/commands/run.ts
5404
5475
  import { mkdir as mkdir2 } from "fs/promises";
5405
- import { homedir as homedir7 } from "os";
5406
- import { join as join9 } from "path";
5476
+ import { homedir as homedir8 } from "os";
5477
+ import { join as join10 } from "path";
5407
5478
  import {
5408
5479
  acquireLock as acquireLock5,
5409
5480
  assertBasouRootSafe as assertBasouRootSafe11,
@@ -5417,7 +5488,7 @@ import {
5417
5488
  overwriteYamlFile as overwriteYamlFile2,
5418
5489
  prefixedUlid as prefixedUlid4,
5419
5490
  readManifest as readManifest7,
5420
- readYamlFile as readYamlFile5,
5491
+ readYamlFile as readYamlFile6,
5421
5492
  resolveClaudeCodeCommand,
5422
5493
  resolveRepositoryRoot as resolveRepositoryRoot9,
5423
5494
  SessionSchema as SessionSchema2,
@@ -5456,13 +5527,13 @@ async function runClaudeCode(args, options, ctx = {}) {
5456
5527
  await assertBasouRootSafe11(paths.root);
5457
5528
  const manifest = await readManifest7(paths);
5458
5529
  const sessionId = prefixedUlid4("ses");
5459
- const sessionDir = join9(paths.sessions, sessionId);
5530
+ const sessionDir = join10(paths.sessions, sessionId);
5460
5531
  await mkdir2(sessionDir, { recursive: true });
5461
5532
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
5462
5533
  await coreAppendChainedEvent2(paths, sessionId, event);
5463
5534
  });
5464
5535
  const startedAt = now().toISOString();
5465
- const sessionYamlPath = join9(sessionDir, "session.yaml");
5536
+ const sessionYamlPath = join10(sessionDir, "session.yaml");
5466
5537
  const session = buildInitialSession2({
5467
5538
  id: sessionId,
5468
5539
  command,
@@ -5588,7 +5659,7 @@ async function runClaudeCode(args, options, ctx = {}) {
5588
5659
  const rawRelated = computeRelatedFiles(preSnapshot, postSnapshot, diff);
5589
5660
  const relatedFiles = sanitizeRelatedFiles(rawRelated, {
5590
5661
  workingDirectory: repoRoot,
5591
- homedir: homedir7()
5662
+ homedir: homedir8()
5592
5663
  }).sanitized;
5593
5664
  const finalStatus = decideFinalStatus2(result, signalReceived);
5594
5665
  await appendEvent(sessionDir, {
@@ -5732,7 +5803,7 @@ function buildInitialSession2(input) {
5732
5803
  source: { ...claudeCodeAdapterMetadata },
5733
5804
  started_at: input.startedAt,
5734
5805
  status: "initialized",
5735
- working_directory: sanitizeWorkingDirectory2(input.cwd, { homedir: homedir7() }),
5806
+ working_directory: sanitizeWorkingDirectory2(input.cwd, { homedir: homedir8() }),
5736
5807
  invocation: {
5737
5808
  command: input.command,
5738
5809
  args: [...input.args],
@@ -5744,7 +5815,7 @@ function buildInitialSession2(input) {
5744
5815
  };
5745
5816
  }
5746
5817
  async function mutateSessionYaml2(filePath, mutator) {
5747
- const raw = await readYamlFile5(filePath);
5818
+ const raw = await readYamlFile6(filePath);
5748
5819
  const parsed = SessionSchema2.parse(raw);
5749
5820
  mutator(parsed);
5750
5821
  const validated = SessionSchema2.parse(parsed);
@@ -5805,7 +5876,7 @@ async function resolveRepositoryRootForRun(cwd) {
5805
5876
 
5806
5877
  // src/commands/session.ts
5807
5878
  import { readFile as readFile4 } from "fs/promises";
5808
- import { basename as basename6, isAbsolute as isAbsolute5, join as join10, relative as relative3 } from "path";
5879
+ import { basename as basename6, isAbsolute as isAbsolute6, join as join11, relative as relative3 } from "path";
5809
5880
  import {
5810
5881
  acquireLock as acquireLock6,
5811
5882
  appendEventToExistingSession as appendEventToExistingSession3,
@@ -5817,7 +5888,7 @@ import {
5817
5888
  loadSessionEntries,
5818
5889
  readAllEvents,
5819
5890
  readManifest as readManifest8,
5820
- readYamlFile as readYamlFile6,
5891
+ readYamlFile as readYamlFile7,
5821
5892
  rechainSessionInPlace,
5822
5893
  resolveSessionId as resolveSessionId3,
5823
5894
  resolveTaskId,
@@ -5930,11 +6001,11 @@ async function doRunSessionShow(idInput, options, ctx) {
5930
6001
  const paths = basouPaths15(repositoryRoot);
5931
6002
  await assertWorkspaceInitialized10(paths.root);
5932
6003
  const sessionId = await resolveSessionId3(paths, idInput);
5933
- const sessionDir = join10(paths.sessions, sessionId);
5934
- const sessionYamlPath = join10(sessionDir, "session.yaml");
6004
+ const sessionDir = join11(paths.sessions, sessionId);
6005
+ const sessionYamlPath = join11(sessionDir, "session.yaml");
5935
6006
  let session;
5936
6007
  try {
5937
- const raw = await readYamlFile6(sessionYamlPath);
6008
+ const raw = await readYamlFile7(sessionYamlPath);
5938
6009
  session = SessionSchema3.parse(raw);
5939
6010
  } catch (error) {
5940
6011
  if (findErrorCode11(error, "ENOENT")) {
@@ -6052,7 +6123,7 @@ function formatSessionWork(session, events, now) {
6052
6123
  }
6053
6124
  function formatWorkingDir(workingDir, repositoryRoot, options) {
6054
6125
  if (options.fullPath === true) return workingDir;
6055
- if (!isAbsolute5(workingDir)) {
6126
+ if (!isAbsolute6(workingDir)) {
6056
6127
  if (workingDir === ".") return "<repository_root>";
6057
6128
  return workingDir;
6058
6129
  }
@@ -6669,7 +6740,7 @@ async function resolveRepositoryRootForStatus(cwd) {
6669
6740
 
6670
6741
  // src/commands/task.ts
6671
6742
  import { readFile as readFile5 } from "fs/promises";
6672
- import { join as join11 } from "path";
6743
+ import { join as join12 } from "path";
6673
6744
  import {
6674
6745
  archiveTask,
6675
6746
  assertBasouRootSafe as assertBasouRootSafe15,
@@ -6996,7 +7067,7 @@ async function doRunTaskShow(idInput, options, ctx) {
6996
7067
  const events = [];
6997
7068
  const linkedSessionIds = new Set(doc.task.task.linked_sessions);
6998
7069
  for (const s of sessions) {
6999
- const sessionDir = join11(paths.sessions, s.sessionId);
7070
+ const sessionDir = join12(paths.sessions, s.sessionId);
7000
7071
  try {
7001
7072
  for await (const ev of replayEvents2(sessionDir, {
7002
7073
  onWarning: (w) => printReplayWarning(w, s.sessionId)
@@ -7899,7 +7970,7 @@ async function assertWorkspaceInitialized13(basouRoot) {
7899
7970
  // src/commands/view.ts
7900
7971
  import { spawn } from "child_process";
7901
7972
  import { createHash } from "crypto";
7902
- import { basename as basename7, resolve as resolve10 } from "path";
7973
+ import { basename as basename7, resolve as resolve11 } from "path";
7903
7974
  import {
7904
7975
  assertBasouRootSafe as assertBasouRootSafe17,
7905
7976
  basouPaths as basouPaths20,
@@ -7912,7 +7983,7 @@ import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
7912
7983
  // src/lib/portfolio-safety.ts
7913
7984
  import { execFile } from "child_process";
7914
7985
  import { lstat as lstat2, realpath as realpath2 } from "fs/promises";
7915
- import { isAbsolute as isAbsolute6, join as join12, relative as relative4, resolve as resolve9 } from "path";
7986
+ import { isAbsolute as isAbsolute7, join as join13, relative as relative4, resolve as resolve10 } from "path";
7916
7987
  import { promisify } from "util";
7917
7988
  import { readManifest as readManifest11 } from "@basou/core";
7918
7989
  var execFileAsync = promisify(execFile);
@@ -7923,12 +7994,12 @@ async function canonical(p) {
7923
7994
  try {
7924
7995
  return await realpath2(p);
7925
7996
  } catch {
7926
- return resolve9(p);
7997
+ return resolve10(p);
7927
7998
  }
7928
7999
  }
7929
8000
  function isInside(child, parent) {
7930
8001
  const rel = relative4(parent, child);
7931
- return rel === "" || !rel.startsWith("..") && !isAbsolute6(rel);
8002
+ return rel === "" || !rel.startsWith("..") && !isAbsolute7(rel);
7932
8003
  }
7933
8004
  function isBasouPath(p) {
7934
8005
  return p === ".basou" || p.startsWith(".basou/") || p.includes("/.basou/") || p.endsWith("/.basou");
@@ -7936,7 +8007,7 @@ function isBasouPath(p) {
7936
8007
  async function inspectRepo(repoPath) {
7937
8008
  let hasEntry = false;
7938
8009
  try {
7939
- await lstat2(join12(repoPath, ".basou"));
8010
+ await lstat2(join13(repoPath, ".basou"));
7940
8011
  hasEntry = true;
7941
8012
  } catch (error) {
7942
8013
  if (errorCode(error) !== "ENOENT") {
@@ -7985,7 +8056,7 @@ async function checkPortfolioSafety(workspaces) {
7985
8056
  }
7986
8057
  const monitored = /* @__PURE__ */ new Map();
7987
8058
  for (const root of sourceRoots) {
7988
- const display = resolve9(ws.repoRoot, root);
8059
+ const display = resolve10(ws.repoRoot, root);
7989
8060
  const real = await canonical(display);
7990
8061
  if (real !== wsReal) monitored.set(real, display);
7991
8062
  }
@@ -8037,7 +8108,7 @@ function formatSafetyReport(result) {
8037
8108
 
8038
8109
  // src/lib/view-server.ts
8039
8110
  import { createServer } from "http";
8040
- import { join as join13 } from "path";
8111
+ import { join as join14 } from "path";
8041
8112
  import {
8042
8113
  computeWorkStats as computeWorkStats2,
8043
8114
  enumerateApprovals as enumerateApprovals2,
@@ -8694,7 +8765,7 @@ function startViewServer(opts) {
8694
8765
  };
8695
8766
  let boundPort = port;
8696
8767
  const getPort = () => boundPort;
8697
- return new Promise((resolve11, reject) => {
8768
+ return new Promise((resolve12, reject) => {
8698
8769
  const server = createServer((req, res) => {
8699
8770
  handleRequest(req, res, deps, getPort, runExclusive).catch((error) => {
8700
8771
  sendError(res, error instanceof HttpError ? error.status : 500, pathlessMessage(error));
@@ -8705,7 +8776,7 @@ function startViewServer(opts) {
8705
8776
  const address = server.address();
8706
8777
  boundPort = isAddressInfo(address) ? address.port : port;
8707
8778
  server.off("error", reject);
8708
- resolve11({
8779
+ resolve12({
8709
8780
  url: `http://${host}:${boundPort}`,
8710
8781
  port: boundPort,
8711
8782
  close: () => closeServer(server)
@@ -8717,8 +8788,8 @@ function isAddressInfo(value) {
8717
8788
  return value !== null && typeof value === "object";
8718
8789
  }
8719
8790
  function closeServer(server) {
8720
- return new Promise((resolve11) => {
8721
- server.close(() => resolve11());
8791
+ return new Promise((resolve12) => {
8792
+ server.close(() => resolve12());
8722
8793
  server.closeAllConnections();
8723
8794
  });
8724
8795
  }
@@ -8992,7 +9063,7 @@ async function sessionDetail(ws, sessionId) {
8992
9063
  throw error;
8993
9064
  }
8994
9065
  try {
8995
- const events = await readAllEvents2(join13(ws.paths.sessions, sessionId));
9066
+ const events = await readAllEvents2(join14(ws.paths.sessions, sessionId));
8996
9067
  return { session, events };
8997
9068
  } catch {
8998
9069
  return { session, events: [], degraded: true };
@@ -9212,12 +9283,12 @@ async function buildSingleDeps(ctx, cwd) {
9212
9283
  return { workspaces: [entry], mode: "single", nowProvider: nowProviderOf(ctx) };
9213
9284
  }
9214
9285
  async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
9215
- const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve10(cwd, p) })) : await loadPortfolioConfig(ctx.portfolioConfigPath);
9286
+ const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve11(cwd, p) })) : await loadPortfolioConfig(ctx.portfolioConfigPath);
9216
9287
  const entries = [];
9217
9288
  const seenPath = /* @__PURE__ */ new Set();
9218
9289
  const seenKey = /* @__PURE__ */ new Set();
9219
9290
  for (const spec of specs) {
9220
- const repoRoot = resolve10(spec.path);
9291
+ const repoRoot = resolve11(spec.path);
9221
9292
  if (seenPath.has(repoRoot)) continue;
9222
9293
  seenPath.add(repoRoot);
9223
9294
  const entry = await buildWorkspaceEntry(repoRoot, ctx, spec.label);
@@ -9289,7 +9360,7 @@ function openInBrowser(url, override) {
9289
9360
  }
9290
9361
  }
9291
9362
  function waitForShutdown(signal) {
9292
- return new Promise((resolve11) => {
9363
+ return new Promise((resolve12) => {
9293
9364
  const cleanup = () => {
9294
9365
  process.off("SIGINT", onSignal);
9295
9366
  process.off("SIGTERM", onSignal);
@@ -9297,18 +9368,18 @@ function waitForShutdown(signal) {
9297
9368
  };
9298
9369
  const onSignal = () => {
9299
9370
  cleanup();
9300
- resolve11();
9371
+ resolve12();
9301
9372
  };
9302
9373
  const onAbort = () => {
9303
9374
  cleanup();
9304
- resolve11();
9375
+ resolve12();
9305
9376
  };
9306
9377
  process.on("SIGINT", onSignal);
9307
9378
  process.on("SIGTERM", onSignal);
9308
9379
  if (signal !== void 0) {
9309
9380
  if (signal.aborted) {
9310
9381
  cleanup();
9311
- resolve11();
9382
+ resolve12();
9312
9383
  return;
9313
9384
  }
9314
9385
  signal.addEventListener("abort", onAbort);