@basou/cli 0.29.0 → 0.30.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 +598 -263
- package/dist/index.js.map +1 -1
- package/dist/program.js +598 -263
- package/dist/program.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2114,16 +2114,20 @@ function registerHookCommand(program2) {
|
|
|
2114
2114
|
).option(
|
|
2115
2115
|
"--block",
|
|
2116
2116
|
"Opt-in enforcement: hold the agent in-turn (decision:block) instead of a non-blocking reminder"
|
|
2117
|
+
).option(
|
|
2118
|
+
"--require-review",
|
|
2119
|
+
"Opt-in review gate: also remind when a session shipped substantive code (push / PR / merge) without recording a review"
|
|
2117
2120
|
).addHelpText("after", HOOK_STOP_HELP).action(async (options) => {
|
|
2118
2121
|
const minEdits = parseMinEdits(options.minEdits);
|
|
2119
2122
|
await runHookStop({
|
|
2120
2123
|
...minEdits !== void 0 ? { minEdits } : {},
|
|
2121
|
-
...options.block === true ? { block: true } : {}
|
|
2124
|
+
...options.block === true ? { block: true } : {},
|
|
2125
|
+
...options.requireReview === true ? { requireReview: true } : {}
|
|
2122
2126
|
});
|
|
2123
2127
|
});
|
|
2124
2128
|
hook.command("install").description(
|
|
2125
|
-
"Register the Stop hook in ~/.claude/settings.json (reproducible, idempotent). Default is advisory; --block opts into in-turn enforcement."
|
|
2126
|
-
).option("--block", "Register the blocking (opt-in enforcement) form instead of advisory").option("--min-edits <n>", "Pass a custom file-edit threshold to the registered hook").option("--settings <path>", "Override the settings.json path (intended for tests)").option("--dry-run", "Print what would change without writing").option("-v, --verbose", "Show error causes").action(async (opts) => {
|
|
2129
|
+
"Register the Stop hook in ~/.claude/settings.json (reproducible, idempotent). Default is advisory capture-only; --block opts into in-turn enforcement, --require-review opts into the review gate."
|
|
2130
|
+
).option("--block", "Register the blocking (opt-in enforcement) form instead of advisory").option("--require-review", "Register with the opt-in review gate enabled").option("--min-edits <n>", "Pass a custom file-edit threshold to the registered hook").option("--settings <path>", "Override the settings.json path (intended for tests)").option("--dry-run", "Print what would change without writing").option("-v, --verbose", "Show error causes").action(async (opts) => {
|
|
2127
2131
|
await runHookInstall(opts);
|
|
2128
2132
|
});
|
|
2129
2133
|
hook.command("uninstall").description(
|
|
@@ -2147,6 +2151,12 @@ Substantive = EITHER >= ${DEFAULT_STOP_HOOK_MIN_EDITS} file edits (default) OR a
|
|
|
2147
2151
|
answer (an uncaptured conversational decision). Read-only Bash (ls / grep /
|
|
2148
2152
|
git status) does NOT count.
|
|
2149
2153
|
|
|
2154
|
+
With --require-review (opt-in, 'basou hook install --require-review') it also
|
|
2155
|
+
reminds when the session SHIPPED substantive code (git push / git merge /
|
|
2156
|
+
gh pr create|merge) without recording a review ('basou review record'). This
|
|
2157
|
+
gate is off by default; when on, its reminder is composed into the same
|
|
2158
|
+
envelope as the capture reminder.
|
|
2159
|
+
|
|
2150
2160
|
By default the reminder is non-blocking: Claude sees it and may act on it or
|
|
2151
2161
|
stop. With --block (opt-in enforcement, 'basou hook install --block') it instead
|
|
2152
2162
|
returns decision:block, holding the agent in-turn to act on the reminder; the
|
|
@@ -2190,11 +2200,17 @@ async function doRunHookStop(options, ctx) {
|
|
|
2190
2200
|
stopHookActive: false,
|
|
2191
2201
|
...options.minEdits !== void 0 ? { minEdits: options.minEdits } : {}
|
|
2192
2202
|
});
|
|
2193
|
-
|
|
2194
|
-
|
|
2203
|
+
const parts = [];
|
|
2204
|
+
if (evaluation.kind === "nudge") parts.push(evaluation.additionalContext);
|
|
2205
|
+
if (options.requireReview === true && evaluation.review.fires) {
|
|
2206
|
+
parts.push(evaluation.review.additionalContext);
|
|
2207
|
+
}
|
|
2208
|
+
if (parts.length === 0) return;
|
|
2209
|
+
const reason = parts.join("\n\n");
|
|
2210
|
+
const payloadJson = options.block === true ? JSON.stringify({ decision: "block", reason }) : JSON.stringify({
|
|
2195
2211
|
hookSpecificOutput: {
|
|
2196
2212
|
hookEventName: "Stop",
|
|
2197
|
-
additionalContext:
|
|
2213
|
+
additionalContext: reason
|
|
2198
2214
|
}
|
|
2199
2215
|
});
|
|
2200
2216
|
write(`${payloadJson}
|
|
@@ -2247,6 +2263,7 @@ function resolveCliEntry() {
|
|
|
2247
2263
|
function normalizeInstallOptions(raw) {
|
|
2248
2264
|
const out = {};
|
|
2249
2265
|
if (raw.block === true) out.block = true;
|
|
2266
|
+
if (raw.requireReview === true) out.requireReview = true;
|
|
2250
2267
|
if (raw.settings !== void 0) out.settings = raw.settings;
|
|
2251
2268
|
if (raw.dryRun === true) out.dryRun = true;
|
|
2252
2269
|
if (raw.verbose === true) out.verbose = true;
|
|
@@ -2306,9 +2323,13 @@ async function doRunHookInstall(options, ctx = {}) {
|
|
|
2306
2323
|
const command = buildStopHookCommand({
|
|
2307
2324
|
cliEntry,
|
|
2308
2325
|
...options.block === true ? { block: true } : {},
|
|
2326
|
+
...options.requireReview === true ? { requireReview: true } : {},
|
|
2309
2327
|
...options.minEdits !== void 0 ? { minEdits: options.minEdits } : {}
|
|
2310
2328
|
});
|
|
2311
|
-
const mode =
|
|
2329
|
+
const mode = describeHookMode({
|
|
2330
|
+
block: options.block === true,
|
|
2331
|
+
review: options.requireReview === true
|
|
2332
|
+
});
|
|
2312
2333
|
await assertNotSymlink(settingsPath);
|
|
2313
2334
|
const { raw, parsed } = await readSettings(settingsPath);
|
|
2314
2335
|
const { settings, action } = upsertStopHook(parsed, command);
|
|
@@ -2385,9 +2406,17 @@ async function doRunHookStatus(options) {
|
|
|
2385
2406
|
console.log("basou Stop hook: not registered. Run 'basou hook install' to register it.");
|
|
2386
2407
|
return;
|
|
2387
2408
|
}
|
|
2388
|
-
const mode =
|
|
2409
|
+
const mode = describeHookMode({
|
|
2410
|
+
block: / --block\b/.test(command),
|
|
2411
|
+
review: / --require-review\b/.test(command)
|
|
2412
|
+
});
|
|
2389
2413
|
console.log(`basou Stop hook: registered, ${mode}.`);
|
|
2390
2414
|
}
|
|
2415
|
+
function describeHookMode(tiers) {
|
|
2416
|
+
const enforcement = tiers.block ? "blocking (opt-in enforcement)" : "advisory (non-blocking)";
|
|
2417
|
+
const gates = tiers.review ? "capture + review" : "capture";
|
|
2418
|
+
return `${enforcement}, ${gates}`;
|
|
2419
|
+
}
|
|
2391
2420
|
|
|
2392
2421
|
// src/commands/import.ts
|
|
2393
2422
|
import { createReadStream } from "fs";
|
|
@@ -6220,25 +6249,131 @@ function renderProjectRetrofit(result) {
|
|
|
6220
6249
|
|
|
6221
6250
|
// src/commands/protocol.ts
|
|
6222
6251
|
import { readFile as readFile4 } from "fs/promises";
|
|
6252
|
+
import { PROTOCOL_END, PROTOCOL_START, parseMarkers as parseMarkers3, readMarkdownFile as readMarkdownFile6 } from "@basou/core";
|
|
6253
|
+
|
|
6254
|
+
// src/lib/context-channel.ts
|
|
6255
|
+
import { homedir as homedir7 } from "os";
|
|
6256
|
+
import { join as join10 } from "path";
|
|
6223
6257
|
import {
|
|
6224
|
-
|
|
6225
|
-
|
|
6258
|
+
ORIENTATION_END,
|
|
6259
|
+
ORIENTATION_START,
|
|
6226
6260
|
parseMarkers as parseMarkers2,
|
|
6227
6261
|
readMarkdownFile as readMarkdownFile5,
|
|
6228
6262
|
removeMarkerSection as removeMarkerSection2
|
|
6229
6263
|
} from "@basou/core";
|
|
6264
|
+
var CODEX_TARGET_PATH = join10(homedir7(), ".codex", "AGENTS.md");
|
|
6265
|
+
var ORIENTATION_MARKERS = { start: ORIENTATION_START, end: ORIENTATION_END };
|
|
6266
|
+
var ORIENTATION_MANAGED_NOTE = "<!-- Managed by basou: 'basou refresh' regenerates everything between the BASOU:ORIENTATION markers with the workspace's current position. This block is transient \u2014 it changes every refresh; do not edit it. -->";
|
|
6267
|
+
function buildTargetBody(existing, block, markers) {
|
|
6268
|
+
const wrapped = `${markers.start}
|
|
6269
|
+
${block}${markers.end}
|
|
6270
|
+
`;
|
|
6271
|
+
if (existing === null || existing === "") return wrapped;
|
|
6272
|
+
const section = parseMarkers2(existing, markers);
|
|
6273
|
+
switch (section.kind) {
|
|
6274
|
+
case "ok":
|
|
6275
|
+
return `${section.before}${markers.start}
|
|
6276
|
+
${block}${markers.end}${section.after}`;
|
|
6277
|
+
case "no_markers": {
|
|
6278
|
+
const sep = existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
6279
|
+
return `${existing}${sep}${wrapped}`;
|
|
6280
|
+
}
|
|
6281
|
+
default:
|
|
6282
|
+
throw new Error(
|
|
6283
|
+
"The basou-managed markers in the target are malformed (a marker is missing, duplicated, or out of order). Fix or remove them, then retry."
|
|
6284
|
+
);
|
|
6285
|
+
}
|
|
6286
|
+
}
|
|
6287
|
+
async function backupOnce(target, existing) {
|
|
6288
|
+
if (existing === null) return;
|
|
6289
|
+
const bak = `${target}.basou-bak`;
|
|
6290
|
+
const already = await readMarkdownFile5(bak);
|
|
6291
|
+
if (already !== null) return;
|
|
6292
|
+
await writeFileDurable(bak, existing);
|
|
6293
|
+
}
|
|
6294
|
+
async function syncMarkerBlock(opts) {
|
|
6295
|
+
const { target, markers, block } = opts;
|
|
6296
|
+
await assertNotSymlink(target);
|
|
6297
|
+
const existing = await readMarkdownFile5(target);
|
|
6298
|
+
const newBody = buildTargetBody(existing, block, markers);
|
|
6299
|
+
if (newBody === existing) return { action: "unchanged" };
|
|
6300
|
+
const hadBlock = existing !== null && parseMarkers2(existing, markers).kind === "ok";
|
|
6301
|
+
const action = hadBlock ? "updated" : "installed";
|
|
6302
|
+
if (opts.dryRun === true) return { action };
|
|
6303
|
+
const recheck = await readMarkdownFile5(target);
|
|
6304
|
+
if (recheck !== existing) {
|
|
6305
|
+
throw new Error(
|
|
6306
|
+
"The target changed during sync; aborting so a concurrent edit is not overwritten. Re-run the command."
|
|
6307
|
+
);
|
|
6308
|
+
}
|
|
6309
|
+
await backupOnce(target, existing);
|
|
6310
|
+
await writeFileDurable(target, newBody);
|
|
6311
|
+
return { action };
|
|
6312
|
+
}
|
|
6313
|
+
function assertNoMarkerLine(body, markers) {
|
|
6314
|
+
for (const line of body.split(/\r?\n/)) {
|
|
6315
|
+
if (line === markers.start || line === markers.end) {
|
|
6316
|
+
throw new Error(
|
|
6317
|
+
"The content contains a basou marker line, which would corrupt the managed block. Remove that line from the source."
|
|
6318
|
+
);
|
|
6319
|
+
}
|
|
6320
|
+
}
|
|
6321
|
+
}
|
|
6322
|
+
async function removeMarkerBlock(opts) {
|
|
6323
|
+
const { target, markers, fileLabel } = opts;
|
|
6324
|
+
await assertNotSymlink(target);
|
|
6325
|
+
const existing = await readMarkdownFile5(target);
|
|
6326
|
+
if (existing === null) return { removed: false };
|
|
6327
|
+
const newBody = removeMarkerSection2(existing, fileLabel, markers);
|
|
6328
|
+
if (newBody === existing) return { removed: false };
|
|
6329
|
+
if (opts.dryRun === true) return { removed: true };
|
|
6330
|
+
const recheck = await readMarkdownFile5(target);
|
|
6331
|
+
if (recheck !== existing) {
|
|
6332
|
+
throw new Error(
|
|
6333
|
+
"The target changed during unsync; aborting so a concurrent edit is not overwritten. Re-run the command."
|
|
6334
|
+
);
|
|
6335
|
+
}
|
|
6336
|
+
await backupOnce(target, existing);
|
|
6337
|
+
await writeFileDurable(target, newBody);
|
|
6338
|
+
return { removed: true };
|
|
6339
|
+
}
|
|
6340
|
+
async function syncOrientationChannel(opts) {
|
|
6341
|
+
assertNoMarkerLine(opts.body, ORIENTATION_MARKERS);
|
|
6342
|
+
const block = `${ORIENTATION_MANAGED_NOTE}
|
|
6343
|
+
|
|
6344
|
+
${opts.body.replace(/\s+$/, "")}
|
|
6345
|
+
`;
|
|
6346
|
+
return syncMarkerBlock({
|
|
6347
|
+
target: opts.target ?? CODEX_TARGET_PATH,
|
|
6348
|
+
markers: ORIENTATION_MARKERS,
|
|
6349
|
+
block,
|
|
6350
|
+
...opts.dryRun === true ? { dryRun: true } : {}
|
|
6351
|
+
});
|
|
6352
|
+
}
|
|
6353
|
+
async function renderOrientationToCodexChannel(opts) {
|
|
6354
|
+
const body = await readMarkdownFile5(opts.orientationPath);
|
|
6355
|
+
if (body === null) return null;
|
|
6356
|
+
const { action } = await syncOrientationChannel({
|
|
6357
|
+
body,
|
|
6358
|
+
...opts.channelPath !== void 0 ? { target: opts.channelPath } : {}
|
|
6359
|
+
});
|
|
6360
|
+
return {
|
|
6361
|
+
action,
|
|
6362
|
+
line: `codex channel: orientation ${action} in ${opts.channelPath ?? "~/.codex/AGENTS.md"}`
|
|
6363
|
+
};
|
|
6364
|
+
}
|
|
6230
6365
|
|
|
6231
6366
|
// src/lib/protocols-config.ts
|
|
6232
|
-
import { homedir as
|
|
6233
|
-
import { isAbsolute as isAbsolute4, join as
|
|
6367
|
+
import { homedir as homedir8 } from "os";
|
|
6368
|
+
import { isAbsolute as isAbsolute4, join as join11, resolve as resolve8 } from "path";
|
|
6234
6369
|
import { readYamlFile as readYamlFile5 } from "@basou/core";
|
|
6235
|
-
var DEFAULT_PROTOCOLS_CONFIG_PATH =
|
|
6236
|
-
var DEFAULT_TARGET_PATH =
|
|
6370
|
+
var DEFAULT_PROTOCOLS_CONFIG_PATH = join11(homedir8(), ".basou", "protocols.yaml");
|
|
6371
|
+
var DEFAULT_TARGET_PATH = join11(homedir8(), ".claude", "CLAUDE.md");
|
|
6237
6372
|
var ALLOWED_TOP_KEYS = /* @__PURE__ */ new Set(["version", "protocols"]);
|
|
6238
6373
|
var ALLOWED_ENTRY_KEYS = /* @__PURE__ */ new Set(["source", "title"]);
|
|
6239
6374
|
function expandTilde3(p) {
|
|
6240
|
-
if (p === "~") return
|
|
6241
|
-
if (p.startsWith("~/")) return
|
|
6375
|
+
if (p === "~") return homedir8();
|
|
6376
|
+
if (p.startsWith("~/")) return join11(homedir8(), p.slice(2));
|
|
6242
6377
|
return p;
|
|
6243
6378
|
}
|
|
6244
6379
|
function isRecord3(value) {
|
|
@@ -6359,13 +6494,7 @@ async function readProtocolSources(entries) {
|
|
|
6359
6494
|
}
|
|
6360
6495
|
throw new Error("Failed to read a protocol source file.", { cause: error });
|
|
6361
6496
|
}
|
|
6362
|
-
|
|
6363
|
-
if (line === PROTOCOL_START || line === PROTOCOL_END) {
|
|
6364
|
-
throw new Error(
|
|
6365
|
-
"A protocol source contains a BASOU:PROTOCOLS marker line, which would corrupt the managed block. Remove that line from the source."
|
|
6366
|
-
);
|
|
6367
|
-
}
|
|
6368
|
-
}
|
|
6497
|
+
assertNoMarkerLine(content, PROTOCOL_MARKERS);
|
|
6369
6498
|
out.push({ entry, content });
|
|
6370
6499
|
}
|
|
6371
6500
|
return out;
|
|
@@ -6382,74 +6511,41 @@ ${body}` : body;
|
|
|
6382
6511
|
${sections.join("\n\n")}
|
|
6383
6512
|
`;
|
|
6384
6513
|
}
|
|
6385
|
-
function buildTargetBody(existing, block) {
|
|
6386
|
-
const wrapped = `${PROTOCOL_START}
|
|
6387
|
-
${block}${PROTOCOL_END}
|
|
6388
|
-
`;
|
|
6389
|
-
if (existing === null || existing === "") return wrapped;
|
|
6390
|
-
const section = parseMarkers2(existing, PROTOCOL_MARKERS);
|
|
6391
|
-
switch (section.kind) {
|
|
6392
|
-
case "ok":
|
|
6393
|
-
return `${section.before}${PROTOCOL_START}
|
|
6394
|
-
${block}${PROTOCOL_END}${section.after}`;
|
|
6395
|
-
case "no_markers": {
|
|
6396
|
-
const sep = existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
6397
|
-
return `${existing}${sep}${wrapped}`;
|
|
6398
|
-
}
|
|
6399
|
-
default:
|
|
6400
|
-
throw new Error(
|
|
6401
|
-
"The BASOU:PROTOCOLS markers in the target are malformed (a marker is missing, duplicated, or out of order). Fix or remove them, then retry."
|
|
6402
|
-
);
|
|
6403
|
-
}
|
|
6404
|
-
}
|
|
6405
|
-
async function backupOnce(target, existing) {
|
|
6406
|
-
if (existing === null) return;
|
|
6407
|
-
const bak = `${target}.basou-bak`;
|
|
6408
|
-
const already = await readMarkdownFile5(bak);
|
|
6409
|
-
if (already !== null) return;
|
|
6410
|
-
await writeFileDurable(bak, existing);
|
|
6411
|
-
}
|
|
6412
6514
|
async function doRunProtocolSync(options) {
|
|
6413
6515
|
const configPath = options.config ?? DEFAULT_PROTOCOLS_CONFIG_PATH;
|
|
6414
6516
|
const target = options.target ?? DEFAULT_TARGET_PATH;
|
|
6415
6517
|
const entries = await loadProtocolsConfig(configPath);
|
|
6416
6518
|
const sources = await readProtocolSources(entries);
|
|
6417
6519
|
const block = buildBlock(sources);
|
|
6418
|
-
await
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6520
|
+
const result = await syncMarkerBlock({
|
|
6521
|
+
target,
|
|
6522
|
+
markers: PROTOCOL_MARKERS,
|
|
6523
|
+
block,
|
|
6524
|
+
...options.dryRun === true ? { dryRun: true } : {}
|
|
6525
|
+
});
|
|
6526
|
+
if (result.action === "unchanged") {
|
|
6422
6527
|
console.log(`The basou:protocols block is already up to date (${entries.length} protocol(s)).`);
|
|
6423
6528
|
return;
|
|
6424
6529
|
}
|
|
6425
|
-
const hadBlock = existing !== null && parseMarkers2(existing, PROTOCOL_MARKERS).kind === "ok";
|
|
6426
6530
|
if (options.dryRun === true) {
|
|
6427
6531
|
console.log(
|
|
6428
|
-
`[dry-run] Would ${
|
|
6532
|
+
`[dry-run] Would ${result.action === "updated" ? "update" : "install"} the basou:protocols block (${entries.length} protocol(s)).`
|
|
6429
6533
|
);
|
|
6430
6534
|
for (const { entry } of sources) {
|
|
6431
6535
|
console.log(` - ${entry.title ?? entry.source}`);
|
|
6432
6536
|
}
|
|
6433
6537
|
return;
|
|
6434
6538
|
}
|
|
6435
|
-
const recheck = await readMarkdownFile5(target);
|
|
6436
|
-
if (recheck !== existing) {
|
|
6437
|
-
throw new Error(
|
|
6438
|
-
"The target changed during sync; aborting so a concurrent edit is not overwritten. Re-run 'basou protocol sync'."
|
|
6439
|
-
);
|
|
6440
|
-
}
|
|
6441
|
-
await backupOnce(target, existing);
|
|
6442
|
-
await writeFileDurable(target, newBody);
|
|
6443
6539
|
console.log(
|
|
6444
|
-
`${
|
|
6540
|
+
`${result.action === "updated" ? "Updated" : "Installed"} the basou:protocols block in the global CLAUDE.md (${entries.length} protocol(s)).`
|
|
6445
6541
|
);
|
|
6446
6542
|
}
|
|
6447
6543
|
async function doRunProtocolList(options) {
|
|
6448
6544
|
const configPath = options.config ?? DEFAULT_PROTOCOLS_CONFIG_PATH;
|
|
6449
6545
|
const target = options.target ?? DEFAULT_TARGET_PATH;
|
|
6450
6546
|
const entries = await loadProtocolsConfig(configPath);
|
|
6451
|
-
const existing = await
|
|
6452
|
-
const installed = existing !== null &&
|
|
6547
|
+
const existing = await readMarkdownFile6(target);
|
|
6548
|
+
const installed = existing !== null && parseMarkers3(existing, PROTOCOL_MARKERS).kind === "ok";
|
|
6453
6549
|
console.log(`Declared protocols (${entries.length}):`);
|
|
6454
6550
|
for (const entry of entries) {
|
|
6455
6551
|
console.log(` - ${entry.title ?? entry.source}`);
|
|
@@ -6458,14 +6554,13 @@ async function doRunProtocolList(options) {
|
|
|
6458
6554
|
}
|
|
6459
6555
|
async function doRunProtocolUnsync(options) {
|
|
6460
6556
|
const target = options.target ?? DEFAULT_TARGET_PATH;
|
|
6461
|
-
await
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
}
|
|
6467
|
-
|
|
6468
|
-
if (newBody === existing) {
|
|
6557
|
+
const result = await removeMarkerBlock({
|
|
6558
|
+
target,
|
|
6559
|
+
markers: PROTOCOL_MARKERS,
|
|
6560
|
+
fileLabel: "CLAUDE.md",
|
|
6561
|
+
...options.dryRun === true ? { dryRun: true } : {}
|
|
6562
|
+
});
|
|
6563
|
+
if (!result.removed) {
|
|
6469
6564
|
console.log("No basou:protocols block found; nothing removed.");
|
|
6470
6565
|
return;
|
|
6471
6566
|
}
|
|
@@ -6473,14 +6568,6 @@ async function doRunProtocolUnsync(options) {
|
|
|
6473
6568
|
console.log("[dry-run] Would remove the basou:protocols block from the global CLAUDE.md.");
|
|
6474
6569
|
return;
|
|
6475
6570
|
}
|
|
6476
|
-
const recheck = await readMarkdownFile5(target);
|
|
6477
|
-
if (recheck !== existing) {
|
|
6478
|
-
throw new Error(
|
|
6479
|
-
"The target changed during unsync; aborting so a concurrent edit is not overwritten. Re-run 'basou protocol unsync'."
|
|
6480
|
-
);
|
|
6481
|
-
}
|
|
6482
|
-
await backupOnce(target, existing);
|
|
6483
|
-
await writeFileDurable(target, newBody);
|
|
6484
6571
|
console.log("Removed the basou:protocols block from the global CLAUDE.md.");
|
|
6485
6572
|
}
|
|
6486
6573
|
|
|
@@ -6490,16 +6577,16 @@ import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
|
|
|
6490
6577
|
|
|
6491
6578
|
// src/commands/refresh-watch.ts
|
|
6492
6579
|
import { readdir as readdir2, stat as stat5 } from "fs/promises";
|
|
6493
|
-
import { homedir as
|
|
6494
|
-
import { join as
|
|
6580
|
+
import { homedir as homedir9 } from "os";
|
|
6581
|
+
import { join as join12 } from "path";
|
|
6495
6582
|
import { findErrorCode as findErrorCode8 } from "@basou/core";
|
|
6496
6583
|
var DEFAULT_WATCH_INTERVAL_SEC = 30;
|
|
6497
6584
|
var MIN_WATCH_INTERVAL_SEC = 5;
|
|
6498
6585
|
var MAX_WATCH_INTERVAL_SEC = 86400;
|
|
6499
6586
|
function watchedRoots(ctx) {
|
|
6500
6587
|
return [
|
|
6501
|
-
ctx.codexSessionsDir ??
|
|
6502
|
-
ctx.claudeProjectsDir ??
|
|
6588
|
+
ctx.codexSessionsDir ?? join12(homedir9(), ".codex", "sessions"),
|
|
6589
|
+
ctx.claudeProjectsDir ?? join12(homedir9(), ".claude", "projects")
|
|
6503
6590
|
];
|
|
6504
6591
|
}
|
|
6505
6592
|
async function scanSourceLogs(roots) {
|
|
@@ -6513,7 +6600,7 @@ async function scanSourceLogs(roots) {
|
|
|
6513
6600
|
throw new Error("Failed to read a source log directory", { cause: error });
|
|
6514
6601
|
}
|
|
6515
6602
|
for (const entry of entries) {
|
|
6516
|
-
const full =
|
|
6603
|
+
const full = join12(dir, entry.name);
|
|
6517
6604
|
if (entry.isDirectory()) {
|
|
6518
6605
|
await walk(full);
|
|
6519
6606
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
@@ -6620,19 +6707,19 @@ function parseInterval(value) {
|
|
|
6620
6707
|
return seconds;
|
|
6621
6708
|
}
|
|
6622
6709
|
function abortableSleep(ms, signal) {
|
|
6623
|
-
return new Promise((
|
|
6710
|
+
return new Promise((resolve13) => {
|
|
6624
6711
|
if (signal.aborted) {
|
|
6625
|
-
|
|
6712
|
+
resolve13();
|
|
6626
6713
|
return;
|
|
6627
6714
|
}
|
|
6628
6715
|
let timer;
|
|
6629
6716
|
const onAbort = () => {
|
|
6630
6717
|
clearTimeout(timer);
|
|
6631
|
-
|
|
6718
|
+
resolve13();
|
|
6632
6719
|
};
|
|
6633
6720
|
timer = setTimeout(() => {
|
|
6634
6721
|
signal.removeEventListener("abort", onAbort);
|
|
6635
|
-
|
|
6722
|
+
resolve13();
|
|
6636
6723
|
}, ms);
|
|
6637
6724
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
6638
6725
|
});
|
|
@@ -6685,7 +6772,7 @@ async function doRunRefreshPortfolio(options, ctx) {
|
|
|
6685
6772
|
for (const ws of workspaces) {
|
|
6686
6773
|
const label = ws.label ?? ws.path;
|
|
6687
6774
|
try {
|
|
6688
|
-
const result = await computeRefresh(
|
|
6775
|
+
const { result } = await computeRefresh(
|
|
6689
6776
|
{ ...options, portfolio: false },
|
|
6690
6777
|
{ ...ctx, cwd: ws.path }
|
|
6691
6778
|
);
|
|
@@ -6754,7 +6841,7 @@ async function computeRefresh(options, ctx) {
|
|
|
6754
6841
|
const paths = basouPaths11(repositoryRoot);
|
|
6755
6842
|
await assertWorkspaceInitialized8(paths.root);
|
|
6756
6843
|
const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
6757
|
-
|
|
6844
|
+
const result = await refreshAll({
|
|
6758
6845
|
options: {
|
|
6759
6846
|
...options.project !== void 0 && options.project.length > 0 ? { project: options.project } : {},
|
|
6760
6847
|
...options.force === true ? { force: true } : {},
|
|
@@ -6766,16 +6853,30 @@ async function computeRefresh(options, ctx) {
|
|
|
6766
6853
|
paths,
|
|
6767
6854
|
nowIso
|
|
6768
6855
|
});
|
|
6856
|
+
return { result, paths };
|
|
6769
6857
|
}
|
|
6770
6858
|
async function doRunRefresh(options, ctx) {
|
|
6771
|
-
const result = await computeRefresh(options, ctx);
|
|
6859
|
+
const { result, paths } = await computeRefresh(options, ctx);
|
|
6860
|
+
const channelLine = options.dryRun === true ? null : await syncCodexOrientationChannel(paths, ctx.codexChannelPath);
|
|
6772
6861
|
if (options.json === true) {
|
|
6773
6862
|
console.log(JSON.stringify(result));
|
|
6774
6863
|
} else {
|
|
6775
6864
|
printRefreshSummary(result);
|
|
6865
|
+
if (channelLine !== null) console.log(channelLine);
|
|
6776
6866
|
}
|
|
6777
6867
|
return result;
|
|
6778
6868
|
}
|
|
6869
|
+
async function syncCodexOrientationChannel(paths, channelPath) {
|
|
6870
|
+
try {
|
|
6871
|
+
const rendered = await renderOrientationToCodexChannel({
|
|
6872
|
+
orientationPath: paths.files.orientation,
|
|
6873
|
+
...channelPath !== void 0 ? { channelPath } : {}
|
|
6874
|
+
});
|
|
6875
|
+
return rendered === null ? null : rendered.line;
|
|
6876
|
+
} catch (error) {
|
|
6877
|
+
return `codex channel skipped: ${error instanceof Error ? error.message : String(error)}`;
|
|
6878
|
+
}
|
|
6879
|
+
}
|
|
6779
6880
|
function describeImport(outcome) {
|
|
6780
6881
|
if (outcome.status === "skipped") {
|
|
6781
6882
|
return `${outcome.adapter}: skipped (${outcome.reason})`;
|
|
@@ -6908,9 +7009,195 @@ async function assertWorkspaceInitialized9(basouRoot) {
|
|
|
6908
7009
|
}
|
|
6909
7010
|
}
|
|
6910
7011
|
|
|
6911
|
-
// src/commands/review
|
|
7012
|
+
// src/commands/review.ts
|
|
7013
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
7014
|
+
import { homedir as homedir10 } from "os";
|
|
7015
|
+
import { resolve as resolve10 } from "path";
|
|
6912
7016
|
import {
|
|
7017
|
+
assertBasouRootSafe as assertBasouRootSafe11,
|
|
6913
7018
|
basouPaths as basouPaths13,
|
|
7019
|
+
buildReviewRecordedEvent,
|
|
7020
|
+
buildReviewRecordLabel,
|
|
7021
|
+
createAdHocSessionWithEvent as createAdHocSessionWithEvent3,
|
|
7022
|
+
findErrorCode as findErrorCode11,
|
|
7023
|
+
parseReviewRecordInput,
|
|
7024
|
+
readManifest as readManifest7,
|
|
7025
|
+
sanitizePath as sanitizePath2
|
|
7026
|
+
} from "@basou/core";
|
|
7027
|
+
function registerReviewCommand(program2) {
|
|
7028
|
+
const review = program2.command("review").description("Record reviews that ran (the durable signal a review happened)");
|
|
7029
|
+
review.command("record").description(
|
|
7030
|
+
"Record that a review ran, from a JSON object (stdin or --file). The in-loop agent runs an adversarial / second-opinion review and pipes a description -- reviewer, target, optional verdict/findings/blocked -- and basou writes one review_recorded event deterministically."
|
|
7031
|
+
).option("--file <path>", "Read the JSON object from a file instead of stdin").option("--dry-run", "Validate and preview the review without writing it").option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").addHelpText("after", REVIEW_RECORD_HELP).action(async (options) => {
|
|
7032
|
+
await runReviewRecord(options);
|
|
7033
|
+
});
|
|
7034
|
+
}
|
|
7035
|
+
var REVIEW_RECORD_HELP = `
|
|
7036
|
+
Input format (a single JSON object describing one review):
|
|
7037
|
+
{
|
|
7038
|
+
"reviewer": "codex",
|
|
7039
|
+
"target": "working-tree",
|
|
7040
|
+
"verdict": "needs-attention",
|
|
7041
|
+
"findings": [
|
|
7042
|
+
{ "title": "Off-by-one in pager", "severity": "medium", "location": "src/page.ts:42", "summary": "..." }
|
|
7043
|
+
],
|
|
7044
|
+
"blocked": [
|
|
7045
|
+
{ "title": "Reviewer wanted to drop the singleton", "reason": "design-reversal", "why": "Settled in decision_X" }
|
|
7046
|
+
]
|
|
7047
|
+
}
|
|
7048
|
+
|
|
7049
|
+
Only "reviewer" and "target" are required; verdict / findings / blocked are
|
|
7050
|
+
optional. Record blocked findings (spec-deviation / design-reversal) here so the
|
|
7051
|
+
adversarial-review protocol's "always report what you blocked" becomes a durable
|
|
7052
|
+
trail artifact -- an explicit empty "blocked": [] is encouraged to record that
|
|
7053
|
+
you blocked nothing. The review is written into one ad-hoc session timestamped
|
|
7054
|
+
now. Run from a workspace-view directory and it resolves to the planning repo,
|
|
7055
|
+
like 'basou decision capture' / 'basou note'.
|
|
7056
|
+
|
|
7057
|
+
Example (heredoc on stdin):
|
|
7058
|
+
basou review record <<'JSON'
|
|
7059
|
+
{ "reviewer": "codex", "target": "working-tree", "verdict": "pass", "blocked": [] }
|
|
7060
|
+
JSON
|
|
7061
|
+
`;
|
|
7062
|
+
async function runReviewRecord(options, ctx = {}) {
|
|
7063
|
+
try {
|
|
7064
|
+
await doRunReviewRecord(options, ctx);
|
|
7065
|
+
} catch (error) {
|
|
7066
|
+
renderCliError(error, {
|
|
7067
|
+
verbose: isVerbose(options),
|
|
7068
|
+
classifiers: [failedToFinalizeClassifier]
|
|
7069
|
+
});
|
|
7070
|
+
process.exitCode = 1;
|
|
7071
|
+
}
|
|
7072
|
+
}
|
|
7073
|
+
async function doRunReviewRecord(options, ctx) {
|
|
7074
|
+
const cwd = ctx.cwd ?? process.cwd();
|
|
7075
|
+
const repositoryRoot = await resolveBasouRootForCommand(cwd, "review record");
|
|
7076
|
+
const paths = basouPaths13(repositoryRoot);
|
|
7077
|
+
await assertWorkspaceInitialized10(paths.root);
|
|
7078
|
+
const raw = await readReviewInput(options, ctx);
|
|
7079
|
+
const review = parseReviewRecordInput(raw);
|
|
7080
|
+
if (options.dryRun === true) {
|
|
7081
|
+
printReviewPreview(options, review);
|
|
7082
|
+
return;
|
|
7083
|
+
}
|
|
7084
|
+
const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
|
|
7085
|
+
const occurredAt = now.toISOString();
|
|
7086
|
+
const manifest = await readManifest7(paths);
|
|
7087
|
+
const invocationArgs = options.file !== void 0 ? [
|
|
7088
|
+
"--file",
|
|
7089
|
+
sanitizePath2(resolve10(cwd, options.file), {
|
|
7090
|
+
workingDirectory: repositoryRoot,
|
|
7091
|
+
homedir: homedir10()
|
|
7092
|
+
})
|
|
7093
|
+
] : [];
|
|
7094
|
+
const adHoc = await createAdHocSessionWithEvent3({
|
|
7095
|
+
paths,
|
|
7096
|
+
manifest,
|
|
7097
|
+
label: buildReviewRecordLabel(review),
|
|
7098
|
+
occurredAt,
|
|
7099
|
+
sessionSource: "human",
|
|
7100
|
+
workingDirectory: repositoryRoot,
|
|
7101
|
+
invocation: { command: "basou review record", args: invocationArgs },
|
|
7102
|
+
targetEventBuilders: [
|
|
7103
|
+
(sessionId, eventId) => buildReviewRecordedEvent({ eventId, sessionId, occurredAt, review })
|
|
7104
|
+
]
|
|
7105
|
+
});
|
|
7106
|
+
printReviewResult(options, {
|
|
7107
|
+
sessionId: adHoc.sessionId,
|
|
7108
|
+
eventId: adHoc.targetEventIds[0],
|
|
7109
|
+
review
|
|
7110
|
+
});
|
|
7111
|
+
}
|
|
7112
|
+
async function readReviewInput(options, ctx) {
|
|
7113
|
+
if (options.file !== void 0) {
|
|
7114
|
+
try {
|
|
7115
|
+
return await readFile5(options.file, "utf8");
|
|
7116
|
+
} catch (error) {
|
|
7117
|
+
if (findErrorCode11(error, "ENOENT")) {
|
|
7118
|
+
throw new Error(`Input file not found: ${options.file}`);
|
|
7119
|
+
}
|
|
7120
|
+
throw error;
|
|
7121
|
+
}
|
|
7122
|
+
}
|
|
7123
|
+
if (ctx.readInput !== void 0) {
|
|
7124
|
+
return await ctx.readInput();
|
|
7125
|
+
}
|
|
7126
|
+
if (process.stdin.isTTY === true) {
|
|
7127
|
+
throw new Error(NO_INPUT_HINT2);
|
|
7128
|
+
}
|
|
7129
|
+
return await readStdinToEnd2();
|
|
7130
|
+
}
|
|
7131
|
+
async function readStdinToEnd2() {
|
|
7132
|
+
const chunks = [];
|
|
7133
|
+
for await (const chunk of process.stdin) {
|
|
7134
|
+
chunks.push(Buffer.from(chunk));
|
|
7135
|
+
}
|
|
7136
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
7137
|
+
}
|
|
7138
|
+
var NO_INPUT_HINT2 = "No input: pipe a JSON object describing the review to stdin or pass --file <path>.";
|
|
7139
|
+
function reviewToPayload(review) {
|
|
7140
|
+
const payload = {
|
|
7141
|
+
reviewer: review.reviewer,
|
|
7142
|
+
target: review.target
|
|
7143
|
+
};
|
|
7144
|
+
if (review.verdict !== void 0) payload.verdict = review.verdict;
|
|
7145
|
+
if (review.findings !== void 0) payload.findings = review.findings;
|
|
7146
|
+
if (review.blocked !== void 0) payload.blocked = review.blocked;
|
|
7147
|
+
return payload;
|
|
7148
|
+
}
|
|
7149
|
+
function reviewSummaryLine(review) {
|
|
7150
|
+
const parts = [];
|
|
7151
|
+
if (review.verdict !== void 0) parts.push(`verdict: ${review.verdict}`);
|
|
7152
|
+
if (review.findings !== void 0) {
|
|
7153
|
+
parts.push(`${review.findings.length} finding${review.findings.length === 1 ? "" : "s"}`);
|
|
7154
|
+
}
|
|
7155
|
+
if (review.blocked !== void 0) {
|
|
7156
|
+
parts.push(`${review.blocked.length} blocked`);
|
|
7157
|
+
}
|
|
7158
|
+
return parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
7159
|
+
}
|
|
7160
|
+
function printReviewPreview(options, review) {
|
|
7161
|
+
if (options.json === true) {
|
|
7162
|
+
console.log(JSON.stringify({ dry_run: true, review: reviewToPayload(review) }));
|
|
7163
|
+
return;
|
|
7164
|
+
}
|
|
7165
|
+
console.log(
|
|
7166
|
+
`Would record review by ${review.reviewer} of ${review.target}${reviewSummaryLine(review)} (dry run; nothing written).`
|
|
7167
|
+
);
|
|
7168
|
+
}
|
|
7169
|
+
function printReviewResult(options, result) {
|
|
7170
|
+
const sid = shortSessionId(result.sessionId);
|
|
7171
|
+
if (options.json === true) {
|
|
7172
|
+
console.log(
|
|
7173
|
+
JSON.stringify({
|
|
7174
|
+
mode: "ad-hoc",
|
|
7175
|
+
session_id: result.sessionId,
|
|
7176
|
+
session_status: "completed",
|
|
7177
|
+
event_id: result.eventId,
|
|
7178
|
+
review: reviewToPayload(result.review)
|
|
7179
|
+
})
|
|
7180
|
+
);
|
|
7181
|
+
return;
|
|
7182
|
+
}
|
|
7183
|
+
console.log(
|
|
7184
|
+
`Recorded review by ${result.review.reviewer} of ${result.review.target}${reviewSummaryLine(result.review)} in ad-hoc session ${sid}.`
|
|
7185
|
+
);
|
|
7186
|
+
}
|
|
7187
|
+
async function assertWorkspaceInitialized10(basouRoot) {
|
|
7188
|
+
try {
|
|
7189
|
+
await assertBasouRootSafe11(basouRoot);
|
|
7190
|
+
} catch (error) {
|
|
7191
|
+
if (findErrorCode11(error, "ENOENT")) {
|
|
7192
|
+
throw new Error("Workspace not initialized. Run 'basou init' first.");
|
|
7193
|
+
}
|
|
7194
|
+
throw error;
|
|
7195
|
+
}
|
|
7196
|
+
}
|
|
7197
|
+
|
|
7198
|
+
// src/commands/review-gaps.ts
|
|
7199
|
+
import {
|
|
7200
|
+
basouPaths as basouPaths14,
|
|
6914
7201
|
findReviewGaps
|
|
6915
7202
|
} from "@basou/core";
|
|
6916
7203
|
import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
|
|
@@ -6951,7 +7238,7 @@ async function runReviewGaps(options, ctx = {}) {
|
|
|
6951
7238
|
async function doRunReviewGaps(options, ctx) {
|
|
6952
7239
|
const cwd = ctx.cwd ?? process.cwd();
|
|
6953
7240
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "review-gaps");
|
|
6954
|
-
const paths =
|
|
7241
|
+
const paths = basouPaths14(repositoryRoot);
|
|
6955
7242
|
const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
6956
7243
|
const summary = await findReviewGaps({
|
|
6957
7244
|
paths,
|
|
@@ -7034,23 +7321,25 @@ function renderReviewGaps(summary) {
|
|
|
7034
7321
|
|
|
7035
7322
|
// src/commands/run.ts
|
|
7036
7323
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
7037
|
-
import { homedir as
|
|
7038
|
-
import { join as
|
|
7324
|
+
import { homedir as homedir11 } from "os";
|
|
7325
|
+
import { join as join13 } from "path";
|
|
7039
7326
|
import {
|
|
7040
7327
|
acquireLock as acquireLock5,
|
|
7041
|
-
assertBasouRootSafe as
|
|
7042
|
-
basouPaths as
|
|
7328
|
+
assertBasouRootSafe as assertBasouRootSafe12,
|
|
7329
|
+
basouPaths as basouPaths15,
|
|
7043
7330
|
ChildProcessRunner as ChildProcessRunner2,
|
|
7044
7331
|
claudeCodeAdapterMetadata,
|
|
7332
|
+
codexAdapterMetadata,
|
|
7045
7333
|
appendChainedEvent as coreAppendChainedEvent2,
|
|
7046
7334
|
finalizeSessionYaml as finalizeSessionYaml2,
|
|
7047
7335
|
getDiff,
|
|
7048
7336
|
getSnapshot as getSnapshot2,
|
|
7049
7337
|
overwriteYamlFile as overwriteYamlFile2,
|
|
7050
7338
|
prefixedUlid as prefixedUlid4,
|
|
7051
|
-
readManifest as
|
|
7339
|
+
readManifest as readManifest8,
|
|
7052
7340
|
readYamlFile as readYamlFile6,
|
|
7053
7341
|
resolveClaudeCodeCommand,
|
|
7342
|
+
resolveCodexCommand,
|
|
7054
7343
|
resolveRepositoryRoot as resolveRepositoryRoot10,
|
|
7055
7344
|
SessionSchema as SessionSchema2,
|
|
7056
7345
|
sanitizeRelatedFiles,
|
|
@@ -7058,50 +7347,72 @@ import {
|
|
|
7058
7347
|
writeYamlFile as writeYamlFile2
|
|
7059
7348
|
} from "@basou/core";
|
|
7060
7349
|
function registerRunCommand(program2, ctx = {}) {
|
|
7061
|
-
const runCommand =
|
|
7062
|
-
|
|
7350
|
+
const runCommand = addRunOptions(
|
|
7351
|
+
program2.command("run").description("Run an AI coding tool through Basou as a tracked session").enablePositionalOptions()
|
|
7352
|
+
);
|
|
7353
|
+
const dispatch = async (args, options, command, run) => {
|
|
7063
7354
|
const parentOptions = command.parent?.opts() ?? {};
|
|
7064
7355
|
const snapshotOn = parentOptions.snapshot !== false && options.snapshot !== false;
|
|
7065
|
-
const merged = {
|
|
7066
|
-
...parentOptions,
|
|
7067
|
-
...options,
|
|
7068
|
-
snapshot: snapshotOn
|
|
7069
|
-
};
|
|
7356
|
+
const merged = { ...parentOptions, ...options, snapshot: snapshotOn };
|
|
7070
7357
|
try {
|
|
7071
|
-
const exitCode = await
|
|
7358
|
+
const exitCode = await run(args, merged, ctx);
|
|
7072
7359
|
process.exit(exitCode);
|
|
7073
7360
|
} catch (error) {
|
|
7074
7361
|
renderCliError(error, { verbose: isVerbose(merged) });
|
|
7075
7362
|
process.exit(1);
|
|
7076
7363
|
}
|
|
7364
|
+
};
|
|
7365
|
+
addRunOptions(runCommand.command("claude-code [args...]")).description("Run Claude Code CLI as a Basou-tracked session").passThroughOptions().action(
|
|
7366
|
+
(args, options, command) => dispatch(args, options, command, runClaudeCode)
|
|
7367
|
+
);
|
|
7368
|
+
addRunOptions(runCommand.command("codex [args...]")).description("Run the Codex CLI as a Basou-tracked session").passThroughOptions().action(
|
|
7369
|
+
(args, options, command) => dispatch(args, options, command, runCodex)
|
|
7370
|
+
);
|
|
7371
|
+
}
|
|
7372
|
+
function addRunOptions(command) {
|
|
7373
|
+
return command.option("--no-snapshot", "Skip git_snapshot before/after the session").option("--cwd <path>", "Run from a Basou root other than process.cwd()").option("-v, --verbose", "Show error causes");
|
|
7374
|
+
}
|
|
7375
|
+
function runClaudeCode(args, options, ctx = {}) {
|
|
7376
|
+
return runTrackedTool(args, options, ctx, {
|
|
7377
|
+
resolveCommand: ctx.resolveCommand ?? resolveClaudeCodeCommand,
|
|
7378
|
+
metadata: claudeCodeAdapterMetadata
|
|
7379
|
+
});
|
|
7380
|
+
}
|
|
7381
|
+
function runCodex(args, options, ctx = {}) {
|
|
7382
|
+
return runTrackedTool(args, options, ctx, {
|
|
7383
|
+
resolveCommand: ctx.resolveCodexCommand ?? resolveCodexCommand,
|
|
7384
|
+
metadata: codexAdapterMetadata,
|
|
7385
|
+
transformArgs: (a) => ["-c", "shell_environment_policy.inherit=all", ...a],
|
|
7386
|
+
preSpawn: syncCodexOrientationChannelPreSpawn
|
|
7077
7387
|
});
|
|
7078
7388
|
}
|
|
7079
|
-
async function
|
|
7389
|
+
async function runTrackedTool(args, options, ctx, adapter) {
|
|
7080
7390
|
const runner = ctx.runner ?? new ChildProcessRunner2();
|
|
7081
7391
|
const now = ctx.now ?? (() => /* @__PURE__ */ new Date());
|
|
7082
|
-
const resolveCommand = ctx.resolveCommand ?? resolveClaudeCodeCommand;
|
|
7083
7392
|
const getDiffFn = ctx.getDiff ?? getDiff;
|
|
7084
|
-
const { command } = await resolveCommand();
|
|
7393
|
+
const { command } = await adapter.resolveCommand();
|
|
7394
|
+
const childArgs = adapter.transformArgs ? adapter.transformArgs(args) : args;
|
|
7085
7395
|
const cwd = options.cwd ?? process.cwd();
|
|
7086
7396
|
const repoRoot = await resolveRepositoryRootForRun(cwd);
|
|
7087
|
-
const paths =
|
|
7088
|
-
await
|
|
7089
|
-
const manifest = await
|
|
7397
|
+
const paths = basouPaths15(repoRoot);
|
|
7398
|
+
await assertBasouRootSafe12(paths.root);
|
|
7399
|
+
const manifest = await readManifest8(paths);
|
|
7090
7400
|
const sessionId = prefixedUlid4("ses");
|
|
7091
|
-
const sessionDir =
|
|
7401
|
+
const sessionDir = join13(paths.sessions, sessionId);
|
|
7092
7402
|
await mkdir2(sessionDir, { recursive: true });
|
|
7093
7403
|
const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
|
|
7094
7404
|
await coreAppendChainedEvent2(paths, sessionId, event);
|
|
7095
7405
|
});
|
|
7096
7406
|
const startedAt = now().toISOString();
|
|
7097
|
-
const sessionYamlPath =
|
|
7407
|
+
const sessionYamlPath = join13(sessionDir, "session.yaml");
|
|
7098
7408
|
const session = buildInitialSession2({
|
|
7099
7409
|
id: sessionId,
|
|
7100
7410
|
command,
|
|
7101
|
-
args,
|
|
7411
|
+
args: childArgs,
|
|
7102
7412
|
cwd: repoRoot,
|
|
7103
7413
|
workspaceId: manifest.workspace.id,
|
|
7104
|
-
startedAt
|
|
7414
|
+
startedAt,
|
|
7415
|
+
source: adapter.metadata
|
|
7105
7416
|
});
|
|
7106
7417
|
await writeYamlFile2(sessionYamlPath, session);
|
|
7107
7418
|
await appendEvent(sessionDir, {
|
|
@@ -7110,7 +7421,7 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
7110
7421
|
id: prefixedUlid4("evt"),
|
|
7111
7422
|
session_id: sessionId,
|
|
7112
7423
|
occurred_at: startedAt,
|
|
7113
|
-
source:
|
|
7424
|
+
source: adapter.metadata.kind
|
|
7114
7425
|
});
|
|
7115
7426
|
let preSnapshot = null;
|
|
7116
7427
|
if (options.snapshot !== false) {
|
|
@@ -7123,7 +7434,7 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
7123
7434
|
id: prefixedUlid4("evt"),
|
|
7124
7435
|
session_id: sessionId,
|
|
7125
7436
|
occurred_at: runningAt,
|
|
7126
|
-
source:
|
|
7437
|
+
source: adapter.metadata.kind,
|
|
7127
7438
|
from: "initialized",
|
|
7128
7439
|
to: "running"
|
|
7129
7440
|
});
|
|
@@ -7157,10 +7468,14 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
7157
7468
|
process.on("SIGTERM", onSigTerm);
|
|
7158
7469
|
process.on("exit", exitHandler);
|
|
7159
7470
|
ctx.onExitHookInstalled?.(exitHandler);
|
|
7471
|
+
if (adapter.preSpawn !== void 0) {
|
|
7472
|
+
const line = await adapter.preSpawn(cwd, ctx);
|
|
7473
|
+
if (line !== null) console.log(line);
|
|
7474
|
+
}
|
|
7160
7475
|
let result;
|
|
7161
7476
|
try {
|
|
7162
7477
|
try {
|
|
7163
|
-
result = await runner.run(command,
|
|
7478
|
+
result = await runner.run(command, childArgs, {
|
|
7164
7479
|
cwd: repoRoot,
|
|
7165
7480
|
capture: "none",
|
|
7166
7481
|
signal: controller.signal,
|
|
@@ -7171,10 +7486,11 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
7171
7486
|
} catch (spawnError) {
|
|
7172
7487
|
await finalizeSessionAsFailed2(paths, sessionDir, sessionId, appendEvent, {
|
|
7173
7488
|
command,
|
|
7174
|
-
args,
|
|
7489
|
+
args: childArgs,
|
|
7175
7490
|
cwd: repoRoot,
|
|
7176
7491
|
occurredAt: now().toISOString(),
|
|
7177
|
-
signalReceived
|
|
7492
|
+
signalReceived,
|
|
7493
|
+
sourceKind: adapter.metadata.kind
|
|
7178
7494
|
});
|
|
7179
7495
|
throw spawnError;
|
|
7180
7496
|
}
|
|
@@ -7193,7 +7509,7 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
7193
7509
|
occurred_at: endedAt,
|
|
7194
7510
|
source: "terminal-recording",
|
|
7195
7511
|
command,
|
|
7196
|
-
args,
|
|
7512
|
+
args: childArgs,
|
|
7197
7513
|
cwd: repoRoot,
|
|
7198
7514
|
exit_code: result.exit_code,
|
|
7199
7515
|
...result.signal !== null ? { signal: result.signal } : {},
|
|
@@ -7220,7 +7536,7 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
7220
7536
|
const rawRelated = computeRelatedFiles(preSnapshot, postSnapshot, diff);
|
|
7221
7537
|
const relatedFiles = sanitizeRelatedFiles(rawRelated, {
|
|
7222
7538
|
workingDirectory: repoRoot,
|
|
7223
|
-
homedir:
|
|
7539
|
+
homedir: homedir11()
|
|
7224
7540
|
}).sanitized;
|
|
7225
7541
|
const finalStatus = decideFinalStatus2(result, signalReceived);
|
|
7226
7542
|
await appendEvent(sessionDir, {
|
|
@@ -7229,7 +7545,7 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
7229
7545
|
id: prefixedUlid4("evt"),
|
|
7230
7546
|
session_id: sessionId,
|
|
7231
7547
|
occurred_at: endedAt,
|
|
7232
|
-
source:
|
|
7548
|
+
source: adapter.metadata.kind,
|
|
7233
7549
|
from: "running",
|
|
7234
7550
|
to: finalStatus
|
|
7235
7551
|
});
|
|
@@ -7239,7 +7555,7 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
7239
7555
|
id: prefixedUlid4("evt"),
|
|
7240
7556
|
session_id: sessionId,
|
|
7241
7557
|
occurred_at: endedAt,
|
|
7242
|
-
source:
|
|
7558
|
+
source: adapter.metadata.kind,
|
|
7243
7559
|
...result.exit_code !== null ? { exit_code: result.exit_code } : {}
|
|
7244
7560
|
});
|
|
7245
7561
|
await finalizeSessionYaml2(paths, sessionId, (s) => {
|
|
@@ -7361,10 +7677,10 @@ function buildInitialSession2(input) {
|
|
|
7361
7677
|
label: `basou run ${cmdline} (${input.startedAt})`,
|
|
7362
7678
|
task_id: null,
|
|
7363
7679
|
workspace_id: input.workspaceId,
|
|
7364
|
-
source: { ...
|
|
7680
|
+
source: { ...input.source },
|
|
7365
7681
|
started_at: input.startedAt,
|
|
7366
7682
|
status: "initialized",
|
|
7367
|
-
working_directory: sanitizeWorkingDirectory2(input.cwd, { homedir:
|
|
7683
|
+
working_directory: sanitizeWorkingDirectory2(input.cwd, { homedir: homedir11() }),
|
|
7368
7684
|
invocation: {
|
|
7369
7685
|
command: input.command,
|
|
7370
7686
|
args: [...input.args],
|
|
@@ -7404,7 +7720,7 @@ async function finalizeSessionAsFailed2(paths, sessionDir, sessionId, appendEven
|
|
|
7404
7720
|
id: prefixedUlid4("evt"),
|
|
7405
7721
|
session_id: sessionId,
|
|
7406
7722
|
occurred_at: ctx.occurredAt,
|
|
7407
|
-
source:
|
|
7723
|
+
source: ctx.sourceKind,
|
|
7408
7724
|
from: "running",
|
|
7409
7725
|
to: "failed"
|
|
7410
7726
|
});
|
|
@@ -7414,7 +7730,7 @@ async function finalizeSessionAsFailed2(paths, sessionDir, sessionId, appendEven
|
|
|
7414
7730
|
id: prefixedUlid4("evt"),
|
|
7415
7731
|
session_id: sessionId,
|
|
7416
7732
|
occurred_at: ctx.occurredAt,
|
|
7417
|
-
source:
|
|
7733
|
+
source: ctx.sourceKind
|
|
7418
7734
|
});
|
|
7419
7735
|
await finalizeSessionYaml2(paths, sessionId, (s) => {
|
|
7420
7736
|
s.session.status = "failed";
|
|
@@ -7434,21 +7750,34 @@ async function resolveRepositoryRootForRun(cwd) {
|
|
|
7434
7750
|
throw error;
|
|
7435
7751
|
}
|
|
7436
7752
|
}
|
|
7753
|
+
async function syncCodexOrientationChannelPreSpawn(cwd, ctx) {
|
|
7754
|
+
try {
|
|
7755
|
+
const root = await resolveBasouRootForCommand(cwd, "run");
|
|
7756
|
+
const paths = basouPaths15(root);
|
|
7757
|
+
const rendered = await renderOrientationToCodexChannel({
|
|
7758
|
+
orientationPath: paths.files.orientation,
|
|
7759
|
+
...ctx.codexChannelPath !== void 0 ? { channelPath: ctx.codexChannelPath } : {}
|
|
7760
|
+
});
|
|
7761
|
+
return rendered === null ? null : rendered.line;
|
|
7762
|
+
} catch {
|
|
7763
|
+
return null;
|
|
7764
|
+
}
|
|
7765
|
+
}
|
|
7437
7766
|
|
|
7438
7767
|
// src/commands/session.ts
|
|
7439
|
-
import { readFile as
|
|
7440
|
-
import { basename as basename6, isAbsolute as isAbsolute6, join as
|
|
7768
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
7769
|
+
import { basename as basename6, isAbsolute as isAbsolute6, join as join14, relative as relative3 } from "path";
|
|
7441
7770
|
import {
|
|
7442
7771
|
acquireLock as acquireLock6,
|
|
7443
7772
|
appendEventToExistingSession as appendEventToExistingSession3,
|
|
7444
|
-
assertBasouRootSafe as
|
|
7445
|
-
basouPaths as
|
|
7773
|
+
assertBasouRootSafe as assertBasouRootSafe13,
|
|
7774
|
+
basouPaths as basouPaths16,
|
|
7446
7775
|
enumerateSessionDirs as enumerateSessionDirs2,
|
|
7447
|
-
findErrorCode as
|
|
7776
|
+
findErrorCode as findErrorCode12,
|
|
7448
7777
|
importSessionFromJson as importSessionFromJson2,
|
|
7449
7778
|
loadSessionEntries as loadSessionEntries2,
|
|
7450
7779
|
readAllEvents,
|
|
7451
|
-
readManifest as
|
|
7780
|
+
readManifest as readManifest9,
|
|
7452
7781
|
readYamlFile as readYamlFile7,
|
|
7453
7782
|
rechainSessionInPlace,
|
|
7454
7783
|
resolveSessionId as resolveSessionId3,
|
|
@@ -7507,8 +7836,8 @@ async function runSessionList(options, ctx = {}) {
|
|
|
7507
7836
|
async function doRunSessionList(options, ctx) {
|
|
7508
7837
|
const cwd = ctx.cwd ?? process.cwd();
|
|
7509
7838
|
const repositoryRoot = await resolveRepositoryRootForSession(cwd, "list");
|
|
7510
|
-
const paths =
|
|
7511
|
-
await
|
|
7839
|
+
const paths = basouPaths16(repositoryRoot);
|
|
7840
|
+
await assertWorkspaceInitialized11(paths.root);
|
|
7512
7841
|
const now = /* @__PURE__ */ new Date();
|
|
7513
7842
|
const records = (await loadSessionEntries2(paths, {
|
|
7514
7843
|
now,
|
|
@@ -7559,17 +7888,17 @@ async function runSessionShow(idInput, options, ctx = {}) {
|
|
|
7559
7888
|
async function doRunSessionShow(idInput, options, ctx) {
|
|
7560
7889
|
const cwd = ctx.cwd ?? process.cwd();
|
|
7561
7890
|
const repositoryRoot = await resolveRepositoryRootForSession(cwd, "show");
|
|
7562
|
-
const paths =
|
|
7563
|
-
await
|
|
7891
|
+
const paths = basouPaths16(repositoryRoot);
|
|
7892
|
+
await assertWorkspaceInitialized11(paths.root);
|
|
7564
7893
|
const sessionId = await resolveSessionId3(paths, idInput);
|
|
7565
|
-
const sessionDir =
|
|
7566
|
-
const sessionYamlPath =
|
|
7894
|
+
const sessionDir = join14(paths.sessions, sessionId);
|
|
7895
|
+
const sessionYamlPath = join14(sessionDir, "session.yaml");
|
|
7567
7896
|
let session;
|
|
7568
7897
|
try {
|
|
7569
7898
|
const raw = await readYamlFile7(sessionYamlPath);
|
|
7570
7899
|
session = SessionSchema3.parse(raw);
|
|
7571
7900
|
} catch (error) {
|
|
7572
|
-
if (
|
|
7901
|
+
if (findErrorCode12(error, "ENOENT")) {
|
|
7573
7902
|
throw new Error(`Session not found: ${idInput}`);
|
|
7574
7903
|
}
|
|
7575
7904
|
throw new Error("Failed to read session", { cause: error });
|
|
@@ -7763,6 +8092,11 @@ function eventVariantSummary(ev) {
|
|
|
7763
8092
|
return `task ${shortTaskId2(ev.task_id)}: ${ev.title} (archived)`;
|
|
7764
8093
|
case "note_added":
|
|
7765
8094
|
return ev.body.length > 80 ? `${ev.body.slice(0, 77)}...` : ev.body;
|
|
8095
|
+
case "review_recorded": {
|
|
8096
|
+
const verdict = ev.verdict !== void 0 ? ` (${ev.verdict})` : "";
|
|
8097
|
+
const blocked = ev.blocked !== void 0 && ev.blocked.length > 0 ? ` ${ev.blocked.length} blocked` : "";
|
|
8098
|
+
return `${ev.reviewer} -> ${ev.target}${verdict}${blocked}`;
|
|
8099
|
+
}
|
|
7766
8100
|
case "adapter_output":
|
|
7767
8101
|
return `${ev.stream} "${ev.summary}" raw_ref=${ev.raw_ref}`;
|
|
7768
8102
|
}
|
|
@@ -7810,11 +8144,11 @@ function maxLen2(values, floor) {
|
|
|
7810
8144
|
async function resolveRepositoryRootForSession(cwd, subcmd) {
|
|
7811
8145
|
return resolveBasouRootForCommand(cwd, `session ${subcmd}`);
|
|
7812
8146
|
}
|
|
7813
|
-
async function
|
|
8147
|
+
async function assertWorkspaceInitialized11(basouRoot) {
|
|
7814
8148
|
try {
|
|
7815
|
-
await
|
|
8149
|
+
await assertBasouRootSafe13(basouRoot);
|
|
7816
8150
|
} catch (error) {
|
|
7817
|
-
if (
|
|
8151
|
+
if (findErrorCode12(error, "ENOENT")) {
|
|
7818
8152
|
throw new Error("Workspace not initialized. Run 'basou init' first.");
|
|
7819
8153
|
}
|
|
7820
8154
|
throw error;
|
|
@@ -7852,9 +8186,9 @@ async function runSessionImport(options, ctx = {}) {
|
|
|
7852
8186
|
async function doRunSessionImport(options, ctx) {
|
|
7853
8187
|
const cwd = ctx.cwd ?? process.cwd();
|
|
7854
8188
|
const repositoryRoot = await resolveRepositoryRootForSession(cwd, "import");
|
|
7855
|
-
const paths =
|
|
7856
|
-
await
|
|
7857
|
-
const manifest = await
|
|
8189
|
+
const paths = basouPaths16(repositoryRoot);
|
|
8190
|
+
await assertWorkspaceInitialized11(paths.root);
|
|
8191
|
+
const manifest = await readManifest9(paths);
|
|
7858
8192
|
const rawBody = await readInputFile(options.from);
|
|
7859
8193
|
const json = parseJsonStrict(rawBody);
|
|
7860
8194
|
const parsed = SessionImportPayloadSchema2.safeParse(json);
|
|
@@ -7881,12 +8215,12 @@ async function doRunSessionImport(options, ctx) {
|
|
|
7881
8215
|
}
|
|
7882
8216
|
async function readInputFile(path) {
|
|
7883
8217
|
try {
|
|
7884
|
-
return await
|
|
8218
|
+
return await readFile6(path, "utf8");
|
|
7885
8219
|
} catch (error) {
|
|
7886
|
-
if (
|
|
8220
|
+
if (findErrorCode12(error, "ENOENT")) {
|
|
7887
8221
|
throw new Error("Import source not found", { cause: error });
|
|
7888
8222
|
}
|
|
7889
|
-
if (
|
|
8223
|
+
if (findErrorCode12(error, "EISDIR")) {
|
|
7890
8224
|
throw new Error("Import source is not a file", { cause: error });
|
|
7891
8225
|
}
|
|
7892
8226
|
throw new Error("Failed to read import source", { cause: error });
|
|
@@ -7966,8 +8300,8 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
|
|
|
7966
8300
|
}
|
|
7967
8301
|
const cwd = ctx.cwd ?? process.cwd();
|
|
7968
8302
|
const repositoryRoot = await resolveRepositoryRootForSession(cwd, "note");
|
|
7969
|
-
const paths =
|
|
7970
|
-
await
|
|
8303
|
+
const paths = basouPaths16(repositoryRoot);
|
|
8304
|
+
await assertWorkspaceInitialized11(paths.root);
|
|
7971
8305
|
const sessionId = await resolveSessionId3(paths, sessionIdInput);
|
|
7972
8306
|
const body = hasBody ? options.body : await readNoteFile(options.fromFile);
|
|
7973
8307
|
if (body.length === 0) {
|
|
@@ -7998,12 +8332,12 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
|
|
|
7998
8332
|
}
|
|
7999
8333
|
async function readNoteFile(path) {
|
|
8000
8334
|
try {
|
|
8001
|
-
return await
|
|
8335
|
+
return await readFile6(path, "utf8");
|
|
8002
8336
|
} catch (error) {
|
|
8003
|
-
if (
|
|
8337
|
+
if (findErrorCode12(error, "ENOENT")) {
|
|
8004
8338
|
throw new Error("Note source not found", { cause: error });
|
|
8005
8339
|
}
|
|
8006
|
-
if (
|
|
8340
|
+
if (findErrorCode12(error, "EISDIR")) {
|
|
8007
8341
|
throw new Error("Note source is not a file", { cause: error });
|
|
8008
8342
|
}
|
|
8009
8343
|
throw new Error("Failed to read note source", { cause: error });
|
|
@@ -8048,8 +8382,8 @@ async function doRunSessionRechain(options, ctx) {
|
|
|
8048
8382
|
}
|
|
8049
8383
|
const cwd = ctx.cwd ?? process.cwd();
|
|
8050
8384
|
const repositoryRoot = await resolveRepositoryRootForSession(cwd, "rechain");
|
|
8051
|
-
const paths =
|
|
8052
|
-
await
|
|
8385
|
+
const paths = basouPaths16(repositoryRoot);
|
|
8386
|
+
await assertWorkspaceInitialized11(paths.root);
|
|
8053
8387
|
const sessionIds = options.session !== void 0 ? [await resolveSessionId3(paths, options.session)] : await enumerateSessionDirs2(paths);
|
|
8054
8388
|
const dryRun = options.dryRun === true;
|
|
8055
8389
|
const rows = [];
|
|
@@ -8102,10 +8436,10 @@ function renderRechainRow(row, dryRun) {
|
|
|
8102
8436
|
|
|
8103
8437
|
// src/commands/stats.ts
|
|
8104
8438
|
import {
|
|
8105
|
-
assertBasouRootSafe as
|
|
8106
|
-
basouPaths as
|
|
8439
|
+
assertBasouRootSafe as assertBasouRootSafe14,
|
|
8440
|
+
basouPaths as basouPaths17,
|
|
8107
8441
|
computeWorkStats,
|
|
8108
|
-
findErrorCode as
|
|
8442
|
+
findErrorCode as findErrorCode13,
|
|
8109
8443
|
resolveRepositoryRoot as resolveRepositoryRoot11
|
|
8110
8444
|
} from "@basou/core";
|
|
8111
8445
|
function registerStatsCommand(program2) {
|
|
@@ -8124,8 +8458,8 @@ async function runStats(options, ctx = {}) {
|
|
|
8124
8458
|
async function doRunStats(options, ctx) {
|
|
8125
8459
|
const cwd = ctx.cwd ?? process.cwd();
|
|
8126
8460
|
const repositoryRoot = await resolveRepositoryRootForStats(cwd);
|
|
8127
|
-
const paths =
|
|
8128
|
-
await
|
|
8461
|
+
const paths = basouPaths17(repositoryRoot);
|
|
8462
|
+
await assertWorkspaceInitialized12(paths.root);
|
|
8129
8463
|
const now = ctx.nowProvider?.() ?? /* @__PURE__ */ new Date();
|
|
8130
8464
|
const result = await computeWorkStats({
|
|
8131
8465
|
paths,
|
|
@@ -8219,11 +8553,11 @@ async function resolveRepositoryRootForStats(cwd) {
|
|
|
8219
8553
|
throw error;
|
|
8220
8554
|
}
|
|
8221
8555
|
}
|
|
8222
|
-
async function
|
|
8556
|
+
async function assertWorkspaceInitialized12(basouRoot) {
|
|
8223
8557
|
try {
|
|
8224
|
-
await
|
|
8558
|
+
await assertBasouRootSafe14(basouRoot);
|
|
8225
8559
|
} catch (error) {
|
|
8226
|
-
if (
|
|
8560
|
+
if (findErrorCode13(error, "ENOENT")) {
|
|
8227
8561
|
throw new Error("Workspace not initialized. Run 'basou init' first.");
|
|
8228
8562
|
}
|
|
8229
8563
|
throw error;
|
|
@@ -8232,11 +8566,11 @@ async function assertWorkspaceInitialized11(basouRoot) {
|
|
|
8232
8566
|
|
|
8233
8567
|
// src/commands/status.ts
|
|
8234
8568
|
import {
|
|
8235
|
-
assertBasouRootSafe as
|
|
8236
|
-
basouPaths as
|
|
8569
|
+
assertBasouRootSafe as assertBasouRootSafe15,
|
|
8570
|
+
basouPaths as basouPaths18,
|
|
8237
8571
|
buildStatusSnapshot,
|
|
8238
|
-
findErrorCode as
|
|
8239
|
-
readManifest as
|
|
8572
|
+
findErrorCode as findErrorCode14,
|
|
8573
|
+
readManifest as readManifest10,
|
|
8240
8574
|
resolveRepositoryRoot as resolveRepositoryRoot12,
|
|
8241
8575
|
writeStatus
|
|
8242
8576
|
} from "@basou/core";
|
|
@@ -8256,20 +8590,20 @@ async function runStatus(options, ctx = {}) {
|
|
|
8256
8590
|
async function doRunStatus(options, ctx) {
|
|
8257
8591
|
const cwd = ctx.cwd ?? process.cwd();
|
|
8258
8592
|
const repositoryRoot = await resolveRepositoryRootForStatus(cwd);
|
|
8259
|
-
const paths =
|
|
8593
|
+
const paths = basouPaths18(repositoryRoot);
|
|
8260
8594
|
try {
|
|
8261
|
-
await
|
|
8595
|
+
await assertBasouRootSafe15(paths.root);
|
|
8262
8596
|
} catch (error) {
|
|
8263
|
-
if (
|
|
8597
|
+
if (findErrorCode14(error, "ENOENT")) {
|
|
8264
8598
|
throw new Error("Workspace not initialized. Run 'basou init' first.");
|
|
8265
8599
|
}
|
|
8266
8600
|
throw error;
|
|
8267
8601
|
}
|
|
8268
8602
|
let manifest;
|
|
8269
8603
|
try {
|
|
8270
|
-
manifest = await
|
|
8604
|
+
manifest = await readManifest10(paths);
|
|
8271
8605
|
} catch (error) {
|
|
8272
|
-
if (
|
|
8606
|
+
if (findErrorCode14(error, "ENOENT")) {
|
|
8273
8607
|
throw new Error("Workspace not initialized. Run 'basou init' first.");
|
|
8274
8608
|
}
|
|
8275
8609
|
throw new Error("Failed to read workspace manifest", { cause: error });
|
|
@@ -8305,21 +8639,21 @@ async function resolveRepositoryRootForStatus(cwd) {
|
|
|
8305
8639
|
}
|
|
8306
8640
|
|
|
8307
8641
|
// src/commands/task.ts
|
|
8308
|
-
import { readFile as
|
|
8309
|
-
import { join as
|
|
8642
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
8643
|
+
import { join as join15 } from "path";
|
|
8310
8644
|
import {
|
|
8311
8645
|
archiveTask,
|
|
8312
|
-
assertBasouRootSafe as
|
|
8313
|
-
basouPaths as
|
|
8646
|
+
assertBasouRootSafe as assertBasouRootSafe16,
|
|
8647
|
+
basouPaths as basouPaths19,
|
|
8314
8648
|
createTaskWithEvent,
|
|
8315
8649
|
deleteTask,
|
|
8316
8650
|
editTask,
|
|
8317
8651
|
enumerateArchivedTaskIds,
|
|
8318
|
-
findErrorCode as
|
|
8652
|
+
findErrorCode as findErrorCode15,
|
|
8319
8653
|
loadSessionEntries as loadSessionEntries3,
|
|
8320
8654
|
loadTaskEntries,
|
|
8321
8655
|
prefixedUlid as prefixedUlid5,
|
|
8322
|
-
readManifest as
|
|
8656
|
+
readManifest as readManifest11,
|
|
8323
8657
|
readTaskFile,
|
|
8324
8658
|
readTaskFileWithArchiveFallback,
|
|
8325
8659
|
reconcileAllTasks,
|
|
@@ -8411,8 +8745,8 @@ async function doRunTaskNew(options, ctx) {
|
|
|
8411
8745
|
}
|
|
8412
8746
|
const cwd = ctx.cwd ?? process.cwd();
|
|
8413
8747
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "new");
|
|
8414
|
-
const paths =
|
|
8415
|
-
await
|
|
8748
|
+
const paths = basouPaths19(repositoryRoot);
|
|
8749
|
+
await assertWorkspaceInitialized13(paths.root);
|
|
8416
8750
|
const description = options.description !== void 0 ? options.description : options.fromFile !== void 0 ? await readDescriptionFile(options.fromFile) : "";
|
|
8417
8751
|
const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
|
|
8418
8752
|
const occurredAt = now.toISOString();
|
|
@@ -8446,7 +8780,7 @@ async function doRunTaskNew(options, ctx) {
|
|
|
8446
8780
|
});
|
|
8447
8781
|
return;
|
|
8448
8782
|
}
|
|
8449
|
-
const manifest = await
|
|
8783
|
+
const manifest = await readManifest11(paths);
|
|
8450
8784
|
const result = await createTaskWithEvent({
|
|
8451
8785
|
mode: "ad-hoc",
|
|
8452
8786
|
paths,
|
|
@@ -8520,8 +8854,8 @@ async function runTaskList(options, ctx = {}) {
|
|
|
8520
8854
|
async function doRunTaskList(options, ctx) {
|
|
8521
8855
|
const cwd = ctx.cwd ?? process.cwd();
|
|
8522
8856
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "list");
|
|
8523
|
-
const paths =
|
|
8524
|
-
await
|
|
8857
|
+
const paths = basouPaths19(repositoryRoot);
|
|
8858
|
+
await assertWorkspaceInitialized13(paths.root);
|
|
8525
8859
|
const entries = await loadTaskEntries(paths, {
|
|
8526
8860
|
onSkip: (id, reason) => printTaskSkip(id, reason)
|
|
8527
8861
|
});
|
|
@@ -8624,15 +8958,15 @@ async function runTaskShow(idInput, options, ctx = {}) {
|
|
|
8624
8958
|
async function doRunTaskShow(idInput, options, ctx) {
|
|
8625
8959
|
const cwd = ctx.cwd ?? process.cwd();
|
|
8626
8960
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "show");
|
|
8627
|
-
const paths =
|
|
8628
|
-
await
|
|
8961
|
+
const paths = basouPaths19(repositoryRoot);
|
|
8962
|
+
await assertWorkspaceInitialized13(paths.root);
|
|
8629
8963
|
const taskId = await resolveTaskId2(paths, idInput, { includeArchived: true });
|
|
8630
8964
|
const { doc, archived } = await readTaskFileWithArchiveFallback(paths, taskId);
|
|
8631
8965
|
const sessions = await loadSessionEntries3(paths, { now: /* @__PURE__ */ new Date() });
|
|
8632
8966
|
const events = [];
|
|
8633
8967
|
const linkedSessionIds = new Set(doc.task.task.linked_sessions);
|
|
8634
8968
|
for (const s of sessions) {
|
|
8635
|
-
const sessionDir =
|
|
8969
|
+
const sessionDir = join15(paths.sessions, s.sessionId);
|
|
8636
8970
|
try {
|
|
8637
8971
|
for await (const ev of replayEvents3(sessionDir, {
|
|
8638
8972
|
onWarning: (w) => printReplayWarning(w, s.sessionId)
|
|
@@ -8768,8 +9102,8 @@ async function doRunTaskStatus(taskIdInput, newStatusInput, options, ctx) {
|
|
|
8768
9102
|
const newStatus = parseTaskStatusPositional(newStatusInput);
|
|
8769
9103
|
const cwd = ctx.cwd ?? process.cwd();
|
|
8770
9104
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "status");
|
|
8771
|
-
const paths =
|
|
8772
|
-
await
|
|
9105
|
+
const paths = basouPaths19(repositoryRoot);
|
|
9106
|
+
await assertWorkspaceInitialized13(paths.root);
|
|
8773
9107
|
const taskId = await resolveTaskId2(paths, taskIdInput);
|
|
8774
9108
|
const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
|
|
8775
9109
|
const occurredAt = now.toISOString();
|
|
@@ -8794,7 +9128,7 @@ async function doRunTaskStatus(taskIdInput, newStatusInput, options, ctx) {
|
|
|
8794
9128
|
});
|
|
8795
9129
|
return;
|
|
8796
9130
|
}
|
|
8797
|
-
const manifest = await
|
|
9131
|
+
const manifest = await readManifest11(paths);
|
|
8798
9132
|
const result = await updateTaskStatusWithEvent({
|
|
8799
9133
|
mode: "ad-hoc",
|
|
8800
9134
|
paths,
|
|
@@ -8845,9 +9179,9 @@ async function runTaskReconcile(options, ctx = {}) {
|
|
|
8845
9179
|
async function doRunTaskReconcile(options, ctx) {
|
|
8846
9180
|
const cwd = ctx.cwd ?? process.cwd();
|
|
8847
9181
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "reconcile");
|
|
8848
|
-
const paths =
|
|
8849
|
-
await
|
|
8850
|
-
const manifest = await
|
|
9182
|
+
const paths = basouPaths19(repositoryRoot);
|
|
9183
|
+
await assertWorkspaceInitialized13(paths.root);
|
|
9184
|
+
const manifest = await readManifest11(paths);
|
|
8851
9185
|
const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
|
|
8852
9186
|
const write = options.write === true;
|
|
8853
9187
|
const verbose = isVerbose(options);
|
|
@@ -9025,9 +9359,9 @@ async function doRunTaskRefreshLinkage(taskIdInput, options, ctx) {
|
|
|
9025
9359
|
}
|
|
9026
9360
|
const cwd = ctx.cwd ?? process.cwd();
|
|
9027
9361
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "refresh-linkage");
|
|
9028
|
-
const paths =
|
|
9029
|
-
await
|
|
9030
|
-
const manifest = await
|
|
9362
|
+
const paths = basouPaths19(repositoryRoot);
|
|
9363
|
+
await assertWorkspaceInitialized13(paths.root);
|
|
9364
|
+
const manifest = await readManifest11(paths);
|
|
9031
9365
|
const taskId = await resolveTaskId2(paths, taskIdInput);
|
|
9032
9366
|
const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
|
|
9033
9367
|
const write = options.write === true;
|
|
@@ -9105,9 +9439,9 @@ async function doRunTaskEdit(taskIdInput, options, ctx) {
|
|
|
9105
9439
|
}
|
|
9106
9440
|
const cwd = ctx.cwd ?? process.cwd();
|
|
9107
9441
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "edit");
|
|
9108
|
-
const paths =
|
|
9109
|
-
await
|
|
9110
|
-
const manifest = await
|
|
9442
|
+
const paths = basouPaths19(repositoryRoot);
|
|
9443
|
+
await assertWorkspaceInitialized13(paths.root);
|
|
9444
|
+
const manifest = await readManifest11(paths);
|
|
9111
9445
|
const taskId = await resolveTaskId2(paths, taskIdInput);
|
|
9112
9446
|
const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
|
|
9113
9447
|
const occurredAt = now.toISOString();
|
|
@@ -9161,9 +9495,9 @@ async function doRunTaskDelete(taskIdInput, options, ctx) {
|
|
|
9161
9495
|
}
|
|
9162
9496
|
const cwd = ctx.cwd ?? process.cwd();
|
|
9163
9497
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "delete");
|
|
9164
|
-
const paths =
|
|
9165
|
-
await
|
|
9166
|
-
const manifest = await
|
|
9498
|
+
const paths = basouPaths19(repositoryRoot);
|
|
9499
|
+
await assertWorkspaceInitialized13(paths.root);
|
|
9500
|
+
const manifest = await readManifest11(paths);
|
|
9167
9501
|
const taskId = await resolveTaskId2(paths, taskIdInput);
|
|
9168
9502
|
if (options.yes !== true) {
|
|
9169
9503
|
await confirmDestructiveAction("delete", taskId);
|
|
@@ -9206,9 +9540,9 @@ async function doRunTaskArchive(taskIdInput, options, ctx) {
|
|
|
9206
9540
|
}
|
|
9207
9541
|
const cwd = ctx.cwd ?? process.cwd();
|
|
9208
9542
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "archive");
|
|
9209
|
-
const paths =
|
|
9210
|
-
await
|
|
9211
|
-
const manifest = await
|
|
9543
|
+
const paths = basouPaths19(repositoryRoot);
|
|
9544
|
+
await assertWorkspaceInitialized13(paths.root);
|
|
9545
|
+
const manifest = await readManifest11(paths);
|
|
9212
9546
|
const taskId = await resolveTaskId2(paths, taskIdInput);
|
|
9213
9547
|
if (options.yes !== true) {
|
|
9214
9548
|
await confirmDestructiveAction("archive", taskId);
|
|
@@ -9320,12 +9654,12 @@ function parsePositiveInt2(raw) {
|
|
|
9320
9654
|
}
|
|
9321
9655
|
async function readDescriptionFile(path) {
|
|
9322
9656
|
try {
|
|
9323
|
-
return await
|
|
9657
|
+
return await readFile7(path, "utf8");
|
|
9324
9658
|
} catch (error) {
|
|
9325
|
-
if (
|
|
9659
|
+
if (findErrorCode15(error, "ENOENT")) {
|
|
9326
9660
|
throw new Error("Description source not found", { cause: error });
|
|
9327
9661
|
}
|
|
9328
|
-
if (
|
|
9662
|
+
if (findErrorCode15(error, "EISDIR")) {
|
|
9329
9663
|
throw new Error("Description source is not a file", { cause: error });
|
|
9330
9664
|
}
|
|
9331
9665
|
throw new Error("Failed to read description source", { cause: error });
|
|
@@ -9334,11 +9668,11 @@ async function readDescriptionFile(path) {
|
|
|
9334
9668
|
async function resolveRepositoryRootForTask(cwd, subcmd) {
|
|
9335
9669
|
return resolveBasouRootForCommand(cwd, `task ${subcmd}`);
|
|
9336
9670
|
}
|
|
9337
|
-
async function
|
|
9671
|
+
async function assertWorkspaceInitialized13(basouRoot) {
|
|
9338
9672
|
try {
|
|
9339
|
-
await
|
|
9673
|
+
await assertBasouRootSafe16(basouRoot);
|
|
9340
9674
|
} catch (error) {
|
|
9341
|
-
if (
|
|
9675
|
+
if (findErrorCode15(error, "ENOENT")) {
|
|
9342
9676
|
throw new Error("Workspace not initialized. Run 'basou init' first.");
|
|
9343
9677
|
}
|
|
9344
9678
|
throw error;
|
|
@@ -9426,10 +9760,10 @@ function maxLen3(values, floor) {
|
|
|
9426
9760
|
|
|
9427
9761
|
// src/commands/verify.ts
|
|
9428
9762
|
import {
|
|
9429
|
-
assertBasouRootSafe as
|
|
9430
|
-
basouPaths as
|
|
9763
|
+
assertBasouRootSafe as assertBasouRootSafe17,
|
|
9764
|
+
basouPaths as basouPaths20,
|
|
9431
9765
|
enumerateSessionDirs as enumerateSessionDirs3,
|
|
9432
|
-
findErrorCode as
|
|
9766
|
+
findErrorCode as findErrorCode16,
|
|
9433
9767
|
resolveRepositoryRoot as resolveRepositoryRoot13,
|
|
9434
9768
|
resolveSessionId as resolveSessionId5,
|
|
9435
9769
|
verifyEventsChain
|
|
@@ -9453,8 +9787,8 @@ async function doRunVerify(options, ctx) {
|
|
|
9453
9787
|
}
|
|
9454
9788
|
const cwd = ctx.cwd ?? process.cwd();
|
|
9455
9789
|
const repositoryRoot = await resolveRepositoryRootForVerify(cwd);
|
|
9456
|
-
const paths =
|
|
9457
|
-
await
|
|
9790
|
+
const paths = basouPaths20(repositoryRoot);
|
|
9791
|
+
await assertWorkspaceInitialized14(paths.root);
|
|
9458
9792
|
const sessionIds = options.session !== void 0 ? [await resolveSessionId5(paths, options.session)] : await enumerateSessionDirs3(paths);
|
|
9459
9793
|
const rows = [];
|
|
9460
9794
|
for (const sessionId of sessionIds) {
|
|
@@ -9511,11 +9845,11 @@ async function resolveRepositoryRootForVerify(cwd) {
|
|
|
9511
9845
|
throw error;
|
|
9512
9846
|
}
|
|
9513
9847
|
}
|
|
9514
|
-
async function
|
|
9848
|
+
async function assertWorkspaceInitialized14(basouRoot) {
|
|
9515
9849
|
try {
|
|
9516
|
-
await
|
|
9850
|
+
await assertBasouRootSafe17(basouRoot);
|
|
9517
9851
|
} catch (error) {
|
|
9518
|
-
if (
|
|
9852
|
+
if (findErrorCode16(error, "ENOENT")) {
|
|
9519
9853
|
throw new Error("Workspace not initialized. Run 'basou init' first.");
|
|
9520
9854
|
}
|
|
9521
9855
|
throw error;
|
|
@@ -9525,12 +9859,12 @@ async function assertWorkspaceInitialized13(basouRoot) {
|
|
|
9525
9859
|
// src/commands/view.ts
|
|
9526
9860
|
import { spawn } from "child_process";
|
|
9527
9861
|
import { createHash } from "crypto";
|
|
9528
|
-
import { basename as basename7, resolve as
|
|
9862
|
+
import { basename as basename7, resolve as resolve12 } from "path";
|
|
9529
9863
|
import {
|
|
9530
|
-
assertBasouRootSafe as
|
|
9531
|
-
basouPaths as
|
|
9532
|
-
findErrorCode as
|
|
9533
|
-
readManifest as
|
|
9864
|
+
assertBasouRootSafe as assertBasouRootSafe18,
|
|
9865
|
+
basouPaths as basouPaths21,
|
|
9866
|
+
findErrorCode as findErrorCode18,
|
|
9867
|
+
readManifest as readManifest14,
|
|
9534
9868
|
resolveRepositoryRoot as resolveRepositoryRoot14
|
|
9535
9869
|
} from "@basou/core";
|
|
9536
9870
|
import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
|
|
@@ -9538,9 +9872,9 @@ import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
|
|
|
9538
9872
|
// src/lib/portfolio-safety.ts
|
|
9539
9873
|
import { execFile } from "child_process";
|
|
9540
9874
|
import { lstat as lstat2, realpath as realpath2 } from "fs/promises";
|
|
9541
|
-
import { isAbsolute as isAbsolute7, join as
|
|
9875
|
+
import { isAbsolute as isAbsolute7, join as join16, relative as relative4, resolve as resolve11 } from "path";
|
|
9542
9876
|
import { promisify } from "util";
|
|
9543
|
-
import { readManifest as
|
|
9877
|
+
import { readManifest as readManifest12 } from "@basou/core";
|
|
9544
9878
|
var execFileAsync = promisify(execFile);
|
|
9545
9879
|
function errorCode(error) {
|
|
9546
9880
|
return error instanceof Error ? error.code : void 0;
|
|
@@ -9549,7 +9883,7 @@ async function canonical(p) {
|
|
|
9549
9883
|
try {
|
|
9550
9884
|
return await realpath2(p);
|
|
9551
9885
|
} catch {
|
|
9552
|
-
return
|
|
9886
|
+
return resolve11(p);
|
|
9553
9887
|
}
|
|
9554
9888
|
}
|
|
9555
9889
|
function isInside(child, parent) {
|
|
@@ -9562,7 +9896,7 @@ function isBasouPath(p) {
|
|
|
9562
9896
|
async function inspectRepo(repoPath) {
|
|
9563
9897
|
let hasEntry = false;
|
|
9564
9898
|
try {
|
|
9565
|
-
await lstat2(
|
|
9899
|
+
await lstat2(join16(repoPath, ".basou"));
|
|
9566
9900
|
hasEntry = true;
|
|
9567
9901
|
} catch (error) {
|
|
9568
9902
|
if (errorCode(error) !== "ENOENT") {
|
|
@@ -9593,7 +9927,7 @@ async function checkPortfolioSafety(workspaces) {
|
|
|
9593
9927
|
const wsReal = await canonical(ws.repoRoot);
|
|
9594
9928
|
let sourceRoots = [];
|
|
9595
9929
|
try {
|
|
9596
|
-
const manifest = await
|
|
9930
|
+
const manifest = await readManifest12(ws.paths);
|
|
9597
9931
|
sourceRoots = manifest.import?.source_roots ?? [];
|
|
9598
9932
|
} catch (error) {
|
|
9599
9933
|
if (error instanceof Error && error.message === "YAML file not found") {
|
|
@@ -9611,7 +9945,7 @@ async function checkPortfolioSafety(workspaces) {
|
|
|
9611
9945
|
}
|
|
9612
9946
|
const monitored = /* @__PURE__ */ new Map();
|
|
9613
9947
|
for (const root of sourceRoots) {
|
|
9614
|
-
const display =
|
|
9948
|
+
const display = resolve11(ws.repoRoot, root);
|
|
9615
9949
|
const real = await canonical(display);
|
|
9616
9950
|
if (real !== wsReal) monitored.set(real, display);
|
|
9617
9951
|
}
|
|
@@ -9663,18 +9997,18 @@ function formatSafetyReport(result) {
|
|
|
9663
9997
|
|
|
9664
9998
|
// src/lib/view-server.ts
|
|
9665
9999
|
import { createServer } from "http";
|
|
9666
|
-
import { join as
|
|
10000
|
+
import { join as join17 } from "path";
|
|
9667
10001
|
import {
|
|
9668
10002
|
computeWorkStats as computeWorkStats2,
|
|
9669
10003
|
enumerateApprovals as enumerateApprovals2,
|
|
9670
|
-
findErrorCode as
|
|
10004
|
+
findErrorCode as findErrorCode17,
|
|
9671
10005
|
isLazyExpired as isLazyExpired2,
|
|
9672
10006
|
loadApproval as loadApproval2,
|
|
9673
10007
|
loadSessionEntries as loadSessionEntries4,
|
|
9674
10008
|
loadTaskEntries as loadTaskEntries2,
|
|
9675
10009
|
readAllEvents as readAllEvents2,
|
|
9676
|
-
readManifest as
|
|
9677
|
-
readMarkdownFile as
|
|
10010
|
+
readManifest as readManifest13,
|
|
10011
|
+
readMarkdownFile as readMarkdownFile7,
|
|
9678
10012
|
readSessionYaml as readSessionYaml3,
|
|
9679
10013
|
readTaskFile as readTaskFile2,
|
|
9680
10014
|
renderDecisions as renderDecisions3,
|
|
@@ -10324,7 +10658,7 @@ function startViewServer(opts) {
|
|
|
10324
10658
|
};
|
|
10325
10659
|
let boundPort = port;
|
|
10326
10660
|
const getPort = () => boundPort;
|
|
10327
|
-
return new Promise((
|
|
10661
|
+
return new Promise((resolve13, reject) => {
|
|
10328
10662
|
const server = createServer((req, res) => {
|
|
10329
10663
|
handleRequest(req, res, deps, getPort, runExclusive).catch((error) => {
|
|
10330
10664
|
sendError(res, error instanceof HttpError ? error.status : 500, pathlessMessage(error));
|
|
@@ -10335,7 +10669,7 @@ function startViewServer(opts) {
|
|
|
10335
10669
|
const address = server.address();
|
|
10336
10670
|
boundPort = isAddressInfo(address) ? address.port : port;
|
|
10337
10671
|
server.off("error", reject);
|
|
10338
|
-
|
|
10672
|
+
resolve13({
|
|
10339
10673
|
url: `http://${host}:${boundPort}`,
|
|
10340
10674
|
port: boundPort,
|
|
10341
10675
|
close: () => closeServer(server)
|
|
@@ -10347,8 +10681,8 @@ function isAddressInfo(value) {
|
|
|
10347
10681
|
return value !== null && typeof value === "object";
|
|
10348
10682
|
}
|
|
10349
10683
|
function closeServer(server) {
|
|
10350
|
-
return new Promise((
|
|
10351
|
-
server.close(() =>
|
|
10684
|
+
return new Promise((resolve13) => {
|
|
10685
|
+
server.close(() => resolve13());
|
|
10352
10686
|
server.closeAllConnections();
|
|
10353
10687
|
});
|
|
10354
10688
|
}
|
|
@@ -10565,9 +10899,9 @@ async function captureStaleness(ws, nowIso) {
|
|
|
10565
10899
|
async function overview(ws, nowProvider) {
|
|
10566
10900
|
let manifest;
|
|
10567
10901
|
try {
|
|
10568
|
-
manifest = await
|
|
10902
|
+
manifest = await readManifest13(ws.paths);
|
|
10569
10903
|
} catch (error) {
|
|
10570
|
-
if (
|
|
10904
|
+
if (findErrorCode17(error, "ENOENT")) {
|
|
10571
10905
|
return { initialized: false, repoRoot: ws.repoRoot };
|
|
10572
10906
|
}
|
|
10573
10907
|
throw error;
|
|
@@ -10622,7 +10956,7 @@ async function sessionDetail(ws, sessionId) {
|
|
|
10622
10956
|
throw error;
|
|
10623
10957
|
}
|
|
10624
10958
|
try {
|
|
10625
|
-
const events = await readAllEvents2(
|
|
10959
|
+
const events = await readAllEvents2(join17(ws.paths.sessions, sessionId));
|
|
10626
10960
|
return { session, events };
|
|
10627
10961
|
} catch {
|
|
10628
10962
|
return { session, events: [], degraded: true };
|
|
@@ -10644,7 +10978,7 @@ async function taskDetail(ws, taskId) {
|
|
|
10644
10978
|
}
|
|
10645
10979
|
}
|
|
10646
10980
|
async function decisionsView(ws, nowProvider) {
|
|
10647
|
-
const fromDisk = await
|
|
10981
|
+
const fromDisk = await readMarkdownFile7(ws.paths.files.decisions);
|
|
10648
10982
|
if (fromDisk !== null) {
|
|
10649
10983
|
return { body: fromDisk, fromDisk: true };
|
|
10650
10984
|
}
|
|
@@ -10667,7 +11001,7 @@ async function approvalsView(ws, nowProvider) {
|
|
|
10667
11001
|
return { pending: await toViews(ids.pending), resolved: await toViews(ids.resolved) };
|
|
10668
11002
|
}
|
|
10669
11003
|
async function handoffView(ws, nowProvider) {
|
|
10670
|
-
const fromDisk = await
|
|
11004
|
+
const fromDisk = await readMarkdownFile7(ws.paths.files.handoff);
|
|
10671
11005
|
if (fromDisk !== null) {
|
|
10672
11006
|
return { body: fromDisk, fromDisk: true };
|
|
10673
11007
|
}
|
|
@@ -10836,18 +11170,18 @@ async function doRunView(options, ctx) {
|
|
|
10836
11170
|
}
|
|
10837
11171
|
async function buildSingleDeps(ctx, cwd) {
|
|
10838
11172
|
const repositoryRoot = await resolveRepositoryRootForView(cwd);
|
|
10839
|
-
const paths =
|
|
10840
|
-
await
|
|
11173
|
+
const paths = basouPaths21(repositoryRoot);
|
|
11174
|
+
await assertWorkspaceInitialized15(paths.root);
|
|
10841
11175
|
const entry = await buildWorkspaceEntry(repositoryRoot, ctx);
|
|
10842
11176
|
return { workspaces: [entry], mode: "single", nowProvider: nowProviderOf(ctx) };
|
|
10843
11177
|
}
|
|
10844
11178
|
async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
|
|
10845
|
-
const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path:
|
|
11179
|
+
const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve12(cwd, p) })) : await loadPortfolioConfig(ctx.portfolioConfigPath);
|
|
10846
11180
|
const entries = [];
|
|
10847
11181
|
const seenPath = /* @__PURE__ */ new Set();
|
|
10848
11182
|
const seenKey = /* @__PURE__ */ new Set();
|
|
10849
11183
|
for (const spec of specs) {
|
|
10850
|
-
const repoRoot =
|
|
11184
|
+
const repoRoot = resolve12(spec.path);
|
|
10851
11185
|
if (seenPath.has(repoRoot)) continue;
|
|
10852
11186
|
seenPath.add(repoRoot);
|
|
10853
11187
|
const entry = await buildWorkspaceEntry(repoRoot, ctx, spec.label);
|
|
@@ -10860,14 +11194,14 @@ async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
|
|
|
10860
11194
|
return { workspaces: entries, mode: "portfolio", nowProvider: nowProviderOf(ctx) };
|
|
10861
11195
|
}
|
|
10862
11196
|
async function buildWorkspaceEntry(repoRoot, ctx, labelOverride) {
|
|
10863
|
-
const paths =
|
|
11197
|
+
const paths = basouPaths21(repoRoot);
|
|
10864
11198
|
const importCtx = {
|
|
10865
11199
|
cwd: repoRoot,
|
|
10866
11200
|
...ctx.claudeProjectsDir !== void 0 ? { claudeProjectsDir: ctx.claudeProjectsDir } : {},
|
|
10867
11201
|
...ctx.codexSessionsDir !== void 0 ? { codexSessionsDir: ctx.codexSessionsDir } : {}
|
|
10868
11202
|
};
|
|
10869
11203
|
try {
|
|
10870
|
-
const manifest = await
|
|
11204
|
+
const manifest = await readManifest14(paths);
|
|
10871
11205
|
return {
|
|
10872
11206
|
key: manifest.workspace.id,
|
|
10873
11207
|
label: labelOverride ?? manifest.workspace.name,
|
|
@@ -10896,7 +11230,7 @@ async function startListening(port, deps) {
|
|
|
10896
11230
|
try {
|
|
10897
11231
|
return await startViewServer({ port, deps });
|
|
10898
11232
|
} catch (error) {
|
|
10899
|
-
if (
|
|
11233
|
+
if (findErrorCode18(error, "EADDRINUSE")) {
|
|
10900
11234
|
throw new Error(`Port ${port} is already in use. Pass --port <n> to choose another.`, {
|
|
10901
11235
|
cause: error
|
|
10902
11236
|
});
|
|
@@ -10919,7 +11253,7 @@ function openInBrowser(url, override) {
|
|
|
10919
11253
|
}
|
|
10920
11254
|
}
|
|
10921
11255
|
function waitForShutdown(signal) {
|
|
10922
|
-
return new Promise((
|
|
11256
|
+
return new Promise((resolve13) => {
|
|
10923
11257
|
const cleanup = () => {
|
|
10924
11258
|
process.off("SIGINT", onSignal);
|
|
10925
11259
|
process.off("SIGTERM", onSignal);
|
|
@@ -10927,18 +11261,18 @@ function waitForShutdown(signal) {
|
|
|
10927
11261
|
};
|
|
10928
11262
|
const onSignal = () => {
|
|
10929
11263
|
cleanup();
|
|
10930
|
-
|
|
11264
|
+
resolve13();
|
|
10931
11265
|
};
|
|
10932
11266
|
const onAbort = () => {
|
|
10933
11267
|
cleanup();
|
|
10934
|
-
|
|
11268
|
+
resolve13();
|
|
10935
11269
|
};
|
|
10936
11270
|
process.on("SIGINT", onSignal);
|
|
10937
11271
|
process.on("SIGTERM", onSignal);
|
|
10938
11272
|
if (signal !== void 0) {
|
|
10939
11273
|
if (signal.aborted) {
|
|
10940
11274
|
cleanup();
|
|
10941
|
-
|
|
11275
|
+
resolve13();
|
|
10942
11276
|
return;
|
|
10943
11277
|
}
|
|
10944
11278
|
signal.addEventListener("abort", onAbort);
|
|
@@ -10957,11 +11291,11 @@ async function resolveRepositoryRootForView(cwd) {
|
|
|
10957
11291
|
throw error;
|
|
10958
11292
|
}
|
|
10959
11293
|
}
|
|
10960
|
-
async function
|
|
11294
|
+
async function assertWorkspaceInitialized15(basouRoot) {
|
|
10961
11295
|
try {
|
|
10962
|
-
await
|
|
11296
|
+
await assertBasouRootSafe18(basouRoot);
|
|
10963
11297
|
} catch (error) {
|
|
10964
|
-
if (
|
|
11298
|
+
if (findErrorCode18(error, "ENOENT")) {
|
|
10965
11299
|
throw new Error("Workspace not initialized. Run 'basou init' first.");
|
|
10966
11300
|
}
|
|
10967
11301
|
throw error;
|
|
@@ -10993,6 +11327,7 @@ function buildProgram() {
|
|
|
10993
11327
|
registerDecisionsCommand(program2);
|
|
10994
11328
|
registerReportCommand(program2);
|
|
10995
11329
|
registerOrientCommand(program2);
|
|
11330
|
+
registerReviewCommand(program2);
|
|
10996
11331
|
registerReviewGapsCommand(program2);
|
|
10997
11332
|
registerProjectCommand(program2);
|
|
10998
11333
|
registerProtocolCommand(program2);
|