@basou/cli 0.16.0 → 0.18.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 +340 -78
- package/dist/index.js.map +1 -1
- package/dist/program.js +340 -78
- package/dist/program.js.map +1 -1
- package/package.json +2 -2
package/dist/program.js
CHANGED
|
@@ -635,17 +635,21 @@ function printNoApprovals(options) {
|
|
|
635
635
|
// src/commands/decision.ts
|
|
636
636
|
import { readFile } from "fs/promises";
|
|
637
637
|
import { homedir as homedir2 } from "os";
|
|
638
|
-
import { resolve as resolve3 } from "path";
|
|
638
|
+
import { join as join3, resolve as resolve3 } from "path";
|
|
639
639
|
import {
|
|
640
|
+
AGENT_INFRA_DIRS,
|
|
640
641
|
acquireLock as acquireLock2,
|
|
641
642
|
appendEventToExistingSession,
|
|
642
643
|
assertBasouRootSafe as assertBasouRootSafe2,
|
|
643
644
|
basouPaths as basouPaths3,
|
|
645
|
+
classifyFilesBySourceRoot,
|
|
644
646
|
createAdHocSessionWithEvent,
|
|
645
647
|
findErrorCode as findErrorCode2,
|
|
646
648
|
isValidPrefixedId,
|
|
649
|
+
loadSessionEntries,
|
|
647
650
|
prefixedUlid as prefixedUlid2,
|
|
648
651
|
readManifest as readManifest2,
|
|
652
|
+
replayEvents as replayEvents2,
|
|
649
653
|
resolveRepositoryRoot as resolveRepositoryRoot2,
|
|
650
654
|
resolveSessionId,
|
|
651
655
|
sanitizePath
|
|
@@ -845,6 +849,17 @@ function registerDecisionCommand(program) {
|
|
|
845
849
|
).option("--file <path>", "Read the JSON array from a file instead of stdin").option("--dry-run", "Validate and preview the decisions without writing them").option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").addHelpText("after", CAPTURE_HELP).action(async (options) => {
|
|
846
850
|
await runDecisionCapture(options);
|
|
847
851
|
});
|
|
852
|
+
decision.command("void").description(
|
|
853
|
+
"Void (or supersede) a recorded decision. Append-only: the original is kept but struck in decisions.md and skipped as orientation's latest direction. Use when a decision was wrong or recorded in the wrong project."
|
|
854
|
+
).argument("<decision_id>", "The decision to void (its decision_ ULID)").option("--reason <text>", "Why the decision is voided", parseReason).option(
|
|
855
|
+
"--superseded-by <decision_id>",
|
|
856
|
+
"The decision that replaces this one (records a supersede rather than a plain void)"
|
|
857
|
+
).option(
|
|
858
|
+
"--session <session_id>",
|
|
859
|
+
"Attach to an existing session; otherwise an ad-hoc session is created"
|
|
860
|
+
).option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").action(async (decisionId, options) => {
|
|
861
|
+
await runDecisionVoid(decisionId, options);
|
|
862
|
+
});
|
|
848
863
|
}
|
|
849
864
|
var CAPTURE_HELP = `
|
|
850
865
|
Input format (a JSON array; one object per decision):
|
|
@@ -879,6 +894,28 @@ async function runDecisionRecord(options, ctx = {}) {
|
|
|
879
894
|
process.exitCode = 1;
|
|
880
895
|
}
|
|
881
896
|
}
|
|
897
|
+
async function warnLinkedFilesOutsideRoots(input) {
|
|
898
|
+
if (input.linkedFiles.length === 0) return;
|
|
899
|
+
try {
|
|
900
|
+
const manifest = await readManifest2(input.paths);
|
|
901
|
+
if ((manifest.import?.source_roots?.length ?? 0) === 0) return;
|
|
902
|
+
const scope = await classifyFilesBySourceRoot({
|
|
903
|
+
files: input.linkedFiles,
|
|
904
|
+
workingDirectory: input.cwd,
|
|
905
|
+
sourceRoots: manifest.import?.source_roots,
|
|
906
|
+
masterRoot: input.repositoryRoot,
|
|
907
|
+
extraInRoot: AGENT_INFRA_DIRS
|
|
908
|
+
});
|
|
909
|
+
if (scope.outOfRoot.length === 0) return;
|
|
910
|
+
const PATH_SAMPLE = 5;
|
|
911
|
+
const sample = scope.outOfRoot.slice(0, PATH_SAMPLE).join(", ");
|
|
912
|
+
const more = scope.outOfRoot.length > PATH_SAMPLE ? ` (... +${scope.outOfRoot.length - PATH_SAMPLE} more)` : "";
|
|
913
|
+
console.error(
|
|
914
|
+
`basou: ${scope.outOfRoot.length} linked file(s) resolve outside this project's source_roots: ${sample}${more} \u2014 this decision may belong to another project.`
|
|
915
|
+
);
|
|
916
|
+
} catch {
|
|
917
|
+
}
|
|
918
|
+
}
|
|
882
919
|
async function doRunDecisionRecord(options, ctx) {
|
|
883
920
|
const cwd = ctx.cwd ?? process.cwd();
|
|
884
921
|
const repositoryRoot = await resolveRepositoryRootForDecision(cwd);
|
|
@@ -888,6 +925,12 @@ async function doRunDecisionRecord(options, ctx) {
|
|
|
888
925
|
const occurredAt = now.toISOString();
|
|
889
926
|
const decisionId = prefixedUlid2("decision");
|
|
890
927
|
const rich = pickRichFields(options);
|
|
928
|
+
await warnLinkedFilesOutsideRoots({
|
|
929
|
+
linkedFiles: rich.linked_files ?? [],
|
|
930
|
+
cwd,
|
|
931
|
+
paths,
|
|
932
|
+
repositoryRoot
|
|
933
|
+
});
|
|
891
934
|
if (options.session !== void 0) {
|
|
892
935
|
const sessionId = await resolveSessionId(paths, options.session);
|
|
893
936
|
const sesId = sessionId;
|
|
@@ -971,6 +1014,12 @@ async function doRunDecisionCapture(options, ctx) {
|
|
|
971
1014
|
await assertWorkspaceInitialized2(paths.root);
|
|
972
1015
|
const raw = await readCaptureInput(options, ctx);
|
|
973
1016
|
const decisions = parseCaptureInput(raw);
|
|
1017
|
+
await warnLinkedFilesOutsideRoots({
|
|
1018
|
+
linkedFiles: decisions.flatMap((d) => d.linked_files ?? []),
|
|
1019
|
+
cwd,
|
|
1020
|
+
paths,
|
|
1021
|
+
repositoryRoot
|
|
1022
|
+
});
|
|
974
1023
|
if (options.dryRun === true) {
|
|
975
1024
|
printCapturePreview(options, decisions);
|
|
976
1025
|
return;
|
|
@@ -1017,6 +1066,161 @@ async function doRunDecisionCapture(options, ctx) {
|
|
|
1017
1066
|
}))
|
|
1018
1067
|
});
|
|
1019
1068
|
}
|
|
1069
|
+
async function runDecisionVoid(decisionId, options, ctx = {}) {
|
|
1070
|
+
try {
|
|
1071
|
+
await doRunDecisionVoid(decisionId, options, ctx);
|
|
1072
|
+
} catch (error) {
|
|
1073
|
+
renderCliError(error, {
|
|
1074
|
+
verbose: isVerbose(options),
|
|
1075
|
+
classifiers: [failedToFinalizeClassifier]
|
|
1076
|
+
});
|
|
1077
|
+
process.exitCode = 1;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
async function doRunDecisionVoid(decisionId, options, ctx) {
|
|
1081
|
+
if (!isDecisionId(decisionId)) {
|
|
1082
|
+
throw new Error(`Invalid decision id: ${decisionId} (expected a decision_<ULID>).`);
|
|
1083
|
+
}
|
|
1084
|
+
if (options.supersededBy !== void 0 && !isDecisionId(options.supersededBy)) {
|
|
1085
|
+
throw new Error(
|
|
1086
|
+
`Invalid --superseded-by id: ${options.supersededBy} (expected a decision_<ULID>).`
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
if (options.supersededBy === decisionId) {
|
|
1090
|
+
throw new Error("A decision cannot supersede itself.");
|
|
1091
|
+
}
|
|
1092
|
+
const cwd = ctx.cwd ?? process.cwd();
|
|
1093
|
+
const repositoryRoot = await resolveBasouRootForCommand(cwd, "decision void");
|
|
1094
|
+
const paths = basouPaths3(repositoryRoot);
|
|
1095
|
+
await assertWorkspaceInitialized2(paths.root);
|
|
1096
|
+
if (!await decisionExists(paths, decisionId)) {
|
|
1097
|
+
throw new Error(
|
|
1098
|
+
`Decision ${decisionId} not found in this workspace. Run 'basou decisions generate' or check the id.`
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
|
|
1102
|
+
const occurredAt = now.toISOString();
|
|
1103
|
+
const reason = options.reason;
|
|
1104
|
+
const supersededBy = options.supersededBy;
|
|
1105
|
+
if (options.session !== void 0) {
|
|
1106
|
+
const sessionId = await resolveSessionId(paths, options.session);
|
|
1107
|
+
const sessionLock = await acquireLock2(paths, "session", sessionId);
|
|
1108
|
+
let result;
|
|
1109
|
+
try {
|
|
1110
|
+
result = await appendEventToExistingSession({
|
|
1111
|
+
paths,
|
|
1112
|
+
sessionId,
|
|
1113
|
+
eventBuilder: (eventId) => buildDecisionVoidedEvent({
|
|
1114
|
+
eventId,
|
|
1115
|
+
sessionId,
|
|
1116
|
+
decisionId,
|
|
1117
|
+
occurredAt,
|
|
1118
|
+
reason,
|
|
1119
|
+
supersededBy
|
|
1120
|
+
})
|
|
1121
|
+
});
|
|
1122
|
+
} finally {
|
|
1123
|
+
await sessionLock.release();
|
|
1124
|
+
}
|
|
1125
|
+
printVoidResult(options, {
|
|
1126
|
+
mode: "attached",
|
|
1127
|
+
sessionId,
|
|
1128
|
+
decisionId,
|
|
1129
|
+
eventId: result.eventId,
|
|
1130
|
+
sessionStatus: result.sessionStatus,
|
|
1131
|
+
reason,
|
|
1132
|
+
supersededBy
|
|
1133
|
+
});
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
const manifest = await readManifest2(paths);
|
|
1137
|
+
const adHoc = await createAdHocSessionWithEvent({
|
|
1138
|
+
paths,
|
|
1139
|
+
manifest,
|
|
1140
|
+
label: `Ad-hoc decision void: ${decisionId}`,
|
|
1141
|
+
occurredAt,
|
|
1142
|
+
sessionSource: "human",
|
|
1143
|
+
workingDirectory: repositoryRoot,
|
|
1144
|
+
invocation: { command: "basou decision void", args: [decisionId] },
|
|
1145
|
+
targetEventBuilders: [
|
|
1146
|
+
(sessionId, eventId) => buildDecisionVoidedEvent({
|
|
1147
|
+
eventId,
|
|
1148
|
+
sessionId,
|
|
1149
|
+
decisionId,
|
|
1150
|
+
occurredAt,
|
|
1151
|
+
reason,
|
|
1152
|
+
supersededBy
|
|
1153
|
+
})
|
|
1154
|
+
]
|
|
1155
|
+
});
|
|
1156
|
+
printVoidResult(options, {
|
|
1157
|
+
mode: "ad-hoc",
|
|
1158
|
+
sessionId: adHoc.sessionId,
|
|
1159
|
+
decisionId,
|
|
1160
|
+
eventId: adHoc.targetEventIds[0],
|
|
1161
|
+
sessionStatus: "completed",
|
|
1162
|
+
reason,
|
|
1163
|
+
supersededBy
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
function isDecisionId(value) {
|
|
1167
|
+
return value.startsWith("decision_") && isValidPrefixedId(value);
|
|
1168
|
+
}
|
|
1169
|
+
async function decisionExists(paths, decisionId) {
|
|
1170
|
+
const entries = await loadSessionEntries(paths, { now: /* @__PURE__ */ new Date() });
|
|
1171
|
+
for (const entry of entries) {
|
|
1172
|
+
const sessionDir = join3(paths.sessions, entry.sessionId);
|
|
1173
|
+
try {
|
|
1174
|
+
for await (const ev of replayEvents2(sessionDir, {})) {
|
|
1175
|
+
if (ev.type === "decision_recorded" && ev.decision_id === decisionId) return true;
|
|
1176
|
+
}
|
|
1177
|
+
} catch {
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
return false;
|
|
1181
|
+
}
|
|
1182
|
+
function buildDecisionVoidedEvent(input) {
|
|
1183
|
+
return {
|
|
1184
|
+
schema_version: "0.1.0",
|
|
1185
|
+
id: input.eventId,
|
|
1186
|
+
session_id: input.sessionId,
|
|
1187
|
+
occurred_at: input.occurredAt,
|
|
1188
|
+
source: "local-cli",
|
|
1189
|
+
type: "decision_voided",
|
|
1190
|
+
decision_id: input.decisionId,
|
|
1191
|
+
...input.reason !== void 0 ? { reason: input.reason } : {},
|
|
1192
|
+
...input.supersededBy !== void 0 ? { superseded_by: input.supersededBy } : {}
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
function printVoidResult(options, result) {
|
|
1196
|
+
if (options.json === true) {
|
|
1197
|
+
console.log(
|
|
1198
|
+
JSON.stringify({
|
|
1199
|
+
event_id: result.eventId,
|
|
1200
|
+
session_id: result.sessionId,
|
|
1201
|
+
decision_id: result.decisionId,
|
|
1202
|
+
session_status: result.sessionStatus,
|
|
1203
|
+
mode: result.mode,
|
|
1204
|
+
...result.reason !== void 0 ? { reason: result.reason } : {},
|
|
1205
|
+
...result.supersededBy !== void 0 ? { superseded_by: result.supersededBy } : {}
|
|
1206
|
+
})
|
|
1207
|
+
);
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
const sid = shortSessionId(result.sessionId);
|
|
1211
|
+
const tail = result.supersededBy !== void 0 ? ` (superseded by ${result.supersededBy})` : "";
|
|
1212
|
+
if (result.mode === "ad-hoc") {
|
|
1213
|
+
console.log(`Voided ${result.decisionId} in ad-hoc session ${sid}${tail}`);
|
|
1214
|
+
} else {
|
|
1215
|
+
console.log(`Voided ${result.decisionId} in session ${sid} (${result.sessionStatus})${tail}`);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
function parseReason(raw) {
|
|
1219
|
+
if (raw.trim().length === 0) {
|
|
1220
|
+
throw new InvalidArgumentError("--reason must not be empty");
|
|
1221
|
+
}
|
|
1222
|
+
return raw;
|
|
1223
|
+
}
|
|
1020
1224
|
async function readCaptureInput(options, ctx) {
|
|
1021
1225
|
if (options.file !== void 0) {
|
|
1022
1226
|
try {
|
|
@@ -1408,7 +1612,7 @@ async function assertWorkspaceInitialized3(basouRoot) {
|
|
|
1408
1612
|
// src/commands/exec.ts
|
|
1409
1613
|
import { mkdir } from "fs/promises";
|
|
1410
1614
|
import { homedir as homedir3 } from "os";
|
|
1411
|
-
import { join as
|
|
1615
|
+
import { join as join4 } from "path";
|
|
1412
1616
|
import {
|
|
1413
1617
|
acquireLock as acquireLock3,
|
|
1414
1618
|
assertBasouRootSafe as assertBasouRootSafe4,
|
|
@@ -1448,13 +1652,13 @@ async function runExec(command, args, options, ctx = {}) {
|
|
|
1448
1652
|
await assertBasouRootSafe4(paths.root);
|
|
1449
1653
|
const manifest = await readManifest3(paths);
|
|
1450
1654
|
const sessionId = prefixedUlid3("ses");
|
|
1451
|
-
const sessionDir =
|
|
1655
|
+
const sessionDir = join4(paths.sessions, sessionId);
|
|
1452
1656
|
await mkdir(sessionDir, { recursive: true });
|
|
1453
1657
|
const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
|
|
1454
1658
|
await coreAppendChainedEvent(paths, sessionId, event);
|
|
1455
1659
|
});
|
|
1456
1660
|
const startedAt = now().toISOString();
|
|
1457
|
-
const sessionYamlPath =
|
|
1661
|
+
const sessionYamlPath = join4(sessionDir, "session.yaml");
|
|
1458
1662
|
const session = buildInitialSession({
|
|
1459
1663
|
id: sessionId,
|
|
1460
1664
|
command,
|
|
@@ -1804,13 +2008,15 @@ async function assertWorkspaceInitialized4(basouRoot) {
|
|
|
1804
2008
|
import { createReadStream } from "fs";
|
|
1805
2009
|
import { readdir, readFile as readFile2, rm, stat as stat2 } from "fs/promises";
|
|
1806
2010
|
import { homedir as homedir4 } from "os";
|
|
1807
|
-
import { basename as basename2, join as
|
|
2011
|
+
import { basename as basename2, dirname, join as join5, resolve as resolve4 } from "path";
|
|
1808
2012
|
import { createInterface } from "readline";
|
|
1809
2013
|
import {
|
|
2014
|
+
AGENT_INFRA_DIRS as AGENT_INFRA_DIRS2,
|
|
1810
2015
|
assertBasouRootSafe as assertBasouRootSafe6,
|
|
1811
2016
|
basouPaths as basouPaths7,
|
|
1812
2017
|
CLAUDE_IMPORT_SOURCE,
|
|
1813
2018
|
CODEX_IMPORT_SOURCE,
|
|
2019
|
+
classifyFilesBySourceRoot as classifyFilesBySourceRoot2,
|
|
1814
2020
|
claudeTranscriptToImportPayload,
|
|
1815
2021
|
codexRolloutToImportPayload,
|
|
1816
2022
|
enumerateSessionDirs,
|
|
@@ -1888,7 +2094,7 @@ async function doRunImportClaudeCode(options, ctx) {
|
|
|
1888
2094
|
repoRoot: repositoryRoot,
|
|
1889
2095
|
cwd: ctx.cwd ?? process.cwd()
|
|
1890
2096
|
});
|
|
1891
|
-
const projectsRoot = ctx.claudeProjectsDir ??
|
|
2097
|
+
const projectsRoot = ctx.claudeProjectsDir ?? join5(homedir4(), ".claude", "projects");
|
|
1892
2098
|
const files = await selectTranscriptFiles(projectsRoot, projectPaths, options);
|
|
1893
2099
|
const projectSet = new Set(projectPaths);
|
|
1894
2100
|
const candidates = files.map((file) => {
|
|
@@ -1908,7 +2114,15 @@ async function doRunImportClaudeCode(options, ctx) {
|
|
|
1908
2114
|
}
|
|
1909
2115
|
};
|
|
1910
2116
|
});
|
|
1911
|
-
await importDerivedSessions(
|
|
2117
|
+
await importDerivedSessions(
|
|
2118
|
+
paths,
|
|
2119
|
+
manifest,
|
|
2120
|
+
options,
|
|
2121
|
+
CLAUDE_IMPORT_SOURCE,
|
|
2122
|
+
candidates,
|
|
2123
|
+
projectPaths,
|
|
2124
|
+
hasDeclaredBoundary(options, manifest)
|
|
2125
|
+
);
|
|
1912
2126
|
}
|
|
1913
2127
|
async function doRunImportCodex(options, ctx) {
|
|
1914
2128
|
assertSelector(options);
|
|
@@ -1919,7 +2133,7 @@ async function doRunImportCodex(options, ctx) {
|
|
|
1919
2133
|
repoRoot: repositoryRoot,
|
|
1920
2134
|
cwd: ctx.cwd ?? process.cwd()
|
|
1921
2135
|
});
|
|
1922
|
-
const sessionsRoot = ctx.codexSessionsDir ??
|
|
2136
|
+
const sessionsRoot = ctx.codexSessionsDir ?? join5(homedir4(), ".codex", "sessions");
|
|
1923
2137
|
const rollouts = await discoverCodexRollouts(sessionsRoot, projectPaths, options);
|
|
1924
2138
|
const candidates = rollouts.map(({ file, externalId }) => ({
|
|
1925
2139
|
externalId,
|
|
@@ -1933,7 +2147,18 @@ async function doRunImportCodex(options, ctx) {
|
|
|
1933
2147
|
});
|
|
1934
2148
|
}
|
|
1935
2149
|
}));
|
|
1936
|
-
await importDerivedSessions(
|
|
2150
|
+
await importDerivedSessions(
|
|
2151
|
+
paths,
|
|
2152
|
+
manifest,
|
|
2153
|
+
options,
|
|
2154
|
+
CODEX_IMPORT_SOURCE,
|
|
2155
|
+
candidates,
|
|
2156
|
+
projectPaths,
|
|
2157
|
+
hasDeclaredBoundary(options, manifest)
|
|
2158
|
+
);
|
|
2159
|
+
}
|
|
2160
|
+
function hasDeclaredBoundary(options, manifest) {
|
|
2161
|
+
return (options.project?.length ?? 0) > 0 || (manifest.import?.source_roots?.length ?? 0) > 0;
|
|
1937
2162
|
}
|
|
1938
2163
|
function assertSelector(options) {
|
|
1939
2164
|
if (options.session !== void 0 && options.all === true) {
|
|
@@ -1951,9 +2176,25 @@ async function resolveImportTarget(ctx) {
|
|
|
1951
2176
|
const manifest = await readManifest4(paths);
|
|
1952
2177
|
return { repositoryRoot, paths, manifest };
|
|
1953
2178
|
}
|
|
1954
|
-
async function importDerivedSessions(paths, manifest, options, sourceKind, candidates) {
|
|
2179
|
+
async function importDerivedSessions(paths, manifest, options, sourceKind, candidates, projectPaths, boundaryDeclared) {
|
|
1955
2180
|
const existingByExternalId = await loadExistingByExternalId(paths, sourceKind);
|
|
1956
2181
|
const seenThisRun = /* @__PURE__ */ new Set();
|
|
2182
|
+
const crossProjectCheck = boundaryDeclared;
|
|
2183
|
+
const crossProject = [];
|
|
2184
|
+
const noteCrossProject = async (externalId, payload) => {
|
|
2185
|
+
if (!crossProjectCheck) return;
|
|
2186
|
+
try {
|
|
2187
|
+
const scope = await classifyFilesBySourceRoot2({
|
|
2188
|
+
files: payload.session.related_files ?? [],
|
|
2189
|
+
workingDirectory: payload.session.working_directory,
|
|
2190
|
+
sourceRoots: projectPaths,
|
|
2191
|
+
masterRoot: dirname(paths.root),
|
|
2192
|
+
extraInRoot: AGENT_INFRA_DIRS2
|
|
2193
|
+
});
|
|
2194
|
+
if (scope.outOfRoot.length > 0) crossProject.push({ externalId, outOfRoot: scope.outOfRoot });
|
|
2195
|
+
} catch {
|
|
2196
|
+
}
|
|
2197
|
+
};
|
|
1957
2198
|
const results = [];
|
|
1958
2199
|
const counts = {
|
|
1959
2200
|
skippedNoAction: 0,
|
|
@@ -2010,6 +2251,7 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
|
|
|
2010
2251
|
}
|
|
2011
2252
|
counts.reimported++;
|
|
2012
2253
|
seenThisRun.add(externalId);
|
|
2254
|
+
await noteCrossProject(externalId, payload2);
|
|
2013
2255
|
continue;
|
|
2014
2256
|
}
|
|
2015
2257
|
const payload = validate(await toPayload());
|
|
@@ -2020,7 +2262,7 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
|
|
|
2020
2262
|
if (priors.length > 0 && options.force === true) {
|
|
2021
2263
|
if (options.dryRun !== true) {
|
|
2022
2264
|
for (const { sessionId } of priors) {
|
|
2023
|
-
await rm(
|
|
2265
|
+
await rm(join5(paths.sessions, sessionId), { recursive: true, force: true });
|
|
2024
2266
|
}
|
|
2025
2267
|
}
|
|
2026
2268
|
counts.replaced++;
|
|
@@ -2031,10 +2273,21 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
|
|
|
2031
2273
|
results.push(result);
|
|
2032
2274
|
seenThisRun.add(externalId);
|
|
2033
2275
|
sanitizedPaths += result.pathSanitizeReport.relatedFiles + (result.pathSanitizeReport.workingDirectoryRewritten ? 1 : 0);
|
|
2276
|
+
await noteCrossProject(externalId, payload);
|
|
2034
2277
|
}
|
|
2035
2278
|
if (sanitizedPaths > 0) {
|
|
2036
2279
|
console.error(`Imported sessions: ${sanitizedPaths} path(s) sanitized`);
|
|
2037
2280
|
}
|
|
2281
|
+
if (crossProject.length > 0) {
|
|
2282
|
+
const PATH_SAMPLE = 5;
|
|
2283
|
+
for (const { externalId, outOfRoot } of crossProject) {
|
|
2284
|
+
const sample = outOfRoot.slice(0, PATH_SAMPLE).join(", ");
|
|
2285
|
+
const more = outOfRoot.length > PATH_SAMPLE ? ` (... +${outOfRoot.length - PATH_SAMPLE} more)` : "";
|
|
2286
|
+
console.error(
|
|
2287
|
+
`basou: session ${externalId} edited ${outOfRoot.length} file(s) outside this project's source_roots: ${sample}${more} \u2014 they may belong to another project.`
|
|
2288
|
+
);
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2038
2291
|
printImportResult(options, results, counts);
|
|
2039
2292
|
}
|
|
2040
2293
|
async function classifyReimport(priors, sourcePath, externalId, counts) {
|
|
@@ -2120,7 +2373,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
|
|
|
2120
2373
|
if (options.session !== void 0) {
|
|
2121
2374
|
const matches = [];
|
|
2122
2375
|
for (const projectPath of projectPaths) {
|
|
2123
|
-
const file =
|
|
2376
|
+
const file = join5(projectsRoot, encodeProjectDir(projectPath), `${options.session}.jsonl`);
|
|
2124
2377
|
if (await pathExists(file)) matches.push(file);
|
|
2125
2378
|
}
|
|
2126
2379
|
if (matches.length === 0) {
|
|
@@ -2131,7 +2384,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
|
|
|
2131
2384
|
const files = [];
|
|
2132
2385
|
let anyDirFound = false;
|
|
2133
2386
|
for (const projectPath of projectPaths) {
|
|
2134
|
-
const transcriptDir =
|
|
2387
|
+
const transcriptDir = join5(projectsRoot, encodeProjectDir(projectPath));
|
|
2135
2388
|
let entries;
|
|
2136
2389
|
try {
|
|
2137
2390
|
entries = await readdir(transcriptDir);
|
|
@@ -2141,7 +2394,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
|
|
|
2141
2394
|
}
|
|
2142
2395
|
anyDirFound = true;
|
|
2143
2396
|
for (const name of entries) {
|
|
2144
|
-
if (name.endsWith(".jsonl")) files.push(
|
|
2397
|
+
if (name.endsWith(".jsonl")) files.push(join5(transcriptDir, name));
|
|
2145
2398
|
}
|
|
2146
2399
|
}
|
|
2147
2400
|
if (!anyDirFound) {
|
|
@@ -2198,7 +2451,7 @@ async function findRolloutFiles(sessionsRoot) {
|
|
|
2198
2451
|
throw new Error("Failed to read Codex sessions directory", { cause: error });
|
|
2199
2452
|
}
|
|
2200
2453
|
for (const entry of entries) {
|
|
2201
|
-
const full =
|
|
2454
|
+
const full = join5(dir, entry.name);
|
|
2202
2455
|
if (entry.isDirectory()) {
|
|
2203
2456
|
await walk(full, false);
|
|
2204
2457
|
} else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) {
|
|
@@ -2622,12 +2875,12 @@ import {
|
|
|
2622
2875
|
|
|
2623
2876
|
// src/lib/hosts-config.ts
|
|
2624
2877
|
import { homedir as homedir5 } from "os";
|
|
2625
|
-
import { isAbsolute as isAbsolute2, join as
|
|
2878
|
+
import { isAbsolute as isAbsolute2, join as join6, resolve as resolve6 } from "path";
|
|
2626
2879
|
import { readYamlFile as readYamlFile4 } from "@basou/core";
|
|
2627
|
-
var DEFAULT_HOSTS_CONFIG_PATH =
|
|
2880
|
+
var DEFAULT_HOSTS_CONFIG_PATH = join6(homedir5(), ".basou", "hosts.yaml");
|
|
2628
2881
|
function expandTilde2(p) {
|
|
2629
2882
|
if (p === "~") return homedir5();
|
|
2630
|
-
if (p.startsWith("~/")) return
|
|
2883
|
+
if (p.startsWith("~/")) return join6(homedir5(), p.slice(2));
|
|
2631
2884
|
return p;
|
|
2632
2885
|
}
|
|
2633
2886
|
function isRecord2(value) {
|
|
@@ -2941,7 +3194,7 @@ import {
|
|
|
2941
3194
|
unlinkSync,
|
|
2942
3195
|
writeFileSync
|
|
2943
3196
|
} from "fs";
|
|
2944
|
-
import { basename as basename4, dirname, isAbsolute as isAbsolute3, join as
|
|
3197
|
+
import { basename as basename4, dirname as dirname2, isAbsolute as isAbsolute3, join as join7, relative as relative2, resolve as resolve7 } from "path";
|
|
2945
3198
|
import {
|
|
2946
3199
|
basouPaths as basouPaths10,
|
|
2947
3200
|
GENERATED_END,
|
|
@@ -3200,7 +3453,7 @@ function classifySourceRoot(repositoryRoot, declaredPath) {
|
|
|
3200
3453
|
} catch {
|
|
3201
3454
|
return { path: declaredPath, kind: "unresolved" };
|
|
3202
3455
|
}
|
|
3203
|
-
return { path: declaredPath, kind: existsSync(
|
|
3456
|
+
return { path: declaredPath, kind: existsSync(join7(real, ".git")) ? "repo" : "non-repo" };
|
|
3204
3457
|
}
|
|
3205
3458
|
async function doRunProjectAdopt(options, ctx) {
|
|
3206
3459
|
const cwd = ctx.cwd ?? process.cwd();
|
|
@@ -3299,7 +3552,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
|
|
|
3299
3552
|
} catch {
|
|
3300
3553
|
return { ...base, reachable: false, instructionFiles: [] };
|
|
3301
3554
|
}
|
|
3302
|
-
if (!existsSync(
|
|
3555
|
+
if (!existsSync(join7(real, ".git"))) {
|
|
3303
3556
|
return { ...base, reachable: false, instructionFiles: [] };
|
|
3304
3557
|
}
|
|
3305
3558
|
try {
|
|
@@ -3307,7 +3560,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
|
|
|
3307
3560
|
for (const name of INSTRUCTION_FILES) {
|
|
3308
3561
|
let present = true;
|
|
3309
3562
|
try {
|
|
3310
|
-
lstatSync(
|
|
3563
|
+
lstatSync(join7(real, name));
|
|
3311
3564
|
} catch {
|
|
3312
3565
|
present = false;
|
|
3313
3566
|
}
|
|
@@ -3404,10 +3657,10 @@ function gatherRepoGitignore(repositoryRoot, entry) {
|
|
|
3404
3657
|
} catch {
|
|
3405
3658
|
return { ...base, reachable: false, currentLines: [] };
|
|
3406
3659
|
}
|
|
3407
|
-
if (!existsSync(
|
|
3660
|
+
if (!existsSync(join7(real, ".git"))) {
|
|
3408
3661
|
return { ...base, reachable: false, currentLines: [] };
|
|
3409
3662
|
}
|
|
3410
|
-
return { ...base, reachable: true, currentLines: readGitignoreLines(
|
|
3663
|
+
return { ...base, reachable: true, currentLines: readGitignoreLines(join7(real, ".gitignore")) };
|
|
3411
3664
|
}
|
|
3412
3665
|
function hasErrorCode(error) {
|
|
3413
3666
|
return error instanceof Error && typeof error.code === "string";
|
|
@@ -3421,7 +3674,7 @@ function readGitignoreLines(file) {
|
|
|
3421
3674
|
}
|
|
3422
3675
|
}
|
|
3423
3676
|
function applyGitignorePlan(repositoryRoot, plan) {
|
|
3424
|
-
const file =
|
|
3677
|
+
const file = join7(realpathSync(resolve7(repositoryRoot, plan.path)), ".gitignore");
|
|
3425
3678
|
let existing = "";
|
|
3426
3679
|
try {
|
|
3427
3680
|
existing = readFileSync(file, "utf8");
|
|
@@ -3540,16 +3793,16 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
|
|
|
3540
3793
|
if (real === anchorReal) {
|
|
3541
3794
|
return { ...base, isAnchor: true, reachable: true, canonicalPresent: false, files: [] };
|
|
3542
3795
|
}
|
|
3543
|
-
if (!existsSync(
|
|
3796
|
+
if (!existsSync(join7(real, ".git"))) {
|
|
3544
3797
|
return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
|
|
3545
3798
|
}
|
|
3546
|
-
const canonicalFile =
|
|
3799
|
+
const canonicalFile = join7(anchorReal, "agents", basename4(real), CANONICAL_FILE);
|
|
3547
3800
|
if (!existsSync(canonicalFile)) {
|
|
3548
3801
|
return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
|
|
3549
3802
|
}
|
|
3550
3803
|
const files = expectedSymlinkTargets(real, canonicalFile).map(
|
|
3551
3804
|
(spec) => {
|
|
3552
|
-
const { state, actualTarget } = inspectSymlink(
|
|
3805
|
+
const { state, actualTarget } = inspectSymlink(join7(real, spec.name), spec.target);
|
|
3553
3806
|
return {
|
|
3554
3807
|
name: spec.name,
|
|
3555
3808
|
expectedTarget: spec.target,
|
|
@@ -3578,9 +3831,9 @@ function applySymlinkPlan(repositoryRoot, plan) {
|
|
|
3578
3831
|
const created = [];
|
|
3579
3832
|
const failed = [];
|
|
3580
3833
|
for (const { name, target } of plan.toCreate) {
|
|
3581
|
-
const filePath =
|
|
3834
|
+
const filePath = join7(real, name);
|
|
3582
3835
|
try {
|
|
3583
|
-
mkdirSync(
|
|
3836
|
+
mkdirSync(dirname2(filePath), { recursive: true });
|
|
3584
3837
|
symlinkSync(target, filePath);
|
|
3585
3838
|
created.push(name);
|
|
3586
3839
|
} catch (error) {
|
|
@@ -3720,7 +3973,7 @@ function resolveViewDir(repositoryRoot, viewPath) {
|
|
|
3720
3973
|
return realpathSync(abs);
|
|
3721
3974
|
} catch {
|
|
3722
3975
|
try {
|
|
3723
|
-
return
|
|
3976
|
+
return join7(realpathSync(dirname2(abs)), basename4(abs));
|
|
3724
3977
|
} catch {
|
|
3725
3978
|
return abs;
|
|
3726
3979
|
}
|
|
@@ -3738,7 +3991,7 @@ function gatherViewRepo(repositoryRoot, viewDir, entry) {
|
|
|
3738
3991
|
return { path: entry.path, reachable: false };
|
|
3739
3992
|
}
|
|
3740
3993
|
const linkName = basename4(repoReal);
|
|
3741
|
-
const { state, actualTarget } = inspectSymlink(
|
|
3994
|
+
const { state, actualTarget } = inspectSymlink(join7(viewDir, linkName), expectedTarget);
|
|
3742
3995
|
return {
|
|
3743
3996
|
path: entry.path,
|
|
3744
3997
|
reachable: true,
|
|
@@ -3752,9 +4005,9 @@ function applyViewPlan(viewDir, toCreate) {
|
|
|
3752
4005
|
const created = [];
|
|
3753
4006
|
const failed = [];
|
|
3754
4007
|
for (const { name, target } of toCreate) {
|
|
3755
|
-
const filePath =
|
|
4008
|
+
const filePath = join7(viewDir, name);
|
|
3756
4009
|
try {
|
|
3757
|
-
mkdirSync(
|
|
4010
|
+
mkdirSync(dirname2(filePath), { recursive: true });
|
|
3758
4011
|
symlinkSync(target, filePath);
|
|
3759
4012
|
created.push(name);
|
|
3760
4013
|
} catch (error) {
|
|
@@ -3767,7 +4020,7 @@ var TOP_LEVEL_INSTRUCTION_FILES_LOWER = new Set(
|
|
|
3767
4020
|
INSTRUCTION_FILES.filter((f) => !f.includes("/")).map((f) => f.toLowerCase())
|
|
3768
4021
|
);
|
|
3769
4022
|
function classifyViewLink(viewDir, name, rosterRealpaths) {
|
|
3770
|
-
const filePath =
|
|
4023
|
+
const filePath = join7(viewDir, name);
|
|
3771
4024
|
let isLink;
|
|
3772
4025
|
try {
|
|
3773
4026
|
isLink = lstatSync(filePath).isSymbolicLink();
|
|
@@ -3796,7 +4049,7 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
|
|
|
3796
4049
|
if (!isDir) {
|
|
3797
4050
|
return { target, kind: existsSync(resolved) ? "non-repo" : "broken" };
|
|
3798
4051
|
}
|
|
3799
|
-
return { target, kind: existsSync(
|
|
4052
|
+
return { target, kind: existsSync(join7(resolved, ".git")) ? "repo" : "non-repo" };
|
|
3800
4053
|
}
|
|
3801
4054
|
function gatherExistingViewLinks(viewDir, rosterRealpaths) {
|
|
3802
4055
|
let names;
|
|
@@ -3821,7 +4074,7 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
|
|
|
3821
4074
|
const pruned = [];
|
|
3822
4075
|
const failed = [];
|
|
3823
4076
|
for (const { name } of toPrune) {
|
|
3824
|
-
const filePath =
|
|
4077
|
+
const filePath = join7(viewDir, name);
|
|
3825
4078
|
const c = classifyViewLink(viewDir, name, rosterRealpaths);
|
|
3826
4079
|
if (c === null || c.kind !== "repo") {
|
|
3827
4080
|
failed.push({
|
|
@@ -4032,10 +4285,10 @@ async function runProjectPreset(options, ctx = {}) {
|
|
|
4032
4285
|
}
|
|
4033
4286
|
}
|
|
4034
4287
|
function canonicalFileFor(anchorReal, canonicalName) {
|
|
4035
|
-
return
|
|
4288
|
+
return join7(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
4036
4289
|
}
|
|
4037
4290
|
function canonicalLabelFor(canonicalName) {
|
|
4038
|
-
return
|
|
4291
|
+
return join7("agents", canonicalName, CANONICAL_FILE);
|
|
4039
4292
|
}
|
|
4040
4293
|
async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
|
|
4041
4294
|
const declared = {
|
|
@@ -4053,7 +4306,7 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
|
|
|
4053
4306
|
if (real === anchorReal) {
|
|
4054
4307
|
return { ...declared, isAnchor: true, reachable: true, canonicalPresent: false };
|
|
4055
4308
|
}
|
|
4056
|
-
if (!existsSync(
|
|
4309
|
+
if (!existsSync(join7(real, ".git"))) {
|
|
4057
4310
|
return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
|
|
4058
4311
|
}
|
|
4059
4312
|
const canonicalName = basename4(real);
|
|
@@ -4101,7 +4354,7 @@ async function applyPresetPlan(anchorReal, plan) {
|
|
|
4101
4354
|
isLink = false;
|
|
4102
4355
|
}
|
|
4103
4356
|
if (isLink) throw new Error(`Canonical is a symlink in ${label}`);
|
|
4104
|
-
if (plan.action === "create") mkdirSync(
|
|
4357
|
+
if (plan.action === "create") mkdirSync(dirname2(file), { recursive: true });
|
|
4105
4358
|
const existing = await readMarkdownFile4(file);
|
|
4106
4359
|
await writeMarkdownFile5(file, renderWithMarkers4(existing, plan.desiredBlock, label));
|
|
4107
4360
|
}
|
|
@@ -4284,24 +4537,24 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
|
|
|
4284
4537
|
const instructionFiles = [];
|
|
4285
4538
|
for (const name of INSTRUCTION_FILES) {
|
|
4286
4539
|
try {
|
|
4287
|
-
lstatSync(
|
|
4540
|
+
lstatSync(join7(real, name));
|
|
4288
4541
|
instructionFiles.push(name);
|
|
4289
4542
|
} catch {
|
|
4290
4543
|
}
|
|
4291
4544
|
}
|
|
4292
4545
|
let ignored;
|
|
4293
4546
|
try {
|
|
4294
|
-
ignored = new Set(readGitignoreLines(
|
|
4547
|
+
ignored = new Set(readGitignoreLines(join7(real, ".gitignore")).map((l) => l.trim()));
|
|
4295
4548
|
} catch {
|
|
4296
4549
|
ignored = /* @__PURE__ */ new Set();
|
|
4297
4550
|
}
|
|
4298
4551
|
const gitignorePatterns = INSTRUCTION_FILES.filter((p) => ignored.has(p) || ignored.has(`/${p}`));
|
|
4299
|
-
const canonical2 = existsSync(
|
|
4552
|
+
const canonical2 = existsSync(join7(anchorReal, "agents", canonicalName, CANONICAL_FILE));
|
|
4300
4553
|
let viewLink = false;
|
|
4301
4554
|
const viewPath = manifest.workspace.view;
|
|
4302
4555
|
if (viewPath !== void 0) {
|
|
4303
4556
|
try {
|
|
4304
|
-
lstatSync(
|
|
4557
|
+
lstatSync(join7(resolveViewDir(repositoryRoot, viewPath), canonicalName));
|
|
4305
4558
|
viewLink = true;
|
|
4306
4559
|
} catch {
|
|
4307
4560
|
}
|
|
@@ -4463,12 +4716,12 @@ function gatherRenameWiring(repositoryRoot, manifest, oldBasename) {
|
|
|
4463
4716
|
} catch {
|
|
4464
4717
|
return { canonicalDirOld: false, viewLinkOld: false };
|
|
4465
4718
|
}
|
|
4466
|
-
const canonicalDirOld = existsSync(
|
|
4719
|
+
const canonicalDirOld = existsSync(join7(anchorReal, "agents", oldBasename));
|
|
4467
4720
|
let viewLinkOld = false;
|
|
4468
4721
|
const viewPath = manifest.workspace.view;
|
|
4469
4722
|
if (viewPath !== void 0) {
|
|
4470
4723
|
try {
|
|
4471
|
-
lstatSync(
|
|
4724
|
+
lstatSync(join7(resolveViewDir(repositoryRoot, viewPath), oldBasename));
|
|
4472
4725
|
viewLinkOld = true;
|
|
4473
4726
|
} catch {
|
|
4474
4727
|
}
|
|
@@ -4617,7 +4870,7 @@ import {
|
|
|
4617
4870
|
// src/lib/durable-write.ts
|
|
4618
4871
|
import { randomUUID } from "crypto";
|
|
4619
4872
|
import { lstat, open, rename, stat as stat3, unlink as unlink2 } from "fs/promises";
|
|
4620
|
-
import { basename as basename5, dirname as
|
|
4873
|
+
import { basename as basename5, dirname as dirname3, join as join8 } from "path";
|
|
4621
4874
|
async function assertNotSymlink(targetPath) {
|
|
4622
4875
|
try {
|
|
4623
4876
|
const st = await lstat(targetPath);
|
|
@@ -4632,8 +4885,8 @@ async function assertNotSymlink(targetPath) {
|
|
|
4632
4885
|
}
|
|
4633
4886
|
}
|
|
4634
4887
|
async function writeFileDurable(targetPath, content) {
|
|
4635
|
-
const dir =
|
|
4636
|
-
const tmpPath =
|
|
4888
|
+
const dir = dirname3(targetPath);
|
|
4889
|
+
const tmpPath = join8(dir, `.${basename5(targetPath)}.tmp.${randomUUID()}`);
|
|
4637
4890
|
let mode = 420;
|
|
4638
4891
|
try {
|
|
4639
4892
|
mode = (await stat3(targetPath)).mode & 511;
|
|
@@ -4669,15 +4922,15 @@ async function writeFileDurable(targetPath, content) {
|
|
|
4669
4922
|
|
|
4670
4923
|
// src/lib/protocols-config.ts
|
|
4671
4924
|
import { homedir as homedir6 } from "os";
|
|
4672
|
-
import { isAbsolute as isAbsolute4, join as
|
|
4925
|
+
import { isAbsolute as isAbsolute4, join as join9, resolve as resolve8 } from "path";
|
|
4673
4926
|
import { readYamlFile as readYamlFile5 } from "@basou/core";
|
|
4674
|
-
var DEFAULT_PROTOCOLS_CONFIG_PATH =
|
|
4675
|
-
var DEFAULT_TARGET_PATH =
|
|
4927
|
+
var DEFAULT_PROTOCOLS_CONFIG_PATH = join9(homedir6(), ".basou", "protocols.yaml");
|
|
4928
|
+
var DEFAULT_TARGET_PATH = join9(homedir6(), ".claude", "CLAUDE.md");
|
|
4676
4929
|
var ALLOWED_TOP_KEYS = /* @__PURE__ */ new Set(["version", "protocols"]);
|
|
4677
4930
|
var ALLOWED_ENTRY_KEYS = /* @__PURE__ */ new Set(["source", "title"]);
|
|
4678
4931
|
function expandTilde3(p) {
|
|
4679
4932
|
if (p === "~") return homedir6();
|
|
4680
|
-
if (p.startsWith("~/")) return
|
|
4933
|
+
if (p.startsWith("~/")) return join9(homedir6(), p.slice(2));
|
|
4681
4934
|
return p;
|
|
4682
4935
|
}
|
|
4683
4936
|
function isRecord3(value) {
|
|
@@ -4930,15 +5183,15 @@ import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
|
|
|
4930
5183
|
// src/commands/refresh-watch.ts
|
|
4931
5184
|
import { readdir as readdir2, stat as stat4 } from "fs/promises";
|
|
4932
5185
|
import { homedir as homedir7 } from "os";
|
|
4933
|
-
import { join as
|
|
5186
|
+
import { join as join10 } from "path";
|
|
4934
5187
|
import { findErrorCode as findErrorCode8 } from "@basou/core";
|
|
4935
5188
|
var DEFAULT_WATCH_INTERVAL_SEC = 30;
|
|
4936
5189
|
var MIN_WATCH_INTERVAL_SEC = 5;
|
|
4937
5190
|
var MAX_WATCH_INTERVAL_SEC = 86400;
|
|
4938
5191
|
function watchedRoots(ctx) {
|
|
4939
5192
|
return [
|
|
4940
|
-
ctx.codexSessionsDir ??
|
|
4941
|
-
ctx.claudeProjectsDir ??
|
|
5193
|
+
ctx.codexSessionsDir ?? join10(homedir7(), ".codex", "sessions"),
|
|
5194
|
+
ctx.claudeProjectsDir ?? join10(homedir7(), ".claude", "projects")
|
|
4942
5195
|
];
|
|
4943
5196
|
}
|
|
4944
5197
|
async function scanSourceLogs(roots) {
|
|
@@ -4952,7 +5205,7 @@ async function scanSourceLogs(roots) {
|
|
|
4952
5205
|
throw new Error("Failed to read a source log directory", { cause: error });
|
|
4953
5206
|
}
|
|
4954
5207
|
for (const entry of entries) {
|
|
4955
|
-
const full =
|
|
5208
|
+
const full = join10(dir, entry.name);
|
|
4956
5209
|
if (entry.isDirectory()) {
|
|
4957
5210
|
await walk(full);
|
|
4958
5211
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
@@ -5474,7 +5727,7 @@ function renderReviewGaps(summary) {
|
|
|
5474
5727
|
// src/commands/run.ts
|
|
5475
5728
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
5476
5729
|
import { homedir as homedir8 } from "os";
|
|
5477
|
-
import { join as
|
|
5730
|
+
import { join as join11 } from "path";
|
|
5478
5731
|
import {
|
|
5479
5732
|
acquireLock as acquireLock5,
|
|
5480
5733
|
assertBasouRootSafe as assertBasouRootSafe11,
|
|
@@ -5527,13 +5780,13 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
5527
5780
|
await assertBasouRootSafe11(paths.root);
|
|
5528
5781
|
const manifest = await readManifest7(paths);
|
|
5529
5782
|
const sessionId = prefixedUlid4("ses");
|
|
5530
|
-
const sessionDir =
|
|
5783
|
+
const sessionDir = join11(paths.sessions, sessionId);
|
|
5531
5784
|
await mkdir2(sessionDir, { recursive: true });
|
|
5532
5785
|
const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
|
|
5533
5786
|
await coreAppendChainedEvent2(paths, sessionId, event);
|
|
5534
5787
|
});
|
|
5535
5788
|
const startedAt = now().toISOString();
|
|
5536
|
-
const sessionYamlPath =
|
|
5789
|
+
const sessionYamlPath = join11(sessionDir, "session.yaml");
|
|
5537
5790
|
const session = buildInitialSession2({
|
|
5538
5791
|
id: sessionId,
|
|
5539
5792
|
command,
|
|
@@ -5876,7 +6129,7 @@ async function resolveRepositoryRootForRun(cwd) {
|
|
|
5876
6129
|
|
|
5877
6130
|
// src/commands/session.ts
|
|
5878
6131
|
import { readFile as readFile4 } from "fs/promises";
|
|
5879
|
-
import { basename as basename6, isAbsolute as isAbsolute6, join as
|
|
6132
|
+
import { basename as basename6, isAbsolute as isAbsolute6, join as join12, relative as relative3 } from "path";
|
|
5880
6133
|
import {
|
|
5881
6134
|
acquireLock as acquireLock6,
|
|
5882
6135
|
appendEventToExistingSession as appendEventToExistingSession3,
|
|
@@ -5885,7 +6138,7 @@ import {
|
|
|
5885
6138
|
enumerateSessionDirs as enumerateSessionDirs2,
|
|
5886
6139
|
findErrorCode as findErrorCode11,
|
|
5887
6140
|
importSessionFromJson as importSessionFromJson2,
|
|
5888
|
-
loadSessionEntries,
|
|
6141
|
+
loadSessionEntries as loadSessionEntries2,
|
|
5889
6142
|
readAllEvents,
|
|
5890
6143
|
readManifest as readManifest8,
|
|
5891
6144
|
readYamlFile as readYamlFile7,
|
|
@@ -5949,7 +6202,7 @@ async function doRunSessionList(options, ctx) {
|
|
|
5949
6202
|
const paths = basouPaths15(repositoryRoot);
|
|
5950
6203
|
await assertWorkspaceInitialized10(paths.root);
|
|
5951
6204
|
const now = /* @__PURE__ */ new Date();
|
|
5952
|
-
const records = (await
|
|
6205
|
+
const records = (await loadSessionEntries2(paths, {
|
|
5953
6206
|
now,
|
|
5954
6207
|
onWarning: (w, sid) => printReplayWarning(w, sid),
|
|
5955
6208
|
onSkip: (sid, reason) => printSessionListSkip(sid, reason)
|
|
@@ -6001,8 +6254,8 @@ async function doRunSessionShow(idInput, options, ctx) {
|
|
|
6001
6254
|
const paths = basouPaths15(repositoryRoot);
|
|
6002
6255
|
await assertWorkspaceInitialized10(paths.root);
|
|
6003
6256
|
const sessionId = await resolveSessionId3(paths, idInput);
|
|
6004
|
-
const sessionDir =
|
|
6005
|
-
const sessionYamlPath =
|
|
6257
|
+
const sessionDir = join12(paths.sessions, sessionId);
|
|
6258
|
+
const sessionYamlPath = join12(sessionDir, "session.yaml");
|
|
6006
6259
|
let session;
|
|
6007
6260
|
try {
|
|
6008
6261
|
const raw = await readYamlFile7(sessionYamlPath);
|
|
@@ -6177,6 +6430,11 @@ function eventVariantSummary(ev) {
|
|
|
6177
6430
|
return `approval=${ev.approval_id}`;
|
|
6178
6431
|
case "decision_recorded":
|
|
6179
6432
|
return ev.title;
|
|
6433
|
+
case "decision_voided": {
|
|
6434
|
+
const sup = ev.superseded_by !== void 0 ? ` superseded by ${ev.superseded_by}` : "";
|
|
6435
|
+
const reason = typeof ev.reason === "string" && ev.reason.length > 0 ? `: ${ev.reason}` : "";
|
|
6436
|
+
return `voided ${ev.decision_id}${reason}${sup}`;
|
|
6437
|
+
}
|
|
6180
6438
|
case "task_created":
|
|
6181
6439
|
return ev.title;
|
|
6182
6440
|
case "task_status_changed":
|
|
@@ -6740,7 +6998,7 @@ async function resolveRepositoryRootForStatus(cwd) {
|
|
|
6740
6998
|
|
|
6741
6999
|
// src/commands/task.ts
|
|
6742
7000
|
import { readFile as readFile5 } from "fs/promises";
|
|
6743
|
-
import { join as
|
|
7001
|
+
import { join as join13 } from "path";
|
|
6744
7002
|
import {
|
|
6745
7003
|
archiveTask,
|
|
6746
7004
|
assertBasouRootSafe as assertBasouRootSafe15,
|
|
@@ -6750,7 +7008,7 @@ import {
|
|
|
6750
7008
|
editTask,
|
|
6751
7009
|
enumerateArchivedTaskIds,
|
|
6752
7010
|
findErrorCode as findErrorCode14,
|
|
6753
|
-
loadSessionEntries as
|
|
7011
|
+
loadSessionEntries as loadSessionEntries3,
|
|
6754
7012
|
loadTaskEntries,
|
|
6755
7013
|
prefixedUlid as prefixedUlid5,
|
|
6756
7014
|
readManifest as readManifest10,
|
|
@@ -6759,7 +7017,7 @@ import {
|
|
|
6759
7017
|
reconcileAllTasks,
|
|
6760
7018
|
reconcileTask,
|
|
6761
7019
|
refreshTaskLinkedSessions,
|
|
6762
|
-
replayEvents as
|
|
7020
|
+
replayEvents as replayEvents3,
|
|
6763
7021
|
resolveRepositoryRoot as resolveRepositoryRoot12,
|
|
6764
7022
|
resolveSessionId as resolveSessionId4,
|
|
6765
7023
|
resolveTaskId as resolveTaskId2,
|
|
@@ -7063,13 +7321,13 @@ async function doRunTaskShow(idInput, options, ctx) {
|
|
|
7063
7321
|
await assertWorkspaceInitialized12(paths.root);
|
|
7064
7322
|
const taskId = await resolveTaskId2(paths, idInput, { includeArchived: true });
|
|
7065
7323
|
const { doc, archived } = await readTaskFileWithArchiveFallback(paths, taskId);
|
|
7066
|
-
const sessions = await
|
|
7324
|
+
const sessions = await loadSessionEntries3(paths, { now: /* @__PURE__ */ new Date() });
|
|
7067
7325
|
const events = [];
|
|
7068
7326
|
const linkedSessionIds = new Set(doc.task.task.linked_sessions);
|
|
7069
7327
|
for (const s of sessions) {
|
|
7070
|
-
const sessionDir =
|
|
7328
|
+
const sessionDir = join13(paths.sessions, s.sessionId);
|
|
7071
7329
|
try {
|
|
7072
|
-
for await (const ev of
|
|
7330
|
+
for await (const ev of replayEvents3(sessionDir, {
|
|
7073
7331
|
onWarning: (w) => printReplayWarning(w, s.sessionId)
|
|
7074
7332
|
})) {
|
|
7075
7333
|
if ((ev.type === "task_created" || ev.type === "task_status_changed" || ev.type === "task_reconciled" || ev.type === "task_linkage_refreshed" || ev.type === "task_deleted" || ev.type === "task_archived") && ev.task_id === taskId) {
|
|
@@ -7983,7 +8241,7 @@ import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
|
|
|
7983
8241
|
// src/lib/portfolio-safety.ts
|
|
7984
8242
|
import { execFile } from "child_process";
|
|
7985
8243
|
import { lstat as lstat2, realpath as realpath2 } from "fs/promises";
|
|
7986
|
-
import { isAbsolute as isAbsolute7, join as
|
|
8244
|
+
import { isAbsolute as isAbsolute7, join as join14, relative as relative4, resolve as resolve10 } from "path";
|
|
7987
8245
|
import { promisify } from "util";
|
|
7988
8246
|
import { readManifest as readManifest11 } from "@basou/core";
|
|
7989
8247
|
var execFileAsync = promisify(execFile);
|
|
@@ -8007,7 +8265,7 @@ function isBasouPath(p) {
|
|
|
8007
8265
|
async function inspectRepo(repoPath) {
|
|
8008
8266
|
let hasEntry = false;
|
|
8009
8267
|
try {
|
|
8010
|
-
await lstat2(
|
|
8268
|
+
await lstat2(join14(repoPath, ".basou"));
|
|
8011
8269
|
hasEntry = true;
|
|
8012
8270
|
} catch (error) {
|
|
8013
8271
|
if (errorCode(error) !== "ENOENT") {
|
|
@@ -8108,14 +8366,14 @@ function formatSafetyReport(result) {
|
|
|
8108
8366
|
|
|
8109
8367
|
// src/lib/view-server.ts
|
|
8110
8368
|
import { createServer } from "http";
|
|
8111
|
-
import { join as
|
|
8369
|
+
import { join as join15 } from "path";
|
|
8112
8370
|
import {
|
|
8113
8371
|
computeWorkStats as computeWorkStats2,
|
|
8114
8372
|
enumerateApprovals as enumerateApprovals2,
|
|
8115
8373
|
findErrorCode as findErrorCode16,
|
|
8116
8374
|
isLazyExpired as isLazyExpired2,
|
|
8117
8375
|
loadApproval as loadApproval2,
|
|
8118
|
-
loadSessionEntries as
|
|
8376
|
+
loadSessionEntries as loadSessionEntries4,
|
|
8119
8377
|
loadTaskEntries as loadTaskEntries2,
|
|
8120
8378
|
readAllEvents as readAllEvents2,
|
|
8121
8379
|
readManifest as readManifest12,
|
|
@@ -8660,6 +8918,10 @@ var VIEW_HTML = `<!doctype html>
|
|
|
8660
8918
|
}
|
|
8661
8919
|
if (ev.type === 'file_changed') return ev.path + ' [' + ev.change_type + ']';
|
|
8662
8920
|
if (ev.type === 'decision_recorded') return ev.title || '';
|
|
8921
|
+
if (ev.type === 'decision_voided') {
|
|
8922
|
+
var vs = ev.superseded_by ? ' superseded by ' + ev.superseded_by : '';
|
|
8923
|
+
return 'voided ' + ev.decision_id + (ev.reason ? ': ' + ev.reason : '') + vs;
|
|
8924
|
+
}
|
|
8663
8925
|
return '';
|
|
8664
8926
|
}
|
|
8665
8927
|
|
|
@@ -9037,7 +9299,7 @@ async function overview(ws, nowProvider) {
|
|
|
9037
9299
|
};
|
|
9038
9300
|
}
|
|
9039
9301
|
async function sessionsList(ws, nowProvider) {
|
|
9040
|
-
const entries = await
|
|
9302
|
+
const entries = await loadSessionEntries4(ws.paths, { now: nowProvider() });
|
|
9041
9303
|
const sessions = entries.map((entry) => ({
|
|
9042
9304
|
sessionId: entry.sessionId,
|
|
9043
9305
|
label: entry.session.session.label ?? null,
|
|
@@ -9063,7 +9325,7 @@ async function sessionDetail(ws, sessionId) {
|
|
|
9063
9325
|
throw error;
|
|
9064
9326
|
}
|
|
9065
9327
|
try {
|
|
9066
|
-
const events = await readAllEvents2(
|
|
9328
|
+
const events = await readAllEvents2(join15(ws.paths.sessions, sessionId));
|
|
9067
9329
|
return { session, events };
|
|
9068
9330
|
} catch {
|
|
9069
9331
|
return { session, events: [], degraded: true };
|