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