@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/index.js CHANGED
@@ -830,32 +830,190 @@ function printNoApprovals(options) {
830
830
  }
831
831
 
832
832
  // src/commands/board.ts
833
- import { lstat, readFile as readFile2, realpath as realpath2 } from "fs/promises";
834
- import { basename as basename3, dirname as dirname3, join as join3, relative, resolve as resolve3, sep } from "path";
833
+ import { lstat, readdir as readdir2, readFile as readFile2, realpath as realpath3 } from "fs/promises";
834
+ import { basename as basename4, dirname as dirname5, join as join4, relative, resolve as resolve3, sep as sep2 } from "path";
835
835
  import {
836
836
  assertBasouRootSafe as assertBasouRootSafe3,
837
+ BOARD_INIT_SAVE,
837
838
  basouPaths as basouPaths4,
839
+ boardGuide,
840
+ boardInitText,
838
841
  buildRecord,
839
842
  byCodePoint,
840
843
  diffCells,
841
844
  diffObserved,
842
845
  displayPath as displayPath3,
846
+ effortStartOf,
843
847
  findErrorCode as findErrorCode3,
848
+ isBoardTimeZone,
844
849
  measureBoard,
850
+ measureBoardLive,
845
851
  NO_PREVIOUS_RECORDS,
846
852
  parseBoardDeclaration,
847
853
  parseRecordInput,
848
- readManifest as readManifest3,
854
+ readManifest as readManifest4,
849
855
  readPreviousRecords,
856
+ recordIds,
857
+ resolveViewLanguage,
858
+ shellWord,
850
859
  writeRecord
851
860
  } from "@basou/core";
852
861
 
862
+ // src/lib/foreign-workspace-warn.ts
863
+ import { realpath as realpath2 } from "fs/promises";
864
+ import { dirname } from "path";
865
+ import {
866
+ isPositionBoardLine,
867
+ loadPortfolioConfig as loadPortfolioConfig2,
868
+ PortfolioConfigMissingError,
869
+ readMarkdownFile
870
+ } from "@basou/core";
871
+
872
+ // src/lib/foreign-workspace-scan.ts
873
+ import { homedir as osHomedir } from "os";
874
+ import { basename as basename2, normalize, sep } from "path";
875
+ var MIN_TOKEN_LENGTH = 4;
876
+ function stripTrailingSep(p) {
877
+ return p.length > 1 && p.endsWith(sep) ? p.slice(0, -sep.length) : p;
878
+ }
879
+ function toTildePath(absPath, home) {
880
+ if (absPath === home) return "~";
881
+ if (absPath.startsWith(home + sep)) return `~${absPath.slice(home.length)}`;
882
+ return null;
883
+ }
884
+ function encodedSpelling(name) {
885
+ return name.replace(/[^a-zA-Z0-9]/g, "-");
886
+ }
887
+ function directoryNameSpellings(dir) {
888
+ const paired = /^(.+)-(planning|workspace)$/.exec(dir);
889
+ const plain = paired !== null ? [`${paired[1]}-planning`, `${paired[1]}-workspace`] : [dir, `${dir}-workspace`];
890
+ return [...plain, ...plain.map(encodedSpelling)];
891
+ }
892
+ function nameTokensFor(workspacePath, home) {
893
+ const abs = stripTrailingSep(normalize(workspacePath));
894
+ const tokens = /* @__PURE__ */ new Set([abs]);
895
+ const tilde = toTildePath(abs, home);
896
+ if (tilde !== null) tokens.add(tilde);
897
+ for (const name of directoryNameSpellings(basename2(abs))) tokens.add(name);
898
+ return [...tokens].filter((t) => t.length >= MIN_TOKEN_LENGTH);
899
+ }
900
+ function isSpellingOfSelf(workspacePath, selfPath) {
901
+ const own = new Set(directoryNameSpellings(basename2(stripTrailingSep(normalize(selfPath)))));
902
+ return directoryNameSpellings(basename2(stripTrailingSep(normalize(workspacePath)))).some(
903
+ (s) => own.has(s)
904
+ );
905
+ }
906
+ function scanForeignWorkspaceNames(input) {
907
+ if (input.text.length === 0 || input.workspacePaths.length === 0) return [];
908
+ const home = input.homedir ?? osHomedir();
909
+ const own = input.selfPath === void 0 ? [] : nameTokensFor(input.selfPath, home);
910
+ const lines = input.text.split("\n");
911
+ const hits = [];
912
+ for (const workspacePath of input.workspacePaths) {
913
+ if (input.selfPath !== void 0 && isSpellingOfSelf(workspacePath, input.selfPath)) continue;
914
+ const tokens = nameTokensFor(workspacePath, home).filter(
915
+ (token) => !own.some((ownToken) => ownToken.includes(token))
916
+ );
917
+ if (tokens.length === 0) continue;
918
+ const matched = /* @__PURE__ */ new Set();
919
+ const matchedLines = [];
920
+ for (const [index, line] of lines.entries()) {
921
+ let lineMatched = false;
922
+ for (const token of tokens) {
923
+ if (line.includes(token)) {
924
+ matched.add(token);
925
+ lineMatched = true;
926
+ }
927
+ }
928
+ if (lineMatched) matchedLines.push(index + 1);
929
+ }
930
+ if (matchedLines.length === 0) continue;
931
+ hits.push({
932
+ workspacePath,
933
+ tokens: [...matched].sort((a, b) => b.length - a.length || a.localeCompare(b)),
934
+ lines: matchedLines
935
+ });
936
+ }
937
+ return hits;
938
+ }
939
+
940
+ // src/lib/foreign-workspace-warn.ts
941
+ var MAX_LISTED_LINES = 5;
942
+ async function canonicalize(path) {
943
+ try {
944
+ return await realpath2(path);
945
+ } catch {
946
+ return path;
947
+ }
948
+ }
949
+ async function findForeignWorkspaceNames(args) {
950
+ let workspacePaths;
951
+ let selfPath;
952
+ try {
953
+ const workspaces = await loadPortfolioConfig2(args.configPath);
954
+ workspacePaths = await Promise.all(workspaces.map((w) => canonicalize(w.path)));
955
+ selfPath = args.selfPath === void 0 ? void 0 : await canonicalize(args.selfPath);
956
+ } catch {
957
+ return null;
958
+ }
959
+ const text = args.text.split("\n").map((line) => isPositionBoardLine(line) ? "" : line).join("\n");
960
+ const hits = scanForeignWorkspaceNames({ text, workspacePaths, selfPath });
961
+ if (hits.length === 0) return null;
962
+ const lines = [...new Set(hits.flatMap((h) => h.lines))].sort((a, b) => a - b);
963
+ return { workspaceCount: hits.length, lines };
964
+ }
965
+ async function findForeignWorkspaceFields(args) {
966
+ let workspacePaths;
967
+ let selfPath;
968
+ try {
969
+ const workspaces = await loadPortfolioConfig2(args.configPath);
970
+ workspacePaths = await Promise.all(workspaces.map((w) => canonicalize(w.path)));
971
+ selfPath = await canonicalize(args.selfPath);
972
+ } catch (error) {
973
+ return error instanceof PortfolioConfigMissingError ? { status: "no_registry" } : { status: "unreadable" };
974
+ }
975
+ const at = args.fields.filter(
976
+ (field) => scanForeignWorkspaceNames({ text: field.text, workspacePaths, selfPath }).length > 0
977
+ ).map((field) => field.at);
978
+ return { status: "checked", at };
979
+ }
980
+ function describeForeignWorkspaceLines(lines) {
981
+ const listed = lines.slice(0, MAX_LISTED_LINES).join(", ");
982
+ const rest = lines.length - MAX_LISTED_LINES;
983
+ const numbers = rest > 0 ? `${listed} and ${rest} more` : listed;
984
+ return `${lines.length === 1 ? "line" : "lines"} ${numbers}`;
985
+ }
986
+ function positionForeignWorkspaceWarning(report, where) {
987
+ const subject = report.workspaceCount === 1 ? "names another registered workspace" : `names ${report.workspaceCount} other registered workspaces`;
988
+ 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.`;
989
+ }
990
+ function protocolForeignWorkspaceWarning(report) {
991
+ const subject = report.workspaceCount === 1 ? "names a registered workspace" : `names ${report.workspaceCount} registered workspaces`;
992
+ 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.`;
993
+ }
994
+ async function warnIfPositionNamesOtherWorkspaces(args) {
995
+ try {
996
+ const body = await readMarkdownFile(args.paths.files.orientation);
997
+ if (body === null) return;
998
+ const report = await findForeignWorkspaceNames({
999
+ text: body,
1000
+ selfPath: dirname(args.paths.root),
1001
+ configPath: args.configPath
1002
+ });
1003
+ if (report !== null) {
1004
+ console.error(positionForeignWorkspaceWarning(report, args.paths.files.orientation));
1005
+ }
1006
+ } catch {
1007
+ }
1008
+ }
1009
+
853
1010
  // src/lib/provenance-actions.ts
1011
+ import { dirname as dirname4 } from "path";
854
1012
  import {
855
1013
  assertApprovalStoreSafe,
856
1014
  assertLockStoreSafe as assertLockStoreSafe3,
857
1015
  assertTaskStoreSafe,
858
- readMarkdownFile,
1016
+ readMarkdownFile as readMarkdownFile2,
859
1017
  renderDecisions,
860
1018
  renderHandoff,
861
1019
  renderOrientation,
@@ -867,7 +1025,7 @@ import {
867
1025
  import { createReadStream } from "fs";
868
1026
  import { readdir, readFile, rm, stat as stat2 } from "fs/promises";
869
1027
  import { homedir } from "os";
870
- import { basename as basename2, dirname as dirname2, join as join2, resolve as resolve2 } from "path";
1028
+ import { basename as basename3, dirname as dirname3, join as join2, resolve as resolve2 } from "path";
871
1029
  import { createInterface } from "readline";
872
1030
  import {
873
1031
  AGENT_INFRA_DIRS,
@@ -894,7 +1052,7 @@ import {
894
1052
  } from "@basou/core";
895
1053
 
896
1054
  // src/lib/observation-warn.ts
897
- import { dirname } from "path";
1055
+ import { dirname as dirname2 } from "path";
898
1056
  import { assertObservationsDirSafe, displayPath } from "@basou/core";
899
1057
  async function warnIfObservationsRefused(paths) {
900
1058
  try {
@@ -902,7 +1060,7 @@ async function warnIfObservationsRefused(paths) {
902
1060
  } catch (error) {
903
1061
  const reason = error instanceof Error ? error.message : String(error);
904
1062
  console.error(
905
- `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.`
1063
+ `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.`
906
1064
  );
907
1065
  }
908
1066
  }
@@ -979,7 +1137,7 @@ async function doRunImportClaudeCode(options, ctx) {
979
1137
  const files = await selectTranscriptFiles(projectsRoot, projectPaths, options);
980
1138
  const projectSet = new Set(projectPaths);
981
1139
  const candidates = files.map((file) => {
982
- const externalId = basename2(file, ".jsonl");
1140
+ const externalId = basename3(file, ".jsonl");
983
1141
  return {
984
1142
  externalId,
985
1143
  sourcePath: file,
@@ -1078,7 +1236,7 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
1078
1236
  files: payload.session.related_files ?? [],
1079
1237
  workingDirectory: payload.session.working_directory,
1080
1238
  sourceRoots: projectPaths,
1081
- masterRoot: dirname2(paths.root),
1239
+ masterRoot: dirname3(paths.root),
1082
1240
  extraInRoot: AGENT_INFRA_DIRS
1083
1241
  });
1084
1242
  if (scope.outOfRoot.length > 0) crossProject.push({ externalId, outOfRoot: scope.outOfRoot });
@@ -1516,8 +1674,27 @@ async function assertWorkspaceInitialized2(basouRoot) {
1516
1674
  }
1517
1675
  }
1518
1676
 
1677
+ // src/lib/board-glance.ts
1678
+ import { join as join3 } from "path";
1679
+ import { glanceBoard, readManifest as readManifest3 } from "@basou/core";
1680
+ async function glanceDefaultBoard(root, paths) {
1681
+ try {
1682
+ const own = (await readManifest3(paths)).repos?.find((repo) => repo.path === ".");
1683
+ if (own?.visibility !== "private") return null;
1684
+ } catch {
1685
+ return null;
1686
+ }
1687
+ return glanceBoard(join3(root, DEFAULT_BOARD_PATH));
1688
+ }
1689
+
1519
1690
  // src/lib/provenance-actions.ts
1520
- async function captureImportJson(fn) {
1691
+ var capturing = Promise.resolve();
1692
+ function captureImportJson(fn) {
1693
+ const turn = capturing.then(() => captureNow(fn));
1694
+ capturing = turn.catch(() => void 0);
1695
+ return turn;
1696
+ }
1697
+ async function captureNow(fn) {
1521
1698
  const stdout = [];
1522
1699
  const originalLog = console.log;
1523
1700
  const originalError = console.error;
@@ -1594,7 +1771,7 @@ function importCodex(options, ctx) {
1594
1771
  }
1595
1772
  async function regenerateHandoff(paths, nowIso, callbacks) {
1596
1773
  const result = await renderHandoff({ paths, nowIso, ...callbacks });
1597
- const existing = await readMarkdownFile(paths.files.handoff);
1774
+ const existing = await readMarkdownFile2(paths.files.handoff);
1598
1775
  await writeMarkdownFile(
1599
1776
  paths.files.handoff,
1600
1777
  renderWithMarkers(existing, result.body, "handoff.md")
@@ -1608,7 +1785,7 @@ async function regenerateHandoff(paths, nowIso, callbacks) {
1608
1785
  }
1609
1786
  async function regenerateDecisions(paths, nowIso, callbacks) {
1610
1787
  const result = await renderDecisions({ paths, nowIso, ...callbacks });
1611
- const existing = await readMarkdownFile(paths.files.decisions);
1788
+ const existing = await readMarkdownFile2(paths.files.decisions);
1612
1789
  await writeMarkdownFile(
1613
1790
  paths.files.decisions,
1614
1791
  renderWithMarkers(existing, result.body, "decisions.md")
@@ -1616,7 +1793,12 @@ async function regenerateDecisions(paths, nowIso, callbacks) {
1616
1793
  return { decisionCount: result.decisionCount };
1617
1794
  }
1618
1795
  async function regenerateOrientation(paths, nowIso, callbacks) {
1619
- const result = await renderOrientation({ paths, nowIso, ...callbacks });
1796
+ const result = await renderOrientation({
1797
+ paths,
1798
+ nowIso,
1799
+ board: await glanceDefaultBoard(dirname4(paths.root), paths),
1800
+ ...callbacks
1801
+ });
1620
1802
  await writeMarkdownFile(paths.files.orientation, `${result.body}
1621
1803
  `);
1622
1804
  return {
@@ -1734,8 +1916,9 @@ read (nothing is printed on stdout).`
1734
1916
  "after",
1735
1917
  `
1736
1918
  The input is a JSON object: measure_digest (the digest of the measurement the
1737
- judgement saw), observed, cells (every lane at every stage), prose, judged_by
1738
- and axis_review. The board is measured again, and nothing is written when the
1919
+ judgement saw), observed (exactly the observations the board declares, when it
1920
+ declares any), cells (every lane at every stage), prose, judged_by and
1921
+ axis_review. The board is measured again, and nothing is written when the
1739
1922
  digest differs. Records are never written under a .basou/ directory. Exit
1740
1923
  codes: 0 when the record was written, or with --dry-run when everything
1741
1924
  checked out (nothing is written then); 1 when it was refused (nothing is
@@ -1743,6 +1926,191 @@ written; the reasons are on stderr).`
1743
1926
  ).action(async (options) => {
1744
1927
  await runBoardRecord(options);
1745
1928
  });
1929
+ board.command("guide").description(
1930
+ "Print the steps an agent follows to judge the board and record it (writes nothing, sends nothing)"
1931
+ ).option("-v, --verbose", "Show error causes").addHelpText(
1932
+ "after",
1933
+ `
1934
+ The steps are filled in with this workspace's board (${DEFAULT_BOARD_PATH},
1935
+ read only when the manifest declares the workspace's own repo private): where
1936
+ it is and its last record, the commands that measure, observe, record and show
1937
+ it, what each stage is judged by, and the record's input to fill in, with the
1938
+ previous record's state of each cell beside it. With no board declared yet, it
1939
+ says how to declare one. The text is for an agent to read; its shape may change
1940
+ at any release. Exit codes: 0 when the steps were printed; 1 when the workspace,
1941
+ its manifest or its board.yaml cannot be read, or its own repo is not declared
1942
+ private (the reason is on stderr).`
1943
+ ).action(async (options) => {
1944
+ await runBoardGuide(options);
1945
+ });
1946
+ board.command("init").description("Print a board.yaml to start a board from (writes nothing)").option("-v, --verbose", "Show error causes").addHelpText(
1947
+ "after",
1948
+ `
1949
+ It prints a declaration of the newest board_version, in the language the
1950
+ manifest declares for the workspace's own repo: a title from the workspace's
1951
+ name, the six stages with a meaning each, one lane to cut again, nothing to
1952
+ observe, no component registered, an axis of version 1, and the effort from
1953
+ the day of the first session, in this host's time zone. It writes nothing:
1954
+ save it, from the workspace's own repo, only where no board is, through a file
1955
+ of its own:
1956
+
1957
+ ${INIT_SAVE}
1958
+
1959
+ Never print it straight into ${DEFAULT_BOARD_PATH}: the shell empties that file
1960
+ before init runs, so a board already there would be lost.
1961
+
1962
+ Exit codes: 0 when it was printed; 1 when the manifest does not declare the
1963
+ workspace's own repo private (a board's records hold what the trail holds),
1964
+ when ${DEFAULT_BOARD_PATH} is already there, or when the workspace cannot be
1965
+ read (the reason is on stderr, and nothing is printed on stdout).`
1966
+ ).action(async (options) => {
1967
+ await runBoardInit(options);
1968
+ });
1969
+ }
1970
+ var INIT_SAVE = BOARD_INIT_SAVE;
1971
+ async function runBoardInit(options, ctx = {}) {
1972
+ try {
1973
+ await doRunBoardInit(options, ctx);
1974
+ } catch (error) {
1975
+ renderCliError(error, { verbose: isVerbose(options) });
1976
+ process.exitCode = 1;
1977
+ }
1978
+ }
1979
+ async function doRunBoardInit(_options, ctx) {
1980
+ const { root, paths, manifest } = await privateWorkspace(ctx, "board init");
1981
+ const cwd = ctx.cwd ?? process.cwd();
1982
+ if (await realpath3(cwd).catch(() => cwd) !== await realpath3(root).catch(() => root)) {
1983
+ throw new Error(
1984
+ `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.`
1985
+ );
1986
+ }
1987
+ const board = join4(root, DEFAULT_BOARD_PATH);
1988
+ try {
1989
+ await lstat(board);
1990
+ throw new Error(
1991
+ `${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).`
1992
+ );
1993
+ } catch (error) {
1994
+ if (!findErrorCode3(error, "ENOENT")) throw error;
1995
+ }
1996
+ const { start, timeZone, sessionsRead } = await effortStartOf(
1997
+ paths,
1998
+ ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()
1999
+ );
2000
+ if (timeZone === void 0) {
2001
+ console.error(
2002
+ "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."
2003
+ );
2004
+ } else if (!sessionsRead) {
2005
+ console.error(
2006
+ "basou: the sessions could not be read, so effort.start is today; set it to the day the work began."
2007
+ );
2008
+ } else if (!isBoardTimeZone(timeZone)) {
2009
+ console.error(
2010
+ "basou: this host's time zone has no name a board can declare, so effort.time_zone is left out; write one."
2011
+ );
2012
+ }
2013
+ const text = boardInitText({
2014
+ name: manifest.workspace.name,
2015
+ language: resolveViewLanguage(manifest),
2016
+ start,
2017
+ startIsFirstSession: timeZone !== void 0,
2018
+ // An offset such as +00:00, which some hosts name their zone, is not
2019
+ // one a board can declare.
2020
+ timeZone: timeZone !== void 0 && isBoardTimeZone(timeZone) ? timeZone : void 0
2021
+ });
2022
+ process.stdout.write(text);
2023
+ return text;
2024
+ }
2025
+ async function privateWorkspace(ctx, command) {
2026
+ const cwd = ctx.cwd ?? process.cwd();
2027
+ const root = await resolveBasouRootForCommand(
2028
+ cwd,
2029
+ command,
2030
+ ctx.portfolioConfigPath === void 0 ? {} : { portfolioConfigPath: ctx.portfolioConfigPath }
2031
+ );
2032
+ const paths = basouPaths4(root);
2033
+ await assertWorkspaceInitialized3(paths.root);
2034
+ const manifest = await readManifest4(paths);
2035
+ const own = (manifest.repos ?? []).find((repo) => repo.path === ".");
2036
+ if (own?.visibility !== "private") {
2037
+ throw new Error(
2038
+ `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.`
2039
+ );
2040
+ }
2041
+ return { root, paths, manifest };
2042
+ }
2043
+ async function runBoardGuide(options, ctx = {}) {
2044
+ try {
2045
+ await doRunBoardGuide(options, ctx);
2046
+ } catch (error) {
2047
+ renderCliError(error, { verbose: isVerbose(options) });
2048
+ process.exitCode = 1;
2049
+ }
2050
+ }
2051
+ async function doRunBoardGuide(_options, ctx) {
2052
+ const { root, manifest } = await privateWorkspace(ctx, "board guide");
2053
+ const board = {
2054
+ path: join4(root, DEFAULT_BOARD_PATH),
2055
+ shown: DEFAULT_BOARD_PATH,
2056
+ given: false
2057
+ };
2058
+ let text;
2059
+ try {
2060
+ text = await readDeclaration(board);
2061
+ } catch (error) {
2062
+ if (!findErrorCode3(error, "ENOENT")) throw error;
2063
+ if (await lstat(board.path).then(
2064
+ () => true,
2065
+ () => false
2066
+ )) {
2067
+ throw new Error(
2068
+ `${DEFAULT_BOARD_PATH} is there but cannot be read (a link to nothing?). Fix it before the board is updated.`
2069
+ );
2070
+ }
2071
+ text = null;
2072
+ }
2073
+ const otherRepos = (manifest.repos ?? []).map((repo) => repo.path).filter((p) => p !== ".");
2074
+ if ([root, ...otherRepos].some((path) => displayPath3(path) !== path)) {
2075
+ throw new Error(
2076
+ "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."
2077
+ );
2078
+ }
2079
+ const common = {
2080
+ // As basou resolves it, the root measure resolves the repos' paths against.
2081
+ anchor: root,
2082
+ otherRepos,
2083
+ language: resolveViewLanguage(manifest),
2084
+ basouCommand: ctx.basouCommand ?? [process.execPath, process.argv[1] ?? ""].filter((part) => part !== "").map(shellWord).join(" "),
2085
+ basouVersion: BASOU_CLI_VERSION,
2086
+ now: ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()
2087
+ };
2088
+ let guide;
2089
+ if (text === null) {
2090
+ guide = boardGuide({ ...common, board: { status: "undeclared" } });
2091
+ } else {
2092
+ const parsed = parseBoardDeclaration(text, {
2093
+ manifestRepoPaths: (manifest.repos ?? []).map((repo) => repo.path)
2094
+ });
2095
+ if (!parsed.ok) {
2096
+ throw new Error(
2097
+ `${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):
2098
+ ${parsed.errors.map((e) => ` - ${displayPath3(e)}`).join("\n")}`
2099
+ );
2100
+ }
2101
+ const recordsDir = join4(dirname5(board.path), "records");
2102
+ guide = boardGuide({
2103
+ ...common,
2104
+ board: {
2105
+ status: "declared",
2106
+ declaration: parsed.declaration,
2107
+ recordCount: recordIds(await readdir2(recordsDir).catch(() => [])).length,
2108
+ previous: await readPreviousRecords(recordsDir)
2109
+ }
2110
+ });
2111
+ }
2112
+ console.log(guide.trimEnd());
2113
+ return guide;
1746
2114
  }
1747
2115
  async function runBoardMeasure(options, ctx = {}) {
1748
2116
  try {
@@ -1789,8 +2157,8 @@ Nothing was written.`, { cause: error.cause });
1789
2157
  async function checkAndRecord(options, ctx) {
1790
2158
  const cwd = ctx.cwd ?? process.cwd();
1791
2159
  const loaded = await loadBoard(options, ctx, "board record");
1792
- const beside = (...parts) => loaded.board.given ? join3(dirname3(loaded.board.shown), ...parts) : relative(cwd, join3(dirname3(loaded.board.path), ...parts));
1793
- const recordsDir = join3(dirname3(loaded.board.path), "records");
2160
+ const beside = (...parts) => loaded.board.given ? join4(dirname5(loaded.board.shown), ...parts) : relative(cwd, join4(dirname5(loaded.board.path), ...parts));
2161
+ const recordsDir = join4(dirname5(loaded.board.path), "records");
1794
2162
  await checkRecordsPlace(loaded, recordsDir, beside("records"), options.notPrivate === true);
1795
2163
  const text = await readRecordInput(options, ctx);
1796
2164
  let value;
@@ -1801,6 +2169,7 @@ async function checkAndRecord(options, ctx) {
1801
2169
  cause: error
1802
2170
  });
1803
2171
  }
2172
+ await refuseOtherWorkspaces(value, loaded.root, ctx);
1804
2173
  const parsed = parseRecordInput(value, loaded.declaration);
1805
2174
  if (!parsed.ok) {
1806
2175
  throw new RecordRefusal(
@@ -1857,11 +2226,90 @@ ${parsed.errors.map((e) => ` - ${displayPath3(e)}`).join("\n")}`
1857
2226
  diff
1858
2227
  };
1859
2228
  }
2229
+ var INPUT_KEYS = /* @__PURE__ */ new Set([
2230
+ "measure_digest",
2231
+ "observed",
2232
+ "cells",
2233
+ "prose",
2234
+ "judged_by",
2235
+ "axis_review",
2236
+ "value",
2237
+ "observed_at",
2238
+ "source",
2239
+ "error",
2240
+ "lane",
2241
+ "stage",
2242
+ "state",
2243
+ "reason",
2244
+ "summary",
2245
+ "lanes",
2246
+ "operator_turns",
2247
+ "footnotes",
2248
+ "text",
2249
+ "model",
2250
+ "self_reported",
2251
+ "triggers"
2252
+ ]);
2253
+ var SHAPED_PLACES = [
2254
+ /^measure_digest$/,
2255
+ /^observed \(a named entry\)\.observed_at$/,
2256
+ /^cells\[\d+\]\.state$/,
2257
+ /^axis_review\.triggers$/
2258
+ ];
2259
+ var NAMED_MAPS = /* @__PURE__ */ new Set(["observed", "prose.lanes"]);
2260
+ function inputTexts(value) {
2261
+ const out = [];
2262
+ const stack = [{ value, at: "" }];
2263
+ for (let next = stack.pop(); next !== void 0; next = stack.pop()) {
2264
+ const { value: v, at } = next;
2265
+ if (SHAPED_PLACES.some((place) => place.test(at))) continue;
2266
+ if (typeof v === "string" || typeof v === "number") {
2267
+ out.push({ at: at === "" ? "(top level)" : at, text: String(v) });
2268
+ } else if (Array.isArray(v)) {
2269
+ for (let i = v.length - 1; i >= 0; i--) stack.push({ value: v[i], at: `${at}[${i}]` });
2270
+ } else if (typeof v === "object" && v !== null) {
2271
+ const named = NAMED_MAPS.has(at);
2272
+ const where = at === "" ? "(top level)" : at;
2273
+ const children = [];
2274
+ for (const [k, child] of Object.entries(v)) {
2275
+ if (!named && INPUT_KEYS.has(k)) {
2276
+ children.push({ value: child, at: at === "" ? k : `${at}.${k}` });
2277
+ } else {
2278
+ out.push({ at: `${where}: a name`, text: k });
2279
+ children.push({ value: child, at: `${where} (a named entry)` });
2280
+ }
2281
+ }
2282
+ stack.push(...children.reverse());
2283
+ }
2284
+ }
2285
+ return out;
2286
+ }
2287
+ async function refuseOtherWorkspaces(input, root, ctx) {
2288
+ const found = await findForeignWorkspaceFields({
2289
+ fields: inputTexts(input),
2290
+ selfPath: root,
2291
+ configPath: ctx.portfolioConfigPath
2292
+ });
2293
+ if (found.status === "unreadable") {
2294
+ console.error(
2295
+ "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."
2296
+ );
2297
+ return;
2298
+ }
2299
+ if (found.status === "checked" && found.at.length > 0) {
2300
+ throw new RecordRefusal(
2301
+ `The record's input names another workspace the portfolio registers, and a board holds this workspace's own work only; nothing was written:
2302
+ ${[
2303
+ ...new Set(found.at)
2304
+ ].map((where) => ` - ${displayPath3(where)}`).join("\n")}`
2305
+ );
2306
+ }
2307
+ }
1860
2308
  function hasRecords(loaded) {
1861
- return basename3(loaded.board.path) === "board.yaml";
2309
+ return basename4(loaded.board.path) === "board.yaml";
1862
2310
  }
1863
2311
  async function previousOf(loaded) {
1864
- return hasRecords(loaded) ? readPreviousRecords(join3(dirname3(loaded.board.path), "records")) : NO_PREVIOUS_RECORDS;
2312
+ return hasRecords(loaded) ? readPreviousRecords(join4(dirname5(loaded.board.path), "records")) : NO_PREVIOUS_RECORDS;
1865
2313
  }
1866
2314
  function errorCode(error) {
1867
2315
  const code = error?.code;
@@ -1869,21 +2317,21 @@ function errorCode(error) {
1869
2317
  }
1870
2318
  async function checkRecordsPlace(loaded, recordsDir, shown, notPrivate) {
1871
2319
  const { board } = loaded;
1872
- if (basename3(board.path) !== "board.yaml") {
2320
+ if (basename4(board.path) !== "board.yaml") {
1873
2321
  throw new RecordRefusal(
1874
2322
  `${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.`
1875
2323
  );
1876
2324
  }
1877
2325
  let dir;
1878
2326
  try {
1879
- dir = await realpath2(dirname3(board.path));
2327
+ dir = await realpath3(dirname5(board.path));
1880
2328
  } catch (error) {
1881
2329
  throw new RecordRefusal(
1882
2330
  `The directory of ${displayPath3(board.shown)} could not be resolved; nothing was written.`,
1883
2331
  { cause: error }
1884
2332
  );
1885
2333
  }
1886
- if (dir.split(sep).includes(".basou")) {
2334
+ if (dir.split(sep2).includes(".basou")) {
1887
2335
  throw new RecordRefusal(
1888
2336
  `${displayPath3(shown)} would be under a .basou/ directory, where basou board record writes nothing; nothing was written.`
1889
2337
  );
@@ -1891,8 +2339,8 @@ async function checkRecordsPlace(loaded, recordsDir, shown, notPrivate) {
1891
2339
  if (!notPrivate) {
1892
2340
  let holder;
1893
2341
  for (const repo of loaded.manifest.repos ?? []) {
1894
- const real = await realpath2(resolve3(loaded.root, repo.path)).catch(() => void 0);
1895
- if (real === void 0 || dir !== real && !dir.startsWith(`${real}${sep}`)) continue;
2342
+ const real = await realpath3(resolve3(loaded.root, repo.path)).catch(() => void 0);
2343
+ if (real === void 0 || dir !== real && !dir.startsWith(`${real}${sep2}`)) continue;
1896
2344
  if (holder === void 0 || real.length > holder.real.length) {
1897
2345
  holder = { real, private: repo.visibility === "private" };
1898
2346
  }
@@ -1992,7 +2440,7 @@ async function loadBoard(options, ctx, command) {
1992
2440
  );
1993
2441
  const paths = basouPaths4(root);
1994
2442
  await assertWorkspaceInitialized3(paths.root);
1995
- const manifest = await readManifest3(paths);
2443
+ const manifest = await readManifest4(paths);
1996
2444
  const board = boardPath(options, cwd, root, manifest);
1997
2445
  const text = await readDeclaration(board);
1998
2446
  const repoPaths = (manifest.repos ?? []).map((repo) => repo.path);
@@ -2026,6 +2474,41 @@ async function measureLoaded(loaded, ctx, model, previous) {
2026
2474
  previous
2027
2475
  });
2028
2476
  }
2477
+ async function measureLiveBoard(root, ctx) {
2478
+ const paths = basouPaths4(root);
2479
+ let repos = ["."];
2480
+ let manifestUnread = false;
2481
+ try {
2482
+ const declared = ((await readManifest4(paths)).repos ?? []).map((repo) => repo.path);
2483
+ if (declared.length > 0) repos = declared;
2484
+ } catch {
2485
+ manifestUnread = true;
2486
+ }
2487
+ const now = ctx.nowProvider?.() ?? /* @__PURE__ */ new Date();
2488
+ const probeCtx = { cwd: root };
2489
+ if (ctx.claudeProjectsDir !== void 0) probeCtx.claudeProjectsDir = ctx.claudeProjectsDir;
2490
+ if (ctx.codexSessionsDir !== void 0) probeCtx.codexSessionsDir = ctx.codexSessionsDir;
2491
+ const measured = await measureBoardLive({
2492
+ root,
2493
+ repos,
2494
+ paths,
2495
+ now,
2496
+ measuredWith: { basou: BASOU_CLI_VERSION, build: BASOU_BUILD?.commit ?? null },
2497
+ probeImports: () => probeStaleness({ ctx: probeCtx, paths, nowIso: now.toISOString() })
2498
+ });
2499
+ if (!manifestUnread) return measured;
2500
+ return {
2501
+ ...measured,
2502
+ complete: false,
2503
+ not_found: [
2504
+ {
2505
+ at: "repos",
2506
+ reason: "the manifest could not be read, so only the workspace's own repo was measured"
2507
+ },
2508
+ ...measured.not_found
2509
+ ]
2510
+ };
2511
+ }
2029
2512
  function boardPath(options, cwd, root, manifest) {
2030
2513
  if (options.board !== void 0)
2031
2514
  return { path: resolve3(cwd, options.board), shown: options.board, given: true };
@@ -2035,7 +2518,7 @@ function boardPath(options, cwd, root, manifest) {
2035
2518
  `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>.`
2036
2519
  );
2037
2520
  }
2038
- return { path: join3(root, DEFAULT_BOARD_PATH), shown: DEFAULT_BOARD_PATH, given: false };
2521
+ return { path: join4(root, DEFAULT_BOARD_PATH), shown: DEFAULT_BOARD_PATH, given: false };
2039
2522
  }
2040
2523
  async function readDeclaration(board) {
2041
2524
  try {
@@ -2267,19 +2750,19 @@ function printMeasurementText(m, hasRecords2) {
2267
2750
 
2268
2751
  // src/lib/context-channel.ts
2269
2752
  import { homedir as homedir2 } from "os";
2270
- import { join as join5 } from "path";
2753
+ import { join as join6 } from "path";
2271
2754
  import {
2272
2755
  ORIENTATION_END,
2273
2756
  ORIENTATION_START,
2274
2757
  parseMarkers,
2275
- readMarkdownFile as readMarkdownFile2,
2758
+ readMarkdownFile as readMarkdownFile3,
2276
2759
  removeMarkerSection
2277
2760
  } from "@basou/core";
2278
2761
 
2279
2762
  // src/lib/durable-write.ts
2280
2763
  import { randomUUID } from "crypto";
2281
2764
  import { lstat as lstat2, open, rename, stat as stat3, unlink as unlink2 } from "fs/promises";
2282
- import { basename as basename4, dirname as dirname4, join as join4 } from "path";
2765
+ import { basename as basename5, dirname as dirname6, join as join5 } from "path";
2283
2766
  async function assertNotSymlink(targetPath) {
2284
2767
  try {
2285
2768
  const st = await lstat2(targetPath);
@@ -2294,8 +2777,8 @@ async function assertNotSymlink(targetPath) {
2294
2777
  }
2295
2778
  }
2296
2779
  async function writeFileDurable(targetPath, content) {
2297
- const dir = dirname4(targetPath);
2298
- const tmpPath = join4(dir, `.${basename4(targetPath)}.tmp.${randomUUID()}`);
2780
+ const dir = dirname6(targetPath);
2781
+ const tmpPath = join5(dir, `.${basename5(targetPath)}.tmp.${randomUUID()}`);
2299
2782
  let mode = 420;
2300
2783
  try {
2301
2784
  mode = (await stat3(targetPath)).mode & 511;
@@ -2330,7 +2813,7 @@ async function writeFileDurable(targetPath, content) {
2330
2813
  }
2331
2814
 
2332
2815
  // src/lib/context-channel.ts
2333
- var CODEX_TARGET_PATH = join5(homedir2(), ".codex", "AGENTS.md");
2816
+ var CODEX_TARGET_PATH = join6(homedir2(), ".codex", "AGENTS.md");
2334
2817
  var ORIENTATION_MARKERS = { start: ORIENTATION_START, end: ORIENTATION_END };
2335
2818
  function buildTargetBody(existing, block, markers) {
2336
2819
  const wrapped = `${markers.start}
@@ -2354,20 +2837,20 @@ ${block}${markers.end}${section.after}`;
2354
2837
  }
2355
2838
  async function backupOnce(target, existing) {
2356
2839
  const bak = `${target}.basou-bak`;
2357
- const already = await readMarkdownFile2(bak);
2840
+ const already = await readMarkdownFile3(bak);
2358
2841
  if (already !== null) return;
2359
2842
  await writeFileDurable(bak, existing ?? "");
2360
2843
  }
2361
2844
  async function syncMarkerBlock(opts) {
2362
2845
  const { target, markers, block } = opts;
2363
2846
  await assertNotSymlink(target);
2364
- const existing = await readMarkdownFile2(target);
2847
+ const existing = await readMarkdownFile3(target);
2365
2848
  const newBody = buildTargetBody(existing, block, markers);
2366
2849
  if (newBody === existing) return { action: "unchanged" };
2367
2850
  const hadBlock = existing !== null && parseMarkers(existing, markers).kind === "ok";
2368
2851
  const action = hadBlock ? "updated" : "installed";
2369
2852
  if (opts.dryRun === true) return { action };
2370
- const recheck = await readMarkdownFile2(target);
2853
+ const recheck = await readMarkdownFile3(target);
2371
2854
  if (recheck !== existing) {
2372
2855
  throw new Error(
2373
2856
  "The target changed during sync; aborting so a concurrent edit is not overwritten. Re-run the command."
@@ -2389,12 +2872,12 @@ function assertNoMarkerLine(body, markers) {
2389
2872
  async function removeMarkerBlock(opts) {
2390
2873
  const { target, markers, fileLabel } = opts;
2391
2874
  await assertNotSymlink(target);
2392
- const existing = await readMarkdownFile2(target);
2875
+ const existing = await readMarkdownFile3(target);
2393
2876
  if (existing === null) return { removed: false };
2394
2877
  const newBody = removeMarkerSection(existing, fileLabel, markers);
2395
2878
  if (newBody === existing) return { removed: false };
2396
2879
  if (opts.dryRun === true) return { removed: true };
2397
- const recheck = await readMarkdownFile2(target);
2880
+ const recheck = await readMarkdownFile3(target);
2398
2881
  if (recheck !== existing) {
2399
2882
  throw new Error(
2400
2883
  "The target changed during unsync; aborting so a concurrent edit is not overwritten. Re-run the command."
@@ -2475,7 +2958,7 @@ async function doRunChannelClear(face, options) {
2475
2958
  // src/commands/decision.ts
2476
2959
  import { readFile as readFile3 } from "fs/promises";
2477
2960
  import { homedir as homedir3 } from "os";
2478
- import { join as join6, resolve as resolve4 } from "path";
2961
+ import { join as join7, resolve as resolve4 } from "path";
2479
2962
  import {
2480
2963
  AGENT_INFRA_DIRS as AGENT_INFRA_DIRS2,
2481
2964
  acquireLock as acquireLock2,
@@ -2491,7 +2974,7 @@ import {
2491
2974
  LOCAL_CLI_EVENT_SOURCE as LOCAL_CLI_EVENT_SOURCE2,
2492
2975
  loadSessionEntries,
2493
2976
  prefixedUlid as prefixedUlid2,
2494
- readManifest as readManifest4,
2977
+ readManifest as readManifest5,
2495
2978
  replayEvents as replayEvents2,
2496
2979
  resolveSessionId,
2497
2980
  sanitizePath
@@ -2754,7 +3237,7 @@ async function runDecisionRecord(options, ctx = {}) {
2754
3237
  async function warnLinkedFilesOutsideRoots(input) {
2755
3238
  if (input.linkedFiles.length === 0) return;
2756
3239
  try {
2757
- const manifest = await readManifest4(input.paths);
3240
+ const manifest = await readManifest5(input.paths);
2758
3241
  if ((manifest.import?.source_roots?.length ?? 0) === 0) return;
2759
3242
  const scope = await classifyFilesBySourceRoot2({
2760
3243
  files: input.linkedFiles,
@@ -2820,7 +3303,7 @@ async function doRunDecisionRecord(options, ctx) {
2820
3303
  });
2821
3304
  return;
2822
3305
  }
2823
- const manifest = await readManifest4(paths);
3306
+ const manifest = await readManifest5(paths);
2824
3307
  const adHoc = await createAdHocSessionWithEvent({
2825
3308
  paths,
2826
3309
  manifest,
@@ -2884,7 +3367,7 @@ async function doRunDecisionCapture(options, ctx) {
2884
3367
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
2885
3368
  const occurredAt = now.toISOString();
2886
3369
  const decisionIds = decisions.map(() => prefixedUlid2("decision"));
2887
- const manifest = await readManifest4(paths);
3370
+ const manifest = await readManifest5(paths);
2888
3371
  const invocationArgs = options.file !== void 0 ? [
2889
3372
  "--file",
2890
3373
  sanitizePath(resolve4(cwd, options.file), {
@@ -2990,7 +3473,7 @@ async function doRunDecisionVoid(decisionId, options, ctx) {
2990
3473
  });
2991
3474
  return;
2992
3475
  }
2993
- const manifest = await readManifest4(paths);
3476
+ const manifest = await readManifest5(paths);
2994
3477
  const adHoc = await createAdHocSessionWithEvent({
2995
3478
  paths,
2996
3479
  manifest,
@@ -3026,7 +3509,7 @@ function isDecisionId(value) {
3026
3509
  async function decisionExists(paths, decisionId) {
3027
3510
  const entries = await loadSessionEntries(paths, { now: /* @__PURE__ */ new Date() });
3028
3511
  for (const entry of entries) {
3029
- const sessionDir = join6(paths.sessions, entry.sessionId);
3512
+ const sessionDir = join7(paths.sessions, entry.sessionId);
3030
3513
  try {
3031
3514
  for await (const ev of replayEvents2(sessionDir, {})) {
3032
3515
  if (ev.type === "decision_recorded" && ev.decision_id === decisionId) return true;
@@ -3438,7 +3921,7 @@ import {
3438
3921
  assertBasouRootSafe as assertBasouRootSafe5,
3439
3922
  basouPaths as basouPaths7,
3440
3923
  findErrorCode as findErrorCode5,
3441
- readMarkdownFile as readMarkdownFile3,
3924
+ readMarkdownFile as readMarkdownFile4,
3442
3925
  renderDecisions as renderDecisions2,
3443
3926
  renderWithMarkers as renderWithMarkers2,
3444
3927
  writeMarkdownFile as writeMarkdownFile2
@@ -3470,7 +3953,7 @@ async function doRunDecisionsGenerate(options, ctx) {
3470
3953
  onWarning: (w, sid) => printReplayWarning(w, sid),
3471
3954
  onSessionSkip: (sid, reason) => printSessionSkip(sid, reason)
3472
3955
  });
3473
- const existing = await readMarkdownFile3(paths.files.decisions);
3956
+ const existing = await readMarkdownFile4(paths.files.decisions);
3474
3957
  const finalBody = renderWithMarkers2(existing, result.body, "decisions.md");
3475
3958
  await writeMarkdownFile2(paths.files.decisions, finalBody);
3476
3959
  console.log(`Generated .basou/decisions.md (decisions: ${result.decisionCount})`);
@@ -3489,7 +3972,7 @@ async function assertWorkspaceInitialized5(basouRoot) {
3489
3972
  // src/commands/exec.ts
3490
3973
  import { mkdir as mkdir2, rm as rm2 } from "fs/promises";
3491
3974
  import { homedir as homedir4 } from "os";
3492
- import { join as join7 } from "path";
3975
+ import { join as join8 } from "path";
3493
3976
  import {
3494
3977
  acquireLock as acquireLock3,
3495
3978
  assertBasouRootSafe as assertBasouRootSafe6,
@@ -3503,7 +3986,7 @@ import {
3503
3986
  overwriteYamlFile,
3504
3987
  parseDuration,
3505
3988
  prefixedUlid as prefixedUlid3,
3506
- readManifest as readManifest5,
3989
+ readManifest as readManifest6,
3507
3990
  readYamlFile as readYamlFile2,
3508
3991
  SESSION_SCHEMA_VERSION,
3509
3992
  SessionSchema,
@@ -3530,15 +4013,15 @@ async function runExec(command, args, options, ctx = {}) {
3530
4013
  const repoRoot = await resolveRepositoryRootInPlace(cwd, "exec");
3531
4014
  const paths = basouPaths8(repoRoot);
3532
4015
  await assertBasouRootSafe6(paths.root);
3533
- const manifest = await readManifest5(paths);
4016
+ const manifest = await readManifest6(paths);
3534
4017
  await assertSessionStoreSafe2(paths);
3535
4018
  const sessionId = prefixedUlid3("ses");
3536
- const sessionDir = join7(paths.sessions, sessionId);
4019
+ const sessionDir = join8(paths.sessions, sessionId);
3537
4020
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
3538
4021
  await coreAppendChainedEvent(paths, sessionId, event);
3539
4022
  });
3540
4023
  const startedAt = now().toISOString();
3541
- const sessionYamlPath = join7(sessionDir, "session.yaml");
4024
+ const sessionYamlPath = join8(sessionDir, "session.yaml");
3542
4025
  const session = buildInitialSession({
3543
4026
  id: sessionId,
3544
4027
  command,
@@ -3831,7 +4314,7 @@ import {
3831
4314
  assertBasouRootSafe as assertBasouRootSafe7,
3832
4315
  basouPaths as basouPaths9,
3833
4316
  findErrorCode as findErrorCode6,
3834
- readMarkdownFile as readMarkdownFile4,
4317
+ readMarkdownFile as readMarkdownFile5,
3835
4318
  renderHandoff as renderHandoff2,
3836
4319
  renderWithMarkers as renderWithMarkers3,
3837
4320
  writeMarkdownFile as writeMarkdownFile3
@@ -3864,7 +4347,7 @@ async function doRunHandoffGenerate(options, ctx) {
3864
4347
  onSessionSkip: (sid, reason) => printSessionSkip(sid, reason),
3865
4348
  onTaskSkip: (taskId, reason) => printTaskSkip(taskId, reason)
3866
4349
  });
3867
- const existing = await readMarkdownFile4(paths.files.handoff);
4350
+ const existing = await readMarkdownFile5(paths.files.handoff);
3868
4351
  const finalBody = renderWithMarkers3(existing, result.body, "handoff.md");
3869
4352
  await writeMarkdownFile3(paths.files.handoff, finalBody);
3870
4353
  console.log(
@@ -3899,7 +4382,7 @@ async function assertWorkspaceInitialized6(basouRoot) {
3899
4382
  import { execFile } from "child_process";
3900
4383
  import { open as open2, readFile as readFile4, realpath as realpath4, stat as stat4 } from "fs/promises";
3901
4384
  import { homedir as homedir7 } from "os";
3902
- import { join as join10 } from "path";
4385
+ import { join as join11 } from "path";
3903
4386
  import { fileURLToPath } from "url";
3904
4387
  import { promisify } from "util";
3905
4388
  import {
@@ -3926,7 +4409,7 @@ import {
3926
4409
  parseProtocolStamp,
3927
4410
  protocolSectionsFrom,
3928
4411
  protocolUpdateToken,
3929
- readManifest as readManifest6,
4412
+ readManifest as readManifest7,
3930
4413
  readMarkdownFile as readMarkdownFile6,
3931
4414
  recordSessionBaseline,
3932
4415
  removeClaudeSessionStartHook,
@@ -3953,12 +4436,12 @@ function commandHandlerFields(handler) {
3953
4436
  out.additionalContextLimit = handler.additionalContextLimit;
3954
4437
  return out;
3955
4438
  }
3956
- function canonicalize(value) {
3957
- if (Array.isArray(value)) return value.map(canonicalize);
4439
+ function canonicalize2(value) {
4440
+ if (Array.isArray(value)) return value.map(canonicalize2);
3958
4441
  if (typeof value === "object" && value !== null) {
3959
4442
  const src = value;
3960
4443
  const out = {};
3961
- for (const key of Object.keys(src).sort()) out[key] = canonicalize(src[key]);
4444
+ for (const key of Object.keys(src).sort()) out[key] = canonicalize2(src[key]);
3962
4445
  return out;
3963
4446
  }
3964
4447
  return value;
@@ -3977,7 +4460,7 @@ function computeCodexHookIdentityHash(input) {
3977
4460
  }
3978
4461
  const identity = { event_name: input.eventKey, hooks: [normalized] };
3979
4462
  if (input.matcher !== void 0) identity.matcher = input.matcher;
3980
- const blob = JSON.stringify(canonicalize(identity));
4463
+ const blob = JSON.stringify(canonicalize2(identity));
3981
4464
  return `sha256:${createHash("sha256").update(blob, "utf8").digest("hex")}`;
3982
4465
  }
3983
4466
  function codexHookStateKey(hooksPath, eventKey, groupIndex, handlerIndex) {
@@ -4058,148 +4541,17 @@ function describeCodexHookTrust(trust) {
4058
4541
  }
4059
4542
  }
4060
4543
 
4061
- // src/lib/foreign-workspace-warn.ts
4062
- import { realpath as realpath3 } from "fs/promises";
4063
- import { dirname as dirname5 } from "path";
4064
- import { loadPortfolioConfig as loadPortfolioConfig2, readMarkdownFile as readMarkdownFile5 } from "@basou/core";
4065
-
4066
- // src/lib/foreign-workspace-scan.ts
4067
- import { homedir as osHomedir } from "os";
4068
- import { basename as basename5, normalize, sep as sep2 } from "path";
4069
- var MIN_TOKEN_LENGTH = 4;
4070
- function stripTrailingSep(p) {
4071
- return p.length > 1 && p.endsWith(sep2) ? p.slice(0, -sep2.length) : p;
4072
- }
4073
- function toTildePath(absPath, home) {
4074
- if (absPath === home) return "~";
4075
- if (absPath.startsWith(home + sep2)) return `~${absPath.slice(home.length)}`;
4076
- return null;
4077
- }
4078
- function encodedSpelling(name) {
4079
- return name.replace(/[^a-zA-Z0-9]/g, "-");
4080
- }
4081
- function directoryNameSpellings(dir) {
4082
- const paired = /^(.+)-(planning|workspace)$/.exec(dir);
4083
- const plain = paired !== null ? [`${paired[1]}-planning`, `${paired[1]}-workspace`] : [dir, `${dir}-workspace`];
4084
- return [...plain, ...plain.map(encodedSpelling)];
4085
- }
4086
- function nameTokensFor(workspacePath, home) {
4087
- const abs = stripTrailingSep(normalize(workspacePath));
4088
- const tokens = /* @__PURE__ */ new Set([abs]);
4089
- const tilde = toTildePath(abs, home);
4090
- if (tilde !== null) tokens.add(tilde);
4091
- for (const name of directoryNameSpellings(basename5(abs))) tokens.add(name);
4092
- return [...tokens].filter((t) => t.length >= MIN_TOKEN_LENGTH);
4093
- }
4094
- function isSpellingOfSelf(workspacePath, selfPath) {
4095
- const own = new Set(directoryNameSpellings(basename5(stripTrailingSep(normalize(selfPath)))));
4096
- return directoryNameSpellings(basename5(stripTrailingSep(normalize(workspacePath)))).some(
4097
- (s) => own.has(s)
4098
- );
4099
- }
4100
- function scanForeignWorkspaceNames(input) {
4101
- if (input.text.length === 0 || input.workspacePaths.length === 0) return [];
4102
- const home = input.homedir ?? osHomedir();
4103
- const own = input.selfPath === void 0 ? [] : nameTokensFor(input.selfPath, home);
4104
- const lines = input.text.split("\n");
4105
- const hits = [];
4106
- for (const workspacePath of input.workspacePaths) {
4107
- if (input.selfPath !== void 0 && isSpellingOfSelf(workspacePath, input.selfPath)) continue;
4108
- const tokens = nameTokensFor(workspacePath, home).filter(
4109
- (token) => !own.some((ownToken) => ownToken.includes(token))
4110
- );
4111
- if (tokens.length === 0) continue;
4112
- const matched = /* @__PURE__ */ new Set();
4113
- const matchedLines = [];
4114
- for (const [index, line] of lines.entries()) {
4115
- let lineMatched = false;
4116
- for (const token of tokens) {
4117
- if (line.includes(token)) {
4118
- matched.add(token);
4119
- lineMatched = true;
4120
- }
4121
- }
4122
- if (lineMatched) matchedLines.push(index + 1);
4123
- }
4124
- if (matchedLines.length === 0) continue;
4125
- hits.push({
4126
- workspacePath,
4127
- tokens: [...matched].sort((a, b) => b.length - a.length || a.localeCompare(b)),
4128
- lines: matchedLines
4129
- });
4130
- }
4131
- return hits;
4132
- }
4133
-
4134
- // src/lib/foreign-workspace-warn.ts
4135
- var MAX_LISTED_LINES = 5;
4136
- async function canonicalize2(path) {
4137
- try {
4138
- return await realpath3(path);
4139
- } catch {
4140
- return path;
4141
- }
4142
- }
4143
- async function findForeignWorkspaceNames(args) {
4144
- let workspacePaths;
4145
- let selfPath;
4146
- try {
4147
- const workspaces = await loadPortfolioConfig2(args.configPath);
4148
- workspacePaths = await Promise.all(workspaces.map((w) => canonicalize2(w.path)));
4149
- selfPath = args.selfPath === void 0 ? void 0 : await canonicalize2(args.selfPath);
4150
- } catch {
4151
- return null;
4152
- }
4153
- const hits = scanForeignWorkspaceNames({
4154
- text: args.text,
4155
- workspacePaths,
4156
- selfPath
4157
- });
4158
- if (hits.length === 0) return null;
4159
- const lines = [...new Set(hits.flatMap((h) => h.lines))].sort((a, b) => a - b);
4160
- return { workspaceCount: hits.length, lines };
4161
- }
4162
- function describeForeignWorkspaceLines(lines) {
4163
- const listed = lines.slice(0, MAX_LISTED_LINES).join(", ");
4164
- const rest = lines.length - MAX_LISTED_LINES;
4165
- const numbers = rest > 0 ? `${listed} and ${rest} more` : listed;
4166
- return `${lines.length === 1 ? "line" : "lines"} ${numbers}`;
4167
- }
4168
- function positionForeignWorkspaceWarning(report, where) {
4169
- const subject = report.workspaceCount === 1 ? "names another registered workspace" : `names ${report.workspaceCount} other registered workspaces`;
4170
- 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.`;
4171
- }
4172
- function protocolForeignWorkspaceWarning(report) {
4173
- const subject = report.workspaceCount === 1 ? "names a registered workspace" : `names ${report.workspaceCount} registered workspaces`;
4174
- 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.`;
4175
- }
4176
- async function warnIfPositionNamesOtherWorkspaces(args) {
4177
- try {
4178
- const body = await readMarkdownFile5(args.paths.files.orientation);
4179
- if (body === null) return;
4180
- const report = await findForeignWorkspaceNames({
4181
- text: body,
4182
- selfPath: dirname5(args.paths.root),
4183
- configPath: args.configPath
4184
- });
4185
- if (report !== null) {
4186
- console.error(positionForeignWorkspaceWarning(report, args.paths.files.orientation));
4187
- }
4188
- } catch {
4189
- }
4190
- }
4191
-
4192
4544
  // src/lib/protocols-config.ts
4193
4545
  import { homedir as homedir5 } from "os";
4194
- import { isAbsolute, join as join8, resolve as resolve5 } from "path";
4546
+ import { isAbsolute, join as join9, resolve as resolve5 } from "path";
4195
4547
  import { readYamlFile as readYamlFile3 } from "@basou/core";
4196
- var DEFAULT_PROTOCOLS_CONFIG_PATH = join8(homedir5(), ".basou", "protocols.yaml");
4197
- var DEFAULT_TARGET_PATH = join8(homedir5(), ".claude", "CLAUDE.md");
4548
+ var DEFAULT_PROTOCOLS_CONFIG_PATH = join9(homedir5(), ".basou", "protocols.yaml");
4549
+ var DEFAULT_TARGET_PATH = join9(homedir5(), ".claude", "CLAUDE.md");
4198
4550
  var ALLOWED_TOP_KEYS = /* @__PURE__ */ new Set(["version", "protocols"]);
4199
4551
  var ALLOWED_ENTRY_KEYS = /* @__PURE__ */ new Set(["source", "title"]);
4200
4552
  function expandTilde(p) {
4201
4553
  if (p === "~") return homedir5();
4202
- if (p.startsWith("~/")) return join8(homedir5(), p.slice(2));
4554
+ if (p.startsWith("~/")) return join9(homedir5(), p.slice(2));
4203
4555
  return p;
4204
4556
  }
4205
4557
  function isRecord(value) {
@@ -4277,12 +4629,12 @@ import {
4277
4629
 
4278
4630
  // src/lib/hosts-config.ts
4279
4631
  import { homedir as homedir6 } from "os";
4280
- import { isAbsolute as isAbsolute2, join as join9, resolve as resolve6 } from "path";
4632
+ import { isAbsolute as isAbsolute2, join as join10, resolve as resolve6 } from "path";
4281
4633
  import { readYamlFile as readYamlFile4 } from "@basou/core";
4282
- var DEFAULT_HOSTS_CONFIG_PATH = join9(homedir6(), ".basou", "hosts.yaml");
4634
+ var DEFAULT_HOSTS_CONFIG_PATH = join10(homedir6(), ".basou", "hosts.yaml");
4283
4635
  function expandTilde2(p) {
4284
4636
  if (p === "~") return homedir6();
4285
- if (p.startsWith("~/")) return join9(homedir6(), p.slice(2));
4637
+ if (p.startsWith("~/")) return join10(homedir6(), p.slice(2));
4286
4638
  return p;
4287
4639
  }
4288
4640
  function isRecord2(value) {
@@ -4402,6 +4754,7 @@ async function renderOrientationForRoot(repositoryRoot, options, ctx, behaviour)
4402
4754
  paths,
4403
4755
  nowIso,
4404
4756
  staleness,
4757
+ board: await glanceDefaultBoard(repositoryRoot, paths),
4405
4758
  verbose: options.verbose === true,
4406
4759
  federatedRoots,
4407
4760
  onWarning: (w, sid) => printReplayWarning(w, sid),
@@ -4803,7 +5156,7 @@ async function observeSessionFromPayload(fields, pass, portfolioConfigPath) {
4803
5156
  });
4804
5157
  return;
4805
5158
  }
4806
- const manifest = await readManifest6(paths);
5159
+ const manifest = await readManifest7(paths);
4807
5160
  await recordSessionBaseline({
4808
5161
  observationsDir: paths.observations,
4809
5162
  repoRoots: observedRepoRoots(root, manifest),
@@ -4865,7 +5218,7 @@ function parseMinEdits(raw) {
4865
5218
  if (raw === void 0 || !/^\d+$/.test(raw)) return void 0;
4866
5219
  return Number(raw);
4867
5220
  }
4868
- var DEFAULT_CLAUDE_SETTINGS_PATH = join10(homedir7(), ".claude", "settings.json");
5221
+ var DEFAULT_CLAUDE_SETTINGS_PATH = join11(homedir7(), ".claude", "settings.json");
4869
5222
  function resolveCliEntry() {
4870
5223
  return fileURLToPath(import.meta.url);
4871
5224
  }
@@ -5222,7 +5575,7 @@ function describeHookMode(tiers) {
5222
5575
  const gates = tiers.review ? "capture + review" : "capture";
5223
5576
  return `${enforcement}, ${gates}`;
5224
5577
  }
5225
- var DEFAULT_CODEX_FACE_PATH = join10(homedir7(), ".codex", "AGENTS.md");
5578
+ var DEFAULT_CODEX_FACE_PATH = join11(homedir7(), ".codex", "AGENTS.md");
5226
5579
  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.`;
5227
5580
  async function faceHasLeftoverOrientationBlock(facePath) {
5228
5581
  try {
@@ -5234,8 +5587,8 @@ async function faceHasLeftoverOrientationBlock(facePath) {
5234
5587
  return false;
5235
5588
  }
5236
5589
  }
5237
- var DEFAULT_CODEX_HOOKS_PATH = join10(homedir7(), ".codex", "hooks.json");
5238
- var DEFAULT_CODEX_CONFIG_PATH = join10(homedir7(), ".codex", "config.toml");
5590
+ var DEFAULT_CODEX_HOOKS_PATH = join11(homedir7(), ".codex", "hooks.json");
5591
+ var DEFAULT_CODEX_CONFIG_PATH = join11(homedir7(), ".codex", "config.toml");
5239
5592
  async function readHooksFile(path) {
5240
5593
  let raw;
5241
5594
  try {
@@ -5493,7 +5846,7 @@ import {
5493
5846
  EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION4,
5494
5847
  findErrorCode as findErrorCode8,
5495
5848
  LOCAL_CLI_EVENT_SOURCE as LOCAL_CLI_EVENT_SOURCE3,
5496
- readManifest as readManifest7,
5849
+ readManifest as readManifest8,
5497
5850
  resolveSessionId as resolveSessionId2,
5498
5851
  sanitizePath as sanitizePath2
5499
5852
  } from "@basou/core";
@@ -5605,7 +5958,7 @@ ${HEREDOC_EXAMPLE}`
5605
5958
  });
5606
5959
  return;
5607
5960
  }
5608
- const manifest = await readManifest7(paths);
5961
+ const manifest = await readManifest8(paths);
5609
5962
  const adHoc = await createAdHocSessionWithEvent2({
5610
5963
  paths,
5611
5964
  manifest,
@@ -5853,7 +6206,7 @@ import {
5853
6206
  writeFileSync,
5854
6207
  writeSync
5855
6208
  } from "fs";
5856
- import { basename as basename7, dirname as dirname6, isAbsolute as isAbsolute3, join as join11, relative as relative3, resolve as resolve9 } from "path";
6209
+ import { basename as basename7, dirname as dirname7, isAbsolute as isAbsolute3, join as join12, relative as relative3, resolve as resolve9 } from "path";
5857
6210
  import {
5858
6211
  appendBasouGitignore as appendBasouGitignore2,
5859
6212
  basouPaths as basouPaths13,
@@ -5871,7 +6224,7 @@ import {
5871
6224
  planRename,
5872
6225
  planRosterAdoption,
5873
6226
  planWorkspaceView,
5874
- readManifest as readManifest8,
6227
+ readManifest as readManifest9,
5875
6228
  readMarkdownFile as readMarkdownFile7,
5876
6229
  reconcileSourceRoots,
5877
6230
  removeMarkerSection as removeMarkerSection2,
@@ -6019,7 +6372,7 @@ async function doRunProjectCheck(options, ctx) {
6019
6372
  const cwd = ctx.cwd ?? process.cwd();
6020
6373
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project check");
6021
6374
  const paths = basouPaths13(repositoryRoot);
6022
- const manifest = await readManifest8(paths);
6375
+ const manifest = await readManifest9(paths);
6023
6376
  const roster = summarizeRosterDrift({
6024
6377
  ...manifest.repos !== void 0 ? { repos: manifest.repos } : {},
6025
6378
  sourceRoots: effectiveSourceRoots(manifest)
@@ -6154,7 +6507,7 @@ async function doRunProjectSync(options, ctx) {
6154
6507
  const cwd = ctx.cwd ?? process.cwd();
6155
6508
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project sync");
6156
6509
  const paths = basouPaths13(repositoryRoot);
6157
- const manifest = await readManifest8(paths);
6510
+ const manifest = await readManifest9(paths);
6158
6511
  const hasRoster = manifest.repos !== void 0 && manifest.repos.length > 0;
6159
6512
  const reconcile = reconcileSourceRoots({
6160
6513
  ...manifest.repos !== void 0 ? { repos: manifest.repos } : {},
@@ -6234,13 +6587,13 @@ function classifySourceRoot(repositoryRoot, declaredPath) {
6234
6587
  } catch {
6235
6588
  return { path: declaredPath, kind: "unresolved" };
6236
6589
  }
6237
- return { path: declaredPath, kind: existsSync(join11(real, ".git")) ? "repo" : "non-repo" };
6590
+ return { path: declaredPath, kind: existsSync(join12(real, ".git")) ? "repo" : "non-repo" };
6238
6591
  }
6239
6592
  async function doRunProjectAdopt(options, ctx) {
6240
6593
  const cwd = ctx.cwd ?? process.cwd();
6241
6594
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project adopt");
6242
6595
  const paths = basouPaths13(repositoryRoot);
6243
- const manifest = await readManifest8(paths);
6596
+ const manifest = await readManifest9(paths);
6244
6597
  const alreadyDeclared = manifest.repos !== void 0 && manifest.repos.length > 0;
6245
6598
  const candidates = effectiveSourceRoots(manifest).map(
6246
6599
  (r) => classifySourceRoot(repositoryRoot, r)
@@ -6338,7 +6691,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
6338
6691
  } catch {
6339
6692
  return { ...base, reachable: false, instructionFiles: [] };
6340
6693
  }
6341
- if (!existsSync(join11(real, ".git"))) {
6694
+ if (!existsSync(join12(real, ".git"))) {
6342
6695
  return { ...base, reachable: false, instructionFiles: [] };
6343
6696
  }
6344
6697
  try {
@@ -6346,7 +6699,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
6346
6699
  for (const name of INSTRUCTION_FILES) {
6347
6700
  let present = true;
6348
6701
  try {
6349
- lstatSync(join11(real, name));
6702
+ lstatSync(join12(real, name));
6350
6703
  } catch {
6351
6704
  present = false;
6352
6705
  }
@@ -6362,7 +6715,7 @@ async function doRunProjectWiring(options, ctx) {
6362
6715
  const cwd = ctx.cwd ?? process.cwd();
6363
6716
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project wiring");
6364
6717
  const paths = basouPaths13(repositoryRoot);
6365
- const manifest = await readManifest8(paths);
6718
+ const manifest = await readManifest9(paths);
6366
6719
  const roster = manifest.repos ?? [];
6367
6720
  const facts = [];
6368
6721
  for (const entry of roster) facts.push(await gatherRepoWiring(repositoryRoot, entry));
@@ -6457,10 +6810,10 @@ function gatherRepoGitignore(repositoryRoot, entry) {
6457
6810
  } catch {
6458
6811
  return { ...base, reachable: false, currentLines: [] };
6459
6812
  }
6460
- if (!existsSync(join11(real, ".git"))) {
6813
+ if (!existsSync(join12(real, ".git"))) {
6461
6814
  return { ...base, reachable: false, currentLines: [] };
6462
6815
  }
6463
- return { ...base, reachable: true, currentLines: readGitignoreLines(join11(real, ".gitignore")) };
6816
+ return { ...base, reachable: true, currentLines: readGitignoreLines(join12(real, ".gitignore")) };
6464
6817
  }
6465
6818
  function hasErrorCode(error) {
6466
6819
  return error instanceof Error && typeof error.code === "string";
@@ -6474,7 +6827,7 @@ function readGitignoreLines(file) {
6474
6827
  }
6475
6828
  }
6476
6829
  function applyGitignorePlan(repositoryRoot, plan) {
6477
- const file = join11(realpathSync(resolve9(repositoryRoot, plan.path)), ".gitignore");
6830
+ const file = join12(realpathSync(resolve9(repositoryRoot, plan.path)), ".gitignore");
6478
6831
  let existing = "";
6479
6832
  try {
6480
6833
  existing = readFileSync(file, "utf8");
@@ -6495,7 +6848,7 @@ async function doRunProjectGitignore(options, ctx) {
6495
6848
  const cwd = ctx.cwd ?? process.cwd();
6496
6849
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project gitignore");
6497
6850
  const paths = basouPaths13(repositoryRoot);
6498
- const manifest = await readManifest8(paths);
6851
+ const manifest = await readManifest9(paths);
6499
6852
  const roster = manifest.repos ?? [];
6500
6853
  const facts = roster.map((entry) => gatherRepoGitignore(repositoryRoot, entry));
6501
6854
  const summary = planGitignore({ repos: facts, required: [...INSTRUCTION_FILES] });
@@ -6614,7 +6967,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
6614
6967
  return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
6615
6968
  }
6616
6969
  if (real === anchorReal) {
6617
- const anchorCanonical = join11(real, CANONICAL_FILE);
6970
+ const anchorCanonical = join12(real, CANONICAL_FILE);
6618
6971
  const anchorState = anchorCanonicalState(anchorCanonical);
6619
6972
  if (anchorState === "absent") {
6620
6973
  return { ...base, isAnchor: true, reachable: true, canonicalPresent: false, files: [] };
@@ -6634,7 +6987,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
6634
6987
  anchorCanonical,
6635
6988
  "self"
6636
6989
  ).map((spec) => {
6637
- const { state, actualTarget } = inspectSymlink(join11(real, spec.name), spec.target);
6990
+ const { state, actualTarget } = inspectSymlink(join12(real, spec.name), spec.target);
6638
6991
  return {
6639
6992
  name: spec.name,
6640
6993
  expectedTarget: spec.target,
@@ -6651,16 +7004,16 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
6651
7004
  files: anchorFiles
6652
7005
  };
6653
7006
  }
6654
- if (!existsSync(join11(real, ".git"))) {
7007
+ if (!existsSync(join12(real, ".git"))) {
6655
7008
  return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
6656
7009
  }
6657
- const canonicalFile = isSelf ? join11(real, CANONICAL_FILE) : join11(anchorReal, "agents", basename7(real), CANONICAL_FILE);
7010
+ const canonicalFile = isSelf ? join12(real, CANONICAL_FILE) : join12(anchorReal, "agents", basename7(real), CANONICAL_FILE);
6658
7011
  if (!existsSync(canonicalFile)) {
6659
7012
  return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
6660
7013
  }
6661
7014
  const files = expectedSymlinkTargets(real, canonicalFile, mode).map(
6662
7015
  (spec) => {
6663
- const { state, actualTarget } = inspectSymlink(join11(real, spec.name), spec.target);
7016
+ const { state, actualTarget } = inspectSymlink(join12(real, spec.name), spec.target);
6664
7017
  return {
6665
7018
  name: spec.name,
6666
7019
  expectedTarget: spec.target,
@@ -6689,9 +7042,9 @@ function applySymlinkPlan(repositoryRoot, plan) {
6689
7042
  const created = [];
6690
7043
  const failed = [];
6691
7044
  for (const { name, target } of plan.toCreate) {
6692
- const filePath = join11(real, name);
7045
+ const filePath = join12(real, name);
6693
7046
  try {
6694
- mkdirSync(dirname6(filePath), { recursive: true });
7047
+ mkdirSync(dirname7(filePath), { recursive: true });
6695
7048
  symlinkSync(target, filePath);
6696
7049
  created.push(name);
6697
7050
  } catch (error) {
@@ -6723,7 +7076,7 @@ function gatherViewSymlinks(repositoryRoot, anchorReal, roster, viewDir) {
6723
7076
  if (!existsSync(canonicalFile)) return { kind: "missing-canonical", viewName };
6724
7077
  const files = expectedSymlinkTargets(viewDir, canonicalFile, "hub").map(
6725
7078
  (spec) => {
6726
- const { state, actualTarget } = inspectSymlink(join11(viewDir, spec.name), spec.target);
7079
+ const { state, actualTarget } = inspectSymlink(join12(viewDir, spec.name), spec.target);
6727
7080
  return {
6728
7081
  name: spec.name,
6729
7082
  expectedTarget: spec.target,
@@ -6739,9 +7092,9 @@ function applyViewSymlinks(viewDir, files) {
6739
7092
  const failed = [];
6740
7093
  for (const f of files) {
6741
7094
  if (f.state !== "missing") continue;
6742
- const filePath = join11(viewDir, f.name);
7095
+ const filePath = join12(viewDir, f.name);
6743
7096
  try {
6744
- mkdirSync(dirname6(filePath), { recursive: true });
7097
+ mkdirSync(dirname7(filePath), { recursive: true });
6745
7098
  symlinkSync(f.expectedTarget, filePath);
6746
7099
  created.push(f.name);
6747
7100
  } catch (error) {
@@ -6754,7 +7107,7 @@ async function doRunProjectSymlinks(options, ctx) {
6754
7107
  const cwd = ctx.cwd ?? process.cwd();
6755
7108
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project symlinks");
6756
7109
  const paths = basouPaths13(repositoryRoot);
6757
- const manifest = await readManifest8(paths);
7110
+ const manifest = await readManifest9(paths);
6758
7111
  const roster = manifest.repos ?? [];
6759
7112
  const anchorReal = realpathSync(repositoryRoot);
6760
7113
  const facts = roster.map((entry) => gatherRepoSymlinks(repositoryRoot, anchorReal, entry));
@@ -6961,7 +7314,7 @@ function resolveViewDir(repositoryRoot, viewPath) {
6961
7314
  return realpathSync(abs);
6962
7315
  } catch {
6963
7316
  try {
6964
- return join11(realpathSync(dirname6(abs)), basename7(abs));
7317
+ return join12(realpathSync(dirname7(abs)), basename7(abs));
6965
7318
  } catch {
6966
7319
  return abs;
6967
7320
  }
@@ -6979,7 +7332,7 @@ function gatherViewRepo(repositoryRoot, viewDir, entry) {
6979
7332
  return { path: entry.path, reachable: false };
6980
7333
  }
6981
7334
  const linkName = basename7(repoReal);
6982
- const { state, actualTarget } = inspectSymlink(join11(viewDir, linkName), expectedTarget);
7335
+ const { state, actualTarget } = inspectSymlink(join12(viewDir, linkName), expectedTarget);
6983
7336
  return {
6984
7337
  path: entry.path,
6985
7338
  reachable: true,
@@ -6993,9 +7346,9 @@ function applyViewPlan(viewDir, toCreate) {
6993
7346
  const created = [];
6994
7347
  const failed = [];
6995
7348
  for (const { name, target } of toCreate) {
6996
- const filePath = join11(viewDir, name);
7349
+ const filePath = join12(viewDir, name);
6997
7350
  try {
6998
- mkdirSync(dirname6(filePath), { recursive: true });
7351
+ mkdirSync(dirname7(filePath), { recursive: true });
6999
7352
  symlinkSync(target, filePath);
7000
7353
  created.push(name);
7001
7354
  } catch (error) {
@@ -7008,7 +7361,7 @@ var TOP_LEVEL_INSTRUCTION_FILES_LOWER = new Set(
7008
7361
  INSTRUCTION_FILES.filter((f) => !f.includes("/")).map((f) => f.toLowerCase())
7009
7362
  );
7010
7363
  function classifyViewLink(viewDir, name, rosterRealpaths) {
7011
- const filePath = join11(viewDir, name);
7364
+ const filePath = join12(viewDir, name);
7012
7365
  let isLink;
7013
7366
  try {
7014
7367
  isLink = lstatSync(filePath).isSymbolicLink();
@@ -7037,7 +7390,7 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
7037
7390
  if (!isDir) {
7038
7391
  return { target, kind: existsSync(resolved) ? "non-repo" : "broken" };
7039
7392
  }
7040
- return { target, kind: existsSync(join11(resolved, ".git")) ? "repo" : "non-repo" };
7393
+ return { target, kind: existsSync(join12(resolved, ".git")) ? "repo" : "non-repo" };
7041
7394
  }
7042
7395
  function gatherExistingViewLinks(viewDir, rosterRealpaths) {
7043
7396
  let names;
@@ -7062,7 +7415,7 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
7062
7415
  const pruned = [];
7063
7416
  const failed = [];
7064
7417
  for (const { name } of toPrune) {
7065
- const filePath = join11(viewDir, name);
7418
+ const filePath = join12(viewDir, name);
7066
7419
  const c = classifyViewLink(viewDir, name, rosterRealpaths);
7067
7420
  if (c === null || c.kind !== "repo") {
7068
7421
  failed.push({
@@ -7084,7 +7437,7 @@ async function doRunProjectWorkspace(options, ctx) {
7084
7437
  const cwd = ctx.cwd ?? process.cwd();
7085
7438
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project workspace");
7086
7439
  const paths = basouPaths13(repositoryRoot);
7087
- const manifest = await readManifest8(paths);
7440
+ const manifest = await readManifest9(paths);
7088
7441
  const viewPath = manifest.workspace.view;
7089
7442
  const roster = manifest.repos ?? [];
7090
7443
  let result;
@@ -7275,10 +7628,10 @@ async function runProjectPreset(options, ctx = {}) {
7275
7628
  }
7276
7629
  }
7277
7630
  function canonicalFileFor(anchorReal, canonicalName) {
7278
- return join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
7631
+ return join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
7279
7632
  }
7280
7633
  function canonicalLabelFor(canonicalName) {
7281
- return join11("agents", canonicalName, CANONICAL_FILE);
7634
+ return join12("agents", canonicalName, CANONICAL_FILE);
7282
7635
  }
7283
7636
  async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
7284
7637
  const declared = {
@@ -7299,7 +7652,7 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
7299
7652
  if (real === anchorReal) {
7300
7653
  return { ...declared, isAnchor: true, reachable: true, canonicalPresent: false };
7301
7654
  }
7302
- if (!existsSync(join11(real, ".git"))) {
7655
+ if (!existsSync(join12(real, ".git"))) {
7303
7656
  return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
7304
7657
  }
7305
7658
  const canonicalName = basename7(real);
@@ -7413,7 +7766,7 @@ async function applyViewPreset(anchorReal, outcome) {
7413
7766
  isLink = false;
7414
7767
  }
7415
7768
  if (isLink) throw new Error(`Canonical is a symlink in ${label}`);
7416
- if (outcome.action === "create") mkdirSync(dirname6(file), { recursive: true });
7769
+ if (outcome.action === "create") mkdirSync(dirname7(file), { recursive: true });
7417
7770
  const existing = await readMarkdownFile7(file);
7418
7771
  await writeMarkdownFile5(file, renderWithMarkers4(existing, outcome.block, label));
7419
7772
  }
@@ -7427,7 +7780,7 @@ async function applyPresetPlan(anchorReal, plan) {
7427
7780
  isLink = false;
7428
7781
  }
7429
7782
  if (isLink) throw new Error(`Canonical is a symlink in ${label}`);
7430
- if (plan.action === "create") mkdirSync(dirname6(file), { recursive: true });
7783
+ if (plan.action === "create") mkdirSync(dirname7(file), { recursive: true });
7431
7784
  const existing = await readMarkdownFile7(file);
7432
7785
  await writeMarkdownFile5(file, renderWithMarkers4(existing, plan.desiredBlock, label));
7433
7786
  }
@@ -7444,7 +7797,7 @@ async function doRunProjectPreset(options, ctx) {
7444
7797
  const cwd = ctx.cwd ?? process.cwd();
7445
7798
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project preset");
7446
7799
  const paths = basouPaths13(repositoryRoot);
7447
- const manifest = await readManifest8(paths);
7800
+ const manifest = await readManifest9(paths);
7448
7801
  const roster = manifest.repos ?? [];
7449
7802
  const anchorReal = realpathSync(repositoryRoot);
7450
7803
  const facts = [];
@@ -7681,24 +8034,24 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
7681
8034
  const instructionFiles = [];
7682
8035
  for (const name of INSTRUCTION_FILES) {
7683
8036
  try {
7684
- lstatSync(join11(real, name));
8037
+ lstatSync(join12(real, name));
7685
8038
  instructionFiles.push(name);
7686
8039
  } catch {
7687
8040
  }
7688
8041
  }
7689
8042
  let ignored;
7690
8043
  try {
7691
- ignored = new Set(readGitignoreLines(join11(real, ".gitignore")).map((l) => l.trim()));
8044
+ ignored = new Set(readGitignoreLines(join12(real, ".gitignore")).map((l) => l.trim()));
7692
8045
  } catch {
7693
8046
  ignored = /* @__PURE__ */ new Set();
7694
8047
  }
7695
8048
  const gitignorePatterns = INSTRUCTION_FILES.filter((p) => ignored.has(p) || ignored.has(`/${p}`));
7696
- const canonical2 = existsSync(join11(anchorReal, "agents", canonicalName, CANONICAL_FILE));
8049
+ const canonical2 = existsSync(join12(anchorReal, "agents", canonicalName, CANONICAL_FILE));
7697
8050
  let viewLink = false;
7698
8051
  const viewPath = manifest.workspace.view;
7699
8052
  if (viewPath !== void 0) {
7700
8053
  try {
7701
- lstatSync(join11(resolveViewDir(repositoryRoot, viewPath), canonicalName));
8054
+ lstatSync(join12(resolveViewDir(repositoryRoot, viewPath), canonicalName));
7702
8055
  viewLink = true;
7703
8056
  } catch {
7704
8057
  }
@@ -7712,11 +8065,11 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
7712
8065
  };
7713
8066
  }
7714
8067
  function teardownExpectedTargets(repoReal, anchorReal, canonicalName) {
7715
- const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8068
+ const canonicalFile = join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
7716
8069
  return expectedSymlinkTargets(repoReal, canonicalFile);
7717
8070
  }
7718
8071
  function viewLinkPointsAt(viewDir, name, repoReal) {
7719
- const filePath = join11(viewDir, name);
8072
+ const filePath = join12(viewDir, name);
7720
8073
  try {
7721
8074
  if (!lstatSync(filePath).isSymbolicLink()) return false;
7722
8075
  const target = readlinkSync(filePath);
@@ -7727,7 +8080,7 @@ function viewLinkPointsAt(viewDir, name, repoReal) {
7727
8080
  }
7728
8081
  }
7729
8082
  function viewLinkPointsAtPath(viewDir, name, expectedRepoPath) {
7730
- const filePath = join11(viewDir, name);
8083
+ const filePath = join12(viewDir, name);
7731
8084
  try {
7732
8085
  if (!lstatSync(filePath).isSymbolicLink()) return false;
7733
8086
  const target = readlinkSync(filePath);
@@ -7778,7 +8131,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
7778
8131
  if (!isAnchor) {
7779
8132
  if (repoReal !== void 0) {
7780
8133
  for (const spec of teardownExpectedTargets(repoReal, anchorReal, canonicalName)) {
7781
- const { state, actualTarget } = inspectSymlink(join11(repoReal, spec.name), spec.target);
8134
+ const { state, actualTarget } = inspectSymlink(join12(repoReal, spec.name), spec.target);
7782
8135
  if (isSelf) {
7783
8136
  if (state !== "missing")
7784
8137
  items.push({
@@ -7815,7 +8168,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
7815
8168
  }
7816
8169
  let ignored;
7817
8170
  try {
7818
- ignored = new Set(readGitignoreLines(join11(repoReal, ".gitignore")).map((l) => l.trim()));
8171
+ ignored = new Set(readGitignoreLines(join12(repoReal, ".gitignore")).map((l) => l.trim()));
7819
8172
  for (const p of INSTRUCTION_FILES) {
7820
8173
  if (ignored.has(p) || ignored.has(`/${p}`)) {
7821
8174
  items.push({
@@ -7838,7 +8191,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
7838
8191
  const viewPath = manifest.workspace.view;
7839
8192
  if (viewPath !== void 0) {
7840
8193
  const viewDir = resolveViewDir(repositoryRoot, viewPath);
7841
- const linkPath = join11(viewDir, canonicalName);
8194
+ const linkPath = join12(viewDir, canonicalName);
7842
8195
  let isLink = false;
7843
8196
  try {
7844
8197
  isLink = lstatSync(linkPath).isSymbolicLink();
@@ -7864,8 +8217,8 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
7864
8217
  else items.push({ kind: "view-symlink", label: canonicalName, state: "removable" });
7865
8218
  }
7866
8219
  }
7867
- const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
7868
- const canonicalLabel = join11("agents", canonicalName, CANONICAL_FILE);
8220
+ const canonicalFile = join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8221
+ const canonicalLabel = join12("agents", canonicalName, CANONICAL_FILE);
7869
8222
  let canonicalIsLink = false;
7870
8223
  try {
7871
8224
  canonicalIsLink = lstatSync(canonicalFile).isSymbolicLink();
@@ -7969,12 +8322,12 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
7969
8322
  );
7970
8323
  for (const item of removable.filter((i) => i.kind === "instruction-symlink")) {
7971
8324
  const expected = expectedByName.get(item.label);
7972
- if (repoReal === null || expected === void 0 || inspectSymlink(join11(repoReal, item.label), expected).state !== "correct") {
8325
+ if (repoReal === null || expected === void 0 || inspectSymlink(join12(repoReal, item.label), expected).state !== "correct") {
7973
8326
  changed(item.label);
7974
8327
  continue;
7975
8328
  }
7976
8329
  try {
7977
- unlinkSync(join11(repoReal, item.label));
8330
+ unlinkSync(join12(repoReal, item.label));
7978
8331
  removed.push(item.label);
7979
8332
  } catch (error) {
7980
8333
  failed.push({ label: item.label, message: failureReason(error) });
@@ -7993,7 +8346,7 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
7993
8346
  continue;
7994
8347
  }
7995
8348
  try {
7996
- unlinkSync(join11(viewDir, item.label));
8349
+ unlinkSync(join12(viewDir, item.label));
7997
8350
  removed.push(`view/${item.label}`);
7998
8351
  } catch (error) {
7999
8352
  failed.push({ label: `view/${item.label}`, message: failureReason(error) });
@@ -8001,7 +8354,7 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
8001
8354
  }
8002
8355
  const NOFOLLOW = fsConstants.O_NOFOLLOW ?? 0;
8003
8356
  for (const item of removable.filter((i) => i.kind === "canonical-block")) {
8004
- const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8357
+ const canonicalFile = join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8005
8358
  try {
8006
8359
  if (lstatSync(canonicalFile).isSymbolicLink()) {
8007
8360
  changed(item.label);
@@ -8103,7 +8456,7 @@ async function doRunProjectTeardown(target, options, ctx = {}) {
8103
8456
  const cwd = ctx.cwd ?? process.cwd();
8104
8457
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project teardown");
8105
8458
  const paths = basouPaths13(repositoryRoot);
8106
- const manifest = await readManifest8(paths);
8459
+ const manifest = await readManifest9(paths);
8107
8460
  const plan = gatherRepoTeardown(repositoryRoot, manifest, target);
8108
8461
  const willApply = options.apply === true && !plan.isAnchor && plan.removableCount > 0;
8109
8462
  const { removed, failed } = willApply ? applyRepoTeardown(repositoryRoot, manifest, plan) : { removed: [], failed: [] };
@@ -8145,7 +8498,7 @@ async function doRunProjectArchive(target, options, ctx) {
8145
8498
  const cwd = ctx.cwd ?? process.cwd();
8146
8499
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project archive");
8147
8500
  const paths = basouPaths13(repositoryRoot);
8148
- const manifest = await readManifest8(paths);
8501
+ const manifest = await readManifest9(paths);
8149
8502
  const roster = manifest.repos ?? [];
8150
8503
  let targetIsAnchor = false;
8151
8504
  try {
@@ -8275,12 +8628,12 @@ function gatherRenameWiring(repositoryRoot, manifest, oldBasename) {
8275
8628
  } catch {
8276
8629
  return { canonicalDirOld: false, viewLinkOld: false };
8277
8630
  }
8278
- const canonicalDirOld = existsSync(join11(anchorReal, "agents", oldBasename));
8631
+ const canonicalDirOld = existsSync(join12(anchorReal, "agents", oldBasename));
8279
8632
  let viewLinkOld = false;
8280
8633
  const viewPath = manifest.workspace.view;
8281
8634
  if (viewPath !== void 0) {
8282
8635
  try {
8283
- lstatSync(join11(resolveViewDir(repositoryRoot, viewPath), oldBasename));
8636
+ lstatSync(join12(resolveViewDir(repositoryRoot, viewPath), oldBasename));
8284
8637
  viewLinkOld = true;
8285
8638
  } catch {
8286
8639
  }
@@ -8302,7 +8655,7 @@ async function doRunProjectRename(oldPath, newPath, options, ctx) {
8302
8655
  const cwd = ctx.cwd ?? process.cwd();
8303
8656
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project rename");
8304
8657
  const paths = basouPaths13(repositoryRoot);
8305
- const manifest = await readManifest8(paths);
8658
+ const manifest = await readManifest9(paths);
8306
8659
  const roster = manifest.repos ?? [];
8307
8660
  let oldIsAnchor = false;
8308
8661
  try {
@@ -8576,7 +8929,7 @@ async function doRunProjectDerive(options, ctx) {
8576
8929
  const cwd = ctx.cwd ?? process.cwd();
8577
8930
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project derive");
8578
8931
  const paths = basouPaths13(repositoryRoot);
8579
- const manifest = await readManifest8(paths);
8932
+ const manifest = await readManifest9(paths);
8580
8933
  if (manifest.repos === void 0 || manifest.repos.length === 0) {
8581
8934
  console.log(
8582
8935
  "# 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)."
@@ -8617,7 +8970,7 @@ async function doRunProjectSeedAnchor(options, ctx) {
8617
8970
  const cwd = ctx.cwd ?? process.cwd();
8618
8971
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project derive");
8619
8972
  const paths = basouPaths13(repositoryRoot);
8620
- const manifest = await readManifest8(paths);
8973
+ const manifest = await readManifest9(paths);
8621
8974
  const roster = manifest.repos ?? [];
8622
8975
  console.log("# Anchor instruction-file seed (the planning master's own AGENTS.md)");
8623
8976
  console.log("");
@@ -8625,7 +8978,7 @@ async function doRunProjectSeedAnchor(options, ctx) {
8625
8978
  console.log("\u2139\uFE0F No repo roster declared \u2014 nothing to seed.");
8626
8979
  return;
8627
8980
  }
8628
- const anchorDoc = join11(repositoryRoot, CANONICAL_FILE);
8981
+ const anchorDoc = join12(repositoryRoot, CANONICAL_FILE);
8629
8982
  if (pathPresent(anchorDoc)) {
8630
8983
  console.log(
8631
8984
  `\u2705 The anchor's own \`${CANONICAL_FILE}\` already exists \u2014 hand-maintained, left untouched.`
@@ -8687,7 +9040,7 @@ function regularFileSpokes(repoReal) {
8687
9040
  const out = [];
8688
9041
  for (const spoke of ["CLAUDE.md", ".github/copilot-instructions.md"]) {
8689
9042
  try {
8690
- const st = lstatSync(join11(repoReal, spoke));
9043
+ const st = lstatSync(join12(repoReal, spoke));
8691
9044
  if (!st.isSymbolicLink() && st.isFile()) out.push(spoke);
8692
9045
  } catch {
8693
9046
  }
@@ -8733,8 +9086,8 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argAbs, argReal, vie
8733
9086
  };
8734
9087
  }
8735
9088
  const isAnchor = argReal === anchorReal;
8736
- const reachable = existsSync(join11(argReal, ".git"));
8737
- const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
9089
+ const reachable = existsSync(join12(argReal, ".git"));
9090
+ const canonicalFile = join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
8738
9091
  return {
8739
9092
  path,
8740
9093
  declared,
@@ -8743,15 +9096,15 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argAbs, argReal, vie
8743
9096
  reachable,
8744
9097
  canonicalName,
8745
9098
  ...viewCanonicalName !== void 0 ? { viewCanonicalName } : {},
8746
- agentsState: inspectAgentsState(join11(argReal, CANONICAL_FILE)),
9099
+ agentsState: inspectAgentsState(join12(argReal, CANONICAL_FILE)),
8747
9100
  canonicalExists: pathPresent(canonicalFile),
8748
9101
  regularSpokes: regularFileSpokes(argReal)
8749
9102
  };
8750
9103
  }
8751
9104
  function relocateAgentsFile(repoReal, canonicalFile) {
8752
- const agentsFile = join11(repoReal, CANONICAL_FILE);
9105
+ const agentsFile = join12(repoReal, CANONICAL_FILE);
8753
9106
  try {
8754
- mkdirSync(dirname6(canonicalFile), { recursive: true });
9107
+ mkdirSync(dirname7(canonicalFile), { recursive: true });
8755
9108
  } catch (error) {
8756
9109
  return { ok: false, message: failureReason(error), partial: false };
8757
9110
  }
@@ -8811,7 +9164,7 @@ async function doRunProjectRetrofit(repo, options, ctx) {
8811
9164
  const cwd = ctx.cwd ?? process.cwd();
8812
9165
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project retrofit");
8813
9166
  const paths = basouPaths13(repositoryRoot);
8814
- const manifest = await readManifest8(paths);
9167
+ const manifest = await readManifest9(paths);
8815
9168
  const roster = manifest.repos ?? [];
8816
9169
  const anchorReal = realpathSync(repositoryRoot);
8817
9170
  const viewPath = manifest.workspace.view;
@@ -8865,7 +9218,7 @@ async function doRunProjectRetrofit(repo, options, ctx) {
8865
9218
  let failure;
8866
9219
  let partial = false;
8867
9220
  if (options.apply === true && plan.action === "relocate" && argReal !== void 0) {
8868
- const canonicalFile = join11(anchorReal, "agents", plan.canonicalName, CANONICAL_FILE);
9221
+ const canonicalFile = join12(anchorReal, "agents", plan.canonicalName, CANONICAL_FILE);
8869
9222
  const res = relocateAgentsFile(argReal, canonicalFile);
8870
9223
  if (res.ok) {
8871
9224
  applied = true;
@@ -9250,14 +9603,14 @@ import {
9250
9603
  basouPaths as basouPaths14,
9251
9604
  findErrorCode as findErrorCode10,
9252
9605
  loadPortfolioConfig as loadPortfolioConfig5,
9253
- readManifest as readManifest9
9606
+ readManifest as readManifest10
9254
9607
  } from "@basou/core";
9255
9608
  import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
9256
9609
 
9257
9610
  // src/commands/refresh-watch.ts
9258
- import { readdir as readdir2, stat as stat6 } from "fs/promises";
9611
+ import { readdir as readdir3, stat as stat6 } from "fs/promises";
9259
9612
  import { homedir as homedir9 } from "os";
9260
- import { join as join12 } from "path";
9613
+ import { join as join13 } from "path";
9261
9614
  import {
9262
9615
  assertApprovalStoreSafe as assertApprovalStoreSafe2,
9263
9616
  assertLockStoreSafe as assertLockStoreSafe4,
@@ -9269,8 +9622,8 @@ var MIN_WATCH_INTERVAL_SEC = 5;
9269
9622
  var MAX_WATCH_INTERVAL_SEC = 86400;
9270
9623
  function watchedRoots(ctx) {
9271
9624
  return [
9272
- ctx.codexSessionsDir ?? join12(homedir9(), ".codex", "sessions"),
9273
- ctx.claudeProjectsDir ?? join12(homedir9(), ".claude", "projects")
9625
+ ctx.codexSessionsDir ?? join13(homedir9(), ".codex", "sessions"),
9626
+ ctx.claudeProjectsDir ?? join13(homedir9(), ".claude", "projects")
9274
9627
  ];
9275
9628
  }
9276
9629
  async function scanSourceLogs(roots) {
@@ -9278,13 +9631,13 @@ async function scanSourceLogs(roots) {
9278
9631
  const walk = async (dir) => {
9279
9632
  let entries;
9280
9633
  try {
9281
- entries = await readdir2(dir, { withFileTypes: true });
9634
+ entries = await readdir3(dir, { withFileTypes: true });
9282
9635
  } catch (error) {
9283
9636
  if (findErrorCode9(error, "ENOENT") || findErrorCode9(error, "ENOTDIR")) return;
9284
9637
  throw new Error("Failed to read a source log directory", { cause: error });
9285
9638
  }
9286
9639
  for (const entry of entries) {
9287
- const full = join12(dir, entry.name);
9640
+ const full = join13(dir, entry.name);
9288
9641
  if (entry.isDirectory()) {
9289
9642
  await walk(full);
9290
9643
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
@@ -9567,7 +9920,7 @@ async function warnPosition(paths, result, ctx) {
9567
9920
  }
9568
9921
  async function retiredChannelNotice(paths) {
9569
9922
  try {
9570
- const manifest = await readManifest9(paths);
9923
+ const manifest = await readManifest10(paths);
9571
9924
  if (manifest.channels?.codex !== true) return null;
9572
9925
  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";
9573
9926
  } catch {
@@ -9710,7 +10063,7 @@ import {
9710
10063
  findErrorCode as findErrorCode12,
9711
10064
  findUnbindableRepos,
9712
10065
  parseReviewRecordInput,
9713
- readManifest as readManifest10,
10066
+ readManifest as readManifest11,
9714
10067
  resolveRepoRoot,
9715
10068
  sanitizePath as sanitizePath3
9716
10069
  } from "@basou/core";
@@ -9797,7 +10150,7 @@ async function doRunReviewRecord(options, ctx) {
9797
10150
  }
9798
10151
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
9799
10152
  const occurredAt = now.toISOString();
9800
- const manifest = await readManifest10(paths);
10153
+ const manifest = await readManifest11(paths);
9801
10154
  const invocationArgs = options.file !== void 0 ? [
9802
10155
  "--file",
9803
10156
  sanitizePath3(resolve11(cwd, options.file), {
@@ -10137,7 +10490,7 @@ function unattachedLines(u) {
10137
10490
  // src/commands/run.ts
10138
10491
  import { mkdir as mkdir3, readFile as readFile8, rm as rm3 } from "fs/promises";
10139
10492
  import { homedir as homedir11 } from "os";
10140
- import { join as join13 } from "path";
10493
+ import { join as join14 } from "path";
10141
10494
  import {
10142
10495
  acquireLock as acquireLock5,
10143
10496
  assertBasouRootSafe as assertBasouRootSafe13,
@@ -10154,7 +10507,7 @@ import {
10154
10507
  getSnapshot as getSnapshot2,
10155
10508
  overwriteYamlFile as overwriteYamlFile2,
10156
10509
  prefixedUlid as prefixedUlid4,
10157
- readManifest as readManifest11,
10510
+ readManifest as readManifest12,
10158
10511
  readYamlFile as readYamlFile5,
10159
10512
  resolveClaudeCodeCommand,
10160
10513
  resolveCodexCommand,
@@ -10215,15 +10568,15 @@ async function runTrackedTool(args, options, ctx, adapter) {
10215
10568
  const repoRoot = await resolveRepositoryRootInPlace(cwd, "run");
10216
10569
  const paths = basouPaths18(repoRoot);
10217
10570
  await assertBasouRootSafe13(paths.root);
10218
- const manifest = await readManifest11(paths);
10571
+ const manifest = await readManifest12(paths);
10219
10572
  await assertSessionStoreSafe3(paths);
10220
10573
  const sessionId = prefixedUlid4("ses");
10221
- const sessionDir = join13(paths.sessions, sessionId);
10574
+ const sessionDir = join14(paths.sessions, sessionId);
10222
10575
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
10223
10576
  await coreAppendChainedEvent2(paths, sessionId, event);
10224
10577
  });
10225
10578
  const startedAt = now().toISOString();
10226
- const sessionYamlPath = join13(sessionDir, "session.yaml");
10579
+ const sessionYamlPath = join14(sessionDir, "session.yaml");
10227
10580
  const session = buildInitialSession2({
10228
10581
  id: sessionId,
10229
10582
  command,
@@ -10600,7 +10953,7 @@ async function noteCodexHookStatusPreSpawn(_cwd, ctx) {
10600
10953
 
10601
10954
  // src/commands/session.ts
10602
10955
  import { readFile as readFile9 } from "fs/promises";
10603
- import { basename as basename8, isAbsolute as isAbsolute5, join as join14, relative as relative4, sep as sep3 } from "path";
10956
+ import { basename as basename8, isAbsolute as isAbsolute5, join as join15, relative as relative4, sep as sep3 } from "path";
10604
10957
  import {
10605
10958
  acquireLock as acquireLock6,
10606
10959
  appendEventToExistingSession as appendEventToExistingSession3,
@@ -10616,7 +10969,7 @@ import {
10616
10969
  loadSessionEntries as loadSessionEntries2,
10617
10970
  normalizeSessionTimestamps,
10618
10971
  readAllEvents,
10619
- readManifest as readManifest12,
10972
+ readManifest as readManifest13,
10620
10973
  readObservedDuration,
10621
10974
  readYamlFile as readYamlFile6,
10622
10975
  rechainSessionInPlace,
@@ -10732,8 +11085,8 @@ async function doRunSessionShow(idInput, options, ctx) {
10732
11085
  const paths = basouPaths19(repositoryRoot);
10733
11086
  await assertWorkspaceInitialized12(paths.root);
10734
11087
  const sessionId = await resolveSessionId3(paths, idInput);
10735
- const sessionDir = join14(paths.sessions, sessionId);
10736
- const sessionYamlPath = join14(sessionDir, "session.yaml");
11088
+ const sessionDir = join15(paths.sessions, sessionId);
11089
+ const sessionYamlPath = join15(sessionDir, "session.yaml");
10737
11090
  let session;
10738
11091
  try {
10739
11092
  const raw = await readYamlFile6(sessionYamlPath);
@@ -11043,7 +11396,7 @@ async function doRunSessionImport(options, ctx) {
11043
11396
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "import");
11044
11397
  const paths = basouPaths19(repositoryRoot);
11045
11398
  await assertWorkspaceInitialized12(paths.root);
11046
- const manifest = await readManifest12(paths);
11399
+ const manifest = await readManifest13(paths);
11047
11400
  const rawBody = await readInputFile(options.from);
11048
11401
  const json = parseJsonStrict(rawBody);
11049
11402
  const parsed = SessionImportPayloadSchema2.safeParse(json);
@@ -11429,7 +11782,7 @@ import {
11429
11782
  basouPaths as basouPaths21,
11430
11783
  buildStatusSnapshot,
11431
11784
  findErrorCode as findErrorCode15,
11432
- readManifest as readManifest13,
11785
+ readManifest as readManifest14,
11433
11786
  writeStatus
11434
11787
  } from "@basou/core";
11435
11788
  function registerStatusCommand(program2) {
@@ -11459,7 +11812,7 @@ async function doRunStatus(options, ctx) {
11459
11812
  }
11460
11813
  let manifest;
11461
11814
  try {
11462
- manifest = await readManifest13(paths);
11815
+ manifest = await readManifest14(paths);
11463
11816
  } catch (error) {
11464
11817
  if (findErrorCode15(error, "ENOENT")) {
11465
11818
  throw new Error("Workspace not initialized. Run 'basou init' first.");
@@ -11500,7 +11853,7 @@ function formatVersionGateMessage(error) {
11500
11853
 
11501
11854
  // src/commands/task.ts
11502
11855
  import { readFile as readFile10 } from "fs/promises";
11503
- import { join as join15 } from "path";
11856
+ import { join as join16 } from "path";
11504
11857
  import {
11505
11858
  archiveTask,
11506
11859
  assertBasouRootSafe as assertBasouRootSafe17,
@@ -11514,7 +11867,7 @@ import {
11514
11867
  loadSessionEntries as loadSessionEntries3,
11515
11868
  loadTaskEntries,
11516
11869
  prefixedUlid as prefixedUlid5,
11517
- readManifest as readManifest14,
11870
+ readManifest as readManifest15,
11518
11871
  readTaskFile,
11519
11872
  readTaskFileWithArchiveFallback,
11520
11873
  reconcileAllTasks,
@@ -11641,7 +11994,7 @@ async function doRunTaskNew(options, ctx) {
11641
11994
  });
11642
11995
  return;
11643
11996
  }
11644
- const manifest = await readManifest14(paths);
11997
+ const manifest = await readManifest15(paths);
11645
11998
  const result = await createTaskWithEvent({
11646
11999
  mode: "ad-hoc",
11647
12000
  paths,
@@ -11827,7 +12180,7 @@ async function doRunTaskShow(idInput, options, ctx) {
11827
12180
  const events = [];
11828
12181
  const linkedSessionIds = new Set(doc.task.task.linked_sessions);
11829
12182
  for (const s of sessions) {
11830
- const sessionDir = join15(paths.sessions, s.sessionId);
12183
+ const sessionDir = join16(paths.sessions, s.sessionId);
11831
12184
  try {
11832
12185
  for await (const ev of replayEvents3(sessionDir, {
11833
12186
  onWarning: (w) => printReplayWarning(w, s.sessionId)
@@ -11989,7 +12342,7 @@ async function doRunTaskStatus(taskIdInput, newStatusInput, options, ctx) {
11989
12342
  });
11990
12343
  return;
11991
12344
  }
11992
- const manifest = await readManifest14(paths);
12345
+ const manifest = await readManifest15(paths);
11993
12346
  const result = await updateTaskStatusWithEvent({
11994
12347
  mode: "ad-hoc",
11995
12348
  paths,
@@ -12043,7 +12396,7 @@ async function doRunTaskReconcile(options, ctx) {
12043
12396
  const paths = basouPaths22(repositoryRoot);
12044
12397
  await assertWorkspaceInitialized14(paths.root);
12045
12398
  await assertLockStoreSafe6(paths);
12046
- const manifest = await readManifest14(paths);
12399
+ const manifest = await readManifest15(paths);
12047
12400
  const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
12048
12401
  const write = options.write === true;
12049
12402
  const verbose = isVerbose(options);
@@ -12223,7 +12576,7 @@ async function doRunTaskRefreshLinkage(taskIdInput, options, ctx) {
12223
12576
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "refresh-linkage");
12224
12577
  const paths = basouPaths22(repositoryRoot);
12225
12578
  await assertWorkspaceInitialized14(paths.root);
12226
- const manifest = await readManifest14(paths);
12579
+ const manifest = await readManifest15(paths);
12227
12580
  const taskId = await resolveTaskId2(paths, taskIdInput);
12228
12581
  const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
12229
12582
  const write = options.write === true;
@@ -12303,7 +12656,7 @@ async function doRunTaskEdit(taskIdInput, options, ctx) {
12303
12656
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "edit");
12304
12657
  const paths = basouPaths22(repositoryRoot);
12305
12658
  await assertWorkspaceInitialized14(paths.root);
12306
- const manifest = await readManifest14(paths);
12659
+ const manifest = await readManifest15(paths);
12307
12660
  const taskId = await resolveTaskId2(paths, taskIdInput);
12308
12661
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
12309
12662
  const occurredAt = now.toISOString();
@@ -12359,7 +12712,7 @@ async function doRunTaskDelete(taskIdInput, options, ctx) {
12359
12712
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "delete");
12360
12713
  const paths = basouPaths22(repositoryRoot);
12361
12714
  await assertWorkspaceInitialized14(paths.root);
12362
- const manifest = await readManifest14(paths);
12715
+ const manifest = await readManifest15(paths);
12363
12716
  const taskId = await resolveTaskId2(paths, taskIdInput);
12364
12717
  if (options.yes !== true) {
12365
12718
  await confirmDestructiveAction("delete", taskId);
@@ -12404,7 +12757,7 @@ async function doRunTaskArchive(taskIdInput, options, ctx) {
12404
12757
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "archive");
12405
12758
  const paths = basouPaths22(repositoryRoot);
12406
12759
  await assertWorkspaceInitialized14(paths.root);
12407
- const manifest = await readManifest14(paths);
12760
+ const manifest = await readManifest15(paths);
12408
12761
  const taskId = await resolveTaskId2(paths, taskIdInput);
12409
12762
  if (options.yes !== true) {
12410
12763
  await confirmDestructiveAction("archive", taskId);
@@ -12722,23 +13075,23 @@ async function assertWorkspaceInitialized15(basouRoot) {
12722
13075
  // src/commands/view.ts
12723
13076
  import { spawn } from "child_process";
12724
13077
  import { createHash as createHash2 } from "crypto";
12725
- import { basename as basename11, dirname as dirname8, join as join19, resolve as resolve14 } from "path";
13078
+ import { basename as basename11, dirname as dirname9, join as join20, resolve as resolve14 } from "path";
12726
13079
  import {
12727
13080
  assertBasouRootSafe as assertBasouRootSafe19,
12728
13081
  basouPaths as basouPaths25,
12729
13082
  findErrorCode as findErrorCode19,
12730
13083
  loadPortfolioConfig as loadPortfolioConfig6,
12731
- readManifest as readManifest18
13084
+ readManifest as readManifest19
12732
13085
  } from "@basou/core";
12733
13086
  import { InvalidArgumentError as InvalidArgumentError8 } from "commander";
12734
13087
 
12735
13088
  // src/lib/portfolio-coverage.ts
12736
13089
  import { createReadStream as createReadStream2 } from "fs";
12737
- import { readdir as readdir3, stat as stat7 } from "fs/promises";
13090
+ import { readdir as readdir4, stat as stat7 } from "fs/promises";
12738
13091
  import { homedir as homedir12 } from "os";
12739
- import { basename as basename9, dirname as dirname7, join as join16 } from "path";
13092
+ import { basename as basename9, dirname as dirname8, join as join17 } from "path";
12740
13093
  import { createInterface as createInterface2 } from "readline";
12741
- import { basouPaths as basouPaths24, readManifest as readManifest15 } from "@basou/core";
13094
+ import { basouPaths as basouPaths24, readManifest as readManifest16 } from "@basou/core";
12742
13095
  function uncapturedTotal(result) {
12743
13096
  return result.groups.reduce((sum, g) => sum + g.logs, 0) + result.unplaceable;
12744
13097
  }
@@ -12748,8 +13101,8 @@ async function checkPortfolioCoverage(workspaces, ctx = {}) {
12748
13101
  ctx.portfolioConfigPath
12749
13102
  );
12750
13103
  const listedDirs = new Set([...roots].map((root) => encodeProjectDir(root)));
12751
- const claudeProjectsDir = ctx.claudeProjectsDir ?? join16(homedir12(), ".claude", "projects");
12752
- const codexSessionsDir = ctx.codexSessionsDir ?? join16(homedir12(), ".codex", "sessions");
13104
+ const claudeProjectsDir = ctx.claudeProjectsDir ?? join17(homedir12(), ".claude", "projects");
13105
+ const codexSessionsDir = ctx.codexSessionsDir ?? join17(homedir12(), ".codex", "sessions");
12753
13106
  const tally = new Tally(roots);
12754
13107
  const absentTrees = [];
12755
13108
  const claudeFiles = await listClaudeTranscripts(claudeProjectsDir);
@@ -12760,7 +13113,7 @@ async function checkPortfolioCoverage(workspaces, ctx = {}) {
12760
13113
  file,
12761
13114
  "claude-code",
12762
13115
  claudeTranscriptCwd,
12763
- listedDirs.has(basename9(dirname7(file)))
13116
+ listedDirs.has(basename9(dirname8(file)))
12764
13117
  );
12765
13118
  }
12766
13119
  }
@@ -12787,7 +13140,7 @@ async function collectDeclaredRoots(workspaces, portfolioConfigPath) {
12787
13140
  }
12788
13141
  let resolved;
12789
13142
  try {
12790
- const manifest = await readManifest15(basouPaths24(importRoot));
13143
+ const manifest = await readManifest16(basouPaths24(importRoot));
12791
13144
  resolved = resolveSourceRoots({
12792
13145
  projectFlags: [],
12793
13146
  manifest,
@@ -12887,7 +13240,7 @@ async function isDirEntry(parent, entry) {
12887
13240
  if (entry.isDirectory()) return true;
12888
13241
  if (!entry.isSymbolicLink()) return false;
12889
13242
  try {
12890
- return (await stat7(join16(parent, entry.name))).isDirectory();
13243
+ return (await stat7(join17(parent, entry.name))).isDirectory();
12891
13244
  } catch {
12892
13245
  return false;
12893
13246
  }
@@ -12895,22 +13248,22 @@ async function isDirEntry(parent, entry) {
12895
13248
  async function listClaudeTranscripts(projectsRoot) {
12896
13249
  let entries;
12897
13250
  try {
12898
- entries = await readdir3(projectsRoot, { withFileTypes: true });
13251
+ entries = await readdir4(projectsRoot, { withFileTypes: true });
12899
13252
  } catch {
12900
13253
  return void 0;
12901
13254
  }
12902
13255
  const files = [];
12903
13256
  for (const entry of entries) {
12904
13257
  if (!await isDirEntry(projectsRoot, entry)) continue;
12905
- const full = join16(projectsRoot, entry.name);
13258
+ const full = join17(projectsRoot, entry.name);
12906
13259
  let names;
12907
13260
  try {
12908
- names = await readdir3(full);
13261
+ names = await readdir4(full);
12909
13262
  } catch {
12910
13263
  continue;
12911
13264
  }
12912
13265
  for (const name of names) {
12913
- if (name.endsWith(".jsonl")) files.push(join16(full, name));
13266
+ if (name.endsWith(".jsonl")) files.push(join17(full, name));
12914
13267
  }
12915
13268
  }
12916
13269
  return files.sort();
@@ -12920,12 +13273,12 @@ async function listCodexRollouts(sessionsRoot) {
12920
13273
  const walk = async (dir) => {
12921
13274
  let entries;
12922
13275
  try {
12923
- entries = await readdir3(dir, { withFileTypes: true });
13276
+ entries = await readdir4(dir, { withFileTypes: true });
12924
13277
  } catch {
12925
13278
  return;
12926
13279
  }
12927
13280
  for (const entry of entries) {
12928
- const full = join16(dir, entry.name);
13281
+ const full = join17(dir, entry.name);
12929
13282
  if (entry.isDirectory()) await walk(full);
12930
13283
  else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) {
12931
13284
  found.push(full);
@@ -12933,7 +13286,7 @@ async function listCodexRollouts(sessionsRoot) {
12933
13286
  }
12934
13287
  };
12935
13288
  try {
12936
- await readdir3(sessionsRoot);
13289
+ await readdir4(sessionsRoot);
12937
13290
  } catch {
12938
13291
  return void 0;
12939
13292
  }
@@ -13056,9 +13409,9 @@ function inertLines(result) {
13056
13409
  // src/lib/portfolio-safety.ts
13057
13410
  import { execFile as execFile2 } from "child_process";
13058
13411
  import { lstat as lstat3, realpath as realpath5 } from "fs/promises";
13059
- import { isAbsolute as isAbsolute6, join as join17, relative as relative5, resolve as resolve12, sep as sep4 } from "path";
13412
+ import { isAbsolute as isAbsolute6, join as join18, relative as relative5, resolve as resolve12, sep as sep4 } from "path";
13060
13413
  import { promisify as promisify2 } from "util";
13061
- import { readManifest as readManifest16 } from "@basou/core";
13414
+ import { readManifest as readManifest17 } from "@basou/core";
13062
13415
  var execFileAsync2 = promisify2(execFile2);
13063
13416
  function errorCode2(error) {
13064
13417
  return error instanceof Error ? error.code : void 0;
@@ -13080,7 +13433,7 @@ function isBasouPath(p) {
13080
13433
  async function inspectRepo(repoPath) {
13081
13434
  let hasEntry = false;
13082
13435
  try {
13083
- await lstat3(join17(repoPath, ".basou"));
13436
+ await lstat3(join18(repoPath, ".basou"));
13084
13437
  hasEntry = true;
13085
13438
  } catch (error) {
13086
13439
  if (errorCode2(error) !== "ENOENT") {
@@ -13114,7 +13467,7 @@ async function checkPortfolioSafety(workspaces) {
13114
13467
  let viewPath;
13115
13468
  let isMaster = false;
13116
13469
  try {
13117
- const manifest = await readManifest16(ws.paths);
13470
+ const manifest = await readManifest17(ws.paths);
13118
13471
  sourceRoots = manifest.import?.source_roots ?? [];
13119
13472
  viewPath = manifest.workspace.view;
13120
13473
  isMaster = true;
@@ -13234,8 +13587,9 @@ function formatSafetyReport(result) {
13234
13587
 
13235
13588
  // src/lib/view-server.ts
13236
13589
  import { createServer } from "http";
13237
- import { basename as basename10, join as join18, resolve as resolve13 } from "path";
13590
+ import { basename as basename10, join as join19, resolve as resolve13 } from "path";
13238
13591
  import {
13592
+ boardLivePage,
13239
13593
  boardPage,
13240
13594
  boardPageStrings,
13241
13595
  boardPageUnavailable,
@@ -13248,7 +13602,7 @@ import {
13248
13602
  loadSessionEntries as loadSessionEntries4,
13249
13603
  loadTaskEntries as loadTaskEntries2,
13250
13604
  readAllEvents as readAllEvents2,
13251
- readManifest as readManifest17,
13605
+ readManifest as readManifest18,
13252
13606
  readMarkdownFile as readMarkdownFile9,
13253
13607
  readSessionYaml as readSessionYaml3,
13254
13608
  readTaskFile as readTaskFile2,
@@ -13377,6 +13731,7 @@ var BOARD_HTML = `<!doctype html>
13377
13731
  if (k === 'text') node.textContent = attrs[k];
13378
13732
  else if (k === 'class') node.className = attrs[k];
13379
13733
  else if (k === 'onchange') node.addEventListener('change', attrs[k]);
13734
+ else if (k === 'onclick') node.addEventListener('click', attrs[k]);
13380
13735
  else node.setAttribute(k, attrs[k]);
13381
13736
  });
13382
13737
  }
@@ -13470,7 +13825,11 @@ var BOARD_HTML = `<!doctype html>
13470
13825
  clear(nav);
13471
13826
  var page = d.page;
13472
13827
  var list = page.records || [];
13473
- if (list.length === 0) return;
13828
+ if (list.length > 0) recordLinks(nav, page, list);
13829
+ nav.appendChild(el('a', { href: '/board?live=1', text: S.live.measureNow }));
13830
+ }
13831
+
13832
+ function recordLinks(nav, page, list) {
13474
13833
  var link = function (id, label) {
13475
13834
  return el('a', { href: '/board?record=' + encodeURIComponent(id), text: label });
13476
13835
  };
@@ -13951,8 +14310,12 @@ var BOARD_HTML = `<!doctype html>
13951
14310
  not_found: S.unavailable.notFound, not_json: S.unavailable.notJson,
13952
14311
  unknown_version: S.unavailable.unknownVersion, not_a_record: S.unavailable.notARecord
13953
14312
  };
13954
- var quiet = page.why === 'no_board' || page.why === 'no_records';
13955
- setStatus(fill(messages[page.why] || page.why, { record: page.id || '', version: page.version }), !quiet);
14313
+ // With no board or no record yet, draw what basou measures now instead.
14314
+ if (page.why === 'no_board' || page.why === 'no_records') {
14315
+ loadLive(page.why);
14316
+ return;
14317
+ }
14318
+ setStatus(fill(messages[page.why] || page.why, { record: page.id || '', version: page.version }), true);
13956
14319
  return;
13957
14320
  }
13958
14321
  setStatus('', false);
@@ -13961,17 +14324,153 @@ var BOARD_HTML = `<!doctype html>
13961
14324
  .forEach(function (node) { root.appendChild(node); });
13962
14325
  }
13963
14326
 
13964
- var record = new URLSearchParams(location.search).get('record');
13965
- setStatus('...', false);
13966
- fetch('/api/board' + (record ? '/' + encodeURIComponent(record) : ''))
13967
- .then(function (res) {
14327
+ // The board measured now: the sections basou measures with nothing declared
14328
+ // or judged. why is the reason there was no record to draw, or null when
14329
+ // asked for with ?live=1.
14330
+ function liveTile(k, v, sub) {
14331
+ return el('div', { class: 'tile' }, [
14332
+ el('div', { class: 'k', text: k }), el('div', { class: 'v', text: v }),
14333
+ sub ? el('div', { class: 's', text: sub }) : null
14334
+ ]);
14335
+ }
14336
+ function liveHeading(l, why) {
14337
+ var h = l.heading;
14338
+ var measured = h.complete ? S.heading.complete : fill(S.heading.incomplete, { n: h.not_found });
14339
+ var again = el('button', { type: 'button', text: S.live.remeasure, onclick: function () { loadLive(why); } });
14340
+ return el('header', null, [
14341
+ el('h1', { text: h.title }),
14342
+ el('div', { class: 'stamp' }, [fill(S.live.measuredAt, { at: when(h.measured_at) }) + ' / ' + measured + ' ', again])
14343
+ ]);
14344
+ }
14345
+ // Whether the measurement says why the value at a place was not measured.
14346
+ function missingIn(l) {
14347
+ return function (at) { return l.footnotes.not_found.some(function (n) { return n.at === at; }); };
14348
+ }
14349
+ function liveRepos(l) {
14350
+ var missing = missingIn(l);
14351
+ var labels = [S.live.repo, S.live.branch, S.live.head, S.live.lastCommit, S.live.commits, S.live.uncommitted, S.live.behindMain];
14352
+ var head = el('tr', null, labels.map(function (label, i) { return el('th', { class: i >= 4 ? 'n' : '', text: label }); }));
14353
+ var rows = l.repos.map(function (r) {
14354
+ var at = 'repos[' + r.path + ']';
14355
+ // A null the measurement says it could not measure, or one that means
14356
+ // so (a detached HEAD, no commit yet, no origin/main), drawn as '-'.
14357
+ var value = function (field, v, show) {
14358
+ if (v !== null) return show(v);
14359
+ return missing(at) || missing(at + '.' + field) ? S.tiles.notMeasured : '-';
14360
+ };
14361
+ return el('tr', null, [
14362
+ el('td', { text: r.name === null ? r.path : r.name }),
14363
+ el('td', { text: value('branch', r.branch, String) }),
14364
+ el('td', { text: value('head', r.head, function (h) { return h.slice(0, 7); }) }),
14365
+ el('td', { text: value('last_commit', r.last_commit, when) }),
14366
+ el('td', { class: 'n', text: num(r.commits) }),
14367
+ el('td', { class: 'n', text: num(r.uncommitted) }),
14368
+ el('td', { class: 'n', text: value('behind_main', r.behind_main, num) })
14369
+ ]);
14370
+ });
14371
+ return section(S.live.repos, false, [el('div', { class: 'scroll' }, [el('table', { class: 'grid' }, [head].concat(rows))])]);
14372
+ }
14373
+ function liveTrail(l) {
14374
+ var t = l.trail;
14375
+ var i = l.integrity;
14376
+ var f = l.freshness;
14377
+ var missing = missingIn(l);
14378
+ var newest = f.newest_session_at !== null ? when(f.newest_session_at)
14379
+ : missing('freshness.newest_session_at') ? S.tiles.notMeasured : S.live.noSession;
14380
+ var unimported = f.unimported === null ? S.live.unimportedNotMeasured : fill(S.live.unimported, {
14381
+ new: f.unimported.new, updated: f.unimported.updated, unverifiable: f.unimported.unverifiable
14382
+ });
14383
+ var counts = function (byKey) {
14384
+ if (byKey === null) return S.tiles.notMeasured;
14385
+ return Object.keys(byKey).map(function (k) { return k + ' ' + num(byKey[k]); }).join(' / ');
14386
+ };
14387
+ var tiles = el('div', { class: 'tiles' }, [
14388
+ liveTile(S.live.decisions, num(t.decisions_live), t.decisions_all === null ? null : fill(S.live.decisionsAll, { n: num(t.decisions_all) })),
14389
+ liveTile(S.tiles.openTracks, t.tracks_open === null ? S.tiles.notMeasured : num(t.tracks_open.length), null),
14390
+ liveTile(S.tiles.sessions, num(i.sessions), i.not_verified === null ? null : fill(S.tiles.sessionsNotVerified, { n: num(i.not_verified) })),
14391
+ liveTile(S.live.reviewGaps, num(l.review_gaps.gaps), null),
14392
+ liveTile(S.live.newestSession, newest, unimported)
14393
+ ]);
14394
+ var tracks = t.tracks_open === null ? el('p', { class: 'muted', text: S.tiles.notMeasured })
14395
+ : t.tracks_open.length === 0 ? el('p', { class: 'muted', text: S.live.noTracks })
14396
+ : el('ul', null, t.tracks_open.map(function (tr) {
14397
+ return el('li', null, [el('span', { class: 'muted', text: tr.id + ' ' }), tr.title]);
14398
+ }));
14399
+ var breakdown = el('div', { class: 'muted' }, [
14400
+ el('div', { text: fill(S.live.byStatus, { list: counts(i.by_status) }) }),
14401
+ el('div', { text: fill(S.live.byVerdict, { list: counts(l.review_gaps.by_verdict) }) })
14402
+ ]);
14403
+ return section(S.live.trail, false, [tiles, breakdown, el('h3', { text: S.live.tracks }), tracks]);
14404
+ }
14405
+ function liveComponents(l) {
14406
+ var c = l.components;
14407
+ var body = c === null ? el('p', { class: 'muted', text: S.tiles.notMeasured })
14408
+ : c.length === 0 ? el('p', { class: 'muted', text: S.live.noComponents })
14409
+ : el('ul', null, c.map(function (x) {
14410
+ return el('li', null, [el('code', { text: x.key }), el('span', { class: 'muted', text: ' ' + x.kinds.join(', ') })]);
14411
+ }));
14412
+ return section(S.live.components, false, [body]);
14413
+ }
14414
+ function liveFootnotes(l, why) {
14415
+ var f = l.footnotes;
14416
+ var items = [el('li', { text: S.live.note }), el('li', { text: S.live.judged })];
14417
+ if (why === 'no_board') items.push(el('li', { text: S.unavailable.noBoard }));
14418
+ if (f.not_found.length > 0) {
14419
+ items.push(el('li', null, [el('b', { text: S.footnotes.notMeasured }), el('ul', null, f.not_found.map(function (n) {
14420
+ return el('li', { text: n.at + ': ' + n.reason });
14421
+ }))]));
14422
+ }
14423
+ var build = f.measured_with.build === null ? '' : ' (' + f.measured_with.build + ')';
14424
+ items.push(el('li', { text: fill(S.footnotes.measuredWith, { basou: f.measured_with.basou, build: build }) }));
14425
+ return section(S.sections.footnotes, false, [el('ul', { class: 'foot' }, items)]);
14426
+ }
14427
+ var liveDrawn = 0;
14428
+ function drawLive(d, why) {
14429
+ S = d.strings;
14430
+ lang = d.language;
14431
+ document.documentElement.lang = lang;
14432
+ document.title = S.pageTitle;
14433
+ var drawn = ++liveDrawn;
14434
+ var nav = $('records');
14435
+ clear(nav);
14436
+ // Asked for with ?live=1: lead back to the records, when there are any.
14437
+ if (why === null) {
14438
+ getJson('/api/board').then(function (b) {
14439
+ var list = b && b.page && b.page.records ? b.page.records : [];
14440
+ if (drawn === liveDrawn && list.length > 0) nav.appendChild(el('a', { href: '/board', text: S.live.toRecords }));
14441
+ }).catch(function () {});
14442
+ }
14443
+ var root = $('board');
14444
+ clear(root);
14445
+ setStatus('', false);
14446
+ var l = d.live;
14447
+ [liveHeading(l, why), effort(l), liveRepos(l), liveTrail(l), liveComponents(l), liveFootnotes(l, why)]
14448
+ .forEach(function (node) { root.appendChild(node); });
14449
+ }
14450
+
14451
+ function getJson(url) {
14452
+ return fetch(url).then(function (res) {
13968
14453
  return res.json().then(function (body) {
13969
14454
  if (!res.ok) throw new Error(body && body.error ? body.error : String(res.status));
13970
14455
  return body;
13971
14456
  });
13972
- })
13973
- .then(draw)
13974
- .catch(function (err) { setStatus(S ? fill(S.loadFailed, { message: err.message }) : String(err.message), true); });
14457
+ });
14458
+ }
14459
+ function failed(err) { setStatus(S ? fill(S.loadFailed, { message: err.message }) : String(err.message), true); }
14460
+ // Measuring takes seconds, so the page says so until it is done.
14461
+ function loadLive(why) {
14462
+ setStatus(S ? S.live.measuring : '...', false);
14463
+ getJson('/api/board/live').then(function (d) { drawLive(d, why); }).catch(failed);
14464
+ }
14465
+
14466
+ var params = new URLSearchParams(location.search);
14467
+ var record = params.get('record');
14468
+ if (params.get('live') !== null) {
14469
+ loadLive(null);
14470
+ } else {
14471
+ setStatus('...', false);
14472
+ getJson('/api/board' + (record ? '/' + encodeURIComponent(record) : '')).then(draw).catch(failed);
14473
+ }
13975
14474
  })();
13976
14475
  </script>
13977
14476
  </body>
@@ -14792,7 +15291,8 @@ async function handleGet(res, pathname, deps) {
14792
15291
  ws,
14793
15292
  deps.nowProvider,
14794
15293
  remoteUrlOf(deps),
14795
- boardOf(deps)
15294
+ boardOf(deps),
15295
+ boardLiveOf(deps)
14796
15296
  )) {
14797
15297
  sendError(res, 404, "Not found");
14798
15298
  }
@@ -14806,7 +15306,8 @@ async function handleGet(res, pathname, deps) {
14806
15306
  primaryWorkspace(deps),
14807
15307
  deps.nowProvider,
14808
15308
  remoteUrlOf(deps),
14809
- boardOf(deps)
15309
+ boardOf(deps),
15310
+ boardLiveOf(deps)
14810
15311
  )) {
14811
15312
  sendError(res, 404, "Not found");
14812
15313
  }
@@ -14817,6 +15318,9 @@ async function handleGet(res, pathname, deps) {
14817
15318
  function boardOf(deps) {
14818
15319
  return deps.mode === "single" ? deps.board : void 0;
14819
15320
  }
15321
+ function boardLiveOf(deps) {
15322
+ return deps.mode === "single" ? deps.boardLive : void 0;
15323
+ }
14820
15324
  function remoteUrlOf(deps) {
14821
15325
  return deps.remoteUrlOf ?? tryRemoteUrl;
14822
15326
  }
@@ -14842,7 +15346,11 @@ async function handlePost(res, pathname, body, deps, runExclusive) {
14842
15346
  }
14843
15347
  sendError(res, 404, "Not found");
14844
15348
  }
14845
- async function handleWorkspaceGet(res, sub, ws, nowProvider, resolveRemoteUrl, board) {
15349
+ async function handleWorkspaceGet(res, sub, ws, nowProvider, resolveRemoteUrl, board, live) {
15350
+ if (live !== void 0 && sub === "board/live") {
15351
+ sendJson(res, 200, await boardLiveView(ws, live));
15352
+ return true;
15353
+ }
14846
15354
  if (board !== void 0) {
14847
15355
  if (sub === "board") {
14848
15356
  sendJson(res, 200, await boardView(ws, board, void 0));
@@ -14958,6 +15466,14 @@ async function boardView(ws, board, recordId) {
14958
15466
  const page = "why" in board ? boardPageUnavailable(board.why) : await boardPage(board.recordsDir, recordId);
14959
15467
  return { language, strings: boardPageStrings(language), page };
14960
15468
  }
15469
+ async function boardLiveView(ws, live) {
15470
+ const language = await resolveViewLanguageFromPaths(ws.paths);
15471
+ return {
15472
+ language,
15473
+ strings: boardPageStrings(language),
15474
+ live: boardLivePage(ws.label, await live())
15475
+ };
15476
+ }
14961
15477
  async function portfolio(deps) {
14962
15478
  const nowIso = deps.nowProvider().toISOString();
14963
15479
  const workspaces = await Promise.all(deps.workspaces.map((ws) => portfolioCard(ws, nowIso)));
@@ -15010,7 +15526,7 @@ async function captureStaleness(ws, nowIso) {
15010
15526
  async function overview(ws, nowProvider, resolveRemoteUrl) {
15011
15527
  let manifest;
15012
15528
  try {
15013
- manifest = await readManifest17(ws.paths);
15529
+ manifest = await readManifest18(ws.paths);
15014
15530
  } catch (error) {
15015
15531
  if (findErrorCode18(error, "ENOENT")) {
15016
15532
  return { initialized: false, repoRoot: ws.repoRoot };
@@ -15095,7 +15611,7 @@ async function sessionDetail(ws, sessionId) {
15095
15611
  throw error;
15096
15612
  }
15097
15613
  try {
15098
- const events = await readAllEvents2(join18(ws.paths.sessions, sessionId));
15614
+ const events = await readAllEvents2(join19(ws.paths.sessions, sessionId));
15099
15615
  return { session, events };
15100
15616
  } catch {
15101
15617
  return { session, events: [], degraded: true };
@@ -15330,18 +15846,37 @@ async function buildSingleDeps(ctx, cwd) {
15330
15846
  mode: "single",
15331
15847
  nowProvider: nowProviderOf(ctx),
15332
15848
  ...ctx.remoteUrlOf !== void 0 ? { remoteUrlOf: ctx.remoteUrlOf } : {},
15333
- board: await viewBoardOf(repositoryRoot, paths)
15849
+ board: await viewBoardOf(repositoryRoot, paths),
15850
+ boardLive: liveBoardOf(repositoryRoot, ctx)
15851
+ };
15852
+ }
15853
+ function liveBoardOf(root, ctx) {
15854
+ return sharedWhileRunning(
15855
+ () => measureLiveBoard(root, {
15856
+ ...ctx.nowProvider !== void 0 ? { nowProvider: ctx.nowProvider } : {},
15857
+ ...ctx.claudeProjectsDir !== void 0 ? { claudeProjectsDir: ctx.claudeProjectsDir } : {},
15858
+ ...ctx.codexSessionsDir !== void 0 ? { codexSessionsDir: ctx.codexSessionsDir } : {}
15859
+ })
15860
+ );
15861
+ }
15862
+ function sharedWhileRunning(run) {
15863
+ let running = null;
15864
+ return () => {
15865
+ running ??= run().finally(() => {
15866
+ running = null;
15867
+ });
15868
+ return running;
15334
15869
  };
15335
15870
  }
15336
15871
  async function viewBoardOf(root, paths) {
15337
15872
  let own;
15338
15873
  try {
15339
- own = (await readManifest18(paths)).repos?.find((repo) => repo.path === ".");
15874
+ own = (await readManifest19(paths)).repos?.find((repo) => repo.path === ".");
15340
15875
  } catch {
15341
15876
  own = void 0;
15342
15877
  }
15343
15878
  if (own?.visibility !== "private") return { why: "no_board" };
15344
- return { recordsDir: join19(root, dirname8(DEFAULT_BOARD_PATH), "records") };
15879
+ return { recordsDir: join20(root, dirname9(DEFAULT_BOARD_PATH), "records") };
15345
15880
  }
15346
15881
  async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
15347
15882
  const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve14(cwd, p) })) : await loadPortfolioConfig6(ctx.portfolioConfigPath);
@@ -15374,7 +15909,7 @@ async function buildWorkspaceEntry(repoRoot, ctx, labelOverride) {
15374
15909
  ...ctx.codexSessionsDir !== void 0 ? { codexSessionsDir: ctx.codexSessionsDir } : {}
15375
15910
  };
15376
15911
  try {
15377
- const manifest = await readManifest18(paths);
15912
+ const manifest = await readManifest19(paths);
15378
15913
  return {
15379
15914
  key: manifest.workspace.id,
15380
15915
  label: labelOverride ?? manifest.workspace.name,
@@ -15495,7 +16030,7 @@ function installExperimentalNotice(program2, names = EXPERIMENTAL_COMMANDS, writ
15495
16030
  function readBuildStamp() {
15496
16031
  if (false) return void 0;
15497
16032
  try {
15498
- return JSON.parse('{"version":"0.66.0","commit":"2cd6700","committedAt":"2026-10-08T13:10:32+09:00"}');
16033
+ return JSON.parse('{"version":"0.68.0","commit":"52aa4d4","committedAt":"2026-10-09T22:45:36+09:00"}');
15499
16034
  } catch {
15500
16035
  return void 0;
15501
16036
  }