@basou/cli 0.9.0 → 0.11.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 +318 -220
- package/dist/index.js.map +1 -1
- package/dist/program.js +318 -220
- package/dist/program.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -115,7 +115,8 @@ import { join } from "path";
|
|
|
115
115
|
import {
|
|
116
116
|
ApprovalSchema,
|
|
117
117
|
ApprovalStatusSchema,
|
|
118
|
-
|
|
118
|
+
acquireLock,
|
|
119
|
+
appendChainedEventLocked,
|
|
119
120
|
assertBasouRootSafe,
|
|
120
121
|
basouPaths,
|
|
121
122
|
enumerateApprovals,
|
|
@@ -320,55 +321,63 @@ async function doRunApprovalResolve(idInput, options, ctx, decision) {
|
|
|
320
321
|
if (approval.status !== "pending") {
|
|
321
322
|
throw new Error(`Approval status mismatch: pending YAML has status=${approval.status}`);
|
|
322
323
|
}
|
|
323
|
-
const sessionDir = join(paths.sessions, approval.session_id);
|
|
324
|
-
for await (const ev of replayEvents(sessionDir, {
|
|
325
|
-
onWarning: (w) => printReplayWarning(w, approval.session_id)
|
|
326
|
-
})) {
|
|
327
|
-
if (isApprovalEvent(ev) && ev.approval_id === approval.id && (ev.type === "approval_approved" || ev.type === "approval_rejected" || ev.type === "approval_expired")) {
|
|
328
|
-
throw new Error(`Approval already resolved (per events.jsonl): ${idInput}`);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
324
|
const now = /* @__PURE__ */ new Date();
|
|
332
|
-
if (isLazyExpired(approval, now)) {
|
|
333
|
-
throw new Error(`Approval already expired: ${idInput}`);
|
|
334
|
-
}
|
|
335
|
-
let sessionStatus = null;
|
|
336
|
-
try {
|
|
337
|
-
sessionStatus = (await readSessionYaml(paths, approval.session_id)).session.status;
|
|
338
|
-
} catch {
|
|
339
|
-
sessionStatus = null;
|
|
340
|
-
}
|
|
341
|
-
if (sessionStatus === "imported") {
|
|
342
|
-
throw new Error(`Cannot resolve an approval for an imported session: ${idInput}`);
|
|
343
|
-
}
|
|
344
325
|
const occurredAt = now.toISOString();
|
|
345
326
|
const eventId = prefixedUlid("evt");
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
327
|
+
const sessionLock = await acquireLock(paths, "session", approval.session_id);
|
|
328
|
+
try {
|
|
329
|
+
const sessionDir = join(paths.sessions, approval.session_id);
|
|
330
|
+
for await (const ev of replayEvents(sessionDir, {
|
|
331
|
+
onWarning: (w) => printReplayWarning(w, approval.session_id)
|
|
332
|
+
})) {
|
|
333
|
+
if (isApprovalEvent(ev) && ev.approval_id === approval.id && (ev.type === "approval_approved" || ev.type === "approval_rejected" || ev.type === "approval_expired")) {
|
|
334
|
+
throw new Error(`Approval already resolved (per events.jsonl): ${idInput}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (isLazyExpired(approval, now)) {
|
|
338
|
+
throw new Error(`Approval already expired: ${idInput}`);
|
|
339
|
+
}
|
|
340
|
+
let sessionStatus = null;
|
|
341
|
+
try {
|
|
342
|
+
sessionStatus = (await readSessionYaml(paths, approval.session_id)).session.status;
|
|
343
|
+
} catch {
|
|
344
|
+
sessionStatus = null;
|
|
345
|
+
}
|
|
346
|
+
const attachable = sessionStatus === "initialized" || sessionStatus === "running" || sessionStatus === "waiting_approval";
|
|
347
|
+
if (sessionStatus !== null && !attachable) {
|
|
348
|
+
throw new Error(
|
|
349
|
+
`Cannot resolve an approval for a session that is not active (status=${sessionStatus}): ${idInput}`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
if (decision === "approve") {
|
|
353
|
+
const note = options.note ?? null;
|
|
354
|
+
await appendChainedEventLocked(paths, approval.session_id, {
|
|
355
|
+
schema_version: "0.1.0",
|
|
356
|
+
id: eventId,
|
|
357
|
+
session_id: approval.session_id,
|
|
358
|
+
occurred_at: occurredAt,
|
|
359
|
+
source: "local-cli",
|
|
360
|
+
type: "approval_approved",
|
|
361
|
+
approval_id: approval.id,
|
|
362
|
+
resolver: "local-cli",
|
|
363
|
+
note
|
|
364
|
+
});
|
|
365
|
+
} else {
|
|
366
|
+
const reason = options.reason;
|
|
367
|
+
await appendChainedEventLocked(paths, approval.session_id, {
|
|
368
|
+
schema_version: "0.1.0",
|
|
369
|
+
id: eventId,
|
|
370
|
+
session_id: approval.session_id,
|
|
371
|
+
occurred_at: occurredAt,
|
|
372
|
+
source: "local-cli",
|
|
373
|
+
type: "approval_rejected",
|
|
374
|
+
approval_id: approval.id,
|
|
375
|
+
resolver: "local-cli",
|
|
376
|
+
reason
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
} finally {
|
|
380
|
+
await sessionLock.release();
|
|
372
381
|
}
|
|
373
382
|
const resolvedApproval = decision === "approve" ? {
|
|
374
383
|
...approval,
|
|
@@ -625,7 +634,7 @@ function printNoApprovals(options) {
|
|
|
625
634
|
|
|
626
635
|
// src/commands/decision.ts
|
|
627
636
|
import {
|
|
628
|
-
acquireLock,
|
|
637
|
+
acquireLock as acquireLock2,
|
|
629
638
|
appendEventToExistingSession,
|
|
630
639
|
assertBasouRootSafe as assertBasouRootSafe2,
|
|
631
640
|
basouPaths as basouPaths2,
|
|
@@ -690,7 +699,7 @@ async function doRunDecisionRecord(options, ctx) {
|
|
|
690
699
|
if (options.session !== void 0) {
|
|
691
700
|
const sessionId = await resolveSessionId(paths, options.session);
|
|
692
701
|
const sesId = sessionId;
|
|
693
|
-
const sessionLock = await
|
|
702
|
+
const sessionLock = await acquireLock2(paths, "session", sesId);
|
|
694
703
|
let result;
|
|
695
704
|
try {
|
|
696
705
|
result = await appendEventToExistingSession({
|
|
@@ -954,10 +963,12 @@ import { mkdir } from "fs/promises";
|
|
|
954
963
|
import { homedir } from "os";
|
|
955
964
|
import { join as join2 } from "path";
|
|
956
965
|
import {
|
|
966
|
+
acquireLock as acquireLock3,
|
|
957
967
|
assertBasouRootSafe as assertBasouRootSafe4,
|
|
958
968
|
basouPaths as basouPaths4,
|
|
959
969
|
ChildProcessRunner,
|
|
960
|
-
|
|
970
|
+
appendChainedEvent as coreAppendChainedEvent,
|
|
971
|
+
finalizeSessionYaml,
|
|
961
972
|
getSnapshot,
|
|
962
973
|
overwriteYamlFile,
|
|
963
974
|
parseDuration,
|
|
@@ -983,7 +994,6 @@ function registerExecCommand(program2) {
|
|
|
983
994
|
async function runExec(command, args, options, ctx = {}) {
|
|
984
995
|
const runner = ctx.runner ?? new ChildProcessRunner();
|
|
985
996
|
const now = ctx.now ?? (() => /* @__PURE__ */ new Date());
|
|
986
|
-
const appendEvent2 = ctx.appendEvent ?? coreAppendEvent;
|
|
987
997
|
const cwd = options.cwd ?? process.cwd();
|
|
988
998
|
const timeout_ms = options.timeout !== void 0 ? parseDuration(options.timeout) : void 0;
|
|
989
999
|
const repoRoot = await resolveRepositoryRootForExec(cwd);
|
|
@@ -993,6 +1003,9 @@ async function runExec(command, args, options, ctx = {}) {
|
|
|
993
1003
|
const sessionId = prefixedUlid3("ses");
|
|
994
1004
|
const sessionDir = join2(paths.sessions, sessionId);
|
|
995
1005
|
await mkdir(sessionDir, { recursive: true });
|
|
1006
|
+
const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
|
|
1007
|
+
await coreAppendChainedEvent(paths, sessionId, event);
|
|
1008
|
+
});
|
|
996
1009
|
const startedAt = now().toISOString();
|
|
997
1010
|
const sessionYamlPath = join2(sessionDir, "session.yaml");
|
|
998
1011
|
const session = buildInitialSession({
|
|
@@ -1004,7 +1017,7 @@ async function runExec(command, args, options, ctx = {}) {
|
|
|
1004
1017
|
startedAt
|
|
1005
1018
|
});
|
|
1006
1019
|
await writeYamlFile(sessionYamlPath, session);
|
|
1007
|
-
await
|
|
1020
|
+
await appendEvent(sessionDir, {
|
|
1008
1021
|
schema_version: "0.1.0",
|
|
1009
1022
|
type: "session_started",
|
|
1010
1023
|
id: prefixedUlid3("evt"),
|
|
@@ -1013,10 +1026,10 @@ async function runExec(command, args, options, ctx = {}) {
|
|
|
1013
1026
|
source: "terminal-recording"
|
|
1014
1027
|
});
|
|
1015
1028
|
if (options.snapshot !== false) {
|
|
1016
|
-
await tryAppendGitSnapshot(sessionDir, sessionId, repoRoot, now,
|
|
1029
|
+
await tryAppendGitSnapshot(sessionDir, sessionId, repoRoot, now, appendEvent);
|
|
1017
1030
|
}
|
|
1018
1031
|
const runningAt = now().toISOString();
|
|
1019
|
-
await
|
|
1032
|
+
await appendEvent(sessionDir, {
|
|
1020
1033
|
schema_version: "0.1.0",
|
|
1021
1034
|
type: "session_status_changed",
|
|
1022
1035
|
id: prefixedUlid3("evt"),
|
|
@@ -1026,9 +1039,14 @@ async function runExec(command, args, options, ctx = {}) {
|
|
|
1026
1039
|
from: "initialized",
|
|
1027
1040
|
to: "running"
|
|
1028
1041
|
});
|
|
1029
|
-
await
|
|
1030
|
-
|
|
1031
|
-
|
|
1042
|
+
const runningLock = await acquireLock3(paths, "session", sessionId);
|
|
1043
|
+
try {
|
|
1044
|
+
await mutateSessionYaml(sessionYamlPath, (s) => {
|
|
1045
|
+
s.session.status = "running";
|
|
1046
|
+
});
|
|
1047
|
+
} finally {
|
|
1048
|
+
await runningLock.release();
|
|
1049
|
+
}
|
|
1032
1050
|
const controller = new AbortController();
|
|
1033
1051
|
let signalReceived = null;
|
|
1034
1052
|
let activeChild = null;
|
|
@@ -1064,7 +1082,7 @@ async function runExec(command, args, options, ctx = {}) {
|
|
|
1064
1082
|
}
|
|
1065
1083
|
});
|
|
1066
1084
|
} catch (spawnError) {
|
|
1067
|
-
await finalizeSessionAsFailed(
|
|
1085
|
+
await finalizeSessionAsFailed(paths, sessionDir, sessionId, appendEvent, {
|
|
1068
1086
|
command,
|
|
1069
1087
|
args,
|
|
1070
1088
|
cwd,
|
|
@@ -1080,7 +1098,7 @@ async function runExec(command, args, options, ctx = {}) {
|
|
|
1080
1098
|
activeChild = null;
|
|
1081
1099
|
}
|
|
1082
1100
|
const endedAt = now().toISOString();
|
|
1083
|
-
await
|
|
1101
|
+
await appendEvent(sessionDir, {
|
|
1084
1102
|
schema_version: "0.1.0",
|
|
1085
1103
|
type: "command_executed",
|
|
1086
1104
|
id: prefixedUlid3("evt"),
|
|
@@ -1096,10 +1114,10 @@ async function runExec(command, args, options, ctx = {}) {
|
|
|
1096
1114
|
duration_ms: result.duration_ms
|
|
1097
1115
|
});
|
|
1098
1116
|
if (options.snapshot !== false) {
|
|
1099
|
-
await tryAppendGitSnapshot(sessionDir, sessionId, repoRoot, now,
|
|
1117
|
+
await tryAppendGitSnapshot(sessionDir, sessionId, repoRoot, now, appendEvent);
|
|
1100
1118
|
}
|
|
1101
1119
|
const finalStatus = decideFinalStatus(result, signalReceived);
|
|
1102
|
-
await
|
|
1120
|
+
await appendEvent(sessionDir, {
|
|
1103
1121
|
schema_version: "0.1.0",
|
|
1104
1122
|
type: "session_status_changed",
|
|
1105
1123
|
id: prefixedUlid3("evt"),
|
|
@@ -1109,7 +1127,7 @@ async function runExec(command, args, options, ctx = {}) {
|
|
|
1109
1127
|
from: "running",
|
|
1110
1128
|
to: finalStatus
|
|
1111
1129
|
});
|
|
1112
|
-
await
|
|
1130
|
+
await appendEvent(sessionDir, {
|
|
1113
1131
|
schema_version: "0.1.0",
|
|
1114
1132
|
type: "session_ended",
|
|
1115
1133
|
id: prefixedUlid3("evt"),
|
|
@@ -1118,7 +1136,7 @@ async function runExec(command, args, options, ctx = {}) {
|
|
|
1118
1136
|
source: "terminal-recording",
|
|
1119
1137
|
...result.exit_code !== null ? { exit_code: result.exit_code } : {}
|
|
1120
1138
|
});
|
|
1121
|
-
await
|
|
1139
|
+
await finalizeSessionYaml(paths, sessionId, (s) => {
|
|
1122
1140
|
s.session.status = finalStatus;
|
|
1123
1141
|
s.session.ended_at = endedAt;
|
|
1124
1142
|
s.session.invocation.exit_code = result.exit_code;
|
|
@@ -1148,7 +1166,7 @@ function signalToExitCode(sig) {
|
|
|
1148
1166
|
const num = SIGNUM_MAP[sig] ?? 1;
|
|
1149
1167
|
return 128 + num;
|
|
1150
1168
|
}
|
|
1151
|
-
async function tryAppendGitSnapshot(sessionDir, sessionId, repoRoot, now,
|
|
1169
|
+
async function tryAppendGitSnapshot(sessionDir, sessionId, repoRoot, now, appendEvent) {
|
|
1152
1170
|
let snapshot;
|
|
1153
1171
|
try {
|
|
1154
1172
|
snapshot = await getSnapshot(repoRoot);
|
|
@@ -1156,7 +1174,7 @@ async function tryAppendGitSnapshot(sessionDir, sessionId, repoRoot, now, append
|
|
|
1156
1174
|
console.warn(normalizeGitSnapshotSkipMessage(error));
|
|
1157
1175
|
return;
|
|
1158
1176
|
}
|
|
1159
|
-
await
|
|
1177
|
+
await appendEvent(sessionDir, {
|
|
1160
1178
|
schema_version: "0.1.0",
|
|
1161
1179
|
type: "git_snapshot",
|
|
1162
1180
|
id: prefixedUlid3("evt"),
|
|
@@ -1212,8 +1230,8 @@ async function mutateSessionYaml(filePath, mutator) {
|
|
|
1212
1230
|
const validated = SessionSchema.parse(parsed);
|
|
1213
1231
|
await overwriteYamlFile(filePath, validated);
|
|
1214
1232
|
}
|
|
1215
|
-
async function finalizeSessionAsFailed(
|
|
1216
|
-
await
|
|
1233
|
+
async function finalizeSessionAsFailed(paths, sessionDir, sessionId, appendEvent, ctx) {
|
|
1234
|
+
await appendEvent(sessionDir, {
|
|
1217
1235
|
schema_version: "0.1.0",
|
|
1218
1236
|
type: "command_executed",
|
|
1219
1237
|
id: prefixedUlid3("evt"),
|
|
@@ -1228,7 +1246,7 @@ async function finalizeSessionAsFailed(sessionDir, sessionYamlPath, sessionId, a
|
|
|
1228
1246
|
...ctx.signalReceived !== null ? { received_signal: ctx.signalReceived } : {},
|
|
1229
1247
|
duration_ms: 0
|
|
1230
1248
|
});
|
|
1231
|
-
await
|
|
1249
|
+
await appendEvent(sessionDir, {
|
|
1232
1250
|
schema_version: "0.1.0",
|
|
1233
1251
|
type: "session_status_changed",
|
|
1234
1252
|
id: prefixedUlid3("evt"),
|
|
@@ -1238,7 +1256,7 @@ async function finalizeSessionAsFailed(sessionDir, sessionYamlPath, sessionId, a
|
|
|
1238
1256
|
from: "running",
|
|
1239
1257
|
to: "failed"
|
|
1240
1258
|
});
|
|
1241
|
-
await
|
|
1259
|
+
await appendEvent(sessionDir, {
|
|
1242
1260
|
schema_version: "0.1.0",
|
|
1243
1261
|
type: "session_ended",
|
|
1244
1262
|
id: prefixedUlid3("evt"),
|
|
@@ -1246,7 +1264,7 @@ async function finalizeSessionAsFailed(sessionDir, sessionYamlPath, sessionId, a
|
|
|
1246
1264
|
occurred_at: ctx.occurredAt,
|
|
1247
1265
|
source: "terminal-recording"
|
|
1248
1266
|
});
|
|
1249
|
-
await
|
|
1267
|
+
await finalizeSessionYaml(paths, sessionId, (s) => {
|
|
1250
1268
|
s.session.status = "failed";
|
|
1251
1269
|
s.session.ended_at = ctx.occurredAt;
|
|
1252
1270
|
s.session.invocation.exit_code = null;
|
|
@@ -2241,19 +2259,19 @@ function parseInterval(value) {
|
|
|
2241
2259
|
return seconds;
|
|
2242
2260
|
}
|
|
2243
2261
|
function abortableSleep(ms, signal) {
|
|
2244
|
-
return new Promise((
|
|
2262
|
+
return new Promise((resolve4) => {
|
|
2245
2263
|
if (signal.aborted) {
|
|
2246
|
-
|
|
2264
|
+
resolve4();
|
|
2247
2265
|
return;
|
|
2248
2266
|
}
|
|
2249
2267
|
let timer;
|
|
2250
2268
|
const onAbort = () => {
|
|
2251
2269
|
clearTimeout(timer);
|
|
2252
|
-
|
|
2270
|
+
resolve4();
|
|
2253
2271
|
};
|
|
2254
2272
|
timer = setTimeout(() => {
|
|
2255
2273
|
signal.removeEventListener("abort", onAbort);
|
|
2256
|
-
|
|
2274
|
+
resolve4();
|
|
2257
2275
|
}, ms);
|
|
2258
2276
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
2259
2277
|
});
|
|
@@ -2393,16 +2411,96 @@ async function assertWorkspaceInitialized6(basouRoot) {
|
|
|
2393
2411
|
}
|
|
2394
2412
|
}
|
|
2395
2413
|
|
|
2414
|
+
// src/commands/report.ts
|
|
2415
|
+
import { isAbsolute, resolve as resolve3 } from "path";
|
|
2416
|
+
import {
|
|
2417
|
+
assertBasouRootSafe as assertBasouRootSafe8,
|
|
2418
|
+
basouPaths as basouPaths8,
|
|
2419
|
+
findErrorCode as findErrorCode8,
|
|
2420
|
+
renderReport,
|
|
2421
|
+
resolveRepositoryRoot as resolveRepositoryRoot9,
|
|
2422
|
+
writeMarkdownFile as writeMarkdownFile4
|
|
2423
|
+
} from "@basou/core";
|
|
2424
|
+
function registerReportCommand(program2) {
|
|
2425
|
+
const report = program2.command("report").description(
|
|
2426
|
+
"Generate a work report \u2014 a shareable export explaining the work in this workspace"
|
|
2427
|
+
);
|
|
2428
|
+
report.command("generate").description("Generate a work report from the current workspace state").option("--out <path>", "Write the markdown report to a file instead of stdout").option("--json", "Emit the structured report data as JSON to stdout").option("--title <text>", "Subject line shown in the report header").option("-v, --verbose", "Show error causes").action(async (opts) => {
|
|
2429
|
+
await runReportGenerate(opts);
|
|
2430
|
+
});
|
|
2431
|
+
}
|
|
2432
|
+
async function runReportGenerate(options, ctx = {}) {
|
|
2433
|
+
try {
|
|
2434
|
+
await doRunReportGenerate(options, ctx);
|
|
2435
|
+
} catch (error) {
|
|
2436
|
+
renderCliError(error, { verbose: isVerbose(options) });
|
|
2437
|
+
process.exitCode = 1;
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
async function doRunReportGenerate(options, ctx) {
|
|
2441
|
+
const cwd = ctx.cwd ?? process.cwd();
|
|
2442
|
+
const repositoryRoot = await resolveRepositoryRootForReport(cwd);
|
|
2443
|
+
const paths = basouPaths8(repositoryRoot);
|
|
2444
|
+
await assertWorkspaceInitialized7(paths.root);
|
|
2445
|
+
const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
2446
|
+
const result = await renderReport({
|
|
2447
|
+
paths,
|
|
2448
|
+
nowIso,
|
|
2449
|
+
...options.title !== void 0 ? { title: options.title } : {},
|
|
2450
|
+
onWarning: (w, sid) => printReplayWarning(w, sid),
|
|
2451
|
+
onSessionSkip: (sid, reason) => printSessionSkip(sid, reason),
|
|
2452
|
+
onTaskSkip: (taskId, reason) => printTaskSkip(taskId, reason)
|
|
2453
|
+
});
|
|
2454
|
+
if (options.out !== void 0) {
|
|
2455
|
+
const outPath = isAbsolute(options.out) ? options.out : resolve3(cwd, options.out);
|
|
2456
|
+
await writeMarkdownFile4(outPath, result.body);
|
|
2457
|
+
const { sessions, decisions, tasks } = result.data;
|
|
2458
|
+
console.error(
|
|
2459
|
+
`Wrote report to ${options.out} (sessions: ${sessions.total}, decisions: ${decisions.count}, tasks: ${tasks.total})`
|
|
2460
|
+
);
|
|
2461
|
+
}
|
|
2462
|
+
if (options.json === true) {
|
|
2463
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
2464
|
+
} else if (options.out === void 0) {
|
|
2465
|
+
console.log(result.body);
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
async function resolveRepositoryRootForReport(cwd) {
|
|
2469
|
+
try {
|
|
2470
|
+
return await resolveRepositoryRoot9(cwd);
|
|
2471
|
+
} catch (error) {
|
|
2472
|
+
if (error instanceof Error && error.message === "Not a git repository") {
|
|
2473
|
+
throw new Error(
|
|
2474
|
+
"Not a git repository. Run 'git init' first, then re-run 'basou report generate'.",
|
|
2475
|
+
{ cause: error }
|
|
2476
|
+
);
|
|
2477
|
+
}
|
|
2478
|
+
throw error;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
async function assertWorkspaceInitialized7(basouRoot) {
|
|
2482
|
+
try {
|
|
2483
|
+
await assertBasouRootSafe8(basouRoot);
|
|
2484
|
+
} catch (error) {
|
|
2485
|
+
if (findErrorCode8(error, "ENOENT")) {
|
|
2486
|
+
throw new Error("Workspace not initialized. Run 'basou init' first.");
|
|
2487
|
+
}
|
|
2488
|
+
throw error;
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2396
2492
|
// src/commands/run.ts
|
|
2397
2493
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
2398
2494
|
import { homedir as homedir4 } from "os";
|
|
2399
2495
|
import { join as join5 } from "path";
|
|
2400
2496
|
import {
|
|
2401
|
-
|
|
2402
|
-
|
|
2497
|
+
acquireLock as acquireLock4,
|
|
2498
|
+
assertBasouRootSafe as assertBasouRootSafe9,
|
|
2499
|
+
basouPaths as basouPaths9,
|
|
2403
2500
|
ChildProcessRunner as ChildProcessRunner2,
|
|
2404
2501
|
claudeCodeAdapterMetadata,
|
|
2405
|
-
|
|
2502
|
+
appendChainedEvent as coreAppendChainedEvent2,
|
|
2503
|
+
finalizeSessionYaml as finalizeSessionYaml2,
|
|
2406
2504
|
getDiff,
|
|
2407
2505
|
getSnapshot as getSnapshot2,
|
|
2408
2506
|
overwriteYamlFile as overwriteYamlFile2,
|
|
@@ -2410,7 +2508,7 @@ import {
|
|
|
2410
2508
|
readManifest as readManifest4,
|
|
2411
2509
|
readYamlFile as readYamlFile3,
|
|
2412
2510
|
resolveClaudeCodeCommand,
|
|
2413
|
-
resolveRepositoryRoot as
|
|
2511
|
+
resolveRepositoryRoot as resolveRepositoryRoot10,
|
|
2414
2512
|
SessionSchema as SessionSchema2,
|
|
2415
2513
|
sanitizeRelatedFiles,
|
|
2416
2514
|
sanitizeWorkingDirectory as sanitizeWorkingDirectory2,
|
|
@@ -2438,18 +2536,20 @@ function registerRunCommand(program2, ctx = {}) {
|
|
|
2438
2536
|
async function runClaudeCode(args, options, ctx = {}) {
|
|
2439
2537
|
const runner = ctx.runner ?? new ChildProcessRunner2();
|
|
2440
2538
|
const now = ctx.now ?? (() => /* @__PURE__ */ new Date());
|
|
2441
|
-
const appendEvent2 = ctx.appendEvent ?? coreAppendEvent2;
|
|
2442
2539
|
const resolveCommand = ctx.resolveCommand ?? resolveClaudeCodeCommand;
|
|
2443
2540
|
const getDiffFn = ctx.getDiff ?? getDiff;
|
|
2444
2541
|
const { command } = await resolveCommand();
|
|
2445
2542
|
const cwd = options.cwd ?? process.cwd();
|
|
2446
2543
|
const repoRoot = await resolveRepositoryRootForRun(cwd);
|
|
2447
|
-
const paths =
|
|
2448
|
-
await
|
|
2544
|
+
const paths = basouPaths9(repoRoot);
|
|
2545
|
+
await assertBasouRootSafe9(paths.root);
|
|
2449
2546
|
const manifest = await readManifest4(paths);
|
|
2450
2547
|
const sessionId = prefixedUlid4("ses");
|
|
2451
2548
|
const sessionDir = join5(paths.sessions, sessionId);
|
|
2452
2549
|
await mkdir2(sessionDir, { recursive: true });
|
|
2550
|
+
const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
|
|
2551
|
+
await coreAppendChainedEvent2(paths, sessionId, event);
|
|
2552
|
+
});
|
|
2453
2553
|
const startedAt = now().toISOString();
|
|
2454
2554
|
const sessionYamlPath = join5(sessionDir, "session.yaml");
|
|
2455
2555
|
const session = buildInitialSession2({
|
|
@@ -2461,7 +2561,7 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
2461
2561
|
startedAt
|
|
2462
2562
|
});
|
|
2463
2563
|
await writeYamlFile2(sessionYamlPath, session);
|
|
2464
|
-
await
|
|
2564
|
+
await appendEvent(sessionDir, {
|
|
2465
2565
|
schema_version: "0.1.0",
|
|
2466
2566
|
type: "session_started",
|
|
2467
2567
|
id: prefixedUlid4("evt"),
|
|
@@ -2471,10 +2571,10 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
2471
2571
|
});
|
|
2472
2572
|
let preSnapshot = null;
|
|
2473
2573
|
if (options.snapshot !== false) {
|
|
2474
|
-
preSnapshot = await tryAppendGitSnapshot2(sessionDir, sessionId, repoRoot, now,
|
|
2574
|
+
preSnapshot = await tryAppendGitSnapshot2(sessionDir, sessionId, repoRoot, now, appendEvent);
|
|
2475
2575
|
}
|
|
2476
2576
|
const runningAt = now().toISOString();
|
|
2477
|
-
await
|
|
2577
|
+
await appendEvent(sessionDir, {
|
|
2478
2578
|
schema_version: "0.1.0",
|
|
2479
2579
|
type: "session_status_changed",
|
|
2480
2580
|
id: prefixedUlid4("evt"),
|
|
@@ -2484,9 +2584,14 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
2484
2584
|
from: "initialized",
|
|
2485
2585
|
to: "running"
|
|
2486
2586
|
});
|
|
2487
|
-
await
|
|
2488
|
-
|
|
2489
|
-
|
|
2587
|
+
const runningLock = await acquireLock4(paths, "session", sessionId);
|
|
2588
|
+
try {
|
|
2589
|
+
await mutateSessionYaml2(sessionYamlPath, (s) => {
|
|
2590
|
+
s.session.status = "running";
|
|
2591
|
+
});
|
|
2592
|
+
} finally {
|
|
2593
|
+
await runningLock.release();
|
|
2594
|
+
}
|
|
2490
2595
|
const controller = new AbortController();
|
|
2491
2596
|
let signalReceived = null;
|
|
2492
2597
|
let activeChild = null;
|
|
@@ -2521,7 +2626,7 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
2521
2626
|
}
|
|
2522
2627
|
});
|
|
2523
2628
|
} catch (spawnError) {
|
|
2524
|
-
await finalizeSessionAsFailed2(
|
|
2629
|
+
await finalizeSessionAsFailed2(paths, sessionDir, sessionId, appendEvent, {
|
|
2525
2630
|
command,
|
|
2526
2631
|
args,
|
|
2527
2632
|
cwd: repoRoot,
|
|
@@ -2537,7 +2642,7 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
2537
2642
|
activeChild = null;
|
|
2538
2643
|
}
|
|
2539
2644
|
const endedAt = now().toISOString();
|
|
2540
|
-
await
|
|
2645
|
+
await appendEvent(sessionDir, {
|
|
2541
2646
|
schema_version: "0.1.0",
|
|
2542
2647
|
type: "command_executed",
|
|
2543
2648
|
id: prefixedUlid4("evt"),
|
|
@@ -2554,7 +2659,7 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
2554
2659
|
});
|
|
2555
2660
|
let postSnapshot = null;
|
|
2556
2661
|
if (options.snapshot !== false) {
|
|
2557
|
-
postSnapshot = await tryAppendGitSnapshot2(sessionDir, sessionId, repoRoot, now,
|
|
2662
|
+
postSnapshot = await tryAppendGitSnapshot2(sessionDir, sessionId, repoRoot, now, appendEvent);
|
|
2558
2663
|
}
|
|
2559
2664
|
let diff = null;
|
|
2560
2665
|
if (preSnapshot !== null && postSnapshot !== null) {
|
|
@@ -2565,7 +2670,7 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
2565
2670
|
preSnapshot.head,
|
|
2566
2671
|
postSnapshot.head,
|
|
2567
2672
|
now().toISOString(),
|
|
2568
|
-
|
|
2673
|
+
appendEvent,
|
|
2569
2674
|
getDiffFn
|
|
2570
2675
|
);
|
|
2571
2676
|
}
|
|
@@ -2575,7 +2680,7 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
2575
2680
|
homedir: homedir4()
|
|
2576
2681
|
}).sanitized;
|
|
2577
2682
|
const finalStatus = decideFinalStatus2(result, signalReceived);
|
|
2578
|
-
await
|
|
2683
|
+
await appendEvent(sessionDir, {
|
|
2579
2684
|
schema_version: "0.1.0",
|
|
2580
2685
|
type: "session_status_changed",
|
|
2581
2686
|
id: prefixedUlid4("evt"),
|
|
@@ -2585,7 +2690,7 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
2585
2690
|
from: "running",
|
|
2586
2691
|
to: finalStatus
|
|
2587
2692
|
});
|
|
2588
|
-
await
|
|
2693
|
+
await appendEvent(sessionDir, {
|
|
2589
2694
|
schema_version: "0.1.0",
|
|
2590
2695
|
type: "session_ended",
|
|
2591
2696
|
id: prefixedUlid4("evt"),
|
|
@@ -2594,7 +2699,7 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
2594
2699
|
source: claudeCodeAdapterMetadata.kind,
|
|
2595
2700
|
...result.exit_code !== null ? { exit_code: result.exit_code } : {}
|
|
2596
2701
|
});
|
|
2597
|
-
await
|
|
2702
|
+
await finalizeSessionYaml2(paths, sessionId, (s) => {
|
|
2598
2703
|
s.session.status = finalStatus;
|
|
2599
2704
|
s.session.ended_at = endedAt;
|
|
2600
2705
|
s.session.invocation.exit_code = result.exit_code;
|
|
@@ -2623,7 +2728,7 @@ function signalToExitCode2(sig) {
|
|
|
2623
2728
|
const num = SIGNUM_MAP2[sig] ?? 1;
|
|
2624
2729
|
return 128 + num;
|
|
2625
2730
|
}
|
|
2626
|
-
async function tryAppendGitSnapshot2(sessionDir, sessionId, repoRoot, now,
|
|
2731
|
+
async function tryAppendGitSnapshot2(sessionDir, sessionId, repoRoot, now, appendEvent) {
|
|
2627
2732
|
let snapshot;
|
|
2628
2733
|
try {
|
|
2629
2734
|
snapshot = await getSnapshot2(repoRoot);
|
|
@@ -2631,7 +2736,7 @@ async function tryAppendGitSnapshot2(sessionDir, sessionId, repoRoot, now, appen
|
|
|
2631
2736
|
console.warn(normalizeGitSnapshotSkipMessage2(error));
|
|
2632
2737
|
return null;
|
|
2633
2738
|
}
|
|
2634
|
-
await
|
|
2739
|
+
await appendEvent(sessionDir, {
|
|
2635
2740
|
schema_version: "0.1.0",
|
|
2636
2741
|
type: "git_snapshot",
|
|
2637
2742
|
id: prefixedUlid4("evt"),
|
|
@@ -2642,7 +2747,7 @@ async function tryAppendGitSnapshot2(sessionDir, sessionId, repoRoot, now, appen
|
|
|
2642
2747
|
});
|
|
2643
2748
|
return snapshot;
|
|
2644
2749
|
}
|
|
2645
|
-
async function tryAppendFileChangedEvents(sessionDir, sessionId, repoRoot, baseRef, headRef, occurredAt,
|
|
2750
|
+
async function tryAppendFileChangedEvents(sessionDir, sessionId, repoRoot, baseRef, headRef, occurredAt, appendEvent, getDiffFn) {
|
|
2646
2751
|
let diff;
|
|
2647
2752
|
try {
|
|
2648
2753
|
diff = await getDiffFn(repoRoot, baseRef, headRef);
|
|
@@ -2651,7 +2756,7 @@ async function tryAppendFileChangedEvents(sessionDir, sessionId, repoRoot, baseR
|
|
|
2651
2756
|
return null;
|
|
2652
2757
|
}
|
|
2653
2758
|
for (const change of diff.changed_files) {
|
|
2654
|
-
await
|
|
2759
|
+
await appendEvent(sessionDir, {
|
|
2655
2760
|
schema_version: "0.1.0",
|
|
2656
2761
|
type: "file_changed",
|
|
2657
2762
|
id: prefixedUlid4("evt"),
|
|
@@ -2734,8 +2839,8 @@ async function mutateSessionYaml2(filePath, mutator) {
|
|
|
2734
2839
|
const validated = SessionSchema2.parse(parsed);
|
|
2735
2840
|
await overwriteYamlFile2(filePath, validated);
|
|
2736
2841
|
}
|
|
2737
|
-
async function finalizeSessionAsFailed2(
|
|
2738
|
-
await
|
|
2842
|
+
async function finalizeSessionAsFailed2(paths, sessionDir, sessionId, appendEvent, ctx) {
|
|
2843
|
+
await appendEvent(sessionDir, {
|
|
2739
2844
|
schema_version: "0.1.0",
|
|
2740
2845
|
type: "command_executed",
|
|
2741
2846
|
id: prefixedUlid4("evt"),
|
|
@@ -2750,7 +2855,7 @@ async function finalizeSessionAsFailed2(sessionDir, sessionYamlPath, sessionId,
|
|
|
2750
2855
|
...ctx.signalReceived !== null ? { received_signal: ctx.signalReceived } : {},
|
|
2751
2856
|
duration_ms: 0
|
|
2752
2857
|
});
|
|
2753
|
-
await
|
|
2858
|
+
await appendEvent(sessionDir, {
|
|
2754
2859
|
schema_version: "0.1.0",
|
|
2755
2860
|
type: "session_status_changed",
|
|
2756
2861
|
id: prefixedUlid4("evt"),
|
|
@@ -2760,7 +2865,7 @@ async function finalizeSessionAsFailed2(sessionDir, sessionYamlPath, sessionId,
|
|
|
2760
2865
|
from: "running",
|
|
2761
2866
|
to: "failed"
|
|
2762
2867
|
});
|
|
2763
|
-
await
|
|
2868
|
+
await appendEvent(sessionDir, {
|
|
2764
2869
|
schema_version: "0.1.0",
|
|
2765
2870
|
type: "session_ended",
|
|
2766
2871
|
id: prefixedUlid4("evt"),
|
|
@@ -2768,7 +2873,7 @@ async function finalizeSessionAsFailed2(sessionDir, sessionYamlPath, sessionId,
|
|
|
2768
2873
|
occurred_at: ctx.occurredAt,
|
|
2769
2874
|
source: claudeCodeAdapterMetadata.kind
|
|
2770
2875
|
});
|
|
2771
|
-
await
|
|
2876
|
+
await finalizeSessionYaml2(paths, sessionId, (s) => {
|
|
2772
2877
|
s.session.status = "failed";
|
|
2773
2878
|
s.session.ended_at = ctx.occurredAt;
|
|
2774
2879
|
s.session.invocation.exit_code = null;
|
|
@@ -2776,7 +2881,7 @@ async function finalizeSessionAsFailed2(sessionDir, sessionYamlPath, sessionId,
|
|
|
2776
2881
|
}
|
|
2777
2882
|
async function resolveRepositoryRootForRun(cwd) {
|
|
2778
2883
|
try {
|
|
2779
|
-
return await
|
|
2884
|
+
return await resolveRepositoryRoot10(cwd);
|
|
2780
2885
|
} catch (error) {
|
|
2781
2886
|
if (error instanceof Error && error.message === "Not a git repository") {
|
|
2782
2887
|
throw new Error("Not a git repository. Run 'git init' first, then re-run 'basou run'.", {
|
|
@@ -2789,21 +2894,21 @@ async function resolveRepositoryRootForRun(cwd) {
|
|
|
2789
2894
|
|
|
2790
2895
|
// src/commands/session.ts
|
|
2791
2896
|
import { readFile as readFile2 } from "fs/promises";
|
|
2792
|
-
import { basename as basename3, isAbsolute, join as join6, relative as relative2 } from "path";
|
|
2897
|
+
import { basename as basename3, isAbsolute as isAbsolute2, join as join6, relative as relative2 } from "path";
|
|
2793
2898
|
import {
|
|
2794
|
-
acquireLock as
|
|
2899
|
+
acquireLock as acquireLock5,
|
|
2795
2900
|
appendEventToExistingSession as appendEventToExistingSession2,
|
|
2796
|
-
assertBasouRootSafe as
|
|
2797
|
-
basouPaths as
|
|
2901
|
+
assertBasouRootSafe as assertBasouRootSafe10,
|
|
2902
|
+
basouPaths as basouPaths10,
|
|
2798
2903
|
enumerateSessionDirs as enumerateSessionDirs2,
|
|
2799
|
-
findErrorCode as
|
|
2904
|
+
findErrorCode as findErrorCode9,
|
|
2800
2905
|
importSessionFromJson as importSessionFromJson2,
|
|
2801
2906
|
loadSessionEntries,
|
|
2802
2907
|
readAllEvents,
|
|
2803
2908
|
readManifest as readManifest5,
|
|
2804
2909
|
readYamlFile as readYamlFile4,
|
|
2805
2910
|
rechainSessionInPlace,
|
|
2806
|
-
resolveRepositoryRoot as
|
|
2911
|
+
resolveRepositoryRoot as resolveRepositoryRoot11,
|
|
2807
2912
|
resolveSessionId as resolveSessionId2,
|
|
2808
2913
|
resolveTaskId,
|
|
2809
2914
|
SessionImportPayloadSchema as SessionImportPayloadSchema2,
|
|
@@ -2814,15 +2919,7 @@ import {
|
|
|
2814
2919
|
import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
|
|
2815
2920
|
|
|
2816
2921
|
// src/lib/format-duration.ts
|
|
2817
|
-
|
|
2818
|
-
const totalSeconds = Math.round(ms / 1e3);
|
|
2819
|
-
const hours = Math.floor(totalSeconds / 3600);
|
|
2820
|
-
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
2821
|
-
const seconds = totalSeconds % 60;
|
|
2822
|
-
if (hours > 0) return `${hours}h ${String(minutes).padStart(2, "0")}m`;
|
|
2823
|
-
if (minutes > 0) return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
|
|
2824
|
-
return `${seconds}s`;
|
|
2825
|
-
}
|
|
2922
|
+
import { formatDurationMs } from "@basou/core";
|
|
2826
2923
|
|
|
2827
2924
|
// src/commands/session.ts
|
|
2828
2925
|
var SES_PREFIX3 = "ses_";
|
|
@@ -2868,8 +2965,8 @@ async function runSessionList(options, ctx = {}) {
|
|
|
2868
2965
|
async function doRunSessionList(options, ctx) {
|
|
2869
2966
|
const cwd = ctx.cwd ?? process.cwd();
|
|
2870
2967
|
const repositoryRoot = await resolveRepositoryRootForSession(cwd, "list");
|
|
2871
|
-
const paths =
|
|
2872
|
-
await
|
|
2968
|
+
const paths = basouPaths10(repositoryRoot);
|
|
2969
|
+
await assertWorkspaceInitialized8(paths.root);
|
|
2873
2970
|
const now = /* @__PURE__ */ new Date();
|
|
2874
2971
|
const records = (await loadSessionEntries(paths, {
|
|
2875
2972
|
now,
|
|
@@ -2920,8 +3017,8 @@ async function runSessionShow(idInput, options, ctx = {}) {
|
|
|
2920
3017
|
async function doRunSessionShow(idInput, options, ctx) {
|
|
2921
3018
|
const cwd = ctx.cwd ?? process.cwd();
|
|
2922
3019
|
const repositoryRoot = await resolveRepositoryRootForSession(cwd, "show");
|
|
2923
|
-
const paths =
|
|
2924
|
-
await
|
|
3020
|
+
const paths = basouPaths10(repositoryRoot);
|
|
3021
|
+
await assertWorkspaceInitialized8(paths.root);
|
|
2925
3022
|
const sessionId = await resolveSessionId2(paths, idInput);
|
|
2926
3023
|
const sessionDir = join6(paths.sessions, sessionId);
|
|
2927
3024
|
const sessionYamlPath = join6(sessionDir, "session.yaml");
|
|
@@ -2930,7 +3027,7 @@ async function doRunSessionShow(idInput, options, ctx) {
|
|
|
2930
3027
|
const raw = await readYamlFile4(sessionYamlPath);
|
|
2931
3028
|
session = SessionSchema3.parse(raw);
|
|
2932
3029
|
} catch (error) {
|
|
2933
|
-
if (
|
|
3030
|
+
if (findErrorCode9(error, "ENOENT")) {
|
|
2934
3031
|
throw new Error(`Session not found: ${idInput}`);
|
|
2935
3032
|
}
|
|
2936
3033
|
throw new Error("Failed to read session", { cause: error });
|
|
@@ -3045,7 +3142,7 @@ function formatSessionWork(session, events, now) {
|
|
|
3045
3142
|
}
|
|
3046
3143
|
function formatWorkingDir(workingDir, repositoryRoot, options) {
|
|
3047
3144
|
if (options.fullPath === true) return workingDir;
|
|
3048
|
-
if (!
|
|
3145
|
+
if (!isAbsolute2(workingDir)) {
|
|
3049
3146
|
if (workingDir === ".") return "<repository_root>";
|
|
3050
3147
|
return workingDir;
|
|
3051
3148
|
}
|
|
@@ -3165,7 +3262,7 @@ function maxLen2(values, floor) {
|
|
|
3165
3262
|
}
|
|
3166
3263
|
async function resolveRepositoryRootForSession(cwd, subcmd) {
|
|
3167
3264
|
try {
|
|
3168
|
-
return await
|
|
3265
|
+
return await resolveRepositoryRoot11(cwd);
|
|
3169
3266
|
} catch (error) {
|
|
3170
3267
|
if (error instanceof Error && error.message === "Not a git repository") {
|
|
3171
3268
|
throw new Error(
|
|
@@ -3176,11 +3273,11 @@ async function resolveRepositoryRootForSession(cwd, subcmd) {
|
|
|
3176
3273
|
throw error;
|
|
3177
3274
|
}
|
|
3178
3275
|
}
|
|
3179
|
-
async function
|
|
3276
|
+
async function assertWorkspaceInitialized8(basouRoot) {
|
|
3180
3277
|
try {
|
|
3181
|
-
await
|
|
3278
|
+
await assertBasouRootSafe10(basouRoot);
|
|
3182
3279
|
} catch (error) {
|
|
3183
|
-
if (
|
|
3280
|
+
if (findErrorCode9(error, "ENOENT")) {
|
|
3184
3281
|
throw new Error("Workspace not initialized. Run 'basou init' first.");
|
|
3185
3282
|
}
|
|
3186
3283
|
throw error;
|
|
@@ -3218,8 +3315,8 @@ async function runSessionImport(options, ctx = {}) {
|
|
|
3218
3315
|
async function doRunSessionImport(options, ctx) {
|
|
3219
3316
|
const cwd = ctx.cwd ?? process.cwd();
|
|
3220
3317
|
const repositoryRoot = await resolveRepositoryRootForSession(cwd, "import");
|
|
3221
|
-
const paths =
|
|
3222
|
-
await
|
|
3318
|
+
const paths = basouPaths10(repositoryRoot);
|
|
3319
|
+
await assertWorkspaceInitialized8(paths.root);
|
|
3223
3320
|
const manifest = await readManifest5(paths);
|
|
3224
3321
|
const rawBody = await readInputFile(options.from);
|
|
3225
3322
|
const json = parseJsonStrict(rawBody);
|
|
@@ -3249,10 +3346,10 @@ async function readInputFile(path) {
|
|
|
3249
3346
|
try {
|
|
3250
3347
|
return await readFile2(path, "utf8");
|
|
3251
3348
|
} catch (error) {
|
|
3252
|
-
if (
|
|
3349
|
+
if (findErrorCode9(error, "ENOENT")) {
|
|
3253
3350
|
throw new Error("Import source not found", { cause: error });
|
|
3254
3351
|
}
|
|
3255
|
-
if (
|
|
3352
|
+
if (findErrorCode9(error, "EISDIR")) {
|
|
3256
3353
|
throw new Error("Import source is not a file", { cause: error });
|
|
3257
3354
|
}
|
|
3258
3355
|
throw new Error("Failed to read import source", { cause: error });
|
|
@@ -3332,8 +3429,8 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
|
|
|
3332
3429
|
}
|
|
3333
3430
|
const cwd = ctx.cwd ?? process.cwd();
|
|
3334
3431
|
const repositoryRoot = await resolveRepositoryRootForSession(cwd, "note");
|
|
3335
|
-
const paths =
|
|
3336
|
-
await
|
|
3432
|
+
const paths = basouPaths10(repositoryRoot);
|
|
3433
|
+
await assertWorkspaceInitialized8(paths.root);
|
|
3337
3434
|
const sessionId = await resolveSessionId2(paths, sessionIdInput);
|
|
3338
3435
|
const body = hasBody ? options.body : await readNoteFile(options.fromFile);
|
|
3339
3436
|
if (body.length === 0) {
|
|
@@ -3341,7 +3438,7 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
|
|
|
3341
3438
|
}
|
|
3342
3439
|
const occurredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3343
3440
|
const sesId = sessionId;
|
|
3344
|
-
const sessionLock = await
|
|
3441
|
+
const sessionLock = await acquireLock5(paths, "session", sesId);
|
|
3345
3442
|
let result;
|
|
3346
3443
|
try {
|
|
3347
3444
|
result = await appendEventToExistingSession2({
|
|
@@ -3366,10 +3463,10 @@ async function readNoteFile(path) {
|
|
|
3366
3463
|
try {
|
|
3367
3464
|
return await readFile2(path, "utf8");
|
|
3368
3465
|
} catch (error) {
|
|
3369
|
-
if (
|
|
3466
|
+
if (findErrorCode9(error, "ENOENT")) {
|
|
3370
3467
|
throw new Error("Note source not found", { cause: error });
|
|
3371
3468
|
}
|
|
3372
|
-
if (
|
|
3469
|
+
if (findErrorCode9(error, "EISDIR")) {
|
|
3373
3470
|
throw new Error("Note source is not a file", { cause: error });
|
|
3374
3471
|
}
|
|
3375
3472
|
throw new Error("Failed to read note source", { cause: error });
|
|
@@ -3414,8 +3511,8 @@ async function doRunSessionRechain(options, ctx) {
|
|
|
3414
3511
|
}
|
|
3415
3512
|
const cwd = ctx.cwd ?? process.cwd();
|
|
3416
3513
|
const repositoryRoot = await resolveRepositoryRootForSession(cwd, "rechain");
|
|
3417
|
-
const paths =
|
|
3418
|
-
await
|
|
3514
|
+
const paths = basouPaths10(repositoryRoot);
|
|
3515
|
+
await assertWorkspaceInitialized8(paths.root);
|
|
3419
3516
|
const sessionIds = options.session !== void 0 ? [await resolveSessionId2(paths, options.session)] : await enumerateSessionDirs2(paths);
|
|
3420
3517
|
const dryRun = options.dryRun === true;
|
|
3421
3518
|
const rows = [];
|
|
@@ -3468,11 +3565,11 @@ function renderRechainRow(row, dryRun) {
|
|
|
3468
3565
|
|
|
3469
3566
|
// src/commands/stats.ts
|
|
3470
3567
|
import {
|
|
3471
|
-
assertBasouRootSafe as
|
|
3472
|
-
basouPaths as
|
|
3568
|
+
assertBasouRootSafe as assertBasouRootSafe11,
|
|
3569
|
+
basouPaths as basouPaths11,
|
|
3473
3570
|
computeWorkStats,
|
|
3474
|
-
findErrorCode as
|
|
3475
|
-
resolveRepositoryRoot as
|
|
3571
|
+
findErrorCode as findErrorCode10,
|
|
3572
|
+
resolveRepositoryRoot as resolveRepositoryRoot12
|
|
3476
3573
|
} from "@basou/core";
|
|
3477
3574
|
function registerStatsCommand(program2) {
|
|
3478
3575
|
program2.command("stats").description("Report how much the AI worked (output volume + time proxies) across sessions").option("--by-source", "Break the totals down by session source kind").option("--by-day", "Break billable time and volume down by calendar day").option("--json", "Output the full stats as JSON").option("-v, --verbose", "Show error causes").action(async (options) => {
|
|
@@ -3490,8 +3587,8 @@ async function runStats(options, ctx = {}) {
|
|
|
3490
3587
|
async function doRunStats(options, ctx) {
|
|
3491
3588
|
const cwd = ctx.cwd ?? process.cwd();
|
|
3492
3589
|
const repositoryRoot = await resolveRepositoryRootForStats(cwd);
|
|
3493
|
-
const paths =
|
|
3494
|
-
await
|
|
3590
|
+
const paths = basouPaths11(repositoryRoot);
|
|
3591
|
+
await assertWorkspaceInitialized9(paths.root);
|
|
3495
3592
|
const now = ctx.nowProvider?.() ?? /* @__PURE__ */ new Date();
|
|
3496
3593
|
const result = await computeWorkStats({
|
|
3497
3594
|
paths,
|
|
@@ -3575,7 +3672,7 @@ function formatInt(n) {
|
|
|
3575
3672
|
}
|
|
3576
3673
|
async function resolveRepositoryRootForStats(cwd) {
|
|
3577
3674
|
try {
|
|
3578
|
-
return await
|
|
3675
|
+
return await resolveRepositoryRoot12(cwd);
|
|
3579
3676
|
} catch (error) {
|
|
3580
3677
|
if (error instanceof Error && error.message === "Not a git repository") {
|
|
3581
3678
|
throw new Error("Not a git repository. Run 'git init' first, then re-run 'basou stats'.", {
|
|
@@ -3585,11 +3682,11 @@ async function resolveRepositoryRootForStats(cwd) {
|
|
|
3585
3682
|
throw error;
|
|
3586
3683
|
}
|
|
3587
3684
|
}
|
|
3588
|
-
async function
|
|
3685
|
+
async function assertWorkspaceInitialized9(basouRoot) {
|
|
3589
3686
|
try {
|
|
3590
|
-
await
|
|
3687
|
+
await assertBasouRootSafe11(basouRoot);
|
|
3591
3688
|
} catch (error) {
|
|
3592
|
-
if (
|
|
3689
|
+
if (findErrorCode10(error, "ENOENT")) {
|
|
3593
3690
|
throw new Error("Workspace not initialized. Run 'basou init' first.");
|
|
3594
3691
|
}
|
|
3595
3692
|
throw error;
|
|
@@ -3598,12 +3695,12 @@ async function assertWorkspaceInitialized8(basouRoot) {
|
|
|
3598
3695
|
|
|
3599
3696
|
// src/commands/status.ts
|
|
3600
3697
|
import {
|
|
3601
|
-
assertBasouRootSafe as
|
|
3602
|
-
basouPaths as
|
|
3698
|
+
assertBasouRootSafe as assertBasouRootSafe12,
|
|
3699
|
+
basouPaths as basouPaths12,
|
|
3603
3700
|
buildStatusSnapshot,
|
|
3604
|
-
findErrorCode as
|
|
3701
|
+
findErrorCode as findErrorCode11,
|
|
3605
3702
|
readManifest as readManifest6,
|
|
3606
|
-
resolveRepositoryRoot as
|
|
3703
|
+
resolveRepositoryRoot as resolveRepositoryRoot13,
|
|
3607
3704
|
writeStatus
|
|
3608
3705
|
} from "@basou/core";
|
|
3609
3706
|
function registerStatusCommand(program2) {
|
|
@@ -3622,11 +3719,11 @@ async function runStatus(options, ctx = {}) {
|
|
|
3622
3719
|
async function doRunStatus(options, ctx) {
|
|
3623
3720
|
const cwd = ctx.cwd ?? process.cwd();
|
|
3624
3721
|
const repositoryRoot = await resolveRepositoryRootForStatus(cwd);
|
|
3625
|
-
const paths =
|
|
3722
|
+
const paths = basouPaths12(repositoryRoot);
|
|
3626
3723
|
try {
|
|
3627
|
-
await
|
|
3724
|
+
await assertBasouRootSafe12(paths.root);
|
|
3628
3725
|
} catch (error) {
|
|
3629
|
-
if (
|
|
3726
|
+
if (findErrorCode11(error, "ENOENT")) {
|
|
3630
3727
|
throw new Error("Workspace not initialized. Run 'basou init' first.");
|
|
3631
3728
|
}
|
|
3632
3729
|
throw error;
|
|
@@ -3635,7 +3732,7 @@ async function doRunStatus(options, ctx) {
|
|
|
3635
3732
|
try {
|
|
3636
3733
|
manifest = await readManifest6(paths);
|
|
3637
3734
|
} catch (error) {
|
|
3638
|
-
if (
|
|
3735
|
+
if (findErrorCode11(error, "ENOENT")) {
|
|
3639
3736
|
throw new Error("Workspace not initialized. Run 'basou init' first.");
|
|
3640
3737
|
}
|
|
3641
3738
|
throw new Error("Failed to read workspace manifest", { cause: error });
|
|
@@ -3659,7 +3756,7 @@ function renderTextStatus(s) {
|
|
|
3659
3756
|
}
|
|
3660
3757
|
async function resolveRepositoryRootForStatus(cwd) {
|
|
3661
3758
|
try {
|
|
3662
|
-
return await
|
|
3759
|
+
return await resolveRepositoryRoot13(cwd);
|
|
3663
3760
|
} catch (error) {
|
|
3664
3761
|
if (error instanceof Error && error.message === "Not a git repository") {
|
|
3665
3762
|
throw new Error("Not a git repository. Run 'git init' first, then re-run 'basou status'.", {
|
|
@@ -3675,13 +3772,13 @@ import { readFile as readFile3 } from "fs/promises";
|
|
|
3675
3772
|
import { join as join7 } from "path";
|
|
3676
3773
|
import {
|
|
3677
3774
|
archiveTask,
|
|
3678
|
-
assertBasouRootSafe as
|
|
3679
|
-
basouPaths as
|
|
3775
|
+
assertBasouRootSafe as assertBasouRootSafe13,
|
|
3776
|
+
basouPaths as basouPaths13,
|
|
3680
3777
|
createTaskWithEvent,
|
|
3681
3778
|
deleteTask,
|
|
3682
3779
|
editTask,
|
|
3683
3780
|
enumerateArchivedTaskIds,
|
|
3684
|
-
findErrorCode as
|
|
3781
|
+
findErrorCode as findErrorCode12,
|
|
3685
3782
|
loadSessionEntries as loadSessionEntries2,
|
|
3686
3783
|
loadTaskEntries,
|
|
3687
3784
|
prefixedUlid as prefixedUlid5,
|
|
@@ -3692,7 +3789,7 @@ import {
|
|
|
3692
3789
|
reconcileTask,
|
|
3693
3790
|
refreshTaskLinkedSessions,
|
|
3694
3791
|
replayEvents as replayEvents2,
|
|
3695
|
-
resolveRepositoryRoot as
|
|
3792
|
+
resolveRepositoryRoot as resolveRepositoryRoot14,
|
|
3696
3793
|
resolveSessionId as resolveSessionId3,
|
|
3697
3794
|
resolveTaskId as resolveTaskId2,
|
|
3698
3795
|
TaskStatusSchema,
|
|
@@ -3778,8 +3875,8 @@ async function doRunTaskNew(options, ctx) {
|
|
|
3778
3875
|
}
|
|
3779
3876
|
const cwd = ctx.cwd ?? process.cwd();
|
|
3780
3877
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "new");
|
|
3781
|
-
const paths =
|
|
3782
|
-
await
|
|
3878
|
+
const paths = basouPaths13(repositoryRoot);
|
|
3879
|
+
await assertWorkspaceInitialized10(paths.root);
|
|
3783
3880
|
const description = options.description !== void 0 ? options.description : options.fromFile !== void 0 ? await readDescriptionFile(options.fromFile) : "";
|
|
3784
3881
|
const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
|
|
3785
3882
|
const occurredAt = now.toISOString();
|
|
@@ -3887,8 +3984,8 @@ async function runTaskList(options, ctx = {}) {
|
|
|
3887
3984
|
async function doRunTaskList(options, ctx) {
|
|
3888
3985
|
const cwd = ctx.cwd ?? process.cwd();
|
|
3889
3986
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "list");
|
|
3890
|
-
const paths =
|
|
3891
|
-
await
|
|
3987
|
+
const paths = basouPaths13(repositoryRoot);
|
|
3988
|
+
await assertWorkspaceInitialized10(paths.root);
|
|
3892
3989
|
const entries = await loadTaskEntries(paths, {
|
|
3893
3990
|
onSkip: (id, reason) => printTaskSkip(id, reason)
|
|
3894
3991
|
});
|
|
@@ -3991,8 +4088,8 @@ async function runTaskShow(idInput, options, ctx = {}) {
|
|
|
3991
4088
|
async function doRunTaskShow(idInput, options, ctx) {
|
|
3992
4089
|
const cwd = ctx.cwd ?? process.cwd();
|
|
3993
4090
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "show");
|
|
3994
|
-
const paths =
|
|
3995
|
-
await
|
|
4091
|
+
const paths = basouPaths13(repositoryRoot);
|
|
4092
|
+
await assertWorkspaceInitialized10(paths.root);
|
|
3996
4093
|
const taskId = await resolveTaskId2(paths, idInput, { includeArchived: true });
|
|
3997
4094
|
const { doc, archived } = await readTaskFileWithArchiveFallback(paths, taskId);
|
|
3998
4095
|
const sessions = await loadSessionEntries2(paths, { now: /* @__PURE__ */ new Date() });
|
|
@@ -4135,8 +4232,8 @@ async function doRunTaskStatus(taskIdInput, newStatusInput, options, ctx) {
|
|
|
4135
4232
|
const newStatus = parseTaskStatusPositional(newStatusInput);
|
|
4136
4233
|
const cwd = ctx.cwd ?? process.cwd();
|
|
4137
4234
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "status");
|
|
4138
|
-
const paths =
|
|
4139
|
-
await
|
|
4235
|
+
const paths = basouPaths13(repositoryRoot);
|
|
4236
|
+
await assertWorkspaceInitialized10(paths.root);
|
|
4140
4237
|
const taskId = await resolveTaskId2(paths, taskIdInput);
|
|
4141
4238
|
const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
|
|
4142
4239
|
const occurredAt = now.toISOString();
|
|
@@ -4212,8 +4309,8 @@ async function runTaskReconcile(options, ctx = {}) {
|
|
|
4212
4309
|
async function doRunTaskReconcile(options, ctx) {
|
|
4213
4310
|
const cwd = ctx.cwd ?? process.cwd();
|
|
4214
4311
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "reconcile");
|
|
4215
|
-
const paths =
|
|
4216
|
-
await
|
|
4312
|
+
const paths = basouPaths13(repositoryRoot);
|
|
4313
|
+
await assertWorkspaceInitialized10(paths.root);
|
|
4217
4314
|
const manifest = await readManifest7(paths);
|
|
4218
4315
|
const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
|
|
4219
4316
|
const write = options.write === true;
|
|
@@ -4392,8 +4489,8 @@ async function doRunTaskRefreshLinkage(taskIdInput, options, ctx) {
|
|
|
4392
4489
|
}
|
|
4393
4490
|
const cwd = ctx.cwd ?? process.cwd();
|
|
4394
4491
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "refresh-linkage");
|
|
4395
|
-
const paths =
|
|
4396
|
-
await
|
|
4492
|
+
const paths = basouPaths13(repositoryRoot);
|
|
4493
|
+
await assertWorkspaceInitialized10(paths.root);
|
|
4397
4494
|
const manifest = await readManifest7(paths);
|
|
4398
4495
|
const taskId = await resolveTaskId2(paths, taskIdInput);
|
|
4399
4496
|
const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
|
|
@@ -4472,8 +4569,8 @@ async function doRunTaskEdit(taskIdInput, options, ctx) {
|
|
|
4472
4569
|
}
|
|
4473
4570
|
const cwd = ctx.cwd ?? process.cwd();
|
|
4474
4571
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "edit");
|
|
4475
|
-
const paths =
|
|
4476
|
-
await
|
|
4572
|
+
const paths = basouPaths13(repositoryRoot);
|
|
4573
|
+
await assertWorkspaceInitialized10(paths.root);
|
|
4477
4574
|
const manifest = await readManifest7(paths);
|
|
4478
4575
|
const taskId = await resolveTaskId2(paths, taskIdInput);
|
|
4479
4576
|
const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
|
|
@@ -4528,8 +4625,8 @@ async function doRunTaskDelete(taskIdInput, options, ctx) {
|
|
|
4528
4625
|
}
|
|
4529
4626
|
const cwd = ctx.cwd ?? process.cwd();
|
|
4530
4627
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "delete");
|
|
4531
|
-
const paths =
|
|
4532
|
-
await
|
|
4628
|
+
const paths = basouPaths13(repositoryRoot);
|
|
4629
|
+
await assertWorkspaceInitialized10(paths.root);
|
|
4533
4630
|
const manifest = await readManifest7(paths);
|
|
4534
4631
|
const taskId = await resolveTaskId2(paths, taskIdInput);
|
|
4535
4632
|
if (options.yes !== true) {
|
|
@@ -4573,8 +4670,8 @@ async function doRunTaskArchive(taskIdInput, options, ctx) {
|
|
|
4573
4670
|
}
|
|
4574
4671
|
const cwd = ctx.cwd ?? process.cwd();
|
|
4575
4672
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "archive");
|
|
4576
|
-
const paths =
|
|
4577
|
-
await
|
|
4673
|
+
const paths = basouPaths13(repositoryRoot);
|
|
4674
|
+
await assertWorkspaceInitialized10(paths.root);
|
|
4578
4675
|
const manifest = await readManifest7(paths);
|
|
4579
4676
|
const taskId = await resolveTaskId2(paths, taskIdInput);
|
|
4580
4677
|
if (options.yes !== true) {
|
|
@@ -4689,10 +4786,10 @@ async function readDescriptionFile(path) {
|
|
|
4689
4786
|
try {
|
|
4690
4787
|
return await readFile3(path, "utf8");
|
|
4691
4788
|
} catch (error) {
|
|
4692
|
-
if (
|
|
4789
|
+
if (findErrorCode12(error, "ENOENT")) {
|
|
4693
4790
|
throw new Error("Description source not found", { cause: error });
|
|
4694
4791
|
}
|
|
4695
|
-
if (
|
|
4792
|
+
if (findErrorCode12(error, "EISDIR")) {
|
|
4696
4793
|
throw new Error("Description source is not a file", { cause: error });
|
|
4697
4794
|
}
|
|
4698
4795
|
throw new Error("Failed to read description source", { cause: error });
|
|
@@ -4700,7 +4797,7 @@ async function readDescriptionFile(path) {
|
|
|
4700
4797
|
}
|
|
4701
4798
|
async function resolveRepositoryRootForTask(cwd, subcmd) {
|
|
4702
4799
|
try {
|
|
4703
|
-
return await
|
|
4800
|
+
return await resolveRepositoryRoot14(cwd);
|
|
4704
4801
|
} catch (error) {
|
|
4705
4802
|
if (error instanceof Error && error.message === "Not a git repository") {
|
|
4706
4803
|
throw new Error(
|
|
@@ -4711,11 +4808,11 @@ async function resolveRepositoryRootForTask(cwd, subcmd) {
|
|
|
4711
4808
|
throw error;
|
|
4712
4809
|
}
|
|
4713
4810
|
}
|
|
4714
|
-
async function
|
|
4811
|
+
async function assertWorkspaceInitialized10(basouRoot) {
|
|
4715
4812
|
try {
|
|
4716
|
-
await
|
|
4813
|
+
await assertBasouRootSafe13(basouRoot);
|
|
4717
4814
|
} catch (error) {
|
|
4718
|
-
if (
|
|
4815
|
+
if (findErrorCode12(error, "ENOENT")) {
|
|
4719
4816
|
throw new Error("Workspace not initialized. Run 'basou init' first.");
|
|
4720
4817
|
}
|
|
4721
4818
|
throw error;
|
|
@@ -4803,18 +4900,16 @@ function maxLen3(values, floor) {
|
|
|
4803
4900
|
|
|
4804
4901
|
// src/commands/verify.ts
|
|
4805
4902
|
import {
|
|
4806
|
-
assertBasouRootSafe as
|
|
4807
|
-
basouPaths as
|
|
4903
|
+
assertBasouRootSafe as assertBasouRootSafe14,
|
|
4904
|
+
basouPaths as basouPaths14,
|
|
4808
4905
|
enumerateSessionDirs as enumerateSessionDirs3,
|
|
4809
|
-
findErrorCode as
|
|
4810
|
-
resolveRepositoryRoot as
|
|
4906
|
+
findErrorCode as findErrorCode13,
|
|
4907
|
+
resolveRepositoryRoot as resolveRepositoryRoot15,
|
|
4811
4908
|
resolveSessionId as resolveSessionId4,
|
|
4812
4909
|
verifyEventsChain
|
|
4813
4910
|
} from "@basou/core";
|
|
4814
4911
|
function registerVerifyCommand(program2) {
|
|
4815
|
-
program2.command("verify").description(
|
|
4816
|
-
"Verify the tamper-evidence hash chain of imported sessions' event logs (read-only)"
|
|
4817
|
-
).option("--session <id>", "Verify a single session (unique id prefix accepted)").option("--all", "Verify every session (the default when --session is omitted)").option("--json", "Output the verdicts as JSON").option("-v, --verbose", "Show error causes").action(async (opts) => {
|
|
4912
|
+
program2.command("verify").description("Verify the tamper-evidence hash chain of sessions' event logs (read-only)").option("--session <id>", "Verify a single session (unique id prefix accepted)").option("--all", "Verify every session (the default when --session is omitted)").option("--json", "Output the verdicts as JSON").option("-v, --verbose", "Show error causes").action(async (opts) => {
|
|
4818
4913
|
await runVerify(opts);
|
|
4819
4914
|
});
|
|
4820
4915
|
}
|
|
@@ -4832,8 +4927,8 @@ async function doRunVerify(options, ctx) {
|
|
|
4832
4927
|
}
|
|
4833
4928
|
const cwd = ctx.cwd ?? process.cwd();
|
|
4834
4929
|
const repositoryRoot = await resolveRepositoryRootForVerify(cwd);
|
|
4835
|
-
const paths =
|
|
4836
|
-
await
|
|
4930
|
+
const paths = basouPaths14(repositoryRoot);
|
|
4931
|
+
await assertWorkspaceInitialized11(paths.root);
|
|
4837
4932
|
const sessionIds = options.session !== void 0 ? [await resolveSessionId4(paths, options.session)] : await enumerateSessionDirs3(paths);
|
|
4838
4933
|
const rows = [];
|
|
4839
4934
|
for (const sessionId of sessionIds) {
|
|
@@ -4855,7 +4950,7 @@ async function doRunVerify(options, ctx) {
|
|
|
4855
4950
|
}
|
|
4856
4951
|
const tally = (status) => rows.filter((r) => r.status === status).length;
|
|
4857
4952
|
console.log(
|
|
4858
|
-
`Sessions: ${rows.length} total \u2014 ${tally("verified")} verified, ${tally("unchained")} unchained, ${tally("empty")} empty, ${tally("incomplete")} incomplete, ${tamperedCount} tampered`
|
|
4953
|
+
`Sessions: ${rows.length} total \u2014 ${tally("verified")} verified, ${tally("unchained")} unchained, ${tally("empty")} empty, ${tally("incomplete")} incomplete, ${tally("in_progress")} in_progress, ${tamperedCount} tampered`
|
|
4859
4954
|
);
|
|
4860
4955
|
}
|
|
4861
4956
|
if (tamperedCount > 0) {
|
|
@@ -4870,15 +4965,17 @@ function renderVerdict(row) {
|
|
|
4870
4965
|
return row.line !== void 0 ? `TAMPERED (${row.reason} at line ${row.line})` : `TAMPERED (${row.reason})`;
|
|
4871
4966
|
case "incomplete":
|
|
4872
4967
|
return "incomplete (session.yaml missing; re-import to repair)";
|
|
4968
|
+
case "in_progress":
|
|
4969
|
+
return `in_progress (${row.event_count} events; live session, anchor written at finalize)`;
|
|
4873
4970
|
case "unchained":
|
|
4874
|
-
return "unchained (
|
|
4971
|
+
return "unchained (session created before event-log chaining)";
|
|
4875
4972
|
case "empty":
|
|
4876
4973
|
return "empty";
|
|
4877
4974
|
}
|
|
4878
4975
|
}
|
|
4879
4976
|
async function resolveRepositoryRootForVerify(cwd) {
|
|
4880
4977
|
try {
|
|
4881
|
-
return await
|
|
4978
|
+
return await resolveRepositoryRoot15(cwd);
|
|
4882
4979
|
} catch (error) {
|
|
4883
4980
|
if (error instanceof Error && error.message === "Not a git repository") {
|
|
4884
4981
|
throw new Error("Not a git repository. Run 'git init' first, then re-run 'basou verify'.", {
|
|
@@ -4888,11 +4985,11 @@ async function resolveRepositoryRootForVerify(cwd) {
|
|
|
4888
4985
|
throw error;
|
|
4889
4986
|
}
|
|
4890
4987
|
}
|
|
4891
|
-
async function
|
|
4988
|
+
async function assertWorkspaceInitialized11(basouRoot) {
|
|
4892
4989
|
try {
|
|
4893
|
-
await
|
|
4990
|
+
await assertBasouRootSafe14(basouRoot);
|
|
4894
4991
|
} catch (error) {
|
|
4895
|
-
if (
|
|
4992
|
+
if (findErrorCode13(error, "ENOENT")) {
|
|
4896
4993
|
throw new Error("Workspace not initialized. Run 'basou init' first.");
|
|
4897
4994
|
}
|
|
4898
4995
|
throw error;
|
|
@@ -4901,7 +4998,7 @@ async function assertWorkspaceInitialized10(basouRoot) {
|
|
|
4901
4998
|
|
|
4902
4999
|
// src/commands/view.ts
|
|
4903
5000
|
import { spawn } from "child_process";
|
|
4904
|
-
import { assertBasouRootSafe as
|
|
5001
|
+
import { assertBasouRootSafe as assertBasouRootSafe15, basouPaths as basouPaths15, findErrorCode as findErrorCode15, resolveRepositoryRoot as resolveRepositoryRoot16 } from "@basou/core";
|
|
4905
5002
|
import { InvalidArgumentError as InvalidArgumentError5 } from "commander";
|
|
4906
5003
|
|
|
4907
5004
|
// src/lib/view-server.ts
|
|
@@ -4910,7 +5007,7 @@ import { join as join8 } from "path";
|
|
|
4910
5007
|
import {
|
|
4911
5008
|
computeWorkStats as computeWorkStats2,
|
|
4912
5009
|
enumerateApprovals as enumerateApprovals2,
|
|
4913
|
-
findErrorCode as
|
|
5010
|
+
findErrorCode as findErrorCode14,
|
|
4914
5011
|
isLazyExpired as isLazyExpired2,
|
|
4915
5012
|
loadApproval as loadApproval2,
|
|
4916
5013
|
loadSessionEntries as loadSessionEntries3,
|
|
@@ -5383,7 +5480,7 @@ function startViewServer(opts) {
|
|
|
5383
5480
|
};
|
|
5384
5481
|
let boundPort = port;
|
|
5385
5482
|
const getPort = () => boundPort;
|
|
5386
|
-
return new Promise((
|
|
5483
|
+
return new Promise((resolve4, reject) => {
|
|
5387
5484
|
const server = createServer((req, res) => {
|
|
5388
5485
|
handleRequest(req, res, deps, getPort, runExclusive).catch((error) => {
|
|
5389
5486
|
sendError(res, error instanceof HttpError ? error.status : 500, pathlessMessage(error));
|
|
@@ -5394,7 +5491,7 @@ function startViewServer(opts) {
|
|
|
5394
5491
|
const address = server.address();
|
|
5395
5492
|
boundPort = isAddressInfo(address) ? address.port : port;
|
|
5396
5493
|
server.off("error", reject);
|
|
5397
|
-
|
|
5494
|
+
resolve4({
|
|
5398
5495
|
url: `http://${host}:${boundPort}`,
|
|
5399
5496
|
port: boundPort,
|
|
5400
5497
|
close: () => closeServer(server)
|
|
@@ -5406,8 +5503,8 @@ function isAddressInfo(value) {
|
|
|
5406
5503
|
return value !== null && typeof value === "object";
|
|
5407
5504
|
}
|
|
5408
5505
|
function closeServer(server) {
|
|
5409
|
-
return new Promise((
|
|
5410
|
-
server.close(() =>
|
|
5506
|
+
return new Promise((resolve4) => {
|
|
5507
|
+
server.close(() => resolve4());
|
|
5411
5508
|
server.closeAllConnections();
|
|
5412
5509
|
});
|
|
5413
5510
|
}
|
|
@@ -5512,7 +5609,7 @@ async function overview(deps) {
|
|
|
5512
5609
|
try {
|
|
5513
5610
|
manifest = await readManifest8(deps.paths);
|
|
5514
5611
|
} catch (error) {
|
|
5515
|
-
if (
|
|
5612
|
+
if (findErrorCode14(error, "ENOENT")) {
|
|
5516
5613
|
return { initialized: false, repoRoot: deps.repoRoot };
|
|
5517
5614
|
}
|
|
5518
5615
|
throw error;
|
|
@@ -5727,8 +5824,8 @@ async function runView(options, ctx = {}) {
|
|
|
5727
5824
|
async function doRunView(options, ctx) {
|
|
5728
5825
|
const cwd = ctx.cwd ?? process.cwd();
|
|
5729
5826
|
const repositoryRoot = await resolveRepositoryRootForView(cwd);
|
|
5730
|
-
const paths =
|
|
5731
|
-
await
|
|
5827
|
+
const paths = basouPaths15(repositoryRoot);
|
|
5828
|
+
await assertWorkspaceInitialized12(paths.root);
|
|
5732
5829
|
const deps = {
|
|
5733
5830
|
paths,
|
|
5734
5831
|
repoRoot: repositoryRoot,
|
|
@@ -5759,7 +5856,7 @@ async function startListening(port, deps) {
|
|
|
5759
5856
|
try {
|
|
5760
5857
|
return await startViewServer({ port, deps });
|
|
5761
5858
|
} catch (error) {
|
|
5762
|
-
if (
|
|
5859
|
+
if (findErrorCode15(error, "EADDRINUSE")) {
|
|
5763
5860
|
throw new Error(`Port ${port} is already in use. Pass --port <n> to choose another.`, {
|
|
5764
5861
|
cause: error
|
|
5765
5862
|
});
|
|
@@ -5782,7 +5879,7 @@ function openInBrowser(url, override) {
|
|
|
5782
5879
|
}
|
|
5783
5880
|
}
|
|
5784
5881
|
function waitForShutdown(signal) {
|
|
5785
|
-
return new Promise((
|
|
5882
|
+
return new Promise((resolve4) => {
|
|
5786
5883
|
const cleanup = () => {
|
|
5787
5884
|
process.off("SIGINT", onSignal);
|
|
5788
5885
|
process.off("SIGTERM", onSignal);
|
|
@@ -5790,18 +5887,18 @@ function waitForShutdown(signal) {
|
|
|
5790
5887
|
};
|
|
5791
5888
|
const onSignal = () => {
|
|
5792
5889
|
cleanup();
|
|
5793
|
-
|
|
5890
|
+
resolve4();
|
|
5794
5891
|
};
|
|
5795
5892
|
const onAbort = () => {
|
|
5796
5893
|
cleanup();
|
|
5797
|
-
|
|
5894
|
+
resolve4();
|
|
5798
5895
|
};
|
|
5799
5896
|
process.on("SIGINT", onSignal);
|
|
5800
5897
|
process.on("SIGTERM", onSignal);
|
|
5801
5898
|
if (signal !== void 0) {
|
|
5802
5899
|
if (signal.aborted) {
|
|
5803
5900
|
cleanup();
|
|
5804
|
-
|
|
5901
|
+
resolve4();
|
|
5805
5902
|
return;
|
|
5806
5903
|
}
|
|
5807
5904
|
signal.addEventListener("abort", onAbort);
|
|
@@ -5810,7 +5907,7 @@ function waitForShutdown(signal) {
|
|
|
5810
5907
|
}
|
|
5811
5908
|
async function resolveRepositoryRootForView(cwd) {
|
|
5812
5909
|
try {
|
|
5813
|
-
return await
|
|
5910
|
+
return await resolveRepositoryRoot16(cwd);
|
|
5814
5911
|
} catch (error) {
|
|
5815
5912
|
if (error instanceof Error && error.message === "Not a git repository") {
|
|
5816
5913
|
throw new Error("Not a git repository. Run 'git init' first, then re-run 'basou view'.", {
|
|
@@ -5820,11 +5917,11 @@ async function resolveRepositoryRootForView(cwd) {
|
|
|
5820
5917
|
throw error;
|
|
5821
5918
|
}
|
|
5822
5919
|
}
|
|
5823
|
-
async function
|
|
5920
|
+
async function assertWorkspaceInitialized12(basouRoot) {
|
|
5824
5921
|
try {
|
|
5825
|
-
await
|
|
5922
|
+
await assertBasouRootSafe15(basouRoot);
|
|
5826
5923
|
} catch (error) {
|
|
5827
|
-
if (
|
|
5924
|
+
if (findErrorCode15(error, "ENOENT")) {
|
|
5828
5925
|
throw new Error("Workspace not initialized. Run 'basou init' first.");
|
|
5829
5926
|
}
|
|
5830
5927
|
throw error;
|
|
@@ -5853,6 +5950,7 @@ function buildProgram() {
|
|
|
5853
5950
|
registerTaskCommand(program2);
|
|
5854
5951
|
registerHandoffCommand(program2);
|
|
5855
5952
|
registerDecisionsCommand(program2);
|
|
5953
|
+
registerReportCommand(program2);
|
|
5856
5954
|
return program2;
|
|
5857
5955
|
}
|
|
5858
5956
|
|