@basou/cli 0.66.0 → 0.68.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/program.js CHANGED
@@ -828,32 +828,190 @@ function printNoApprovals(options) {
828
828
  }
829
829
 
830
830
  // src/commands/board.ts
831
- import { lstat, readFile as readFile2, realpath as realpath2 } from "fs/promises";
832
- import { basename as basename3, dirname as dirname3, join as join3, relative, resolve as resolve3, sep } from "path";
831
+ import { lstat, readdir as readdir2, readFile as readFile2, realpath as realpath3 } from "fs/promises";
832
+ import { basename as basename4, dirname as dirname5, join as join4, relative, resolve as resolve3, sep as sep2 } from "path";
833
833
  import {
834
834
  assertBasouRootSafe as assertBasouRootSafe3,
835
+ BOARD_INIT_SAVE,
835
836
  basouPaths as basouPaths4,
837
+ boardGuide,
838
+ boardInitText,
836
839
  buildRecord,
837
840
  byCodePoint,
838
841
  diffCells,
839
842
  diffObserved,
840
843
  displayPath as displayPath3,
844
+ effortStartOf,
841
845
  findErrorCode as findErrorCode3,
846
+ isBoardTimeZone,
842
847
  measureBoard,
848
+ measureBoardLive,
843
849
  NO_PREVIOUS_RECORDS,
844
850
  parseBoardDeclaration,
845
851
  parseRecordInput,
846
- readManifest as readManifest3,
852
+ readManifest as readManifest4,
847
853
  readPreviousRecords,
854
+ recordIds,
855
+ resolveViewLanguage,
856
+ shellWord,
848
857
  writeRecord
849
858
  } from "@basou/core";
850
859
 
860
+ // src/lib/foreign-workspace-warn.ts
861
+ import { realpath as realpath2 } from "fs/promises";
862
+ import { dirname } from "path";
863
+ import {
864
+ isPositionBoardLine,
865
+ loadPortfolioConfig as loadPortfolioConfig2,
866
+ PortfolioConfigMissingError,
867
+ readMarkdownFile
868
+ } from "@basou/core";
869
+
870
+ // src/lib/foreign-workspace-scan.ts
871
+ import { homedir as osHomedir } from "os";
872
+ import { basename as basename2, normalize, sep } from "path";
873
+ var MIN_TOKEN_LENGTH = 4;
874
+ function stripTrailingSep(p) {
875
+ return p.length > 1 && p.endsWith(sep) ? p.slice(0, -sep.length) : p;
876
+ }
877
+ function toTildePath(absPath, home) {
878
+ if (absPath === home) return "~";
879
+ if (absPath.startsWith(home + sep)) return `~${absPath.slice(home.length)}`;
880
+ return null;
881
+ }
882
+ function encodedSpelling(name) {
883
+ return name.replace(/[^a-zA-Z0-9]/g, "-");
884
+ }
885
+ function directoryNameSpellings(dir) {
886
+ const paired = /^(.+)-(planning|workspace)$/.exec(dir);
887
+ const plain = paired !== null ? [`${paired[1]}-planning`, `${paired[1]}-workspace`] : [dir, `${dir}-workspace`];
888
+ return [...plain, ...plain.map(encodedSpelling)];
889
+ }
890
+ function nameTokensFor(workspacePath, home) {
891
+ const abs = stripTrailingSep(normalize(workspacePath));
892
+ const tokens = /* @__PURE__ */ new Set([abs]);
893
+ const tilde = toTildePath(abs, home);
894
+ if (tilde !== null) tokens.add(tilde);
895
+ for (const name of directoryNameSpellings(basename2(abs))) tokens.add(name);
896
+ return [...tokens].filter((t) => t.length >= MIN_TOKEN_LENGTH);
897
+ }
898
+ function isSpellingOfSelf(workspacePath, selfPath) {
899
+ const own = new Set(directoryNameSpellings(basename2(stripTrailingSep(normalize(selfPath)))));
900
+ return directoryNameSpellings(basename2(stripTrailingSep(normalize(workspacePath)))).some(
901
+ (s) => own.has(s)
902
+ );
903
+ }
904
+ function scanForeignWorkspaceNames(input) {
905
+ if (input.text.length === 0 || input.workspacePaths.length === 0) return [];
906
+ const home = input.homedir ?? osHomedir();
907
+ const own = input.selfPath === void 0 ? [] : nameTokensFor(input.selfPath, home);
908
+ const lines = input.text.split("\n");
909
+ const hits = [];
910
+ for (const workspacePath of input.workspacePaths) {
911
+ if (input.selfPath !== void 0 && isSpellingOfSelf(workspacePath, input.selfPath)) continue;
912
+ const tokens = nameTokensFor(workspacePath, home).filter(
913
+ (token) => !own.some((ownToken) => ownToken.includes(token))
914
+ );
915
+ if (tokens.length === 0) continue;
916
+ const matched = /* @__PURE__ */ new Set();
917
+ const matchedLines = [];
918
+ for (const [index, line] of lines.entries()) {
919
+ let lineMatched = false;
920
+ for (const token of tokens) {
921
+ if (line.includes(token)) {
922
+ matched.add(token);
923
+ lineMatched = true;
924
+ }
925
+ }
926
+ if (lineMatched) matchedLines.push(index + 1);
927
+ }
928
+ if (matchedLines.length === 0) continue;
929
+ hits.push({
930
+ workspacePath,
931
+ tokens: [...matched].sort((a, b) => b.length - a.length || a.localeCompare(b)),
932
+ lines: matchedLines
933
+ });
934
+ }
935
+ return hits;
936
+ }
937
+
938
+ // src/lib/foreign-workspace-warn.ts
939
+ var MAX_LISTED_LINES = 5;
940
+ async function canonicalize(path) {
941
+ try {
942
+ return await realpath2(path);
943
+ } catch {
944
+ return path;
945
+ }
946
+ }
947
+ async function findForeignWorkspaceNames(args) {
948
+ let workspacePaths;
949
+ let selfPath;
950
+ try {
951
+ const workspaces = await loadPortfolioConfig2(args.configPath);
952
+ workspacePaths = await Promise.all(workspaces.map((w) => canonicalize(w.path)));
953
+ selfPath = args.selfPath === void 0 ? void 0 : await canonicalize(args.selfPath);
954
+ } catch {
955
+ return null;
956
+ }
957
+ const text = args.text.split("\n").map((line) => isPositionBoardLine(line) ? "" : line).join("\n");
958
+ const hits = scanForeignWorkspaceNames({ text, workspacePaths, selfPath });
959
+ if (hits.length === 0) return null;
960
+ const lines = [...new Set(hits.flatMap((h) => h.lines))].sort((a, b) => a - b);
961
+ return { workspaceCount: hits.length, lines };
962
+ }
963
+ async function findForeignWorkspaceFields(args) {
964
+ let workspacePaths;
965
+ let selfPath;
966
+ try {
967
+ const workspaces = await loadPortfolioConfig2(args.configPath);
968
+ workspacePaths = await Promise.all(workspaces.map((w) => canonicalize(w.path)));
969
+ selfPath = await canonicalize(args.selfPath);
970
+ } catch (error) {
971
+ return error instanceof PortfolioConfigMissingError ? { status: "no_registry" } : { status: "unreadable" };
972
+ }
973
+ const at = args.fields.filter(
974
+ (field) => scanForeignWorkspaceNames({ text: field.text, workspacePaths, selfPath }).length > 0
975
+ ).map((field) => field.at);
976
+ return { status: "checked", at };
977
+ }
978
+ function describeForeignWorkspaceLines(lines) {
979
+ const listed = lines.slice(0, MAX_LISTED_LINES).join(", ");
980
+ const rest = lines.length - MAX_LISTED_LINES;
981
+ const numbers = rest > 0 ? `${listed} and ${rest} more` : listed;
982
+ return `${lines.length === 1 ? "line" : "lines"} ${numbers}`;
983
+ }
984
+ function positionForeignWorkspaceWarning(report, where) {
985
+ const subject = report.workspaceCount === 1 ? "names another registered workspace" : `names ${report.workspaceCount} other registered workspaces`;
986
+ return `basou: this workspace's position ${subject} (${where}, ${describeForeignWorkspaceLines(report.lines)}). The position is handed to the agent session running in this workspace, so those names travel with it. Advisory only: nothing was withheld.`;
987
+ }
988
+ function protocolForeignWorkspaceWarning(report) {
989
+ const subject = report.workspaceCount === 1 ? "names a registered workspace" : `names ${report.workspaceCount} registered workspaces`;
990
+ return `basou: the protocol block ${subject} (block ${describeForeignWorkspaceLines(report.lines)}, counted from the block's first line, not the file's). Standing protocols are written to the user-global CLAUDE.md, which every project on this machine loads, so a workspace-specific name there reaches every workspace's sessions. Only the basou-managed block is checked; the rest of that file is not basou's to inspect. Advisory only: nothing was withheld.`;
991
+ }
992
+ async function warnIfPositionNamesOtherWorkspaces(args) {
993
+ try {
994
+ const body = await readMarkdownFile(args.paths.files.orientation);
995
+ if (body === null) return;
996
+ const report = await findForeignWorkspaceNames({
997
+ text: body,
998
+ selfPath: dirname(args.paths.root),
999
+ configPath: args.configPath
1000
+ });
1001
+ if (report !== null) {
1002
+ console.error(positionForeignWorkspaceWarning(report, args.paths.files.orientation));
1003
+ }
1004
+ } catch {
1005
+ }
1006
+ }
1007
+
851
1008
  // src/lib/provenance-actions.ts
1009
+ import { dirname as dirname4 } from "path";
852
1010
  import {
853
1011
  assertApprovalStoreSafe,
854
1012
  assertLockStoreSafe as assertLockStoreSafe3,
855
1013
  assertTaskStoreSafe,
856
- readMarkdownFile,
1014
+ readMarkdownFile as readMarkdownFile2,
857
1015
  renderDecisions,
858
1016
  renderHandoff,
859
1017
  renderOrientation,
@@ -865,7 +1023,7 @@ import {
865
1023
  import { createReadStream } from "fs";
866
1024
  import { readdir, readFile, rm, stat as stat2 } from "fs/promises";
867
1025
  import { homedir } from "os";
868
- import { basename as basename2, dirname as dirname2, join as join2, resolve as resolve2 } from "path";
1026
+ import { basename as basename3, dirname as dirname3, join as join2, resolve as resolve2 } from "path";
869
1027
  import { createInterface } from "readline";
870
1028
  import {
871
1029
  AGENT_INFRA_DIRS,
@@ -892,7 +1050,7 @@ import {
892
1050
  } from "@basou/core";
893
1051
 
894
1052
  // src/lib/observation-warn.ts
895
- import { dirname } from "path";
1053
+ import { dirname as dirname2 } from "path";
896
1054
  import { assertObservationsDirSafe, displayPath } from "@basou/core";
897
1055
  async function warnIfObservationsRefused(paths) {
898
1056
  try {
@@ -900,7 +1058,7 @@ async function warnIfObservationsRefused(paths) {
900
1058
  } catch (error) {
901
1059
  const reason = error instanceof Error ? error.message : String(error);
902
1060
  console.error(
903
- `basou: ${reason} (in ${displayPath(dirname(paths.root))}). The files a session changes through the shell are not observed there, so a session imported now records only the files its transcript names.`
1061
+ `basou: ${reason} (in ${displayPath(dirname2(paths.root))}). The files a session changes through the shell are not observed there, so a session imported now records only the files its transcript names.`
904
1062
  );
905
1063
  }
906
1064
  }
@@ -977,7 +1135,7 @@ async function doRunImportClaudeCode(options, ctx) {
977
1135
  const files = await selectTranscriptFiles(projectsRoot, projectPaths, options);
978
1136
  const projectSet = new Set(projectPaths);
979
1137
  const candidates = files.map((file) => {
980
- const externalId = basename2(file, ".jsonl");
1138
+ const externalId = basename3(file, ".jsonl");
981
1139
  return {
982
1140
  externalId,
983
1141
  sourcePath: file,
@@ -1076,7 +1234,7 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
1076
1234
  files: payload.session.related_files ?? [],
1077
1235
  workingDirectory: payload.session.working_directory,
1078
1236
  sourceRoots: projectPaths,
1079
- masterRoot: dirname2(paths.root),
1237
+ masterRoot: dirname3(paths.root),
1080
1238
  extraInRoot: AGENT_INFRA_DIRS
1081
1239
  });
1082
1240
  if (scope.outOfRoot.length > 0) crossProject.push({ externalId, outOfRoot: scope.outOfRoot });
@@ -1514,8 +1672,27 @@ async function assertWorkspaceInitialized2(basouRoot) {
1514
1672
  }
1515
1673
  }
1516
1674
 
1675
+ // src/lib/board-glance.ts
1676
+ import { join as join3 } from "path";
1677
+ import { glanceBoard, readManifest as readManifest3 } from "@basou/core";
1678
+ async function glanceDefaultBoard(root, paths) {
1679
+ try {
1680
+ const own = (await readManifest3(paths)).repos?.find((repo) => repo.path === ".");
1681
+ if (own?.visibility !== "private") return null;
1682
+ } catch {
1683
+ return null;
1684
+ }
1685
+ return glanceBoard(join3(root, DEFAULT_BOARD_PATH));
1686
+ }
1687
+
1517
1688
  // src/lib/provenance-actions.ts
1518
- async function captureImportJson(fn) {
1689
+ var capturing = Promise.resolve();
1690
+ function captureImportJson(fn) {
1691
+ const turn = capturing.then(() => captureNow(fn));
1692
+ capturing = turn.catch(() => void 0);
1693
+ return turn;
1694
+ }
1695
+ async function captureNow(fn) {
1519
1696
  const stdout = [];
1520
1697
  const originalLog = console.log;
1521
1698
  const originalError = console.error;
@@ -1592,7 +1769,7 @@ function importCodex(options, ctx) {
1592
1769
  }
1593
1770
  async function regenerateHandoff(paths, nowIso, callbacks) {
1594
1771
  const result = await renderHandoff({ paths, nowIso, ...callbacks });
1595
- const existing = await readMarkdownFile(paths.files.handoff);
1772
+ const existing = await readMarkdownFile2(paths.files.handoff);
1596
1773
  await writeMarkdownFile(
1597
1774
  paths.files.handoff,
1598
1775
  renderWithMarkers(existing, result.body, "handoff.md")
@@ -1606,7 +1783,7 @@ async function regenerateHandoff(paths, nowIso, callbacks) {
1606
1783
  }
1607
1784
  async function regenerateDecisions(paths, nowIso, callbacks) {
1608
1785
  const result = await renderDecisions({ paths, nowIso, ...callbacks });
1609
- const existing = await readMarkdownFile(paths.files.decisions);
1786
+ const existing = await readMarkdownFile2(paths.files.decisions);
1610
1787
  await writeMarkdownFile(
1611
1788
  paths.files.decisions,
1612
1789
  renderWithMarkers(existing, result.body, "decisions.md")
@@ -1614,7 +1791,12 @@ async function regenerateDecisions(paths, nowIso, callbacks) {
1614
1791
  return { decisionCount: result.decisionCount };
1615
1792
  }
1616
1793
  async function regenerateOrientation(paths, nowIso, callbacks) {
1617
- const result = await renderOrientation({ paths, nowIso, ...callbacks });
1794
+ const result = await renderOrientation({
1795
+ paths,
1796
+ nowIso,
1797
+ board: await glanceDefaultBoard(dirname4(paths.root), paths),
1798
+ ...callbacks
1799
+ });
1618
1800
  await writeMarkdownFile(paths.files.orientation, `${result.body}
1619
1801
  `);
1620
1802
  return {
@@ -1732,8 +1914,9 @@ read (nothing is printed on stdout).`
1732
1914
  "after",
1733
1915
  `
1734
1916
  The input is a JSON object: measure_digest (the digest of the measurement the
1735
- judgement saw), observed, cells (every lane at every stage), prose, judged_by
1736
- and axis_review. The board is measured again, and nothing is written when the
1917
+ judgement saw), observed (exactly the observations the board declares, when it
1918
+ declares any), cells (every lane at every stage), prose, judged_by and
1919
+ axis_review. The board is measured again, and nothing is written when the
1737
1920
  digest differs. Records are never written under a .basou/ directory. Exit
1738
1921
  codes: 0 when the record was written, or with --dry-run when everything
1739
1922
  checked out (nothing is written then); 1 when it was refused (nothing is
@@ -1741,6 +1924,191 @@ written; the reasons are on stderr).`
1741
1924
  ).action(async (options) => {
1742
1925
  await runBoardRecord(options);
1743
1926
  });
1927
+ board.command("guide").description(
1928
+ "Print the steps an agent follows to judge the board and record it (writes nothing, sends nothing)"
1929
+ ).option("-v, --verbose", "Show error causes").addHelpText(
1930
+ "after",
1931
+ `
1932
+ The steps are filled in with this workspace's board (${DEFAULT_BOARD_PATH},
1933
+ read only when the manifest declares the workspace's own repo private): where
1934
+ it is and its last record, the commands that measure, observe, record and show
1935
+ it, what each stage is judged by, and the record's input to fill in, with the
1936
+ previous record's state of each cell beside it. With no board declared yet, it
1937
+ says how to declare one. The text is for an agent to read; its shape may change
1938
+ at any release. Exit codes: 0 when the steps were printed; 1 when the workspace,
1939
+ its manifest or its board.yaml cannot be read, or its own repo is not declared
1940
+ private (the reason is on stderr).`
1941
+ ).action(async (options) => {
1942
+ await runBoardGuide(options);
1943
+ });
1944
+ board.command("init").description("Print a board.yaml to start a board from (writes nothing)").option("-v, --verbose", "Show error causes").addHelpText(
1945
+ "after",
1946
+ `
1947
+ It prints a declaration of the newest board_version, in the language the
1948
+ manifest declares for the workspace's own repo: a title from the workspace's
1949
+ name, the six stages with a meaning each, one lane to cut again, nothing to
1950
+ observe, no component registered, an axis of version 1, and the effort from
1951
+ the day of the first session, in this host's time zone. It writes nothing:
1952
+ save it, from the workspace's own repo, only where no board is, through a file
1953
+ of its own:
1954
+
1955
+ ${INIT_SAVE}
1956
+
1957
+ Never print it straight into ${DEFAULT_BOARD_PATH}: the shell empties that file
1958
+ before init runs, so a board already there would be lost.
1959
+
1960
+ Exit codes: 0 when it was printed; 1 when the manifest does not declare the
1961
+ workspace's own repo private (a board's records hold what the trail holds),
1962
+ when ${DEFAULT_BOARD_PATH} is already there, or when the workspace cannot be
1963
+ read (the reason is on stderr, and nothing is printed on stdout).`
1964
+ ).action(async (options) => {
1965
+ await runBoardInit(options);
1966
+ });
1967
+ }
1968
+ var INIT_SAVE = BOARD_INIT_SAVE;
1969
+ async function runBoardInit(options, ctx = {}) {
1970
+ try {
1971
+ await doRunBoardInit(options, ctx);
1972
+ } catch (error) {
1973
+ renderCliError(error, { verbose: isVerbose(options) });
1974
+ process.exitCode = 1;
1975
+ }
1976
+ }
1977
+ async function doRunBoardInit(_options, ctx) {
1978
+ const { root, paths, manifest } = await privateWorkspace(ctx, "board init");
1979
+ const cwd = ctx.cwd ?? process.cwd();
1980
+ if (await realpath3(cwd).catch(() => cwd) !== await realpath3(root).catch(() => root)) {
1981
+ throw new Error(
1982
+ `Run basou board init from the top of the workspace's own repo, where its .basou/ is: the way to save what it prints writes ${DEFAULT_BOARD_PATH} in the current directory.`
1983
+ );
1984
+ }
1985
+ const board = join4(root, DEFAULT_BOARD_PATH);
1986
+ try {
1987
+ await lstat(board);
1988
+ throw new Error(
1989
+ `${DEFAULT_BOARD_PATH} is already there, so there is no board to start: change that one (through an axis review when it changes the lanes or the stages).`
1990
+ );
1991
+ } catch (error) {
1992
+ if (!findErrorCode3(error, "ENOENT")) throw error;
1993
+ }
1994
+ const { start, timeZone, sessionsRead } = await effortStartOf(
1995
+ paths,
1996
+ ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()
1997
+ );
1998
+ if (timeZone === void 0) {
1999
+ console.error(
2000
+ "basou: this host's time zone has no name, so effort.start is today in UTC and effort.time_zone is left out; write both."
2001
+ );
2002
+ } else if (!sessionsRead) {
2003
+ console.error(
2004
+ "basou: the sessions could not be read, so effort.start is today; set it to the day the work began."
2005
+ );
2006
+ } else if (!isBoardTimeZone(timeZone)) {
2007
+ console.error(
2008
+ "basou: this host's time zone has no name a board can declare, so effort.time_zone is left out; write one."
2009
+ );
2010
+ }
2011
+ const text = boardInitText({
2012
+ name: manifest.workspace.name,
2013
+ language: resolveViewLanguage(manifest),
2014
+ start,
2015
+ startIsFirstSession: timeZone !== void 0,
2016
+ // An offset such as +00:00, which some hosts name their zone, is not
2017
+ // one a board can declare.
2018
+ timeZone: timeZone !== void 0 && isBoardTimeZone(timeZone) ? timeZone : void 0
2019
+ });
2020
+ process.stdout.write(text);
2021
+ return text;
2022
+ }
2023
+ async function privateWorkspace(ctx, command) {
2024
+ const cwd = ctx.cwd ?? process.cwd();
2025
+ const root = await resolveBasouRootForCommand(
2026
+ cwd,
2027
+ command,
2028
+ ctx.portfolioConfigPath === void 0 ? {} : { portfolioConfigPath: ctx.portfolioConfigPath }
2029
+ );
2030
+ const paths = basouPaths4(root);
2031
+ await assertWorkspaceInitialized3(paths.root);
2032
+ const manifest = await readManifest4(paths);
2033
+ const own = (manifest.repos ?? []).find((repo) => repo.path === ".");
2034
+ if (own?.visibility !== "private") {
2035
+ throw new Error(
2036
+ `The board is kept at ${DEFAULT_BOARD_PATH} only in a workspace whose manifest declares its own repo (path: .) private, since a record holds what the trail holds (open tracks, time worked, model names). This one does not, so it has no judged board; basou view's /board page shows what is measured without one.`
2037
+ );
2038
+ }
2039
+ return { root, paths, manifest };
2040
+ }
2041
+ async function runBoardGuide(options, ctx = {}) {
2042
+ try {
2043
+ await doRunBoardGuide(options, ctx);
2044
+ } catch (error) {
2045
+ renderCliError(error, { verbose: isVerbose(options) });
2046
+ process.exitCode = 1;
2047
+ }
2048
+ }
2049
+ async function doRunBoardGuide(_options, ctx) {
2050
+ const { root, manifest } = await privateWorkspace(ctx, "board guide");
2051
+ const board = {
2052
+ path: join4(root, DEFAULT_BOARD_PATH),
2053
+ shown: DEFAULT_BOARD_PATH,
2054
+ given: false
2055
+ };
2056
+ let text;
2057
+ try {
2058
+ text = await readDeclaration(board);
2059
+ } catch (error) {
2060
+ if (!findErrorCode3(error, "ENOENT")) throw error;
2061
+ if (await lstat(board.path).then(
2062
+ () => true,
2063
+ () => false
2064
+ )) {
2065
+ throw new Error(
2066
+ `${DEFAULT_BOARD_PATH} is there but cannot be read (a link to nothing?). Fix it before the board is updated.`
2067
+ );
2068
+ }
2069
+ text = null;
2070
+ }
2071
+ const otherRepos = (manifest.repos ?? []).map((repo) => repo.path).filter((p) => p !== ".");
2072
+ if ([root, ...otherRepos].some((path) => displayPath3(path) !== path)) {
2073
+ throw new Error(
2074
+ "The workspace's path, or a repo path the manifest declares, holds a control character, which the guide's commands cannot carry safely. Rename it, or follow the steps by hand."
2075
+ );
2076
+ }
2077
+ const common = {
2078
+ // As basou resolves it, the root measure resolves the repos' paths against.
2079
+ anchor: root,
2080
+ otherRepos,
2081
+ language: resolveViewLanguage(manifest),
2082
+ basouCommand: ctx.basouCommand ?? [process.execPath, process.argv[1] ?? ""].filter((part) => part !== "").map(shellWord).join(" "),
2083
+ basouVersion: BASOU_CLI_VERSION,
2084
+ now: ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()
2085
+ };
2086
+ let guide;
2087
+ if (text === null) {
2088
+ guide = boardGuide({ ...common, board: { status: "undeclared" } });
2089
+ } else {
2090
+ const parsed = parseBoardDeclaration(text, {
2091
+ manifestRepoPaths: (manifest.repos ?? []).map((repo) => repo.path)
2092
+ });
2093
+ if (!parsed.ok) {
2094
+ throw new Error(
2095
+ `${DEFAULT_BOARD_PATH} is not a valid board declaration, so there are no steps to print until it reads (fix it through an axis review when it changes the lanes or the stages):
2096
+ ${parsed.errors.map((e) => ` - ${displayPath3(e)}`).join("\n")}`
2097
+ );
2098
+ }
2099
+ const recordsDir = join4(dirname5(board.path), "records");
2100
+ guide = boardGuide({
2101
+ ...common,
2102
+ board: {
2103
+ status: "declared",
2104
+ declaration: parsed.declaration,
2105
+ recordCount: recordIds(await readdir2(recordsDir).catch(() => [])).length,
2106
+ previous: await readPreviousRecords(recordsDir)
2107
+ }
2108
+ });
2109
+ }
2110
+ console.log(guide.trimEnd());
2111
+ return guide;
1744
2112
  }
1745
2113
  async function runBoardMeasure(options, ctx = {}) {
1746
2114
  try {
@@ -1787,8 +2155,8 @@ Nothing was written.`, { cause: error.cause });
1787
2155
  async function checkAndRecord(options, ctx) {
1788
2156
  const cwd = ctx.cwd ?? process.cwd();
1789
2157
  const loaded = await loadBoard(options, ctx, "board record");
1790
- const beside = (...parts) => loaded.board.given ? join3(dirname3(loaded.board.shown), ...parts) : relative(cwd, join3(dirname3(loaded.board.path), ...parts));
1791
- const recordsDir = join3(dirname3(loaded.board.path), "records");
2158
+ const beside = (...parts) => loaded.board.given ? join4(dirname5(loaded.board.shown), ...parts) : relative(cwd, join4(dirname5(loaded.board.path), ...parts));
2159
+ const recordsDir = join4(dirname5(loaded.board.path), "records");
1792
2160
  await checkRecordsPlace(loaded, recordsDir, beside("records"), options.notPrivate === true);
1793
2161
  const text = await readRecordInput(options, ctx);
1794
2162
  let value;
@@ -1799,6 +2167,7 @@ async function checkAndRecord(options, ctx) {
1799
2167
  cause: error
1800
2168
  });
1801
2169
  }
2170
+ await refuseOtherWorkspaces(value, loaded.root, ctx);
1802
2171
  const parsed = parseRecordInput(value, loaded.declaration);
1803
2172
  if (!parsed.ok) {
1804
2173
  throw new RecordRefusal(
@@ -1855,11 +2224,90 @@ ${parsed.errors.map((e) => ` - ${displayPath3(e)}`).join("\n")}`
1855
2224
  diff
1856
2225
  };
1857
2226
  }
2227
+ var INPUT_KEYS = /* @__PURE__ */ new Set([
2228
+ "measure_digest",
2229
+ "observed",
2230
+ "cells",
2231
+ "prose",
2232
+ "judged_by",
2233
+ "axis_review",
2234
+ "value",
2235
+ "observed_at",
2236
+ "source",
2237
+ "error",
2238
+ "lane",
2239
+ "stage",
2240
+ "state",
2241
+ "reason",
2242
+ "summary",
2243
+ "lanes",
2244
+ "operator_turns",
2245
+ "footnotes",
2246
+ "text",
2247
+ "model",
2248
+ "self_reported",
2249
+ "triggers"
2250
+ ]);
2251
+ var SHAPED_PLACES = [
2252
+ /^measure_digest$/,
2253
+ /^observed \(a named entry\)\.observed_at$/,
2254
+ /^cells\[\d+\]\.state$/,
2255
+ /^axis_review\.triggers$/
2256
+ ];
2257
+ var NAMED_MAPS = /* @__PURE__ */ new Set(["observed", "prose.lanes"]);
2258
+ function inputTexts(value) {
2259
+ const out = [];
2260
+ const stack = [{ value, at: "" }];
2261
+ for (let next = stack.pop(); next !== void 0; next = stack.pop()) {
2262
+ const { value: v, at } = next;
2263
+ if (SHAPED_PLACES.some((place) => place.test(at))) continue;
2264
+ if (typeof v === "string" || typeof v === "number") {
2265
+ out.push({ at: at === "" ? "(top level)" : at, text: String(v) });
2266
+ } else if (Array.isArray(v)) {
2267
+ for (let i = v.length - 1; i >= 0; i--) stack.push({ value: v[i], at: `${at}[${i}]` });
2268
+ } else if (typeof v === "object" && v !== null) {
2269
+ const named = NAMED_MAPS.has(at);
2270
+ const where = at === "" ? "(top level)" : at;
2271
+ const children = [];
2272
+ for (const [k, child] of Object.entries(v)) {
2273
+ if (!named && INPUT_KEYS.has(k)) {
2274
+ children.push({ value: child, at: at === "" ? k : `${at}.${k}` });
2275
+ } else {
2276
+ out.push({ at: `${where}: a name`, text: k });
2277
+ children.push({ value: child, at: `${where} (a named entry)` });
2278
+ }
2279
+ }
2280
+ stack.push(...children.reverse());
2281
+ }
2282
+ }
2283
+ return out;
2284
+ }
2285
+ async function refuseOtherWorkspaces(input, root, ctx) {
2286
+ const found = await findForeignWorkspaceFields({
2287
+ fields: inputTexts(input),
2288
+ selfPath: root,
2289
+ configPath: ctx.portfolioConfigPath
2290
+ });
2291
+ if (found.status === "unreadable") {
2292
+ console.error(
2293
+ "basou: ~/.basou/portfolio.yaml could not be read, or does not list workspaces as basou reads them, so the record's input was not checked for the names of other registered workspaces."
2294
+ );
2295
+ return;
2296
+ }
2297
+ if (found.status === "checked" && found.at.length > 0) {
2298
+ throw new RecordRefusal(
2299
+ `The record's input names another workspace the portfolio registers, and a board holds this workspace's own work only; nothing was written:
2300
+ ${[
2301
+ ...new Set(found.at)
2302
+ ].map((where) => ` - ${displayPath3(where)}`).join("\n")}`
2303
+ );
2304
+ }
2305
+ }
1858
2306
  function hasRecords(loaded) {
1859
- return basename3(loaded.board.path) === "board.yaml";
2307
+ return basename4(loaded.board.path) === "board.yaml";
1860
2308
  }
1861
2309
  async function previousOf(loaded) {
1862
- return hasRecords(loaded) ? readPreviousRecords(join3(dirname3(loaded.board.path), "records")) : NO_PREVIOUS_RECORDS;
2310
+ return hasRecords(loaded) ? readPreviousRecords(join4(dirname5(loaded.board.path), "records")) : NO_PREVIOUS_RECORDS;
1863
2311
  }
1864
2312
  function errorCode(error) {
1865
2313
  const code = error?.code;
@@ -1867,21 +2315,21 @@ function errorCode(error) {
1867
2315
  }
1868
2316
  async function checkRecordsPlace(loaded, recordsDir, shown, notPrivate) {
1869
2317
  const { board } = loaded;
1870
- if (basename3(board.path) !== "board.yaml") {
2318
+ if (basename4(board.path) !== "board.yaml") {
1871
2319
  throw new RecordRefusal(
1872
2320
  `${displayPath3(board.shown)} is not named board.yaml: basou board record reads only a file of that name, so that the records/ beside it holds one board's records; nothing was written.`
1873
2321
  );
1874
2322
  }
1875
2323
  let dir;
1876
2324
  try {
1877
- dir = await realpath2(dirname3(board.path));
2325
+ dir = await realpath3(dirname5(board.path));
1878
2326
  } catch (error) {
1879
2327
  throw new RecordRefusal(
1880
2328
  `The directory of ${displayPath3(board.shown)} could not be resolved; nothing was written.`,
1881
2329
  { cause: error }
1882
2330
  );
1883
2331
  }
1884
- if (dir.split(sep).includes(".basou")) {
2332
+ if (dir.split(sep2).includes(".basou")) {
1885
2333
  throw new RecordRefusal(
1886
2334
  `${displayPath3(shown)} would be under a .basou/ directory, where basou board record writes nothing; nothing was written.`
1887
2335
  );
@@ -1889,8 +2337,8 @@ async function checkRecordsPlace(loaded, recordsDir, shown, notPrivate) {
1889
2337
  if (!notPrivate) {
1890
2338
  let holder;
1891
2339
  for (const repo of loaded.manifest.repos ?? []) {
1892
- const real = await realpath2(resolve3(loaded.root, repo.path)).catch(() => void 0);
1893
- if (real === void 0 || dir !== real && !dir.startsWith(`${real}${sep}`)) continue;
2340
+ const real = await realpath3(resolve3(loaded.root, repo.path)).catch(() => void 0);
2341
+ if (real === void 0 || dir !== real && !dir.startsWith(`${real}${sep2}`)) continue;
1894
2342
  if (holder === void 0 || real.length > holder.real.length) {
1895
2343
  holder = { real, private: repo.visibility === "private" };
1896
2344
  }
@@ -1990,7 +2438,7 @@ async function loadBoard(options, ctx, command) {
1990
2438
  );
1991
2439
  const paths = basouPaths4(root);
1992
2440
  await assertWorkspaceInitialized3(paths.root);
1993
- const manifest = await readManifest3(paths);
2441
+ const manifest = await readManifest4(paths);
1994
2442
  const board = boardPath(options, cwd, root, manifest);
1995
2443
  const text = await readDeclaration(board);
1996
2444
  const repoPaths = (manifest.repos ?? []).map((repo) => repo.path);
@@ -2024,6 +2472,41 @@ async function measureLoaded(loaded, ctx, model, previous) {
2024
2472
  previous
2025
2473
  });
2026
2474
  }
2475
+ async function measureLiveBoard(root, ctx) {
2476
+ const paths = basouPaths4(root);
2477
+ let repos = ["."];
2478
+ let manifestUnread = false;
2479
+ try {
2480
+ const declared = ((await readManifest4(paths)).repos ?? []).map((repo) => repo.path);
2481
+ if (declared.length > 0) repos = declared;
2482
+ } catch {
2483
+ manifestUnread = true;
2484
+ }
2485
+ const now = ctx.nowProvider?.() ?? /* @__PURE__ */ new Date();
2486
+ const probeCtx = { cwd: root };
2487
+ if (ctx.claudeProjectsDir !== void 0) probeCtx.claudeProjectsDir = ctx.claudeProjectsDir;
2488
+ if (ctx.codexSessionsDir !== void 0) probeCtx.codexSessionsDir = ctx.codexSessionsDir;
2489
+ const measured = await measureBoardLive({
2490
+ root,
2491
+ repos,
2492
+ paths,
2493
+ now,
2494
+ measuredWith: { basou: BASOU_CLI_VERSION, build: BASOU_BUILD?.commit ?? null },
2495
+ probeImports: () => probeStaleness({ ctx: probeCtx, paths, nowIso: now.toISOString() })
2496
+ });
2497
+ if (!manifestUnread) return measured;
2498
+ return {
2499
+ ...measured,
2500
+ complete: false,
2501
+ not_found: [
2502
+ {
2503
+ at: "repos",
2504
+ reason: "the manifest could not be read, so only the workspace's own repo was measured"
2505
+ },
2506
+ ...measured.not_found
2507
+ ]
2508
+ };
2509
+ }
2027
2510
  function boardPath(options, cwd, root, manifest) {
2028
2511
  if (options.board !== void 0)
2029
2512
  return { path: resolve3(cwd, options.board), shown: options.board, given: true };
@@ -2033,7 +2516,7 @@ function boardPath(options, cwd, root, manifest) {
2033
2516
  `No --board given, and the default ${DEFAULT_BOARD_PATH} is used only when the manifest declares this workspace's own repo (path: .) private. Pass --board <path to board.yaml>.`
2034
2517
  );
2035
2518
  }
2036
- return { path: join3(root, DEFAULT_BOARD_PATH), shown: DEFAULT_BOARD_PATH, given: false };
2519
+ return { path: join4(root, DEFAULT_BOARD_PATH), shown: DEFAULT_BOARD_PATH, given: false };
2037
2520
  }
2038
2521
  async function readDeclaration(board) {
2039
2522
  try {
@@ -2265,19 +2748,19 @@ function printMeasurementText(m, hasRecords2) {
2265
2748
 
2266
2749
  // src/lib/context-channel.ts
2267
2750
  import { homedir as homedir2 } from "os";
2268
- import { join as join5 } from "path";
2751
+ import { join as join6 } from "path";
2269
2752
  import {
2270
2753
  ORIENTATION_END,
2271
2754
  ORIENTATION_START,
2272
2755
  parseMarkers,
2273
- readMarkdownFile as readMarkdownFile2,
2756
+ readMarkdownFile as readMarkdownFile3,
2274
2757
  removeMarkerSection
2275
2758
  } from "@basou/core";
2276
2759
 
2277
2760
  // src/lib/durable-write.ts
2278
2761
  import { randomUUID } from "crypto";
2279
2762
  import { lstat as lstat2, open, rename, stat as stat3, unlink as unlink2 } from "fs/promises";
2280
- import { basename as basename4, dirname as dirname4, join as join4 } from "path";
2763
+ import { basename as basename5, dirname as dirname6, join as join5 } from "path";
2281
2764
  async function assertNotSymlink(targetPath) {
2282
2765
  try {
2283
2766
  const st = await lstat2(targetPath);
@@ -2292,8 +2775,8 @@ async function assertNotSymlink(targetPath) {
2292
2775
  }
2293
2776
  }
2294
2777
  async function writeFileDurable(targetPath, content) {
2295
- const dir = dirname4(targetPath);
2296
- const tmpPath = join4(dir, `.${basename4(targetPath)}.tmp.${randomUUID()}`);
2778
+ const dir = dirname6(targetPath);
2779
+ const tmpPath = join5(dir, `.${basename5(targetPath)}.tmp.${randomUUID()}`);
2297
2780
  let mode = 420;
2298
2781
  try {
2299
2782
  mode = (await stat3(targetPath)).mode & 511;
@@ -2328,7 +2811,7 @@ async function writeFileDurable(targetPath, content) {
2328
2811
  }
2329
2812
 
2330
2813
  // src/lib/context-channel.ts
2331
- var CODEX_TARGET_PATH = join5(homedir2(), ".codex", "AGENTS.md");
2814
+ var CODEX_TARGET_PATH = join6(homedir2(), ".codex", "AGENTS.md");
2332
2815
  var ORIENTATION_MARKERS = { start: ORIENTATION_START, end: ORIENTATION_END };
2333
2816
  function buildTargetBody(existing, block, markers) {
2334
2817
  const wrapped = `${markers.start}
@@ -2352,20 +2835,20 @@ ${block}${markers.end}${section.after}`;
2352
2835
  }
2353
2836
  async function backupOnce(target, existing) {
2354
2837
  const bak = `${target}.basou-bak`;
2355
- const already = await readMarkdownFile2(bak);
2838
+ const already = await readMarkdownFile3(bak);
2356
2839
  if (already !== null) return;
2357
2840
  await writeFileDurable(bak, existing ?? "");
2358
2841
  }
2359
2842
  async function syncMarkerBlock(opts) {
2360
2843
  const { target, markers, block } = opts;
2361
2844
  await assertNotSymlink(target);
2362
- const existing = await readMarkdownFile2(target);
2845
+ const existing = await readMarkdownFile3(target);
2363
2846
  const newBody = buildTargetBody(existing, block, markers);
2364
2847
  if (newBody === existing) return { action: "unchanged" };
2365
2848
  const hadBlock = existing !== null && parseMarkers(existing, markers).kind === "ok";
2366
2849
  const action = hadBlock ? "updated" : "installed";
2367
2850
  if (opts.dryRun === true) return { action };
2368
- const recheck = await readMarkdownFile2(target);
2851
+ const recheck = await readMarkdownFile3(target);
2369
2852
  if (recheck !== existing) {
2370
2853
  throw new Error(
2371
2854
  "The target changed during sync; aborting so a concurrent edit is not overwritten. Re-run the command."
@@ -2387,12 +2870,12 @@ function assertNoMarkerLine(body, markers) {
2387
2870
  async function removeMarkerBlock(opts) {
2388
2871
  const { target, markers, fileLabel } = opts;
2389
2872
  await assertNotSymlink(target);
2390
- const existing = await readMarkdownFile2(target);
2873
+ const existing = await readMarkdownFile3(target);
2391
2874
  if (existing === null) return { removed: false };
2392
2875
  const newBody = removeMarkerSection(existing, fileLabel, markers);
2393
2876
  if (newBody === existing) return { removed: false };
2394
2877
  if (opts.dryRun === true) return { removed: true };
2395
- const recheck = await readMarkdownFile2(target);
2878
+ const recheck = await readMarkdownFile3(target);
2396
2879
  if (recheck !== existing) {
2397
2880
  throw new Error(
2398
2881
  "The target changed during unsync; aborting so a concurrent edit is not overwritten. Re-run the command."
@@ -2473,7 +2956,7 @@ async function doRunChannelClear(face, options) {
2473
2956
  // src/commands/decision.ts
2474
2957
  import { readFile as readFile3 } from "fs/promises";
2475
2958
  import { homedir as homedir3 } from "os";
2476
- import { join as join6, resolve as resolve4 } from "path";
2959
+ import { join as join7, resolve as resolve4 } from "path";
2477
2960
  import {
2478
2961
  AGENT_INFRA_DIRS as AGENT_INFRA_DIRS2,
2479
2962
  acquireLock as acquireLock2,
@@ -2489,7 +2972,7 @@ import {
2489
2972
  LOCAL_CLI_EVENT_SOURCE as LOCAL_CLI_EVENT_SOURCE2,
2490
2973
  loadSessionEntries,
2491
2974
  prefixedUlid as prefixedUlid2,
2492
- readManifest as readManifest4,
2975
+ readManifest as readManifest5,
2493
2976
  replayEvents as replayEvents2,
2494
2977
  resolveSessionId,
2495
2978
  sanitizePath
@@ -2752,7 +3235,7 @@ async function runDecisionRecord(options, ctx = {}) {
2752
3235
  async function warnLinkedFilesOutsideRoots(input) {
2753
3236
  if (input.linkedFiles.length === 0) return;
2754
3237
  try {
2755
- const manifest = await readManifest4(input.paths);
3238
+ const manifest = await readManifest5(input.paths);
2756
3239
  if ((manifest.import?.source_roots?.length ?? 0) === 0) return;
2757
3240
  const scope = await classifyFilesBySourceRoot2({
2758
3241
  files: input.linkedFiles,
@@ -2818,7 +3301,7 @@ async function doRunDecisionRecord(options, ctx) {
2818
3301
  });
2819
3302
  return;
2820
3303
  }
2821
- const manifest = await readManifest4(paths);
3304
+ const manifest = await readManifest5(paths);
2822
3305
  const adHoc = await createAdHocSessionWithEvent({
2823
3306
  paths,
2824
3307
  manifest,
@@ -2882,7 +3365,7 @@ async function doRunDecisionCapture(options, ctx) {
2882
3365
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
2883
3366
  const occurredAt = now.toISOString();
2884
3367
  const decisionIds = decisions.map(() => prefixedUlid2("decision"));
2885
- const manifest = await readManifest4(paths);
3368
+ const manifest = await readManifest5(paths);
2886
3369
  const invocationArgs = options.file !== void 0 ? [
2887
3370
  "--file",
2888
3371
  sanitizePath(resolve4(cwd, options.file), {
@@ -2988,7 +3471,7 @@ async function doRunDecisionVoid(decisionId, options, ctx) {
2988
3471
  });
2989
3472
  return;
2990
3473
  }
2991
- const manifest = await readManifest4(paths);
3474
+ const manifest = await readManifest5(paths);
2992
3475
  const adHoc = await createAdHocSessionWithEvent({
2993
3476
  paths,
2994
3477
  manifest,
@@ -3024,7 +3507,7 @@ function isDecisionId(value) {
3024
3507
  async function decisionExists(paths, decisionId) {
3025
3508
  const entries = await loadSessionEntries(paths, { now: /* @__PURE__ */ new Date() });
3026
3509
  for (const entry of entries) {
3027
- const sessionDir = join6(paths.sessions, entry.sessionId);
3510
+ const sessionDir = join7(paths.sessions, entry.sessionId);
3028
3511
  try {
3029
3512
  for await (const ev of replayEvents2(sessionDir, {})) {
3030
3513
  if (ev.type === "decision_recorded" && ev.decision_id === decisionId) return true;
@@ -3436,7 +3919,7 @@ import {
3436
3919
  assertBasouRootSafe as assertBasouRootSafe5,
3437
3920
  basouPaths as basouPaths7,
3438
3921
  findErrorCode as findErrorCode5,
3439
- readMarkdownFile as readMarkdownFile3,
3922
+ readMarkdownFile as readMarkdownFile4,
3440
3923
  renderDecisions as renderDecisions2,
3441
3924
  renderWithMarkers as renderWithMarkers2,
3442
3925
  writeMarkdownFile as writeMarkdownFile2
@@ -3468,7 +3951,7 @@ async function doRunDecisionsGenerate(options, ctx) {
3468
3951
  onWarning: (w, sid) => printReplayWarning(w, sid),
3469
3952
  onSessionSkip: (sid, reason) => printSessionSkip(sid, reason)
3470
3953
  });
3471
- const existing = await readMarkdownFile3(paths.files.decisions);
3954
+ const existing = await readMarkdownFile4(paths.files.decisions);
3472
3955
  const finalBody = renderWithMarkers2(existing, result.body, "decisions.md");
3473
3956
  await writeMarkdownFile2(paths.files.decisions, finalBody);
3474
3957
  console.log(`Generated .basou/decisions.md (decisions: ${result.decisionCount})`);
@@ -3487,7 +3970,7 @@ async function assertWorkspaceInitialized5(basouRoot) {
3487
3970
  // src/commands/exec.ts
3488
3971
  import { mkdir as mkdir2, rm as rm2 } from "fs/promises";
3489
3972
  import { homedir as homedir4 } from "os";
3490
- import { join as join7 } from "path";
3973
+ import { join as join8 } from "path";
3491
3974
  import {
3492
3975
  acquireLock as acquireLock3,
3493
3976
  assertBasouRootSafe as assertBasouRootSafe6,
@@ -3501,7 +3984,7 @@ import {
3501
3984
  overwriteYamlFile,
3502
3985
  parseDuration,
3503
3986
  prefixedUlid as prefixedUlid3,
3504
- readManifest as readManifest5,
3987
+ readManifest as readManifest6,
3505
3988
  readYamlFile as readYamlFile2,
3506
3989
  SESSION_SCHEMA_VERSION,
3507
3990
  SessionSchema,
@@ -3528,15 +4011,15 @@ async function runExec(command, args, options, ctx = {}) {
3528
4011
  const repoRoot = await resolveRepositoryRootInPlace(cwd, "exec");
3529
4012
  const paths = basouPaths8(repoRoot);
3530
4013
  await assertBasouRootSafe6(paths.root);
3531
- const manifest = await readManifest5(paths);
4014
+ const manifest = await readManifest6(paths);
3532
4015
  await assertSessionStoreSafe2(paths);
3533
4016
  const sessionId = prefixedUlid3("ses");
3534
- const sessionDir = join7(paths.sessions, sessionId);
4017
+ const sessionDir = join8(paths.sessions, sessionId);
3535
4018
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
3536
4019
  await coreAppendChainedEvent(paths, sessionId, event);
3537
4020
  });
3538
4021
  const startedAt = now().toISOString();
3539
- const sessionYamlPath = join7(sessionDir, "session.yaml");
4022
+ const sessionYamlPath = join8(sessionDir, "session.yaml");
3540
4023
  const session = buildInitialSession({
3541
4024
  id: sessionId,
3542
4025
  command,
@@ -3829,7 +4312,7 @@ import {
3829
4312
  assertBasouRootSafe as assertBasouRootSafe7,
3830
4313
  basouPaths as basouPaths9,
3831
4314
  findErrorCode as findErrorCode6,
3832
- readMarkdownFile as readMarkdownFile4,
4315
+ readMarkdownFile as readMarkdownFile5,
3833
4316
  renderHandoff as renderHandoff2,
3834
4317
  renderWithMarkers as renderWithMarkers3,
3835
4318
  writeMarkdownFile as writeMarkdownFile3
@@ -3862,7 +4345,7 @@ async function doRunHandoffGenerate(options, ctx) {
3862
4345
  onSessionSkip: (sid, reason) => printSessionSkip(sid, reason),
3863
4346
  onTaskSkip: (taskId, reason) => printTaskSkip(taskId, reason)
3864
4347
  });
3865
- const existing = await readMarkdownFile4(paths.files.handoff);
4348
+ const existing = await readMarkdownFile5(paths.files.handoff);
3866
4349
  const finalBody = renderWithMarkers3(existing, result.body, "handoff.md");
3867
4350
  await writeMarkdownFile3(paths.files.handoff, finalBody);
3868
4351
  console.log(
@@ -3897,7 +4380,7 @@ async function assertWorkspaceInitialized6(basouRoot) {
3897
4380
  import { execFile } from "child_process";
3898
4381
  import { open as open2, readFile as readFile4, realpath as realpath4, stat as stat4 } from "fs/promises";
3899
4382
  import { homedir as homedir7 } from "os";
3900
- import { join as join10 } from "path";
4383
+ import { join as join11 } from "path";
3901
4384
  import { fileURLToPath } from "url";
3902
4385
  import { promisify } from "util";
3903
4386
  import {
@@ -3924,7 +4407,7 @@ import {
3924
4407
  parseProtocolStamp,
3925
4408
  protocolSectionsFrom,
3926
4409
  protocolUpdateToken,
3927
- readManifest as readManifest6,
4410
+ readManifest as readManifest7,
3928
4411
  readMarkdownFile as readMarkdownFile6,
3929
4412
  recordSessionBaseline,
3930
4413
  removeClaudeSessionStartHook,
@@ -3951,12 +4434,12 @@ function commandHandlerFields(handler) {
3951
4434
  out.additionalContextLimit = handler.additionalContextLimit;
3952
4435
  return out;
3953
4436
  }
3954
- function canonicalize(value) {
3955
- if (Array.isArray(value)) return value.map(canonicalize);
4437
+ function canonicalize2(value) {
4438
+ if (Array.isArray(value)) return value.map(canonicalize2);
3956
4439
  if (typeof value === "object" && value !== null) {
3957
4440
  const src = value;
3958
4441
  const out = {};
3959
- for (const key of Object.keys(src).sort()) out[key] = canonicalize(src[key]);
4442
+ for (const key of Object.keys(src).sort()) out[key] = canonicalize2(src[key]);
3960
4443
  return out;
3961
4444
  }
3962
4445
  return value;
@@ -3975,7 +4458,7 @@ function computeCodexHookIdentityHash(input) {
3975
4458
  }
3976
4459
  const identity = { event_name: input.eventKey, hooks: [normalized] };
3977
4460
  if (input.matcher !== void 0) identity.matcher = input.matcher;
3978
- const blob = JSON.stringify(canonicalize(identity));
4461
+ const blob = JSON.stringify(canonicalize2(identity));
3979
4462
  return `sha256:${createHash("sha256").update(blob, "utf8").digest("hex")}`;
3980
4463
  }
3981
4464
  function codexHookStateKey(hooksPath, eventKey, groupIndex, handlerIndex) {
@@ -4056,148 +4539,17 @@ function describeCodexHookTrust(trust) {
4056
4539
  }
4057
4540
  }
4058
4541
 
4059
- // src/lib/foreign-workspace-warn.ts
4060
- import { realpath as realpath3 } from "fs/promises";
4061
- import { dirname as dirname5 } from "path";
4062
- import { loadPortfolioConfig as loadPortfolioConfig2, readMarkdownFile as readMarkdownFile5 } from "@basou/core";
4063
-
4064
- // src/lib/foreign-workspace-scan.ts
4065
- import { homedir as osHomedir } from "os";
4066
- import { basename as basename5, normalize, sep as sep2 } from "path";
4067
- var MIN_TOKEN_LENGTH = 4;
4068
- function stripTrailingSep(p) {
4069
- return p.length > 1 && p.endsWith(sep2) ? p.slice(0, -sep2.length) : p;
4070
- }
4071
- function toTildePath(absPath, home) {
4072
- if (absPath === home) return "~";
4073
- if (absPath.startsWith(home + sep2)) return `~${absPath.slice(home.length)}`;
4074
- return null;
4075
- }
4076
- function encodedSpelling(name) {
4077
- return name.replace(/[^a-zA-Z0-9]/g, "-");
4078
- }
4079
- function directoryNameSpellings(dir) {
4080
- const paired = /^(.+)-(planning|workspace)$/.exec(dir);
4081
- const plain = paired !== null ? [`${paired[1]}-planning`, `${paired[1]}-workspace`] : [dir, `${dir}-workspace`];
4082
- return [...plain, ...plain.map(encodedSpelling)];
4083
- }
4084
- function nameTokensFor(workspacePath, home) {
4085
- const abs = stripTrailingSep(normalize(workspacePath));
4086
- const tokens = /* @__PURE__ */ new Set([abs]);
4087
- const tilde = toTildePath(abs, home);
4088
- if (tilde !== null) tokens.add(tilde);
4089
- for (const name of directoryNameSpellings(basename5(abs))) tokens.add(name);
4090
- return [...tokens].filter((t) => t.length >= MIN_TOKEN_LENGTH);
4091
- }
4092
- function isSpellingOfSelf(workspacePath, selfPath) {
4093
- const own = new Set(directoryNameSpellings(basename5(stripTrailingSep(normalize(selfPath)))));
4094
- return directoryNameSpellings(basename5(stripTrailingSep(normalize(workspacePath)))).some(
4095
- (s) => own.has(s)
4096
- );
4097
- }
4098
- function scanForeignWorkspaceNames(input) {
4099
- if (input.text.length === 0 || input.workspacePaths.length === 0) return [];
4100
- const home = input.homedir ?? osHomedir();
4101
- const own = input.selfPath === void 0 ? [] : nameTokensFor(input.selfPath, home);
4102
- const lines = input.text.split("\n");
4103
- const hits = [];
4104
- for (const workspacePath of input.workspacePaths) {
4105
- if (input.selfPath !== void 0 && isSpellingOfSelf(workspacePath, input.selfPath)) continue;
4106
- const tokens = nameTokensFor(workspacePath, home).filter(
4107
- (token) => !own.some((ownToken) => ownToken.includes(token))
4108
- );
4109
- if (tokens.length === 0) continue;
4110
- const matched = /* @__PURE__ */ new Set();
4111
- const matchedLines = [];
4112
- for (const [index, line] of lines.entries()) {
4113
- let lineMatched = false;
4114
- for (const token of tokens) {
4115
- if (line.includes(token)) {
4116
- matched.add(token);
4117
- lineMatched = true;
4118
- }
4119
- }
4120
- if (lineMatched) matchedLines.push(index + 1);
4121
- }
4122
- if (matchedLines.length === 0) continue;
4123
- hits.push({
4124
- workspacePath,
4125
- tokens: [...matched].sort((a, b) => b.length - a.length || a.localeCompare(b)),
4126
- lines: matchedLines
4127
- });
4128
- }
4129
- return hits;
4130
- }
4131
-
4132
- // src/lib/foreign-workspace-warn.ts
4133
- var MAX_LISTED_LINES = 5;
4134
- async function canonicalize2(path) {
4135
- try {
4136
- return await realpath3(path);
4137
- } catch {
4138
- return path;
4139
- }
4140
- }
4141
- async function findForeignWorkspaceNames(args) {
4142
- let workspacePaths;
4143
- let selfPath;
4144
- try {
4145
- const workspaces = await loadPortfolioConfig2(args.configPath);
4146
- workspacePaths = await Promise.all(workspaces.map((w) => canonicalize2(w.path)));
4147
- selfPath = args.selfPath === void 0 ? void 0 : await canonicalize2(args.selfPath);
4148
- } catch {
4149
- return null;
4150
- }
4151
- const hits = scanForeignWorkspaceNames({
4152
- text: args.text,
4153
- workspacePaths,
4154
- selfPath
4155
- });
4156
- if (hits.length === 0) return null;
4157
- const lines = [...new Set(hits.flatMap((h) => h.lines))].sort((a, b) => a - b);
4158
- return { workspaceCount: hits.length, lines };
4159
- }
4160
- function describeForeignWorkspaceLines(lines) {
4161
- const listed = lines.slice(0, MAX_LISTED_LINES).join(", ");
4162
- const rest = lines.length - MAX_LISTED_LINES;
4163
- const numbers = rest > 0 ? `${listed} and ${rest} more` : listed;
4164
- return `${lines.length === 1 ? "line" : "lines"} ${numbers}`;
4165
- }
4166
- function positionForeignWorkspaceWarning(report, where) {
4167
- const subject = report.workspaceCount === 1 ? "names another registered workspace" : `names ${report.workspaceCount} other registered workspaces`;
4168
- return `basou: this workspace's position ${subject} (${where}, ${describeForeignWorkspaceLines(report.lines)}). The position is handed to the agent session running in this workspace, so those names travel with it. Advisory only: nothing was withheld.`;
4169
- }
4170
- function protocolForeignWorkspaceWarning(report) {
4171
- const subject = report.workspaceCount === 1 ? "names a registered workspace" : `names ${report.workspaceCount} registered workspaces`;
4172
- return `basou: the protocol block ${subject} (block ${describeForeignWorkspaceLines(report.lines)}, counted from the block's first line, not the file's). Standing protocols are written to the user-global CLAUDE.md, which every project on this machine loads, so a workspace-specific name there reaches every workspace's sessions. Only the basou-managed block is checked; the rest of that file is not basou's to inspect. Advisory only: nothing was withheld.`;
4173
- }
4174
- async function warnIfPositionNamesOtherWorkspaces(args) {
4175
- try {
4176
- const body = await readMarkdownFile5(args.paths.files.orientation);
4177
- if (body === null) return;
4178
- const report = await findForeignWorkspaceNames({
4179
- text: body,
4180
- selfPath: dirname5(args.paths.root),
4181
- configPath: args.configPath
4182
- });
4183
- if (report !== null) {
4184
- console.error(positionForeignWorkspaceWarning(report, args.paths.files.orientation));
4185
- }
4186
- } catch {
4187
- }
4188
- }
4189
-
4190
4542
  // src/lib/protocols-config.ts
4191
4543
  import { homedir as homedir5 } from "os";
4192
- import { isAbsolute, join as join8, resolve as resolve5 } from "path";
4544
+ import { isAbsolute, join as join9, resolve as resolve5 } from "path";
4193
4545
  import { readYamlFile as readYamlFile3 } from "@basou/core";
4194
- var DEFAULT_PROTOCOLS_CONFIG_PATH = join8(homedir5(), ".basou", "protocols.yaml");
4195
- var DEFAULT_TARGET_PATH = join8(homedir5(), ".claude", "CLAUDE.md");
4546
+ var DEFAULT_PROTOCOLS_CONFIG_PATH = join9(homedir5(), ".basou", "protocols.yaml");
4547
+ var DEFAULT_TARGET_PATH = join9(homedir5(), ".claude", "CLAUDE.md");
4196
4548
  var ALLOWED_TOP_KEYS = /* @__PURE__ */ new Set(["version", "protocols"]);
4197
4549
  var ALLOWED_ENTRY_KEYS = /* @__PURE__ */ new Set(["source", "title"]);
4198
4550
  function expandTilde(p) {
4199
4551
  if (p === "~") return homedir5();
4200
- if (p.startsWith("~/")) return join8(homedir5(), p.slice(2));
4552
+ if (p.startsWith("~/")) return join9(homedir5(), p.slice(2));
4201
4553
  return p;
4202
4554
  }
4203
4555
  function isRecord(value) {
@@ -4275,12 +4627,12 @@ import {
4275
4627
 
4276
4628
  // src/lib/hosts-config.ts
4277
4629
  import { homedir as homedir6 } from "os";
4278
- import { isAbsolute as isAbsolute2, join as join9, resolve as resolve6 } from "path";
4630
+ import { isAbsolute as isAbsolute2, join as join10, resolve as resolve6 } from "path";
4279
4631
  import { readYamlFile as readYamlFile4 } from "@basou/core";
4280
- var DEFAULT_HOSTS_CONFIG_PATH = join9(homedir6(), ".basou", "hosts.yaml");
4632
+ var DEFAULT_HOSTS_CONFIG_PATH = join10(homedir6(), ".basou", "hosts.yaml");
4281
4633
  function expandTilde2(p) {
4282
4634
  if (p === "~") return homedir6();
4283
- if (p.startsWith("~/")) return join9(homedir6(), p.slice(2));
4635
+ if (p.startsWith("~/")) return join10(homedir6(), p.slice(2));
4284
4636
  return p;
4285
4637
  }
4286
4638
  function isRecord2(value) {
@@ -4400,6 +4752,7 @@ async function renderOrientationForRoot(repositoryRoot, options, ctx, behaviour)
4400
4752
  paths,
4401
4753
  nowIso,
4402
4754
  staleness,
4755
+ board: await glanceDefaultBoard(repositoryRoot, paths),
4403
4756
  verbose: options.verbose === true,
4404
4757
  federatedRoots,
4405
4758
  onWarning: (w, sid) => printReplayWarning(w, sid),
@@ -4801,7 +5154,7 @@ async function observeSessionFromPayload(fields, pass, portfolioConfigPath) {
4801
5154
  });
4802
5155
  return;
4803
5156
  }
4804
- const manifest = await readManifest6(paths);
5157
+ const manifest = await readManifest7(paths);
4805
5158
  await recordSessionBaseline({
4806
5159
  observationsDir: paths.observations,
4807
5160
  repoRoots: observedRepoRoots(root, manifest),
@@ -4863,7 +5216,7 @@ function parseMinEdits(raw) {
4863
5216
  if (raw === void 0 || !/^\d+$/.test(raw)) return void 0;
4864
5217
  return Number(raw);
4865
5218
  }
4866
- var DEFAULT_CLAUDE_SETTINGS_PATH = join10(homedir7(), ".claude", "settings.json");
5219
+ var DEFAULT_CLAUDE_SETTINGS_PATH = join11(homedir7(), ".claude", "settings.json");
4867
5220
  function resolveCliEntry() {
4868
5221
  return fileURLToPath(import.meta.url);
4869
5222
  }
@@ -5220,7 +5573,7 @@ function describeHookMode(tiers) {
5220
5573
  const gates = tiers.review ? "capture + review" : "capture";
5221
5574
  return `${enforcement}, ${gates}`;
5222
5575
  }
5223
- var DEFAULT_CODEX_FACE_PATH = join10(homedir7(), ".codex", "AGENTS.md");
5576
+ var DEFAULT_CODEX_FACE_PATH = join11(homedir7(), ".codex", "AGENTS.md");
5224
5577
  var LEFTOVER_FACE_NOTE = (label) => `${label} still carries an orientation block rendered by an earlier basou (0.39 or before); every Codex session on this machine reads it. \`basou channel clear codex\` removes it.`;
5225
5578
  async function faceHasLeftoverOrientationBlock(facePath) {
5226
5579
  try {
@@ -5232,8 +5585,8 @@ async function faceHasLeftoverOrientationBlock(facePath) {
5232
5585
  return false;
5233
5586
  }
5234
5587
  }
5235
- var DEFAULT_CODEX_HOOKS_PATH = join10(homedir7(), ".codex", "hooks.json");
5236
- var DEFAULT_CODEX_CONFIG_PATH = join10(homedir7(), ".codex", "config.toml");
5588
+ var DEFAULT_CODEX_HOOKS_PATH = join11(homedir7(), ".codex", "hooks.json");
5589
+ var DEFAULT_CODEX_CONFIG_PATH = join11(homedir7(), ".codex", "config.toml");
5237
5590
  async function readHooksFile(path) {
5238
5591
  let raw;
5239
5592
  try {
@@ -5491,7 +5844,7 @@ import {
5491
5844
  EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION4,
5492
5845
  findErrorCode as findErrorCode8,
5493
5846
  LOCAL_CLI_EVENT_SOURCE as LOCAL_CLI_EVENT_SOURCE3,
5494
- readManifest as readManifest7,
5847
+ readManifest as readManifest8,
5495
5848
  resolveSessionId as resolveSessionId2,
5496
5849
  sanitizePath as sanitizePath2
5497
5850
  } from "@basou/core";
@@ -5603,7 +5956,7 @@ ${HEREDOC_EXAMPLE}`
5603
5956
  });
5604
5957
  return;
5605
5958
  }
5606
- const manifest = await readManifest7(paths);
5959
+ const manifest = await readManifest8(paths);
5607
5960
  const adHoc = await createAdHocSessionWithEvent2({
5608
5961
  paths,
5609
5962
  manifest,
@@ -5851,7 +6204,7 @@ import {
5851
6204
  writeFileSync,
5852
6205
  writeSync
5853
6206
  } from "fs";
5854
- import { basename as basename7, dirname as dirname6, isAbsolute as isAbsolute3, join as join11, relative as relative3, resolve as resolve9 } from "path";
6207
+ import { basename as basename7, dirname as dirname7, isAbsolute as isAbsolute3, join as join12, relative as relative3, resolve as resolve9 } from "path";
5855
6208
  import {
5856
6209
  appendBasouGitignore as appendBasouGitignore2,
5857
6210
  basouPaths as basouPaths13,
@@ -5869,7 +6222,7 @@ import {
5869
6222
  planRename,
5870
6223
  planRosterAdoption,
5871
6224
  planWorkspaceView,
5872
- readManifest as readManifest8,
6225
+ readManifest as readManifest9,
5873
6226
  readMarkdownFile as readMarkdownFile7,
5874
6227
  reconcileSourceRoots,
5875
6228
  removeMarkerSection as removeMarkerSection2,
@@ -6017,7 +6370,7 @@ async function doRunProjectCheck(options, ctx) {
6017
6370
  const cwd = ctx.cwd ?? process.cwd();
6018
6371
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project check");
6019
6372
  const paths = basouPaths13(repositoryRoot);
6020
- const manifest = await readManifest8(paths);
6373
+ const manifest = await readManifest9(paths);
6021
6374
  const roster = summarizeRosterDrift({
6022
6375
  ...manifest.repos !== void 0 ? { repos: manifest.repos } : {},
6023
6376
  sourceRoots: effectiveSourceRoots(manifest)
@@ -6152,7 +6505,7 @@ async function doRunProjectSync(options, ctx) {
6152
6505
  const cwd = ctx.cwd ?? process.cwd();
6153
6506
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project sync");
6154
6507
  const paths = basouPaths13(repositoryRoot);
6155
- const manifest = await readManifest8(paths);
6508
+ const manifest = await readManifest9(paths);
6156
6509
  const hasRoster = manifest.repos !== void 0 && manifest.repos.length > 0;
6157
6510
  const reconcile = reconcileSourceRoots({
6158
6511
  ...manifest.repos !== void 0 ? { repos: manifest.repos } : {},
@@ -6232,13 +6585,13 @@ function classifySourceRoot(repositoryRoot, declaredPath) {
6232
6585
  } catch {
6233
6586
  return { path: declaredPath, kind: "unresolved" };
6234
6587
  }
6235
- return { path: declaredPath, kind: existsSync(join11(real, ".git")) ? "repo" : "non-repo" };
6588
+ return { path: declaredPath, kind: existsSync(join12(real, ".git")) ? "repo" : "non-repo" };
6236
6589
  }
6237
6590
  async function doRunProjectAdopt(options, ctx) {
6238
6591
  const cwd = ctx.cwd ?? process.cwd();
6239
6592
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project adopt");
6240
6593
  const paths = basouPaths13(repositoryRoot);
6241
- const manifest = await readManifest8(paths);
6594
+ const manifest = await readManifest9(paths);
6242
6595
  const alreadyDeclared = manifest.repos !== void 0 && manifest.repos.length > 0;
6243
6596
  const candidates = effectiveSourceRoots(manifest).map(
6244
6597
  (r) => classifySourceRoot(repositoryRoot, r)
@@ -6336,7 +6689,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
6336
6689
  } catch {
6337
6690
  return { ...base, reachable: false, instructionFiles: [] };
6338
6691
  }
6339
- if (!existsSync(join11(real, ".git"))) {
6692
+ if (!existsSync(join12(real, ".git"))) {
6340
6693
  return { ...base, reachable: false, instructionFiles: [] };
6341
6694
  }
6342
6695
  try {
@@ -6344,7 +6697,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
6344
6697
  for (const name of INSTRUCTION_FILES) {
6345
6698
  let present = true;
6346
6699
  try {
6347
- lstatSync(join11(real, name));
6700
+ lstatSync(join12(real, name));
6348
6701
  } catch {
6349
6702
  present = false;
6350
6703
  }
@@ -6360,7 +6713,7 @@ async function doRunProjectWiring(options, ctx) {
6360
6713
  const cwd = ctx.cwd ?? process.cwd();
6361
6714
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project wiring");
6362
6715
  const paths = basouPaths13(repositoryRoot);
6363
- const manifest = await readManifest8(paths);
6716
+ const manifest = await readManifest9(paths);
6364
6717
  const roster = manifest.repos ?? [];
6365
6718
  const facts = [];
6366
6719
  for (const entry of roster) facts.push(await gatherRepoWiring(repositoryRoot, entry));
@@ -6455,10 +6808,10 @@ function gatherRepoGitignore(repositoryRoot, entry) {
6455
6808
  } catch {
6456
6809
  return { ...base, reachable: false, currentLines: [] };
6457
6810
  }
6458
- if (!existsSync(join11(real, ".git"))) {
6811
+ if (!existsSync(join12(real, ".git"))) {
6459
6812
  return { ...base, reachable: false, currentLines: [] };
6460
6813
  }
6461
- return { ...base, reachable: true, currentLines: readGitignoreLines(join11(real, ".gitignore")) };
6814
+ return { ...base, reachable: true, currentLines: readGitignoreLines(join12(real, ".gitignore")) };
6462
6815
  }
6463
6816
  function hasErrorCode(error) {
6464
6817
  return error instanceof Error && typeof error.code === "string";
@@ -6472,7 +6825,7 @@ function readGitignoreLines(file) {
6472
6825
  }
6473
6826
  }
6474
6827
  function applyGitignorePlan(repositoryRoot, plan) {
6475
- const file = join11(realpathSync(resolve9(repositoryRoot, plan.path)), ".gitignore");
6828
+ const file = join12(realpathSync(resolve9(repositoryRoot, plan.path)), ".gitignore");
6476
6829
  let existing = "";
6477
6830
  try {
6478
6831
  existing = readFileSync(file, "utf8");
@@ -6493,7 +6846,7 @@ async function doRunProjectGitignore(options, ctx) {
6493
6846
  const cwd = ctx.cwd ?? process.cwd();
6494
6847
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project gitignore");
6495
6848
  const paths = basouPaths13(repositoryRoot);
6496
- const manifest = await readManifest8(paths);
6849
+ const manifest = await readManifest9(paths);
6497
6850
  const roster = manifest.repos ?? [];
6498
6851
  const facts = roster.map((entry) => gatherRepoGitignore(repositoryRoot, entry));
6499
6852
  const summary = planGitignore({ repos: facts, required: [...INSTRUCTION_FILES] });
@@ -6612,7 +6965,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
6612
6965
  return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
6613
6966
  }
6614
6967
  if (real === anchorReal) {
6615
- const anchorCanonical = join11(real, CANONICAL_FILE);
6968
+ const anchorCanonical = join12(real, CANONICAL_FILE);
6616
6969
  const anchorState = anchorCanonicalState(anchorCanonical);
6617
6970
  if (anchorState === "absent") {
6618
6971
  return { ...base, isAnchor: true, reachable: true, canonicalPresent: false, files: [] };
@@ -6632,7 +6985,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
6632
6985
  anchorCanonical,
6633
6986
  "self"
6634
6987
  ).map((spec) => {
6635
- const { state, actualTarget } = inspectSymlink(join11(real, spec.name), spec.target);
6988
+ const { state, actualTarget } = inspectSymlink(join12(real, spec.name), spec.target);
6636
6989
  return {
6637
6990
  name: spec.name,
6638
6991
  expectedTarget: spec.target,
@@ -6649,16 +7002,16 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
6649
7002
  files: anchorFiles
6650
7003
  };
6651
7004
  }
6652
- if (!existsSync(join11(real, ".git"))) {
7005
+ if (!existsSync(join12(real, ".git"))) {
6653
7006
  return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
6654
7007
  }
6655
- const canonicalFile = isSelf ? join11(real, CANONICAL_FILE) : join11(anchorReal, "agents", basename7(real), CANONICAL_FILE);
7008
+ const canonicalFile = isSelf ? join12(real, CANONICAL_FILE) : join12(anchorReal, "agents", basename7(real), CANONICAL_FILE);
6656
7009
  if (!existsSync(canonicalFile)) {
6657
7010
  return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
6658
7011
  }
6659
7012
  const files = expectedSymlinkTargets(real, canonicalFile, mode).map(
6660
7013
  (spec) => {
6661
- const { state, actualTarget } = inspectSymlink(join11(real, spec.name), spec.target);
7014
+ const { state, actualTarget } = inspectSymlink(join12(real, spec.name), spec.target);
6662
7015
  return {
6663
7016
  name: spec.name,
6664
7017
  expectedTarget: spec.target,
@@ -6687,9 +7040,9 @@ function applySymlinkPlan(repositoryRoot, plan) {
6687
7040
  const created = [];
6688
7041
  const failed = [];
6689
7042
  for (const { name, target } of plan.toCreate) {
6690
- const filePath = join11(real, name);
7043
+ const filePath = join12(real, name);
6691
7044
  try {
6692
- mkdirSync(dirname6(filePath), { recursive: true });
7045
+ mkdirSync(dirname7(filePath), { recursive: true });
6693
7046
  symlinkSync(target, filePath);
6694
7047
  created.push(name);
6695
7048
  } catch (error) {
@@ -6721,7 +7074,7 @@ function gatherViewSymlinks(repositoryRoot, anchorReal, roster, viewDir) {
6721
7074
  if (!existsSync(canonicalFile)) return { kind: "missing-canonical", viewName };
6722
7075
  const files = expectedSymlinkTargets(viewDir, canonicalFile, "hub").map(
6723
7076
  (spec) => {
6724
- const { state, actualTarget } = inspectSymlink(join11(viewDir, spec.name), spec.target);
7077
+ const { state, actualTarget } = inspectSymlink(join12(viewDir, spec.name), spec.target);
6725
7078
  return {
6726
7079
  name: spec.name,
6727
7080
  expectedTarget: spec.target,
@@ -6737,9 +7090,9 @@ function applyViewSymlinks(viewDir, files) {
6737
7090
  const failed = [];
6738
7091
  for (const f of files) {
6739
7092
  if (f.state !== "missing") continue;
6740
- const filePath = join11(viewDir, f.name);
7093
+ const filePath = join12(viewDir, f.name);
6741
7094
  try {
6742
- mkdirSync(dirname6(filePath), { recursive: true });
7095
+ mkdirSync(dirname7(filePath), { recursive: true });
6743
7096
  symlinkSync(f.expectedTarget, filePath);
6744
7097
  created.push(f.name);
6745
7098
  } catch (error) {
@@ -6752,7 +7105,7 @@ async function doRunProjectSymlinks(options, ctx) {
6752
7105
  const cwd = ctx.cwd ?? process.cwd();
6753
7106
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project symlinks");
6754
7107
  const paths = basouPaths13(repositoryRoot);
6755
- const manifest = await readManifest8(paths);
7108
+ const manifest = await readManifest9(paths);
6756
7109
  const roster = manifest.repos ?? [];
6757
7110
  const anchorReal = realpathSync(repositoryRoot);
6758
7111
  const facts = roster.map((entry) => gatherRepoSymlinks(repositoryRoot, anchorReal, entry));
@@ -6959,7 +7312,7 @@ function resolveViewDir(repositoryRoot, viewPath) {
6959
7312
  return realpathSync(abs);
6960
7313
  } catch {
6961
7314
  try {
6962
- return join11(realpathSync(dirname6(abs)), basename7(abs));
7315
+ return join12(realpathSync(dirname7(abs)), basename7(abs));
6963
7316
  } catch {
6964
7317
  return abs;
6965
7318
  }
@@ -6977,7 +7330,7 @@ function gatherViewRepo(repositoryRoot, viewDir, entry) {
6977
7330
  return { path: entry.path, reachable: false };
6978
7331
  }
6979
7332
  const linkName = basename7(repoReal);
6980
- const { state, actualTarget } = inspectSymlink(join11(viewDir, linkName), expectedTarget);
7333
+ const { state, actualTarget } = inspectSymlink(join12(viewDir, linkName), expectedTarget);
6981
7334
  return {
6982
7335
  path: entry.path,
6983
7336
  reachable: true,
@@ -6991,9 +7344,9 @@ function applyViewPlan(viewDir, toCreate) {
6991
7344
  const created = [];
6992
7345
  const failed = [];
6993
7346
  for (const { name, target } of toCreate) {
6994
- const filePath = join11(viewDir, name);
7347
+ const filePath = join12(viewDir, name);
6995
7348
  try {
6996
- mkdirSync(dirname6(filePath), { recursive: true });
7349
+ mkdirSync(dirname7(filePath), { recursive: true });
6997
7350
  symlinkSync(target, filePath);
6998
7351
  created.push(name);
6999
7352
  } catch (error) {
@@ -7006,7 +7359,7 @@ var TOP_LEVEL_INSTRUCTION_FILES_LOWER = new Set(
7006
7359
  INSTRUCTION_FILES.filter((f) => !f.includes("/")).map((f) => f.toLowerCase())
7007
7360
  );
7008
7361
  function classifyViewLink(viewDir, name, rosterRealpaths) {
7009
- const filePath = join11(viewDir, name);
7362
+ const filePath = join12(viewDir, name);
7010
7363
  let isLink;
7011
7364
  try {
7012
7365
  isLink = lstatSync(filePath).isSymbolicLink();
@@ -7035,7 +7388,7 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
7035
7388
  if (!isDir) {
7036
7389
  return { target, kind: existsSync(resolved) ? "non-repo" : "broken" };
7037
7390
  }
7038
- return { target, kind: existsSync(join11(resolved, ".git")) ? "repo" : "non-repo" };
7391
+ return { target, kind: existsSync(join12(resolved, ".git")) ? "repo" : "non-repo" };
7039
7392
  }
7040
7393
  function gatherExistingViewLinks(viewDir, rosterRealpaths) {
7041
7394
  let names;
@@ -7060,7 +7413,7 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
7060
7413
  const pruned = [];
7061
7414
  const failed = [];
7062
7415
  for (const { name } of toPrune) {
7063
- const filePath = join11(viewDir, name);
7416
+ const filePath = join12(viewDir, name);
7064
7417
  const c = classifyViewLink(viewDir, name, rosterRealpaths);
7065
7418
  if (c === null || c.kind !== "repo") {
7066
7419
  failed.push({
@@ -7082,7 +7435,7 @@ async function doRunProjectWorkspace(options, ctx) {
7082
7435
  const cwd = ctx.cwd ?? process.cwd();
7083
7436
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project workspace");
7084
7437
  const paths = basouPaths13(repositoryRoot);
7085
- const manifest = await readManifest8(paths);
7438
+ const manifest = await readManifest9(paths);
7086
7439
  const viewPath = manifest.workspace.view;
7087
7440
  const roster = manifest.repos ?? [];
7088
7441
  let result;
@@ -7273,10 +7626,10 @@ async function runProjectPreset(options, ctx = {}) {
7273
7626
  }
7274
7627
  }
7275
7628
  function canonicalFileFor(anchorReal, canonicalName) {
7276
- return join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
7629
+ return join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
7277
7630
  }
7278
7631
  function canonicalLabelFor(canonicalName) {
7279
- return join11("agents", canonicalName, CANONICAL_FILE);
7632
+ return join12("agents", canonicalName, CANONICAL_FILE);
7280
7633
  }
7281
7634
  async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
7282
7635
  const declared = {
@@ -7297,7 +7650,7 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
7297
7650
  if (real === anchorReal) {
7298
7651
  return { ...declared, isAnchor: true, reachable: true, canonicalPresent: false };
7299
7652
  }
7300
- if (!existsSync(join11(real, ".git"))) {
7653
+ if (!existsSync(join12(real, ".git"))) {
7301
7654
  return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
7302
7655
  }
7303
7656
  const canonicalName = basename7(real);
@@ -7411,7 +7764,7 @@ async function applyViewPreset(anchorReal, outcome) {
7411
7764
  isLink = false;
7412
7765
  }
7413
7766
  if (isLink) throw new Error(`Canonical is a symlink in ${label}`);
7414
- if (outcome.action === "create") mkdirSync(dirname6(file), { recursive: true });
7767
+ if (outcome.action === "create") mkdirSync(dirname7(file), { recursive: true });
7415
7768
  const existing = await readMarkdownFile7(file);
7416
7769
  await writeMarkdownFile5(file, renderWithMarkers4(existing, outcome.block, label));
7417
7770
  }
@@ -7425,7 +7778,7 @@ async function applyPresetPlan(anchorReal, plan) {
7425
7778
  isLink = false;
7426
7779
  }
7427
7780
  if (isLink) throw new Error(`Canonical is a symlink in ${label}`);
7428
- if (plan.action === "create") mkdirSync(dirname6(file), { recursive: true });
7781
+ if (plan.action === "create") mkdirSync(dirname7(file), { recursive: true });
7429
7782
  const existing = await readMarkdownFile7(file);
7430
7783
  await writeMarkdownFile5(file, renderWithMarkers4(existing, plan.desiredBlock, label));
7431
7784
  }
@@ -7442,7 +7795,7 @@ async function doRunProjectPreset(options, ctx) {
7442
7795
  const cwd = ctx.cwd ?? process.cwd();
7443
7796
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project preset");
7444
7797
  const paths = basouPaths13(repositoryRoot);
7445
- const manifest = await readManifest8(paths);
7798
+ const manifest = await readManifest9(paths);
7446
7799
  const roster = manifest.repos ?? [];
7447
7800
  const anchorReal = realpathSync(repositoryRoot);
7448
7801
  const facts = [];
@@ -7679,24 +8032,24 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
7679
8032
  const instructionFiles = [];
7680
8033
  for (const name of INSTRUCTION_FILES) {
7681
8034
  try {
7682
- lstatSync(join11(real, name));
8035
+ lstatSync(join12(real, name));
7683
8036
  instructionFiles.push(name);
7684
8037
  } catch {
7685
8038
  }
7686
8039
  }
7687
8040
  let ignored;
7688
8041
  try {
7689
- ignored = new Set(readGitignoreLines(join11(real, ".gitignore")).map((l) => l.trim()));
8042
+ ignored = new Set(readGitignoreLines(join12(real, ".gitignore")).map((l) => l.trim()));
7690
8043
  } catch {
7691
8044
  ignored = /* @__PURE__ */ new Set();
7692
8045
  }
7693
8046
  const gitignorePatterns = INSTRUCTION_FILES.filter((p) => ignored.has(p) || ignored.has(`/${p}`));
7694
- const canonical2 = existsSync(join11(anchorReal, "agents", canonicalName, CANONICAL_FILE));
8047
+ const canonical2 = existsSync(join12(anchorReal, "agents", canonicalName, CANONICAL_FILE));
7695
8048
  let viewLink = false;
7696
8049
  const viewPath = manifest.workspace.view;
7697
8050
  if (viewPath !== void 0) {
7698
8051
  try {
7699
- lstatSync(join11(resolveViewDir(repositoryRoot, viewPath), canonicalName));
8052
+ lstatSync(join12(resolveViewDir(repositoryRoot, viewPath), canonicalName));
7700
8053
  viewLink = true;
7701
8054
  } catch {
7702
8055
  }
@@ -7710,11 +8063,11 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
7710
8063
  };
7711
8064
  }
7712
8065
  function teardownExpectedTargets(repoReal, anchorReal, canonicalName) {
7713
- const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8066
+ const canonicalFile = join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
7714
8067
  return expectedSymlinkTargets(repoReal, canonicalFile);
7715
8068
  }
7716
8069
  function viewLinkPointsAt(viewDir, name, repoReal) {
7717
- const filePath = join11(viewDir, name);
8070
+ const filePath = join12(viewDir, name);
7718
8071
  try {
7719
8072
  if (!lstatSync(filePath).isSymbolicLink()) return false;
7720
8073
  const target = readlinkSync(filePath);
@@ -7725,7 +8078,7 @@ function viewLinkPointsAt(viewDir, name, repoReal) {
7725
8078
  }
7726
8079
  }
7727
8080
  function viewLinkPointsAtPath(viewDir, name, expectedRepoPath) {
7728
- const filePath = join11(viewDir, name);
8081
+ const filePath = join12(viewDir, name);
7729
8082
  try {
7730
8083
  if (!lstatSync(filePath).isSymbolicLink()) return false;
7731
8084
  const target = readlinkSync(filePath);
@@ -7776,7 +8129,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
7776
8129
  if (!isAnchor) {
7777
8130
  if (repoReal !== void 0) {
7778
8131
  for (const spec of teardownExpectedTargets(repoReal, anchorReal, canonicalName)) {
7779
- const { state, actualTarget } = inspectSymlink(join11(repoReal, spec.name), spec.target);
8132
+ const { state, actualTarget } = inspectSymlink(join12(repoReal, spec.name), spec.target);
7780
8133
  if (isSelf) {
7781
8134
  if (state !== "missing")
7782
8135
  items.push({
@@ -7813,7 +8166,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
7813
8166
  }
7814
8167
  let ignored;
7815
8168
  try {
7816
- ignored = new Set(readGitignoreLines(join11(repoReal, ".gitignore")).map((l) => l.trim()));
8169
+ ignored = new Set(readGitignoreLines(join12(repoReal, ".gitignore")).map((l) => l.trim()));
7817
8170
  for (const p of INSTRUCTION_FILES) {
7818
8171
  if (ignored.has(p) || ignored.has(`/${p}`)) {
7819
8172
  items.push({
@@ -7836,7 +8189,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
7836
8189
  const viewPath = manifest.workspace.view;
7837
8190
  if (viewPath !== void 0) {
7838
8191
  const viewDir = resolveViewDir(repositoryRoot, viewPath);
7839
- const linkPath = join11(viewDir, canonicalName);
8192
+ const linkPath = join12(viewDir, canonicalName);
7840
8193
  let isLink = false;
7841
8194
  try {
7842
8195
  isLink = lstatSync(linkPath).isSymbolicLink();
@@ -7862,8 +8215,8 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
7862
8215
  else items.push({ kind: "view-symlink", label: canonicalName, state: "removable" });
7863
8216
  }
7864
8217
  }
7865
- const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
7866
- const canonicalLabel = join11("agents", canonicalName, CANONICAL_FILE);
8218
+ const canonicalFile = join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8219
+ const canonicalLabel = join12("agents", canonicalName, CANONICAL_FILE);
7867
8220
  let canonicalIsLink = false;
7868
8221
  try {
7869
8222
  canonicalIsLink = lstatSync(canonicalFile).isSymbolicLink();
@@ -7967,12 +8320,12 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
7967
8320
  );
7968
8321
  for (const item of removable.filter((i) => i.kind === "instruction-symlink")) {
7969
8322
  const expected = expectedByName.get(item.label);
7970
- if (repoReal === null || expected === void 0 || inspectSymlink(join11(repoReal, item.label), expected).state !== "correct") {
8323
+ if (repoReal === null || expected === void 0 || inspectSymlink(join12(repoReal, item.label), expected).state !== "correct") {
7971
8324
  changed(item.label);
7972
8325
  continue;
7973
8326
  }
7974
8327
  try {
7975
- unlinkSync(join11(repoReal, item.label));
8328
+ unlinkSync(join12(repoReal, item.label));
7976
8329
  removed.push(item.label);
7977
8330
  } catch (error) {
7978
8331
  failed.push({ label: item.label, message: failureReason(error) });
@@ -7991,7 +8344,7 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
7991
8344
  continue;
7992
8345
  }
7993
8346
  try {
7994
- unlinkSync(join11(viewDir, item.label));
8347
+ unlinkSync(join12(viewDir, item.label));
7995
8348
  removed.push(`view/${item.label}`);
7996
8349
  } catch (error) {
7997
8350
  failed.push({ label: `view/${item.label}`, message: failureReason(error) });
@@ -7999,7 +8352,7 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
7999
8352
  }
8000
8353
  const NOFOLLOW = fsConstants.O_NOFOLLOW ?? 0;
8001
8354
  for (const item of removable.filter((i) => i.kind === "canonical-block")) {
8002
- const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8355
+ const canonicalFile = join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8003
8356
  try {
8004
8357
  if (lstatSync(canonicalFile).isSymbolicLink()) {
8005
8358
  changed(item.label);
@@ -8101,7 +8454,7 @@ async function doRunProjectTeardown(target, options, ctx = {}) {
8101
8454
  const cwd = ctx.cwd ?? process.cwd();
8102
8455
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project teardown");
8103
8456
  const paths = basouPaths13(repositoryRoot);
8104
- const manifest = await readManifest8(paths);
8457
+ const manifest = await readManifest9(paths);
8105
8458
  const plan = gatherRepoTeardown(repositoryRoot, manifest, target);
8106
8459
  const willApply = options.apply === true && !plan.isAnchor && plan.removableCount > 0;
8107
8460
  const { removed, failed } = willApply ? applyRepoTeardown(repositoryRoot, manifest, plan) : { removed: [], failed: [] };
@@ -8143,7 +8496,7 @@ async function doRunProjectArchive(target, options, ctx) {
8143
8496
  const cwd = ctx.cwd ?? process.cwd();
8144
8497
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project archive");
8145
8498
  const paths = basouPaths13(repositoryRoot);
8146
- const manifest = await readManifest8(paths);
8499
+ const manifest = await readManifest9(paths);
8147
8500
  const roster = manifest.repos ?? [];
8148
8501
  let targetIsAnchor = false;
8149
8502
  try {
@@ -8273,12 +8626,12 @@ function gatherRenameWiring(repositoryRoot, manifest, oldBasename) {
8273
8626
  } catch {
8274
8627
  return { canonicalDirOld: false, viewLinkOld: false };
8275
8628
  }
8276
- const canonicalDirOld = existsSync(join11(anchorReal, "agents", oldBasename));
8629
+ const canonicalDirOld = existsSync(join12(anchorReal, "agents", oldBasename));
8277
8630
  let viewLinkOld = false;
8278
8631
  const viewPath = manifest.workspace.view;
8279
8632
  if (viewPath !== void 0) {
8280
8633
  try {
8281
- lstatSync(join11(resolveViewDir(repositoryRoot, viewPath), oldBasename));
8634
+ lstatSync(join12(resolveViewDir(repositoryRoot, viewPath), oldBasename));
8282
8635
  viewLinkOld = true;
8283
8636
  } catch {
8284
8637
  }
@@ -8300,7 +8653,7 @@ async function doRunProjectRename(oldPath, newPath, options, ctx) {
8300
8653
  const cwd = ctx.cwd ?? process.cwd();
8301
8654
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project rename");
8302
8655
  const paths = basouPaths13(repositoryRoot);
8303
- const manifest = await readManifest8(paths);
8656
+ const manifest = await readManifest9(paths);
8304
8657
  const roster = manifest.repos ?? [];
8305
8658
  let oldIsAnchor = false;
8306
8659
  try {
@@ -8574,7 +8927,7 @@ async function doRunProjectDerive(options, ctx) {
8574
8927
  const cwd = ctx.cwd ?? process.cwd();
8575
8928
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project derive");
8576
8929
  const paths = basouPaths13(repositoryRoot);
8577
- const manifest = await readManifest8(paths);
8930
+ const manifest = await readManifest9(paths);
8578
8931
  if (manifest.repos === void 0 || manifest.repos.length === 0) {
8579
8932
  console.log(
8580
8933
  "# Generate project wiring in one pass (declaration \u2192 wiring)\n\n\u2139\uFE0F No repo roster declared (manifest `repos`). Declare one first with `basou project new` (new project) or `basou project adopt` (bootstrap from existing source_roots)."
@@ -8615,7 +8968,7 @@ async function doRunProjectSeedAnchor(options, ctx) {
8615
8968
  const cwd = ctx.cwd ?? process.cwd();
8616
8969
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project derive");
8617
8970
  const paths = basouPaths13(repositoryRoot);
8618
- const manifest = await readManifest8(paths);
8971
+ const manifest = await readManifest9(paths);
8619
8972
  const roster = manifest.repos ?? [];
8620
8973
  console.log("# Anchor instruction-file seed (the planning master's own AGENTS.md)");
8621
8974
  console.log("");
@@ -8623,7 +8976,7 @@ async function doRunProjectSeedAnchor(options, ctx) {
8623
8976
  console.log("\u2139\uFE0F No repo roster declared \u2014 nothing to seed.");
8624
8977
  return;
8625
8978
  }
8626
- const anchorDoc = join11(repositoryRoot, CANONICAL_FILE);
8979
+ const anchorDoc = join12(repositoryRoot, CANONICAL_FILE);
8627
8980
  if (pathPresent(anchorDoc)) {
8628
8981
  console.log(
8629
8982
  `\u2705 The anchor's own \`${CANONICAL_FILE}\` already exists \u2014 hand-maintained, left untouched.`
@@ -8685,7 +9038,7 @@ function regularFileSpokes(repoReal) {
8685
9038
  const out = [];
8686
9039
  for (const spoke of ["CLAUDE.md", ".github/copilot-instructions.md"]) {
8687
9040
  try {
8688
- const st = lstatSync(join11(repoReal, spoke));
9041
+ const st = lstatSync(join12(repoReal, spoke));
8689
9042
  if (!st.isSymbolicLink() && st.isFile()) out.push(spoke);
8690
9043
  } catch {
8691
9044
  }
@@ -8731,8 +9084,8 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argAbs, argReal, vie
8731
9084
  };
8732
9085
  }
8733
9086
  const isAnchor = argReal === anchorReal;
8734
- const reachable = existsSync(join11(argReal, ".git"));
8735
- const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
9087
+ const reachable = existsSync(join12(argReal, ".git"));
9088
+ const canonicalFile = join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8736
9089
  return {
8737
9090
  path,
8738
9091
  declared,
@@ -8741,15 +9094,15 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argAbs, argReal, vie
8741
9094
  reachable,
8742
9095
  canonicalName,
8743
9096
  ...viewCanonicalName !== void 0 ? { viewCanonicalName } : {},
8744
- agentsState: inspectAgentsState(join11(argReal, CANONICAL_FILE)),
9097
+ agentsState: inspectAgentsState(join12(argReal, CANONICAL_FILE)),
8745
9098
  canonicalExists: pathPresent(canonicalFile),
8746
9099
  regularSpokes: regularFileSpokes(argReal)
8747
9100
  };
8748
9101
  }
8749
9102
  function relocateAgentsFile(repoReal, canonicalFile) {
8750
- const agentsFile = join11(repoReal, CANONICAL_FILE);
9103
+ const agentsFile = join12(repoReal, CANONICAL_FILE);
8751
9104
  try {
8752
- mkdirSync(dirname6(canonicalFile), { recursive: true });
9105
+ mkdirSync(dirname7(canonicalFile), { recursive: true });
8753
9106
  } catch (error) {
8754
9107
  return { ok: false, message: failureReason(error), partial: false };
8755
9108
  }
@@ -8809,7 +9162,7 @@ async function doRunProjectRetrofit(repo, options, ctx) {
8809
9162
  const cwd = ctx.cwd ?? process.cwd();
8810
9163
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project retrofit");
8811
9164
  const paths = basouPaths13(repositoryRoot);
8812
- const manifest = await readManifest8(paths);
9165
+ const manifest = await readManifest9(paths);
8813
9166
  const roster = manifest.repos ?? [];
8814
9167
  const anchorReal = realpathSync(repositoryRoot);
8815
9168
  const viewPath = manifest.workspace.view;
@@ -8863,7 +9216,7 @@ async function doRunProjectRetrofit(repo, options, ctx) {
8863
9216
  let failure;
8864
9217
  let partial = false;
8865
9218
  if (options.apply === true && plan.action === "relocate" && argReal !== void 0) {
8866
- const canonicalFile = join11(anchorReal, "agents", plan.canonicalName, CANONICAL_FILE);
9219
+ const canonicalFile = join12(anchorReal, "agents", plan.canonicalName, CANONICAL_FILE);
8867
9220
  const res = relocateAgentsFile(argReal, canonicalFile);
8868
9221
  if (res.ok) {
8869
9222
  applied = true;
@@ -9248,14 +9601,14 @@ import {
9248
9601
  basouPaths as basouPaths14,
9249
9602
  findErrorCode as findErrorCode10,
9250
9603
  loadPortfolioConfig as loadPortfolioConfig5,
9251
- readManifest as readManifest9
9604
+ readManifest as readManifest10
9252
9605
  } from "@basou/core";
9253
9606
  import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
9254
9607
 
9255
9608
  // src/commands/refresh-watch.ts
9256
- import { readdir as readdir2, stat as stat6 } from "fs/promises";
9609
+ import { readdir as readdir3, stat as stat6 } from "fs/promises";
9257
9610
  import { homedir as homedir9 } from "os";
9258
- import { join as join12 } from "path";
9611
+ import { join as join13 } from "path";
9259
9612
  import {
9260
9613
  assertApprovalStoreSafe as assertApprovalStoreSafe2,
9261
9614
  assertLockStoreSafe as assertLockStoreSafe4,
@@ -9267,8 +9620,8 @@ var MIN_WATCH_INTERVAL_SEC = 5;
9267
9620
  var MAX_WATCH_INTERVAL_SEC = 86400;
9268
9621
  function watchedRoots(ctx) {
9269
9622
  return [
9270
- ctx.codexSessionsDir ?? join12(homedir9(), ".codex", "sessions"),
9271
- ctx.claudeProjectsDir ?? join12(homedir9(), ".claude", "projects")
9623
+ ctx.codexSessionsDir ?? join13(homedir9(), ".codex", "sessions"),
9624
+ ctx.claudeProjectsDir ?? join13(homedir9(), ".claude", "projects")
9272
9625
  ];
9273
9626
  }
9274
9627
  async function scanSourceLogs(roots) {
@@ -9276,13 +9629,13 @@ async function scanSourceLogs(roots) {
9276
9629
  const walk = async (dir) => {
9277
9630
  let entries;
9278
9631
  try {
9279
- entries = await readdir2(dir, { withFileTypes: true });
9632
+ entries = await readdir3(dir, { withFileTypes: true });
9280
9633
  } catch (error) {
9281
9634
  if (findErrorCode9(error, "ENOENT") || findErrorCode9(error, "ENOTDIR")) return;
9282
9635
  throw new Error("Failed to read a source log directory", { cause: error });
9283
9636
  }
9284
9637
  for (const entry of entries) {
9285
- const full = join12(dir, entry.name);
9638
+ const full = join13(dir, entry.name);
9286
9639
  if (entry.isDirectory()) {
9287
9640
  await walk(full);
9288
9641
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
@@ -9565,7 +9918,7 @@ async function warnPosition(paths, result, ctx) {
9565
9918
  }
9566
9919
  async function retiredChannelNotice(paths) {
9567
9920
  try {
9568
- const manifest = await readManifest9(paths);
9921
+ const manifest = await readManifest10(paths);
9569
9922
  if (manifest.channels?.codex !== true) return null;
9570
9923
  return "codex channel: retired \u2014 the manifest's channels.codex is ignored; a Codex session now receives this workspace's position from the SessionStart hook (see `basou hook status codex`), and nothing is written to the user-global ~/.codex/AGENTS.md";
9571
9924
  } catch {
@@ -9708,7 +10061,7 @@ import {
9708
10061
  findErrorCode as findErrorCode12,
9709
10062
  findUnbindableRepos,
9710
10063
  parseReviewRecordInput,
9711
- readManifest as readManifest10,
10064
+ readManifest as readManifest11,
9712
10065
  resolveRepoRoot,
9713
10066
  sanitizePath as sanitizePath3
9714
10067
  } from "@basou/core";
@@ -9795,7 +10148,7 @@ async function doRunReviewRecord(options, ctx) {
9795
10148
  }
9796
10149
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
9797
10150
  const occurredAt = now.toISOString();
9798
- const manifest = await readManifest10(paths);
10151
+ const manifest = await readManifest11(paths);
9799
10152
  const invocationArgs = options.file !== void 0 ? [
9800
10153
  "--file",
9801
10154
  sanitizePath3(resolve11(cwd, options.file), {
@@ -10135,7 +10488,7 @@ function unattachedLines(u) {
10135
10488
  // src/commands/run.ts
10136
10489
  import { mkdir as mkdir3, readFile as readFile8, rm as rm3 } from "fs/promises";
10137
10490
  import { homedir as homedir11 } from "os";
10138
- import { join as join13 } from "path";
10491
+ import { join as join14 } from "path";
10139
10492
  import {
10140
10493
  acquireLock as acquireLock5,
10141
10494
  assertBasouRootSafe as assertBasouRootSafe13,
@@ -10152,7 +10505,7 @@ import {
10152
10505
  getSnapshot as getSnapshot2,
10153
10506
  overwriteYamlFile as overwriteYamlFile2,
10154
10507
  prefixedUlid as prefixedUlid4,
10155
- readManifest as readManifest11,
10508
+ readManifest as readManifest12,
10156
10509
  readYamlFile as readYamlFile5,
10157
10510
  resolveClaudeCodeCommand,
10158
10511
  resolveCodexCommand,
@@ -10213,15 +10566,15 @@ async function runTrackedTool(args, options, ctx, adapter) {
10213
10566
  const repoRoot = await resolveRepositoryRootInPlace(cwd, "run");
10214
10567
  const paths = basouPaths18(repoRoot);
10215
10568
  await assertBasouRootSafe13(paths.root);
10216
- const manifest = await readManifest11(paths);
10569
+ const manifest = await readManifest12(paths);
10217
10570
  await assertSessionStoreSafe3(paths);
10218
10571
  const sessionId = prefixedUlid4("ses");
10219
- const sessionDir = join13(paths.sessions, sessionId);
10572
+ const sessionDir = join14(paths.sessions, sessionId);
10220
10573
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
10221
10574
  await coreAppendChainedEvent2(paths, sessionId, event);
10222
10575
  });
10223
10576
  const startedAt = now().toISOString();
10224
- const sessionYamlPath = join13(sessionDir, "session.yaml");
10577
+ const sessionYamlPath = join14(sessionDir, "session.yaml");
10225
10578
  const session = buildInitialSession2({
10226
10579
  id: sessionId,
10227
10580
  command,
@@ -10598,7 +10951,7 @@ async function noteCodexHookStatusPreSpawn(_cwd, ctx) {
10598
10951
 
10599
10952
  // src/commands/session.ts
10600
10953
  import { readFile as readFile9 } from "fs/promises";
10601
- import { basename as basename8, isAbsolute as isAbsolute5, join as join14, relative as relative4, sep as sep3 } from "path";
10954
+ import { basename as basename8, isAbsolute as isAbsolute5, join as join15, relative as relative4, sep as sep3 } from "path";
10602
10955
  import {
10603
10956
  acquireLock as acquireLock6,
10604
10957
  appendEventToExistingSession as appendEventToExistingSession3,
@@ -10614,7 +10967,7 @@ import {
10614
10967
  loadSessionEntries as loadSessionEntries2,
10615
10968
  normalizeSessionTimestamps,
10616
10969
  readAllEvents,
10617
- readManifest as readManifest12,
10970
+ readManifest as readManifest13,
10618
10971
  readObservedDuration,
10619
10972
  readYamlFile as readYamlFile6,
10620
10973
  rechainSessionInPlace,
@@ -10730,8 +11083,8 @@ async function doRunSessionShow(idInput, options, ctx) {
10730
11083
  const paths = basouPaths19(repositoryRoot);
10731
11084
  await assertWorkspaceInitialized12(paths.root);
10732
11085
  const sessionId = await resolveSessionId3(paths, idInput);
10733
- const sessionDir = join14(paths.sessions, sessionId);
10734
- const sessionYamlPath = join14(sessionDir, "session.yaml");
11086
+ const sessionDir = join15(paths.sessions, sessionId);
11087
+ const sessionYamlPath = join15(sessionDir, "session.yaml");
10735
11088
  let session;
10736
11089
  try {
10737
11090
  const raw = await readYamlFile6(sessionYamlPath);
@@ -11041,7 +11394,7 @@ async function doRunSessionImport(options, ctx) {
11041
11394
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "import");
11042
11395
  const paths = basouPaths19(repositoryRoot);
11043
11396
  await assertWorkspaceInitialized12(paths.root);
11044
- const manifest = await readManifest12(paths);
11397
+ const manifest = await readManifest13(paths);
11045
11398
  const rawBody = await readInputFile(options.from);
11046
11399
  const json = parseJsonStrict(rawBody);
11047
11400
  const parsed = SessionImportPayloadSchema2.safeParse(json);
@@ -11427,7 +11780,7 @@ import {
11427
11780
  basouPaths as basouPaths21,
11428
11781
  buildStatusSnapshot,
11429
11782
  findErrorCode as findErrorCode15,
11430
- readManifest as readManifest13,
11783
+ readManifest as readManifest14,
11431
11784
  writeStatus
11432
11785
  } from "@basou/core";
11433
11786
  function registerStatusCommand(program) {
@@ -11457,7 +11810,7 @@ async function doRunStatus(options, ctx) {
11457
11810
  }
11458
11811
  let manifest;
11459
11812
  try {
11460
- manifest = await readManifest13(paths);
11813
+ manifest = await readManifest14(paths);
11461
11814
  } catch (error) {
11462
11815
  if (findErrorCode15(error, "ENOENT")) {
11463
11816
  throw new Error("Workspace not initialized. Run 'basou init' first.");
@@ -11498,7 +11851,7 @@ function formatVersionGateMessage(error) {
11498
11851
 
11499
11852
  // src/commands/task.ts
11500
11853
  import { readFile as readFile10 } from "fs/promises";
11501
- import { join as join15 } from "path";
11854
+ import { join as join16 } from "path";
11502
11855
  import {
11503
11856
  archiveTask,
11504
11857
  assertBasouRootSafe as assertBasouRootSafe17,
@@ -11512,7 +11865,7 @@ import {
11512
11865
  loadSessionEntries as loadSessionEntries3,
11513
11866
  loadTaskEntries,
11514
11867
  prefixedUlid as prefixedUlid5,
11515
- readManifest as readManifest14,
11868
+ readManifest as readManifest15,
11516
11869
  readTaskFile,
11517
11870
  readTaskFileWithArchiveFallback,
11518
11871
  reconcileAllTasks,
@@ -11639,7 +11992,7 @@ async function doRunTaskNew(options, ctx) {
11639
11992
  });
11640
11993
  return;
11641
11994
  }
11642
- const manifest = await readManifest14(paths);
11995
+ const manifest = await readManifest15(paths);
11643
11996
  const result = await createTaskWithEvent({
11644
11997
  mode: "ad-hoc",
11645
11998
  paths,
@@ -11825,7 +12178,7 @@ async function doRunTaskShow(idInput, options, ctx) {
11825
12178
  const events = [];
11826
12179
  const linkedSessionIds = new Set(doc.task.task.linked_sessions);
11827
12180
  for (const s of sessions) {
11828
- const sessionDir = join15(paths.sessions, s.sessionId);
12181
+ const sessionDir = join16(paths.sessions, s.sessionId);
11829
12182
  try {
11830
12183
  for await (const ev of replayEvents3(sessionDir, {
11831
12184
  onWarning: (w) => printReplayWarning(w, s.sessionId)
@@ -11987,7 +12340,7 @@ async function doRunTaskStatus(taskIdInput, newStatusInput, options, ctx) {
11987
12340
  });
11988
12341
  return;
11989
12342
  }
11990
- const manifest = await readManifest14(paths);
12343
+ const manifest = await readManifest15(paths);
11991
12344
  const result = await updateTaskStatusWithEvent({
11992
12345
  mode: "ad-hoc",
11993
12346
  paths,
@@ -12041,7 +12394,7 @@ async function doRunTaskReconcile(options, ctx) {
12041
12394
  const paths = basouPaths22(repositoryRoot);
12042
12395
  await assertWorkspaceInitialized14(paths.root);
12043
12396
  await assertLockStoreSafe6(paths);
12044
- const manifest = await readManifest14(paths);
12397
+ const manifest = await readManifest15(paths);
12045
12398
  const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
12046
12399
  const write = options.write === true;
12047
12400
  const verbose = isVerbose(options);
@@ -12221,7 +12574,7 @@ async function doRunTaskRefreshLinkage(taskIdInput, options, ctx) {
12221
12574
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "refresh-linkage");
12222
12575
  const paths = basouPaths22(repositoryRoot);
12223
12576
  await assertWorkspaceInitialized14(paths.root);
12224
- const manifest = await readManifest14(paths);
12577
+ const manifest = await readManifest15(paths);
12225
12578
  const taskId = await resolveTaskId2(paths, taskIdInput);
12226
12579
  const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
12227
12580
  const write = options.write === true;
@@ -12301,7 +12654,7 @@ async function doRunTaskEdit(taskIdInput, options, ctx) {
12301
12654
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "edit");
12302
12655
  const paths = basouPaths22(repositoryRoot);
12303
12656
  await assertWorkspaceInitialized14(paths.root);
12304
- const manifest = await readManifest14(paths);
12657
+ const manifest = await readManifest15(paths);
12305
12658
  const taskId = await resolveTaskId2(paths, taskIdInput);
12306
12659
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
12307
12660
  const occurredAt = now.toISOString();
@@ -12357,7 +12710,7 @@ async function doRunTaskDelete(taskIdInput, options, ctx) {
12357
12710
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "delete");
12358
12711
  const paths = basouPaths22(repositoryRoot);
12359
12712
  await assertWorkspaceInitialized14(paths.root);
12360
- const manifest = await readManifest14(paths);
12713
+ const manifest = await readManifest15(paths);
12361
12714
  const taskId = await resolveTaskId2(paths, taskIdInput);
12362
12715
  if (options.yes !== true) {
12363
12716
  await confirmDestructiveAction("delete", taskId);
@@ -12402,7 +12755,7 @@ async function doRunTaskArchive(taskIdInput, options, ctx) {
12402
12755
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "archive");
12403
12756
  const paths = basouPaths22(repositoryRoot);
12404
12757
  await assertWorkspaceInitialized14(paths.root);
12405
- const manifest = await readManifest14(paths);
12758
+ const manifest = await readManifest15(paths);
12406
12759
  const taskId = await resolveTaskId2(paths, taskIdInput);
12407
12760
  if (options.yes !== true) {
12408
12761
  await confirmDestructiveAction("archive", taskId);
@@ -12720,23 +13073,23 @@ async function assertWorkspaceInitialized15(basouRoot) {
12720
13073
  // src/commands/view.ts
12721
13074
  import { spawn } from "child_process";
12722
13075
  import { createHash as createHash2 } from "crypto";
12723
- import { basename as basename11, dirname as dirname8, join as join19, resolve as resolve14 } from "path";
13076
+ import { basename as basename11, dirname as dirname9, join as join20, resolve as resolve14 } from "path";
12724
13077
  import {
12725
13078
  assertBasouRootSafe as assertBasouRootSafe19,
12726
13079
  basouPaths as basouPaths25,
12727
13080
  findErrorCode as findErrorCode19,
12728
13081
  loadPortfolioConfig as loadPortfolioConfig6,
12729
- readManifest as readManifest18
13082
+ readManifest as readManifest19
12730
13083
  } from "@basou/core";
12731
13084
  import { InvalidArgumentError as InvalidArgumentError8 } from "commander";
12732
13085
 
12733
13086
  // src/lib/portfolio-coverage.ts
12734
13087
  import { createReadStream as createReadStream2 } from "fs";
12735
- import { readdir as readdir3, stat as stat7 } from "fs/promises";
13088
+ import { readdir as readdir4, stat as stat7 } from "fs/promises";
12736
13089
  import { homedir as homedir12 } from "os";
12737
- import { basename as basename9, dirname as dirname7, join as join16 } from "path";
13090
+ import { basename as basename9, dirname as dirname8, join as join17 } from "path";
12738
13091
  import { createInterface as createInterface2 } from "readline";
12739
- import { basouPaths as basouPaths24, readManifest as readManifest15 } from "@basou/core";
13092
+ import { basouPaths as basouPaths24, readManifest as readManifest16 } from "@basou/core";
12740
13093
  function uncapturedTotal(result) {
12741
13094
  return result.groups.reduce((sum, g) => sum + g.logs, 0) + result.unplaceable;
12742
13095
  }
@@ -12746,8 +13099,8 @@ async function checkPortfolioCoverage(workspaces, ctx = {}) {
12746
13099
  ctx.portfolioConfigPath
12747
13100
  );
12748
13101
  const listedDirs = new Set([...roots].map((root) => encodeProjectDir(root)));
12749
- const claudeProjectsDir = ctx.claudeProjectsDir ?? join16(homedir12(), ".claude", "projects");
12750
- const codexSessionsDir = ctx.codexSessionsDir ?? join16(homedir12(), ".codex", "sessions");
13102
+ const claudeProjectsDir = ctx.claudeProjectsDir ?? join17(homedir12(), ".claude", "projects");
13103
+ const codexSessionsDir = ctx.codexSessionsDir ?? join17(homedir12(), ".codex", "sessions");
12751
13104
  const tally = new Tally(roots);
12752
13105
  const absentTrees = [];
12753
13106
  const claudeFiles = await listClaudeTranscripts(claudeProjectsDir);
@@ -12758,7 +13111,7 @@ async function checkPortfolioCoverage(workspaces, ctx = {}) {
12758
13111
  file,
12759
13112
  "claude-code",
12760
13113
  claudeTranscriptCwd,
12761
- listedDirs.has(basename9(dirname7(file)))
13114
+ listedDirs.has(basename9(dirname8(file)))
12762
13115
  );
12763
13116
  }
12764
13117
  }
@@ -12785,7 +13138,7 @@ async function collectDeclaredRoots(workspaces, portfolioConfigPath) {
12785
13138
  }
12786
13139
  let resolved;
12787
13140
  try {
12788
- const manifest = await readManifest15(basouPaths24(importRoot));
13141
+ const manifest = await readManifest16(basouPaths24(importRoot));
12789
13142
  resolved = resolveSourceRoots({
12790
13143
  projectFlags: [],
12791
13144
  manifest,
@@ -12885,7 +13238,7 @@ async function isDirEntry(parent, entry) {
12885
13238
  if (entry.isDirectory()) return true;
12886
13239
  if (!entry.isSymbolicLink()) return false;
12887
13240
  try {
12888
- return (await stat7(join16(parent, entry.name))).isDirectory();
13241
+ return (await stat7(join17(parent, entry.name))).isDirectory();
12889
13242
  } catch {
12890
13243
  return false;
12891
13244
  }
@@ -12893,22 +13246,22 @@ async function isDirEntry(parent, entry) {
12893
13246
  async function listClaudeTranscripts(projectsRoot) {
12894
13247
  let entries;
12895
13248
  try {
12896
- entries = await readdir3(projectsRoot, { withFileTypes: true });
13249
+ entries = await readdir4(projectsRoot, { withFileTypes: true });
12897
13250
  } catch {
12898
13251
  return void 0;
12899
13252
  }
12900
13253
  const files = [];
12901
13254
  for (const entry of entries) {
12902
13255
  if (!await isDirEntry(projectsRoot, entry)) continue;
12903
- const full = join16(projectsRoot, entry.name);
13256
+ const full = join17(projectsRoot, entry.name);
12904
13257
  let names;
12905
13258
  try {
12906
- names = await readdir3(full);
13259
+ names = await readdir4(full);
12907
13260
  } catch {
12908
13261
  continue;
12909
13262
  }
12910
13263
  for (const name of names) {
12911
- if (name.endsWith(".jsonl")) files.push(join16(full, name));
13264
+ if (name.endsWith(".jsonl")) files.push(join17(full, name));
12912
13265
  }
12913
13266
  }
12914
13267
  return files.sort();
@@ -12918,12 +13271,12 @@ async function listCodexRollouts(sessionsRoot) {
12918
13271
  const walk = async (dir) => {
12919
13272
  let entries;
12920
13273
  try {
12921
- entries = await readdir3(dir, { withFileTypes: true });
13274
+ entries = await readdir4(dir, { withFileTypes: true });
12922
13275
  } catch {
12923
13276
  return;
12924
13277
  }
12925
13278
  for (const entry of entries) {
12926
- const full = join16(dir, entry.name);
13279
+ const full = join17(dir, entry.name);
12927
13280
  if (entry.isDirectory()) await walk(full);
12928
13281
  else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) {
12929
13282
  found.push(full);
@@ -12931,7 +13284,7 @@ async function listCodexRollouts(sessionsRoot) {
12931
13284
  }
12932
13285
  };
12933
13286
  try {
12934
- await readdir3(sessionsRoot);
13287
+ await readdir4(sessionsRoot);
12935
13288
  } catch {
12936
13289
  return void 0;
12937
13290
  }
@@ -13054,9 +13407,9 @@ function inertLines(result) {
13054
13407
  // src/lib/portfolio-safety.ts
13055
13408
  import { execFile as execFile2 } from "child_process";
13056
13409
  import { lstat as lstat3, realpath as realpath5 } from "fs/promises";
13057
- import { isAbsolute as isAbsolute6, join as join17, relative as relative5, resolve as resolve12, sep as sep4 } from "path";
13410
+ import { isAbsolute as isAbsolute6, join as join18, relative as relative5, resolve as resolve12, sep as sep4 } from "path";
13058
13411
  import { promisify as promisify2 } from "util";
13059
- import { readManifest as readManifest16 } from "@basou/core";
13412
+ import { readManifest as readManifest17 } from "@basou/core";
13060
13413
  var execFileAsync2 = promisify2(execFile2);
13061
13414
  function errorCode2(error) {
13062
13415
  return error instanceof Error ? error.code : void 0;
@@ -13078,7 +13431,7 @@ function isBasouPath(p) {
13078
13431
  async function inspectRepo(repoPath) {
13079
13432
  let hasEntry = false;
13080
13433
  try {
13081
- await lstat3(join17(repoPath, ".basou"));
13434
+ await lstat3(join18(repoPath, ".basou"));
13082
13435
  hasEntry = true;
13083
13436
  } catch (error) {
13084
13437
  if (errorCode2(error) !== "ENOENT") {
@@ -13112,7 +13465,7 @@ async function checkPortfolioSafety(workspaces) {
13112
13465
  let viewPath;
13113
13466
  let isMaster = false;
13114
13467
  try {
13115
- const manifest = await readManifest16(ws.paths);
13468
+ const manifest = await readManifest17(ws.paths);
13116
13469
  sourceRoots = manifest.import?.source_roots ?? [];
13117
13470
  viewPath = manifest.workspace.view;
13118
13471
  isMaster = true;
@@ -13232,8 +13585,9 @@ function formatSafetyReport(result) {
13232
13585
 
13233
13586
  // src/lib/view-server.ts
13234
13587
  import { createServer } from "http";
13235
- import { basename as basename10, join as join18, resolve as resolve13 } from "path";
13588
+ import { basename as basename10, join as join19, resolve as resolve13 } from "path";
13236
13589
  import {
13590
+ boardLivePage,
13237
13591
  boardPage,
13238
13592
  boardPageStrings,
13239
13593
  boardPageUnavailable,
@@ -13246,7 +13600,7 @@ import {
13246
13600
  loadSessionEntries as loadSessionEntries4,
13247
13601
  loadTaskEntries as loadTaskEntries2,
13248
13602
  readAllEvents as readAllEvents2,
13249
- readManifest as readManifest17,
13603
+ readManifest as readManifest18,
13250
13604
  readMarkdownFile as readMarkdownFile9,
13251
13605
  readSessionYaml as readSessionYaml3,
13252
13606
  readTaskFile as readTaskFile2,
@@ -13375,6 +13729,7 @@ var BOARD_HTML = `<!doctype html>
13375
13729
  if (k === 'text') node.textContent = attrs[k];
13376
13730
  else if (k === 'class') node.className = attrs[k];
13377
13731
  else if (k === 'onchange') node.addEventListener('change', attrs[k]);
13732
+ else if (k === 'onclick') node.addEventListener('click', attrs[k]);
13378
13733
  else node.setAttribute(k, attrs[k]);
13379
13734
  });
13380
13735
  }
@@ -13468,7 +13823,11 @@ var BOARD_HTML = `<!doctype html>
13468
13823
  clear(nav);
13469
13824
  var page = d.page;
13470
13825
  var list = page.records || [];
13471
- if (list.length === 0) return;
13826
+ if (list.length > 0) recordLinks(nav, page, list);
13827
+ nav.appendChild(el('a', { href: '/board?live=1', text: S.live.measureNow }));
13828
+ }
13829
+
13830
+ function recordLinks(nav, page, list) {
13472
13831
  var link = function (id, label) {
13473
13832
  return el('a', { href: '/board?record=' + encodeURIComponent(id), text: label });
13474
13833
  };
@@ -13949,8 +14308,12 @@ var BOARD_HTML = `<!doctype html>
13949
14308
  not_found: S.unavailable.notFound, not_json: S.unavailable.notJson,
13950
14309
  unknown_version: S.unavailable.unknownVersion, not_a_record: S.unavailable.notARecord
13951
14310
  };
13952
- var quiet = page.why === 'no_board' || page.why === 'no_records';
13953
- setStatus(fill(messages[page.why] || page.why, { record: page.id || '', version: page.version }), !quiet);
14311
+ // With no board or no record yet, draw what basou measures now instead.
14312
+ if (page.why === 'no_board' || page.why === 'no_records') {
14313
+ loadLive(page.why);
14314
+ return;
14315
+ }
14316
+ setStatus(fill(messages[page.why] || page.why, { record: page.id || '', version: page.version }), true);
13954
14317
  return;
13955
14318
  }
13956
14319
  setStatus('', false);
@@ -13959,17 +14322,153 @@ var BOARD_HTML = `<!doctype html>
13959
14322
  .forEach(function (node) { root.appendChild(node); });
13960
14323
  }
13961
14324
 
13962
- var record = new URLSearchParams(location.search).get('record');
13963
- setStatus('...', false);
13964
- fetch('/api/board' + (record ? '/' + encodeURIComponent(record) : ''))
13965
- .then(function (res) {
14325
+ // The board measured now: the sections basou measures with nothing declared
14326
+ // or judged. why is the reason there was no record to draw, or null when
14327
+ // asked for with ?live=1.
14328
+ function liveTile(k, v, sub) {
14329
+ return el('div', { class: 'tile' }, [
14330
+ el('div', { class: 'k', text: k }), el('div', { class: 'v', text: v }),
14331
+ sub ? el('div', { class: 's', text: sub }) : null
14332
+ ]);
14333
+ }
14334
+ function liveHeading(l, why) {
14335
+ var h = l.heading;
14336
+ var measured = h.complete ? S.heading.complete : fill(S.heading.incomplete, { n: h.not_found });
14337
+ var again = el('button', { type: 'button', text: S.live.remeasure, onclick: function () { loadLive(why); } });
14338
+ return el('header', null, [
14339
+ el('h1', { text: h.title }),
14340
+ el('div', { class: 'stamp' }, [fill(S.live.measuredAt, { at: when(h.measured_at) }) + ' / ' + measured + ' ', again])
14341
+ ]);
14342
+ }
14343
+ // Whether the measurement says why the value at a place was not measured.
14344
+ function missingIn(l) {
14345
+ return function (at) { return l.footnotes.not_found.some(function (n) { return n.at === at; }); };
14346
+ }
14347
+ function liveRepos(l) {
14348
+ var missing = missingIn(l);
14349
+ var labels = [S.live.repo, S.live.branch, S.live.head, S.live.lastCommit, S.live.commits, S.live.uncommitted, S.live.behindMain];
14350
+ var head = el('tr', null, labels.map(function (label, i) { return el('th', { class: i >= 4 ? 'n' : '', text: label }); }));
14351
+ var rows = l.repos.map(function (r) {
14352
+ var at = 'repos[' + r.path + ']';
14353
+ // A null the measurement says it could not measure, or one that means
14354
+ // so (a detached HEAD, no commit yet, no origin/main), drawn as '-'.
14355
+ var value = function (field, v, show) {
14356
+ if (v !== null) return show(v);
14357
+ return missing(at) || missing(at + '.' + field) ? S.tiles.notMeasured : '-';
14358
+ };
14359
+ return el('tr', null, [
14360
+ el('td', { text: r.name === null ? r.path : r.name }),
14361
+ el('td', { text: value('branch', r.branch, String) }),
14362
+ el('td', { text: value('head', r.head, function (h) { return h.slice(0, 7); }) }),
14363
+ el('td', { text: value('last_commit', r.last_commit, when) }),
14364
+ el('td', { class: 'n', text: num(r.commits) }),
14365
+ el('td', { class: 'n', text: num(r.uncommitted) }),
14366
+ el('td', { class: 'n', text: value('behind_main', r.behind_main, num) })
14367
+ ]);
14368
+ });
14369
+ return section(S.live.repos, false, [el('div', { class: 'scroll' }, [el('table', { class: 'grid' }, [head].concat(rows))])]);
14370
+ }
14371
+ function liveTrail(l) {
14372
+ var t = l.trail;
14373
+ var i = l.integrity;
14374
+ var f = l.freshness;
14375
+ var missing = missingIn(l);
14376
+ var newest = f.newest_session_at !== null ? when(f.newest_session_at)
14377
+ : missing('freshness.newest_session_at') ? S.tiles.notMeasured : S.live.noSession;
14378
+ var unimported = f.unimported === null ? S.live.unimportedNotMeasured : fill(S.live.unimported, {
14379
+ new: f.unimported.new, updated: f.unimported.updated, unverifiable: f.unimported.unverifiable
14380
+ });
14381
+ var counts = function (byKey) {
14382
+ if (byKey === null) return S.tiles.notMeasured;
14383
+ return Object.keys(byKey).map(function (k) { return k + ' ' + num(byKey[k]); }).join(' / ');
14384
+ };
14385
+ var tiles = el('div', { class: 'tiles' }, [
14386
+ liveTile(S.live.decisions, num(t.decisions_live), t.decisions_all === null ? null : fill(S.live.decisionsAll, { n: num(t.decisions_all) })),
14387
+ liveTile(S.tiles.openTracks, t.tracks_open === null ? S.tiles.notMeasured : num(t.tracks_open.length), null),
14388
+ liveTile(S.tiles.sessions, num(i.sessions), i.not_verified === null ? null : fill(S.tiles.sessionsNotVerified, { n: num(i.not_verified) })),
14389
+ liveTile(S.live.reviewGaps, num(l.review_gaps.gaps), null),
14390
+ liveTile(S.live.newestSession, newest, unimported)
14391
+ ]);
14392
+ var tracks = t.tracks_open === null ? el('p', { class: 'muted', text: S.tiles.notMeasured })
14393
+ : t.tracks_open.length === 0 ? el('p', { class: 'muted', text: S.live.noTracks })
14394
+ : el('ul', null, t.tracks_open.map(function (tr) {
14395
+ return el('li', null, [el('span', { class: 'muted', text: tr.id + ' ' }), tr.title]);
14396
+ }));
14397
+ var breakdown = el('div', { class: 'muted' }, [
14398
+ el('div', { text: fill(S.live.byStatus, { list: counts(i.by_status) }) }),
14399
+ el('div', { text: fill(S.live.byVerdict, { list: counts(l.review_gaps.by_verdict) }) })
14400
+ ]);
14401
+ return section(S.live.trail, false, [tiles, breakdown, el('h3', { text: S.live.tracks }), tracks]);
14402
+ }
14403
+ function liveComponents(l) {
14404
+ var c = l.components;
14405
+ var body = c === null ? el('p', { class: 'muted', text: S.tiles.notMeasured })
14406
+ : c.length === 0 ? el('p', { class: 'muted', text: S.live.noComponents })
14407
+ : el('ul', null, c.map(function (x) {
14408
+ return el('li', null, [el('code', { text: x.key }), el('span', { class: 'muted', text: ' ' + x.kinds.join(', ') })]);
14409
+ }));
14410
+ return section(S.live.components, false, [body]);
14411
+ }
14412
+ function liveFootnotes(l, why) {
14413
+ var f = l.footnotes;
14414
+ var items = [el('li', { text: S.live.note }), el('li', { text: S.live.judged })];
14415
+ if (why === 'no_board') items.push(el('li', { text: S.unavailable.noBoard }));
14416
+ if (f.not_found.length > 0) {
14417
+ items.push(el('li', null, [el('b', { text: S.footnotes.notMeasured }), el('ul', null, f.not_found.map(function (n) {
14418
+ return el('li', { text: n.at + ': ' + n.reason });
14419
+ }))]));
14420
+ }
14421
+ var build = f.measured_with.build === null ? '' : ' (' + f.measured_with.build + ')';
14422
+ items.push(el('li', { text: fill(S.footnotes.measuredWith, { basou: f.measured_with.basou, build: build }) }));
14423
+ return section(S.sections.footnotes, false, [el('ul', { class: 'foot' }, items)]);
14424
+ }
14425
+ var liveDrawn = 0;
14426
+ function drawLive(d, why) {
14427
+ S = d.strings;
14428
+ lang = d.language;
14429
+ document.documentElement.lang = lang;
14430
+ document.title = S.pageTitle;
14431
+ var drawn = ++liveDrawn;
14432
+ var nav = $('records');
14433
+ clear(nav);
14434
+ // Asked for with ?live=1: lead back to the records, when there are any.
14435
+ if (why === null) {
14436
+ getJson('/api/board').then(function (b) {
14437
+ var list = b && b.page && b.page.records ? b.page.records : [];
14438
+ if (drawn === liveDrawn && list.length > 0) nav.appendChild(el('a', { href: '/board', text: S.live.toRecords }));
14439
+ }).catch(function () {});
14440
+ }
14441
+ var root = $('board');
14442
+ clear(root);
14443
+ setStatus('', false);
14444
+ var l = d.live;
14445
+ [liveHeading(l, why), effort(l), liveRepos(l), liveTrail(l), liveComponents(l), liveFootnotes(l, why)]
14446
+ .forEach(function (node) { root.appendChild(node); });
14447
+ }
14448
+
14449
+ function getJson(url) {
14450
+ return fetch(url).then(function (res) {
13966
14451
  return res.json().then(function (body) {
13967
14452
  if (!res.ok) throw new Error(body && body.error ? body.error : String(res.status));
13968
14453
  return body;
13969
14454
  });
13970
- })
13971
- .then(draw)
13972
- .catch(function (err) { setStatus(S ? fill(S.loadFailed, { message: err.message }) : String(err.message), true); });
14455
+ });
14456
+ }
14457
+ function failed(err) { setStatus(S ? fill(S.loadFailed, { message: err.message }) : String(err.message), true); }
14458
+ // Measuring takes seconds, so the page says so until it is done.
14459
+ function loadLive(why) {
14460
+ setStatus(S ? S.live.measuring : '...', false);
14461
+ getJson('/api/board/live').then(function (d) { drawLive(d, why); }).catch(failed);
14462
+ }
14463
+
14464
+ var params = new URLSearchParams(location.search);
14465
+ var record = params.get('record');
14466
+ if (params.get('live') !== null) {
14467
+ loadLive(null);
14468
+ } else {
14469
+ setStatus('...', false);
14470
+ getJson('/api/board' + (record ? '/' + encodeURIComponent(record) : '')).then(draw).catch(failed);
14471
+ }
13973
14472
  })();
13974
14473
  </script>
13975
14474
  </body>
@@ -14790,7 +15289,8 @@ async function handleGet(res, pathname, deps) {
14790
15289
  ws,
14791
15290
  deps.nowProvider,
14792
15291
  remoteUrlOf(deps),
14793
- boardOf(deps)
15292
+ boardOf(deps),
15293
+ boardLiveOf(deps)
14794
15294
  )) {
14795
15295
  sendError(res, 404, "Not found");
14796
15296
  }
@@ -14804,7 +15304,8 @@ async function handleGet(res, pathname, deps) {
14804
15304
  primaryWorkspace(deps),
14805
15305
  deps.nowProvider,
14806
15306
  remoteUrlOf(deps),
14807
- boardOf(deps)
15307
+ boardOf(deps),
15308
+ boardLiveOf(deps)
14808
15309
  )) {
14809
15310
  sendError(res, 404, "Not found");
14810
15311
  }
@@ -14815,6 +15316,9 @@ async function handleGet(res, pathname, deps) {
14815
15316
  function boardOf(deps) {
14816
15317
  return deps.mode === "single" ? deps.board : void 0;
14817
15318
  }
15319
+ function boardLiveOf(deps) {
15320
+ return deps.mode === "single" ? deps.boardLive : void 0;
15321
+ }
14818
15322
  function remoteUrlOf(deps) {
14819
15323
  return deps.remoteUrlOf ?? tryRemoteUrl;
14820
15324
  }
@@ -14840,7 +15344,11 @@ async function handlePost(res, pathname, body, deps, runExclusive) {
14840
15344
  }
14841
15345
  sendError(res, 404, "Not found");
14842
15346
  }
14843
- async function handleWorkspaceGet(res, sub, ws, nowProvider, resolveRemoteUrl, board) {
15347
+ async function handleWorkspaceGet(res, sub, ws, nowProvider, resolveRemoteUrl, board, live) {
15348
+ if (live !== void 0 && sub === "board/live") {
15349
+ sendJson(res, 200, await boardLiveView(ws, live));
15350
+ return true;
15351
+ }
14844
15352
  if (board !== void 0) {
14845
15353
  if (sub === "board") {
14846
15354
  sendJson(res, 200, await boardView(ws, board, void 0));
@@ -14956,6 +15464,14 @@ async function boardView(ws, board, recordId) {
14956
15464
  const page = "why" in board ? boardPageUnavailable(board.why) : await boardPage(board.recordsDir, recordId);
14957
15465
  return { language, strings: boardPageStrings(language), page };
14958
15466
  }
15467
+ async function boardLiveView(ws, live) {
15468
+ const language = await resolveViewLanguageFromPaths(ws.paths);
15469
+ return {
15470
+ language,
15471
+ strings: boardPageStrings(language),
15472
+ live: boardLivePage(ws.label, await live())
15473
+ };
15474
+ }
14959
15475
  async function portfolio(deps) {
14960
15476
  const nowIso = deps.nowProvider().toISOString();
14961
15477
  const workspaces = await Promise.all(deps.workspaces.map((ws) => portfolioCard(ws, nowIso)));
@@ -15008,7 +15524,7 @@ async function captureStaleness(ws, nowIso) {
15008
15524
  async function overview(ws, nowProvider, resolveRemoteUrl) {
15009
15525
  let manifest;
15010
15526
  try {
15011
- manifest = await readManifest17(ws.paths);
15527
+ manifest = await readManifest18(ws.paths);
15012
15528
  } catch (error) {
15013
15529
  if (findErrorCode18(error, "ENOENT")) {
15014
15530
  return { initialized: false, repoRoot: ws.repoRoot };
@@ -15093,7 +15609,7 @@ async function sessionDetail(ws, sessionId) {
15093
15609
  throw error;
15094
15610
  }
15095
15611
  try {
15096
- const events = await readAllEvents2(join18(ws.paths.sessions, sessionId));
15612
+ const events = await readAllEvents2(join19(ws.paths.sessions, sessionId));
15097
15613
  return { session, events };
15098
15614
  } catch {
15099
15615
  return { session, events: [], degraded: true };
@@ -15328,18 +15844,37 @@ async function buildSingleDeps(ctx, cwd) {
15328
15844
  mode: "single",
15329
15845
  nowProvider: nowProviderOf(ctx),
15330
15846
  ...ctx.remoteUrlOf !== void 0 ? { remoteUrlOf: ctx.remoteUrlOf } : {},
15331
- board: await viewBoardOf(repositoryRoot, paths)
15847
+ board: await viewBoardOf(repositoryRoot, paths),
15848
+ boardLive: liveBoardOf(repositoryRoot, ctx)
15849
+ };
15850
+ }
15851
+ function liveBoardOf(root, ctx) {
15852
+ return sharedWhileRunning(
15853
+ () => measureLiveBoard(root, {
15854
+ ...ctx.nowProvider !== void 0 ? { nowProvider: ctx.nowProvider } : {},
15855
+ ...ctx.claudeProjectsDir !== void 0 ? { claudeProjectsDir: ctx.claudeProjectsDir } : {},
15856
+ ...ctx.codexSessionsDir !== void 0 ? { codexSessionsDir: ctx.codexSessionsDir } : {}
15857
+ })
15858
+ );
15859
+ }
15860
+ function sharedWhileRunning(run) {
15861
+ let running = null;
15862
+ return () => {
15863
+ running ??= run().finally(() => {
15864
+ running = null;
15865
+ });
15866
+ return running;
15332
15867
  };
15333
15868
  }
15334
15869
  async function viewBoardOf(root, paths) {
15335
15870
  let own;
15336
15871
  try {
15337
- own = (await readManifest18(paths)).repos?.find((repo) => repo.path === ".");
15872
+ own = (await readManifest19(paths)).repos?.find((repo) => repo.path === ".");
15338
15873
  } catch {
15339
15874
  own = void 0;
15340
15875
  }
15341
15876
  if (own?.visibility !== "private") return { why: "no_board" };
15342
- return { recordsDir: join19(root, dirname8(DEFAULT_BOARD_PATH), "records") };
15877
+ return { recordsDir: join20(root, dirname9(DEFAULT_BOARD_PATH), "records") };
15343
15878
  }
15344
15879
  async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
15345
15880
  const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve14(cwd, p) })) : await loadPortfolioConfig6(ctx.portfolioConfigPath);
@@ -15372,7 +15907,7 @@ async function buildWorkspaceEntry(repoRoot, ctx, labelOverride) {
15372
15907
  ...ctx.codexSessionsDir !== void 0 ? { codexSessionsDir: ctx.codexSessionsDir } : {}
15373
15908
  };
15374
15909
  try {
15375
- const manifest = await readManifest18(paths);
15910
+ const manifest = await readManifest19(paths);
15376
15911
  return {
15377
15912
  key: manifest.workspace.id,
15378
15913
  label: labelOverride ?? manifest.workspace.name,
@@ -15493,7 +16028,7 @@ function installExperimentalNotice(program, names = EXPERIMENTAL_COMMANDS, write
15493
16028
  function readBuildStamp() {
15494
16029
  if (false) return void 0;
15495
16030
  try {
15496
- return JSON.parse('{"version":"0.66.0","commit":"2cd6700","committedAt":"2026-10-08T13:10:32+09:00"}');
16031
+ return JSON.parse('{"version":"0.68.0","commit":"52aa4d4","committedAt":"2026-10-09T22:45:36+09:00"}');
15497
16032
  } catch {
15498
16033
  return void 0;
15499
16034
  }