@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 +884 -349
- package/dist/index.js.map +1 -1
- package/dist/program.js +884 -349
- package/dist/program.js.map +1 -1
- package/package.json +2 -2
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
|
|
834
|
-
import { basename as
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
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({
|
|
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
|
|
1738
|
-
|
|
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 ?
|
|
1793
|
-
const recordsDir =
|
|
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
|
|
2309
|
+
return basename4(loaded.board.path) === "board.yaml";
|
|
1862
2310
|
}
|
|
1863
2311
|
async function previousOf(loaded) {
|
|
1864
|
-
return hasRecords(loaded) ? readPreviousRecords(
|
|
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 (
|
|
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
|
|
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(
|
|
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
|
|
1895
|
-
if (real === void 0 || dir !== real && !dir.startsWith(`${real}${
|
|
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
|
|
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:
|
|
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
|
|
2753
|
+
import { join as join6 } from "path";
|
|
2271
2754
|
import {
|
|
2272
2755
|
ORIENTATION_END,
|
|
2273
2756
|
ORIENTATION_START,
|
|
2274
2757
|
parseMarkers,
|
|
2275
|
-
readMarkdownFile as
|
|
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
|
|
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 =
|
|
2298
|
-
const tmpPath =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4016
|
+
const manifest = await readManifest6(paths);
|
|
3534
4017
|
await assertSessionStoreSafe2(paths);
|
|
3535
4018
|
const sessionId = prefixedUlid3("ses");
|
|
3536
|
-
const sessionDir =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3957
|
-
if (Array.isArray(value)) return value.map(
|
|
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] =
|
|
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(
|
|
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
|
|
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 =
|
|
4197
|
-
var DEFAULT_TARGET_PATH =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
5238
|
-
var DEFAULT_CODEX_CONFIG_PATH =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
6813
|
+
if (!existsSync(join12(real, ".git"))) {
|
|
6461
6814
|
return { ...base, reachable: false, currentLines: [] };
|
|
6462
6815
|
}
|
|
6463
|
-
return { ...base, reachable: true, currentLines: readGitignoreLines(
|
|
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 =
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
7007
|
+
if (!existsSync(join12(real, ".git"))) {
|
|
6655
7008
|
return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
|
|
6656
7009
|
}
|
|
6657
|
-
const canonicalFile = isSelf ?
|
|
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(
|
|
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 =
|
|
7045
|
+
const filePath = join12(real, name);
|
|
6693
7046
|
try {
|
|
6694
|
-
mkdirSync(
|
|
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(
|
|
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 =
|
|
7095
|
+
const filePath = join12(viewDir, f.name);
|
|
6743
7096
|
try {
|
|
6744
|
-
mkdirSync(
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
7349
|
+
const filePath = join12(viewDir, name);
|
|
6997
7350
|
try {
|
|
6998
|
-
mkdirSync(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
7631
|
+
return join12(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
7279
7632
|
}
|
|
7280
7633
|
function canonicalLabelFor(canonicalName) {
|
|
7281
|
-
return
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
7868
|
-
const canonicalLabel =
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
8737
|
-
const canonicalFile =
|
|
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(
|
|
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 =
|
|
9105
|
+
const agentsFile = join12(repoReal, CANONICAL_FILE);
|
|
8753
9106
|
try {
|
|
8754
|
-
mkdirSync(
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
9611
|
+
import { readdir as readdir3, stat as stat6 } from "fs/promises";
|
|
9259
9612
|
import { homedir as homedir9 } from "os";
|
|
9260
|
-
import { join as
|
|
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 ??
|
|
9273
|
-
ctx.claudeProjectsDir ??
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
10571
|
+
const manifest = await readManifest12(paths);
|
|
10219
10572
|
await assertSessionStoreSafe3(paths);
|
|
10220
10573
|
const sessionId = prefixedUlid4("ses");
|
|
10221
|
-
const sessionDir =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
10736
|
-
const sessionYamlPath =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 ??
|
|
12752
|
-
const codexSessionsDir = ctx.codexSessionsDir ??
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
13258
|
+
const full = join17(projectsRoot, entry.name);
|
|
12906
13259
|
let names;
|
|
12907
13260
|
try {
|
|
12908
|
-
names = await
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
13955
|
-
|
|
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
|
-
|
|
13965
|
-
|
|
13966
|
-
|
|
13967
|
-
|
|
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
|
-
|
|
13974
|
-
|
|
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
|
|
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(
|
|
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
|
|
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:
|
|
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
|
|
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.
|
|
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
|
}
|