@basou/cli 0.14.1 → 0.15.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;
658
675
  try {
659
- return await resolveBasouRepositoryRoot(cwd, {
660
- onRedirect: ({ via, root }) => console.error(`Resolved workspace view to ${root} (via ${via}).`)
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;
720
+ try {
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,7 +2614,7 @@ 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
@@ -2679,7 +2819,7 @@ async function runOrient(options, ctx = {}) {
2679
2819
  async function doRunOrient(options, ctx) {
2680
2820
  const cwd = ctx.cwd ?? process.cwd();
2681
2821
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "orient");
2682
- const paths = basouPaths8(repositoryRoot);
2822
+ const paths = basouPaths9(repositoryRoot);
2683
2823
  await assertWorkspaceInitialized7(paths.root);
2684
2824
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
2685
2825
  const probeCtx = { cwd: repositoryRoot };
@@ -2730,9 +2870,9 @@ import {
2730
2870
  unlinkSync,
2731
2871
  writeFileSync
2732
2872
  } from "fs";
2733
- import { basename as basename3, dirname, isAbsolute, join as join4, relative as relative2, resolve as resolve4 } from "path";
2873
+ import { basename as basename4, dirname, isAbsolute as isAbsolute2, join as join5, relative as relative2, resolve as resolve6 } from "path";
2734
2874
  import {
2735
- basouPaths as basouPaths9,
2875
+ basouPaths as basouPaths10,
2736
2876
  GENERATED_END,
2737
2877
  GENERATED_START,
2738
2878
  isGitNotFound,
@@ -2743,7 +2883,7 @@ import {
2743
2883
  planRename,
2744
2884
  planRosterAdoption,
2745
2885
  planWorkspaceView,
2746
- readManifest as readManifest5,
2886
+ readManifest as readManifest6,
2747
2887
  readMarkdownFile as readMarkdownFile4,
2748
2888
  reconcileSourceRoots,
2749
2889
  renderWithMarkers as renderWithMarkers4,
@@ -2850,8 +2990,8 @@ function preservedUnknownLines(fields) {
2850
2990
  async function doRunProjectCheck(options, ctx) {
2851
2991
  const cwd = ctx.cwd ?? process.cwd();
2852
2992
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project check");
2853
- const paths = basouPaths9(repositoryRoot);
2854
- const manifest = await readManifest5(paths);
2993
+ const paths = basouPaths10(repositoryRoot);
2994
+ const manifest = await readManifest6(paths);
2855
2995
  const summary = summarizeRosterDrift({
2856
2996
  ...manifest.repos !== void 0 ? { repos: manifest.repos } : {},
2857
2997
  sourceRoots: effectiveSourceRoots(manifest)
@@ -2912,8 +3052,8 @@ async function runProjectSync(options, ctx = {}) {
2912
3052
  async function doRunProjectSync(options, ctx) {
2913
3053
  const cwd = ctx.cwd ?? process.cwd();
2914
3054
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project sync");
2915
- const paths = basouPaths9(repositoryRoot);
2916
- const manifest = await readManifest5(paths);
3055
+ const paths = basouPaths10(repositoryRoot);
3056
+ const manifest = await readManifest6(paths);
2917
3057
  const hasRoster = manifest.repos !== void 0 && manifest.repos.length > 0;
2918
3058
  const reconcile = reconcileSourceRoots({
2919
3059
  ...manifest.repos !== void 0 ? { repos: manifest.repos } : {},
@@ -2982,20 +3122,20 @@ async function runProjectAdopt(options, ctx = {}) {
2982
3122
  }
2983
3123
  }
2984
3124
  function classifySourceRoot(repositoryRoot, declaredPath) {
2985
- const absolute = resolve4(repositoryRoot, declaredPath);
3125
+ const absolute = resolve6(repositoryRoot, declaredPath);
2986
3126
  let real;
2987
3127
  try {
2988
3128
  real = realpathSync(absolute);
2989
3129
  } catch {
2990
3130
  return { path: declaredPath, kind: "unresolved" };
2991
3131
  }
2992
- return { path: declaredPath, kind: existsSync(join4(real, ".git")) ? "repo" : "non-repo" };
3132
+ return { path: declaredPath, kind: existsSync(join5(real, ".git")) ? "repo" : "non-repo" };
2993
3133
  }
2994
3134
  async function doRunProjectAdopt(options, ctx) {
2995
3135
  const cwd = ctx.cwd ?? process.cwd();
2996
3136
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project adopt");
2997
- const paths = basouPaths9(repositoryRoot);
2998
- const manifest = await readManifest5(paths);
3137
+ const paths = basouPaths10(repositoryRoot);
3138
+ const manifest = await readManifest6(paths);
2999
3139
  const alreadyDeclared = manifest.repos !== void 0 && manifest.repos.length > 0;
3000
3140
  const candidates = effectiveSourceRoots(manifest).map(
3001
3141
  (r) => classifySourceRoot(repositoryRoot, r)
@@ -3084,11 +3224,11 @@ async function gatherRepoWiring(repositoryRoot, entry) {
3084
3224
  };
3085
3225
  let real;
3086
3226
  try {
3087
- real = realpathSync(resolve4(repositoryRoot, entry.path));
3227
+ real = realpathSync(resolve6(repositoryRoot, entry.path));
3088
3228
  } catch {
3089
3229
  return { ...base, reachable: false, instructionFiles: [] };
3090
3230
  }
3091
- if (!existsSync(join4(real, ".git"))) {
3231
+ if (!existsSync(join5(real, ".git"))) {
3092
3232
  return { ...base, reachable: false, instructionFiles: [] };
3093
3233
  }
3094
3234
  try {
@@ -3096,7 +3236,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
3096
3236
  for (const name of INSTRUCTION_FILES) {
3097
3237
  let present = true;
3098
3238
  try {
3099
- lstatSync(join4(real, name));
3239
+ lstatSync(join5(real, name));
3100
3240
  } catch {
3101
3241
  present = false;
3102
3242
  }
@@ -3111,8 +3251,8 @@ async function gatherRepoWiring(repositoryRoot, entry) {
3111
3251
  async function doRunProjectWiring(options, ctx) {
3112
3252
  const cwd = ctx.cwd ?? process.cwd();
3113
3253
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project wiring");
3114
- const paths = basouPaths9(repositoryRoot);
3115
- const manifest = await readManifest5(paths);
3254
+ const paths = basouPaths10(repositoryRoot);
3255
+ const manifest = await readManifest6(paths);
3116
3256
  const roster = manifest.repos ?? [];
3117
3257
  const facts = [];
3118
3258
  for (const entry of roster) facts.push(await gatherRepoWiring(repositoryRoot, entry));
@@ -3189,14 +3329,14 @@ function gatherRepoGitignore(repositoryRoot, entry) {
3189
3329
  };
3190
3330
  let real;
3191
3331
  try {
3192
- real = realpathSync(resolve4(repositoryRoot, entry.path));
3332
+ real = realpathSync(resolve6(repositoryRoot, entry.path));
3193
3333
  } catch {
3194
3334
  return { ...base, reachable: false, currentLines: [] };
3195
3335
  }
3196
- if (!existsSync(join4(real, ".git"))) {
3336
+ if (!existsSync(join5(real, ".git"))) {
3197
3337
  return { ...base, reachable: false, currentLines: [] };
3198
3338
  }
3199
- return { ...base, reachable: true, currentLines: readGitignoreLines(join4(real, ".gitignore")) };
3339
+ return { ...base, reachable: true, currentLines: readGitignoreLines(join5(real, ".gitignore")) };
3200
3340
  }
3201
3341
  function hasErrorCode(error) {
3202
3342
  return error instanceof Error && typeof error.code === "string";
@@ -3210,7 +3350,7 @@ function readGitignoreLines(file) {
3210
3350
  }
3211
3351
  }
3212
3352
  function applyGitignorePlan(repositoryRoot, plan) {
3213
- const file = join4(realpathSync(resolve4(repositoryRoot, plan.path)), ".gitignore");
3353
+ const file = join5(realpathSync(resolve6(repositoryRoot, plan.path)), ".gitignore");
3214
3354
  let existing = "";
3215
3355
  try {
3216
3356
  existing = readFileSync(file, "utf8");
@@ -3230,8 +3370,8 @@ function applyGitignorePlan(repositoryRoot, plan) {
3230
3370
  async function doRunProjectGitignore(options, ctx) {
3231
3371
  const cwd = ctx.cwd ?? process.cwd();
3232
3372
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project gitignore");
3233
- const paths = basouPaths9(repositoryRoot);
3234
- const manifest = await readManifest5(paths);
3373
+ const paths = basouPaths10(repositoryRoot);
3374
+ const manifest = await readManifest6(paths);
3235
3375
  const roster = manifest.repos ?? [];
3236
3376
  const facts = roster.map((entry) => gatherRepoGitignore(repositoryRoot, entry));
3237
3377
  const summary = planGitignore({ repos: facts, required: [...INSTRUCTION_FILES] });
@@ -3322,23 +3462,23 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
3322
3462
  const base = { path: entry.path };
3323
3463
  let real;
3324
3464
  try {
3325
- real = realpathSync(resolve4(repositoryRoot, entry.path));
3465
+ real = realpathSync(resolve6(repositoryRoot, entry.path));
3326
3466
  } catch {
3327
3467
  return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
3328
3468
  }
3329
3469
  if (real === anchorReal) {
3330
3470
  return { ...base, isAnchor: true, reachable: true, canonicalPresent: false, files: [] };
3331
3471
  }
3332
- if (!existsSync(join4(real, ".git"))) {
3472
+ if (!existsSync(join5(real, ".git"))) {
3333
3473
  return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
3334
3474
  }
3335
- const canonicalFile = join4(anchorReal, "agents", basename3(real), CANONICAL_FILE);
3475
+ const canonicalFile = join5(anchorReal, "agents", basename4(real), CANONICAL_FILE);
3336
3476
  if (!existsSync(canonicalFile)) {
3337
3477
  return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
3338
3478
  }
3339
3479
  const files = expectedSymlinkTargets(real, canonicalFile).map(
3340
3480
  (spec) => {
3341
- const { state, actualTarget } = inspectSymlink(join4(real, spec.name), spec.target);
3481
+ const { state, actualTarget } = inspectSymlink(join5(real, spec.name), spec.target);
3342
3482
  return {
3343
3483
  name: spec.name,
3344
3484
  expectedTarget: spec.target,
@@ -3352,14 +3492,14 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
3352
3492
  isAnchor: false,
3353
3493
  reachable: true,
3354
3494
  canonicalPresent: true,
3355
- canonicalName: basename3(real),
3495
+ canonicalName: basename4(real),
3356
3496
  files
3357
3497
  };
3358
3498
  }
3359
3499
  function applySymlinkPlan(repositoryRoot, plan) {
3360
3500
  let real;
3361
3501
  try {
3362
- real = realpathSync(resolve4(repositoryRoot, plan.path));
3502
+ real = realpathSync(resolve6(repositoryRoot, plan.path));
3363
3503
  } catch (error) {
3364
3504
  const message = failureReason(error);
3365
3505
  return { created: [], failed: plan.toCreate.map((c) => ({ file: c.name, message })) };
@@ -3367,7 +3507,7 @@ function applySymlinkPlan(repositoryRoot, plan) {
3367
3507
  const created = [];
3368
3508
  const failed = [];
3369
3509
  for (const { name, target } of plan.toCreate) {
3370
- const filePath = join4(real, name);
3510
+ const filePath = join5(real, name);
3371
3511
  try {
3372
3512
  mkdirSync(dirname(filePath), { recursive: true });
3373
3513
  symlinkSync(target, filePath);
@@ -3384,8 +3524,8 @@ function failureReason(error) {
3384
3524
  async function doRunProjectSymlinks(options, ctx) {
3385
3525
  const cwd = ctx.cwd ?? process.cwd();
3386
3526
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project symlinks");
3387
- const paths = basouPaths9(repositoryRoot);
3388
- const manifest = await readManifest5(paths);
3527
+ const paths = basouPaths10(repositoryRoot);
3528
+ const manifest = await readManifest6(paths);
3389
3529
  const roster = manifest.repos ?? [];
3390
3530
  const anchorReal = realpathSync(repositoryRoot);
3391
3531
  const facts = roster.map((entry) => gatherRepoSymlinks(repositoryRoot, anchorReal, entry));
@@ -3504,12 +3644,12 @@ async function runProjectWorkspace(options, ctx = {}) {
3504
3644
  }
3505
3645
  }
3506
3646
  function resolveViewDir(repositoryRoot, viewPath) {
3507
- const abs = resolve4(repositoryRoot, viewPath);
3647
+ const abs = resolve6(repositoryRoot, viewPath);
3508
3648
  try {
3509
3649
  return realpathSync(abs);
3510
3650
  } catch {
3511
3651
  try {
3512
- return join4(realpathSync(dirname(abs)), basename3(abs));
3652
+ return join5(realpathSync(dirname(abs)), basename4(abs));
3513
3653
  } catch {
3514
3654
  return abs;
3515
3655
  }
@@ -3518,7 +3658,7 @@ function resolveViewDir(repositoryRoot, viewPath) {
3518
3658
  function gatherViewRepo(repositoryRoot, viewDir, entry) {
3519
3659
  let repoReal;
3520
3660
  try {
3521
- repoReal = realpathSync(resolve4(repositoryRoot, entry.path));
3661
+ repoReal = realpathSync(resolve6(repositoryRoot, entry.path));
3522
3662
  } catch {
3523
3663
  return { path: entry.path, reachable: false };
3524
3664
  }
@@ -3526,8 +3666,8 @@ function gatherViewRepo(repositoryRoot, viewDir, entry) {
3526
3666
  if (expectedTarget === "" || expectedTarget === ".") {
3527
3667
  return { path: entry.path, reachable: false };
3528
3668
  }
3529
- const linkName = basename3(repoReal);
3530
- const { state, actualTarget } = inspectSymlink(join4(viewDir, linkName), expectedTarget);
3669
+ const linkName = basename4(repoReal);
3670
+ const { state, actualTarget } = inspectSymlink(join5(viewDir, linkName), expectedTarget);
3531
3671
  return {
3532
3672
  path: entry.path,
3533
3673
  reachable: true,
@@ -3541,7 +3681,7 @@ function applyViewPlan(viewDir, toCreate) {
3541
3681
  const created = [];
3542
3682
  const failed = [];
3543
3683
  for (const { name, target } of toCreate) {
3544
- const filePath = join4(viewDir, name);
3684
+ const filePath = join5(viewDir, name);
3545
3685
  try {
3546
3686
  mkdirSync(dirname(filePath), { recursive: true });
3547
3687
  symlinkSync(target, filePath);
@@ -3556,7 +3696,7 @@ var TOP_LEVEL_INSTRUCTION_FILES_LOWER = new Set(
3556
3696
  INSTRUCTION_FILES.filter((f) => !f.includes("/")).map((f) => f.toLowerCase())
3557
3697
  );
3558
3698
  function classifyViewLink(viewDir, name, rosterRealpaths) {
3559
- const filePath = join4(viewDir, name);
3699
+ const filePath = join5(viewDir, name);
3560
3700
  let isLink;
3561
3701
  try {
3562
3702
  isLink = lstatSync(filePath).isSymbolicLink();
@@ -3570,12 +3710,12 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
3570
3710
  } catch {
3571
3711
  return null;
3572
3712
  }
3573
- const resolved = isAbsolute(target) ? target : resolve4(viewDir, target);
3713
+ const resolved = isAbsolute2(target) ? target : resolve6(viewDir, target);
3574
3714
  try {
3575
3715
  if (rosterRealpaths.has(realpathSync(resolved))) return null;
3576
3716
  } catch {
3577
3717
  }
3578
- if (isAbsolute(target)) return { target, kind: "absolute" };
3718
+ if (isAbsolute2(target)) return { target, kind: "absolute" };
3579
3719
  let isDir = false;
3580
3720
  try {
3581
3721
  isDir = statSync(resolved).isDirectory();
@@ -3585,7 +3725,7 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
3585
3725
  if (!isDir) {
3586
3726
  return { target, kind: existsSync(resolved) ? "non-repo" : "broken" };
3587
3727
  }
3588
- return { target, kind: existsSync(join4(resolved, ".git")) ? "repo" : "non-repo" };
3728
+ return { target, kind: existsSync(join5(resolved, ".git")) ? "repo" : "non-repo" };
3589
3729
  }
3590
3730
  function gatherExistingViewLinks(viewDir, rosterRealpaths) {
3591
3731
  let names;
@@ -3610,7 +3750,7 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
3610
3750
  const pruned = [];
3611
3751
  const failed = [];
3612
3752
  for (const { name } of toPrune) {
3613
- const filePath = join4(viewDir, name);
3753
+ const filePath = join5(viewDir, name);
3614
3754
  const c = classifyViewLink(viewDir, name, rosterRealpaths);
3615
3755
  if (c === null || c.kind !== "repo") {
3616
3756
  failed.push({
@@ -3631,8 +3771,8 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
3631
3771
  async function doRunProjectWorkspace(options, ctx) {
3632
3772
  const cwd = ctx.cwd ?? process.cwd();
3633
3773
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project workspace");
3634
- const paths = basouPaths9(repositoryRoot);
3635
- const manifest = await readManifest5(paths);
3774
+ const paths = basouPaths10(repositoryRoot);
3775
+ const manifest = await readManifest6(paths);
3636
3776
  const viewPath = manifest.workspace.view;
3637
3777
  const roster = manifest.repos ?? [];
3638
3778
  let result;
@@ -3656,11 +3796,11 @@ async function doRunProjectWorkspace(options, ctx) {
3656
3796
  } else {
3657
3797
  const viewDir = resolveViewDir(repositoryRoot, viewPath);
3658
3798
  const facts = roster.map((entry) => gatherViewRepo(repositoryRoot, viewDir, entry));
3659
- const rosterNames = roster.map((entry) => basename3(resolve4(repositoryRoot, entry.path)));
3799
+ const rosterNames = roster.map((entry) => basename4(resolve6(repositoryRoot, entry.path)));
3660
3800
  const rosterRealpaths = /* @__PURE__ */ new Set();
3661
3801
  for (const entry of roster) {
3662
3802
  try {
3663
- rosterRealpaths.add(realpathSync(resolve4(repositoryRoot, entry.path)));
3803
+ rosterRealpaths.add(realpathSync(resolve6(repositoryRoot, entry.path)));
3664
3804
  } catch {
3665
3805
  }
3666
3806
  }
@@ -3821,10 +3961,10 @@ async function runProjectPreset(options, ctx = {}) {
3821
3961
  }
3822
3962
  }
3823
3963
  function canonicalFileFor(anchorReal, canonicalName) {
3824
- return join4(anchorReal, "agents", canonicalName, CANONICAL_FILE);
3964
+ return join5(anchorReal, "agents", canonicalName, CANONICAL_FILE);
3825
3965
  }
3826
3966
  function canonicalLabelFor(canonicalName) {
3827
- return join4("agents", canonicalName, CANONICAL_FILE);
3967
+ return join5("agents", canonicalName, CANONICAL_FILE);
3828
3968
  }
3829
3969
  async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
3830
3970
  const declared = {
@@ -3835,17 +3975,17 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
3835
3975
  };
3836
3976
  let real;
3837
3977
  try {
3838
- real = realpathSync(resolve4(repositoryRoot, entry.path));
3978
+ real = realpathSync(resolve6(repositoryRoot, entry.path));
3839
3979
  } catch {
3840
3980
  return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
3841
3981
  }
3842
3982
  if (real === anchorReal) {
3843
3983
  return { ...declared, isAnchor: true, reachable: true, canonicalPresent: false };
3844
3984
  }
3845
- if (!existsSync(join4(real, ".git"))) {
3985
+ if (!existsSync(join5(real, ".git"))) {
3846
3986
  return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
3847
3987
  }
3848
- const canonicalName = basename3(real);
3988
+ const canonicalName = basename4(real);
3849
3989
  let content;
3850
3990
  try {
3851
3991
  content = await readMarkdownFile4(canonicalFileFor(anchorReal, canonicalName));
@@ -3906,8 +4046,8 @@ function presetFailureReason(error) {
3906
4046
  async function doRunProjectPreset(options, ctx) {
3907
4047
  const cwd = ctx.cwd ?? process.cwd();
3908
4048
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project preset");
3909
- const paths = basouPaths9(repositoryRoot);
3910
- const manifest = await readManifest5(paths);
4049
+ const paths = basouPaths10(repositoryRoot);
4050
+ const manifest = await readManifest6(paths);
3911
4051
  const roster = manifest.repos ?? [];
3912
4052
  const anchorReal = realpathSync(repositoryRoot);
3913
4053
  const facts = [];
@@ -4064,33 +4204,33 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
4064
4204
  };
4065
4205
  let real;
4066
4206
  try {
4067
- real = realpathSync(resolve4(repositoryRoot, target));
4207
+ real = realpathSync(resolve6(repositoryRoot, target));
4068
4208
  } catch {
4069
4209
  return empty;
4070
4210
  }
4071
4211
  const anchorReal = realpathSync(repositoryRoot);
4072
- const canonicalName = basename3(real);
4212
+ const canonicalName = basename4(real);
4073
4213
  const instructionFiles = [];
4074
4214
  for (const name of INSTRUCTION_FILES) {
4075
4215
  try {
4076
- lstatSync(join4(real, name));
4216
+ lstatSync(join5(real, name));
4077
4217
  instructionFiles.push(name);
4078
4218
  } catch {
4079
4219
  }
4080
4220
  }
4081
4221
  let ignored;
4082
4222
  try {
4083
- ignored = new Set(readGitignoreLines(join4(real, ".gitignore")).map((l) => l.trim()));
4223
+ ignored = new Set(readGitignoreLines(join5(real, ".gitignore")).map((l) => l.trim()));
4084
4224
  } catch {
4085
4225
  ignored = /* @__PURE__ */ new Set();
4086
4226
  }
4087
4227
  const gitignorePatterns = INSTRUCTION_FILES.filter((p) => ignored.has(p) || ignored.has(`/${p}`));
4088
- const canonical2 = existsSync(join4(anchorReal, "agents", canonicalName, CANONICAL_FILE));
4228
+ const canonical2 = existsSync(join5(anchorReal, "agents", canonicalName, CANONICAL_FILE));
4089
4229
  let viewLink = false;
4090
4230
  const viewPath = manifest.workspace.view;
4091
4231
  if (viewPath !== void 0) {
4092
4232
  try {
4093
- lstatSync(join4(resolveViewDir(repositoryRoot, viewPath), canonicalName));
4233
+ lstatSync(join5(resolveViewDir(repositoryRoot, viewPath), canonicalName));
4094
4234
  viewLink = true;
4095
4235
  } catch {
4096
4236
  }
@@ -4127,12 +4267,12 @@ function buildArchivedManifest(manifest, plan, updatedAt) {
4127
4267
  async function doRunProjectArchive(target, options, ctx) {
4128
4268
  const cwd = ctx.cwd ?? process.cwd();
4129
4269
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project archive");
4130
- const paths = basouPaths9(repositoryRoot);
4131
- const manifest = await readManifest5(paths);
4270
+ const paths = basouPaths10(repositoryRoot);
4271
+ const manifest = await readManifest6(paths);
4132
4272
  const roster = manifest.repos ?? [];
4133
4273
  let targetIsAnchor = false;
4134
4274
  try {
4135
- targetIsAnchor = realpathSync(resolve4(repositoryRoot, target)) === realpathSync(repositoryRoot);
4275
+ targetIsAnchor = realpathSync(resolve6(repositoryRoot, target)) === realpathSync(repositoryRoot);
4136
4276
  } catch {
4137
4277
  targetIsAnchor = false;
4138
4278
  }
@@ -4217,7 +4357,7 @@ function renderProjectArchive(result) {
4217
4357
  if (t.instructionFiles.length > 0) items.push(`\u6307\u793A\u66F8(${t.instructionFiles.join(", ")})`);
4218
4358
  if (t.gitignorePatterns.length > 0)
4219
4359
  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)`);
4360
+ if (t.canonical) items.push(`anchor \u306E canonical(agents/${basename4(result.target)}/AGENTS.md)`);
4221
4361
  if (!t.inspected) {
4222
4362
  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
4363
  lines.push(
@@ -4252,12 +4392,12 @@ function gatherRenameWiring(repositoryRoot, manifest, oldBasename) {
4252
4392
  } catch {
4253
4393
  return { canonicalDirOld: false, viewLinkOld: false };
4254
4394
  }
4255
- const canonicalDirOld = existsSync(join4(anchorReal, "agents", oldBasename));
4395
+ const canonicalDirOld = existsSync(join5(anchorReal, "agents", oldBasename));
4256
4396
  let viewLinkOld = false;
4257
4397
  const viewPath = manifest.workspace.view;
4258
4398
  if (viewPath !== void 0) {
4259
4399
  try {
4260
- lstatSync(join4(resolveViewDir(repositoryRoot, viewPath), oldBasename));
4400
+ lstatSync(join5(resolveViewDir(repositoryRoot, viewPath), oldBasename));
4261
4401
  viewLinkOld = true;
4262
4402
  } catch {
4263
4403
  }
@@ -4278,12 +4418,12 @@ function buildRenamedManifest(manifest, plan, updatedAt) {
4278
4418
  async function doRunProjectRename(oldPath, newPath, options, ctx) {
4279
4419
  const cwd = ctx.cwd ?? process.cwd();
4280
4420
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project rename");
4281
- const paths = basouPaths9(repositoryRoot);
4282
- const manifest = await readManifest5(paths);
4421
+ const paths = basouPaths10(repositoryRoot);
4422
+ const manifest = await readManifest6(paths);
4283
4423
  const roster = manifest.repos ?? [];
4284
4424
  let oldIsAnchor = false;
4285
4425
  try {
4286
- oldIsAnchor = realpathSync(resolve4(repositoryRoot, oldPath)) === realpathSync(repositoryRoot);
4426
+ oldIsAnchor = realpathSync(resolve6(repositoryRoot, oldPath)) === realpathSync(repositoryRoot);
4287
4427
  } catch {
4288
4428
  oldIsAnchor = false;
4289
4429
  }
@@ -4393,79 +4533,341 @@ function renderProjectRename(result) {
4393
4533
  return lines.join("\n");
4394
4534
  }
4395
4535
 
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";
4536
+ // src/commands/protocol.ts
4537
+ import { readFile as readFile3 } from "fs/promises";
4538
+ import {
4539
+ PROTOCOL_END,
4540
+ PROTOCOL_START,
4541
+ parseMarkers as parseMarkers2,
4542
+ readMarkdownFile as readMarkdownFile5,
4543
+ removeMarkerSection
4544
+ } from "@basou/core";
4399
4545
 
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));
4546
+ // src/lib/durable-write.ts
4547
+ import { randomUUID } from "crypto";
4548
+ import { lstat, open, rename, stat as stat3, unlink as unlink2 } from "fs/promises";
4549
+ import { basename as basename5, dirname as dirname2, join as join6 } from "path";
4550
+ async function assertNotSymlink(targetPath) {
4551
+ try {
4552
+ const st = await lstat(targetPath);
4553
+ if (st.isSymbolicLink()) {
4554
+ throw new Error(
4555
+ "Refusing to write through a symlink. Replace the symlinked target with a regular file (or remove it) and retry."
4556
+ );
4557
+ }
4558
+ } catch (error) {
4559
+ if (error instanceof Error && error.code === "ENOENT") return;
4560
+ throw error;
4561
+ }
4562
+ }
4563
+ async function writeFileDurable(targetPath, content) {
4564
+ const dir = dirname2(targetPath);
4565
+ const tmpPath = join6(dir, `.${basename5(targetPath)}.tmp.${randomUUID()}`);
4566
+ let mode = 420;
4567
+ try {
4568
+ mode = (await stat3(targetPath)).mode & 511;
4569
+ } catch (error) {
4570
+ if (!(error instanceof Error && error.code === "ENOENT")) {
4571
+ throw error;
4572
+ }
4573
+ }
4574
+ let handle;
4575
+ try {
4576
+ handle = await open(tmpPath, "wx", mode);
4577
+ await handle.writeFile(content, "utf8");
4578
+ await handle.chmod(mode);
4579
+ await handle.sync();
4580
+ await handle.close();
4581
+ handle = void 0;
4582
+ await rename(tmpPath, targetPath);
4583
+ } catch (error) {
4584
+ if (handle) await handle.close().catch(() => void 0);
4585
+ await unlink2(tmpPath).catch(() => void 0);
4586
+ throw error;
4587
+ }
4588
+ try {
4589
+ const dirHandle = await open(dir, "r");
4590
+ try {
4591
+ await dirHandle.sync();
4592
+ } finally {
4593
+ await dirHandle.close();
4594
+ }
4595
+ } catch {
4596
+ }
4597
+ }
4598
+
4599
+ // src/lib/protocols-config.ts
4600
+ import { homedir as homedir5 } from "os";
4601
+ import { isAbsolute as isAbsolute3, join as join7, resolve as resolve7 } from "path";
4602
+ import { readYamlFile as readYamlFile4 } from "@basou/core";
4603
+ var DEFAULT_PROTOCOLS_CONFIG_PATH = join7(homedir5(), ".basou", "protocols.yaml");
4604
+ var DEFAULT_TARGET_PATH = join7(homedir5(), ".claude", "CLAUDE.md");
4605
+ var ALLOWED_TOP_KEYS = /* @__PURE__ */ new Set(["version", "protocols"]);
4606
+ var ALLOWED_ENTRY_KEYS = /* @__PURE__ */ new Set(["source", "title"]);
4607
+ function expandTilde2(p) {
4608
+ if (p === "~") return homedir5();
4609
+ if (p.startsWith("~/")) return join7(homedir5(), p.slice(2));
4408
4610
  return p;
4409
4611
  }
4410
- function isRecord(value) {
4612
+ function isRecord2(value) {
4411
4613
  return typeof value === "object" && value !== null && !Array.isArray(value);
4412
4614
  }
4413
- async function loadPortfolioConfig(configPath = DEFAULT_PORTFOLIO_CONFIG_PATH) {
4615
+ async function loadProtocolsConfig(configPath = DEFAULT_PROTOCOLS_CONFIG_PATH) {
4414
4616
  let raw;
4415
4617
  try {
4416
- raw = await readYamlFile3(configPath);
4618
+ raw = await readYamlFile4(configPath);
4417
4619
  } catch (error) {
4418
4620
  if (error instanceof Error && error.message === "YAML file not found") {
4419
4621
  throw new Error(
4420
- "No portfolio config at ~/.basou/portfolio.yaml. Create one (a 'workspaces:' list of repo paths) or pass --workspace <path>."
4622
+ "No protocols config at ~/.basou/protocols.yaml. Create one (a 'protocols:' list of source markdown paths) before running 'basou protocol sync'."
4421
4623
  );
4422
4624
  }
4423
4625
  if (error instanceof Error && error.message === "Failed to parse YAML content") {
4424
- throw new Error("~/.basou/portfolio.yaml is not valid YAML.");
4626
+ throw new Error("~/.basou/protocols.yaml is not valid YAML.");
4425
4627
  }
4426
4628
  throw error;
4427
4629
  }
4428
- if (!isRecord(raw) || !Array.isArray(raw.workspaces)) {
4429
- throw new Error("~/.basou/portfolio.yaml must contain a 'workspaces:' list.");
4630
+ if (!isRecord2(raw) || !Array.isArray(raw.protocols)) {
4631
+ throw new Error("~/.basou/protocols.yaml must contain a 'protocols:' list.");
4632
+ }
4633
+ for (const key of Object.keys(raw)) {
4634
+ if (!ALLOWED_TOP_KEYS.has(key)) {
4635
+ throw new Error(
4636
+ `~/.basou/protocols.yaml has an unknown key '${key}' (allowed: version, protocols).`
4637
+ );
4638
+ }
4430
4639
  }
4431
4640
  const seen = /* @__PURE__ */ new Set();
4432
4641
  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'.");
4642
+ for (const entry of raw.protocols) {
4643
+ if (!isRecord2(entry)) {
4644
+ throw new Error("Each protocol entry must be a mapping with a 'source' key.");
4436
4645
  }
4437
- if (entry.label !== void 0 && typeof entry.label !== "string") {
4438
- throw new Error("A portfolio workspace 'label' must be a string when present.");
4646
+ for (const key of Object.keys(entry)) {
4647
+ if (!ALLOWED_ENTRY_KEYS.has(key)) {
4648
+ throw new Error(`A protocol entry has an unknown key '${key}' (allowed: source, title).`);
4649
+ }
4439
4650
  }
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
- );
4651
+ if (typeof entry.source !== "string" || entry.source.trim().length === 0) {
4652
+ throw new Error("Each protocol entry needs a non-empty string 'source'.");
4653
+ }
4654
+ if (entry.title !== void 0 && (typeof entry.title !== "string" || entry.title.trim().length === 0)) {
4655
+ throw new Error("A protocol entry 'title' must be a non-empty string when present.");
4656
+ }
4657
+ const expanded = expandTilde2(entry.source.trim());
4658
+ if (!isAbsolute3(expanded)) {
4659
+ throw new Error("Protocol 'source' paths must be absolute (or start with '~').");
4660
+ }
4661
+ const abs = resolve7(expanded);
4662
+ if (seen.has(abs)) {
4663
+ throw new Error("Duplicate protocol source (each source path may appear only once).");
4445
4664
  }
4446
- const abs = resolve5(expanded);
4447
- if (seen.has(abs)) continue;
4448
4665
  seen.add(abs);
4449
- result.push(entry.label !== void 0 ? { path: abs, label: entry.label } : { path: abs });
4666
+ result.push(
4667
+ entry.title !== void 0 ? { source: abs, title: entry.title.trim() } : { source: abs }
4668
+ );
4450
4669
  }
4451
4670
  if (result.length === 0) {
4452
- throw new Error("~/.basou/portfolio.yaml has no workspaces.");
4671
+ throw new Error("~/.basou/protocols.yaml has no protocols.");
4453
4672
  }
4454
4673
  return result;
4455
4674
  }
4456
4675
 
4676
+ // src/commands/protocol.ts
4677
+ var PROTOCOL_MARKERS = { start: PROTOCOL_START, end: PROTOCOL_END };
4678
+ 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. -->";
4679
+ function registerProtocolCommand(program2) {
4680
+ const protocol = program2.command("protocol").description("Manage the basou-managed standing-protocol block in the global CLAUDE.md");
4681
+ 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) => {
4682
+ await runProtocolSync(opts);
4683
+ });
4684
+ 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) => {
4685
+ await runProtocolList(opts);
4686
+ });
4687
+ 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) => {
4688
+ await runProtocolUnsync(opts);
4689
+ });
4690
+ }
4691
+ async function runProtocolSync(options) {
4692
+ try {
4693
+ await doRunProtocolSync(options);
4694
+ } catch (error) {
4695
+ renderCliError(error, { verbose: isVerbose(options) });
4696
+ process.exitCode = 1;
4697
+ }
4698
+ }
4699
+ async function runProtocolList(options) {
4700
+ try {
4701
+ await doRunProtocolList(options);
4702
+ } catch (error) {
4703
+ renderCliError(error, { verbose: isVerbose(options) });
4704
+ process.exitCode = 1;
4705
+ }
4706
+ }
4707
+ async function runProtocolUnsync(options) {
4708
+ try {
4709
+ await doRunProtocolUnsync(options);
4710
+ } catch (error) {
4711
+ renderCliError(error, { verbose: isVerbose(options) });
4712
+ process.exitCode = 1;
4713
+ }
4714
+ }
4715
+ async function readProtocolSources(entries) {
4716
+ const out = [];
4717
+ for (const entry of entries) {
4718
+ let content;
4719
+ try {
4720
+ content = await readFile3(entry.source, "utf8");
4721
+ } catch (error) {
4722
+ if (error instanceof Error && error.code === "ENOENT") {
4723
+ throw new Error(
4724
+ "A protocol source file does not exist. Check the 'source' paths in ~/.basou/protocols.yaml.",
4725
+ { cause: error }
4726
+ );
4727
+ }
4728
+ throw new Error("Failed to read a protocol source file.", { cause: error });
4729
+ }
4730
+ for (const line of content.split(/\r?\n/)) {
4731
+ if (line === PROTOCOL_START || line === PROTOCOL_END) {
4732
+ throw new Error(
4733
+ "A protocol source contains a BASOU:PROTOCOLS marker line, which would corrupt the managed block. Remove that line from the source."
4734
+ );
4735
+ }
4736
+ }
4737
+ out.push({ entry, content });
4738
+ }
4739
+ return out;
4740
+ }
4741
+ function buildBlock(sources) {
4742
+ const sections = sources.map(({ entry, content }) => {
4743
+ const body = content.replace(/\s+$/, "");
4744
+ return entry.title !== void 0 ? `## ${entry.title}
4745
+
4746
+ ${body}` : body;
4747
+ });
4748
+ return `${MANAGED_NOTE}
4749
+
4750
+ ${sections.join("\n\n")}
4751
+ `;
4752
+ }
4753
+ function buildTargetBody(existing, block) {
4754
+ const wrapped = `${PROTOCOL_START}
4755
+ ${block}${PROTOCOL_END}
4756
+ `;
4757
+ if (existing === null || existing === "") return wrapped;
4758
+ const section = parseMarkers2(existing, PROTOCOL_MARKERS);
4759
+ switch (section.kind) {
4760
+ case "ok":
4761
+ return `${section.before}${PROTOCOL_START}
4762
+ ${block}${PROTOCOL_END}${section.after}`;
4763
+ case "no_markers": {
4764
+ const sep = existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
4765
+ return `${existing}${sep}${wrapped}`;
4766
+ }
4767
+ default:
4768
+ throw new Error(
4769
+ "The BASOU:PROTOCOLS markers in the target are malformed (a marker is missing, duplicated, or out of order). Fix or remove them, then retry."
4770
+ );
4771
+ }
4772
+ }
4773
+ async function backupOnce(target, existing) {
4774
+ if (existing === null) return;
4775
+ const bak = `${target}.basou-bak`;
4776
+ const already = await readMarkdownFile5(bak);
4777
+ if (already !== null) return;
4778
+ await writeFileDurable(bak, existing);
4779
+ }
4780
+ async function doRunProtocolSync(options) {
4781
+ const configPath = options.config ?? DEFAULT_PROTOCOLS_CONFIG_PATH;
4782
+ const target = options.target ?? DEFAULT_TARGET_PATH;
4783
+ const entries = await loadProtocolsConfig(configPath);
4784
+ const sources = await readProtocolSources(entries);
4785
+ const block = buildBlock(sources);
4786
+ await assertNotSymlink(target);
4787
+ const existing = await readMarkdownFile5(target);
4788
+ const newBody = buildTargetBody(existing, block);
4789
+ if (newBody === existing) {
4790
+ console.log(`The basou:protocols block is already up to date (${entries.length} protocol(s)).`);
4791
+ return;
4792
+ }
4793
+ const hadBlock = existing !== null && parseMarkers2(existing, PROTOCOL_MARKERS).kind === "ok";
4794
+ if (options.dryRun === true) {
4795
+ console.log(
4796
+ `[dry-run] Would ${hadBlock ? "update" : "install"} the basou:protocols block (${entries.length} protocol(s)).`
4797
+ );
4798
+ for (const { entry } of sources) {
4799
+ console.log(` - ${entry.title ?? entry.source}`);
4800
+ }
4801
+ return;
4802
+ }
4803
+ const recheck = await readMarkdownFile5(target);
4804
+ if (recheck !== existing) {
4805
+ throw new Error(
4806
+ "The target changed during sync; aborting so a concurrent edit is not overwritten. Re-run 'basou protocol sync'."
4807
+ );
4808
+ }
4809
+ await backupOnce(target, existing);
4810
+ await writeFileDurable(target, newBody);
4811
+ console.log(
4812
+ `${hadBlock ? "Updated" : "Installed"} the basou:protocols block in the global CLAUDE.md (${entries.length} protocol(s)).`
4813
+ );
4814
+ }
4815
+ async function doRunProtocolList(options) {
4816
+ const configPath = options.config ?? DEFAULT_PROTOCOLS_CONFIG_PATH;
4817
+ const target = options.target ?? DEFAULT_TARGET_PATH;
4818
+ const entries = await loadProtocolsConfig(configPath);
4819
+ const existing = await readMarkdownFile5(target);
4820
+ const installed = existing !== null && parseMarkers2(existing, PROTOCOL_MARKERS).kind === "ok";
4821
+ console.log(`Declared protocols (${entries.length}):`);
4822
+ for (const entry of entries) {
4823
+ console.log(` - ${entry.title ?? entry.source}`);
4824
+ }
4825
+ console.log(installed ? "Block: installed in the global CLAUDE.md." : "Block: not installed.");
4826
+ }
4827
+ async function doRunProtocolUnsync(options) {
4828
+ const target = options.target ?? DEFAULT_TARGET_PATH;
4829
+ await assertNotSymlink(target);
4830
+ const existing = await readMarkdownFile5(target);
4831
+ if (existing === null) {
4832
+ console.log("No target file; nothing to remove.");
4833
+ return;
4834
+ }
4835
+ const newBody = removeMarkerSection(existing, "CLAUDE.md", PROTOCOL_MARKERS);
4836
+ if (newBody === existing) {
4837
+ console.log("No basou:protocols block found; nothing removed.");
4838
+ return;
4839
+ }
4840
+ if (options.dryRun === true) {
4841
+ console.log("[dry-run] Would remove the basou:protocols block from the global CLAUDE.md.");
4842
+ return;
4843
+ }
4844
+ const recheck = await readMarkdownFile5(target);
4845
+ if (recheck !== existing) {
4846
+ throw new Error(
4847
+ "The target changed during unsync; aborting so a concurrent edit is not overwritten. Re-run 'basou protocol unsync'."
4848
+ );
4849
+ }
4850
+ await backupOnce(target, existing);
4851
+ await writeFileDurable(target, newBody);
4852
+ console.log("Removed the basou:protocols block from the global CLAUDE.md.");
4853
+ }
4854
+
4855
+ // src/commands/refresh.ts
4856
+ import { assertBasouRootSafe as assertBasouRootSafe9, basouPaths as basouPaths11, findErrorCode as findErrorCode9 } from "@basou/core";
4857
+ import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
4858
+
4457
4859
  // 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";
4860
+ import { readdir as readdir2, stat as stat4 } from "fs/promises";
4861
+ import { homedir as homedir6 } from "os";
4862
+ import { join as join8 } from "path";
4461
4863
  import { findErrorCode as findErrorCode8 } from "@basou/core";
4462
4864
  var DEFAULT_WATCH_INTERVAL_SEC = 30;
4463
4865
  var MIN_WATCH_INTERVAL_SEC = 5;
4464
4866
  var MAX_WATCH_INTERVAL_SEC = 86400;
4465
4867
  function watchedRoots(ctx) {
4466
4868
  return [
4467
- ctx.codexSessionsDir ?? join6(homedir5(), ".codex", "sessions"),
4468
- ctx.claudeProjectsDir ?? join6(homedir5(), ".claude", "projects")
4869
+ ctx.codexSessionsDir ?? join8(homedir6(), ".codex", "sessions"),
4870
+ ctx.claudeProjectsDir ?? join8(homedir6(), ".claude", "projects")
4469
4871
  ];
4470
4872
  }
4471
4873
  async function scanSourceLogs(roots) {
@@ -4479,12 +4881,12 @@ async function scanSourceLogs(roots) {
4479
4881
  throw new Error("Failed to read a source log directory", { cause: error });
4480
4882
  }
4481
4883
  for (const entry of entries) {
4482
- const full = join6(dir, entry.name);
4884
+ const full = join8(dir, entry.name);
4483
4885
  if (entry.isDirectory()) {
4484
4886
  await walk(full);
4485
4887
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
4486
4888
  try {
4487
- const info = await stat2(full);
4889
+ const info = await stat4(full);
4488
4890
  out.set(full, { mtimeMs: info.mtimeMs, size: info.size });
4489
4891
  } catch (error) {
4490
4892
  if (findErrorCode8(error, "ENOENT")) continue;
@@ -4586,19 +4988,19 @@ function parseInterval(value) {
4586
4988
  return seconds;
4587
4989
  }
4588
4990
  function abortableSleep(ms, signal) {
4589
- return new Promise((resolve9) => {
4991
+ return new Promise((resolve11) => {
4590
4992
  if (signal.aborted) {
4591
- resolve9();
4993
+ resolve11();
4592
4994
  return;
4593
4995
  }
4594
4996
  let timer;
4595
4997
  const onAbort = () => {
4596
4998
  clearTimeout(timer);
4597
- resolve9();
4999
+ resolve11();
4598
5000
  };
4599
5001
  timer = setTimeout(() => {
4600
5002
  signal.removeEventListener("abort", onAbort);
4601
- resolve9();
5003
+ resolve11();
4602
5004
  }, ms);
4603
5005
  signal.addEventListener("abort", onAbort, { once: true });
4604
5006
  });
@@ -4689,7 +5091,7 @@ async function doRunRefreshWatch(options, ctx) {
4689
5091
  if (options.force === true) throw new Error("--watch cannot be combined with --force.");
4690
5092
  const cwd = ctx.cwd ?? process.cwd();
4691
5093
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "refresh");
4692
- const paths = basouPaths10(repositoryRoot);
5094
+ const paths = basouPaths11(repositoryRoot);
4693
5095
  await assertWorkspaceInitialized8(paths.root);
4694
5096
  const intervalMs = (options.interval ?? DEFAULT_WATCH_INTERVAL_SEC) * 1e3;
4695
5097
  const controller = new AbortController();
@@ -4717,7 +5119,7 @@ async function doRunRefreshWatch(options, ctx) {
4717
5119
  async function computeRefresh(options, ctx) {
4718
5120
  const cwd = ctx.cwd ?? process.cwd();
4719
5121
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "refresh");
4720
- const paths = basouPaths10(repositoryRoot);
5122
+ const paths = basouPaths11(repositoryRoot);
4721
5123
  await assertWorkspaceInitialized8(paths.root);
4722
5124
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
4723
5125
  return refreshAll({
@@ -4797,10 +5199,10 @@ async function assertWorkspaceInitialized8(basouRoot) {
4797
5199
  }
4798
5200
 
4799
5201
  // src/commands/report.ts
4800
- import { isAbsolute as isAbsolute3, resolve as resolve6 } from "path";
5202
+ import { isAbsolute as isAbsolute4, resolve as resolve8 } from "path";
4801
5203
  import {
4802
5204
  assertBasouRootSafe as assertBasouRootSafe10,
4803
- basouPaths as basouPaths11,
5205
+ basouPaths as basouPaths12,
4804
5206
  findErrorCode as findErrorCode10,
4805
5207
  renderReport,
4806
5208
  resolveRepositoryRoot as resolveRepositoryRoot8,
@@ -4825,7 +5227,7 @@ async function runReportGenerate(options, ctx = {}) {
4825
5227
  async function doRunReportGenerate(options, ctx) {
4826
5228
  const cwd = ctx.cwd ?? process.cwd();
4827
5229
  const repositoryRoot = await resolveRepositoryRootForReport(cwd);
4828
- const paths = basouPaths11(repositoryRoot);
5230
+ const paths = basouPaths12(repositoryRoot);
4829
5231
  await assertWorkspaceInitialized9(paths.root);
4830
5232
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
4831
5233
  const result = await renderReport({
@@ -4837,7 +5239,7 @@ async function doRunReportGenerate(options, ctx) {
4837
5239
  onTaskSkip: (taskId, reason) => printTaskSkip(taskId, reason)
4838
5240
  });
4839
5241
  if (options.out !== void 0) {
4840
- const outPath = isAbsolute3(options.out) ? options.out : resolve6(cwd, options.out);
5242
+ const outPath = isAbsolute4(options.out) ? options.out : resolve8(cwd, options.out);
4841
5243
  await writeMarkdownFile6(outPath, result.body);
4842
5244
  const { sessions, decisions, tasks } = result.data;
4843
5245
  console.error(
@@ -4876,7 +5278,7 @@ async function assertWorkspaceInitialized9(basouRoot) {
4876
5278
 
4877
5279
  // src/commands/review-gaps.ts
4878
5280
  import {
4879
- basouPaths as basouPaths12,
5281
+ basouPaths as basouPaths13,
4880
5282
  findReviewGaps
4881
5283
  } from "@basou/core";
4882
5284
  import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
@@ -4917,7 +5319,7 @@ async function runReviewGaps(options, ctx = {}) {
4917
5319
  async function doRunReviewGaps(options, ctx) {
4918
5320
  const cwd = ctx.cwd ?? process.cwd();
4919
5321
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "review-gaps");
4920
- const paths = basouPaths12(repositoryRoot);
5322
+ const paths = basouPaths13(repositoryRoot);
4921
5323
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
4922
5324
  const summary = await findReviewGaps({
4923
5325
  paths,
@@ -5000,12 +5402,12 @@ function renderReviewGaps(summary) {
5000
5402
 
5001
5403
  // src/commands/run.ts
5002
5404
  import { mkdir as mkdir2 } from "fs/promises";
5003
- import { homedir as homedir6 } from "os";
5004
- import { join as join7 } from "path";
5405
+ import { homedir as homedir7 } from "os";
5406
+ import { join as join9 } from "path";
5005
5407
  import {
5006
5408
  acquireLock as acquireLock5,
5007
5409
  assertBasouRootSafe as assertBasouRootSafe11,
5008
- basouPaths as basouPaths13,
5410
+ basouPaths as basouPaths14,
5009
5411
  ChildProcessRunner as ChildProcessRunner2,
5010
5412
  claudeCodeAdapterMetadata,
5011
5413
  appendChainedEvent as coreAppendChainedEvent2,
@@ -5014,8 +5416,8 @@ import {
5014
5416
  getSnapshot as getSnapshot2,
5015
5417
  overwriteYamlFile as overwriteYamlFile2,
5016
5418
  prefixedUlid as prefixedUlid4,
5017
- readManifest as readManifest6,
5018
- readYamlFile as readYamlFile4,
5419
+ readManifest as readManifest7,
5420
+ readYamlFile as readYamlFile5,
5019
5421
  resolveClaudeCodeCommand,
5020
5422
  resolveRepositoryRoot as resolveRepositoryRoot9,
5021
5423
  SessionSchema as SessionSchema2,
@@ -5050,17 +5452,17 @@ async function runClaudeCode(args, options, ctx = {}) {
5050
5452
  const { command } = await resolveCommand();
5051
5453
  const cwd = options.cwd ?? process.cwd();
5052
5454
  const repoRoot = await resolveRepositoryRootForRun(cwd);
5053
- const paths = basouPaths13(repoRoot);
5455
+ const paths = basouPaths14(repoRoot);
5054
5456
  await assertBasouRootSafe11(paths.root);
5055
- const manifest = await readManifest6(paths);
5457
+ const manifest = await readManifest7(paths);
5056
5458
  const sessionId = prefixedUlid4("ses");
5057
- const sessionDir = join7(paths.sessions, sessionId);
5459
+ const sessionDir = join9(paths.sessions, sessionId);
5058
5460
  await mkdir2(sessionDir, { recursive: true });
5059
5461
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
5060
5462
  await coreAppendChainedEvent2(paths, sessionId, event);
5061
5463
  });
5062
5464
  const startedAt = now().toISOString();
5063
- const sessionYamlPath = join7(sessionDir, "session.yaml");
5465
+ const sessionYamlPath = join9(sessionDir, "session.yaml");
5064
5466
  const session = buildInitialSession2({
5065
5467
  id: sessionId,
5066
5468
  command,
@@ -5186,7 +5588,7 @@ async function runClaudeCode(args, options, ctx = {}) {
5186
5588
  const rawRelated = computeRelatedFiles(preSnapshot, postSnapshot, diff);
5187
5589
  const relatedFiles = sanitizeRelatedFiles(rawRelated, {
5188
5590
  workingDirectory: repoRoot,
5189
- homedir: homedir6()
5591
+ homedir: homedir7()
5190
5592
  }).sanitized;
5191
5593
  const finalStatus = decideFinalStatus2(result, signalReceived);
5192
5594
  await appendEvent(sessionDir, {
@@ -5330,7 +5732,7 @@ function buildInitialSession2(input) {
5330
5732
  source: { ...claudeCodeAdapterMetadata },
5331
5733
  started_at: input.startedAt,
5332
5734
  status: "initialized",
5333
- working_directory: sanitizeWorkingDirectory2(input.cwd, { homedir: homedir6() }),
5735
+ working_directory: sanitizeWorkingDirectory2(input.cwd, { homedir: homedir7() }),
5334
5736
  invocation: {
5335
5737
  command: input.command,
5336
5738
  args: [...input.args],
@@ -5342,7 +5744,7 @@ function buildInitialSession2(input) {
5342
5744
  };
5343
5745
  }
5344
5746
  async function mutateSessionYaml2(filePath, mutator) {
5345
- const raw = await readYamlFile4(filePath);
5747
+ const raw = await readYamlFile5(filePath);
5346
5748
  const parsed = SessionSchema2.parse(raw);
5347
5749
  mutator(parsed);
5348
5750
  const validated = SessionSchema2.parse(parsed);
@@ -5402,20 +5804,20 @@ async function resolveRepositoryRootForRun(cwd) {
5402
5804
  }
5403
5805
 
5404
5806
  // 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";
5807
+ import { readFile as readFile4 } from "fs/promises";
5808
+ import { basename as basename6, isAbsolute as isAbsolute5, join as join10, relative as relative3 } from "path";
5407
5809
  import {
5408
5810
  acquireLock as acquireLock6,
5409
5811
  appendEventToExistingSession as appendEventToExistingSession3,
5410
5812
  assertBasouRootSafe as assertBasouRootSafe12,
5411
- basouPaths as basouPaths14,
5813
+ basouPaths as basouPaths15,
5412
5814
  enumerateSessionDirs as enumerateSessionDirs2,
5413
5815
  findErrorCode as findErrorCode11,
5414
5816
  importSessionFromJson as importSessionFromJson2,
5415
5817
  loadSessionEntries,
5416
5818
  readAllEvents,
5417
- readManifest as readManifest7,
5418
- readYamlFile as readYamlFile5,
5819
+ readManifest as readManifest8,
5820
+ readYamlFile as readYamlFile6,
5419
5821
  rechainSessionInPlace,
5420
5822
  resolveSessionId as resolveSessionId3,
5421
5823
  resolveTaskId,
@@ -5473,7 +5875,7 @@ async function runSessionList(options, ctx = {}) {
5473
5875
  async function doRunSessionList(options, ctx) {
5474
5876
  const cwd = ctx.cwd ?? process.cwd();
5475
5877
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "list");
5476
- const paths = basouPaths14(repositoryRoot);
5878
+ const paths = basouPaths15(repositoryRoot);
5477
5879
  await assertWorkspaceInitialized10(paths.root);
5478
5880
  const now = /* @__PURE__ */ new Date();
5479
5881
  const records = (await loadSessionEntries(paths, {
@@ -5525,14 +5927,14 @@ async function runSessionShow(idInput, options, ctx = {}) {
5525
5927
  async function doRunSessionShow(idInput, options, ctx) {
5526
5928
  const cwd = ctx.cwd ?? process.cwd();
5527
5929
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "show");
5528
- const paths = basouPaths14(repositoryRoot);
5930
+ const paths = basouPaths15(repositoryRoot);
5529
5931
  await assertWorkspaceInitialized10(paths.root);
5530
5932
  const sessionId = await resolveSessionId3(paths, idInput);
5531
- const sessionDir = join8(paths.sessions, sessionId);
5532
- const sessionYamlPath = join8(sessionDir, "session.yaml");
5933
+ const sessionDir = join10(paths.sessions, sessionId);
5934
+ const sessionYamlPath = join10(sessionDir, "session.yaml");
5533
5935
  let session;
5534
5936
  try {
5535
- const raw = await readYamlFile5(sessionYamlPath);
5937
+ const raw = await readYamlFile6(sessionYamlPath);
5536
5938
  session = SessionSchema3.parse(raw);
5537
5939
  } catch (error) {
5538
5940
  if (findErrorCode11(error, "ENOENT")) {
@@ -5650,7 +6052,7 @@ function formatSessionWork(session, events, now) {
5650
6052
  }
5651
6053
  function formatWorkingDir(workingDir, repositoryRoot, options) {
5652
6054
  if (options.fullPath === true) return workingDir;
5653
- if (!isAbsolute4(workingDir)) {
6055
+ if (!isAbsolute5(workingDir)) {
5654
6056
  if (workingDir === ".") return "<repository_root>";
5655
6057
  return workingDir;
5656
6058
  }
@@ -5813,9 +6215,9 @@ async function runSessionImport(options, ctx = {}) {
5813
6215
  async function doRunSessionImport(options, ctx) {
5814
6216
  const cwd = ctx.cwd ?? process.cwd();
5815
6217
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "import");
5816
- const paths = basouPaths14(repositoryRoot);
6218
+ const paths = basouPaths15(repositoryRoot);
5817
6219
  await assertWorkspaceInitialized10(paths.root);
5818
- const manifest = await readManifest7(paths);
6220
+ const manifest = await readManifest8(paths);
5819
6221
  const rawBody = await readInputFile(options.from);
5820
6222
  const json = parseJsonStrict(rawBody);
5821
6223
  const parsed = SessionImportPayloadSchema2.safeParse(json);
@@ -5842,7 +6244,7 @@ async function doRunSessionImport(options, ctx) {
5842
6244
  }
5843
6245
  async function readInputFile(path) {
5844
6246
  try {
5845
- return await readFile3(path, "utf8");
6247
+ return await readFile4(path, "utf8");
5846
6248
  } catch (error) {
5847
6249
  if (findErrorCode11(error, "ENOENT")) {
5848
6250
  throw new Error("Import source not found", { cause: error });
@@ -5900,7 +6302,7 @@ function printSessionImportResult(options, result) {
5900
6302
  return;
5901
6303
  }
5902
6304
  console.log(
5903
- `Imported session ${sid} (${result.eventCount} events) from ${basename4(options.from)}`
6305
+ `Imported session ${sid} (${result.eventCount} events) from ${basename6(options.from)}`
5904
6306
  );
5905
6307
  }
5906
6308
  var NOTE_BODY_PREVIEW_LIMIT = 80;
@@ -5927,7 +6329,7 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
5927
6329
  }
5928
6330
  const cwd = ctx.cwd ?? process.cwd();
5929
6331
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "note");
5930
- const paths = basouPaths14(repositoryRoot);
6332
+ const paths = basouPaths15(repositoryRoot);
5931
6333
  await assertWorkspaceInitialized10(paths.root);
5932
6334
  const sessionId = await resolveSessionId3(paths, sessionIdInput);
5933
6335
  const body = hasBody ? options.body : await readNoteFile(options.fromFile);
@@ -5959,7 +6361,7 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
5959
6361
  }
5960
6362
  async function readNoteFile(path) {
5961
6363
  try {
5962
- return await readFile3(path, "utf8");
6364
+ return await readFile4(path, "utf8");
5963
6365
  } catch (error) {
5964
6366
  if (findErrorCode11(error, "ENOENT")) {
5965
6367
  throw new Error("Note source not found", { cause: error });
@@ -6009,7 +6411,7 @@ async function doRunSessionRechain(options, ctx) {
6009
6411
  }
6010
6412
  const cwd = ctx.cwd ?? process.cwd();
6011
6413
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "rechain");
6012
- const paths = basouPaths14(repositoryRoot);
6414
+ const paths = basouPaths15(repositoryRoot);
6013
6415
  await assertWorkspaceInitialized10(paths.root);
6014
6416
  const sessionIds = options.session !== void 0 ? [await resolveSessionId3(paths, options.session)] : await enumerateSessionDirs2(paths);
6015
6417
  const dryRun = options.dryRun === true;
@@ -6064,7 +6466,7 @@ function renderRechainRow(row, dryRun) {
6064
6466
  // src/commands/stats.ts
6065
6467
  import {
6066
6468
  assertBasouRootSafe as assertBasouRootSafe13,
6067
- basouPaths as basouPaths15,
6469
+ basouPaths as basouPaths16,
6068
6470
  computeWorkStats,
6069
6471
  findErrorCode as findErrorCode12,
6070
6472
  resolveRepositoryRoot as resolveRepositoryRoot10
@@ -6085,7 +6487,7 @@ async function runStats(options, ctx = {}) {
6085
6487
  async function doRunStats(options, ctx) {
6086
6488
  const cwd = ctx.cwd ?? process.cwd();
6087
6489
  const repositoryRoot = await resolveRepositoryRootForStats(cwd);
6088
- const paths = basouPaths15(repositoryRoot);
6490
+ const paths = basouPaths16(repositoryRoot);
6089
6491
  await assertWorkspaceInitialized11(paths.root);
6090
6492
  const now = ctx.nowProvider?.() ?? /* @__PURE__ */ new Date();
6091
6493
  const result = await computeWorkStats({
@@ -6194,10 +6596,10 @@ async function assertWorkspaceInitialized11(basouRoot) {
6194
6596
  // src/commands/status.ts
6195
6597
  import {
6196
6598
  assertBasouRootSafe as assertBasouRootSafe14,
6197
- basouPaths as basouPaths16,
6599
+ basouPaths as basouPaths17,
6198
6600
  buildStatusSnapshot,
6199
6601
  findErrorCode as findErrorCode13,
6200
- readManifest as readManifest8,
6602
+ readManifest as readManifest9,
6201
6603
  resolveRepositoryRoot as resolveRepositoryRoot11,
6202
6604
  writeStatus
6203
6605
  } from "@basou/core";
@@ -6217,7 +6619,7 @@ async function runStatus(options, ctx = {}) {
6217
6619
  async function doRunStatus(options, ctx) {
6218
6620
  const cwd = ctx.cwd ?? process.cwd();
6219
6621
  const repositoryRoot = await resolveRepositoryRootForStatus(cwd);
6220
- const paths = basouPaths16(repositoryRoot);
6622
+ const paths = basouPaths17(repositoryRoot);
6221
6623
  try {
6222
6624
  await assertBasouRootSafe14(paths.root);
6223
6625
  } catch (error) {
@@ -6228,7 +6630,7 @@ async function doRunStatus(options, ctx) {
6228
6630
  }
6229
6631
  let manifest;
6230
6632
  try {
6231
- manifest = await readManifest8(paths);
6633
+ manifest = await readManifest9(paths);
6232
6634
  } catch (error) {
6233
6635
  if (findErrorCode13(error, "ENOENT")) {
6234
6636
  throw new Error("Workspace not initialized. Run 'basou init' first.");
@@ -6266,12 +6668,12 @@ async function resolveRepositoryRootForStatus(cwd) {
6266
6668
  }
6267
6669
 
6268
6670
  // src/commands/task.ts
6269
- import { readFile as readFile4 } from "fs/promises";
6270
- import { join as join9 } from "path";
6671
+ import { readFile as readFile5 } from "fs/promises";
6672
+ import { join as join11 } from "path";
6271
6673
  import {
6272
6674
  archiveTask,
6273
6675
  assertBasouRootSafe as assertBasouRootSafe15,
6274
- basouPaths as basouPaths17,
6676
+ basouPaths as basouPaths18,
6275
6677
  createTaskWithEvent,
6276
6678
  deleteTask,
6277
6679
  editTask,
@@ -6280,7 +6682,7 @@ import {
6280
6682
  loadSessionEntries as loadSessionEntries2,
6281
6683
  loadTaskEntries,
6282
6684
  prefixedUlid as prefixedUlid5,
6283
- readManifest as readManifest9,
6685
+ readManifest as readManifest10,
6284
6686
  readTaskFile,
6285
6687
  readTaskFileWithArchiveFallback,
6286
6688
  reconcileAllTasks,
@@ -6373,7 +6775,7 @@ async function doRunTaskNew(options, ctx) {
6373
6775
  }
6374
6776
  const cwd = ctx.cwd ?? process.cwd();
6375
6777
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "new");
6376
- const paths = basouPaths17(repositoryRoot);
6778
+ const paths = basouPaths18(repositoryRoot);
6377
6779
  await assertWorkspaceInitialized12(paths.root);
6378
6780
  const description = options.description !== void 0 ? options.description : options.fromFile !== void 0 ? await readDescriptionFile(options.fromFile) : "";
6379
6781
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
@@ -6408,7 +6810,7 @@ async function doRunTaskNew(options, ctx) {
6408
6810
  });
6409
6811
  return;
6410
6812
  }
6411
- const manifest = await readManifest9(paths);
6813
+ const manifest = await readManifest10(paths);
6412
6814
  const result = await createTaskWithEvent({
6413
6815
  mode: "ad-hoc",
6414
6816
  paths,
@@ -6482,7 +6884,7 @@ async function runTaskList(options, ctx = {}) {
6482
6884
  async function doRunTaskList(options, ctx) {
6483
6885
  const cwd = ctx.cwd ?? process.cwd();
6484
6886
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "list");
6485
- const paths = basouPaths17(repositoryRoot);
6887
+ const paths = basouPaths18(repositoryRoot);
6486
6888
  await assertWorkspaceInitialized12(paths.root);
6487
6889
  const entries = await loadTaskEntries(paths, {
6488
6890
  onSkip: (id, reason) => printTaskSkip(id, reason)
@@ -6586,7 +6988,7 @@ async function runTaskShow(idInput, options, ctx = {}) {
6586
6988
  async function doRunTaskShow(idInput, options, ctx) {
6587
6989
  const cwd = ctx.cwd ?? process.cwd();
6588
6990
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "show");
6589
- const paths = basouPaths17(repositoryRoot);
6991
+ const paths = basouPaths18(repositoryRoot);
6590
6992
  await assertWorkspaceInitialized12(paths.root);
6591
6993
  const taskId = await resolveTaskId2(paths, idInput, { includeArchived: true });
6592
6994
  const { doc, archived } = await readTaskFileWithArchiveFallback(paths, taskId);
@@ -6594,7 +6996,7 @@ async function doRunTaskShow(idInput, options, ctx) {
6594
6996
  const events = [];
6595
6997
  const linkedSessionIds = new Set(doc.task.task.linked_sessions);
6596
6998
  for (const s of sessions) {
6597
- const sessionDir = join9(paths.sessions, s.sessionId);
6999
+ const sessionDir = join11(paths.sessions, s.sessionId);
6598
7000
  try {
6599
7001
  for await (const ev of replayEvents2(sessionDir, {
6600
7002
  onWarning: (w) => printReplayWarning(w, s.sessionId)
@@ -6730,7 +7132,7 @@ async function doRunTaskStatus(taskIdInput, newStatusInput, options, ctx) {
6730
7132
  const newStatus = parseTaskStatusPositional(newStatusInput);
6731
7133
  const cwd = ctx.cwd ?? process.cwd();
6732
7134
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "status");
6733
- const paths = basouPaths17(repositoryRoot);
7135
+ const paths = basouPaths18(repositoryRoot);
6734
7136
  await assertWorkspaceInitialized12(paths.root);
6735
7137
  const taskId = await resolveTaskId2(paths, taskIdInput);
6736
7138
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
@@ -6756,7 +7158,7 @@ async function doRunTaskStatus(taskIdInput, newStatusInput, options, ctx) {
6756
7158
  });
6757
7159
  return;
6758
7160
  }
6759
- const manifest = await readManifest9(paths);
7161
+ const manifest = await readManifest10(paths);
6760
7162
  const result = await updateTaskStatusWithEvent({
6761
7163
  mode: "ad-hoc",
6762
7164
  paths,
@@ -6807,9 +7209,9 @@ async function runTaskReconcile(options, ctx = {}) {
6807
7209
  async function doRunTaskReconcile(options, ctx) {
6808
7210
  const cwd = ctx.cwd ?? process.cwd();
6809
7211
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "reconcile");
6810
- const paths = basouPaths17(repositoryRoot);
7212
+ const paths = basouPaths18(repositoryRoot);
6811
7213
  await assertWorkspaceInitialized12(paths.root);
6812
- const manifest = await readManifest9(paths);
7214
+ const manifest = await readManifest10(paths);
6813
7215
  const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
6814
7216
  const write = options.write === true;
6815
7217
  const verbose = isVerbose(options);
@@ -6987,9 +7389,9 @@ async function doRunTaskRefreshLinkage(taskIdInput, options, ctx) {
6987
7389
  }
6988
7390
  const cwd = ctx.cwd ?? process.cwd();
6989
7391
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "refresh-linkage");
6990
- const paths = basouPaths17(repositoryRoot);
7392
+ const paths = basouPaths18(repositoryRoot);
6991
7393
  await assertWorkspaceInitialized12(paths.root);
6992
- const manifest = await readManifest9(paths);
7394
+ const manifest = await readManifest10(paths);
6993
7395
  const taskId = await resolveTaskId2(paths, taskIdInput);
6994
7396
  const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
6995
7397
  const write = options.write === true;
@@ -7067,9 +7469,9 @@ async function doRunTaskEdit(taskIdInput, options, ctx) {
7067
7469
  }
7068
7470
  const cwd = ctx.cwd ?? process.cwd();
7069
7471
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "edit");
7070
- const paths = basouPaths17(repositoryRoot);
7472
+ const paths = basouPaths18(repositoryRoot);
7071
7473
  await assertWorkspaceInitialized12(paths.root);
7072
- const manifest = await readManifest9(paths);
7474
+ const manifest = await readManifest10(paths);
7073
7475
  const taskId = await resolveTaskId2(paths, taskIdInput);
7074
7476
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
7075
7477
  const occurredAt = now.toISOString();
@@ -7123,9 +7525,9 @@ async function doRunTaskDelete(taskIdInput, options, ctx) {
7123
7525
  }
7124
7526
  const cwd = ctx.cwd ?? process.cwd();
7125
7527
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "delete");
7126
- const paths = basouPaths17(repositoryRoot);
7528
+ const paths = basouPaths18(repositoryRoot);
7127
7529
  await assertWorkspaceInitialized12(paths.root);
7128
- const manifest = await readManifest9(paths);
7530
+ const manifest = await readManifest10(paths);
7129
7531
  const taskId = await resolveTaskId2(paths, taskIdInput);
7130
7532
  if (options.yes !== true) {
7131
7533
  await confirmDestructiveAction("delete", taskId);
@@ -7168,9 +7570,9 @@ async function doRunTaskArchive(taskIdInput, options, ctx) {
7168
7570
  }
7169
7571
  const cwd = ctx.cwd ?? process.cwd();
7170
7572
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "archive");
7171
- const paths = basouPaths17(repositoryRoot);
7573
+ const paths = basouPaths18(repositoryRoot);
7172
7574
  await assertWorkspaceInitialized12(paths.root);
7173
- const manifest = await readManifest9(paths);
7575
+ const manifest = await readManifest10(paths);
7174
7576
  const taskId = await resolveTaskId2(paths, taskIdInput);
7175
7577
  if (options.yes !== true) {
7176
7578
  await confirmDestructiveAction("archive", taskId);
@@ -7282,7 +7684,7 @@ function parsePositiveInt2(raw) {
7282
7684
  }
7283
7685
  async function readDescriptionFile(path) {
7284
7686
  try {
7285
- return await readFile4(path, "utf8");
7687
+ return await readFile5(path, "utf8");
7286
7688
  } catch (error) {
7287
7689
  if (findErrorCode14(error, "ENOENT")) {
7288
7690
  throw new Error("Description source not found", { cause: error });
@@ -7399,7 +7801,7 @@ function maxLen3(values, floor) {
7399
7801
  // src/commands/verify.ts
7400
7802
  import {
7401
7803
  assertBasouRootSafe as assertBasouRootSafe16,
7402
- basouPaths as basouPaths18,
7804
+ basouPaths as basouPaths19,
7403
7805
  enumerateSessionDirs as enumerateSessionDirs3,
7404
7806
  findErrorCode as findErrorCode15,
7405
7807
  resolveRepositoryRoot as resolveRepositoryRoot13,
@@ -7425,7 +7827,7 @@ async function doRunVerify(options, ctx) {
7425
7827
  }
7426
7828
  const cwd = ctx.cwd ?? process.cwd();
7427
7829
  const repositoryRoot = await resolveRepositoryRootForVerify(cwd);
7428
- const paths = basouPaths18(repositoryRoot);
7830
+ const paths = basouPaths19(repositoryRoot);
7429
7831
  await assertWorkspaceInitialized13(paths.root);
7430
7832
  const sessionIds = options.session !== void 0 ? [await resolveSessionId5(paths, options.session)] : await enumerateSessionDirs3(paths);
7431
7833
  const rows = [];
@@ -7497,36 +7899,36 @@ async function assertWorkspaceInitialized13(basouRoot) {
7497
7899
  // src/commands/view.ts
7498
7900
  import { spawn } from "child_process";
7499
7901
  import { createHash } from "crypto";
7500
- import { basename as basename5, resolve as resolve8 } from "path";
7902
+ import { basename as basename7, resolve as resolve10 } from "path";
7501
7903
  import {
7502
7904
  assertBasouRootSafe as assertBasouRootSafe17,
7503
- basouPaths as basouPaths19,
7905
+ basouPaths as basouPaths20,
7504
7906
  findErrorCode as findErrorCode17,
7505
- readManifest as readManifest12,
7907
+ readManifest as readManifest13,
7506
7908
  resolveRepositoryRoot as resolveRepositoryRoot14
7507
7909
  } from "@basou/core";
7508
7910
  import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
7509
7911
 
7510
7912
  // src/lib/portfolio-safety.ts
7511
7913
  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";
7914
+ import { lstat as lstat2, realpath as realpath2 } from "fs/promises";
7915
+ import { isAbsolute as isAbsolute6, join as join12, relative as relative4, resolve as resolve9 } from "path";
7514
7916
  import { promisify } from "util";
7515
- import { readManifest as readManifest10 } from "@basou/core";
7917
+ import { readManifest as readManifest11 } from "@basou/core";
7516
7918
  var execFileAsync = promisify(execFile);
7517
7919
  function errorCode(error) {
7518
7920
  return error instanceof Error ? error.code : void 0;
7519
7921
  }
7520
7922
  async function canonical(p) {
7521
7923
  try {
7522
- return await realpath(p);
7924
+ return await realpath2(p);
7523
7925
  } catch {
7524
- return resolve7(p);
7926
+ return resolve9(p);
7525
7927
  }
7526
7928
  }
7527
7929
  function isInside(child, parent) {
7528
7930
  const rel = relative4(parent, child);
7529
- return rel === "" || !rel.startsWith("..") && !isAbsolute5(rel);
7931
+ return rel === "" || !rel.startsWith("..") && !isAbsolute6(rel);
7530
7932
  }
7531
7933
  function isBasouPath(p) {
7532
7934
  return p === ".basou" || p.startsWith(".basou/") || p.includes("/.basou/") || p.endsWith("/.basou");
@@ -7534,7 +7936,7 @@ function isBasouPath(p) {
7534
7936
  async function inspectRepo(repoPath) {
7535
7937
  let hasEntry = false;
7536
7938
  try {
7537
- await lstat(join10(repoPath, ".basou"));
7939
+ await lstat2(join12(repoPath, ".basou"));
7538
7940
  hasEntry = true;
7539
7941
  } catch (error) {
7540
7942
  if (errorCode(error) !== "ENOENT") {
@@ -7565,7 +7967,7 @@ async function checkPortfolioSafety(workspaces) {
7565
7967
  const wsReal = await canonical(ws.repoRoot);
7566
7968
  let sourceRoots = [];
7567
7969
  try {
7568
- const manifest = await readManifest10(ws.paths);
7970
+ const manifest = await readManifest11(ws.paths);
7569
7971
  sourceRoots = manifest.import?.source_roots ?? [];
7570
7972
  } catch (error) {
7571
7973
  if (error instanceof Error && error.message === "YAML file not found") {
@@ -7583,7 +7985,7 @@ async function checkPortfolioSafety(workspaces) {
7583
7985
  }
7584
7986
  const monitored = /* @__PURE__ */ new Map();
7585
7987
  for (const root of sourceRoots) {
7586
- const display = resolve7(ws.repoRoot, root);
7988
+ const display = resolve9(ws.repoRoot, root);
7587
7989
  const real = await canonical(display);
7588
7990
  if (real !== wsReal) monitored.set(real, display);
7589
7991
  }
@@ -7635,7 +8037,7 @@ function formatSafetyReport(result) {
7635
8037
 
7636
8038
  // src/lib/view-server.ts
7637
8039
  import { createServer } from "http";
7638
- import { join as join11 } from "path";
8040
+ import { join as join13 } from "path";
7639
8041
  import {
7640
8042
  computeWorkStats as computeWorkStats2,
7641
8043
  enumerateApprovals as enumerateApprovals2,
@@ -7645,8 +8047,8 @@ import {
7645
8047
  loadSessionEntries as loadSessionEntries3,
7646
8048
  loadTaskEntries as loadTaskEntries2,
7647
8049
  readAllEvents as readAllEvents2,
7648
- readManifest as readManifest11,
7649
- readMarkdownFile as readMarkdownFile5,
8050
+ readManifest as readManifest12,
8051
+ readMarkdownFile as readMarkdownFile6,
7650
8052
  readSessionYaml as readSessionYaml3,
7651
8053
  readTaskFile as readTaskFile2,
7652
8054
  renderDecisions as renderDecisions3,
@@ -8292,7 +8694,7 @@ function startViewServer(opts) {
8292
8694
  };
8293
8695
  let boundPort = port;
8294
8696
  const getPort = () => boundPort;
8295
- return new Promise((resolve9, reject) => {
8697
+ return new Promise((resolve11, reject) => {
8296
8698
  const server = createServer((req, res) => {
8297
8699
  handleRequest(req, res, deps, getPort, runExclusive).catch((error) => {
8298
8700
  sendError(res, error instanceof HttpError ? error.status : 500, pathlessMessage(error));
@@ -8303,7 +8705,7 @@ function startViewServer(opts) {
8303
8705
  const address = server.address();
8304
8706
  boundPort = isAddressInfo(address) ? address.port : port;
8305
8707
  server.off("error", reject);
8306
- resolve9({
8708
+ resolve11({
8307
8709
  url: `http://${host}:${boundPort}`,
8308
8710
  port: boundPort,
8309
8711
  close: () => closeServer(server)
@@ -8315,8 +8717,8 @@ function isAddressInfo(value) {
8315
8717
  return value !== null && typeof value === "object";
8316
8718
  }
8317
8719
  function closeServer(server) {
8318
- return new Promise((resolve9) => {
8319
- server.close(() => resolve9());
8720
+ return new Promise((resolve11) => {
8721
+ server.close(() => resolve11());
8320
8722
  server.closeAllConnections();
8321
8723
  });
8322
8724
  }
@@ -8533,7 +8935,7 @@ async function captureStaleness(ws, nowIso) {
8533
8935
  async function overview(ws, nowProvider) {
8534
8936
  let manifest;
8535
8937
  try {
8536
- manifest = await readManifest11(ws.paths);
8938
+ manifest = await readManifest12(ws.paths);
8537
8939
  } catch (error) {
8538
8940
  if (findErrorCode16(error, "ENOENT")) {
8539
8941
  return { initialized: false, repoRoot: ws.repoRoot };
@@ -8590,7 +8992,7 @@ async function sessionDetail(ws, sessionId) {
8590
8992
  throw error;
8591
8993
  }
8592
8994
  try {
8593
- const events = await readAllEvents2(join11(ws.paths.sessions, sessionId));
8995
+ const events = await readAllEvents2(join13(ws.paths.sessions, sessionId));
8594
8996
  return { session, events };
8595
8997
  } catch {
8596
8998
  return { session, events: [], degraded: true };
@@ -8612,7 +9014,7 @@ async function taskDetail(ws, taskId) {
8612
9014
  }
8613
9015
  }
8614
9016
  async function decisionsView(ws, nowProvider) {
8615
- const fromDisk = await readMarkdownFile5(ws.paths.files.decisions);
9017
+ const fromDisk = await readMarkdownFile6(ws.paths.files.decisions);
8616
9018
  if (fromDisk !== null) {
8617
9019
  return { body: fromDisk, fromDisk: true };
8618
9020
  }
@@ -8635,7 +9037,7 @@ async function approvalsView(ws, nowProvider) {
8635
9037
  return { pending: await toViews(ids.pending), resolved: await toViews(ids.resolved) };
8636
9038
  }
8637
9039
  async function handoffView(ws, nowProvider) {
8638
- const fromDisk = await readMarkdownFile5(ws.paths.files.handoff);
9040
+ const fromDisk = await readMarkdownFile6(ws.paths.files.handoff);
8639
9041
  if (fromDisk !== null) {
8640
9042
  return { body: fromDisk, fromDisk: true };
8641
9043
  }
@@ -8804,18 +9206,18 @@ async function doRunView(options, ctx) {
8804
9206
  }
8805
9207
  async function buildSingleDeps(ctx, cwd) {
8806
9208
  const repositoryRoot = await resolveRepositoryRootForView(cwd);
8807
- const paths = basouPaths19(repositoryRoot);
9209
+ const paths = basouPaths20(repositoryRoot);
8808
9210
  await assertWorkspaceInitialized14(paths.root);
8809
9211
  const entry = await buildWorkspaceEntry(repositoryRoot, ctx);
8810
9212
  return { workspaces: [entry], mode: "single", nowProvider: nowProviderOf(ctx) };
8811
9213
  }
8812
9214
  async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
8813
- const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve8(cwd, p) })) : await loadPortfolioConfig(ctx.portfolioConfigPath);
9215
+ const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve10(cwd, p) })) : await loadPortfolioConfig(ctx.portfolioConfigPath);
8814
9216
  const entries = [];
8815
9217
  const seenPath = /* @__PURE__ */ new Set();
8816
9218
  const seenKey = /* @__PURE__ */ new Set();
8817
9219
  for (const spec of specs) {
8818
- const repoRoot = resolve8(spec.path);
9220
+ const repoRoot = resolve10(spec.path);
8819
9221
  if (seenPath.has(repoRoot)) continue;
8820
9222
  seenPath.add(repoRoot);
8821
9223
  const entry = await buildWorkspaceEntry(repoRoot, ctx, spec.label);
@@ -8828,14 +9230,14 @@ async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
8828
9230
  return { workspaces: entries, mode: "portfolio", nowProvider: nowProviderOf(ctx) };
8829
9231
  }
8830
9232
  async function buildWorkspaceEntry(repoRoot, ctx, labelOverride) {
8831
- const paths = basouPaths19(repoRoot);
9233
+ const paths = basouPaths20(repoRoot);
8832
9234
  const importCtx = {
8833
9235
  cwd: repoRoot,
8834
9236
  ...ctx.claudeProjectsDir !== void 0 ? { claudeProjectsDir: ctx.claudeProjectsDir } : {},
8835
9237
  ...ctx.codexSessionsDir !== void 0 ? { codexSessionsDir: ctx.codexSessionsDir } : {}
8836
9238
  };
8837
9239
  try {
8838
- const manifest = await readManifest12(paths);
9240
+ const manifest = await readManifest13(paths);
8839
9241
  return {
8840
9242
  key: manifest.workspace.id,
8841
9243
  label: labelOverride ?? manifest.workspace.name,
@@ -8848,7 +9250,7 @@ async function buildWorkspaceEntry(repoRoot, ctx, labelOverride) {
8848
9250
  const notFound = error instanceof Error && error.message === "YAML file not found";
8849
9251
  return {
8850
9252
  key: `ws-${createHash("sha1").update(repoRoot).digest("hex").slice(0, 12)}`,
8851
- label: labelOverride ?? basename5(repoRoot),
9253
+ label: labelOverride ?? basename7(repoRoot),
8852
9254
  paths,
8853
9255
  repoRoot,
8854
9256
  importCtx,
@@ -8887,7 +9289,7 @@ function openInBrowser(url, override) {
8887
9289
  }
8888
9290
  }
8889
9291
  function waitForShutdown(signal) {
8890
- return new Promise((resolve9) => {
9292
+ return new Promise((resolve11) => {
8891
9293
  const cleanup = () => {
8892
9294
  process.off("SIGINT", onSignal);
8893
9295
  process.off("SIGTERM", onSignal);
@@ -8895,18 +9297,18 @@ function waitForShutdown(signal) {
8895
9297
  };
8896
9298
  const onSignal = () => {
8897
9299
  cleanup();
8898
- resolve9();
9300
+ resolve11();
8899
9301
  };
8900
9302
  const onAbort = () => {
8901
9303
  cleanup();
8902
- resolve9();
9304
+ resolve11();
8903
9305
  };
8904
9306
  process.on("SIGINT", onSignal);
8905
9307
  process.on("SIGTERM", onSignal);
8906
9308
  if (signal !== void 0) {
8907
9309
  if (signal.aborted) {
8908
9310
  cleanup();
8909
- resolve9();
9311
+ resolve11();
8910
9312
  return;
8911
9313
  }
8912
9314
  signal.addEventListener("abort", onAbort);
@@ -8963,6 +9365,7 @@ function buildProgram() {
8963
9365
  registerOrientCommand(program2);
8964
9366
  registerReviewGapsCommand(program2);
8965
9367
  registerProjectCommand(program2);
9368
+ registerProtocolCommand(program2);
8966
9369
  return program2;
8967
9370
  }
8968
9371