@basou/cli 0.14.1 → 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/index.js CHANGED
@@ -634,18 +634,18 @@ function printNoApprovals(options) {
634
634
 
635
635
  // src/commands/decision.ts
636
636
  import { readFile } from "fs/promises";
637
- import { homedir } from "os";
638
- import { resolve } from "path";
637
+ import { homedir as homedir2 } from "os";
638
+ import { resolve as resolve3 } from "path";
639
639
  import {
640
640
  acquireLock as acquireLock2,
641
641
  appendEventToExistingSession,
642
642
  assertBasouRootSafe as assertBasouRootSafe2,
643
- basouPaths as basouPaths2,
643
+ basouPaths as basouPaths3,
644
644
  createAdHocSessionWithEvent,
645
645
  findErrorCode as findErrorCode2,
646
646
  isValidPrefixedId,
647
647
  prefixedUlid as prefixedUlid2,
648
- readManifest,
648
+ readManifest as readManifest2,
649
649
  resolveRepositoryRoot as resolveRepositoryRoot2,
650
650
  resolveSessionId,
651
651
  sanitizePath
@@ -653,11 +653,73 @@ import {
653
653
  import { InvalidArgumentError } from "commander";
654
654
 
655
655
  // src/lib/repo-root.ts
656
- import { resolveBasouRepositoryRoot } from "@basou/core";
657
- async function resolveBasouRootForCommand(cwd, commandName) {
656
+ import { realpath, stat } from "fs/promises";
657
+ import { basename, resolve as resolve2 } from "path";
658
+ import { basouPaths as basouPaths2, readManifest, resolveBasouRepositoryRoot } from "@basou/core";
659
+
660
+ // src/lib/portfolio-config.ts
661
+ import { homedir } from "os";
662
+ import { isAbsolute, join as join2, resolve } from "path";
663
+ import { readYamlFile as readYamlFile2 } from "@basou/core";
664
+ var DEFAULT_PORTFOLIO_CONFIG_PATH = join2(homedir(), ".basou", "portfolio.yaml");
665
+ function expandTilde(p) {
666
+ if (p === "~") return homedir();
667
+ if (p.startsWith("~/")) return join2(homedir(), p.slice(2));
668
+ return p;
669
+ }
670
+ function isRecord(value) {
671
+ return typeof value === "object" && value !== null && !Array.isArray(value);
672
+ }
673
+ async function loadPortfolioConfig(configPath = DEFAULT_PORTFOLIO_CONFIG_PATH) {
674
+ let raw;
675
+ try {
676
+ raw = await readYamlFile2(configPath);
677
+ } catch (error) {
678
+ if (error instanceof Error && error.message === "YAML file not found") {
679
+ throw new Error(
680
+ "No portfolio config at ~/.basou/portfolio.yaml. Create one (a 'workspaces:' list of repo paths) or pass --workspace <path>."
681
+ );
682
+ }
683
+ if (error instanceof Error && error.message === "Failed to parse YAML content") {
684
+ throw new Error("~/.basou/portfolio.yaml is not valid YAML.");
685
+ }
686
+ throw error;
687
+ }
688
+ if (!isRecord(raw) || !Array.isArray(raw.workspaces)) {
689
+ throw new Error("~/.basou/portfolio.yaml must contain a 'workspaces:' list.");
690
+ }
691
+ const seen = /* @__PURE__ */ new Set();
692
+ const result = [];
693
+ for (const entry of raw.workspaces) {
694
+ if (!isRecord(entry) || typeof entry.path !== "string" || entry.path.trim().length === 0) {
695
+ throw new Error("Each portfolio workspace needs a non-empty string 'path'.");
696
+ }
697
+ if (entry.label !== void 0 && typeof entry.label !== "string") {
698
+ throw new Error("A portfolio workspace 'label' must be a string when present.");
699
+ }
700
+ const expanded = expandTilde(entry.path.trim());
701
+ if (!isAbsolute(expanded)) {
702
+ throw new Error(
703
+ "Portfolio workspace paths must be absolute (or start with '~'); use --workspace for relative ad-hoc paths."
704
+ );
705
+ }
706
+ const abs = resolve(expanded);
707
+ if (seen.has(abs)) continue;
708
+ seen.add(abs);
709
+ result.push(entry.label !== void 0 ? { path: abs, label: entry.label } : { path: abs });
710
+ }
711
+ if (result.length === 0) {
712
+ throw new Error("~/.basou/portfolio.yaml has no workspaces.");
713
+ }
714
+ return result;
715
+ }
716
+
717
+ // src/lib/repo-root.ts
718
+ async function resolveBasouRootForCommand(cwd, commandName, opts = {}) {
719
+ let root;
658
720
  try {
659
- return await resolveBasouRepositoryRoot(cwd, {
660
- onRedirect: ({ via, root }) => console.error(`Resolved workspace view to ${root} (via ${via}).`)
721
+ root = await resolveBasouRepositoryRoot(cwd, {
722
+ onRedirect: ({ via, root: root2 }) => console.error(`Resolved workspace view to ${root2} (via ${via}).`)
661
723
  });
662
724
  } catch (error) {
663
725
  if (error instanceof Error && error.message === "Not a git repository") {
@@ -668,6 +730,84 @@ async function resolveBasouRootForCommand(cwd, commandName) {
668
730
  }
669
731
  throw error;
670
732
  }
733
+ if (!await hasBasouStore(root)) {
734
+ const master = await resolveMemberToMaster(
735
+ root,
736
+ opts.portfolioConfigPath ?? DEFAULT_PORTFOLIO_CONFIG_PATH
737
+ );
738
+ if (master !== void 0) {
739
+ console.error(
740
+ `Resolved portfolio member to ${master.root} (via portfolio: ${master.label}).`
741
+ );
742
+ return master.root;
743
+ }
744
+ }
745
+ return root;
746
+ }
747
+ async function hasBasouStore(root) {
748
+ try {
749
+ return (await stat(basouPaths2(root).root)).isDirectory();
750
+ } catch {
751
+ return false;
752
+ }
753
+ }
754
+ async function resolveMemberToMaster(repoRoot, configPath) {
755
+ let workspaces;
756
+ try {
757
+ workspaces = await loadPortfolioConfig(configPath);
758
+ } catch (error) {
759
+ if (!(error instanceof Error) || !error.message.startsWith("No portfolio config at")) {
760
+ const detail = error instanceof Error ? error.message : String(error);
761
+ console.error(`Ignoring ~/.basou/portfolio.yaml: ${detail}`);
762
+ }
763
+ return void 0;
764
+ }
765
+ const memberReal = await realpathOrNull(repoRoot);
766
+ if (memberReal === null) return void 0;
767
+ const claimants = /* @__PURE__ */ new Map();
768
+ const seenMaster = /* @__PURE__ */ new Set();
769
+ for (const ws of workspaces) {
770
+ const masterReal = await realpathOrNull(ws.path);
771
+ if (masterReal === null) continue;
772
+ if (masterReal === memberReal) continue;
773
+ if (seenMaster.has(masterReal)) continue;
774
+ seenMaster.add(masterReal);
775
+ let manifest;
776
+ try {
777
+ manifest = await readManifest(basouPaths2(masterReal));
778
+ } catch (error) {
779
+ if (error instanceof Error && error.message !== "YAML file not found") {
780
+ console.error(
781
+ `Skipping portfolio workspace '${ws.label ?? basename(masterReal)}': could not read its manifest (${error.message}).`
782
+ );
783
+ }
784
+ continue;
785
+ }
786
+ const sourceRoots = manifest.import?.source_roots ?? ["."];
787
+ for (const sr of sourceRoots) {
788
+ const real = await realpathOrNull(resolve2(masterReal, sr));
789
+ if (real !== null && real === memberReal) {
790
+ claimants.set(masterReal, { root: masterReal, label: ws.label ?? basename(masterReal) });
791
+ break;
792
+ }
793
+ }
794
+ }
795
+ const matched = [...claimants.values()];
796
+ if (matched.length === 1) return matched[0];
797
+ if (matched.length > 1) {
798
+ const names = matched.map((c) => c.label).join(", ");
799
+ throw new Error(
800
+ `This repository is declared as a source root by ${matched.length} portfolio workspaces (${names}). Disambiguate in ~/.basou/portfolio.yaml so only one aggregates it.`
801
+ );
802
+ }
803
+ return void 0;
804
+ }
805
+ async function realpathOrNull(p) {
806
+ try {
807
+ return await realpath(p);
808
+ } catch {
809
+ return null;
810
+ }
671
811
  }
672
812
 
673
813
  // src/commands/decision.ts
@@ -742,7 +882,7 @@ async function runDecisionRecord(options, ctx = {}) {
742
882
  async function doRunDecisionRecord(options, ctx) {
743
883
  const cwd = ctx.cwd ?? process.cwd();
744
884
  const repositoryRoot = await resolveRepositoryRootForDecision(cwd);
745
- const paths = basouPaths2(repositoryRoot);
885
+ const paths = basouPaths3(repositoryRoot);
746
886
  await assertWorkspaceInitialized2(paths.root);
747
887
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
748
888
  const occurredAt = now.toISOString();
@@ -780,7 +920,7 @@ async function doRunDecisionRecord(options, ctx) {
780
920
  });
781
921
  return;
782
922
  }
783
- const manifest = await readManifest(paths);
923
+ const manifest = await readManifest2(paths);
784
924
  const adHoc = await createAdHocSessionWithEvent({
785
925
  paths,
786
926
  manifest,
@@ -827,7 +967,7 @@ async function runDecisionCapture(options, ctx = {}) {
827
967
  async function doRunDecisionCapture(options, ctx) {
828
968
  const cwd = ctx.cwd ?? process.cwd();
829
969
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "decision capture");
830
- const paths = basouPaths2(repositoryRoot);
970
+ const paths = basouPaths3(repositoryRoot);
831
971
  await assertWorkspaceInitialized2(paths.root);
832
972
  const raw = await readCaptureInput(options, ctx);
833
973
  const decisions = parseCaptureInput(raw);
@@ -838,12 +978,12 @@ async function doRunDecisionCapture(options, ctx) {
838
978
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
839
979
  const occurredAt = now.toISOString();
840
980
  const decisionIds = decisions.map(() => prefixedUlid2("decision"));
841
- const manifest = await readManifest(paths);
981
+ const manifest = await readManifest2(paths);
842
982
  const invocationArgs = options.file !== void 0 ? [
843
983
  "--file",
844
- sanitizePath(resolve(cwd, options.file), {
984
+ sanitizePath(resolve3(cwd, options.file), {
845
985
  workingDirectory: repositoryRoot,
846
- homedir: homedir()
986
+ homedir: homedir2()
847
987
  })
848
988
  ] : [];
849
989
  const adHoc = await createAdHocSessionWithEvent({
@@ -1201,7 +1341,7 @@ async function assertWorkspaceInitialized2(basouRoot) {
1201
1341
  // src/commands/decisions.ts
1202
1342
  import {
1203
1343
  assertBasouRootSafe as assertBasouRootSafe3,
1204
- basouPaths as basouPaths3,
1344
+ basouPaths as basouPaths4,
1205
1345
  findErrorCode as findErrorCode3,
1206
1346
  readMarkdownFile,
1207
1347
  renderDecisions,
@@ -1227,7 +1367,7 @@ async function doRunDecisionsGenerate(options, ctx) {
1227
1367
  void options;
1228
1368
  const cwd = ctx.cwd ?? process.cwd();
1229
1369
  const repositoryRoot = await resolveRepositoryRootForDecisions(cwd);
1230
- const paths = basouPaths3(repositoryRoot);
1370
+ const paths = basouPaths4(repositoryRoot);
1231
1371
  await assertWorkspaceInitialized3(paths.root);
1232
1372
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
1233
1373
  const result = await renderDecisions({
@@ -1267,12 +1407,12 @@ async function assertWorkspaceInitialized3(basouRoot) {
1267
1407
 
1268
1408
  // src/commands/exec.ts
1269
1409
  import { mkdir } from "fs/promises";
1270
- import { homedir as homedir2 } from "os";
1271
- import { join as join2 } from "path";
1410
+ import { homedir as homedir3 } from "os";
1411
+ import { join as join3 } from "path";
1272
1412
  import {
1273
1413
  acquireLock as acquireLock3,
1274
1414
  assertBasouRootSafe as assertBasouRootSafe4,
1275
- basouPaths as basouPaths4,
1415
+ basouPaths as basouPaths5,
1276
1416
  ChildProcessRunner,
1277
1417
  appendChainedEvent as coreAppendChainedEvent,
1278
1418
  finalizeSessionYaml,
@@ -1280,8 +1420,8 @@ import {
1280
1420
  overwriteYamlFile,
1281
1421
  parseDuration,
1282
1422
  prefixedUlid as prefixedUlid3,
1283
- readManifest as readManifest2,
1284
- readYamlFile as readYamlFile2,
1423
+ readManifest as readManifest3,
1424
+ readYamlFile as readYamlFile3,
1285
1425
  resolveRepositoryRoot as resolveRepositoryRoot4,
1286
1426
  SessionSchema,
1287
1427
  sanitizeWorkingDirectory,
@@ -1304,17 +1444,17 @@ async function runExec(command, args, options, ctx = {}) {
1304
1444
  const cwd = options.cwd ?? process.cwd();
1305
1445
  const timeout_ms = options.timeout !== void 0 ? parseDuration(options.timeout) : void 0;
1306
1446
  const repoRoot = await resolveRepositoryRootForExec(cwd);
1307
- const paths = basouPaths4(repoRoot);
1447
+ const paths = basouPaths5(repoRoot);
1308
1448
  await assertBasouRootSafe4(paths.root);
1309
- const manifest = await readManifest2(paths);
1449
+ const manifest = await readManifest3(paths);
1310
1450
  const sessionId = prefixedUlid3("ses");
1311
- const sessionDir = join2(paths.sessions, sessionId);
1451
+ const sessionDir = join3(paths.sessions, sessionId);
1312
1452
  await mkdir(sessionDir, { recursive: true });
1313
1453
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
1314
1454
  await coreAppendChainedEvent(paths, sessionId, event);
1315
1455
  });
1316
1456
  const startedAt = now().toISOString();
1317
- const sessionYamlPath = join2(sessionDir, "session.yaml");
1457
+ const sessionYamlPath = join3(sessionDir, "session.yaml");
1318
1458
  const session = buildInitialSession({
1319
1459
  id: sessionId,
1320
1460
  command,
@@ -1519,7 +1659,7 @@ function buildInitialSession(input) {
1519
1659
  source: { kind: "terminal", version: "0.1.0" },
1520
1660
  started_at: input.startedAt,
1521
1661
  status: "initialized",
1522
- working_directory: sanitizeWorkingDirectory(input.cwd, { homedir: homedir2() }),
1662
+ working_directory: sanitizeWorkingDirectory(input.cwd, { homedir: homedir3() }),
1523
1663
  invocation: {
1524
1664
  command: input.command,
1525
1665
  args: [...input.args],
@@ -1531,7 +1671,7 @@ function buildInitialSession(input) {
1531
1671
  };
1532
1672
  }
1533
1673
  async function mutateSessionYaml(filePath, mutator) {
1534
- const raw = await readYamlFile2(filePath);
1674
+ const raw = await readYamlFile3(filePath);
1535
1675
  const parsed = SessionSchema.parse(raw);
1536
1676
  mutator(parsed);
1537
1677
  const validated = SessionSchema.parse(parsed);
@@ -1593,7 +1733,7 @@ async function resolveRepositoryRootForExec(cwd) {
1593
1733
  // src/commands/handoff.ts
1594
1734
  import {
1595
1735
  assertBasouRootSafe as assertBasouRootSafe5,
1596
- basouPaths as basouPaths5,
1736
+ basouPaths as basouPaths6,
1597
1737
  findErrorCode as findErrorCode4,
1598
1738
  readMarkdownFile as readMarkdownFile2,
1599
1739
  renderHandoff,
@@ -1619,7 +1759,7 @@ async function doRunHandoffGenerate(options, ctx) {
1619
1759
  void options;
1620
1760
  const cwd = ctx.cwd ?? process.cwd();
1621
1761
  const repositoryRoot = await resolveRepositoryRootForHandoff(cwd);
1622
- const paths = basouPaths5(repositoryRoot);
1762
+ const paths = basouPaths6(repositoryRoot);
1623
1763
  await assertWorkspaceInitialized4(paths.root);
1624
1764
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
1625
1765
  const result = await renderHandoff({
@@ -1662,13 +1802,13 @@ async function assertWorkspaceInitialized4(basouRoot) {
1662
1802
 
1663
1803
  // src/commands/import.ts
1664
1804
  import { createReadStream } from "fs";
1665
- import { readdir, readFile as readFile2, rm, stat } from "fs/promises";
1666
- import { homedir as homedir3 } from "os";
1667
- import { basename, join as join3, resolve as resolve2 } from "path";
1805
+ import { readdir, readFile as readFile2, rm, stat as stat2 } from "fs/promises";
1806
+ import { homedir as homedir4 } from "os";
1807
+ import { basename as basename2, join as join4, resolve as resolve4 } from "path";
1668
1808
  import { createInterface } from "readline";
1669
1809
  import {
1670
1810
  assertBasouRootSafe as assertBasouRootSafe6,
1671
- basouPaths as basouPaths6,
1811
+ basouPaths as basouPaths7,
1672
1812
  CLAUDE_IMPORT_SOURCE,
1673
1813
  CODEX_IMPORT_SOURCE,
1674
1814
  claudeTranscriptToImportPayload,
@@ -1676,7 +1816,7 @@ import {
1676
1816
  enumerateSessionDirs,
1677
1817
  findErrorCode as findErrorCode5,
1678
1818
  importSessionFromJson,
1679
- readManifest as readManifest3,
1819
+ readManifest as readManifest4,
1680
1820
  readSessionYaml as readSessionYaml2,
1681
1821
  reimportPreservingId,
1682
1822
  resolveRepositoryRoot as resolveRepositoryRoot6,
@@ -1732,10 +1872,10 @@ function resolveSourceRoots(args) {
1732
1872
  const { projectFlags, manifest, repoRoot, cwd } = args;
1733
1873
  let resolved;
1734
1874
  if (projectFlags.length > 0) {
1735
- resolved = projectFlags.map((p) => resolve2(cwd, p));
1875
+ resolved = projectFlags.map((p) => resolve4(cwd, p));
1736
1876
  } else {
1737
1877
  const roots = manifest.import?.source_roots;
1738
- resolved = roots !== void 0 && roots.length > 0 ? roots.map((r) => resolve2(repoRoot, r)) : [repoRoot];
1878
+ resolved = roots !== void 0 && roots.length > 0 ? roots.map((r) => resolve4(repoRoot, r)) : [repoRoot];
1739
1879
  }
1740
1880
  return [...new Set(resolved)];
1741
1881
  }
@@ -1748,11 +1888,11 @@ async function doRunImportClaudeCode(options, ctx) {
1748
1888
  repoRoot: repositoryRoot,
1749
1889
  cwd: ctx.cwd ?? process.cwd()
1750
1890
  });
1751
- const projectsRoot = ctx.claudeProjectsDir ?? join3(homedir3(), ".claude", "projects");
1891
+ const projectsRoot = ctx.claudeProjectsDir ?? join4(homedir4(), ".claude", "projects");
1752
1892
  const files = await selectTranscriptFiles(projectsRoot, projectPaths, options);
1753
1893
  const projectSet = new Set(projectPaths);
1754
1894
  const candidates = files.map((file) => {
1755
- const externalId = basename(file, ".jsonl");
1895
+ const externalId = basename2(file, ".jsonl");
1756
1896
  return {
1757
1897
  externalId,
1758
1898
  sourcePath: file,
@@ -1779,7 +1919,7 @@ async function doRunImportCodex(options, ctx) {
1779
1919
  repoRoot: repositoryRoot,
1780
1920
  cwd: ctx.cwd ?? process.cwd()
1781
1921
  });
1782
- const sessionsRoot = ctx.codexSessionsDir ?? join3(homedir3(), ".codex", "sessions");
1922
+ const sessionsRoot = ctx.codexSessionsDir ?? join4(homedir4(), ".codex", "sessions");
1783
1923
  const rollouts = await discoverCodexRollouts(sessionsRoot, projectPaths, options);
1784
1924
  const candidates = rollouts.map(({ file, externalId }) => ({
1785
1925
  externalId,
@@ -1806,9 +1946,9 @@ function assertSelector(options) {
1806
1946
  async function resolveImportTarget(ctx) {
1807
1947
  const cwd = ctx.cwd ?? process.cwd();
1808
1948
  const repositoryRoot = await resolveRepositoryRootForImport(cwd);
1809
- const paths = basouPaths6(repositoryRoot);
1949
+ const paths = basouPaths7(repositoryRoot);
1810
1950
  await assertWorkspaceInitialized5(paths.root);
1811
- const manifest = await readManifest3(paths);
1951
+ const manifest = await readManifest4(paths);
1812
1952
  return { repositoryRoot, paths, manifest };
1813
1953
  }
1814
1954
  async function importDerivedSessions(paths, manifest, options, sourceKind, candidates) {
@@ -1880,7 +2020,7 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
1880
2020
  if (priors.length > 0 && options.force === true) {
1881
2021
  if (options.dryRun !== true) {
1882
2022
  for (const { sessionId } of priors) {
1883
- await rm(join3(paths.sessions, sessionId), { recursive: true, force: true });
2023
+ await rm(join4(paths.sessions, sessionId), { recursive: true, force: true });
1884
2024
  }
1885
2025
  }
1886
2026
  counts.replaced++;
@@ -1980,7 +2120,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
1980
2120
  if (options.session !== void 0) {
1981
2121
  const matches = [];
1982
2122
  for (const projectPath of projectPaths) {
1983
- const file = join3(projectsRoot, encodeProjectDir(projectPath), `${options.session}.jsonl`);
2123
+ const file = join4(projectsRoot, encodeProjectDir(projectPath), `${options.session}.jsonl`);
1984
2124
  if (await pathExists(file)) matches.push(file);
1985
2125
  }
1986
2126
  if (matches.length === 0) {
@@ -1991,7 +2131,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
1991
2131
  const files = [];
1992
2132
  let anyDirFound = false;
1993
2133
  for (const projectPath of projectPaths) {
1994
- const transcriptDir = join3(projectsRoot, encodeProjectDir(projectPath));
2134
+ const transcriptDir = join4(projectsRoot, encodeProjectDir(projectPath));
1995
2135
  let entries;
1996
2136
  try {
1997
2137
  entries = await readdir(transcriptDir);
@@ -2001,7 +2141,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
2001
2141
  }
2002
2142
  anyDirFound = true;
2003
2143
  for (const name of entries) {
2004
- if (name.endsWith(".jsonl")) files.push(join3(transcriptDir, name));
2144
+ if (name.endsWith(".jsonl")) files.push(join4(transcriptDir, name));
2005
2145
  }
2006
2146
  }
2007
2147
  if (!anyDirFound) {
@@ -2011,7 +2151,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
2011
2151
  }
2012
2152
  async function pathExists(file) {
2013
2153
  try {
2014
- await stat(file);
2154
+ await stat2(file);
2015
2155
  return true;
2016
2156
  } catch (error) {
2017
2157
  if (findErrorCode5(error, "ENOENT")) return false;
@@ -2020,7 +2160,7 @@ async function pathExists(file) {
2020
2160
  }
2021
2161
  async function statSize(file) {
2022
2162
  try {
2023
- return (await stat(file)).size;
2163
+ return (await stat2(file)).size;
2024
2164
  } catch (error) {
2025
2165
  if (findErrorCode5(error, "ENOENT")) return void 0;
2026
2166
  throw error;
@@ -2058,7 +2198,7 @@ async function findRolloutFiles(sessionsRoot) {
2058
2198
  throw new Error("Failed to read Codex sessions directory", { cause: error });
2059
2199
  }
2060
2200
  for (const entry of entries) {
2061
- const full = join3(dir, entry.name);
2201
+ const full = join4(dir, entry.name);
2062
2202
  if (entry.isDirectory()) {
2063
2203
  await walk(full, false);
2064
2204
  } else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) {
@@ -2236,7 +2376,7 @@ async function assertWorkspaceInitialized5(basouRoot) {
2236
2376
  }
2237
2377
 
2238
2378
  // src/commands/init.ts
2239
- import { basename as basename2, relative, resolve as resolve3 } from "path";
2379
+ import { basename as basename3, relative, resolve as resolve5 } from "path";
2240
2380
  import {
2241
2381
  appendBasouGitignore,
2242
2382
  createManifest,
@@ -2275,7 +2415,7 @@ async function runInit(options, ctx = {}) {
2275
2415
  async function doRunInit(options, ctx) {
2276
2416
  const cwd = ctx.cwd ?? process.cwd();
2277
2417
  const repositoryRoot = await resolveRepositoryRootForInit(cwd);
2278
- const workspaceName = options.name ?? basename2(repositoryRoot);
2418
+ const workspaceName = options.name ?? basename3(repositoryRoot);
2279
2419
  let repositoryUrl;
2280
2420
  if (options.repoUrl !== void 0) {
2281
2421
  repositoryUrl = options.repoUrl === "" ? null : options.repoUrl;
@@ -2283,7 +2423,7 @@ async function doRunInit(options, ctx) {
2283
2423
  repositoryUrl = await tryRemoteUrl(repositoryRoot);
2284
2424
  }
2285
2425
  const sourceRoots = (options.sourceRoot ?? []).map((p) => {
2286
- const rel = relative(repositoryRoot, resolve3(cwd, p));
2426
+ const rel = relative(repositoryRoot, resolve5(cwd, p));
2287
2427
  return rel === "" ? "." : rel;
2288
2428
  });
2289
2429
  const paths = await ensureBasouDirectory(repositoryRoot);
@@ -2330,10 +2470,10 @@ import {
2330
2470
  acquireLock as acquireLock4,
2331
2471
  appendEventToExistingSession as appendEventToExistingSession2,
2332
2472
  assertBasouRootSafe as assertBasouRootSafe7,
2333
- basouPaths as basouPaths7,
2473
+ basouPaths as basouPaths8,
2334
2474
  createAdHocSessionWithEvent as createAdHocSessionWithEvent2,
2335
2475
  findErrorCode as findErrorCode6,
2336
- readManifest as readManifest4,
2476
+ readManifest as readManifest5,
2337
2477
  resolveSessionId as resolveSessionId2
2338
2478
  } from "@basou/core";
2339
2479
  import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
@@ -2364,7 +2504,7 @@ async function doRunNote(body, options, ctx) {
2364
2504
  }
2365
2505
  const cwd = ctx.cwd ?? process.cwd();
2366
2506
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "note");
2367
- const paths = basouPaths7(repositoryRoot);
2507
+ const paths = basouPaths8(repositoryRoot);
2368
2508
  await assertWorkspaceInitialized6(paths.root);
2369
2509
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
2370
2510
  const occurredAt = now.toISOString();
@@ -2391,7 +2531,7 @@ async function doRunNote(body, options, ctx) {
2391
2531
  });
2392
2532
  return;
2393
2533
  }
2394
- const manifest = await readManifest4(paths);
2534
+ const manifest = await readManifest5(paths);
2395
2535
  const adHoc = await createAdHocSessionWithEvent2({
2396
2536
  paths,
2397
2537
  manifest,
@@ -2474,12 +2614,68 @@ async function assertWorkspaceInitialized6(basouRoot) {
2474
2614
  // src/commands/orient.ts
2475
2615
  import {
2476
2616
  assertBasouRootSafe as assertBasouRootSafe8,
2477
- basouPaths as basouPaths8,
2617
+ basouPaths as basouPaths9,
2478
2618
  findErrorCode as findErrorCode7,
2479
2619
  renderOrientation as renderOrientation2,
2480
2620
  writeMarkdownFile as writeMarkdownFile4
2481
2621
  } from "@basou/core";
2482
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
+
2483
2679
  // src/lib/provenance-actions.ts
2484
2680
  import {
2485
2681
  readMarkdownFile as readMarkdownFile3,
@@ -2679,21 +2875,36 @@ async function runOrient(options, ctx = {}) {
2679
2875
  async function doRunOrient(options, ctx) {
2680
2876
  const cwd = ctx.cwd ?? process.cwd();
2681
2877
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "orient");
2682
- const paths = basouPaths8(repositoryRoot);
2878
+ const paths = basouPaths9(repositoryRoot);
2683
2879
  await assertWorkspaceInitialized7(paths.root);
2684
2880
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
2685
2881
  const probeCtx = { cwd: repositoryRoot };
2686
2882
  if (ctx.claudeProjectsDir !== void 0) probeCtx.claudeProjectsDir = ctx.claudeProjectsDir;
2687
2883
  if (ctx.codexSessionsDir !== void 0) probeCtx.codexSessionsDir = ctx.codexSessionsDir;
2688
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
+ }
2689
2896
  const result = await renderOrientation2({
2690
2897
  paths,
2691
2898
  nowIso,
2692
2899
  staleness,
2693
2900
  verbose: options.verbose === true,
2901
+ federatedRoots,
2694
2902
  onWarning: (w, sid) => printReplayWarning(w, sid),
2695
2903
  onSessionSkip: (sid, reason) => printSessionSkip(sid, reason),
2696
- 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
+ )
2697
2908
  });
2698
2909
  await writeMarkdownFile4(paths.files.orientation, `${result.body}
2699
2910
  `);
@@ -2730,9 +2941,9 @@ import {
2730
2941
  unlinkSync,
2731
2942
  writeFileSync
2732
2943
  } from "fs";
2733
- import { basename as basename3, dirname, isAbsolute, join as join4, relative as relative2, resolve as resolve4 } from "path";
2944
+ import { basename as basename4, dirname, isAbsolute as isAbsolute3, join as join6, relative as relative2, resolve as resolve7 } from "path";
2734
2945
  import {
2735
- basouPaths as basouPaths9,
2946
+ basouPaths as basouPaths10,
2736
2947
  GENERATED_END,
2737
2948
  GENERATED_START,
2738
2949
  isGitNotFound,
@@ -2743,7 +2954,7 @@ import {
2743
2954
  planRename,
2744
2955
  planRosterAdoption,
2745
2956
  planWorkspaceView,
2746
- readManifest as readManifest5,
2957
+ readManifest as readManifest6,
2747
2958
  readMarkdownFile as readMarkdownFile4,
2748
2959
  reconcileSourceRoots,
2749
2960
  renderWithMarkers as renderWithMarkers4,
@@ -2850,8 +3061,8 @@ function preservedUnknownLines(fields) {
2850
3061
  async function doRunProjectCheck(options, ctx) {
2851
3062
  const cwd = ctx.cwd ?? process.cwd();
2852
3063
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project check");
2853
- const paths = basouPaths9(repositoryRoot);
2854
- const manifest = await readManifest5(paths);
3064
+ const paths = basouPaths10(repositoryRoot);
3065
+ const manifest = await readManifest6(paths);
2855
3066
  const summary = summarizeRosterDrift({
2856
3067
  ...manifest.repos !== void 0 ? { repos: manifest.repos } : {},
2857
3068
  sourceRoots: effectiveSourceRoots(manifest)
@@ -2912,8 +3123,8 @@ async function runProjectSync(options, ctx = {}) {
2912
3123
  async function doRunProjectSync(options, ctx) {
2913
3124
  const cwd = ctx.cwd ?? process.cwd();
2914
3125
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project sync");
2915
- const paths = basouPaths9(repositoryRoot);
2916
- const manifest = await readManifest5(paths);
3126
+ const paths = basouPaths10(repositoryRoot);
3127
+ const manifest = await readManifest6(paths);
2917
3128
  const hasRoster = manifest.repos !== void 0 && manifest.repos.length > 0;
2918
3129
  const reconcile = reconcileSourceRoots({
2919
3130
  ...manifest.repos !== void 0 ? { repos: manifest.repos } : {},
@@ -2982,20 +3193,20 @@ async function runProjectAdopt(options, ctx = {}) {
2982
3193
  }
2983
3194
  }
2984
3195
  function classifySourceRoot(repositoryRoot, declaredPath) {
2985
- const absolute = resolve4(repositoryRoot, declaredPath);
3196
+ const absolute = resolve7(repositoryRoot, declaredPath);
2986
3197
  let real;
2987
3198
  try {
2988
3199
  real = realpathSync(absolute);
2989
3200
  } catch {
2990
3201
  return { path: declaredPath, kind: "unresolved" };
2991
3202
  }
2992
- return { path: declaredPath, kind: existsSync(join4(real, ".git")) ? "repo" : "non-repo" };
3203
+ return { path: declaredPath, kind: existsSync(join6(real, ".git")) ? "repo" : "non-repo" };
2993
3204
  }
2994
3205
  async function doRunProjectAdopt(options, ctx) {
2995
3206
  const cwd = ctx.cwd ?? process.cwd();
2996
3207
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project adopt");
2997
- const paths = basouPaths9(repositoryRoot);
2998
- const manifest = await readManifest5(paths);
3208
+ const paths = basouPaths10(repositoryRoot);
3209
+ const manifest = await readManifest6(paths);
2999
3210
  const alreadyDeclared = manifest.repos !== void 0 && manifest.repos.length > 0;
3000
3211
  const candidates = effectiveSourceRoots(manifest).map(
3001
3212
  (r) => classifySourceRoot(repositoryRoot, r)
@@ -3084,11 +3295,11 @@ async function gatherRepoWiring(repositoryRoot, entry) {
3084
3295
  };
3085
3296
  let real;
3086
3297
  try {
3087
- real = realpathSync(resolve4(repositoryRoot, entry.path));
3298
+ real = realpathSync(resolve7(repositoryRoot, entry.path));
3088
3299
  } catch {
3089
3300
  return { ...base, reachable: false, instructionFiles: [] };
3090
3301
  }
3091
- if (!existsSync(join4(real, ".git"))) {
3302
+ if (!existsSync(join6(real, ".git"))) {
3092
3303
  return { ...base, reachable: false, instructionFiles: [] };
3093
3304
  }
3094
3305
  try {
@@ -3096,7 +3307,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
3096
3307
  for (const name of INSTRUCTION_FILES) {
3097
3308
  let present = true;
3098
3309
  try {
3099
- lstatSync(join4(real, name));
3310
+ lstatSync(join6(real, name));
3100
3311
  } catch {
3101
3312
  present = false;
3102
3313
  }
@@ -3111,8 +3322,8 @@ async function gatherRepoWiring(repositoryRoot, entry) {
3111
3322
  async function doRunProjectWiring(options, ctx) {
3112
3323
  const cwd = ctx.cwd ?? process.cwd();
3113
3324
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project wiring");
3114
- const paths = basouPaths9(repositoryRoot);
3115
- const manifest = await readManifest5(paths);
3325
+ const paths = basouPaths10(repositoryRoot);
3326
+ const manifest = await readManifest6(paths);
3116
3327
  const roster = manifest.repos ?? [];
3117
3328
  const facts = [];
3118
3329
  for (const entry of roster) facts.push(await gatherRepoWiring(repositoryRoot, entry));
@@ -3189,14 +3400,14 @@ function gatherRepoGitignore(repositoryRoot, entry) {
3189
3400
  };
3190
3401
  let real;
3191
3402
  try {
3192
- real = realpathSync(resolve4(repositoryRoot, entry.path));
3403
+ real = realpathSync(resolve7(repositoryRoot, entry.path));
3193
3404
  } catch {
3194
3405
  return { ...base, reachable: false, currentLines: [] };
3195
3406
  }
3196
- if (!existsSync(join4(real, ".git"))) {
3407
+ if (!existsSync(join6(real, ".git"))) {
3197
3408
  return { ...base, reachable: false, currentLines: [] };
3198
3409
  }
3199
- return { ...base, reachable: true, currentLines: readGitignoreLines(join4(real, ".gitignore")) };
3410
+ return { ...base, reachable: true, currentLines: readGitignoreLines(join6(real, ".gitignore")) };
3200
3411
  }
3201
3412
  function hasErrorCode(error) {
3202
3413
  return error instanceof Error && typeof error.code === "string";
@@ -3210,7 +3421,7 @@ function readGitignoreLines(file) {
3210
3421
  }
3211
3422
  }
3212
3423
  function applyGitignorePlan(repositoryRoot, plan) {
3213
- const file = join4(realpathSync(resolve4(repositoryRoot, plan.path)), ".gitignore");
3424
+ const file = join6(realpathSync(resolve7(repositoryRoot, plan.path)), ".gitignore");
3214
3425
  let existing = "";
3215
3426
  try {
3216
3427
  existing = readFileSync(file, "utf8");
@@ -3230,8 +3441,8 @@ function applyGitignorePlan(repositoryRoot, plan) {
3230
3441
  async function doRunProjectGitignore(options, ctx) {
3231
3442
  const cwd = ctx.cwd ?? process.cwd();
3232
3443
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project gitignore");
3233
- const paths = basouPaths9(repositoryRoot);
3234
- const manifest = await readManifest5(paths);
3444
+ const paths = basouPaths10(repositoryRoot);
3445
+ const manifest = await readManifest6(paths);
3235
3446
  const roster = manifest.repos ?? [];
3236
3447
  const facts = roster.map((entry) => gatherRepoGitignore(repositoryRoot, entry));
3237
3448
  const summary = planGitignore({ repos: facts, required: [...INSTRUCTION_FILES] });
@@ -3322,23 +3533,23 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
3322
3533
  const base = { path: entry.path };
3323
3534
  let real;
3324
3535
  try {
3325
- real = realpathSync(resolve4(repositoryRoot, entry.path));
3536
+ real = realpathSync(resolve7(repositoryRoot, entry.path));
3326
3537
  } catch {
3327
3538
  return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
3328
3539
  }
3329
3540
  if (real === anchorReal) {
3330
3541
  return { ...base, isAnchor: true, reachable: true, canonicalPresent: false, files: [] };
3331
3542
  }
3332
- if (!existsSync(join4(real, ".git"))) {
3543
+ if (!existsSync(join6(real, ".git"))) {
3333
3544
  return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
3334
3545
  }
3335
- const canonicalFile = join4(anchorReal, "agents", basename3(real), CANONICAL_FILE);
3546
+ const canonicalFile = join6(anchorReal, "agents", basename4(real), CANONICAL_FILE);
3336
3547
  if (!existsSync(canonicalFile)) {
3337
3548
  return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
3338
3549
  }
3339
3550
  const files = expectedSymlinkTargets(real, canonicalFile).map(
3340
3551
  (spec) => {
3341
- const { state, actualTarget } = inspectSymlink(join4(real, spec.name), spec.target);
3552
+ const { state, actualTarget } = inspectSymlink(join6(real, spec.name), spec.target);
3342
3553
  return {
3343
3554
  name: spec.name,
3344
3555
  expectedTarget: spec.target,
@@ -3352,14 +3563,14 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
3352
3563
  isAnchor: false,
3353
3564
  reachable: true,
3354
3565
  canonicalPresent: true,
3355
- canonicalName: basename3(real),
3566
+ canonicalName: basename4(real),
3356
3567
  files
3357
3568
  };
3358
3569
  }
3359
3570
  function applySymlinkPlan(repositoryRoot, plan) {
3360
3571
  let real;
3361
3572
  try {
3362
- real = realpathSync(resolve4(repositoryRoot, plan.path));
3573
+ real = realpathSync(resolve7(repositoryRoot, plan.path));
3363
3574
  } catch (error) {
3364
3575
  const message = failureReason(error);
3365
3576
  return { created: [], failed: plan.toCreate.map((c) => ({ file: c.name, message })) };
@@ -3367,7 +3578,7 @@ function applySymlinkPlan(repositoryRoot, plan) {
3367
3578
  const created = [];
3368
3579
  const failed = [];
3369
3580
  for (const { name, target } of plan.toCreate) {
3370
- const filePath = join4(real, name);
3581
+ const filePath = join6(real, name);
3371
3582
  try {
3372
3583
  mkdirSync(dirname(filePath), { recursive: true });
3373
3584
  symlinkSync(target, filePath);
@@ -3384,8 +3595,8 @@ function failureReason(error) {
3384
3595
  async function doRunProjectSymlinks(options, ctx) {
3385
3596
  const cwd = ctx.cwd ?? process.cwd();
3386
3597
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project symlinks");
3387
- const paths = basouPaths9(repositoryRoot);
3388
- const manifest = await readManifest5(paths);
3598
+ const paths = basouPaths10(repositoryRoot);
3599
+ const manifest = await readManifest6(paths);
3389
3600
  const roster = manifest.repos ?? [];
3390
3601
  const anchorReal = realpathSync(repositoryRoot);
3391
3602
  const facts = roster.map((entry) => gatherRepoSymlinks(repositoryRoot, anchorReal, entry));
@@ -3504,12 +3715,12 @@ async function runProjectWorkspace(options, ctx = {}) {
3504
3715
  }
3505
3716
  }
3506
3717
  function resolveViewDir(repositoryRoot, viewPath) {
3507
- const abs = resolve4(repositoryRoot, viewPath);
3718
+ const abs = resolve7(repositoryRoot, viewPath);
3508
3719
  try {
3509
3720
  return realpathSync(abs);
3510
3721
  } catch {
3511
3722
  try {
3512
- return join4(realpathSync(dirname(abs)), basename3(abs));
3723
+ return join6(realpathSync(dirname(abs)), basename4(abs));
3513
3724
  } catch {
3514
3725
  return abs;
3515
3726
  }
@@ -3518,7 +3729,7 @@ function resolveViewDir(repositoryRoot, viewPath) {
3518
3729
  function gatherViewRepo(repositoryRoot, viewDir, entry) {
3519
3730
  let repoReal;
3520
3731
  try {
3521
- repoReal = realpathSync(resolve4(repositoryRoot, entry.path));
3732
+ repoReal = realpathSync(resolve7(repositoryRoot, entry.path));
3522
3733
  } catch {
3523
3734
  return { path: entry.path, reachable: false };
3524
3735
  }
@@ -3526,8 +3737,8 @@ function gatherViewRepo(repositoryRoot, viewDir, entry) {
3526
3737
  if (expectedTarget === "" || expectedTarget === ".") {
3527
3738
  return { path: entry.path, reachable: false };
3528
3739
  }
3529
- const linkName = basename3(repoReal);
3530
- const { state, actualTarget } = inspectSymlink(join4(viewDir, linkName), expectedTarget);
3740
+ const linkName = basename4(repoReal);
3741
+ const { state, actualTarget } = inspectSymlink(join6(viewDir, linkName), expectedTarget);
3531
3742
  return {
3532
3743
  path: entry.path,
3533
3744
  reachable: true,
@@ -3541,7 +3752,7 @@ function applyViewPlan(viewDir, toCreate) {
3541
3752
  const created = [];
3542
3753
  const failed = [];
3543
3754
  for (const { name, target } of toCreate) {
3544
- const filePath = join4(viewDir, name);
3755
+ const filePath = join6(viewDir, name);
3545
3756
  try {
3546
3757
  mkdirSync(dirname(filePath), { recursive: true });
3547
3758
  symlinkSync(target, filePath);
@@ -3556,7 +3767,7 @@ var TOP_LEVEL_INSTRUCTION_FILES_LOWER = new Set(
3556
3767
  INSTRUCTION_FILES.filter((f) => !f.includes("/")).map((f) => f.toLowerCase())
3557
3768
  );
3558
3769
  function classifyViewLink(viewDir, name, rosterRealpaths) {
3559
- const filePath = join4(viewDir, name);
3770
+ const filePath = join6(viewDir, name);
3560
3771
  let isLink;
3561
3772
  try {
3562
3773
  isLink = lstatSync(filePath).isSymbolicLink();
@@ -3570,12 +3781,12 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
3570
3781
  } catch {
3571
3782
  return null;
3572
3783
  }
3573
- const resolved = isAbsolute(target) ? target : resolve4(viewDir, target);
3784
+ const resolved = isAbsolute3(target) ? target : resolve7(viewDir, target);
3574
3785
  try {
3575
3786
  if (rosterRealpaths.has(realpathSync(resolved))) return null;
3576
3787
  } catch {
3577
3788
  }
3578
- if (isAbsolute(target)) return { target, kind: "absolute" };
3789
+ if (isAbsolute3(target)) return { target, kind: "absolute" };
3579
3790
  let isDir = false;
3580
3791
  try {
3581
3792
  isDir = statSync(resolved).isDirectory();
@@ -3585,7 +3796,7 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
3585
3796
  if (!isDir) {
3586
3797
  return { target, kind: existsSync(resolved) ? "non-repo" : "broken" };
3587
3798
  }
3588
- return { target, kind: existsSync(join4(resolved, ".git")) ? "repo" : "non-repo" };
3799
+ return { target, kind: existsSync(join6(resolved, ".git")) ? "repo" : "non-repo" };
3589
3800
  }
3590
3801
  function gatherExistingViewLinks(viewDir, rosterRealpaths) {
3591
3802
  let names;
@@ -3610,7 +3821,7 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
3610
3821
  const pruned = [];
3611
3822
  const failed = [];
3612
3823
  for (const { name } of toPrune) {
3613
- const filePath = join4(viewDir, name);
3824
+ const filePath = join6(viewDir, name);
3614
3825
  const c = classifyViewLink(viewDir, name, rosterRealpaths);
3615
3826
  if (c === null || c.kind !== "repo") {
3616
3827
  failed.push({
@@ -3631,8 +3842,8 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
3631
3842
  async function doRunProjectWorkspace(options, ctx) {
3632
3843
  const cwd = ctx.cwd ?? process.cwd();
3633
3844
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project workspace");
3634
- const paths = basouPaths9(repositoryRoot);
3635
- const manifest = await readManifest5(paths);
3845
+ const paths = basouPaths10(repositoryRoot);
3846
+ const manifest = await readManifest6(paths);
3636
3847
  const viewPath = manifest.workspace.view;
3637
3848
  const roster = manifest.repos ?? [];
3638
3849
  let result;
@@ -3656,11 +3867,11 @@ async function doRunProjectWorkspace(options, ctx) {
3656
3867
  } else {
3657
3868
  const viewDir = resolveViewDir(repositoryRoot, viewPath);
3658
3869
  const facts = roster.map((entry) => gatherViewRepo(repositoryRoot, viewDir, entry));
3659
- const rosterNames = roster.map((entry) => basename3(resolve4(repositoryRoot, entry.path)));
3870
+ const rosterNames = roster.map((entry) => basename4(resolve7(repositoryRoot, entry.path)));
3660
3871
  const rosterRealpaths = /* @__PURE__ */ new Set();
3661
3872
  for (const entry of roster) {
3662
3873
  try {
3663
- rosterRealpaths.add(realpathSync(resolve4(repositoryRoot, entry.path)));
3874
+ rosterRealpaths.add(realpathSync(resolve7(repositoryRoot, entry.path)));
3664
3875
  } catch {
3665
3876
  }
3666
3877
  }
@@ -3821,10 +4032,10 @@ async function runProjectPreset(options, ctx = {}) {
3821
4032
  }
3822
4033
  }
3823
4034
  function canonicalFileFor(anchorReal, canonicalName) {
3824
- return join4(anchorReal, "agents", canonicalName, CANONICAL_FILE);
4035
+ return join6(anchorReal, "agents", canonicalName, CANONICAL_FILE);
3825
4036
  }
3826
4037
  function canonicalLabelFor(canonicalName) {
3827
- return join4("agents", canonicalName, CANONICAL_FILE);
4038
+ return join6("agents", canonicalName, CANONICAL_FILE);
3828
4039
  }
3829
4040
  async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
3830
4041
  const declared = {
@@ -3835,17 +4046,17 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
3835
4046
  };
3836
4047
  let real;
3837
4048
  try {
3838
- real = realpathSync(resolve4(repositoryRoot, entry.path));
4049
+ real = realpathSync(resolve7(repositoryRoot, entry.path));
3839
4050
  } catch {
3840
4051
  return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
3841
4052
  }
3842
4053
  if (real === anchorReal) {
3843
4054
  return { ...declared, isAnchor: true, reachable: true, canonicalPresent: false };
3844
4055
  }
3845
- if (!existsSync(join4(real, ".git"))) {
4056
+ if (!existsSync(join6(real, ".git"))) {
3846
4057
  return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
3847
4058
  }
3848
- const canonicalName = basename3(real);
4059
+ const canonicalName = basename4(real);
3849
4060
  let content;
3850
4061
  try {
3851
4062
  content = await readMarkdownFile4(canonicalFileFor(anchorReal, canonicalName));
@@ -3906,8 +4117,8 @@ function presetFailureReason(error) {
3906
4117
  async function doRunProjectPreset(options, ctx) {
3907
4118
  const cwd = ctx.cwd ?? process.cwd();
3908
4119
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project preset");
3909
- const paths = basouPaths9(repositoryRoot);
3910
- const manifest = await readManifest5(paths);
4120
+ const paths = basouPaths10(repositoryRoot);
4121
+ const manifest = await readManifest6(paths);
3911
4122
  const roster = manifest.repos ?? [];
3912
4123
  const anchorReal = realpathSync(repositoryRoot);
3913
4124
  const facts = [];
@@ -4064,33 +4275,33 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
4064
4275
  };
4065
4276
  let real;
4066
4277
  try {
4067
- real = realpathSync(resolve4(repositoryRoot, target));
4278
+ real = realpathSync(resolve7(repositoryRoot, target));
4068
4279
  } catch {
4069
4280
  return empty;
4070
4281
  }
4071
4282
  const anchorReal = realpathSync(repositoryRoot);
4072
- const canonicalName = basename3(real);
4283
+ const canonicalName = basename4(real);
4073
4284
  const instructionFiles = [];
4074
4285
  for (const name of INSTRUCTION_FILES) {
4075
4286
  try {
4076
- lstatSync(join4(real, name));
4287
+ lstatSync(join6(real, name));
4077
4288
  instructionFiles.push(name);
4078
4289
  } catch {
4079
4290
  }
4080
4291
  }
4081
4292
  let ignored;
4082
4293
  try {
4083
- ignored = new Set(readGitignoreLines(join4(real, ".gitignore")).map((l) => l.trim()));
4294
+ ignored = new Set(readGitignoreLines(join6(real, ".gitignore")).map((l) => l.trim()));
4084
4295
  } catch {
4085
4296
  ignored = /* @__PURE__ */ new Set();
4086
4297
  }
4087
4298
  const gitignorePatterns = INSTRUCTION_FILES.filter((p) => ignored.has(p) || ignored.has(`/${p}`));
4088
- const canonical2 = existsSync(join4(anchorReal, "agents", canonicalName, CANONICAL_FILE));
4299
+ const canonical2 = existsSync(join6(anchorReal, "agents", canonicalName, CANONICAL_FILE));
4089
4300
  let viewLink = false;
4090
4301
  const viewPath = manifest.workspace.view;
4091
4302
  if (viewPath !== void 0) {
4092
4303
  try {
4093
- lstatSync(join4(resolveViewDir(repositoryRoot, viewPath), canonicalName));
4304
+ lstatSync(join6(resolveViewDir(repositoryRoot, viewPath), canonicalName));
4094
4305
  viewLink = true;
4095
4306
  } catch {
4096
4307
  }
@@ -4127,12 +4338,12 @@ function buildArchivedManifest(manifest, plan, updatedAt) {
4127
4338
  async function doRunProjectArchive(target, options, ctx) {
4128
4339
  const cwd = ctx.cwd ?? process.cwd();
4129
4340
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project archive");
4130
- const paths = basouPaths9(repositoryRoot);
4131
- const manifest = await readManifest5(paths);
4341
+ const paths = basouPaths10(repositoryRoot);
4342
+ const manifest = await readManifest6(paths);
4132
4343
  const roster = manifest.repos ?? [];
4133
4344
  let targetIsAnchor = false;
4134
4345
  try {
4135
- targetIsAnchor = realpathSync(resolve4(repositoryRoot, target)) === realpathSync(repositoryRoot);
4346
+ targetIsAnchor = realpathSync(resolve7(repositoryRoot, target)) === realpathSync(repositoryRoot);
4136
4347
  } catch {
4137
4348
  targetIsAnchor = false;
4138
4349
  }
@@ -4217,7 +4428,7 @@ function renderProjectArchive(result) {
4217
4428
  if (t.instructionFiles.length > 0) items.push(`\u6307\u793A\u66F8(${t.instructionFiles.join(", ")})`);
4218
4429
  if (t.gitignorePatterns.length > 0)
4219
4430
  items.push(`.gitignore \u306E\u6307\u793A\u66F8\u30D1\u30BF\u30FC\u30F3(${t.gitignorePatterns.join(", ")})`);
4220
- if (t.canonical) items.push(`anchor \u306E canonical(agents/${basename3(result.target)}/AGENTS.md)`);
4431
+ if (t.canonical) items.push(`anchor \u306E canonical(agents/${basename4(result.target)}/AGENTS.md)`);
4221
4432
  if (!t.inspected) {
4222
4433
  lines.push("## \u624B\u52D5 teardown(repo \u304C\u30C7\u30A3\u30B9\u30AF\u4E0A\u306B\u89E3\u6C7A\u3067\u304D\u306A\u3044\u305F\u3081\u672A\u691C\u67FB)");
4223
4434
  lines.push(
@@ -4252,12 +4463,12 @@ function gatherRenameWiring(repositoryRoot, manifest, oldBasename) {
4252
4463
  } catch {
4253
4464
  return { canonicalDirOld: false, viewLinkOld: false };
4254
4465
  }
4255
- const canonicalDirOld = existsSync(join4(anchorReal, "agents", oldBasename));
4466
+ const canonicalDirOld = existsSync(join6(anchorReal, "agents", oldBasename));
4256
4467
  let viewLinkOld = false;
4257
4468
  const viewPath = manifest.workspace.view;
4258
4469
  if (viewPath !== void 0) {
4259
4470
  try {
4260
- lstatSync(join4(resolveViewDir(repositoryRoot, viewPath), oldBasename));
4471
+ lstatSync(join6(resolveViewDir(repositoryRoot, viewPath), oldBasename));
4261
4472
  viewLinkOld = true;
4262
4473
  } catch {
4263
4474
  }
@@ -4278,12 +4489,12 @@ function buildRenamedManifest(manifest, plan, updatedAt) {
4278
4489
  async function doRunProjectRename(oldPath, newPath, options, ctx) {
4279
4490
  const cwd = ctx.cwd ?? process.cwd();
4280
4491
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project rename");
4281
- const paths = basouPaths9(repositoryRoot);
4282
- const manifest = await readManifest5(paths);
4492
+ const paths = basouPaths10(repositoryRoot);
4493
+ const manifest = await readManifest6(paths);
4283
4494
  const roster = manifest.repos ?? [];
4284
4495
  let oldIsAnchor = false;
4285
4496
  try {
4286
- oldIsAnchor = realpathSync(resolve4(repositoryRoot, oldPath)) === realpathSync(repositoryRoot);
4497
+ oldIsAnchor = realpathSync(resolve7(repositoryRoot, oldPath)) === realpathSync(repositoryRoot);
4287
4498
  } catch {
4288
4499
  oldIsAnchor = false;
4289
4500
  }
@@ -4393,79 +4604,341 @@ function renderProjectRename(result) {
4393
4604
  return lines.join("\n");
4394
4605
  }
4395
4606
 
4396
- // src/commands/refresh.ts
4397
- import { assertBasouRootSafe as assertBasouRootSafe9, basouPaths as basouPaths10, findErrorCode as findErrorCode9 } from "@basou/core";
4398
- import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
4607
+ // src/commands/protocol.ts
4608
+ import { readFile as readFile3 } from "fs/promises";
4609
+ import {
4610
+ PROTOCOL_END,
4611
+ PROTOCOL_START,
4612
+ parseMarkers as parseMarkers2,
4613
+ readMarkdownFile as readMarkdownFile5,
4614
+ removeMarkerSection
4615
+ } from "@basou/core";
4399
4616
 
4400
- // src/lib/portfolio-config.ts
4401
- import { homedir as homedir4 } from "os";
4402
- import { isAbsolute as isAbsolute2, join as join5, resolve as resolve5 } from "path";
4403
- import { readYamlFile as readYamlFile3 } from "@basou/core";
4404
- var DEFAULT_PORTFOLIO_CONFIG_PATH = join5(homedir4(), ".basou", "portfolio.yaml");
4405
- function expandTilde(p) {
4406
- if (p === "~") return homedir4();
4407
- if (p.startsWith("~/")) return join5(homedir4(), p.slice(2));
4617
+ // src/lib/durable-write.ts
4618
+ import { randomUUID } from "crypto";
4619
+ import { lstat, open, rename, stat as stat3, unlink as unlink2 } from "fs/promises";
4620
+ import { basename as basename5, dirname as dirname2, join as join7 } from "path";
4621
+ async function assertNotSymlink(targetPath) {
4622
+ try {
4623
+ const st = await lstat(targetPath);
4624
+ if (st.isSymbolicLink()) {
4625
+ throw new Error(
4626
+ "Refusing to write through a symlink. Replace the symlinked target with a regular file (or remove it) and retry."
4627
+ );
4628
+ }
4629
+ } catch (error) {
4630
+ if (error instanceof Error && error.code === "ENOENT") return;
4631
+ throw error;
4632
+ }
4633
+ }
4634
+ async function writeFileDurable(targetPath, content) {
4635
+ const dir = dirname2(targetPath);
4636
+ const tmpPath = join7(dir, `.${basename5(targetPath)}.tmp.${randomUUID()}`);
4637
+ let mode = 420;
4638
+ try {
4639
+ mode = (await stat3(targetPath)).mode & 511;
4640
+ } catch (error) {
4641
+ if (!(error instanceof Error && error.code === "ENOENT")) {
4642
+ throw error;
4643
+ }
4644
+ }
4645
+ let handle;
4646
+ try {
4647
+ handle = await open(tmpPath, "wx", mode);
4648
+ await handle.writeFile(content, "utf8");
4649
+ await handle.chmod(mode);
4650
+ await handle.sync();
4651
+ await handle.close();
4652
+ handle = void 0;
4653
+ await rename(tmpPath, targetPath);
4654
+ } catch (error) {
4655
+ if (handle) await handle.close().catch(() => void 0);
4656
+ await unlink2(tmpPath).catch(() => void 0);
4657
+ throw error;
4658
+ }
4659
+ try {
4660
+ const dirHandle = await open(dir, "r");
4661
+ try {
4662
+ await dirHandle.sync();
4663
+ } finally {
4664
+ await dirHandle.close();
4665
+ }
4666
+ } catch {
4667
+ }
4668
+ }
4669
+
4670
+ // src/lib/protocols-config.ts
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");
4676
+ var ALLOWED_TOP_KEYS = /* @__PURE__ */ new Set(["version", "protocols"]);
4677
+ var ALLOWED_ENTRY_KEYS = /* @__PURE__ */ new Set(["source", "title"]);
4678
+ function expandTilde3(p) {
4679
+ if (p === "~") return homedir6();
4680
+ if (p.startsWith("~/")) return join8(homedir6(), p.slice(2));
4408
4681
  return p;
4409
4682
  }
4410
- function isRecord(value) {
4683
+ function isRecord3(value) {
4411
4684
  return typeof value === "object" && value !== null && !Array.isArray(value);
4412
4685
  }
4413
- async function loadPortfolioConfig(configPath = DEFAULT_PORTFOLIO_CONFIG_PATH) {
4686
+ async function loadProtocolsConfig(configPath = DEFAULT_PROTOCOLS_CONFIG_PATH) {
4414
4687
  let raw;
4415
4688
  try {
4416
- raw = await readYamlFile3(configPath);
4689
+ raw = await readYamlFile5(configPath);
4417
4690
  } catch (error) {
4418
4691
  if (error instanceof Error && error.message === "YAML file not found") {
4419
4692
  throw new Error(
4420
- "No portfolio config at ~/.basou/portfolio.yaml. Create one (a 'workspaces:' list of repo paths) or pass --workspace <path>."
4693
+ "No protocols config at ~/.basou/protocols.yaml. Create one (a 'protocols:' list of source markdown paths) before running 'basou protocol sync'."
4421
4694
  );
4422
4695
  }
4423
4696
  if (error instanceof Error && error.message === "Failed to parse YAML content") {
4424
- throw new Error("~/.basou/portfolio.yaml is not valid YAML.");
4697
+ throw new Error("~/.basou/protocols.yaml is not valid YAML.");
4425
4698
  }
4426
4699
  throw error;
4427
4700
  }
4428
- if (!isRecord(raw) || !Array.isArray(raw.workspaces)) {
4429
- throw new Error("~/.basou/portfolio.yaml must contain a 'workspaces:' list.");
4701
+ if (!isRecord3(raw) || !Array.isArray(raw.protocols)) {
4702
+ throw new Error("~/.basou/protocols.yaml must contain a 'protocols:' list.");
4703
+ }
4704
+ for (const key of Object.keys(raw)) {
4705
+ if (!ALLOWED_TOP_KEYS.has(key)) {
4706
+ throw new Error(
4707
+ `~/.basou/protocols.yaml has an unknown key '${key}' (allowed: version, protocols).`
4708
+ );
4709
+ }
4430
4710
  }
4431
4711
  const seen = /* @__PURE__ */ new Set();
4432
4712
  const result = [];
4433
- for (const entry of raw.workspaces) {
4434
- if (!isRecord(entry) || typeof entry.path !== "string" || entry.path.trim().length === 0) {
4435
- throw new Error("Each portfolio workspace needs a non-empty string 'path'.");
4713
+ for (const entry of raw.protocols) {
4714
+ if (!isRecord3(entry)) {
4715
+ throw new Error("Each protocol entry must be a mapping with a 'source' key.");
4436
4716
  }
4437
- if (entry.label !== void 0 && typeof entry.label !== "string") {
4438
- throw new Error("A portfolio workspace 'label' must be a string when present.");
4717
+ for (const key of Object.keys(entry)) {
4718
+ if (!ALLOWED_ENTRY_KEYS.has(key)) {
4719
+ throw new Error(`A protocol entry has an unknown key '${key}' (allowed: source, title).`);
4720
+ }
4439
4721
  }
4440
- const expanded = expandTilde(entry.path.trim());
4441
- if (!isAbsolute2(expanded)) {
4442
- throw new Error(
4443
- "Portfolio workspace paths must be absolute (or start with '~'); use --workspace for relative ad-hoc paths."
4444
- );
4722
+ if (typeof entry.source !== "string" || entry.source.trim().length === 0) {
4723
+ throw new Error("Each protocol entry needs a non-empty string 'source'.");
4724
+ }
4725
+ if (entry.title !== void 0 && (typeof entry.title !== "string" || entry.title.trim().length === 0)) {
4726
+ throw new Error("A protocol entry 'title' must be a non-empty string when present.");
4727
+ }
4728
+ const expanded = expandTilde3(entry.source.trim());
4729
+ if (!isAbsolute4(expanded)) {
4730
+ throw new Error("Protocol 'source' paths must be absolute (or start with '~').");
4731
+ }
4732
+ const abs = resolve8(expanded);
4733
+ if (seen.has(abs)) {
4734
+ throw new Error("Duplicate protocol source (each source path may appear only once).");
4445
4735
  }
4446
- const abs = resolve5(expanded);
4447
- if (seen.has(abs)) continue;
4448
4736
  seen.add(abs);
4449
- result.push(entry.label !== void 0 ? { path: abs, label: entry.label } : { path: abs });
4737
+ result.push(
4738
+ entry.title !== void 0 ? { source: abs, title: entry.title.trim() } : { source: abs }
4739
+ );
4450
4740
  }
4451
4741
  if (result.length === 0) {
4452
- throw new Error("~/.basou/portfolio.yaml has no workspaces.");
4742
+ throw new Error("~/.basou/protocols.yaml has no protocols.");
4453
4743
  }
4454
4744
  return result;
4455
4745
  }
4456
4746
 
4747
+ // src/commands/protocol.ts
4748
+ var PROTOCOL_MARKERS = { start: PROTOCOL_START, end: PROTOCOL_END };
4749
+ var MANAGED_NOTE = "<!-- Managed by basou: 'basou protocol sync' regenerates everything between the BASOU:PROTOCOLS markers from ~/.basou/protocols.yaml. Manual edits inside the block are overwritten; edit the source files instead. -->";
4750
+ function registerProtocolCommand(program2) {
4751
+ const protocol = program2.command("protocol").description("Manage the basou-managed standing-protocol block in the global CLAUDE.md");
4752
+ protocol.command("sync").description("Render declared protocols into the global CLAUDE.md (creates/updates the block)").option("--config <path>", "Path to protocols.yaml (default ~/.basou/protocols.yaml)").option("--target <path>", "Override the target file (intended for tests)").option("--dry-run", "Print what would change without writing").option("-v, --verbose", "Show error causes").action(async (opts) => {
4753
+ await runProtocolSync(opts);
4754
+ });
4755
+ protocol.command("list").description("List declared protocols and whether the block is installed").option("--config <path>", "Path to protocols.yaml (default ~/.basou/protocols.yaml)").option("--target <path>", "Override the target file (intended for tests)").option("-v, --verbose", "Show error causes").action(async (opts) => {
4756
+ await runProtocolList(opts);
4757
+ });
4758
+ protocol.command("unsync").description("Remove the basou-managed protocol block from the global CLAUDE.md").option("--target <path>", "Override the target file (intended for tests)").option("--dry-run", "Print what would change without writing").option("-v, --verbose", "Show error causes").action(async (opts) => {
4759
+ await runProtocolUnsync(opts);
4760
+ });
4761
+ }
4762
+ async function runProtocolSync(options) {
4763
+ try {
4764
+ await doRunProtocolSync(options);
4765
+ } catch (error) {
4766
+ renderCliError(error, { verbose: isVerbose(options) });
4767
+ process.exitCode = 1;
4768
+ }
4769
+ }
4770
+ async function runProtocolList(options) {
4771
+ try {
4772
+ await doRunProtocolList(options);
4773
+ } catch (error) {
4774
+ renderCliError(error, { verbose: isVerbose(options) });
4775
+ process.exitCode = 1;
4776
+ }
4777
+ }
4778
+ async function runProtocolUnsync(options) {
4779
+ try {
4780
+ await doRunProtocolUnsync(options);
4781
+ } catch (error) {
4782
+ renderCliError(error, { verbose: isVerbose(options) });
4783
+ process.exitCode = 1;
4784
+ }
4785
+ }
4786
+ async function readProtocolSources(entries) {
4787
+ const out = [];
4788
+ for (const entry of entries) {
4789
+ let content;
4790
+ try {
4791
+ content = await readFile3(entry.source, "utf8");
4792
+ } catch (error) {
4793
+ if (error instanceof Error && error.code === "ENOENT") {
4794
+ throw new Error(
4795
+ "A protocol source file does not exist. Check the 'source' paths in ~/.basou/protocols.yaml.",
4796
+ { cause: error }
4797
+ );
4798
+ }
4799
+ throw new Error("Failed to read a protocol source file.", { cause: error });
4800
+ }
4801
+ for (const line of content.split(/\r?\n/)) {
4802
+ if (line === PROTOCOL_START || line === PROTOCOL_END) {
4803
+ throw new Error(
4804
+ "A protocol source contains a BASOU:PROTOCOLS marker line, which would corrupt the managed block. Remove that line from the source."
4805
+ );
4806
+ }
4807
+ }
4808
+ out.push({ entry, content });
4809
+ }
4810
+ return out;
4811
+ }
4812
+ function buildBlock(sources) {
4813
+ const sections = sources.map(({ entry, content }) => {
4814
+ const body = content.replace(/\s+$/, "");
4815
+ return entry.title !== void 0 ? `## ${entry.title}
4816
+
4817
+ ${body}` : body;
4818
+ });
4819
+ return `${MANAGED_NOTE}
4820
+
4821
+ ${sections.join("\n\n")}
4822
+ `;
4823
+ }
4824
+ function buildTargetBody(existing, block) {
4825
+ const wrapped = `${PROTOCOL_START}
4826
+ ${block}${PROTOCOL_END}
4827
+ `;
4828
+ if (existing === null || existing === "") return wrapped;
4829
+ const section = parseMarkers2(existing, PROTOCOL_MARKERS);
4830
+ switch (section.kind) {
4831
+ case "ok":
4832
+ return `${section.before}${PROTOCOL_START}
4833
+ ${block}${PROTOCOL_END}${section.after}`;
4834
+ case "no_markers": {
4835
+ const sep = existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
4836
+ return `${existing}${sep}${wrapped}`;
4837
+ }
4838
+ default:
4839
+ throw new Error(
4840
+ "The BASOU:PROTOCOLS markers in the target are malformed (a marker is missing, duplicated, or out of order). Fix or remove them, then retry."
4841
+ );
4842
+ }
4843
+ }
4844
+ async function backupOnce(target, existing) {
4845
+ if (existing === null) return;
4846
+ const bak = `${target}.basou-bak`;
4847
+ const already = await readMarkdownFile5(bak);
4848
+ if (already !== null) return;
4849
+ await writeFileDurable(bak, existing);
4850
+ }
4851
+ async function doRunProtocolSync(options) {
4852
+ const configPath = options.config ?? DEFAULT_PROTOCOLS_CONFIG_PATH;
4853
+ const target = options.target ?? DEFAULT_TARGET_PATH;
4854
+ const entries = await loadProtocolsConfig(configPath);
4855
+ const sources = await readProtocolSources(entries);
4856
+ const block = buildBlock(sources);
4857
+ await assertNotSymlink(target);
4858
+ const existing = await readMarkdownFile5(target);
4859
+ const newBody = buildTargetBody(existing, block);
4860
+ if (newBody === existing) {
4861
+ console.log(`The basou:protocols block is already up to date (${entries.length} protocol(s)).`);
4862
+ return;
4863
+ }
4864
+ const hadBlock = existing !== null && parseMarkers2(existing, PROTOCOL_MARKERS).kind === "ok";
4865
+ if (options.dryRun === true) {
4866
+ console.log(
4867
+ `[dry-run] Would ${hadBlock ? "update" : "install"} the basou:protocols block (${entries.length} protocol(s)).`
4868
+ );
4869
+ for (const { entry } of sources) {
4870
+ console.log(` - ${entry.title ?? entry.source}`);
4871
+ }
4872
+ return;
4873
+ }
4874
+ const recheck = await readMarkdownFile5(target);
4875
+ if (recheck !== existing) {
4876
+ throw new Error(
4877
+ "The target changed during sync; aborting so a concurrent edit is not overwritten. Re-run 'basou protocol sync'."
4878
+ );
4879
+ }
4880
+ await backupOnce(target, existing);
4881
+ await writeFileDurable(target, newBody);
4882
+ console.log(
4883
+ `${hadBlock ? "Updated" : "Installed"} the basou:protocols block in the global CLAUDE.md (${entries.length} protocol(s)).`
4884
+ );
4885
+ }
4886
+ async function doRunProtocolList(options) {
4887
+ const configPath = options.config ?? DEFAULT_PROTOCOLS_CONFIG_PATH;
4888
+ const target = options.target ?? DEFAULT_TARGET_PATH;
4889
+ const entries = await loadProtocolsConfig(configPath);
4890
+ const existing = await readMarkdownFile5(target);
4891
+ const installed = existing !== null && parseMarkers2(existing, PROTOCOL_MARKERS).kind === "ok";
4892
+ console.log(`Declared protocols (${entries.length}):`);
4893
+ for (const entry of entries) {
4894
+ console.log(` - ${entry.title ?? entry.source}`);
4895
+ }
4896
+ console.log(installed ? "Block: installed in the global CLAUDE.md." : "Block: not installed.");
4897
+ }
4898
+ async function doRunProtocolUnsync(options) {
4899
+ const target = options.target ?? DEFAULT_TARGET_PATH;
4900
+ await assertNotSymlink(target);
4901
+ const existing = await readMarkdownFile5(target);
4902
+ if (existing === null) {
4903
+ console.log("No target file; nothing to remove.");
4904
+ return;
4905
+ }
4906
+ const newBody = removeMarkerSection(existing, "CLAUDE.md", PROTOCOL_MARKERS);
4907
+ if (newBody === existing) {
4908
+ console.log("No basou:protocols block found; nothing removed.");
4909
+ return;
4910
+ }
4911
+ if (options.dryRun === true) {
4912
+ console.log("[dry-run] Would remove the basou:protocols block from the global CLAUDE.md.");
4913
+ return;
4914
+ }
4915
+ const recheck = await readMarkdownFile5(target);
4916
+ if (recheck !== existing) {
4917
+ throw new Error(
4918
+ "The target changed during unsync; aborting so a concurrent edit is not overwritten. Re-run 'basou protocol unsync'."
4919
+ );
4920
+ }
4921
+ await backupOnce(target, existing);
4922
+ await writeFileDurable(target, newBody);
4923
+ console.log("Removed the basou:protocols block from the global CLAUDE.md.");
4924
+ }
4925
+
4926
+ // src/commands/refresh.ts
4927
+ import { assertBasouRootSafe as assertBasouRootSafe9, basouPaths as basouPaths11, findErrorCode as findErrorCode9 } from "@basou/core";
4928
+ import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
4929
+
4457
4930
  // src/commands/refresh-watch.ts
4458
- import { readdir as readdir2, stat as stat2 } from "fs/promises";
4459
- import { homedir as homedir5 } from "os";
4460
- import { join as join6 } from "path";
4931
+ import { readdir as readdir2, stat as stat4 } from "fs/promises";
4932
+ import { homedir as homedir7 } from "os";
4933
+ import { join as join9 } from "path";
4461
4934
  import { findErrorCode as findErrorCode8 } from "@basou/core";
4462
4935
  var DEFAULT_WATCH_INTERVAL_SEC = 30;
4463
4936
  var MIN_WATCH_INTERVAL_SEC = 5;
4464
4937
  var MAX_WATCH_INTERVAL_SEC = 86400;
4465
4938
  function watchedRoots(ctx) {
4466
4939
  return [
4467
- ctx.codexSessionsDir ?? join6(homedir5(), ".codex", "sessions"),
4468
- ctx.claudeProjectsDir ?? join6(homedir5(), ".claude", "projects")
4940
+ ctx.codexSessionsDir ?? join9(homedir7(), ".codex", "sessions"),
4941
+ ctx.claudeProjectsDir ?? join9(homedir7(), ".claude", "projects")
4469
4942
  ];
4470
4943
  }
4471
4944
  async function scanSourceLogs(roots) {
@@ -4479,12 +4952,12 @@ async function scanSourceLogs(roots) {
4479
4952
  throw new Error("Failed to read a source log directory", { cause: error });
4480
4953
  }
4481
4954
  for (const entry of entries) {
4482
- const full = join6(dir, entry.name);
4955
+ const full = join9(dir, entry.name);
4483
4956
  if (entry.isDirectory()) {
4484
4957
  await walk(full);
4485
4958
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
4486
4959
  try {
4487
- const info = await stat2(full);
4960
+ const info = await stat4(full);
4488
4961
  out.set(full, { mtimeMs: info.mtimeMs, size: info.size });
4489
4962
  } catch (error) {
4490
4963
  if (findErrorCode8(error, "ENOENT")) continue;
@@ -4586,19 +5059,19 @@ function parseInterval(value) {
4586
5059
  return seconds;
4587
5060
  }
4588
5061
  function abortableSleep(ms, signal) {
4589
- return new Promise((resolve9) => {
5062
+ return new Promise((resolve12) => {
4590
5063
  if (signal.aborted) {
4591
- resolve9();
5064
+ resolve12();
4592
5065
  return;
4593
5066
  }
4594
5067
  let timer;
4595
5068
  const onAbort = () => {
4596
5069
  clearTimeout(timer);
4597
- resolve9();
5070
+ resolve12();
4598
5071
  };
4599
5072
  timer = setTimeout(() => {
4600
5073
  signal.removeEventListener("abort", onAbort);
4601
- resolve9();
5074
+ resolve12();
4602
5075
  }, ms);
4603
5076
  signal.addEventListener("abort", onAbort, { once: true });
4604
5077
  });
@@ -4689,7 +5162,7 @@ async function doRunRefreshWatch(options, ctx) {
4689
5162
  if (options.force === true) throw new Error("--watch cannot be combined with --force.");
4690
5163
  const cwd = ctx.cwd ?? process.cwd();
4691
5164
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "refresh");
4692
- const paths = basouPaths10(repositoryRoot);
5165
+ const paths = basouPaths11(repositoryRoot);
4693
5166
  await assertWorkspaceInitialized8(paths.root);
4694
5167
  const intervalMs = (options.interval ?? DEFAULT_WATCH_INTERVAL_SEC) * 1e3;
4695
5168
  const controller = new AbortController();
@@ -4717,7 +5190,7 @@ async function doRunRefreshWatch(options, ctx) {
4717
5190
  async function computeRefresh(options, ctx) {
4718
5191
  const cwd = ctx.cwd ?? process.cwd();
4719
5192
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "refresh");
4720
- const paths = basouPaths10(repositoryRoot);
5193
+ const paths = basouPaths11(repositoryRoot);
4721
5194
  await assertWorkspaceInitialized8(paths.root);
4722
5195
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
4723
5196
  return refreshAll({
@@ -4797,10 +5270,10 @@ async function assertWorkspaceInitialized8(basouRoot) {
4797
5270
  }
4798
5271
 
4799
5272
  // src/commands/report.ts
4800
- import { isAbsolute as isAbsolute3, resolve as resolve6 } from "path";
5273
+ import { isAbsolute as isAbsolute5, resolve as resolve9 } from "path";
4801
5274
  import {
4802
5275
  assertBasouRootSafe as assertBasouRootSafe10,
4803
- basouPaths as basouPaths11,
5276
+ basouPaths as basouPaths12,
4804
5277
  findErrorCode as findErrorCode10,
4805
5278
  renderReport,
4806
5279
  resolveRepositoryRoot as resolveRepositoryRoot8,
@@ -4825,7 +5298,7 @@ async function runReportGenerate(options, ctx = {}) {
4825
5298
  async function doRunReportGenerate(options, ctx) {
4826
5299
  const cwd = ctx.cwd ?? process.cwd();
4827
5300
  const repositoryRoot = await resolveRepositoryRootForReport(cwd);
4828
- const paths = basouPaths11(repositoryRoot);
5301
+ const paths = basouPaths12(repositoryRoot);
4829
5302
  await assertWorkspaceInitialized9(paths.root);
4830
5303
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
4831
5304
  const result = await renderReport({
@@ -4837,7 +5310,7 @@ async function doRunReportGenerate(options, ctx) {
4837
5310
  onTaskSkip: (taskId, reason) => printTaskSkip(taskId, reason)
4838
5311
  });
4839
5312
  if (options.out !== void 0) {
4840
- const outPath = isAbsolute3(options.out) ? options.out : resolve6(cwd, options.out);
5313
+ const outPath = isAbsolute5(options.out) ? options.out : resolve9(cwd, options.out);
4841
5314
  await writeMarkdownFile6(outPath, result.body);
4842
5315
  const { sessions, decisions, tasks } = result.data;
4843
5316
  console.error(
@@ -4876,7 +5349,7 @@ async function assertWorkspaceInitialized9(basouRoot) {
4876
5349
 
4877
5350
  // src/commands/review-gaps.ts
4878
5351
  import {
4879
- basouPaths as basouPaths12,
5352
+ basouPaths as basouPaths13,
4880
5353
  findReviewGaps
4881
5354
  } from "@basou/core";
4882
5355
  import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
@@ -4917,7 +5390,7 @@ async function runReviewGaps(options, ctx = {}) {
4917
5390
  async function doRunReviewGaps(options, ctx) {
4918
5391
  const cwd = ctx.cwd ?? process.cwd();
4919
5392
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "review-gaps");
4920
- const paths = basouPaths12(repositoryRoot);
5393
+ const paths = basouPaths13(repositoryRoot);
4921
5394
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
4922
5395
  const summary = await findReviewGaps({
4923
5396
  paths,
@@ -5000,12 +5473,12 @@ function renderReviewGaps(summary) {
5000
5473
 
5001
5474
  // src/commands/run.ts
5002
5475
  import { mkdir as mkdir2 } from "fs/promises";
5003
- import { homedir as homedir6 } from "os";
5004
- import { join as join7 } from "path";
5476
+ import { homedir as homedir8 } from "os";
5477
+ import { join as join10 } from "path";
5005
5478
  import {
5006
5479
  acquireLock as acquireLock5,
5007
5480
  assertBasouRootSafe as assertBasouRootSafe11,
5008
- basouPaths as basouPaths13,
5481
+ basouPaths as basouPaths14,
5009
5482
  ChildProcessRunner as ChildProcessRunner2,
5010
5483
  claudeCodeAdapterMetadata,
5011
5484
  appendChainedEvent as coreAppendChainedEvent2,
@@ -5014,8 +5487,8 @@ import {
5014
5487
  getSnapshot as getSnapshot2,
5015
5488
  overwriteYamlFile as overwriteYamlFile2,
5016
5489
  prefixedUlid as prefixedUlid4,
5017
- readManifest as readManifest6,
5018
- readYamlFile as readYamlFile4,
5490
+ readManifest as readManifest7,
5491
+ readYamlFile as readYamlFile6,
5019
5492
  resolveClaudeCodeCommand,
5020
5493
  resolveRepositoryRoot as resolveRepositoryRoot9,
5021
5494
  SessionSchema as SessionSchema2,
@@ -5050,17 +5523,17 @@ async function runClaudeCode(args, options, ctx = {}) {
5050
5523
  const { command } = await resolveCommand();
5051
5524
  const cwd = options.cwd ?? process.cwd();
5052
5525
  const repoRoot = await resolveRepositoryRootForRun(cwd);
5053
- const paths = basouPaths13(repoRoot);
5526
+ const paths = basouPaths14(repoRoot);
5054
5527
  await assertBasouRootSafe11(paths.root);
5055
- const manifest = await readManifest6(paths);
5528
+ const manifest = await readManifest7(paths);
5056
5529
  const sessionId = prefixedUlid4("ses");
5057
- const sessionDir = join7(paths.sessions, sessionId);
5530
+ const sessionDir = join10(paths.sessions, sessionId);
5058
5531
  await mkdir2(sessionDir, { recursive: true });
5059
5532
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
5060
5533
  await coreAppendChainedEvent2(paths, sessionId, event);
5061
5534
  });
5062
5535
  const startedAt = now().toISOString();
5063
- const sessionYamlPath = join7(sessionDir, "session.yaml");
5536
+ const sessionYamlPath = join10(sessionDir, "session.yaml");
5064
5537
  const session = buildInitialSession2({
5065
5538
  id: sessionId,
5066
5539
  command,
@@ -5186,7 +5659,7 @@ async function runClaudeCode(args, options, ctx = {}) {
5186
5659
  const rawRelated = computeRelatedFiles(preSnapshot, postSnapshot, diff);
5187
5660
  const relatedFiles = sanitizeRelatedFiles(rawRelated, {
5188
5661
  workingDirectory: repoRoot,
5189
- homedir: homedir6()
5662
+ homedir: homedir8()
5190
5663
  }).sanitized;
5191
5664
  const finalStatus = decideFinalStatus2(result, signalReceived);
5192
5665
  await appendEvent(sessionDir, {
@@ -5330,7 +5803,7 @@ function buildInitialSession2(input) {
5330
5803
  source: { ...claudeCodeAdapterMetadata },
5331
5804
  started_at: input.startedAt,
5332
5805
  status: "initialized",
5333
- working_directory: sanitizeWorkingDirectory2(input.cwd, { homedir: homedir6() }),
5806
+ working_directory: sanitizeWorkingDirectory2(input.cwd, { homedir: homedir8() }),
5334
5807
  invocation: {
5335
5808
  command: input.command,
5336
5809
  args: [...input.args],
@@ -5342,7 +5815,7 @@ function buildInitialSession2(input) {
5342
5815
  };
5343
5816
  }
5344
5817
  async function mutateSessionYaml2(filePath, mutator) {
5345
- const raw = await readYamlFile4(filePath);
5818
+ const raw = await readYamlFile6(filePath);
5346
5819
  const parsed = SessionSchema2.parse(raw);
5347
5820
  mutator(parsed);
5348
5821
  const validated = SessionSchema2.parse(parsed);
@@ -5402,20 +5875,20 @@ async function resolveRepositoryRootForRun(cwd) {
5402
5875
  }
5403
5876
 
5404
5877
  // src/commands/session.ts
5405
- import { readFile as readFile3 } from "fs/promises";
5406
- import { basename as basename4, isAbsolute as isAbsolute4, join as join8, relative as relative3 } from "path";
5878
+ import { readFile as readFile4 } from "fs/promises";
5879
+ import { basename as basename6, isAbsolute as isAbsolute6, join as join11, relative as relative3 } from "path";
5407
5880
  import {
5408
5881
  acquireLock as acquireLock6,
5409
5882
  appendEventToExistingSession as appendEventToExistingSession3,
5410
5883
  assertBasouRootSafe as assertBasouRootSafe12,
5411
- basouPaths as basouPaths14,
5884
+ basouPaths as basouPaths15,
5412
5885
  enumerateSessionDirs as enumerateSessionDirs2,
5413
5886
  findErrorCode as findErrorCode11,
5414
5887
  importSessionFromJson as importSessionFromJson2,
5415
5888
  loadSessionEntries,
5416
5889
  readAllEvents,
5417
- readManifest as readManifest7,
5418
- readYamlFile as readYamlFile5,
5890
+ readManifest as readManifest8,
5891
+ readYamlFile as readYamlFile7,
5419
5892
  rechainSessionInPlace,
5420
5893
  resolveSessionId as resolveSessionId3,
5421
5894
  resolveTaskId,
@@ -5473,7 +5946,7 @@ async function runSessionList(options, ctx = {}) {
5473
5946
  async function doRunSessionList(options, ctx) {
5474
5947
  const cwd = ctx.cwd ?? process.cwd();
5475
5948
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "list");
5476
- const paths = basouPaths14(repositoryRoot);
5949
+ const paths = basouPaths15(repositoryRoot);
5477
5950
  await assertWorkspaceInitialized10(paths.root);
5478
5951
  const now = /* @__PURE__ */ new Date();
5479
5952
  const records = (await loadSessionEntries(paths, {
@@ -5525,14 +5998,14 @@ async function runSessionShow(idInput, options, ctx = {}) {
5525
5998
  async function doRunSessionShow(idInput, options, ctx) {
5526
5999
  const cwd = ctx.cwd ?? process.cwd();
5527
6000
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "show");
5528
- const paths = basouPaths14(repositoryRoot);
6001
+ const paths = basouPaths15(repositoryRoot);
5529
6002
  await assertWorkspaceInitialized10(paths.root);
5530
6003
  const sessionId = await resolveSessionId3(paths, idInput);
5531
- const sessionDir = join8(paths.sessions, sessionId);
5532
- const sessionYamlPath = join8(sessionDir, "session.yaml");
6004
+ const sessionDir = join11(paths.sessions, sessionId);
6005
+ const sessionYamlPath = join11(sessionDir, "session.yaml");
5533
6006
  let session;
5534
6007
  try {
5535
- const raw = await readYamlFile5(sessionYamlPath);
6008
+ const raw = await readYamlFile7(sessionYamlPath);
5536
6009
  session = SessionSchema3.parse(raw);
5537
6010
  } catch (error) {
5538
6011
  if (findErrorCode11(error, "ENOENT")) {
@@ -5650,7 +6123,7 @@ function formatSessionWork(session, events, now) {
5650
6123
  }
5651
6124
  function formatWorkingDir(workingDir, repositoryRoot, options) {
5652
6125
  if (options.fullPath === true) return workingDir;
5653
- if (!isAbsolute4(workingDir)) {
6126
+ if (!isAbsolute6(workingDir)) {
5654
6127
  if (workingDir === ".") return "<repository_root>";
5655
6128
  return workingDir;
5656
6129
  }
@@ -5813,9 +6286,9 @@ async function runSessionImport(options, ctx = {}) {
5813
6286
  async function doRunSessionImport(options, ctx) {
5814
6287
  const cwd = ctx.cwd ?? process.cwd();
5815
6288
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "import");
5816
- const paths = basouPaths14(repositoryRoot);
6289
+ const paths = basouPaths15(repositoryRoot);
5817
6290
  await assertWorkspaceInitialized10(paths.root);
5818
- const manifest = await readManifest7(paths);
6291
+ const manifest = await readManifest8(paths);
5819
6292
  const rawBody = await readInputFile(options.from);
5820
6293
  const json = parseJsonStrict(rawBody);
5821
6294
  const parsed = SessionImportPayloadSchema2.safeParse(json);
@@ -5842,7 +6315,7 @@ async function doRunSessionImport(options, ctx) {
5842
6315
  }
5843
6316
  async function readInputFile(path) {
5844
6317
  try {
5845
- return await readFile3(path, "utf8");
6318
+ return await readFile4(path, "utf8");
5846
6319
  } catch (error) {
5847
6320
  if (findErrorCode11(error, "ENOENT")) {
5848
6321
  throw new Error("Import source not found", { cause: error });
@@ -5900,7 +6373,7 @@ function printSessionImportResult(options, result) {
5900
6373
  return;
5901
6374
  }
5902
6375
  console.log(
5903
- `Imported session ${sid} (${result.eventCount} events) from ${basename4(options.from)}`
6376
+ `Imported session ${sid} (${result.eventCount} events) from ${basename6(options.from)}`
5904
6377
  );
5905
6378
  }
5906
6379
  var NOTE_BODY_PREVIEW_LIMIT = 80;
@@ -5927,7 +6400,7 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
5927
6400
  }
5928
6401
  const cwd = ctx.cwd ?? process.cwd();
5929
6402
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "note");
5930
- const paths = basouPaths14(repositoryRoot);
6403
+ const paths = basouPaths15(repositoryRoot);
5931
6404
  await assertWorkspaceInitialized10(paths.root);
5932
6405
  const sessionId = await resolveSessionId3(paths, sessionIdInput);
5933
6406
  const body = hasBody ? options.body : await readNoteFile(options.fromFile);
@@ -5959,7 +6432,7 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
5959
6432
  }
5960
6433
  async function readNoteFile(path) {
5961
6434
  try {
5962
- return await readFile3(path, "utf8");
6435
+ return await readFile4(path, "utf8");
5963
6436
  } catch (error) {
5964
6437
  if (findErrorCode11(error, "ENOENT")) {
5965
6438
  throw new Error("Note source not found", { cause: error });
@@ -6009,7 +6482,7 @@ async function doRunSessionRechain(options, ctx) {
6009
6482
  }
6010
6483
  const cwd = ctx.cwd ?? process.cwd();
6011
6484
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "rechain");
6012
- const paths = basouPaths14(repositoryRoot);
6485
+ const paths = basouPaths15(repositoryRoot);
6013
6486
  await assertWorkspaceInitialized10(paths.root);
6014
6487
  const sessionIds = options.session !== void 0 ? [await resolveSessionId3(paths, options.session)] : await enumerateSessionDirs2(paths);
6015
6488
  const dryRun = options.dryRun === true;
@@ -6064,7 +6537,7 @@ function renderRechainRow(row, dryRun) {
6064
6537
  // src/commands/stats.ts
6065
6538
  import {
6066
6539
  assertBasouRootSafe as assertBasouRootSafe13,
6067
- basouPaths as basouPaths15,
6540
+ basouPaths as basouPaths16,
6068
6541
  computeWorkStats,
6069
6542
  findErrorCode as findErrorCode12,
6070
6543
  resolveRepositoryRoot as resolveRepositoryRoot10
@@ -6085,7 +6558,7 @@ async function runStats(options, ctx = {}) {
6085
6558
  async function doRunStats(options, ctx) {
6086
6559
  const cwd = ctx.cwd ?? process.cwd();
6087
6560
  const repositoryRoot = await resolveRepositoryRootForStats(cwd);
6088
- const paths = basouPaths15(repositoryRoot);
6561
+ const paths = basouPaths16(repositoryRoot);
6089
6562
  await assertWorkspaceInitialized11(paths.root);
6090
6563
  const now = ctx.nowProvider?.() ?? /* @__PURE__ */ new Date();
6091
6564
  const result = await computeWorkStats({
@@ -6194,10 +6667,10 @@ async function assertWorkspaceInitialized11(basouRoot) {
6194
6667
  // src/commands/status.ts
6195
6668
  import {
6196
6669
  assertBasouRootSafe as assertBasouRootSafe14,
6197
- basouPaths as basouPaths16,
6670
+ basouPaths as basouPaths17,
6198
6671
  buildStatusSnapshot,
6199
6672
  findErrorCode as findErrorCode13,
6200
- readManifest as readManifest8,
6673
+ readManifest as readManifest9,
6201
6674
  resolveRepositoryRoot as resolveRepositoryRoot11,
6202
6675
  writeStatus
6203
6676
  } from "@basou/core";
@@ -6217,7 +6690,7 @@ async function runStatus(options, ctx = {}) {
6217
6690
  async function doRunStatus(options, ctx) {
6218
6691
  const cwd = ctx.cwd ?? process.cwd();
6219
6692
  const repositoryRoot = await resolveRepositoryRootForStatus(cwd);
6220
- const paths = basouPaths16(repositoryRoot);
6693
+ const paths = basouPaths17(repositoryRoot);
6221
6694
  try {
6222
6695
  await assertBasouRootSafe14(paths.root);
6223
6696
  } catch (error) {
@@ -6228,7 +6701,7 @@ async function doRunStatus(options, ctx) {
6228
6701
  }
6229
6702
  let manifest;
6230
6703
  try {
6231
- manifest = await readManifest8(paths);
6704
+ manifest = await readManifest9(paths);
6232
6705
  } catch (error) {
6233
6706
  if (findErrorCode13(error, "ENOENT")) {
6234
6707
  throw new Error("Workspace not initialized. Run 'basou init' first.");
@@ -6266,12 +6739,12 @@ async function resolveRepositoryRootForStatus(cwd) {
6266
6739
  }
6267
6740
 
6268
6741
  // src/commands/task.ts
6269
- import { readFile as readFile4 } from "fs/promises";
6270
- import { join as join9 } from "path";
6742
+ import { readFile as readFile5 } from "fs/promises";
6743
+ import { join as join12 } from "path";
6271
6744
  import {
6272
6745
  archiveTask,
6273
6746
  assertBasouRootSafe as assertBasouRootSafe15,
6274
- basouPaths as basouPaths17,
6747
+ basouPaths as basouPaths18,
6275
6748
  createTaskWithEvent,
6276
6749
  deleteTask,
6277
6750
  editTask,
@@ -6280,7 +6753,7 @@ import {
6280
6753
  loadSessionEntries as loadSessionEntries2,
6281
6754
  loadTaskEntries,
6282
6755
  prefixedUlid as prefixedUlid5,
6283
- readManifest as readManifest9,
6756
+ readManifest as readManifest10,
6284
6757
  readTaskFile,
6285
6758
  readTaskFileWithArchiveFallback,
6286
6759
  reconcileAllTasks,
@@ -6373,7 +6846,7 @@ async function doRunTaskNew(options, ctx) {
6373
6846
  }
6374
6847
  const cwd = ctx.cwd ?? process.cwd();
6375
6848
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "new");
6376
- const paths = basouPaths17(repositoryRoot);
6849
+ const paths = basouPaths18(repositoryRoot);
6377
6850
  await assertWorkspaceInitialized12(paths.root);
6378
6851
  const description = options.description !== void 0 ? options.description : options.fromFile !== void 0 ? await readDescriptionFile(options.fromFile) : "";
6379
6852
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
@@ -6408,7 +6881,7 @@ async function doRunTaskNew(options, ctx) {
6408
6881
  });
6409
6882
  return;
6410
6883
  }
6411
- const manifest = await readManifest9(paths);
6884
+ const manifest = await readManifest10(paths);
6412
6885
  const result = await createTaskWithEvent({
6413
6886
  mode: "ad-hoc",
6414
6887
  paths,
@@ -6482,7 +6955,7 @@ async function runTaskList(options, ctx = {}) {
6482
6955
  async function doRunTaskList(options, ctx) {
6483
6956
  const cwd = ctx.cwd ?? process.cwd();
6484
6957
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "list");
6485
- const paths = basouPaths17(repositoryRoot);
6958
+ const paths = basouPaths18(repositoryRoot);
6486
6959
  await assertWorkspaceInitialized12(paths.root);
6487
6960
  const entries = await loadTaskEntries(paths, {
6488
6961
  onSkip: (id, reason) => printTaskSkip(id, reason)
@@ -6586,7 +7059,7 @@ async function runTaskShow(idInput, options, ctx = {}) {
6586
7059
  async function doRunTaskShow(idInput, options, ctx) {
6587
7060
  const cwd = ctx.cwd ?? process.cwd();
6588
7061
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "show");
6589
- const paths = basouPaths17(repositoryRoot);
7062
+ const paths = basouPaths18(repositoryRoot);
6590
7063
  await assertWorkspaceInitialized12(paths.root);
6591
7064
  const taskId = await resolveTaskId2(paths, idInput, { includeArchived: true });
6592
7065
  const { doc, archived } = await readTaskFileWithArchiveFallback(paths, taskId);
@@ -6594,7 +7067,7 @@ async function doRunTaskShow(idInput, options, ctx) {
6594
7067
  const events = [];
6595
7068
  const linkedSessionIds = new Set(doc.task.task.linked_sessions);
6596
7069
  for (const s of sessions) {
6597
- const sessionDir = join9(paths.sessions, s.sessionId);
7070
+ const sessionDir = join12(paths.sessions, s.sessionId);
6598
7071
  try {
6599
7072
  for await (const ev of replayEvents2(sessionDir, {
6600
7073
  onWarning: (w) => printReplayWarning(w, s.sessionId)
@@ -6730,7 +7203,7 @@ async function doRunTaskStatus(taskIdInput, newStatusInput, options, ctx) {
6730
7203
  const newStatus = parseTaskStatusPositional(newStatusInput);
6731
7204
  const cwd = ctx.cwd ?? process.cwd();
6732
7205
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "status");
6733
- const paths = basouPaths17(repositoryRoot);
7206
+ const paths = basouPaths18(repositoryRoot);
6734
7207
  await assertWorkspaceInitialized12(paths.root);
6735
7208
  const taskId = await resolveTaskId2(paths, taskIdInput);
6736
7209
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
@@ -6756,7 +7229,7 @@ async function doRunTaskStatus(taskIdInput, newStatusInput, options, ctx) {
6756
7229
  });
6757
7230
  return;
6758
7231
  }
6759
- const manifest = await readManifest9(paths);
7232
+ const manifest = await readManifest10(paths);
6760
7233
  const result = await updateTaskStatusWithEvent({
6761
7234
  mode: "ad-hoc",
6762
7235
  paths,
@@ -6807,9 +7280,9 @@ async function runTaskReconcile(options, ctx = {}) {
6807
7280
  async function doRunTaskReconcile(options, ctx) {
6808
7281
  const cwd = ctx.cwd ?? process.cwd();
6809
7282
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "reconcile");
6810
- const paths = basouPaths17(repositoryRoot);
7283
+ const paths = basouPaths18(repositoryRoot);
6811
7284
  await assertWorkspaceInitialized12(paths.root);
6812
- const manifest = await readManifest9(paths);
7285
+ const manifest = await readManifest10(paths);
6813
7286
  const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
6814
7287
  const write = options.write === true;
6815
7288
  const verbose = isVerbose(options);
@@ -6987,9 +7460,9 @@ async function doRunTaskRefreshLinkage(taskIdInput, options, ctx) {
6987
7460
  }
6988
7461
  const cwd = ctx.cwd ?? process.cwd();
6989
7462
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "refresh-linkage");
6990
- const paths = basouPaths17(repositoryRoot);
7463
+ const paths = basouPaths18(repositoryRoot);
6991
7464
  await assertWorkspaceInitialized12(paths.root);
6992
- const manifest = await readManifest9(paths);
7465
+ const manifest = await readManifest10(paths);
6993
7466
  const taskId = await resolveTaskId2(paths, taskIdInput);
6994
7467
  const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
6995
7468
  const write = options.write === true;
@@ -7067,9 +7540,9 @@ async function doRunTaskEdit(taskIdInput, options, ctx) {
7067
7540
  }
7068
7541
  const cwd = ctx.cwd ?? process.cwd();
7069
7542
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "edit");
7070
- const paths = basouPaths17(repositoryRoot);
7543
+ const paths = basouPaths18(repositoryRoot);
7071
7544
  await assertWorkspaceInitialized12(paths.root);
7072
- const manifest = await readManifest9(paths);
7545
+ const manifest = await readManifest10(paths);
7073
7546
  const taskId = await resolveTaskId2(paths, taskIdInput);
7074
7547
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
7075
7548
  const occurredAt = now.toISOString();
@@ -7123,9 +7596,9 @@ async function doRunTaskDelete(taskIdInput, options, ctx) {
7123
7596
  }
7124
7597
  const cwd = ctx.cwd ?? process.cwd();
7125
7598
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "delete");
7126
- const paths = basouPaths17(repositoryRoot);
7599
+ const paths = basouPaths18(repositoryRoot);
7127
7600
  await assertWorkspaceInitialized12(paths.root);
7128
- const manifest = await readManifest9(paths);
7601
+ const manifest = await readManifest10(paths);
7129
7602
  const taskId = await resolveTaskId2(paths, taskIdInput);
7130
7603
  if (options.yes !== true) {
7131
7604
  await confirmDestructiveAction("delete", taskId);
@@ -7168,9 +7641,9 @@ async function doRunTaskArchive(taskIdInput, options, ctx) {
7168
7641
  }
7169
7642
  const cwd = ctx.cwd ?? process.cwd();
7170
7643
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "archive");
7171
- const paths = basouPaths17(repositoryRoot);
7644
+ const paths = basouPaths18(repositoryRoot);
7172
7645
  await assertWorkspaceInitialized12(paths.root);
7173
- const manifest = await readManifest9(paths);
7646
+ const manifest = await readManifest10(paths);
7174
7647
  const taskId = await resolveTaskId2(paths, taskIdInput);
7175
7648
  if (options.yes !== true) {
7176
7649
  await confirmDestructiveAction("archive", taskId);
@@ -7282,7 +7755,7 @@ function parsePositiveInt2(raw) {
7282
7755
  }
7283
7756
  async function readDescriptionFile(path) {
7284
7757
  try {
7285
- return await readFile4(path, "utf8");
7758
+ return await readFile5(path, "utf8");
7286
7759
  } catch (error) {
7287
7760
  if (findErrorCode14(error, "ENOENT")) {
7288
7761
  throw new Error("Description source not found", { cause: error });
@@ -7399,7 +7872,7 @@ function maxLen3(values, floor) {
7399
7872
  // src/commands/verify.ts
7400
7873
  import {
7401
7874
  assertBasouRootSafe as assertBasouRootSafe16,
7402
- basouPaths as basouPaths18,
7875
+ basouPaths as basouPaths19,
7403
7876
  enumerateSessionDirs as enumerateSessionDirs3,
7404
7877
  findErrorCode as findErrorCode15,
7405
7878
  resolveRepositoryRoot as resolveRepositoryRoot13,
@@ -7425,7 +7898,7 @@ async function doRunVerify(options, ctx) {
7425
7898
  }
7426
7899
  const cwd = ctx.cwd ?? process.cwd();
7427
7900
  const repositoryRoot = await resolveRepositoryRootForVerify(cwd);
7428
- const paths = basouPaths18(repositoryRoot);
7901
+ const paths = basouPaths19(repositoryRoot);
7429
7902
  await assertWorkspaceInitialized13(paths.root);
7430
7903
  const sessionIds = options.session !== void 0 ? [await resolveSessionId5(paths, options.session)] : await enumerateSessionDirs3(paths);
7431
7904
  const rows = [];
@@ -7497,36 +7970,36 @@ async function assertWorkspaceInitialized13(basouRoot) {
7497
7970
  // src/commands/view.ts
7498
7971
  import { spawn } from "child_process";
7499
7972
  import { createHash } from "crypto";
7500
- import { basename as basename5, resolve as resolve8 } from "path";
7973
+ import { basename as basename7, resolve as resolve11 } from "path";
7501
7974
  import {
7502
7975
  assertBasouRootSafe as assertBasouRootSafe17,
7503
- basouPaths as basouPaths19,
7976
+ basouPaths as basouPaths20,
7504
7977
  findErrorCode as findErrorCode17,
7505
- readManifest as readManifest12,
7978
+ readManifest as readManifest13,
7506
7979
  resolveRepositoryRoot as resolveRepositoryRoot14
7507
7980
  } from "@basou/core";
7508
7981
  import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
7509
7982
 
7510
7983
  // src/lib/portfolio-safety.ts
7511
7984
  import { execFile } from "child_process";
7512
- import { lstat, realpath } from "fs/promises";
7513
- import { isAbsolute as isAbsolute5, join as join10, relative as relative4, resolve as resolve7 } from "path";
7985
+ import { lstat as lstat2, realpath as realpath2 } from "fs/promises";
7986
+ import { isAbsolute as isAbsolute7, join as join13, relative as relative4, resolve as resolve10 } from "path";
7514
7987
  import { promisify } from "util";
7515
- import { readManifest as readManifest10 } from "@basou/core";
7988
+ import { readManifest as readManifest11 } from "@basou/core";
7516
7989
  var execFileAsync = promisify(execFile);
7517
7990
  function errorCode(error) {
7518
7991
  return error instanceof Error ? error.code : void 0;
7519
7992
  }
7520
7993
  async function canonical(p) {
7521
7994
  try {
7522
- return await realpath(p);
7995
+ return await realpath2(p);
7523
7996
  } catch {
7524
- return resolve7(p);
7997
+ return resolve10(p);
7525
7998
  }
7526
7999
  }
7527
8000
  function isInside(child, parent) {
7528
8001
  const rel = relative4(parent, child);
7529
- return rel === "" || !rel.startsWith("..") && !isAbsolute5(rel);
8002
+ return rel === "" || !rel.startsWith("..") && !isAbsolute7(rel);
7530
8003
  }
7531
8004
  function isBasouPath(p) {
7532
8005
  return p === ".basou" || p.startsWith(".basou/") || p.includes("/.basou/") || p.endsWith("/.basou");
@@ -7534,7 +8007,7 @@ function isBasouPath(p) {
7534
8007
  async function inspectRepo(repoPath) {
7535
8008
  let hasEntry = false;
7536
8009
  try {
7537
- await lstat(join10(repoPath, ".basou"));
8010
+ await lstat2(join13(repoPath, ".basou"));
7538
8011
  hasEntry = true;
7539
8012
  } catch (error) {
7540
8013
  if (errorCode(error) !== "ENOENT") {
@@ -7565,7 +8038,7 @@ async function checkPortfolioSafety(workspaces) {
7565
8038
  const wsReal = await canonical(ws.repoRoot);
7566
8039
  let sourceRoots = [];
7567
8040
  try {
7568
- const manifest = await readManifest10(ws.paths);
8041
+ const manifest = await readManifest11(ws.paths);
7569
8042
  sourceRoots = manifest.import?.source_roots ?? [];
7570
8043
  } catch (error) {
7571
8044
  if (error instanceof Error && error.message === "YAML file not found") {
@@ -7583,7 +8056,7 @@ async function checkPortfolioSafety(workspaces) {
7583
8056
  }
7584
8057
  const monitored = /* @__PURE__ */ new Map();
7585
8058
  for (const root of sourceRoots) {
7586
- const display = resolve7(ws.repoRoot, root);
8059
+ const display = resolve10(ws.repoRoot, root);
7587
8060
  const real = await canonical(display);
7588
8061
  if (real !== wsReal) monitored.set(real, display);
7589
8062
  }
@@ -7635,7 +8108,7 @@ function formatSafetyReport(result) {
7635
8108
 
7636
8109
  // src/lib/view-server.ts
7637
8110
  import { createServer } from "http";
7638
- import { join as join11 } from "path";
8111
+ import { join as join14 } from "path";
7639
8112
  import {
7640
8113
  computeWorkStats as computeWorkStats2,
7641
8114
  enumerateApprovals as enumerateApprovals2,
@@ -7645,8 +8118,8 @@ import {
7645
8118
  loadSessionEntries as loadSessionEntries3,
7646
8119
  loadTaskEntries as loadTaskEntries2,
7647
8120
  readAllEvents as readAllEvents2,
7648
- readManifest as readManifest11,
7649
- readMarkdownFile as readMarkdownFile5,
8121
+ readManifest as readManifest12,
8122
+ readMarkdownFile as readMarkdownFile6,
7650
8123
  readSessionYaml as readSessionYaml3,
7651
8124
  readTaskFile as readTaskFile2,
7652
8125
  renderDecisions as renderDecisions3,
@@ -8292,7 +8765,7 @@ function startViewServer(opts) {
8292
8765
  };
8293
8766
  let boundPort = port;
8294
8767
  const getPort = () => boundPort;
8295
- return new Promise((resolve9, reject) => {
8768
+ return new Promise((resolve12, reject) => {
8296
8769
  const server = createServer((req, res) => {
8297
8770
  handleRequest(req, res, deps, getPort, runExclusive).catch((error) => {
8298
8771
  sendError(res, error instanceof HttpError ? error.status : 500, pathlessMessage(error));
@@ -8303,7 +8776,7 @@ function startViewServer(opts) {
8303
8776
  const address = server.address();
8304
8777
  boundPort = isAddressInfo(address) ? address.port : port;
8305
8778
  server.off("error", reject);
8306
- resolve9({
8779
+ resolve12({
8307
8780
  url: `http://${host}:${boundPort}`,
8308
8781
  port: boundPort,
8309
8782
  close: () => closeServer(server)
@@ -8315,8 +8788,8 @@ function isAddressInfo(value) {
8315
8788
  return value !== null && typeof value === "object";
8316
8789
  }
8317
8790
  function closeServer(server) {
8318
- return new Promise((resolve9) => {
8319
- server.close(() => resolve9());
8791
+ return new Promise((resolve12) => {
8792
+ server.close(() => resolve12());
8320
8793
  server.closeAllConnections();
8321
8794
  });
8322
8795
  }
@@ -8533,7 +9006,7 @@ async function captureStaleness(ws, nowIso) {
8533
9006
  async function overview(ws, nowProvider) {
8534
9007
  let manifest;
8535
9008
  try {
8536
- manifest = await readManifest11(ws.paths);
9009
+ manifest = await readManifest12(ws.paths);
8537
9010
  } catch (error) {
8538
9011
  if (findErrorCode16(error, "ENOENT")) {
8539
9012
  return { initialized: false, repoRoot: ws.repoRoot };
@@ -8590,7 +9063,7 @@ async function sessionDetail(ws, sessionId) {
8590
9063
  throw error;
8591
9064
  }
8592
9065
  try {
8593
- const events = await readAllEvents2(join11(ws.paths.sessions, sessionId));
9066
+ const events = await readAllEvents2(join14(ws.paths.sessions, sessionId));
8594
9067
  return { session, events };
8595
9068
  } catch {
8596
9069
  return { session, events: [], degraded: true };
@@ -8612,7 +9085,7 @@ async function taskDetail(ws, taskId) {
8612
9085
  }
8613
9086
  }
8614
9087
  async function decisionsView(ws, nowProvider) {
8615
- const fromDisk = await readMarkdownFile5(ws.paths.files.decisions);
9088
+ const fromDisk = await readMarkdownFile6(ws.paths.files.decisions);
8616
9089
  if (fromDisk !== null) {
8617
9090
  return { body: fromDisk, fromDisk: true };
8618
9091
  }
@@ -8635,7 +9108,7 @@ async function approvalsView(ws, nowProvider) {
8635
9108
  return { pending: await toViews(ids.pending), resolved: await toViews(ids.resolved) };
8636
9109
  }
8637
9110
  async function handoffView(ws, nowProvider) {
8638
- const fromDisk = await readMarkdownFile5(ws.paths.files.handoff);
9111
+ const fromDisk = await readMarkdownFile6(ws.paths.files.handoff);
8639
9112
  if (fromDisk !== null) {
8640
9113
  return { body: fromDisk, fromDisk: true };
8641
9114
  }
@@ -8804,18 +9277,18 @@ async function doRunView(options, ctx) {
8804
9277
  }
8805
9278
  async function buildSingleDeps(ctx, cwd) {
8806
9279
  const repositoryRoot = await resolveRepositoryRootForView(cwd);
8807
- const paths = basouPaths19(repositoryRoot);
9280
+ const paths = basouPaths20(repositoryRoot);
8808
9281
  await assertWorkspaceInitialized14(paths.root);
8809
9282
  const entry = await buildWorkspaceEntry(repositoryRoot, ctx);
8810
9283
  return { workspaces: [entry], mode: "single", nowProvider: nowProviderOf(ctx) };
8811
9284
  }
8812
9285
  async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
8813
- const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve8(cwd, p) })) : await loadPortfolioConfig(ctx.portfolioConfigPath);
9286
+ const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve11(cwd, p) })) : await loadPortfolioConfig(ctx.portfolioConfigPath);
8814
9287
  const entries = [];
8815
9288
  const seenPath = /* @__PURE__ */ new Set();
8816
9289
  const seenKey = /* @__PURE__ */ new Set();
8817
9290
  for (const spec of specs) {
8818
- const repoRoot = resolve8(spec.path);
9291
+ const repoRoot = resolve11(spec.path);
8819
9292
  if (seenPath.has(repoRoot)) continue;
8820
9293
  seenPath.add(repoRoot);
8821
9294
  const entry = await buildWorkspaceEntry(repoRoot, ctx, spec.label);
@@ -8828,14 +9301,14 @@ async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
8828
9301
  return { workspaces: entries, mode: "portfolio", nowProvider: nowProviderOf(ctx) };
8829
9302
  }
8830
9303
  async function buildWorkspaceEntry(repoRoot, ctx, labelOverride) {
8831
- const paths = basouPaths19(repoRoot);
9304
+ const paths = basouPaths20(repoRoot);
8832
9305
  const importCtx = {
8833
9306
  cwd: repoRoot,
8834
9307
  ...ctx.claudeProjectsDir !== void 0 ? { claudeProjectsDir: ctx.claudeProjectsDir } : {},
8835
9308
  ...ctx.codexSessionsDir !== void 0 ? { codexSessionsDir: ctx.codexSessionsDir } : {}
8836
9309
  };
8837
9310
  try {
8838
- const manifest = await readManifest12(paths);
9311
+ const manifest = await readManifest13(paths);
8839
9312
  return {
8840
9313
  key: manifest.workspace.id,
8841
9314
  label: labelOverride ?? manifest.workspace.name,
@@ -8848,7 +9321,7 @@ async function buildWorkspaceEntry(repoRoot, ctx, labelOverride) {
8848
9321
  const notFound = error instanceof Error && error.message === "YAML file not found";
8849
9322
  return {
8850
9323
  key: `ws-${createHash("sha1").update(repoRoot).digest("hex").slice(0, 12)}`,
8851
- label: labelOverride ?? basename5(repoRoot),
9324
+ label: labelOverride ?? basename7(repoRoot),
8852
9325
  paths,
8853
9326
  repoRoot,
8854
9327
  importCtx,
@@ -8887,7 +9360,7 @@ function openInBrowser(url, override) {
8887
9360
  }
8888
9361
  }
8889
9362
  function waitForShutdown(signal) {
8890
- return new Promise((resolve9) => {
9363
+ return new Promise((resolve12) => {
8891
9364
  const cleanup = () => {
8892
9365
  process.off("SIGINT", onSignal);
8893
9366
  process.off("SIGTERM", onSignal);
@@ -8895,18 +9368,18 @@ function waitForShutdown(signal) {
8895
9368
  };
8896
9369
  const onSignal = () => {
8897
9370
  cleanup();
8898
- resolve9();
9371
+ resolve12();
8899
9372
  };
8900
9373
  const onAbort = () => {
8901
9374
  cleanup();
8902
- resolve9();
9375
+ resolve12();
8903
9376
  };
8904
9377
  process.on("SIGINT", onSignal);
8905
9378
  process.on("SIGTERM", onSignal);
8906
9379
  if (signal !== void 0) {
8907
9380
  if (signal.aborted) {
8908
9381
  cleanup();
8909
- resolve9();
9382
+ resolve12();
8910
9383
  return;
8911
9384
  }
8912
9385
  signal.addEventListener("abort", onAbort);
@@ -8963,6 +9436,7 @@ function buildProgram() {
8963
9436
  registerOrientCommand(program2);
8964
9437
  registerReviewGapsCommand(program2);
8965
9438
  registerProjectCommand(program2);
9439
+ registerProtocolCommand(program2);
8966
9440
  return program2;
8967
9441
  }
8968
9442