@basou/cli 0.28.0 → 0.29.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 +362 -187
- package/dist/index.js.map +1 -1
- package/dist/program.js +362 -187
- package/dist/program.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -2034,11 +2034,73 @@ async function assertWorkspaceInitialized4(basouRoot) {
|
|
|
2034
2034
|
}
|
|
2035
2035
|
|
|
2036
2036
|
// src/commands/hook.ts
|
|
2037
|
-
import { open, readFile as readFile2, stat as
|
|
2037
|
+
import { open as open2, readFile as readFile2, stat as stat3 } from "fs/promises";
|
|
2038
|
+
import { homedir as homedir4 } from "os";
|
|
2039
|
+
import { join as join6 } from "path";
|
|
2040
|
+
import { fileURLToPath } from "url";
|
|
2038
2041
|
import {
|
|
2042
|
+
buildStopHookCommand,
|
|
2039
2043
|
DEFAULT_STOP_HOOK_MIN_EDITS,
|
|
2040
|
-
evaluateStopHook
|
|
2044
|
+
evaluateStopHook,
|
|
2045
|
+
findBasouStopHookCommand,
|
|
2046
|
+
removeStopHook,
|
|
2047
|
+
upsertStopHook
|
|
2041
2048
|
} from "@basou/core";
|
|
2049
|
+
|
|
2050
|
+
// src/lib/durable-write.ts
|
|
2051
|
+
import { randomUUID } from "crypto";
|
|
2052
|
+
import { lstat, open, rename, stat as stat2, unlink as unlink2 } from "fs/promises";
|
|
2053
|
+
import { basename as basename2, dirname, join as join5 } from "path";
|
|
2054
|
+
async function assertNotSymlink(targetPath) {
|
|
2055
|
+
try {
|
|
2056
|
+
const st = await lstat(targetPath);
|
|
2057
|
+
if (st.isSymbolicLink()) {
|
|
2058
|
+
throw new Error(
|
|
2059
|
+
"Refusing to write through a symlink. Replace the symlinked target with a regular file (or remove it) and retry."
|
|
2060
|
+
);
|
|
2061
|
+
}
|
|
2062
|
+
} catch (error) {
|
|
2063
|
+
if (error instanceof Error && error.code === "ENOENT") return;
|
|
2064
|
+
throw error;
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
async function writeFileDurable(targetPath, content) {
|
|
2068
|
+
const dir = dirname(targetPath);
|
|
2069
|
+
const tmpPath = join5(dir, `.${basename2(targetPath)}.tmp.${randomUUID()}`);
|
|
2070
|
+
let mode = 420;
|
|
2071
|
+
try {
|
|
2072
|
+
mode = (await stat2(targetPath)).mode & 511;
|
|
2073
|
+
} catch (error) {
|
|
2074
|
+
if (!(error instanceof Error && error.code === "ENOENT")) {
|
|
2075
|
+
throw error;
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
let handle;
|
|
2079
|
+
try {
|
|
2080
|
+
handle = await open(tmpPath, "wx", mode);
|
|
2081
|
+
await handle.writeFile(content, "utf8");
|
|
2082
|
+
await handle.chmod(mode);
|
|
2083
|
+
await handle.sync();
|
|
2084
|
+
await handle.close();
|
|
2085
|
+
handle = void 0;
|
|
2086
|
+
await rename(tmpPath, targetPath);
|
|
2087
|
+
} catch (error) {
|
|
2088
|
+
if (handle) await handle.close().catch(() => void 0);
|
|
2089
|
+
await unlink2(tmpPath).catch(() => void 0);
|
|
2090
|
+
throw error;
|
|
2091
|
+
}
|
|
2092
|
+
try {
|
|
2093
|
+
const dirHandle = await open(dir, "r");
|
|
2094
|
+
try {
|
|
2095
|
+
await dirHandle.sync();
|
|
2096
|
+
} finally {
|
|
2097
|
+
await dirHandle.close();
|
|
2098
|
+
}
|
|
2099
|
+
} catch {
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
// src/commands/hook.ts
|
|
2042
2104
|
var MAX_TRANSCRIPT_BYTES = 8 * 1024 * 1024;
|
|
2043
2105
|
function registerHookCommand(program2) {
|
|
2044
2106
|
const hook = program2.command("hook").description(
|
|
@@ -2049,29 +2111,48 @@ function registerHookCommand(program2) {
|
|
|
2049
2111
|
).option(
|
|
2050
2112
|
"--min-edits <n>",
|
|
2051
2113
|
`Minimum file edits before nudging on edits alone (default ${DEFAULT_STOP_HOOK_MIN_EDITS})`
|
|
2114
|
+
).option(
|
|
2115
|
+
"--block",
|
|
2116
|
+
"Opt-in enforcement: hold the agent in-turn (decision:block) instead of a non-blocking reminder"
|
|
2052
2117
|
).addHelpText("after", HOOK_STOP_HELP).action(async (options) => {
|
|
2053
2118
|
const minEdits = parseMinEdits(options.minEdits);
|
|
2054
|
-
await runHookStop(
|
|
2119
|
+
await runHookStop({
|
|
2120
|
+
...minEdits !== void 0 ? { minEdits } : {},
|
|
2121
|
+
...options.block === true ? { block: true } : {}
|
|
2122
|
+
});
|
|
2123
|
+
});
|
|
2124
|
+
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) => {
|
|
2127
|
+
await runHookInstall(opts);
|
|
2128
|
+
});
|
|
2129
|
+
hook.command("uninstall").description(
|
|
2130
|
+
"Remove the basou Stop hook from ~/.claude/settings.json (leaves other hooks intact)"
|
|
2131
|
+
).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) => {
|
|
2132
|
+
await runHookUninstall(opts);
|
|
2133
|
+
});
|
|
2134
|
+
hook.command("status").description("Report whether the basou Stop hook is registered, and in which mode").option("--settings <path>", "Override the settings.json path (intended for tests)").option("-v, --verbose", "Show error causes").action(async (opts) => {
|
|
2135
|
+
await runHookStatus(opts);
|
|
2055
2136
|
});
|
|
2056
2137
|
}
|
|
2057
2138
|
var HOOK_STOP_HELP = `
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
"Stop": [
|
|
2062
|
-
{ "hooks": [ { "type": "command", "command": "basou hook stop" } ] }
|
|
2063
|
-
]
|
|
2064
|
-
}
|
|
2065
|
-
}
|
|
2139
|
+
Register this Stop hook reproducibly with 'basou hook install' (it writes the
|
|
2140
|
+
correct node-path command into ~/.claude/settings.json). 'basou hook uninstall'
|
|
2141
|
+
removes it; 'basou hook status' reports whether it is registered.
|
|
2066
2142
|
|
|
2067
2143
|
On every turn end basou inspects the session transcript. If the session did
|
|
2068
2144
|
content-substantive work but ran no capture verb ('basou decision capture' /
|
|
2069
|
-
'decision record' / 'note'), it
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2145
|
+
'decision record' / 'note'), it reminds the agent to record the why / next step.
|
|
2146
|
+
Substantive = EITHER >= ${DEFAULT_STOP_HOOK_MIN_EDITS} file edits (default) OR a free-form AskUserQuestion
|
|
2147
|
+
answer (an uncaptured conversational decision). Read-only Bash (ls / grep /
|
|
2148
|
+
git status) does NOT count.
|
|
2149
|
+
|
|
2150
|
+
By default the reminder is non-blocking: Claude sees it and may act on it or
|
|
2151
|
+
stop. With --block (opt-in enforcement, 'basou hook install --block') it instead
|
|
2152
|
+
returns decision:block, holding the agent in-turn to act on the reminder; the
|
|
2153
|
+
'stop_hook_active' flag and Claude Code's own loop prevention bound it to a
|
|
2154
|
+
single turn. Either way the hook fails open: a bad payload or unreadable
|
|
2155
|
+
transcript exits cleanly with no output.
|
|
2075
2156
|
`;
|
|
2076
2157
|
async function runHookStop(options, ctx = {}) {
|
|
2077
2158
|
try {
|
|
@@ -2110,15 +2191,14 @@ async function doRunHookStop(options, ctx) {
|
|
|
2110
2191
|
...options.minEdits !== void 0 ? { minEdits: options.minEdits } : {}
|
|
2111
2192
|
});
|
|
2112
2193
|
if (evaluation.kind !== "nudge") return;
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
`
|
|
2121
|
-
);
|
|
2194
|
+
const payloadJson = options.block === true ? JSON.stringify({ decision: "block", reason: evaluation.additionalContext }) : JSON.stringify({
|
|
2195
|
+
hookSpecificOutput: {
|
|
2196
|
+
hookEventName: "Stop",
|
|
2197
|
+
additionalContext: evaluation.additionalContext
|
|
2198
|
+
}
|
|
2199
|
+
});
|
|
2200
|
+
write(`${payloadJson}
|
|
2201
|
+
`);
|
|
2122
2202
|
}
|
|
2123
2203
|
function parseTranscript(transcript) {
|
|
2124
2204
|
const records = [];
|
|
@@ -2143,9 +2223,9 @@ async function defaultReadStdin() {
|
|
|
2143
2223
|
return Buffer.concat(chunks).toString("utf8");
|
|
2144
2224
|
}
|
|
2145
2225
|
async function readTranscriptBounded(path, maxBytes = MAX_TRANSCRIPT_BYTES) {
|
|
2146
|
-
const { size } = await
|
|
2226
|
+
const { size } = await stat3(path);
|
|
2147
2227
|
if (size <= maxBytes) return readFile2(path, "utf8");
|
|
2148
|
-
const handle = await
|
|
2228
|
+
const handle = await open2(path, "r");
|
|
2149
2229
|
try {
|
|
2150
2230
|
const buffer = Buffer.alloc(maxBytes);
|
|
2151
2231
|
const { bytesRead } = await handle.read(buffer, 0, maxBytes, size - maxBytes);
|
|
@@ -2160,12 +2240,160 @@ function parseMinEdits(raw) {
|
|
|
2160
2240
|
if (raw === void 0 || !/^\d+$/.test(raw)) return void 0;
|
|
2161
2241
|
return Number(raw);
|
|
2162
2242
|
}
|
|
2243
|
+
var DEFAULT_CLAUDE_SETTINGS_PATH = join6(homedir4(), ".claude", "settings.json");
|
|
2244
|
+
function resolveCliEntry() {
|
|
2245
|
+
return fileURLToPath(import.meta.url);
|
|
2246
|
+
}
|
|
2247
|
+
function normalizeInstallOptions(raw) {
|
|
2248
|
+
const out = {};
|
|
2249
|
+
if (raw.block === true) out.block = true;
|
|
2250
|
+
if (raw.settings !== void 0) out.settings = raw.settings;
|
|
2251
|
+
if (raw.dryRun === true) out.dryRun = true;
|
|
2252
|
+
if (raw.verbose === true) out.verbose = true;
|
|
2253
|
+
if (raw.minEdits !== void 0) {
|
|
2254
|
+
const parsed = parseMinEdits(raw.minEdits);
|
|
2255
|
+
if (parsed === void 0) {
|
|
2256
|
+
throw new Error("--min-edits must be a non-negative integer.");
|
|
2257
|
+
}
|
|
2258
|
+
out.minEdits = parsed;
|
|
2259
|
+
}
|
|
2260
|
+
return out;
|
|
2261
|
+
}
|
|
2262
|
+
async function readSettings(path) {
|
|
2263
|
+
let raw;
|
|
2264
|
+
try {
|
|
2265
|
+
raw = await readFile2(path, "utf8");
|
|
2266
|
+
} catch (error) {
|
|
2267
|
+
if (error instanceof Error && error.code === "ENOENT") {
|
|
2268
|
+
return { raw: null, parsed: void 0 };
|
|
2269
|
+
}
|
|
2270
|
+
throw error;
|
|
2271
|
+
}
|
|
2272
|
+
if (raw.trim().length === 0) return { raw, parsed: void 0 };
|
|
2273
|
+
try {
|
|
2274
|
+
return { raw, parsed: JSON.parse(raw) };
|
|
2275
|
+
} catch (error) {
|
|
2276
|
+
throw new Error(
|
|
2277
|
+
"The Claude settings.json is not valid JSON. Fix it (or remove it) and retry.",
|
|
2278
|
+
{
|
|
2279
|
+
cause: error
|
|
2280
|
+
}
|
|
2281
|
+
);
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
async function backupSettingsOnce(path, raw) {
|
|
2285
|
+
if (raw === null) return;
|
|
2286
|
+
const bak = `${path}.basou-bak`;
|
|
2287
|
+
try {
|
|
2288
|
+
await stat3(bak);
|
|
2289
|
+
return;
|
|
2290
|
+
} catch (error) {
|
|
2291
|
+
if (!(error instanceof Error && error.code === "ENOENT")) throw error;
|
|
2292
|
+
}
|
|
2293
|
+
await writeFileDurable(bak, raw);
|
|
2294
|
+
}
|
|
2295
|
+
async function runHookInstall(options, ctx = {}) {
|
|
2296
|
+
try {
|
|
2297
|
+
await doRunHookInstall(normalizeInstallOptions(options), ctx);
|
|
2298
|
+
} catch (error) {
|
|
2299
|
+
renderCliError(error, { verbose: isVerbose(options) });
|
|
2300
|
+
process.exitCode = 1;
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
async function doRunHookInstall(options, ctx = {}) {
|
|
2304
|
+
const settingsPath = options.settings ?? DEFAULT_CLAUDE_SETTINGS_PATH;
|
|
2305
|
+
const cliEntry = (ctx.resolveCliEntry ?? resolveCliEntry)();
|
|
2306
|
+
const command = buildStopHookCommand({
|
|
2307
|
+
cliEntry,
|
|
2308
|
+
...options.block === true ? { block: true } : {},
|
|
2309
|
+
...options.minEdits !== void 0 ? { minEdits: options.minEdits } : {}
|
|
2310
|
+
});
|
|
2311
|
+
const mode = options.block === true ? "blocking" : "advisory";
|
|
2312
|
+
await assertNotSymlink(settingsPath);
|
|
2313
|
+
const { raw, parsed } = await readSettings(settingsPath);
|
|
2314
|
+
const { settings, action } = upsertStopHook(parsed, command);
|
|
2315
|
+
const newBody = `${JSON.stringify(settings, null, 2)}
|
|
2316
|
+
`;
|
|
2317
|
+
if (raw !== null && newBody === raw) {
|
|
2318
|
+
console.log(`The basou Stop hook is already registered (${mode}); no change.`);
|
|
2319
|
+
return;
|
|
2320
|
+
}
|
|
2321
|
+
if (options.dryRun === true) {
|
|
2322
|
+
console.log(`[dry-run] Would ${action} the basou Stop hook (${mode}).`);
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
const recheck = await readSettings(settingsPath);
|
|
2326
|
+
if (recheck.raw !== raw) {
|
|
2327
|
+
throw new Error(
|
|
2328
|
+
"The settings.json changed during install; aborting so a concurrent edit is not overwritten. Re-run 'basou hook install'."
|
|
2329
|
+
);
|
|
2330
|
+
}
|
|
2331
|
+
await backupSettingsOnce(settingsPath, raw);
|
|
2332
|
+
await writeFileDurable(settingsPath, newBody);
|
|
2333
|
+
console.log(`${action === "installed" ? "Installed" : "Updated"} the basou Stop hook (${mode}).`);
|
|
2334
|
+
}
|
|
2335
|
+
async function runHookUninstall(options) {
|
|
2336
|
+
try {
|
|
2337
|
+
await doRunHookUninstall(normalizeInstallOptions(options));
|
|
2338
|
+
} catch (error) {
|
|
2339
|
+
renderCliError(error, { verbose: isVerbose(options) });
|
|
2340
|
+
process.exitCode = 1;
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
async function doRunHookUninstall(options) {
|
|
2344
|
+
const settingsPath = options.settings ?? DEFAULT_CLAUDE_SETTINGS_PATH;
|
|
2345
|
+
await assertNotSymlink(settingsPath);
|
|
2346
|
+
const { raw, parsed } = await readSettings(settingsPath);
|
|
2347
|
+
if (raw === null) {
|
|
2348
|
+
console.log("No settings.json; nothing to remove.");
|
|
2349
|
+
return;
|
|
2350
|
+
}
|
|
2351
|
+
const { settings, action } = removeStopHook(parsed);
|
|
2352
|
+
if (action === "absent") {
|
|
2353
|
+
console.log("No basou Stop hook found; nothing removed.");
|
|
2354
|
+
return;
|
|
2355
|
+
}
|
|
2356
|
+
const newBody = `${JSON.stringify(settings, null, 2)}
|
|
2357
|
+
`;
|
|
2358
|
+
if (options.dryRun === true) {
|
|
2359
|
+
console.log("[dry-run] Would remove the basou Stop hook from settings.json.");
|
|
2360
|
+
return;
|
|
2361
|
+
}
|
|
2362
|
+
const recheck = await readSettings(settingsPath);
|
|
2363
|
+
if (recheck.raw !== raw) {
|
|
2364
|
+
throw new Error(
|
|
2365
|
+
"The settings.json changed during uninstall; aborting so a concurrent edit is not overwritten. Re-run 'basou hook uninstall'."
|
|
2366
|
+
);
|
|
2367
|
+
}
|
|
2368
|
+
await backupSettingsOnce(settingsPath, raw);
|
|
2369
|
+
await writeFileDurable(settingsPath, newBody);
|
|
2370
|
+
console.log("Removed the basou Stop hook from settings.json.");
|
|
2371
|
+
}
|
|
2372
|
+
async function runHookStatus(options) {
|
|
2373
|
+
try {
|
|
2374
|
+
await doRunHookStatus(normalizeInstallOptions(options));
|
|
2375
|
+
} catch (error) {
|
|
2376
|
+
renderCliError(error, { verbose: isVerbose(options) });
|
|
2377
|
+
process.exitCode = 1;
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
async function doRunHookStatus(options) {
|
|
2381
|
+
const settingsPath = options.settings ?? DEFAULT_CLAUDE_SETTINGS_PATH;
|
|
2382
|
+
const { parsed } = await readSettings(settingsPath);
|
|
2383
|
+
const command = findBasouStopHookCommand(parsed);
|
|
2384
|
+
if (command === null) {
|
|
2385
|
+
console.log("basou Stop hook: not registered. Run 'basou hook install' to register it.");
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
const mode = / --block\b/.test(command) ? "blocking (opt-in enforcement)" : "advisory (non-blocking)";
|
|
2389
|
+
console.log(`basou Stop hook: registered, ${mode}.`);
|
|
2390
|
+
}
|
|
2163
2391
|
|
|
2164
2392
|
// src/commands/import.ts
|
|
2165
2393
|
import { createReadStream } from "fs";
|
|
2166
|
-
import { readdir, readFile as readFile3, rm, stat as
|
|
2167
|
-
import { homedir as
|
|
2168
|
-
import { basename as
|
|
2394
|
+
import { readdir, readFile as readFile3, rm, stat as stat4 } from "fs/promises";
|
|
2395
|
+
import { homedir as homedir5 } from "os";
|
|
2396
|
+
import { basename as basename3, dirname as dirname2, join as join7, resolve as resolve4 } from "path";
|
|
2169
2397
|
import { createInterface } from "readline";
|
|
2170
2398
|
import {
|
|
2171
2399
|
AGENT_INFRA_DIRS as AGENT_INFRA_DIRS2,
|
|
@@ -2251,11 +2479,11 @@ async function doRunImportClaudeCode(options, ctx) {
|
|
|
2251
2479
|
repoRoot: repositoryRoot,
|
|
2252
2480
|
cwd: ctx.cwd ?? process.cwd()
|
|
2253
2481
|
});
|
|
2254
|
-
const projectsRoot = ctx.claudeProjectsDir ??
|
|
2482
|
+
const projectsRoot = ctx.claudeProjectsDir ?? join7(homedir5(), ".claude", "projects");
|
|
2255
2483
|
const files = await selectTranscriptFiles(projectsRoot, projectPaths, options);
|
|
2256
2484
|
const projectSet = new Set(projectPaths);
|
|
2257
2485
|
const candidates = files.map((file) => {
|
|
2258
|
-
const externalId =
|
|
2486
|
+
const externalId = basename3(file, ".jsonl");
|
|
2259
2487
|
return {
|
|
2260
2488
|
externalId,
|
|
2261
2489
|
sourcePath: file,
|
|
@@ -2290,7 +2518,7 @@ async function doRunImportCodex(options, ctx) {
|
|
|
2290
2518
|
repoRoot: repositoryRoot,
|
|
2291
2519
|
cwd: ctx.cwd ?? process.cwd()
|
|
2292
2520
|
});
|
|
2293
|
-
const sessionsRoot = ctx.codexSessionsDir ??
|
|
2521
|
+
const sessionsRoot = ctx.codexSessionsDir ?? join7(homedir5(), ".codex", "sessions");
|
|
2294
2522
|
const rollouts = await discoverCodexRollouts(sessionsRoot, projectPaths, options);
|
|
2295
2523
|
const candidates = rollouts.map(({ file, externalId }) => ({
|
|
2296
2524
|
externalId,
|
|
@@ -2345,7 +2573,7 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
|
|
|
2345
2573
|
files: payload.session.related_files ?? [],
|
|
2346
2574
|
workingDirectory: payload.session.working_directory,
|
|
2347
2575
|
sourceRoots: projectPaths,
|
|
2348
|
-
masterRoot:
|
|
2576
|
+
masterRoot: dirname2(paths.root),
|
|
2349
2577
|
extraInRoot: AGENT_INFRA_DIRS2
|
|
2350
2578
|
});
|
|
2351
2579
|
if (scope.outOfRoot.length > 0) crossProject.push({ externalId, outOfRoot: scope.outOfRoot });
|
|
@@ -2419,7 +2647,7 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
|
|
|
2419
2647
|
if (priors.length > 0 && options.force === true) {
|
|
2420
2648
|
if (options.dryRun !== true) {
|
|
2421
2649
|
for (const { sessionId } of priors) {
|
|
2422
|
-
await rm(
|
|
2650
|
+
await rm(join7(paths.sessions, sessionId), { recursive: true, force: true });
|
|
2423
2651
|
}
|
|
2424
2652
|
}
|
|
2425
2653
|
counts.replaced++;
|
|
@@ -2530,7 +2758,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
|
|
|
2530
2758
|
if (options.session !== void 0) {
|
|
2531
2759
|
const matches = [];
|
|
2532
2760
|
for (const projectPath of projectPaths) {
|
|
2533
|
-
const file =
|
|
2761
|
+
const file = join7(projectsRoot, encodeProjectDir(projectPath), `${options.session}.jsonl`);
|
|
2534
2762
|
if (await pathExists(file)) matches.push(file);
|
|
2535
2763
|
}
|
|
2536
2764
|
if (matches.length === 0) {
|
|
@@ -2541,7 +2769,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
|
|
|
2541
2769
|
const files = [];
|
|
2542
2770
|
let anyDirFound = false;
|
|
2543
2771
|
for (const projectPath of projectPaths) {
|
|
2544
|
-
const transcriptDir =
|
|
2772
|
+
const transcriptDir = join7(projectsRoot, encodeProjectDir(projectPath));
|
|
2545
2773
|
let entries;
|
|
2546
2774
|
try {
|
|
2547
2775
|
entries = await readdir(transcriptDir);
|
|
@@ -2551,7 +2779,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
|
|
|
2551
2779
|
}
|
|
2552
2780
|
anyDirFound = true;
|
|
2553
2781
|
for (const name of entries) {
|
|
2554
|
-
if (name.endsWith(".jsonl")) files.push(
|
|
2782
|
+
if (name.endsWith(".jsonl")) files.push(join7(transcriptDir, name));
|
|
2555
2783
|
}
|
|
2556
2784
|
}
|
|
2557
2785
|
if (!anyDirFound) {
|
|
@@ -2561,7 +2789,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
|
|
|
2561
2789
|
}
|
|
2562
2790
|
async function pathExists(file) {
|
|
2563
2791
|
try {
|
|
2564
|
-
await
|
|
2792
|
+
await stat4(file);
|
|
2565
2793
|
return true;
|
|
2566
2794
|
} catch (error) {
|
|
2567
2795
|
if (findErrorCode5(error, "ENOENT")) return false;
|
|
@@ -2570,7 +2798,7 @@ async function pathExists(file) {
|
|
|
2570
2798
|
}
|
|
2571
2799
|
async function statSize(file) {
|
|
2572
2800
|
try {
|
|
2573
|
-
return (await
|
|
2801
|
+
return (await stat4(file)).size;
|
|
2574
2802
|
} catch (error) {
|
|
2575
2803
|
if (findErrorCode5(error, "ENOENT")) return void 0;
|
|
2576
2804
|
throw error;
|
|
@@ -2608,7 +2836,7 @@ async function findRolloutFiles(sessionsRoot) {
|
|
|
2608
2836
|
throw new Error("Failed to read Codex sessions directory", { cause: error });
|
|
2609
2837
|
}
|
|
2610
2838
|
for (const entry of entries) {
|
|
2611
|
-
const full =
|
|
2839
|
+
const full = join7(dir, entry.name);
|
|
2612
2840
|
if (entry.isDirectory()) {
|
|
2613
2841
|
await walk(full, false);
|
|
2614
2842
|
} else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) {
|
|
@@ -2786,7 +3014,7 @@ async function assertWorkspaceInitialized5(basouRoot) {
|
|
|
2786
3014
|
}
|
|
2787
3015
|
|
|
2788
3016
|
// src/commands/init.ts
|
|
2789
|
-
import { basename as
|
|
3017
|
+
import { basename as basename4, relative, resolve as resolve5 } from "path";
|
|
2790
3018
|
import {
|
|
2791
3019
|
appendBasouGitignore,
|
|
2792
3020
|
createManifest,
|
|
@@ -2825,7 +3053,7 @@ async function runInit(options, ctx = {}) {
|
|
|
2825
3053
|
async function doRunInit(options, ctx) {
|
|
2826
3054
|
const cwd = ctx.cwd ?? process.cwd();
|
|
2827
3055
|
const repositoryRoot = await resolveRepositoryRootForInit(cwd);
|
|
2828
|
-
const workspaceName = options.name ??
|
|
3056
|
+
const workspaceName = options.name ?? basename4(repositoryRoot);
|
|
2829
3057
|
let repositoryUrl;
|
|
2830
3058
|
if (options.repoUrl !== void 0) {
|
|
2831
3059
|
repositoryUrl = options.repoUrl === "" ? null : options.repoUrl;
|
|
@@ -3050,13 +3278,13 @@ import {
|
|
|
3050
3278
|
} from "@basou/core";
|
|
3051
3279
|
|
|
3052
3280
|
// src/lib/hosts-config.ts
|
|
3053
|
-
import { homedir as
|
|
3054
|
-
import { isAbsolute as isAbsolute2, join as
|
|
3281
|
+
import { homedir as homedir6 } from "os";
|
|
3282
|
+
import { isAbsolute as isAbsolute2, join as join8, resolve as resolve6 } from "path";
|
|
3055
3283
|
import { readYamlFile as readYamlFile4 } from "@basou/core";
|
|
3056
|
-
var DEFAULT_HOSTS_CONFIG_PATH =
|
|
3284
|
+
var DEFAULT_HOSTS_CONFIG_PATH = join8(homedir6(), ".basou", "hosts.yaml");
|
|
3057
3285
|
function expandTilde2(p) {
|
|
3058
|
-
if (p === "~") return
|
|
3059
|
-
if (p.startsWith("~/")) return
|
|
3286
|
+
if (p === "~") return homedir6();
|
|
3287
|
+
if (p.startsWith("~/")) return join8(homedir6(), p.slice(2));
|
|
3060
3288
|
return p;
|
|
3061
3289
|
}
|
|
3062
3290
|
function isRecord2(value) {
|
|
@@ -3382,7 +3610,7 @@ import {
|
|
|
3382
3610
|
writeFileSync,
|
|
3383
3611
|
writeSync
|
|
3384
3612
|
} from "fs";
|
|
3385
|
-
import { basename as
|
|
3613
|
+
import { basename as basename5, dirname as dirname3, isAbsolute as isAbsolute3, join as join9, relative as relative2, resolve as resolve7 } from "path";
|
|
3386
3614
|
import {
|
|
3387
3615
|
appendBasouGitignore as appendBasouGitignore2,
|
|
3388
3616
|
basouPaths as basouPaths10,
|
|
@@ -3686,7 +3914,7 @@ function classifySourceRoot(repositoryRoot, declaredPath) {
|
|
|
3686
3914
|
} catch {
|
|
3687
3915
|
return { path: declaredPath, kind: "unresolved" };
|
|
3688
3916
|
}
|
|
3689
|
-
return { path: declaredPath, kind: existsSync(
|
|
3917
|
+
return { path: declaredPath, kind: existsSync(join9(real, ".git")) ? "repo" : "non-repo" };
|
|
3690
3918
|
}
|
|
3691
3919
|
async function doRunProjectAdopt(options, ctx) {
|
|
3692
3920
|
const cwd = ctx.cwd ?? process.cwd();
|
|
@@ -3790,7 +4018,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
|
|
|
3790
4018
|
} catch {
|
|
3791
4019
|
return { ...base, reachable: false, instructionFiles: [] };
|
|
3792
4020
|
}
|
|
3793
|
-
if (!existsSync(
|
|
4021
|
+
if (!existsSync(join9(real, ".git"))) {
|
|
3794
4022
|
return { ...base, reachable: false, instructionFiles: [] };
|
|
3795
4023
|
}
|
|
3796
4024
|
try {
|
|
@@ -3798,7 +4026,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
|
|
|
3798
4026
|
for (const name of INSTRUCTION_FILES) {
|
|
3799
4027
|
let present = true;
|
|
3800
4028
|
try {
|
|
3801
|
-
lstatSync(
|
|
4029
|
+
lstatSync(join9(real, name));
|
|
3802
4030
|
} catch {
|
|
3803
4031
|
present = false;
|
|
3804
4032
|
}
|
|
@@ -3909,10 +4137,10 @@ function gatherRepoGitignore(repositoryRoot, entry) {
|
|
|
3909
4137
|
} catch {
|
|
3910
4138
|
return { ...base, reachable: false, currentLines: [] };
|
|
3911
4139
|
}
|
|
3912
|
-
if (!existsSync(
|
|
4140
|
+
if (!existsSync(join9(real, ".git"))) {
|
|
3913
4141
|
return { ...base, reachable: false, currentLines: [] };
|
|
3914
4142
|
}
|
|
3915
|
-
return { ...base, reachable: true, currentLines: readGitignoreLines(
|
|
4143
|
+
return { ...base, reachable: true, currentLines: readGitignoreLines(join9(real, ".gitignore")) };
|
|
3916
4144
|
}
|
|
3917
4145
|
function hasErrorCode(error) {
|
|
3918
4146
|
return error instanceof Error && typeof error.code === "string";
|
|
@@ -3926,7 +4154,7 @@ function readGitignoreLines(file) {
|
|
|
3926
4154
|
}
|
|
3927
4155
|
}
|
|
3928
4156
|
function applyGitignorePlan(repositoryRoot, plan) {
|
|
3929
|
-
const file =
|
|
4157
|
+
const file = join9(realpathSync(resolve7(repositoryRoot, plan.path)), ".gitignore");
|
|
3930
4158
|
let existing = "";
|
|
3931
4159
|
try {
|
|
3932
4160
|
existing = readFileSync(file, "utf8");
|
|
@@ -4057,16 +4285,16 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
|
|
|
4057
4285
|
if (real === anchorReal) {
|
|
4058
4286
|
return { ...base, isAnchor: true, reachable: true, canonicalPresent: false, files: [] };
|
|
4059
4287
|
}
|
|
4060
|
-
if (!existsSync(
|
|
4288
|
+
if (!existsSync(join9(real, ".git"))) {
|
|
4061
4289
|
return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
|
|
4062
4290
|
}
|
|
4063
|
-
const canonicalFile = isSelf ?
|
|
4291
|
+
const canonicalFile = isSelf ? join9(real, CANONICAL_FILE) : join9(anchorReal, "agents", basename5(real), CANONICAL_FILE);
|
|
4064
4292
|
if (!existsSync(canonicalFile)) {
|
|
4065
4293
|
return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
|
|
4066
4294
|
}
|
|
4067
4295
|
const files = expectedSymlinkTargets(real, canonicalFile, mode).map(
|
|
4068
4296
|
(spec) => {
|
|
4069
|
-
const { state, actualTarget } = inspectSymlink(
|
|
4297
|
+
const { state, actualTarget } = inspectSymlink(join9(real, spec.name), spec.target);
|
|
4070
4298
|
return {
|
|
4071
4299
|
name: spec.name,
|
|
4072
4300
|
expectedTarget: spec.target,
|
|
@@ -4080,7 +4308,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
|
|
|
4080
4308
|
isAnchor: false,
|
|
4081
4309
|
reachable: true,
|
|
4082
4310
|
canonicalPresent: true,
|
|
4083
|
-
canonicalName:
|
|
4311
|
+
canonicalName: basename5(real),
|
|
4084
4312
|
files
|
|
4085
4313
|
};
|
|
4086
4314
|
}
|
|
@@ -4095,9 +4323,9 @@ function applySymlinkPlan(repositoryRoot, plan) {
|
|
|
4095
4323
|
const created = [];
|
|
4096
4324
|
const failed = [];
|
|
4097
4325
|
for (const { name, target } of plan.toCreate) {
|
|
4098
|
-
const filePath =
|
|
4326
|
+
const filePath = join9(real, name);
|
|
4099
4327
|
try {
|
|
4100
|
-
mkdirSync(
|
|
4328
|
+
mkdirSync(dirname3(filePath), { recursive: true });
|
|
4101
4329
|
symlinkSync(target, filePath);
|
|
4102
4330
|
created.push(name);
|
|
4103
4331
|
} catch (error) {
|
|
@@ -4248,7 +4476,7 @@ function resolveViewDir(repositoryRoot, viewPath) {
|
|
|
4248
4476
|
return realpathSync(abs);
|
|
4249
4477
|
} catch {
|
|
4250
4478
|
try {
|
|
4251
|
-
return
|
|
4479
|
+
return join9(realpathSync(dirname3(abs)), basename5(abs));
|
|
4252
4480
|
} catch {
|
|
4253
4481
|
return abs;
|
|
4254
4482
|
}
|
|
@@ -4265,8 +4493,8 @@ function gatherViewRepo(repositoryRoot, viewDir, entry) {
|
|
|
4265
4493
|
if (expectedTarget === "" || expectedTarget === ".") {
|
|
4266
4494
|
return { path: entry.path, reachable: false };
|
|
4267
4495
|
}
|
|
4268
|
-
const linkName =
|
|
4269
|
-
const { state, actualTarget } = inspectSymlink(
|
|
4496
|
+
const linkName = basename5(repoReal);
|
|
4497
|
+
const { state, actualTarget } = inspectSymlink(join9(viewDir, linkName), expectedTarget);
|
|
4270
4498
|
return {
|
|
4271
4499
|
path: entry.path,
|
|
4272
4500
|
reachable: true,
|
|
@@ -4280,9 +4508,9 @@ function applyViewPlan(viewDir, toCreate) {
|
|
|
4280
4508
|
const created = [];
|
|
4281
4509
|
const failed = [];
|
|
4282
4510
|
for (const { name, target } of toCreate) {
|
|
4283
|
-
const filePath =
|
|
4511
|
+
const filePath = join9(viewDir, name);
|
|
4284
4512
|
try {
|
|
4285
|
-
mkdirSync(
|
|
4513
|
+
mkdirSync(dirname3(filePath), { recursive: true });
|
|
4286
4514
|
symlinkSync(target, filePath);
|
|
4287
4515
|
created.push(name);
|
|
4288
4516
|
} catch (error) {
|
|
@@ -4295,7 +4523,7 @@ var TOP_LEVEL_INSTRUCTION_FILES_LOWER = new Set(
|
|
|
4295
4523
|
INSTRUCTION_FILES.filter((f) => !f.includes("/")).map((f) => f.toLowerCase())
|
|
4296
4524
|
);
|
|
4297
4525
|
function classifyViewLink(viewDir, name, rosterRealpaths) {
|
|
4298
|
-
const filePath =
|
|
4526
|
+
const filePath = join9(viewDir, name);
|
|
4299
4527
|
let isLink;
|
|
4300
4528
|
try {
|
|
4301
4529
|
isLink = lstatSync(filePath).isSymbolicLink();
|
|
@@ -4324,7 +4552,7 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
|
|
|
4324
4552
|
if (!isDir) {
|
|
4325
4553
|
return { target, kind: existsSync(resolved) ? "non-repo" : "broken" };
|
|
4326
4554
|
}
|
|
4327
|
-
return { target, kind: existsSync(
|
|
4555
|
+
return { target, kind: existsSync(join9(resolved, ".git")) ? "repo" : "non-repo" };
|
|
4328
4556
|
}
|
|
4329
4557
|
function gatherExistingViewLinks(viewDir, rosterRealpaths) {
|
|
4330
4558
|
let names;
|
|
@@ -4349,7 +4577,7 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
|
|
|
4349
4577
|
const pruned = [];
|
|
4350
4578
|
const failed = [];
|
|
4351
4579
|
for (const { name } of toPrune) {
|
|
4352
|
-
const filePath =
|
|
4580
|
+
const filePath = join9(viewDir, name);
|
|
4353
4581
|
const c = classifyViewLink(viewDir, name, rosterRealpaths);
|
|
4354
4582
|
if (c === null || c.kind !== "repo") {
|
|
4355
4583
|
failed.push({
|
|
@@ -4395,7 +4623,7 @@ async function doRunProjectWorkspace(options, ctx) {
|
|
|
4395
4623
|
} else {
|
|
4396
4624
|
const viewDir = resolveViewDir(repositoryRoot, viewPath);
|
|
4397
4625
|
const facts = roster.map((entry) => gatherViewRepo(repositoryRoot, viewDir, entry));
|
|
4398
|
-
const rosterNames = roster.map((entry) =>
|
|
4626
|
+
const rosterNames = roster.map((entry) => basename5(resolve7(repositoryRoot, entry.path)));
|
|
4399
4627
|
const rosterRealpaths = /* @__PURE__ */ new Set();
|
|
4400
4628
|
for (const entry of roster) {
|
|
4401
4629
|
try {
|
|
@@ -4562,10 +4790,10 @@ async function runProjectPreset(options, ctx = {}) {
|
|
|
4562
4790
|
}
|
|
4563
4791
|
}
|
|
4564
4792
|
function canonicalFileFor(anchorReal, canonicalName) {
|
|
4565
|
-
return
|
|
4793
|
+
return join9(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
4566
4794
|
}
|
|
4567
4795
|
function canonicalLabelFor(canonicalName) {
|
|
4568
|
-
return
|
|
4796
|
+
return join9("agents", canonicalName, CANONICAL_FILE);
|
|
4569
4797
|
}
|
|
4570
4798
|
async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
|
|
4571
4799
|
const declared = {
|
|
@@ -4586,10 +4814,10 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
|
|
|
4586
4814
|
if (real === anchorReal) {
|
|
4587
4815
|
return { ...declared, isAnchor: true, reachable: true, canonicalPresent: false };
|
|
4588
4816
|
}
|
|
4589
|
-
if (!existsSync(
|
|
4817
|
+
if (!existsSync(join9(real, ".git"))) {
|
|
4590
4818
|
return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
|
|
4591
4819
|
}
|
|
4592
|
-
const canonicalName =
|
|
4820
|
+
const canonicalName = basename5(real);
|
|
4593
4821
|
let content;
|
|
4594
4822
|
try {
|
|
4595
4823
|
content = await readMarkdownFile4(canonicalFileFor(anchorReal, canonicalName));
|
|
@@ -4634,7 +4862,7 @@ async function applyPresetPlan(anchorReal, plan) {
|
|
|
4634
4862
|
isLink = false;
|
|
4635
4863
|
}
|
|
4636
4864
|
if (isLink) throw new Error(`Canonical is a symlink in ${label}`);
|
|
4637
|
-
if (plan.action === "create") mkdirSync(
|
|
4865
|
+
if (plan.action === "create") mkdirSync(dirname3(file), { recursive: true });
|
|
4638
4866
|
const existing = await readMarkdownFile4(file);
|
|
4639
4867
|
await writeMarkdownFile5(file, renderWithMarkers4(existing, plan.desiredBlock, label));
|
|
4640
4868
|
}
|
|
@@ -4824,28 +5052,28 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
|
|
|
4824
5052
|
return empty;
|
|
4825
5053
|
}
|
|
4826
5054
|
const anchorReal = realpathSync(repositoryRoot);
|
|
4827
|
-
const canonicalName =
|
|
5055
|
+
const canonicalName = basename5(real);
|
|
4828
5056
|
const instructionFiles = [];
|
|
4829
5057
|
for (const name of INSTRUCTION_FILES) {
|
|
4830
5058
|
try {
|
|
4831
|
-
lstatSync(
|
|
5059
|
+
lstatSync(join9(real, name));
|
|
4832
5060
|
instructionFiles.push(name);
|
|
4833
5061
|
} catch {
|
|
4834
5062
|
}
|
|
4835
5063
|
}
|
|
4836
5064
|
let ignored;
|
|
4837
5065
|
try {
|
|
4838
|
-
ignored = new Set(readGitignoreLines(
|
|
5066
|
+
ignored = new Set(readGitignoreLines(join9(real, ".gitignore")).map((l) => l.trim()));
|
|
4839
5067
|
} catch {
|
|
4840
5068
|
ignored = /* @__PURE__ */ new Set();
|
|
4841
5069
|
}
|
|
4842
5070
|
const gitignorePatterns = INSTRUCTION_FILES.filter((p) => ignored.has(p) || ignored.has(`/${p}`));
|
|
4843
|
-
const canonical2 = existsSync(
|
|
5071
|
+
const canonical2 = existsSync(join9(anchorReal, "agents", canonicalName, CANONICAL_FILE));
|
|
4844
5072
|
let viewLink = false;
|
|
4845
5073
|
const viewPath = manifest.workspace.view;
|
|
4846
5074
|
if (viewPath !== void 0) {
|
|
4847
5075
|
try {
|
|
4848
|
-
lstatSync(
|
|
5076
|
+
lstatSync(join9(resolveViewDir(repositoryRoot, viewPath), canonicalName));
|
|
4849
5077
|
viewLink = true;
|
|
4850
5078
|
} catch {
|
|
4851
5079
|
}
|
|
@@ -4859,11 +5087,11 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
|
|
|
4859
5087
|
};
|
|
4860
5088
|
}
|
|
4861
5089
|
function teardownExpectedTargets(repoReal, anchorReal, canonicalName) {
|
|
4862
|
-
const canonicalFile =
|
|
5090
|
+
const canonicalFile = join9(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
4863
5091
|
return expectedSymlinkTargets(repoReal, canonicalFile);
|
|
4864
5092
|
}
|
|
4865
5093
|
function viewLinkPointsAt(viewDir, name, repoReal) {
|
|
4866
|
-
const filePath =
|
|
5094
|
+
const filePath = join9(viewDir, name);
|
|
4867
5095
|
try {
|
|
4868
5096
|
if (!lstatSync(filePath).isSymbolicLink()) return false;
|
|
4869
5097
|
const target = readlinkSync(filePath);
|
|
@@ -4874,7 +5102,7 @@ function viewLinkPointsAt(viewDir, name, repoReal) {
|
|
|
4874
5102
|
}
|
|
4875
5103
|
}
|
|
4876
5104
|
function viewLinkPointsAtPath(viewDir, name, expectedRepoPath) {
|
|
4877
|
-
const filePath =
|
|
5105
|
+
const filePath = join9(viewDir, name);
|
|
4878
5106
|
try {
|
|
4879
5107
|
if (!lstatSync(filePath).isSymbolicLink()) return false;
|
|
4880
5108
|
const target = readlinkSync(filePath);
|
|
@@ -4894,7 +5122,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
|
|
|
4894
5122
|
}
|
|
4895
5123
|
const isAnchor = repoReal !== void 0 && repoReal === anchorReal;
|
|
4896
5124
|
const targetAbs = resolve7(repositoryRoot, target);
|
|
4897
|
-
const canonicalName =
|
|
5125
|
+
const canonicalName = basename5(repoReal ?? targetAbs);
|
|
4898
5126
|
const roster = manifest.repos ?? [];
|
|
4899
5127
|
const declaredEntry = roster.find((r) => {
|
|
4900
5128
|
try {
|
|
@@ -4915,17 +5143,17 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
|
|
|
4915
5143
|
}
|
|
4916
5144
|
if (rReal !== null) {
|
|
4917
5145
|
if (repoReal !== void 0 && rReal === repoReal) return false;
|
|
4918
|
-
return
|
|
5146
|
+
return basename5(rReal).toLowerCase() === cnFold;
|
|
4919
5147
|
}
|
|
4920
5148
|
if (resolve7(repositoryRoot, r.path) === targetAbs) return false;
|
|
4921
|
-
return
|
|
5149
|
+
return basename5(resolve7(repositoryRoot, r.path)).toLowerCase() === cnFold;
|
|
4922
5150
|
});
|
|
4923
5151
|
const collisionNote = "shared with another repo of the same basename, so it cannot be removed (check manually)";
|
|
4924
5152
|
const items = [];
|
|
4925
5153
|
if (!isAnchor) {
|
|
4926
5154
|
if (repoReal !== void 0) {
|
|
4927
5155
|
for (const spec of teardownExpectedTargets(repoReal, anchorReal, canonicalName)) {
|
|
4928
|
-
const { state, actualTarget } = inspectSymlink(
|
|
5156
|
+
const { state, actualTarget } = inspectSymlink(join9(repoReal, spec.name), spec.target);
|
|
4929
5157
|
if (isSelf) {
|
|
4930
5158
|
if (state !== "missing")
|
|
4931
5159
|
items.push({
|
|
@@ -4962,7 +5190,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
|
|
|
4962
5190
|
}
|
|
4963
5191
|
let ignored;
|
|
4964
5192
|
try {
|
|
4965
|
-
ignored = new Set(readGitignoreLines(
|
|
5193
|
+
ignored = new Set(readGitignoreLines(join9(repoReal, ".gitignore")).map((l) => l.trim()));
|
|
4966
5194
|
for (const p of INSTRUCTION_FILES) {
|
|
4967
5195
|
if (ignored.has(p) || ignored.has(`/${p}`)) {
|
|
4968
5196
|
items.push({
|
|
@@ -4985,7 +5213,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
|
|
|
4985
5213
|
const viewPath = manifest.workspace.view;
|
|
4986
5214
|
if (viewPath !== void 0) {
|
|
4987
5215
|
const viewDir = resolveViewDir(repositoryRoot, viewPath);
|
|
4988
|
-
const linkPath =
|
|
5216
|
+
const linkPath = join9(viewDir, canonicalName);
|
|
4989
5217
|
let isLink = false;
|
|
4990
5218
|
try {
|
|
4991
5219
|
isLink = lstatSync(linkPath).isSymbolicLink();
|
|
@@ -5011,8 +5239,8 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
|
|
|
5011
5239
|
else items.push({ kind: "view-symlink", label: canonicalName, state: "removable" });
|
|
5012
5240
|
}
|
|
5013
5241
|
}
|
|
5014
|
-
const canonicalFile =
|
|
5015
|
-
const canonicalLabel =
|
|
5242
|
+
const canonicalFile = join9(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
5243
|
+
const canonicalLabel = join9("agents", canonicalName, CANONICAL_FILE);
|
|
5016
5244
|
let canonicalIsLink = false;
|
|
5017
5245
|
try {
|
|
5018
5246
|
canonicalIsLink = lstatSync(canonicalFile).isSymbolicLink();
|
|
@@ -5116,12 +5344,12 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
|
|
|
5116
5344
|
);
|
|
5117
5345
|
for (const item of removable.filter((i) => i.kind === "instruction-symlink")) {
|
|
5118
5346
|
const expected = expectedByName.get(item.label);
|
|
5119
|
-
if (repoReal === null || expected === void 0 || inspectSymlink(
|
|
5347
|
+
if (repoReal === null || expected === void 0 || inspectSymlink(join9(repoReal, item.label), expected).state !== "correct") {
|
|
5120
5348
|
changed(item.label);
|
|
5121
5349
|
continue;
|
|
5122
5350
|
}
|
|
5123
5351
|
try {
|
|
5124
|
-
unlinkSync(
|
|
5352
|
+
unlinkSync(join9(repoReal, item.label));
|
|
5125
5353
|
removed.push(item.label);
|
|
5126
5354
|
} catch (error) {
|
|
5127
5355
|
failed.push({ label: item.label, message: failureReason(error) });
|
|
@@ -5140,7 +5368,7 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
|
|
|
5140
5368
|
continue;
|
|
5141
5369
|
}
|
|
5142
5370
|
try {
|
|
5143
|
-
unlinkSync(
|
|
5371
|
+
unlinkSync(join9(viewDir, item.label));
|
|
5144
5372
|
removed.push(`view/${item.label}`);
|
|
5145
5373
|
} catch (error) {
|
|
5146
5374
|
failed.push({ label: `view/${item.label}`, message: failureReason(error) });
|
|
@@ -5148,7 +5376,7 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
|
|
|
5148
5376
|
}
|
|
5149
5377
|
const NOFOLLOW = fsConstants.O_NOFOLLOW ?? 0;
|
|
5150
5378
|
for (const item of removable.filter((i) => i.kind === "canonical-block")) {
|
|
5151
|
-
const canonicalFile =
|
|
5379
|
+
const canonicalFile = join9(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
5152
5380
|
try {
|
|
5153
5381
|
if (lstatSync(canonicalFile).isSymbolicLink()) {
|
|
5154
5382
|
changed(item.label);
|
|
@@ -5383,7 +5611,7 @@ function renderProjectArchive(result) {
|
|
|
5383
5611
|
if (t.gitignorePatterns.length > 0)
|
|
5384
5612
|
items.push(`.gitignore instruction patterns (${t.gitignorePatterns.join(", ")})`);
|
|
5385
5613
|
if (t.canonical)
|
|
5386
|
-
items.push(`the anchor's canonical (agents/${
|
|
5614
|
+
items.push(`the anchor's canonical (agents/${basename5(result.target)}/AGENTS.md)`);
|
|
5387
5615
|
if (!t.inspected) {
|
|
5388
5616
|
lines.push(
|
|
5389
5617
|
"## Manual teardown (the repo could not be resolved on disk, so it was not inspected)"
|
|
@@ -5422,12 +5650,12 @@ function gatherRenameWiring(repositoryRoot, manifest, oldBasename) {
|
|
|
5422
5650
|
} catch {
|
|
5423
5651
|
return { canonicalDirOld: false, viewLinkOld: false };
|
|
5424
5652
|
}
|
|
5425
|
-
const canonicalDirOld = existsSync(
|
|
5653
|
+
const canonicalDirOld = existsSync(join9(anchorReal, "agents", oldBasename));
|
|
5426
5654
|
let viewLinkOld = false;
|
|
5427
5655
|
const viewPath = manifest.workspace.view;
|
|
5428
5656
|
if (viewPath !== void 0) {
|
|
5429
5657
|
try {
|
|
5430
|
-
lstatSync(
|
|
5658
|
+
lstatSync(join9(resolveViewDir(repositoryRoot, viewPath), oldBasename));
|
|
5431
5659
|
viewLinkOld = true;
|
|
5432
5660
|
} catch {
|
|
5433
5661
|
}
|
|
@@ -5586,7 +5814,7 @@ async function resolveRepositoryRootForNew(cwd) {
|
|
|
5586
5814
|
async function doRunProjectNew(repos, options, ctx) {
|
|
5587
5815
|
const cwd = ctx.cwd ?? process.cwd();
|
|
5588
5816
|
const repositoryRoot = await resolveRepositoryRootForNew(cwd);
|
|
5589
|
-
const workspaceName =
|
|
5817
|
+
const workspaceName = basename5(repositoryRoot);
|
|
5590
5818
|
const declared = repos.map((p) => {
|
|
5591
5819
|
const abs = resolve7(cwd, p);
|
|
5592
5820
|
let real;
|
|
@@ -5768,7 +5996,7 @@ function regularFileSpokes(repoReal) {
|
|
|
5768
5996
|
const out = [];
|
|
5769
5997
|
for (const spoke of ["CLAUDE.md", ".github/copilot-instructions.md"]) {
|
|
5770
5998
|
try {
|
|
5771
|
-
const st = lstatSync(
|
|
5999
|
+
const st = lstatSync(join9(repoReal, spoke));
|
|
5772
6000
|
if (!st.isSymbolicLink() && st.isFile()) out.push(spoke);
|
|
5773
6001
|
} catch {
|
|
5774
6002
|
}
|
|
@@ -5798,7 +6026,7 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argPath, argAbs, arg
|
|
|
5798
6026
|
const self = declaredEntry !== void 0 && instructionMode(declaredEntry) === "self";
|
|
5799
6027
|
const displayRel = argReal !== void 0 ? relative2(anchorReal, argReal) : relative2(repositoryRoot, argAbs);
|
|
5800
6028
|
const path = displayRel === "" ? "." : displayRel;
|
|
5801
|
-
const canonicalName =
|
|
6029
|
+
const canonicalName = basename5(argReal ?? argAbs);
|
|
5802
6030
|
if (argReal === void 0) {
|
|
5803
6031
|
return {
|
|
5804
6032
|
path,
|
|
@@ -5813,8 +6041,8 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argPath, argAbs, arg
|
|
|
5813
6041
|
};
|
|
5814
6042
|
}
|
|
5815
6043
|
const isAnchor = argReal === anchorReal;
|
|
5816
|
-
const reachable = existsSync(
|
|
5817
|
-
const canonicalFile =
|
|
6044
|
+
const reachable = existsSync(join9(argReal, ".git"));
|
|
6045
|
+
const canonicalFile = join9(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
5818
6046
|
return {
|
|
5819
6047
|
path,
|
|
5820
6048
|
declared,
|
|
@@ -5822,15 +6050,15 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argPath, argAbs, arg
|
|
|
5822
6050
|
isAnchor,
|
|
5823
6051
|
reachable,
|
|
5824
6052
|
canonicalName,
|
|
5825
|
-
agentsState: inspectAgentsState(
|
|
6053
|
+
agentsState: inspectAgentsState(join9(argReal, CANONICAL_FILE)),
|
|
5826
6054
|
canonicalExists: pathPresent(canonicalFile),
|
|
5827
6055
|
regularSpokes: regularFileSpokes(argReal)
|
|
5828
6056
|
};
|
|
5829
6057
|
}
|
|
5830
6058
|
function relocateAgentsFile(repoReal, canonicalFile) {
|
|
5831
|
-
const agentsFile =
|
|
6059
|
+
const agentsFile = join9(repoReal, CANONICAL_FILE);
|
|
5832
6060
|
try {
|
|
5833
|
-
mkdirSync(
|
|
6061
|
+
mkdirSync(dirname3(canonicalFile), { recursive: true });
|
|
5834
6062
|
} catch (error) {
|
|
5835
6063
|
return { ok: false, message: failureReason(error), partial: false };
|
|
5836
6064
|
}
|
|
@@ -5868,7 +6096,7 @@ async function doRunProjectRetrofit(repo, options, ctx) {
|
|
|
5868
6096
|
let failure;
|
|
5869
6097
|
let partial = false;
|
|
5870
6098
|
if (options.apply === true && plan.action === "relocate" && argReal !== void 0) {
|
|
5871
|
-
const canonicalFile =
|
|
6099
|
+
const canonicalFile = join9(anchorReal, "agents", plan.canonicalName, CANONICAL_FILE);
|
|
5872
6100
|
const res = relocateAgentsFile(argReal, canonicalFile);
|
|
5873
6101
|
if (res.ok) {
|
|
5874
6102
|
applied = true;
|
|
@@ -6000,70 +6228,17 @@ import {
|
|
|
6000
6228
|
removeMarkerSection as removeMarkerSection2
|
|
6001
6229
|
} from "@basou/core";
|
|
6002
6230
|
|
|
6003
|
-
// src/lib/durable-write.ts
|
|
6004
|
-
import { randomUUID } from "crypto";
|
|
6005
|
-
import { lstat, open as open2, rename, stat as stat4, unlink as unlink2 } from "fs/promises";
|
|
6006
|
-
import { basename as basename5, dirname as dirname3, join as join8 } from "path";
|
|
6007
|
-
async function assertNotSymlink(targetPath) {
|
|
6008
|
-
try {
|
|
6009
|
-
const st = await lstat(targetPath);
|
|
6010
|
-
if (st.isSymbolicLink()) {
|
|
6011
|
-
throw new Error(
|
|
6012
|
-
"Refusing to write through a symlink. Replace the symlinked target with a regular file (or remove it) and retry."
|
|
6013
|
-
);
|
|
6014
|
-
}
|
|
6015
|
-
} catch (error) {
|
|
6016
|
-
if (error instanceof Error && error.code === "ENOENT") return;
|
|
6017
|
-
throw error;
|
|
6018
|
-
}
|
|
6019
|
-
}
|
|
6020
|
-
async function writeFileDurable(targetPath, content) {
|
|
6021
|
-
const dir = dirname3(targetPath);
|
|
6022
|
-
const tmpPath = join8(dir, `.${basename5(targetPath)}.tmp.${randomUUID()}`);
|
|
6023
|
-
let mode = 420;
|
|
6024
|
-
try {
|
|
6025
|
-
mode = (await stat4(targetPath)).mode & 511;
|
|
6026
|
-
} catch (error) {
|
|
6027
|
-
if (!(error instanceof Error && error.code === "ENOENT")) {
|
|
6028
|
-
throw error;
|
|
6029
|
-
}
|
|
6030
|
-
}
|
|
6031
|
-
let handle;
|
|
6032
|
-
try {
|
|
6033
|
-
handle = await open2(tmpPath, "wx", mode);
|
|
6034
|
-
await handle.writeFile(content, "utf8");
|
|
6035
|
-
await handle.chmod(mode);
|
|
6036
|
-
await handle.sync();
|
|
6037
|
-
await handle.close();
|
|
6038
|
-
handle = void 0;
|
|
6039
|
-
await rename(tmpPath, targetPath);
|
|
6040
|
-
} catch (error) {
|
|
6041
|
-
if (handle) await handle.close().catch(() => void 0);
|
|
6042
|
-
await unlink2(tmpPath).catch(() => void 0);
|
|
6043
|
-
throw error;
|
|
6044
|
-
}
|
|
6045
|
-
try {
|
|
6046
|
-
const dirHandle = await open2(dir, "r");
|
|
6047
|
-
try {
|
|
6048
|
-
await dirHandle.sync();
|
|
6049
|
-
} finally {
|
|
6050
|
-
await dirHandle.close();
|
|
6051
|
-
}
|
|
6052
|
-
} catch {
|
|
6053
|
-
}
|
|
6054
|
-
}
|
|
6055
|
-
|
|
6056
6231
|
// src/lib/protocols-config.ts
|
|
6057
|
-
import { homedir as
|
|
6058
|
-
import { isAbsolute as isAbsolute4, join as
|
|
6232
|
+
import { homedir as homedir7 } from "os";
|
|
6233
|
+
import { isAbsolute as isAbsolute4, join as join10, resolve as resolve8 } from "path";
|
|
6059
6234
|
import { readYamlFile as readYamlFile5 } from "@basou/core";
|
|
6060
|
-
var DEFAULT_PROTOCOLS_CONFIG_PATH =
|
|
6061
|
-
var DEFAULT_TARGET_PATH =
|
|
6235
|
+
var DEFAULT_PROTOCOLS_CONFIG_PATH = join10(homedir7(), ".basou", "protocols.yaml");
|
|
6236
|
+
var DEFAULT_TARGET_PATH = join10(homedir7(), ".claude", "CLAUDE.md");
|
|
6062
6237
|
var ALLOWED_TOP_KEYS = /* @__PURE__ */ new Set(["version", "protocols"]);
|
|
6063
6238
|
var ALLOWED_ENTRY_KEYS = /* @__PURE__ */ new Set(["source", "title"]);
|
|
6064
6239
|
function expandTilde3(p) {
|
|
6065
|
-
if (p === "~") return
|
|
6066
|
-
if (p.startsWith("~/")) return
|
|
6240
|
+
if (p === "~") return homedir7();
|
|
6241
|
+
if (p.startsWith("~/")) return join10(homedir7(), p.slice(2));
|
|
6067
6242
|
return p;
|
|
6068
6243
|
}
|
|
6069
6244
|
function isRecord3(value) {
|
|
@@ -6315,16 +6490,16 @@ import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
|
|
|
6315
6490
|
|
|
6316
6491
|
// src/commands/refresh-watch.ts
|
|
6317
6492
|
import { readdir as readdir2, stat as stat5 } from "fs/promises";
|
|
6318
|
-
import { homedir as
|
|
6319
|
-
import { join as
|
|
6493
|
+
import { homedir as homedir8 } from "os";
|
|
6494
|
+
import { join as join11 } from "path";
|
|
6320
6495
|
import { findErrorCode as findErrorCode8 } from "@basou/core";
|
|
6321
6496
|
var DEFAULT_WATCH_INTERVAL_SEC = 30;
|
|
6322
6497
|
var MIN_WATCH_INTERVAL_SEC = 5;
|
|
6323
6498
|
var MAX_WATCH_INTERVAL_SEC = 86400;
|
|
6324
6499
|
function watchedRoots(ctx) {
|
|
6325
6500
|
return [
|
|
6326
|
-
ctx.codexSessionsDir ??
|
|
6327
|
-
ctx.claudeProjectsDir ??
|
|
6501
|
+
ctx.codexSessionsDir ?? join11(homedir8(), ".codex", "sessions"),
|
|
6502
|
+
ctx.claudeProjectsDir ?? join11(homedir8(), ".claude", "projects")
|
|
6328
6503
|
];
|
|
6329
6504
|
}
|
|
6330
6505
|
async function scanSourceLogs(roots) {
|
|
@@ -6338,7 +6513,7 @@ async function scanSourceLogs(roots) {
|
|
|
6338
6513
|
throw new Error("Failed to read a source log directory", { cause: error });
|
|
6339
6514
|
}
|
|
6340
6515
|
for (const entry of entries) {
|
|
6341
|
-
const full =
|
|
6516
|
+
const full = join11(dir, entry.name);
|
|
6342
6517
|
if (entry.isDirectory()) {
|
|
6343
6518
|
await walk(full);
|
|
6344
6519
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
@@ -6859,8 +7034,8 @@ function renderReviewGaps(summary) {
|
|
|
6859
7034
|
|
|
6860
7035
|
// src/commands/run.ts
|
|
6861
7036
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
6862
|
-
import { homedir as
|
|
6863
|
-
import { join as
|
|
7037
|
+
import { homedir as homedir9 } from "os";
|
|
7038
|
+
import { join as join12 } from "path";
|
|
6864
7039
|
import {
|
|
6865
7040
|
acquireLock as acquireLock5,
|
|
6866
7041
|
assertBasouRootSafe as assertBasouRootSafe11,
|
|
@@ -6913,13 +7088,13 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
6913
7088
|
await assertBasouRootSafe11(paths.root);
|
|
6914
7089
|
const manifest = await readManifest7(paths);
|
|
6915
7090
|
const sessionId = prefixedUlid4("ses");
|
|
6916
|
-
const sessionDir =
|
|
7091
|
+
const sessionDir = join12(paths.sessions, sessionId);
|
|
6917
7092
|
await mkdir2(sessionDir, { recursive: true });
|
|
6918
7093
|
const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
|
|
6919
7094
|
await coreAppendChainedEvent2(paths, sessionId, event);
|
|
6920
7095
|
});
|
|
6921
7096
|
const startedAt = now().toISOString();
|
|
6922
|
-
const sessionYamlPath =
|
|
7097
|
+
const sessionYamlPath = join12(sessionDir, "session.yaml");
|
|
6923
7098
|
const session = buildInitialSession2({
|
|
6924
7099
|
id: sessionId,
|
|
6925
7100
|
command,
|
|
@@ -7045,7 +7220,7 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
7045
7220
|
const rawRelated = computeRelatedFiles(preSnapshot, postSnapshot, diff);
|
|
7046
7221
|
const relatedFiles = sanitizeRelatedFiles(rawRelated, {
|
|
7047
7222
|
workingDirectory: repoRoot,
|
|
7048
|
-
homedir:
|
|
7223
|
+
homedir: homedir9()
|
|
7049
7224
|
}).sanitized;
|
|
7050
7225
|
const finalStatus = decideFinalStatus2(result, signalReceived);
|
|
7051
7226
|
await appendEvent(sessionDir, {
|
|
@@ -7189,7 +7364,7 @@ function buildInitialSession2(input) {
|
|
|
7189
7364
|
source: { ...claudeCodeAdapterMetadata },
|
|
7190
7365
|
started_at: input.startedAt,
|
|
7191
7366
|
status: "initialized",
|
|
7192
|
-
working_directory: sanitizeWorkingDirectory2(input.cwd, { homedir:
|
|
7367
|
+
working_directory: sanitizeWorkingDirectory2(input.cwd, { homedir: homedir9() }),
|
|
7193
7368
|
invocation: {
|
|
7194
7369
|
command: input.command,
|
|
7195
7370
|
args: [...input.args],
|
|
@@ -7262,7 +7437,7 @@ async function resolveRepositoryRootForRun(cwd) {
|
|
|
7262
7437
|
|
|
7263
7438
|
// src/commands/session.ts
|
|
7264
7439
|
import { readFile as readFile5 } from "fs/promises";
|
|
7265
|
-
import { basename as basename6, isAbsolute as isAbsolute6, join as
|
|
7440
|
+
import { basename as basename6, isAbsolute as isAbsolute6, join as join13, relative as relative3 } from "path";
|
|
7266
7441
|
import {
|
|
7267
7442
|
acquireLock as acquireLock6,
|
|
7268
7443
|
appendEventToExistingSession as appendEventToExistingSession3,
|
|
@@ -7387,8 +7562,8 @@ async function doRunSessionShow(idInput, options, ctx) {
|
|
|
7387
7562
|
const paths = basouPaths15(repositoryRoot);
|
|
7388
7563
|
await assertWorkspaceInitialized10(paths.root);
|
|
7389
7564
|
const sessionId = await resolveSessionId3(paths, idInput);
|
|
7390
|
-
const sessionDir =
|
|
7391
|
-
const sessionYamlPath =
|
|
7565
|
+
const sessionDir = join13(paths.sessions, sessionId);
|
|
7566
|
+
const sessionYamlPath = join13(sessionDir, "session.yaml");
|
|
7392
7567
|
let session;
|
|
7393
7568
|
try {
|
|
7394
7569
|
const raw = await readYamlFile7(sessionYamlPath);
|
|
@@ -8131,7 +8306,7 @@ async function resolveRepositoryRootForStatus(cwd) {
|
|
|
8131
8306
|
|
|
8132
8307
|
// src/commands/task.ts
|
|
8133
8308
|
import { readFile as readFile6 } from "fs/promises";
|
|
8134
|
-
import { join as
|
|
8309
|
+
import { join as join14 } from "path";
|
|
8135
8310
|
import {
|
|
8136
8311
|
archiveTask,
|
|
8137
8312
|
assertBasouRootSafe as assertBasouRootSafe15,
|
|
@@ -8457,7 +8632,7 @@ async function doRunTaskShow(idInput, options, ctx) {
|
|
|
8457
8632
|
const events = [];
|
|
8458
8633
|
const linkedSessionIds = new Set(doc.task.task.linked_sessions);
|
|
8459
8634
|
for (const s of sessions) {
|
|
8460
|
-
const sessionDir =
|
|
8635
|
+
const sessionDir = join14(paths.sessions, s.sessionId);
|
|
8461
8636
|
try {
|
|
8462
8637
|
for await (const ev of replayEvents3(sessionDir, {
|
|
8463
8638
|
onWarning: (w) => printReplayWarning(w, s.sessionId)
|
|
@@ -9363,7 +9538,7 @@ import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
|
|
|
9363
9538
|
// src/lib/portfolio-safety.ts
|
|
9364
9539
|
import { execFile } from "child_process";
|
|
9365
9540
|
import { lstat as lstat2, realpath as realpath2 } from "fs/promises";
|
|
9366
|
-
import { isAbsolute as isAbsolute7, join as
|
|
9541
|
+
import { isAbsolute as isAbsolute7, join as join15, relative as relative4, resolve as resolve10 } from "path";
|
|
9367
9542
|
import { promisify } from "util";
|
|
9368
9543
|
import { readManifest as readManifest11 } from "@basou/core";
|
|
9369
9544
|
var execFileAsync = promisify(execFile);
|
|
@@ -9387,7 +9562,7 @@ function isBasouPath(p) {
|
|
|
9387
9562
|
async function inspectRepo(repoPath) {
|
|
9388
9563
|
let hasEntry = false;
|
|
9389
9564
|
try {
|
|
9390
|
-
await lstat2(
|
|
9565
|
+
await lstat2(join15(repoPath, ".basou"));
|
|
9391
9566
|
hasEntry = true;
|
|
9392
9567
|
} catch (error) {
|
|
9393
9568
|
if (errorCode(error) !== "ENOENT") {
|
|
@@ -9488,7 +9663,7 @@ function formatSafetyReport(result) {
|
|
|
9488
9663
|
|
|
9489
9664
|
// src/lib/view-server.ts
|
|
9490
9665
|
import { createServer } from "http";
|
|
9491
|
-
import { join as
|
|
9666
|
+
import { join as join16 } from "path";
|
|
9492
9667
|
import {
|
|
9493
9668
|
computeWorkStats as computeWorkStats2,
|
|
9494
9669
|
enumerateApprovals as enumerateApprovals2,
|
|
@@ -10447,7 +10622,7 @@ async function sessionDetail(ws, sessionId) {
|
|
|
10447
10622
|
throw error;
|
|
10448
10623
|
}
|
|
10449
10624
|
try {
|
|
10450
|
-
const events = await readAllEvents2(
|
|
10625
|
+
const events = await readAllEvents2(join16(ws.paths.sessions, sessionId));
|
|
10451
10626
|
return { session, events };
|
|
10452
10627
|
} catch {
|
|
10453
10628
|
return { session, events: [], degraded: true };
|