@basou/cli 0.17.0 → 0.19.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 +329 -87
- package/dist/index.js.map +1 -1
- package/dist/program.js +329 -87
- package/dist/program.js.map +1 -1
- package/package.json +2 -2
package/dist/index.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
|
|
@@ -834,6 +838,9 @@ function registerDecisionCommand(program2) {
|
|
|
834
838
|
"Related file path (repeatable). Path is opaque; existence is verified at render time.",
|
|
835
839
|
collectLinkedFile,
|
|
836
840
|
[]
|
|
841
|
+
).option(
|
|
842
|
+
"--track",
|
|
843
|
+
"Record as a strategic track (an unfinished direction + why). orientation/handoff keep resurfacing open tracks until you close one with 'basou decision void'."
|
|
837
844
|
).option(
|
|
838
845
|
"--session <session_id>",
|
|
839
846
|
"Attach to an existing session; otherwise an ad-hoc session is created"
|
|
@@ -845,6 +852,17 @@ function registerDecisionCommand(program2) {
|
|
|
845
852
|
).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
853
|
await runDecisionCapture(options);
|
|
847
854
|
});
|
|
855
|
+
decision.command("void").description(
|
|
856
|
+
"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."
|
|
857
|
+
).argument("<decision_id>", "The decision to void (its decision_ ULID)").option("--reason <text>", "Why the decision is voided", parseReason).option(
|
|
858
|
+
"--superseded-by <decision_id>",
|
|
859
|
+
"The decision that replaces this one (records a supersede rather than a plain void)"
|
|
860
|
+
).option(
|
|
861
|
+
"--session <session_id>",
|
|
862
|
+
"Attach to an existing session; otherwise an ad-hoc session is created"
|
|
863
|
+
).option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").action(async (decisionId, options) => {
|
|
864
|
+
await runDecisionVoid(decisionId, options);
|
|
865
|
+
});
|
|
848
866
|
}
|
|
849
867
|
var CAPTURE_HELP = `
|
|
850
868
|
Input format (a JSON array; one object per decision):
|
|
@@ -855,13 +873,22 @@ Input format (a JSON array; one object per decision):
|
|
|
855
873
|
"alternatives": ["npm workspaces", "yarn"],
|
|
856
874
|
"rejected_reason": "npm hoisting caused phantom-dependency bugs",
|
|
857
875
|
"linked_files": ["pnpm-workspace.yaml"]
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
"title": "Form-based admin editing is the next track (only 6/19 sections done)",
|
|
879
|
+
"rationale": "Raw-JSON editing is a stopgap, not the final shape; cover the rest.",
|
|
880
|
+
"kind": "track"
|
|
858
881
|
}
|
|
859
882
|
]
|
|
860
883
|
|
|
861
|
-
Only "title" is required; every other field is optional.
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
884
|
+
Only "title" is required; every other field is optional. Set "kind": "track" to
|
|
885
|
+
record a strategic, UNFINISHED direction (+ why): orientation/handoff resurface
|
|
886
|
+
open tracks every session until you close one with 'basou decision void <id>'.
|
|
887
|
+
Absent / "decision" is a point-in-time decision (surfaced only as the latest).
|
|
888
|
+
All decisions are written into one ad-hoc session timestamped now, so
|
|
889
|
+
orientation surfaces them as the latest decisions. Run from a workspace-view
|
|
890
|
+
directory and it resolves to the planning repo, like 'basou orient' /
|
|
891
|
+
'basou refresh' / 'basou note'.
|
|
865
892
|
|
|
866
893
|
Example (heredoc on stdin):
|
|
867
894
|
basou decision capture <<'JSON'
|
|
@@ -879,6 +906,28 @@ async function runDecisionRecord(options, ctx = {}) {
|
|
|
879
906
|
process.exitCode = 1;
|
|
880
907
|
}
|
|
881
908
|
}
|
|
909
|
+
async function warnLinkedFilesOutsideRoots(input) {
|
|
910
|
+
if (input.linkedFiles.length === 0) return;
|
|
911
|
+
try {
|
|
912
|
+
const manifest = await readManifest2(input.paths);
|
|
913
|
+
if ((manifest.import?.source_roots?.length ?? 0) === 0) return;
|
|
914
|
+
const scope = await classifyFilesBySourceRoot({
|
|
915
|
+
files: input.linkedFiles,
|
|
916
|
+
workingDirectory: input.cwd,
|
|
917
|
+
sourceRoots: manifest.import?.source_roots,
|
|
918
|
+
masterRoot: input.repositoryRoot,
|
|
919
|
+
extraInRoot: AGENT_INFRA_DIRS
|
|
920
|
+
});
|
|
921
|
+
if (scope.outOfRoot.length === 0) return;
|
|
922
|
+
const PATH_SAMPLE = 5;
|
|
923
|
+
const sample = scope.outOfRoot.slice(0, PATH_SAMPLE).join(", ");
|
|
924
|
+
const more = scope.outOfRoot.length > PATH_SAMPLE ? ` (... +${scope.outOfRoot.length - PATH_SAMPLE} more)` : "";
|
|
925
|
+
console.error(
|
|
926
|
+
`basou: ${scope.outOfRoot.length} linked file(s) resolve outside this project's source_roots: ${sample}${more} \u2014 this decision may belong to another project.`
|
|
927
|
+
);
|
|
928
|
+
} catch {
|
|
929
|
+
}
|
|
930
|
+
}
|
|
882
931
|
async function doRunDecisionRecord(options, ctx) {
|
|
883
932
|
const cwd = ctx.cwd ?? process.cwd();
|
|
884
933
|
const repositoryRoot = await resolveRepositoryRootForDecision(cwd);
|
|
@@ -888,6 +937,12 @@ async function doRunDecisionRecord(options, ctx) {
|
|
|
888
937
|
const occurredAt = now.toISOString();
|
|
889
938
|
const decisionId = prefixedUlid2("decision");
|
|
890
939
|
const rich = pickRichFields(options);
|
|
940
|
+
await warnLinkedFilesOutsideRoots({
|
|
941
|
+
linkedFiles: rich.linked_files ?? [],
|
|
942
|
+
cwd,
|
|
943
|
+
paths,
|
|
944
|
+
repositoryRoot
|
|
945
|
+
});
|
|
891
946
|
if (options.session !== void 0) {
|
|
892
947
|
const sessionId = await resolveSessionId(paths, options.session);
|
|
893
948
|
const sesId = sessionId;
|
|
@@ -930,7 +985,7 @@ async function doRunDecisionRecord(options, ctx) {
|
|
|
930
985
|
workingDirectory: repositoryRoot,
|
|
931
986
|
invocation: {
|
|
932
987
|
command: "basou decision record",
|
|
933
|
-
args: ["--title", options.title]
|
|
988
|
+
args: options.track === true ? ["--title", options.title, "--track"] : ["--title", options.title]
|
|
934
989
|
},
|
|
935
990
|
targetEventBuilders: [
|
|
936
991
|
(sessionId, eventId) => buildDecisionEvent({
|
|
@@ -971,6 +1026,12 @@ async function doRunDecisionCapture(options, ctx) {
|
|
|
971
1026
|
await assertWorkspaceInitialized2(paths.root);
|
|
972
1027
|
const raw = await readCaptureInput(options, ctx);
|
|
973
1028
|
const decisions = parseCaptureInput(raw);
|
|
1029
|
+
await warnLinkedFilesOutsideRoots({
|
|
1030
|
+
linkedFiles: decisions.flatMap((d) => d.linked_files ?? []),
|
|
1031
|
+
cwd,
|
|
1032
|
+
paths,
|
|
1033
|
+
repositoryRoot
|
|
1034
|
+
});
|
|
974
1035
|
if (options.dryRun === true) {
|
|
975
1036
|
printCapturePreview(options, decisions);
|
|
976
1037
|
return;
|
|
@@ -1017,6 +1078,161 @@ async function doRunDecisionCapture(options, ctx) {
|
|
|
1017
1078
|
}))
|
|
1018
1079
|
});
|
|
1019
1080
|
}
|
|
1081
|
+
async function runDecisionVoid(decisionId, options, ctx = {}) {
|
|
1082
|
+
try {
|
|
1083
|
+
await doRunDecisionVoid(decisionId, options, ctx);
|
|
1084
|
+
} catch (error) {
|
|
1085
|
+
renderCliError(error, {
|
|
1086
|
+
verbose: isVerbose(options),
|
|
1087
|
+
classifiers: [failedToFinalizeClassifier]
|
|
1088
|
+
});
|
|
1089
|
+
process.exitCode = 1;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
async function doRunDecisionVoid(decisionId, options, ctx) {
|
|
1093
|
+
if (!isDecisionId(decisionId)) {
|
|
1094
|
+
throw new Error(`Invalid decision id: ${decisionId} (expected a decision_<ULID>).`);
|
|
1095
|
+
}
|
|
1096
|
+
if (options.supersededBy !== void 0 && !isDecisionId(options.supersededBy)) {
|
|
1097
|
+
throw new Error(
|
|
1098
|
+
`Invalid --superseded-by id: ${options.supersededBy} (expected a decision_<ULID>).`
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
if (options.supersededBy === decisionId) {
|
|
1102
|
+
throw new Error("A decision cannot supersede itself.");
|
|
1103
|
+
}
|
|
1104
|
+
const cwd = ctx.cwd ?? process.cwd();
|
|
1105
|
+
const repositoryRoot = await resolveBasouRootForCommand(cwd, "decision void");
|
|
1106
|
+
const paths = basouPaths3(repositoryRoot);
|
|
1107
|
+
await assertWorkspaceInitialized2(paths.root);
|
|
1108
|
+
if (!await decisionExists(paths, decisionId)) {
|
|
1109
|
+
throw new Error(
|
|
1110
|
+
`Decision ${decisionId} not found in this workspace. Run 'basou decisions generate' or check the id.`
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
|
|
1114
|
+
const occurredAt = now.toISOString();
|
|
1115
|
+
const reason = options.reason;
|
|
1116
|
+
const supersededBy = options.supersededBy;
|
|
1117
|
+
if (options.session !== void 0) {
|
|
1118
|
+
const sessionId = await resolveSessionId(paths, options.session);
|
|
1119
|
+
const sessionLock = await acquireLock2(paths, "session", sessionId);
|
|
1120
|
+
let result;
|
|
1121
|
+
try {
|
|
1122
|
+
result = await appendEventToExistingSession({
|
|
1123
|
+
paths,
|
|
1124
|
+
sessionId,
|
|
1125
|
+
eventBuilder: (eventId) => buildDecisionVoidedEvent({
|
|
1126
|
+
eventId,
|
|
1127
|
+
sessionId,
|
|
1128
|
+
decisionId,
|
|
1129
|
+
occurredAt,
|
|
1130
|
+
reason,
|
|
1131
|
+
supersededBy
|
|
1132
|
+
})
|
|
1133
|
+
});
|
|
1134
|
+
} finally {
|
|
1135
|
+
await sessionLock.release();
|
|
1136
|
+
}
|
|
1137
|
+
printVoidResult(options, {
|
|
1138
|
+
mode: "attached",
|
|
1139
|
+
sessionId,
|
|
1140
|
+
decisionId,
|
|
1141
|
+
eventId: result.eventId,
|
|
1142
|
+
sessionStatus: result.sessionStatus,
|
|
1143
|
+
reason,
|
|
1144
|
+
supersededBy
|
|
1145
|
+
});
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
const manifest = await readManifest2(paths);
|
|
1149
|
+
const adHoc = await createAdHocSessionWithEvent({
|
|
1150
|
+
paths,
|
|
1151
|
+
manifest,
|
|
1152
|
+
label: `Ad-hoc decision void: ${decisionId}`,
|
|
1153
|
+
occurredAt,
|
|
1154
|
+
sessionSource: "human",
|
|
1155
|
+
workingDirectory: repositoryRoot,
|
|
1156
|
+
invocation: { command: "basou decision void", args: [decisionId] },
|
|
1157
|
+
targetEventBuilders: [
|
|
1158
|
+
(sessionId, eventId) => buildDecisionVoidedEvent({
|
|
1159
|
+
eventId,
|
|
1160
|
+
sessionId,
|
|
1161
|
+
decisionId,
|
|
1162
|
+
occurredAt,
|
|
1163
|
+
reason,
|
|
1164
|
+
supersededBy
|
|
1165
|
+
})
|
|
1166
|
+
]
|
|
1167
|
+
});
|
|
1168
|
+
printVoidResult(options, {
|
|
1169
|
+
mode: "ad-hoc",
|
|
1170
|
+
sessionId: adHoc.sessionId,
|
|
1171
|
+
decisionId,
|
|
1172
|
+
eventId: adHoc.targetEventIds[0],
|
|
1173
|
+
sessionStatus: "completed",
|
|
1174
|
+
reason,
|
|
1175
|
+
supersededBy
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
function isDecisionId(value) {
|
|
1179
|
+
return value.startsWith("decision_") && isValidPrefixedId(value);
|
|
1180
|
+
}
|
|
1181
|
+
async function decisionExists(paths, decisionId) {
|
|
1182
|
+
const entries = await loadSessionEntries(paths, { now: /* @__PURE__ */ new Date() });
|
|
1183
|
+
for (const entry of entries) {
|
|
1184
|
+
const sessionDir = join3(paths.sessions, entry.sessionId);
|
|
1185
|
+
try {
|
|
1186
|
+
for await (const ev of replayEvents2(sessionDir, {})) {
|
|
1187
|
+
if (ev.type === "decision_recorded" && ev.decision_id === decisionId) return true;
|
|
1188
|
+
}
|
|
1189
|
+
} catch {
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
return false;
|
|
1193
|
+
}
|
|
1194
|
+
function buildDecisionVoidedEvent(input) {
|
|
1195
|
+
return {
|
|
1196
|
+
schema_version: "0.1.0",
|
|
1197
|
+
id: input.eventId,
|
|
1198
|
+
session_id: input.sessionId,
|
|
1199
|
+
occurred_at: input.occurredAt,
|
|
1200
|
+
source: "local-cli",
|
|
1201
|
+
type: "decision_voided",
|
|
1202
|
+
decision_id: input.decisionId,
|
|
1203
|
+
...input.reason !== void 0 ? { reason: input.reason } : {},
|
|
1204
|
+
...input.supersededBy !== void 0 ? { superseded_by: input.supersededBy } : {}
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
function printVoidResult(options, result) {
|
|
1208
|
+
if (options.json === true) {
|
|
1209
|
+
console.log(
|
|
1210
|
+
JSON.stringify({
|
|
1211
|
+
event_id: result.eventId,
|
|
1212
|
+
session_id: result.sessionId,
|
|
1213
|
+
decision_id: result.decisionId,
|
|
1214
|
+
session_status: result.sessionStatus,
|
|
1215
|
+
mode: result.mode,
|
|
1216
|
+
...result.reason !== void 0 ? { reason: result.reason } : {},
|
|
1217
|
+
...result.supersededBy !== void 0 ? { superseded_by: result.supersededBy } : {}
|
|
1218
|
+
})
|
|
1219
|
+
);
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
const sid = shortSessionId(result.sessionId);
|
|
1223
|
+
const tail = result.supersededBy !== void 0 ? ` (superseded by ${result.supersededBy})` : "";
|
|
1224
|
+
if (result.mode === "ad-hoc") {
|
|
1225
|
+
console.log(`Voided ${result.decisionId} in ad-hoc session ${sid}${tail}`);
|
|
1226
|
+
} else {
|
|
1227
|
+
console.log(`Voided ${result.decisionId} in session ${sid} (${result.sessionStatus})${tail}`);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
function parseReason(raw) {
|
|
1231
|
+
if (raw.trim().length === 0) {
|
|
1232
|
+
throw new InvalidArgumentError("--reason must not be empty");
|
|
1233
|
+
}
|
|
1234
|
+
return raw;
|
|
1235
|
+
}
|
|
1020
1236
|
async function readCaptureInput(options, ctx) {
|
|
1021
1237
|
if (options.file !== void 0) {
|
|
1022
1238
|
try {
|
|
@@ -1050,7 +1266,8 @@ var CAPTURE_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
|
1050
1266
|
"rejected_reason",
|
|
1051
1267
|
"alternatives",
|
|
1052
1268
|
"linked_events",
|
|
1053
|
-
"linked_files"
|
|
1269
|
+
"linked_files",
|
|
1270
|
+
"kind"
|
|
1054
1271
|
]);
|
|
1055
1272
|
function parseCaptureInput(raw) {
|
|
1056
1273
|
if (raw.trim().length === 0) {
|
|
@@ -1079,7 +1296,7 @@ function validateCaptureItem(item, index) {
|
|
|
1079
1296
|
for (const key of Object.keys(obj)) {
|
|
1080
1297
|
if (!CAPTURE_ALLOWED_KEYS.has(key)) {
|
|
1081
1298
|
throw new Error(
|
|
1082
|
-
`decision[${index}]: unknown field '${key}'. Allowed: title, rationale, rejected_reason, alternatives, linked_events, linked_files.`
|
|
1299
|
+
`decision[${index}]: unknown field '${key}'. Allowed: title, rationale, rejected_reason, alternatives, linked_events, linked_files, kind.`
|
|
1083
1300
|
);
|
|
1084
1301
|
}
|
|
1085
1302
|
}
|
|
@@ -1087,6 +1304,12 @@ function validateCaptureItem(item, index) {
|
|
|
1087
1304
|
throw new Error(`decision[${index}].title must be a non-empty string.`);
|
|
1088
1305
|
}
|
|
1089
1306
|
const out = { title: obj.title };
|
|
1307
|
+
if (obj.kind !== void 0) {
|
|
1308
|
+
if (obj.kind !== "decision" && obj.kind !== "track") {
|
|
1309
|
+
throw new Error(`decision[${index}].kind must be "decision" or "track", got '${obj.kind}'.`);
|
|
1310
|
+
}
|
|
1311
|
+
if (obj.kind === "track") out.kind = "track";
|
|
1312
|
+
}
|
|
1090
1313
|
if (obj.rationale !== void 0) {
|
|
1091
1314
|
out.rationale = requireNonEmptyString(obj.rationale, index, "rationale");
|
|
1092
1315
|
}
|
|
@@ -1154,6 +1377,7 @@ function toRichFields(decision) {
|
|
|
1154
1377
|
if (decision.alternatives !== void 0) out.alternatives = [...decision.alternatives];
|
|
1155
1378
|
if (decision.linked_events !== void 0) out.linked_events = [...decision.linked_events];
|
|
1156
1379
|
if (decision.linked_files !== void 0) out.linked_files = [...decision.linked_files];
|
|
1380
|
+
if (decision.kind !== void 0) out.kind = decision.kind;
|
|
1157
1381
|
return out;
|
|
1158
1382
|
}
|
|
1159
1383
|
function buildCaptureLabel(count) {
|
|
@@ -1171,6 +1395,7 @@ function captureItemToPayload(item) {
|
|
|
1171
1395
|
payload.rejected_reason = item.input.rejected_reason;
|
|
1172
1396
|
if (item.input.linked_events !== void 0) payload.linked_events = item.input.linked_events;
|
|
1173
1397
|
if (item.input.linked_files !== void 0) payload.linked_files = item.input.linked_files;
|
|
1398
|
+
if (item.input.kind !== void 0) payload.kind = item.input.kind;
|
|
1174
1399
|
return payload;
|
|
1175
1400
|
}
|
|
1176
1401
|
function printCapturePreview(options, decisions) {
|
|
@@ -1182,7 +1407,7 @@ function printCapturePreview(options, decisions) {
|
|
|
1182
1407
|
`Would capture ${decisions.length} decision${decisions.length === 1 ? "" : "s"} (dry run; nothing written):`
|
|
1183
1408
|
);
|
|
1184
1409
|
for (const decision of decisions) {
|
|
1185
|
-
console.log(`- ${decision.title}`);
|
|
1410
|
+
console.log(`- ${decision.title}${decision.kind === "track" ? " [TRACK]" : ""}`);
|
|
1186
1411
|
}
|
|
1187
1412
|
}
|
|
1188
1413
|
function printCaptureResult(options, result) {
|
|
@@ -1203,7 +1428,9 @@ function printCaptureResult(options, result) {
|
|
|
1203
1428
|
`Captured ${result.items.length} decision${result.items.length === 1 ? "" : "s"} in ad-hoc session ${sid}:`
|
|
1204
1429
|
);
|
|
1205
1430
|
for (const item of result.items) {
|
|
1206
|
-
console.log(
|
|
1431
|
+
console.log(
|
|
1432
|
+
`- ${item.decisionId}: ${item.input.title}${item.input.kind === "track" ? " [TRACK]" : ""}`
|
|
1433
|
+
);
|
|
1207
1434
|
}
|
|
1208
1435
|
}
|
|
1209
1436
|
function pickRichFields(options) {
|
|
@@ -1219,6 +1446,7 @@ function pickRichFields(options) {
|
|
|
1219
1446
|
if (options.linkedFile !== void 0 && options.linkedFile.length > 0) {
|
|
1220
1447
|
out.linked_files = [...options.linkedFile];
|
|
1221
1448
|
}
|
|
1449
|
+
if (options.track === true) out.kind = "track";
|
|
1222
1450
|
return out;
|
|
1223
1451
|
}
|
|
1224
1452
|
function buildDecisionEvent(input) {
|
|
@@ -1235,7 +1463,8 @@ function buildDecisionEvent(input) {
|
|
|
1235
1463
|
...input.rich.alternatives !== void 0 ? { alternatives: input.rich.alternatives } : {},
|
|
1236
1464
|
...input.rich.rejected_reason !== void 0 ? { rejected_reason: input.rich.rejected_reason } : {},
|
|
1237
1465
|
...input.rich.linked_events !== void 0 ? { linked_events: input.rich.linked_events } : {},
|
|
1238
|
-
...input.rich.linked_files !== void 0 ? { linked_files: input.rich.linked_files } : {}
|
|
1466
|
+
...input.rich.linked_files !== void 0 ? { linked_files: input.rich.linked_files } : {},
|
|
1467
|
+
...input.rich.kind !== void 0 ? { kind: input.rich.kind } : {}
|
|
1239
1468
|
};
|
|
1240
1469
|
}
|
|
1241
1470
|
function buildAdHocLabel(title) {
|
|
@@ -1302,15 +1531,19 @@ function printDecisionResult(options, result) {
|
|
|
1302
1531
|
}
|
|
1303
1532
|
if (result.rich.linked_events !== void 0) payload.linked_events = result.rich.linked_events;
|
|
1304
1533
|
if (result.rich.linked_files !== void 0) payload.linked_files = result.rich.linked_files;
|
|
1534
|
+
if (result.rich.kind !== void 0) payload.kind = result.rich.kind;
|
|
1305
1535
|
console.log(JSON.stringify(payload));
|
|
1306
1536
|
return;
|
|
1307
1537
|
}
|
|
1538
|
+
const trackPrefix = result.rich.kind === "track" ? "track " : "";
|
|
1308
1539
|
const rationaleSuffix = result.rich.rationale !== void 0 ? ` (rationale: ${result.rich.rationale})` : "";
|
|
1309
1540
|
if (result.mode === "ad-hoc") {
|
|
1310
|
-
console.log(
|
|
1541
|
+
console.log(
|
|
1542
|
+
`Recorded ${trackPrefix}${result.decisionId} in ad-hoc session ${sid}${rationaleSuffix}`
|
|
1543
|
+
);
|
|
1311
1544
|
} else {
|
|
1312
1545
|
console.log(
|
|
1313
|
-
`Recorded ${result.decisionId} in session ${sid} (${result.sessionStatus})${rationaleSuffix}`
|
|
1546
|
+
`Recorded ${trackPrefix}${result.decisionId} in session ${sid} (${result.sessionStatus})${rationaleSuffix}`
|
|
1314
1547
|
);
|
|
1315
1548
|
}
|
|
1316
1549
|
}
|
|
@@ -1408,7 +1641,7 @@ async function assertWorkspaceInitialized3(basouRoot) {
|
|
|
1408
1641
|
// src/commands/exec.ts
|
|
1409
1642
|
import { mkdir } from "fs/promises";
|
|
1410
1643
|
import { homedir as homedir3 } from "os";
|
|
1411
|
-
import { join as
|
|
1644
|
+
import { join as join4 } from "path";
|
|
1412
1645
|
import {
|
|
1413
1646
|
acquireLock as acquireLock3,
|
|
1414
1647
|
assertBasouRootSafe as assertBasouRootSafe4,
|
|
@@ -1448,13 +1681,13 @@ async function runExec(command, args, options, ctx = {}) {
|
|
|
1448
1681
|
await assertBasouRootSafe4(paths.root);
|
|
1449
1682
|
const manifest = await readManifest3(paths);
|
|
1450
1683
|
const sessionId = prefixedUlid3("ses");
|
|
1451
|
-
const sessionDir =
|
|
1684
|
+
const sessionDir = join4(paths.sessions, sessionId);
|
|
1452
1685
|
await mkdir(sessionDir, { recursive: true });
|
|
1453
1686
|
const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
|
|
1454
1687
|
await coreAppendChainedEvent(paths, sessionId, event);
|
|
1455
1688
|
});
|
|
1456
1689
|
const startedAt = now().toISOString();
|
|
1457
|
-
const sessionYamlPath =
|
|
1690
|
+
const sessionYamlPath = join4(sessionDir, "session.yaml");
|
|
1458
1691
|
const session = buildInitialSession({
|
|
1459
1692
|
id: sessionId,
|
|
1460
1693
|
command,
|
|
@@ -1804,15 +2037,15 @@ async function assertWorkspaceInitialized4(basouRoot) {
|
|
|
1804
2037
|
import { createReadStream } from "fs";
|
|
1805
2038
|
import { readdir, readFile as readFile2, rm, stat as stat2 } from "fs/promises";
|
|
1806
2039
|
import { homedir as homedir4 } from "os";
|
|
1807
|
-
import { basename as basename2, dirname, join as
|
|
2040
|
+
import { basename as basename2, dirname, join as join5, resolve as resolve4 } from "path";
|
|
1808
2041
|
import { createInterface } from "readline";
|
|
1809
2042
|
import {
|
|
1810
|
-
AGENT_INFRA_DIRS,
|
|
2043
|
+
AGENT_INFRA_DIRS as AGENT_INFRA_DIRS2,
|
|
1811
2044
|
assertBasouRootSafe as assertBasouRootSafe6,
|
|
1812
2045
|
basouPaths as basouPaths7,
|
|
1813
2046
|
CLAUDE_IMPORT_SOURCE,
|
|
1814
2047
|
CODEX_IMPORT_SOURCE,
|
|
1815
|
-
classifyFilesBySourceRoot,
|
|
2048
|
+
classifyFilesBySourceRoot as classifyFilesBySourceRoot2,
|
|
1816
2049
|
claudeTranscriptToImportPayload,
|
|
1817
2050
|
codexRolloutToImportPayload,
|
|
1818
2051
|
enumerateSessionDirs,
|
|
@@ -1890,7 +2123,7 @@ async function doRunImportClaudeCode(options, ctx) {
|
|
|
1890
2123
|
repoRoot: repositoryRoot,
|
|
1891
2124
|
cwd: ctx.cwd ?? process.cwd()
|
|
1892
2125
|
});
|
|
1893
|
-
const projectsRoot = ctx.claudeProjectsDir ??
|
|
2126
|
+
const projectsRoot = ctx.claudeProjectsDir ?? join5(homedir4(), ".claude", "projects");
|
|
1894
2127
|
const files = await selectTranscriptFiles(projectsRoot, projectPaths, options);
|
|
1895
2128
|
const projectSet = new Set(projectPaths);
|
|
1896
2129
|
const candidates = files.map((file) => {
|
|
@@ -1929,7 +2162,7 @@ async function doRunImportCodex(options, ctx) {
|
|
|
1929
2162
|
repoRoot: repositoryRoot,
|
|
1930
2163
|
cwd: ctx.cwd ?? process.cwd()
|
|
1931
2164
|
});
|
|
1932
|
-
const sessionsRoot = ctx.codexSessionsDir ??
|
|
2165
|
+
const sessionsRoot = ctx.codexSessionsDir ?? join5(homedir4(), ".codex", "sessions");
|
|
1933
2166
|
const rollouts = await discoverCodexRollouts(sessionsRoot, projectPaths, options);
|
|
1934
2167
|
const candidates = rollouts.map(({ file, externalId }) => ({
|
|
1935
2168
|
externalId,
|
|
@@ -1980,12 +2213,12 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
|
|
|
1980
2213
|
const noteCrossProject = async (externalId, payload) => {
|
|
1981
2214
|
if (!crossProjectCheck) return;
|
|
1982
2215
|
try {
|
|
1983
|
-
const scope = await
|
|
2216
|
+
const scope = await classifyFilesBySourceRoot2({
|
|
1984
2217
|
files: payload.session.related_files ?? [],
|
|
1985
2218
|
workingDirectory: payload.session.working_directory,
|
|
1986
2219
|
sourceRoots: projectPaths,
|
|
1987
2220
|
masterRoot: dirname(paths.root),
|
|
1988
|
-
extraInRoot:
|
|
2221
|
+
extraInRoot: AGENT_INFRA_DIRS2
|
|
1989
2222
|
});
|
|
1990
2223
|
if (scope.outOfRoot.length > 0) crossProject.push({ externalId, outOfRoot: scope.outOfRoot });
|
|
1991
2224
|
} catch {
|
|
@@ -2058,7 +2291,7 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
|
|
|
2058
2291
|
if (priors.length > 0 && options.force === true) {
|
|
2059
2292
|
if (options.dryRun !== true) {
|
|
2060
2293
|
for (const { sessionId } of priors) {
|
|
2061
|
-
await rm(
|
|
2294
|
+
await rm(join5(paths.sessions, sessionId), { recursive: true, force: true });
|
|
2062
2295
|
}
|
|
2063
2296
|
}
|
|
2064
2297
|
counts.replaced++;
|
|
@@ -2169,7 +2402,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
|
|
|
2169
2402
|
if (options.session !== void 0) {
|
|
2170
2403
|
const matches = [];
|
|
2171
2404
|
for (const projectPath of projectPaths) {
|
|
2172
|
-
const file =
|
|
2405
|
+
const file = join5(projectsRoot, encodeProjectDir(projectPath), `${options.session}.jsonl`);
|
|
2173
2406
|
if (await pathExists(file)) matches.push(file);
|
|
2174
2407
|
}
|
|
2175
2408
|
if (matches.length === 0) {
|
|
@@ -2180,7 +2413,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
|
|
|
2180
2413
|
const files = [];
|
|
2181
2414
|
let anyDirFound = false;
|
|
2182
2415
|
for (const projectPath of projectPaths) {
|
|
2183
|
-
const transcriptDir =
|
|
2416
|
+
const transcriptDir = join5(projectsRoot, encodeProjectDir(projectPath));
|
|
2184
2417
|
let entries;
|
|
2185
2418
|
try {
|
|
2186
2419
|
entries = await readdir(transcriptDir);
|
|
@@ -2190,7 +2423,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
|
|
|
2190
2423
|
}
|
|
2191
2424
|
anyDirFound = true;
|
|
2192
2425
|
for (const name of entries) {
|
|
2193
|
-
if (name.endsWith(".jsonl")) files.push(
|
|
2426
|
+
if (name.endsWith(".jsonl")) files.push(join5(transcriptDir, name));
|
|
2194
2427
|
}
|
|
2195
2428
|
}
|
|
2196
2429
|
if (!anyDirFound) {
|
|
@@ -2247,7 +2480,7 @@ async function findRolloutFiles(sessionsRoot) {
|
|
|
2247
2480
|
throw new Error("Failed to read Codex sessions directory", { cause: error });
|
|
2248
2481
|
}
|
|
2249
2482
|
for (const entry of entries) {
|
|
2250
|
-
const full =
|
|
2483
|
+
const full = join5(dir, entry.name);
|
|
2251
2484
|
if (entry.isDirectory()) {
|
|
2252
2485
|
await walk(full, false);
|
|
2253
2486
|
} else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) {
|
|
@@ -2671,12 +2904,12 @@ import {
|
|
|
2671
2904
|
|
|
2672
2905
|
// src/lib/hosts-config.ts
|
|
2673
2906
|
import { homedir as homedir5 } from "os";
|
|
2674
|
-
import { isAbsolute as isAbsolute2, join as
|
|
2907
|
+
import { isAbsolute as isAbsolute2, join as join6, resolve as resolve6 } from "path";
|
|
2675
2908
|
import { readYamlFile as readYamlFile4 } from "@basou/core";
|
|
2676
|
-
var DEFAULT_HOSTS_CONFIG_PATH =
|
|
2909
|
+
var DEFAULT_HOSTS_CONFIG_PATH = join6(homedir5(), ".basou", "hosts.yaml");
|
|
2677
2910
|
function expandTilde2(p) {
|
|
2678
2911
|
if (p === "~") return homedir5();
|
|
2679
|
-
if (p.startsWith("~/")) return
|
|
2912
|
+
if (p.startsWith("~/")) return join6(homedir5(), p.slice(2));
|
|
2680
2913
|
return p;
|
|
2681
2914
|
}
|
|
2682
2915
|
function isRecord2(value) {
|
|
@@ -2990,7 +3223,7 @@ import {
|
|
|
2990
3223
|
unlinkSync,
|
|
2991
3224
|
writeFileSync
|
|
2992
3225
|
} from "fs";
|
|
2993
|
-
import { basename as basename4, dirname as dirname2, isAbsolute as isAbsolute3, join as
|
|
3226
|
+
import { basename as basename4, dirname as dirname2, isAbsolute as isAbsolute3, join as join7, relative as relative2, resolve as resolve7 } from "path";
|
|
2994
3227
|
import {
|
|
2995
3228
|
basouPaths as basouPaths10,
|
|
2996
3229
|
GENERATED_END,
|
|
@@ -3249,7 +3482,7 @@ function classifySourceRoot(repositoryRoot, declaredPath) {
|
|
|
3249
3482
|
} catch {
|
|
3250
3483
|
return { path: declaredPath, kind: "unresolved" };
|
|
3251
3484
|
}
|
|
3252
|
-
return { path: declaredPath, kind: existsSync(
|
|
3485
|
+
return { path: declaredPath, kind: existsSync(join7(real, ".git")) ? "repo" : "non-repo" };
|
|
3253
3486
|
}
|
|
3254
3487
|
async function doRunProjectAdopt(options, ctx) {
|
|
3255
3488
|
const cwd = ctx.cwd ?? process.cwd();
|
|
@@ -3348,7 +3581,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
|
|
|
3348
3581
|
} catch {
|
|
3349
3582
|
return { ...base, reachable: false, instructionFiles: [] };
|
|
3350
3583
|
}
|
|
3351
|
-
if (!existsSync(
|
|
3584
|
+
if (!existsSync(join7(real, ".git"))) {
|
|
3352
3585
|
return { ...base, reachable: false, instructionFiles: [] };
|
|
3353
3586
|
}
|
|
3354
3587
|
try {
|
|
@@ -3356,7 +3589,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
|
|
|
3356
3589
|
for (const name of INSTRUCTION_FILES) {
|
|
3357
3590
|
let present = true;
|
|
3358
3591
|
try {
|
|
3359
|
-
lstatSync(
|
|
3592
|
+
lstatSync(join7(real, name));
|
|
3360
3593
|
} catch {
|
|
3361
3594
|
present = false;
|
|
3362
3595
|
}
|
|
@@ -3453,10 +3686,10 @@ function gatherRepoGitignore(repositoryRoot, entry) {
|
|
|
3453
3686
|
} catch {
|
|
3454
3687
|
return { ...base, reachable: false, currentLines: [] };
|
|
3455
3688
|
}
|
|
3456
|
-
if (!existsSync(
|
|
3689
|
+
if (!existsSync(join7(real, ".git"))) {
|
|
3457
3690
|
return { ...base, reachable: false, currentLines: [] };
|
|
3458
3691
|
}
|
|
3459
|
-
return { ...base, reachable: true, currentLines: readGitignoreLines(
|
|
3692
|
+
return { ...base, reachable: true, currentLines: readGitignoreLines(join7(real, ".gitignore")) };
|
|
3460
3693
|
}
|
|
3461
3694
|
function hasErrorCode(error) {
|
|
3462
3695
|
return error instanceof Error && typeof error.code === "string";
|
|
@@ -3470,7 +3703,7 @@ function readGitignoreLines(file) {
|
|
|
3470
3703
|
}
|
|
3471
3704
|
}
|
|
3472
3705
|
function applyGitignorePlan(repositoryRoot, plan) {
|
|
3473
|
-
const file =
|
|
3706
|
+
const file = join7(realpathSync(resolve7(repositoryRoot, plan.path)), ".gitignore");
|
|
3474
3707
|
let existing = "";
|
|
3475
3708
|
try {
|
|
3476
3709
|
existing = readFileSync(file, "utf8");
|
|
@@ -3589,16 +3822,16 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
|
|
|
3589
3822
|
if (real === anchorReal) {
|
|
3590
3823
|
return { ...base, isAnchor: true, reachable: true, canonicalPresent: false, files: [] };
|
|
3591
3824
|
}
|
|
3592
|
-
if (!existsSync(
|
|
3825
|
+
if (!existsSync(join7(real, ".git"))) {
|
|
3593
3826
|
return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
|
|
3594
3827
|
}
|
|
3595
|
-
const canonicalFile =
|
|
3828
|
+
const canonicalFile = join7(anchorReal, "agents", basename4(real), CANONICAL_FILE);
|
|
3596
3829
|
if (!existsSync(canonicalFile)) {
|
|
3597
3830
|
return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
|
|
3598
3831
|
}
|
|
3599
3832
|
const files = expectedSymlinkTargets(real, canonicalFile).map(
|
|
3600
3833
|
(spec) => {
|
|
3601
|
-
const { state, actualTarget } = inspectSymlink(
|
|
3834
|
+
const { state, actualTarget } = inspectSymlink(join7(real, spec.name), spec.target);
|
|
3602
3835
|
return {
|
|
3603
3836
|
name: spec.name,
|
|
3604
3837
|
expectedTarget: spec.target,
|
|
@@ -3627,7 +3860,7 @@ function applySymlinkPlan(repositoryRoot, plan) {
|
|
|
3627
3860
|
const created = [];
|
|
3628
3861
|
const failed = [];
|
|
3629
3862
|
for (const { name, target } of plan.toCreate) {
|
|
3630
|
-
const filePath =
|
|
3863
|
+
const filePath = join7(real, name);
|
|
3631
3864
|
try {
|
|
3632
3865
|
mkdirSync(dirname2(filePath), { recursive: true });
|
|
3633
3866
|
symlinkSync(target, filePath);
|
|
@@ -3769,7 +4002,7 @@ function resolveViewDir(repositoryRoot, viewPath) {
|
|
|
3769
4002
|
return realpathSync(abs);
|
|
3770
4003
|
} catch {
|
|
3771
4004
|
try {
|
|
3772
|
-
return
|
|
4005
|
+
return join7(realpathSync(dirname2(abs)), basename4(abs));
|
|
3773
4006
|
} catch {
|
|
3774
4007
|
return abs;
|
|
3775
4008
|
}
|
|
@@ -3787,7 +4020,7 @@ function gatherViewRepo(repositoryRoot, viewDir, entry) {
|
|
|
3787
4020
|
return { path: entry.path, reachable: false };
|
|
3788
4021
|
}
|
|
3789
4022
|
const linkName = basename4(repoReal);
|
|
3790
|
-
const { state, actualTarget } = inspectSymlink(
|
|
4023
|
+
const { state, actualTarget } = inspectSymlink(join7(viewDir, linkName), expectedTarget);
|
|
3791
4024
|
return {
|
|
3792
4025
|
path: entry.path,
|
|
3793
4026
|
reachable: true,
|
|
@@ -3801,7 +4034,7 @@ function applyViewPlan(viewDir, toCreate) {
|
|
|
3801
4034
|
const created = [];
|
|
3802
4035
|
const failed = [];
|
|
3803
4036
|
for (const { name, target } of toCreate) {
|
|
3804
|
-
const filePath =
|
|
4037
|
+
const filePath = join7(viewDir, name);
|
|
3805
4038
|
try {
|
|
3806
4039
|
mkdirSync(dirname2(filePath), { recursive: true });
|
|
3807
4040
|
symlinkSync(target, filePath);
|
|
@@ -3816,7 +4049,7 @@ var TOP_LEVEL_INSTRUCTION_FILES_LOWER = new Set(
|
|
|
3816
4049
|
INSTRUCTION_FILES.filter((f) => !f.includes("/")).map((f) => f.toLowerCase())
|
|
3817
4050
|
);
|
|
3818
4051
|
function classifyViewLink(viewDir, name, rosterRealpaths) {
|
|
3819
|
-
const filePath =
|
|
4052
|
+
const filePath = join7(viewDir, name);
|
|
3820
4053
|
let isLink;
|
|
3821
4054
|
try {
|
|
3822
4055
|
isLink = lstatSync(filePath).isSymbolicLink();
|
|
@@ -3845,7 +4078,7 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
|
|
|
3845
4078
|
if (!isDir) {
|
|
3846
4079
|
return { target, kind: existsSync(resolved) ? "non-repo" : "broken" };
|
|
3847
4080
|
}
|
|
3848
|
-
return { target, kind: existsSync(
|
|
4081
|
+
return { target, kind: existsSync(join7(resolved, ".git")) ? "repo" : "non-repo" };
|
|
3849
4082
|
}
|
|
3850
4083
|
function gatherExistingViewLinks(viewDir, rosterRealpaths) {
|
|
3851
4084
|
let names;
|
|
@@ -3870,7 +4103,7 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
|
|
|
3870
4103
|
const pruned = [];
|
|
3871
4104
|
const failed = [];
|
|
3872
4105
|
for (const { name } of toPrune) {
|
|
3873
|
-
const filePath =
|
|
4106
|
+
const filePath = join7(viewDir, name);
|
|
3874
4107
|
const c = classifyViewLink(viewDir, name, rosterRealpaths);
|
|
3875
4108
|
if (c === null || c.kind !== "repo") {
|
|
3876
4109
|
failed.push({
|
|
@@ -4081,10 +4314,10 @@ async function runProjectPreset(options, ctx = {}) {
|
|
|
4081
4314
|
}
|
|
4082
4315
|
}
|
|
4083
4316
|
function canonicalFileFor(anchorReal, canonicalName) {
|
|
4084
|
-
return
|
|
4317
|
+
return join7(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
4085
4318
|
}
|
|
4086
4319
|
function canonicalLabelFor(canonicalName) {
|
|
4087
|
-
return
|
|
4320
|
+
return join7("agents", canonicalName, CANONICAL_FILE);
|
|
4088
4321
|
}
|
|
4089
4322
|
async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
|
|
4090
4323
|
const declared = {
|
|
@@ -4102,7 +4335,7 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
|
|
|
4102
4335
|
if (real === anchorReal) {
|
|
4103
4336
|
return { ...declared, isAnchor: true, reachable: true, canonicalPresent: false };
|
|
4104
4337
|
}
|
|
4105
|
-
if (!existsSync(
|
|
4338
|
+
if (!existsSync(join7(real, ".git"))) {
|
|
4106
4339
|
return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
|
|
4107
4340
|
}
|
|
4108
4341
|
const canonicalName = basename4(real);
|
|
@@ -4333,24 +4566,24 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
|
|
|
4333
4566
|
const instructionFiles = [];
|
|
4334
4567
|
for (const name of INSTRUCTION_FILES) {
|
|
4335
4568
|
try {
|
|
4336
|
-
lstatSync(
|
|
4569
|
+
lstatSync(join7(real, name));
|
|
4337
4570
|
instructionFiles.push(name);
|
|
4338
4571
|
} catch {
|
|
4339
4572
|
}
|
|
4340
4573
|
}
|
|
4341
4574
|
let ignored;
|
|
4342
4575
|
try {
|
|
4343
|
-
ignored = new Set(readGitignoreLines(
|
|
4576
|
+
ignored = new Set(readGitignoreLines(join7(real, ".gitignore")).map((l) => l.trim()));
|
|
4344
4577
|
} catch {
|
|
4345
4578
|
ignored = /* @__PURE__ */ new Set();
|
|
4346
4579
|
}
|
|
4347
4580
|
const gitignorePatterns = INSTRUCTION_FILES.filter((p) => ignored.has(p) || ignored.has(`/${p}`));
|
|
4348
|
-
const canonical2 = existsSync(
|
|
4581
|
+
const canonical2 = existsSync(join7(anchorReal, "agents", canonicalName, CANONICAL_FILE));
|
|
4349
4582
|
let viewLink = false;
|
|
4350
4583
|
const viewPath = manifest.workspace.view;
|
|
4351
4584
|
if (viewPath !== void 0) {
|
|
4352
4585
|
try {
|
|
4353
|
-
lstatSync(
|
|
4586
|
+
lstatSync(join7(resolveViewDir(repositoryRoot, viewPath), canonicalName));
|
|
4354
4587
|
viewLink = true;
|
|
4355
4588
|
} catch {
|
|
4356
4589
|
}
|
|
@@ -4512,12 +4745,12 @@ function gatherRenameWiring(repositoryRoot, manifest, oldBasename) {
|
|
|
4512
4745
|
} catch {
|
|
4513
4746
|
return { canonicalDirOld: false, viewLinkOld: false };
|
|
4514
4747
|
}
|
|
4515
|
-
const canonicalDirOld = existsSync(
|
|
4748
|
+
const canonicalDirOld = existsSync(join7(anchorReal, "agents", oldBasename));
|
|
4516
4749
|
let viewLinkOld = false;
|
|
4517
4750
|
const viewPath = manifest.workspace.view;
|
|
4518
4751
|
if (viewPath !== void 0) {
|
|
4519
4752
|
try {
|
|
4520
|
-
lstatSync(
|
|
4753
|
+
lstatSync(join7(resolveViewDir(repositoryRoot, viewPath), oldBasename));
|
|
4521
4754
|
viewLinkOld = true;
|
|
4522
4755
|
} catch {
|
|
4523
4756
|
}
|
|
@@ -4666,7 +4899,7 @@ import {
|
|
|
4666
4899
|
// src/lib/durable-write.ts
|
|
4667
4900
|
import { randomUUID } from "crypto";
|
|
4668
4901
|
import { lstat, open, rename, stat as stat3, unlink as unlink2 } from "fs/promises";
|
|
4669
|
-
import { basename as basename5, dirname as dirname3, join as
|
|
4902
|
+
import { basename as basename5, dirname as dirname3, join as join8 } from "path";
|
|
4670
4903
|
async function assertNotSymlink(targetPath) {
|
|
4671
4904
|
try {
|
|
4672
4905
|
const st = await lstat(targetPath);
|
|
@@ -4682,7 +4915,7 @@ async function assertNotSymlink(targetPath) {
|
|
|
4682
4915
|
}
|
|
4683
4916
|
async function writeFileDurable(targetPath, content) {
|
|
4684
4917
|
const dir = dirname3(targetPath);
|
|
4685
|
-
const tmpPath =
|
|
4918
|
+
const tmpPath = join8(dir, `.${basename5(targetPath)}.tmp.${randomUUID()}`);
|
|
4686
4919
|
let mode = 420;
|
|
4687
4920
|
try {
|
|
4688
4921
|
mode = (await stat3(targetPath)).mode & 511;
|
|
@@ -4718,15 +4951,15 @@ async function writeFileDurable(targetPath, content) {
|
|
|
4718
4951
|
|
|
4719
4952
|
// src/lib/protocols-config.ts
|
|
4720
4953
|
import { homedir as homedir6 } from "os";
|
|
4721
|
-
import { isAbsolute as isAbsolute4, join as
|
|
4954
|
+
import { isAbsolute as isAbsolute4, join as join9, resolve as resolve8 } from "path";
|
|
4722
4955
|
import { readYamlFile as readYamlFile5 } from "@basou/core";
|
|
4723
|
-
var DEFAULT_PROTOCOLS_CONFIG_PATH =
|
|
4724
|
-
var DEFAULT_TARGET_PATH =
|
|
4956
|
+
var DEFAULT_PROTOCOLS_CONFIG_PATH = join9(homedir6(), ".basou", "protocols.yaml");
|
|
4957
|
+
var DEFAULT_TARGET_PATH = join9(homedir6(), ".claude", "CLAUDE.md");
|
|
4725
4958
|
var ALLOWED_TOP_KEYS = /* @__PURE__ */ new Set(["version", "protocols"]);
|
|
4726
4959
|
var ALLOWED_ENTRY_KEYS = /* @__PURE__ */ new Set(["source", "title"]);
|
|
4727
4960
|
function expandTilde3(p) {
|
|
4728
4961
|
if (p === "~") return homedir6();
|
|
4729
|
-
if (p.startsWith("~/")) return
|
|
4962
|
+
if (p.startsWith("~/")) return join9(homedir6(), p.slice(2));
|
|
4730
4963
|
return p;
|
|
4731
4964
|
}
|
|
4732
4965
|
function isRecord3(value) {
|
|
@@ -4979,15 +5212,15 @@ import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
|
|
|
4979
5212
|
// src/commands/refresh-watch.ts
|
|
4980
5213
|
import { readdir as readdir2, stat as stat4 } from "fs/promises";
|
|
4981
5214
|
import { homedir as homedir7 } from "os";
|
|
4982
|
-
import { join as
|
|
5215
|
+
import { join as join10 } from "path";
|
|
4983
5216
|
import { findErrorCode as findErrorCode8 } from "@basou/core";
|
|
4984
5217
|
var DEFAULT_WATCH_INTERVAL_SEC = 30;
|
|
4985
5218
|
var MIN_WATCH_INTERVAL_SEC = 5;
|
|
4986
5219
|
var MAX_WATCH_INTERVAL_SEC = 86400;
|
|
4987
5220
|
function watchedRoots(ctx) {
|
|
4988
5221
|
return [
|
|
4989
|
-
ctx.codexSessionsDir ??
|
|
4990
|
-
ctx.claudeProjectsDir ??
|
|
5222
|
+
ctx.codexSessionsDir ?? join10(homedir7(), ".codex", "sessions"),
|
|
5223
|
+
ctx.claudeProjectsDir ?? join10(homedir7(), ".claude", "projects")
|
|
4991
5224
|
];
|
|
4992
5225
|
}
|
|
4993
5226
|
async function scanSourceLogs(roots) {
|
|
@@ -5001,7 +5234,7 @@ async function scanSourceLogs(roots) {
|
|
|
5001
5234
|
throw new Error("Failed to read a source log directory", { cause: error });
|
|
5002
5235
|
}
|
|
5003
5236
|
for (const entry of entries) {
|
|
5004
|
-
const full =
|
|
5237
|
+
const full = join10(dir, entry.name);
|
|
5005
5238
|
if (entry.isDirectory()) {
|
|
5006
5239
|
await walk(full);
|
|
5007
5240
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
@@ -5523,7 +5756,7 @@ function renderReviewGaps(summary) {
|
|
|
5523
5756
|
// src/commands/run.ts
|
|
5524
5757
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
5525
5758
|
import { homedir as homedir8 } from "os";
|
|
5526
|
-
import { join as
|
|
5759
|
+
import { join as join11 } from "path";
|
|
5527
5760
|
import {
|
|
5528
5761
|
acquireLock as acquireLock5,
|
|
5529
5762
|
assertBasouRootSafe as assertBasouRootSafe11,
|
|
@@ -5576,13 +5809,13 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
5576
5809
|
await assertBasouRootSafe11(paths.root);
|
|
5577
5810
|
const manifest = await readManifest7(paths);
|
|
5578
5811
|
const sessionId = prefixedUlid4("ses");
|
|
5579
|
-
const sessionDir =
|
|
5812
|
+
const sessionDir = join11(paths.sessions, sessionId);
|
|
5580
5813
|
await mkdir2(sessionDir, { recursive: true });
|
|
5581
5814
|
const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
|
|
5582
5815
|
await coreAppendChainedEvent2(paths, sessionId, event);
|
|
5583
5816
|
});
|
|
5584
5817
|
const startedAt = now().toISOString();
|
|
5585
|
-
const sessionYamlPath =
|
|
5818
|
+
const sessionYamlPath = join11(sessionDir, "session.yaml");
|
|
5586
5819
|
const session = buildInitialSession2({
|
|
5587
5820
|
id: sessionId,
|
|
5588
5821
|
command,
|
|
@@ -5925,7 +6158,7 @@ async function resolveRepositoryRootForRun(cwd) {
|
|
|
5925
6158
|
|
|
5926
6159
|
// src/commands/session.ts
|
|
5927
6160
|
import { readFile as readFile4 } from "fs/promises";
|
|
5928
|
-
import { basename as basename6, isAbsolute as isAbsolute6, join as
|
|
6161
|
+
import { basename as basename6, isAbsolute as isAbsolute6, join as join12, relative as relative3 } from "path";
|
|
5929
6162
|
import {
|
|
5930
6163
|
acquireLock as acquireLock6,
|
|
5931
6164
|
appendEventToExistingSession as appendEventToExistingSession3,
|
|
@@ -5934,7 +6167,7 @@ import {
|
|
|
5934
6167
|
enumerateSessionDirs as enumerateSessionDirs2,
|
|
5935
6168
|
findErrorCode as findErrorCode11,
|
|
5936
6169
|
importSessionFromJson as importSessionFromJson2,
|
|
5937
|
-
loadSessionEntries,
|
|
6170
|
+
loadSessionEntries as loadSessionEntries2,
|
|
5938
6171
|
readAllEvents,
|
|
5939
6172
|
readManifest as readManifest8,
|
|
5940
6173
|
readYamlFile as readYamlFile7,
|
|
@@ -5998,7 +6231,7 @@ async function doRunSessionList(options, ctx) {
|
|
|
5998
6231
|
const paths = basouPaths15(repositoryRoot);
|
|
5999
6232
|
await assertWorkspaceInitialized10(paths.root);
|
|
6000
6233
|
const now = /* @__PURE__ */ new Date();
|
|
6001
|
-
const records = (await
|
|
6234
|
+
const records = (await loadSessionEntries2(paths, {
|
|
6002
6235
|
now,
|
|
6003
6236
|
onWarning: (w, sid) => printReplayWarning(w, sid),
|
|
6004
6237
|
onSkip: (sid, reason) => printSessionListSkip(sid, reason)
|
|
@@ -6050,8 +6283,8 @@ async function doRunSessionShow(idInput, options, ctx) {
|
|
|
6050
6283
|
const paths = basouPaths15(repositoryRoot);
|
|
6051
6284
|
await assertWorkspaceInitialized10(paths.root);
|
|
6052
6285
|
const sessionId = await resolveSessionId3(paths, idInput);
|
|
6053
|
-
const sessionDir =
|
|
6054
|
-
const sessionYamlPath =
|
|
6286
|
+
const sessionDir = join12(paths.sessions, sessionId);
|
|
6287
|
+
const sessionYamlPath = join12(sessionDir, "session.yaml");
|
|
6055
6288
|
let session;
|
|
6056
6289
|
try {
|
|
6057
6290
|
const raw = await readYamlFile7(sessionYamlPath);
|
|
@@ -6226,6 +6459,11 @@ function eventVariantSummary(ev) {
|
|
|
6226
6459
|
return `approval=${ev.approval_id}`;
|
|
6227
6460
|
case "decision_recorded":
|
|
6228
6461
|
return ev.title;
|
|
6462
|
+
case "decision_voided": {
|
|
6463
|
+
const sup = ev.superseded_by !== void 0 ? ` superseded by ${ev.superseded_by}` : "";
|
|
6464
|
+
const reason = typeof ev.reason === "string" && ev.reason.length > 0 ? `: ${ev.reason}` : "";
|
|
6465
|
+
return `voided ${ev.decision_id}${reason}${sup}`;
|
|
6466
|
+
}
|
|
6229
6467
|
case "task_created":
|
|
6230
6468
|
return ev.title;
|
|
6231
6469
|
case "task_status_changed":
|
|
@@ -6789,7 +7027,7 @@ async function resolveRepositoryRootForStatus(cwd) {
|
|
|
6789
7027
|
|
|
6790
7028
|
// src/commands/task.ts
|
|
6791
7029
|
import { readFile as readFile5 } from "fs/promises";
|
|
6792
|
-
import { join as
|
|
7030
|
+
import { join as join13 } from "path";
|
|
6793
7031
|
import {
|
|
6794
7032
|
archiveTask,
|
|
6795
7033
|
assertBasouRootSafe as assertBasouRootSafe15,
|
|
@@ -6799,7 +7037,7 @@ import {
|
|
|
6799
7037
|
editTask,
|
|
6800
7038
|
enumerateArchivedTaskIds,
|
|
6801
7039
|
findErrorCode as findErrorCode14,
|
|
6802
|
-
loadSessionEntries as
|
|
7040
|
+
loadSessionEntries as loadSessionEntries3,
|
|
6803
7041
|
loadTaskEntries,
|
|
6804
7042
|
prefixedUlid as prefixedUlid5,
|
|
6805
7043
|
readManifest as readManifest10,
|
|
@@ -6808,7 +7046,7 @@ import {
|
|
|
6808
7046
|
reconcileAllTasks,
|
|
6809
7047
|
reconcileTask,
|
|
6810
7048
|
refreshTaskLinkedSessions,
|
|
6811
|
-
replayEvents as
|
|
7049
|
+
replayEvents as replayEvents3,
|
|
6812
7050
|
resolveRepositoryRoot as resolveRepositoryRoot12,
|
|
6813
7051
|
resolveSessionId as resolveSessionId4,
|
|
6814
7052
|
resolveTaskId as resolveTaskId2,
|
|
@@ -7112,13 +7350,13 @@ async function doRunTaskShow(idInput, options, ctx) {
|
|
|
7112
7350
|
await assertWorkspaceInitialized12(paths.root);
|
|
7113
7351
|
const taskId = await resolveTaskId2(paths, idInput, { includeArchived: true });
|
|
7114
7352
|
const { doc, archived } = await readTaskFileWithArchiveFallback(paths, taskId);
|
|
7115
|
-
const sessions = await
|
|
7353
|
+
const sessions = await loadSessionEntries3(paths, { now: /* @__PURE__ */ new Date() });
|
|
7116
7354
|
const events = [];
|
|
7117
7355
|
const linkedSessionIds = new Set(doc.task.task.linked_sessions);
|
|
7118
7356
|
for (const s of sessions) {
|
|
7119
|
-
const sessionDir =
|
|
7357
|
+
const sessionDir = join13(paths.sessions, s.sessionId);
|
|
7120
7358
|
try {
|
|
7121
|
-
for await (const ev of
|
|
7359
|
+
for await (const ev of replayEvents3(sessionDir, {
|
|
7122
7360
|
onWarning: (w) => printReplayWarning(w, s.sessionId)
|
|
7123
7361
|
})) {
|
|
7124
7362
|
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) {
|
|
@@ -8032,7 +8270,7 @@ import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
|
|
|
8032
8270
|
// src/lib/portfolio-safety.ts
|
|
8033
8271
|
import { execFile } from "child_process";
|
|
8034
8272
|
import { lstat as lstat2, realpath as realpath2 } from "fs/promises";
|
|
8035
|
-
import { isAbsolute as isAbsolute7, join as
|
|
8273
|
+
import { isAbsolute as isAbsolute7, join as join14, relative as relative4, resolve as resolve10 } from "path";
|
|
8036
8274
|
import { promisify } from "util";
|
|
8037
8275
|
import { readManifest as readManifest11 } from "@basou/core";
|
|
8038
8276
|
var execFileAsync = promisify(execFile);
|
|
@@ -8056,7 +8294,7 @@ function isBasouPath(p) {
|
|
|
8056
8294
|
async function inspectRepo(repoPath) {
|
|
8057
8295
|
let hasEntry = false;
|
|
8058
8296
|
try {
|
|
8059
|
-
await lstat2(
|
|
8297
|
+
await lstat2(join14(repoPath, ".basou"));
|
|
8060
8298
|
hasEntry = true;
|
|
8061
8299
|
} catch (error) {
|
|
8062
8300
|
if (errorCode(error) !== "ENOENT") {
|
|
@@ -8157,14 +8395,14 @@ function formatSafetyReport(result) {
|
|
|
8157
8395
|
|
|
8158
8396
|
// src/lib/view-server.ts
|
|
8159
8397
|
import { createServer } from "http";
|
|
8160
|
-
import { join as
|
|
8398
|
+
import { join as join15 } from "path";
|
|
8161
8399
|
import {
|
|
8162
8400
|
computeWorkStats as computeWorkStats2,
|
|
8163
8401
|
enumerateApprovals as enumerateApprovals2,
|
|
8164
8402
|
findErrorCode as findErrorCode16,
|
|
8165
8403
|
isLazyExpired as isLazyExpired2,
|
|
8166
8404
|
loadApproval as loadApproval2,
|
|
8167
|
-
loadSessionEntries as
|
|
8405
|
+
loadSessionEntries as loadSessionEntries4,
|
|
8168
8406
|
loadTaskEntries as loadTaskEntries2,
|
|
8169
8407
|
readAllEvents as readAllEvents2,
|
|
8170
8408
|
readManifest as readManifest12,
|
|
@@ -8709,6 +8947,10 @@ var VIEW_HTML = `<!doctype html>
|
|
|
8709
8947
|
}
|
|
8710
8948
|
if (ev.type === 'file_changed') return ev.path + ' [' + ev.change_type + ']';
|
|
8711
8949
|
if (ev.type === 'decision_recorded') return ev.title || '';
|
|
8950
|
+
if (ev.type === 'decision_voided') {
|
|
8951
|
+
var vs = ev.superseded_by ? ' superseded by ' + ev.superseded_by : '';
|
|
8952
|
+
return 'voided ' + ev.decision_id + (ev.reason ? ': ' + ev.reason : '') + vs;
|
|
8953
|
+
}
|
|
8712
8954
|
return '';
|
|
8713
8955
|
}
|
|
8714
8956
|
|
|
@@ -9086,7 +9328,7 @@ async function overview(ws, nowProvider) {
|
|
|
9086
9328
|
};
|
|
9087
9329
|
}
|
|
9088
9330
|
async function sessionsList(ws, nowProvider) {
|
|
9089
|
-
const entries = await
|
|
9331
|
+
const entries = await loadSessionEntries4(ws.paths, { now: nowProvider() });
|
|
9090
9332
|
const sessions = entries.map((entry) => ({
|
|
9091
9333
|
sessionId: entry.sessionId,
|
|
9092
9334
|
label: entry.session.session.label ?? null,
|
|
@@ -9112,7 +9354,7 @@ async function sessionDetail(ws, sessionId) {
|
|
|
9112
9354
|
throw error;
|
|
9113
9355
|
}
|
|
9114
9356
|
try {
|
|
9115
|
-
const events = await readAllEvents2(
|
|
9357
|
+
const events = await readAllEvents2(join15(ws.paths.sessions, sessionId));
|
|
9116
9358
|
return { session, events };
|
|
9117
9359
|
} catch {
|
|
9118
9360
|
return { session, events: [], degraded: true };
|