@basou/cli 0.69.0 → 0.70.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 +169 -122
- package/dist/index.js.map +1 -1
- package/dist/program.js +169 -122
- package/dist/program.js.map +1 -1
- package/package.json +2 -2
package/dist/program.js
CHANGED
|
@@ -28,7 +28,8 @@ import {
|
|
|
28
28
|
prefixedUlid,
|
|
29
29
|
readSessionYaml,
|
|
30
30
|
readYamlFile,
|
|
31
|
-
replayEvents
|
|
31
|
+
replayEvents,
|
|
32
|
+
SessionFileNotAFileError
|
|
32
33
|
} from "@basou/core";
|
|
33
34
|
|
|
34
35
|
// src/lib/error-render.ts
|
|
@@ -126,7 +127,7 @@ function printSessionSkip(sid, reason) {
|
|
|
126
127
|
const short = shortSessionId(sid);
|
|
127
128
|
if (reason === "events_jsonl_unreadable") {
|
|
128
129
|
console.error(`Warning: skipped suspect check for ${short}: events.jsonl unreadable`);
|
|
129
|
-
} else if (reason === "session_dir_not_directory") {
|
|
130
|
+
} else if (reason === "session_dir_not_directory" || reason === "session_yaml_not_a_file") {
|
|
130
131
|
console.error(`Skipped ${sid}: ${reason}`);
|
|
131
132
|
} else {
|
|
132
133
|
console.error(`Skipped ${short}: ${reason}`);
|
|
@@ -147,6 +148,11 @@ function printSessionListSkip(sid, reason) {
|
|
|
147
148
|
case "session_dir_not_directory":
|
|
148
149
|
console.error(`Skipped ${sid}: not a directory (a symlink or a file is not followed)`);
|
|
149
150
|
break;
|
|
151
|
+
case "session_yaml_not_a_file":
|
|
152
|
+
console.error(
|
|
153
|
+
`Skipped ${sid}: session.yaml is not a regular file (a symlink or anything else is not followed)`
|
|
154
|
+
);
|
|
155
|
+
break;
|
|
150
156
|
}
|
|
151
157
|
}
|
|
152
158
|
function printTaskSkip(taskId, reason) {
|
|
@@ -423,7 +429,7 @@ async function doRunApprovalShow(idInput, options, ctx) {
|
|
|
423
429
|
`Warning: session ${sessionId} is not a directory (a symlink or a file is not followed); its events are not shown`
|
|
424
430
|
);
|
|
425
431
|
} else {
|
|
426
|
-
for await (const ev of replayEvents(
|
|
432
|
+
for await (const ev of replayEvents(paths, sessionId, {
|
|
427
433
|
onWarning: (w) => printReplayWarning(w, sessionId)
|
|
428
434
|
})) {
|
|
429
435
|
if (isApprovalEvent(ev) && ev.approval_id === id) {
|
|
@@ -523,8 +529,7 @@ async function doRunApprovalResolve(idInput, options, ctx, decision) {
|
|
|
523
529
|
const sessionLock = await acquireLock(paths, "session", approval.session_id);
|
|
524
530
|
try {
|
|
525
531
|
await assertSessionDirSafe(paths, approval.session_id);
|
|
526
|
-
const
|
|
527
|
-
for await (const ev of replayEvents(sessionDir, {
|
|
532
|
+
for await (const ev of replayEvents(paths, approval.session_id, {
|
|
528
533
|
onWarning: (w) => printReplayWarning(w, approval.session_id)
|
|
529
534
|
})) {
|
|
530
535
|
if (isApprovalEvent(ev) && ev.approval_id === approval.id && (ev.type === "approval_approved" || ev.type === "approval_rejected" || ev.type === "approval_expired")) {
|
|
@@ -537,7 +542,8 @@ async function doRunApprovalResolve(idInput, options, ctx, decision) {
|
|
|
537
542
|
let sessionStatus = null;
|
|
538
543
|
try {
|
|
539
544
|
sessionStatus = (await readSessionYaml(paths, approval.session_id)).session.status;
|
|
540
|
-
} catch {
|
|
545
|
+
} catch (error) {
|
|
546
|
+
if (error instanceof SessionFileNotAFileError) throw error;
|
|
541
547
|
sessionStatus = null;
|
|
542
548
|
}
|
|
543
549
|
const attachable = sessionStatus === "initialized" || sessionStatus === "running" || sessionStatus === "waiting_approval";
|
|
@@ -1046,6 +1052,7 @@ import {
|
|
|
1046
1052
|
readSessionYaml as readSessionYaml2,
|
|
1047
1053
|
reimportPreservingId,
|
|
1048
1054
|
SESSION_IMPORT_SCHEMA_VERSION,
|
|
1055
|
+
SessionFileNotAFileError as SessionFileNotAFileError2,
|
|
1049
1056
|
SessionImportPayloadSchema
|
|
1050
1057
|
} from "@basou/core";
|
|
1051
1058
|
|
|
@@ -1219,10 +1226,11 @@ async function resolveImportTarget(ctx) {
|
|
|
1219
1226
|
async function importDerivedSessions(paths, manifest, options, sourceKind, candidates, projectPaths, boundaryDeclared) {
|
|
1220
1227
|
await assertSessionStoreSafe(paths);
|
|
1221
1228
|
if (options.dryRun !== true) await assertLockStoreSafe2(paths);
|
|
1222
|
-
const {
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1229
|
+
const {
|
|
1230
|
+
byExternalId: existingByExternalId,
|
|
1231
|
+
notDirectories,
|
|
1232
|
+
yamlNotFiles
|
|
1233
|
+
} = await loadExistingByExternalId(paths, sourceKind);
|
|
1226
1234
|
let notDirectoriesWarned = false;
|
|
1227
1235
|
const seenThisRun = /* @__PURE__ */ new Set();
|
|
1228
1236
|
const crossProjectCheck = boundaryDeclared;
|
|
@@ -1315,12 +1323,20 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
|
|
|
1315
1323
|
}
|
|
1316
1324
|
counts.replaced++;
|
|
1317
1325
|
}
|
|
1318
|
-
if (priors.length === 0 && notDirectories.length > 0 && !notDirectoriesWarned) {
|
|
1326
|
+
if (priors.length === 0 && (notDirectories.length > 0 || yamlNotFiles.length > 0) && !notDirectoriesWarned) {
|
|
1319
1327
|
notDirectoriesWarned = true;
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1328
|
+
if (notDirectories.length > 0) {
|
|
1329
|
+
const one = notDirectories.length === 1;
|
|
1330
|
+
console.error(
|
|
1331
|
+
`Import: ${notDirectories.join(", ")} ${one ? "is not a directory" : "are not directories"} (a symlink or a file is not followed), so ${one ? "it was" : "they were"} not checked for an earlier import; a session imported there may be imported again`
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
if (yamlNotFiles.length > 0) {
|
|
1335
|
+
const one = yamlNotFiles.length === 1;
|
|
1336
|
+
console.error(
|
|
1337
|
+
`Import: the session.yaml of ${yamlNotFiles.join(", ")} ${one ? "is" : "are"} not a regular file (a symlink or anything else is not followed), so ${one ? "it was" : "they were"} not checked for an earlier import; a session imported there may be imported again`
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1324
1340
|
}
|
|
1325
1341
|
const result = await importSessionFromJson(paths, manifest, payload, {
|
|
1326
1342
|
dryRun: options.dryRun === true
|
|
@@ -1402,13 +1418,15 @@ async function loadExistingByExternalId(paths, sourceKind) {
|
|
|
1402
1418
|
try {
|
|
1403
1419
|
({ dirs: sessionIds, notDirectories } = await enumerateSessionEntries(paths));
|
|
1404
1420
|
} catch {
|
|
1405
|
-
return { byExternalId, notDirectories: [] };
|
|
1421
|
+
return { byExternalId, notDirectories: [], yamlNotFiles: [] };
|
|
1406
1422
|
}
|
|
1423
|
+
const yamlNotFiles = [];
|
|
1407
1424
|
for (const sessionId of sessionIds) {
|
|
1408
1425
|
let session;
|
|
1409
1426
|
try {
|
|
1410
1427
|
session = await readSessionYaml2(paths, sessionId);
|
|
1411
|
-
} catch {
|
|
1428
|
+
} catch (error) {
|
|
1429
|
+
if (error instanceof SessionFileNotAFileError2) yamlNotFiles.push(sessionId);
|
|
1412
1430
|
continue;
|
|
1413
1431
|
}
|
|
1414
1432
|
if (session.session.source.kind !== sourceKind) continue;
|
|
@@ -1423,7 +1441,7 @@ async function loadExistingByExternalId(paths, sourceKind) {
|
|
|
1423
1441
|
const match = typeof label === "string" ? label.match(/^claude-code import (\S+)$/) : null;
|
|
1424
1442
|
if (match?.[1] !== void 0) add(match[1], prior);
|
|
1425
1443
|
}
|
|
1426
|
-
return { byExternalId, notDirectories };
|
|
1444
|
+
return { byExternalId, notDirectories, yamlNotFiles };
|
|
1427
1445
|
}
|
|
1428
1446
|
async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
|
|
1429
1447
|
if (options.session !== void 0) {
|
|
@@ -2957,7 +2975,7 @@ async function doRunChannelClear(face, options) {
|
|
|
2957
2975
|
|
|
2958
2976
|
// src/commands/decision.ts
|
|
2959
2977
|
import { homedir as homedir3 } from "os";
|
|
2960
|
-
import {
|
|
2978
|
+
import { resolve as resolve4 } from "path";
|
|
2961
2979
|
import {
|
|
2962
2980
|
AGENT_INFRA_DIRS as AGENT_INFRA_DIRS2,
|
|
2963
2981
|
acquireLock as acquireLock2,
|
|
@@ -3538,9 +3556,8 @@ function isDecisionId(value) {
|
|
|
3538
3556
|
async function decisionExists(paths, decisionId) {
|
|
3539
3557
|
const entries = await loadSessionEntries(paths, { now: /* @__PURE__ */ new Date() });
|
|
3540
3558
|
for (const entry of entries) {
|
|
3541
|
-
const sessionDir = join7(paths.sessions, entry.sessionId);
|
|
3542
3559
|
try {
|
|
3543
|
-
for await (const ev of replayEvents2(
|
|
3560
|
+
for await (const ev of replayEvents2(paths, entry.sessionId, {})) {
|
|
3544
3561
|
if (ev.type === "decision_recorded" && ev.decision_id === decisionId) return true;
|
|
3545
3562
|
}
|
|
3546
3563
|
} catch {
|
|
@@ -3994,7 +4011,7 @@ async function assertWorkspaceInitialized5(basouRoot) {
|
|
|
3994
4011
|
// src/commands/exec.ts
|
|
3995
4012
|
import { mkdir as mkdir2, rm as rm2 } from "fs/promises";
|
|
3996
4013
|
import { homedir as homedir4 } from "os";
|
|
3997
|
-
import { join as
|
|
4014
|
+
import { join as join7 } from "path";
|
|
3998
4015
|
import {
|
|
3999
4016
|
acquireLock as acquireLock3,
|
|
4000
4017
|
assertBasouRootSafe as assertBasouRootSafe6,
|
|
@@ -4005,12 +4022,14 @@ import {
|
|
|
4005
4022
|
EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION3,
|
|
4006
4023
|
finalizeSessionYaml,
|
|
4007
4024
|
getSnapshot,
|
|
4025
|
+
lstatSessionFile,
|
|
4008
4026
|
overwriteYamlFile,
|
|
4009
4027
|
parseDuration,
|
|
4010
4028
|
prefixedUlid as prefixedUlid3,
|
|
4011
4029
|
readManifest as readManifest6,
|
|
4012
4030
|
readYamlFile as readYamlFile2,
|
|
4013
4031
|
SESSION_SCHEMA_VERSION,
|
|
4032
|
+
SessionFileNotAFileError as SessionFileNotAFileError3,
|
|
4014
4033
|
SessionSchema,
|
|
4015
4034
|
sanitizeWorkingDirectory,
|
|
4016
4035
|
writeObservedDuration,
|
|
@@ -4056,12 +4075,12 @@ async function runExec(command, args, options, ctx = {}) {
|
|
|
4056
4075
|
const manifest = await readManifest6(paths);
|
|
4057
4076
|
await assertSessionStoreSafe2(paths);
|
|
4058
4077
|
const sessionId = prefixedUlid3("ses");
|
|
4059
|
-
const sessionDir =
|
|
4078
|
+
const sessionDir = join7(paths.sessions, sessionId);
|
|
4060
4079
|
const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
|
|
4061
4080
|
await coreAppendChainedEvent(paths, sessionId, event);
|
|
4062
4081
|
});
|
|
4063
4082
|
const startedAt = now().toISOString();
|
|
4064
|
-
const sessionYamlPath =
|
|
4083
|
+
const sessionYamlPath = join7(sessionDir, "session.yaml");
|
|
4065
4084
|
const session = buildInitialSession({
|
|
4066
4085
|
id: sessionId,
|
|
4067
4086
|
command,
|
|
@@ -4110,7 +4129,7 @@ async function runExec(command, args, options, ctx = {}) {
|
|
|
4110
4129
|
});
|
|
4111
4130
|
const runningLock = await acquireLock3(paths, "session", sessionId);
|
|
4112
4131
|
try {
|
|
4113
|
-
await mutateSessionYaml(
|
|
4132
|
+
await mutateSessionYaml(paths, sessionId, (s) => {
|
|
4114
4133
|
s.session.status = "running";
|
|
4115
4134
|
});
|
|
4116
4135
|
} finally {
|
|
@@ -4287,7 +4306,14 @@ function buildInitialSession(input) {
|
|
|
4287
4306
|
}
|
|
4288
4307
|
};
|
|
4289
4308
|
}
|
|
4290
|
-
async function mutateSessionYaml(
|
|
4309
|
+
async function mutateSessionYaml(paths, sessionId, mutator) {
|
|
4310
|
+
const kind = await lstatSessionFile(paths, sessionId, "session.yaml").catch((error) => {
|
|
4311
|
+
throw new Error("Failed to read YAML file", { cause: error });
|
|
4312
|
+
});
|
|
4313
|
+
if (kind === "symlink" || kind === "not_a_file") {
|
|
4314
|
+
throw new SessionFileNotAFileError3(sessionId, "session.yaml", kind);
|
|
4315
|
+
}
|
|
4316
|
+
const filePath = join7(paths.sessions, sessionId, "session.yaml");
|
|
4291
4317
|
const raw = await readYamlFile2(filePath);
|
|
4292
4318
|
const parsed = SessionSchema.parse(raw);
|
|
4293
4319
|
mutator(parsed);
|
|
@@ -4410,7 +4436,7 @@ async function assertWorkspaceInitialized6(basouRoot) {
|
|
|
4410
4436
|
import { execFile } from "child_process";
|
|
4411
4437
|
import { open as open2, readFile as readFile4, realpath as realpath4, stat as stat4 } from "fs/promises";
|
|
4412
4438
|
import { homedir as homedir7 } from "os";
|
|
4413
|
-
import { join as
|
|
4439
|
+
import { join as join10 } from "path";
|
|
4414
4440
|
import { fileURLToPath } from "url";
|
|
4415
4441
|
import { promisify } from "util";
|
|
4416
4442
|
import {
|
|
@@ -4571,15 +4597,15 @@ function describeCodexHookTrust(trust) {
|
|
|
4571
4597
|
|
|
4572
4598
|
// src/lib/protocols-config.ts
|
|
4573
4599
|
import { homedir as homedir5 } from "os";
|
|
4574
|
-
import { isAbsolute, join as
|
|
4600
|
+
import { isAbsolute, join as join8, resolve as resolve5 } from "path";
|
|
4575
4601
|
import { readYamlFile as readYamlFile3 } from "@basou/core";
|
|
4576
|
-
var DEFAULT_PROTOCOLS_CONFIG_PATH =
|
|
4577
|
-
var DEFAULT_TARGET_PATH =
|
|
4602
|
+
var DEFAULT_PROTOCOLS_CONFIG_PATH = join8(homedir5(), ".basou", "protocols.yaml");
|
|
4603
|
+
var DEFAULT_TARGET_PATH = join8(homedir5(), ".claude", "CLAUDE.md");
|
|
4578
4604
|
var ALLOWED_TOP_KEYS = /* @__PURE__ */ new Set(["version", "protocols"]);
|
|
4579
4605
|
var ALLOWED_ENTRY_KEYS = /* @__PURE__ */ new Set(["source", "title"]);
|
|
4580
4606
|
function expandTilde(p) {
|
|
4581
4607
|
if (p === "~") return homedir5();
|
|
4582
|
-
if (p.startsWith("~/")) return
|
|
4608
|
+
if (p.startsWith("~/")) return join8(homedir5(), p.slice(2));
|
|
4583
4609
|
return p;
|
|
4584
4610
|
}
|
|
4585
4611
|
function isRecord(value) {
|
|
@@ -4657,12 +4683,12 @@ import {
|
|
|
4657
4683
|
|
|
4658
4684
|
// src/lib/hosts-config.ts
|
|
4659
4685
|
import { homedir as homedir6 } from "os";
|
|
4660
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4686
|
+
import { isAbsolute as isAbsolute2, join as join9, resolve as resolve6 } from "path";
|
|
4661
4687
|
import { readYamlFile as readYamlFile4 } from "@basou/core";
|
|
4662
|
-
var DEFAULT_HOSTS_CONFIG_PATH =
|
|
4688
|
+
var DEFAULT_HOSTS_CONFIG_PATH = join9(homedir6(), ".basou", "hosts.yaml");
|
|
4663
4689
|
function expandTilde2(p) {
|
|
4664
4690
|
if (p === "~") return homedir6();
|
|
4665
|
-
if (p.startsWith("~/")) return
|
|
4691
|
+
if (p.startsWith("~/")) return join9(homedir6(), p.slice(2));
|
|
4666
4692
|
return p;
|
|
4667
4693
|
}
|
|
4668
4694
|
function isRecord2(value) {
|
|
@@ -5246,7 +5272,7 @@ function parseMinEdits(raw) {
|
|
|
5246
5272
|
if (raw === void 0 || !/^\d+$/.test(raw)) return void 0;
|
|
5247
5273
|
return Number(raw);
|
|
5248
5274
|
}
|
|
5249
|
-
var DEFAULT_CLAUDE_SETTINGS_PATH =
|
|
5275
|
+
var DEFAULT_CLAUDE_SETTINGS_PATH = join10(homedir7(), ".claude", "settings.json");
|
|
5250
5276
|
function resolveCliEntry() {
|
|
5251
5277
|
return fileURLToPath(import.meta.url);
|
|
5252
5278
|
}
|
|
@@ -5603,7 +5629,7 @@ function describeHookMode(tiers) {
|
|
|
5603
5629
|
const gates = tiers.review ? "capture + review" : "capture";
|
|
5604
5630
|
return `${enforcement}, ${gates}`;
|
|
5605
5631
|
}
|
|
5606
|
-
var DEFAULT_CODEX_FACE_PATH =
|
|
5632
|
+
var DEFAULT_CODEX_FACE_PATH = join10(homedir7(), ".codex", "AGENTS.md");
|
|
5607
5633
|
var LEFTOVER_FACE_NOTE = (label) => `${label} still carries an orientation block rendered by an earlier basou (0.39 or before); every Codex session on this machine reads it. \`basou channel clear codex\` removes it.`;
|
|
5608
5634
|
async function faceHasLeftoverOrientationBlock(facePath) {
|
|
5609
5635
|
try {
|
|
@@ -5615,8 +5641,8 @@ async function faceHasLeftoverOrientationBlock(facePath) {
|
|
|
5615
5641
|
return false;
|
|
5616
5642
|
}
|
|
5617
5643
|
}
|
|
5618
|
-
var DEFAULT_CODEX_HOOKS_PATH =
|
|
5619
|
-
var DEFAULT_CODEX_CONFIG_PATH =
|
|
5644
|
+
var DEFAULT_CODEX_HOOKS_PATH = join10(homedir7(), ".codex", "hooks.json");
|
|
5645
|
+
var DEFAULT_CODEX_CONFIG_PATH = join10(homedir7(), ".codex", "config.toml");
|
|
5620
5646
|
async function readHooksFile(path) {
|
|
5621
5647
|
let raw;
|
|
5622
5648
|
try {
|
|
@@ -6223,7 +6249,7 @@ import {
|
|
|
6223
6249
|
writeFileSync,
|
|
6224
6250
|
writeSync
|
|
6225
6251
|
} from "fs";
|
|
6226
|
-
import { basename as basename7, dirname as dirname7, isAbsolute as isAbsolute3, join as
|
|
6252
|
+
import { basename as basename7, dirname as dirname7, isAbsolute as isAbsolute3, join as join11, relative as relative3, resolve as resolve9 } from "path";
|
|
6227
6253
|
import {
|
|
6228
6254
|
appendBasouGitignore as appendBasouGitignore2,
|
|
6229
6255
|
basouPaths as basouPaths13,
|
|
@@ -6604,7 +6630,7 @@ function classifySourceRoot(repositoryRoot, declaredPath) {
|
|
|
6604
6630
|
} catch {
|
|
6605
6631
|
return { path: declaredPath, kind: "unresolved" };
|
|
6606
6632
|
}
|
|
6607
|
-
return { path: declaredPath, kind: existsSync(
|
|
6633
|
+
return { path: declaredPath, kind: existsSync(join11(real, ".git")) ? "repo" : "non-repo" };
|
|
6608
6634
|
}
|
|
6609
6635
|
async function doRunProjectAdopt(options, ctx) {
|
|
6610
6636
|
const cwd = ctx.cwd ?? process.cwd();
|
|
@@ -6708,7 +6734,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
|
|
|
6708
6734
|
} catch {
|
|
6709
6735
|
return { ...base, reachable: false, instructionFiles: [] };
|
|
6710
6736
|
}
|
|
6711
|
-
if (!existsSync(
|
|
6737
|
+
if (!existsSync(join11(real, ".git"))) {
|
|
6712
6738
|
return { ...base, reachable: false, instructionFiles: [] };
|
|
6713
6739
|
}
|
|
6714
6740
|
try {
|
|
@@ -6716,7 +6742,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
|
|
|
6716
6742
|
for (const name of INSTRUCTION_FILES) {
|
|
6717
6743
|
let present = true;
|
|
6718
6744
|
try {
|
|
6719
|
-
lstatSync(
|
|
6745
|
+
lstatSync(join11(real, name));
|
|
6720
6746
|
} catch {
|
|
6721
6747
|
present = false;
|
|
6722
6748
|
}
|
|
@@ -6827,10 +6853,10 @@ function gatherRepoGitignore(repositoryRoot, entry) {
|
|
|
6827
6853
|
} catch {
|
|
6828
6854
|
return { ...base, reachable: false, currentLines: [] };
|
|
6829
6855
|
}
|
|
6830
|
-
if (!existsSync(
|
|
6856
|
+
if (!existsSync(join11(real, ".git"))) {
|
|
6831
6857
|
return { ...base, reachable: false, currentLines: [] };
|
|
6832
6858
|
}
|
|
6833
|
-
return { ...base, reachable: true, currentLines: readGitignoreLines(
|
|
6859
|
+
return { ...base, reachable: true, currentLines: readGitignoreLines(join11(real, ".gitignore")) };
|
|
6834
6860
|
}
|
|
6835
6861
|
function hasErrorCode(error) {
|
|
6836
6862
|
return error instanceof Error && typeof error.code === "string";
|
|
@@ -6844,7 +6870,7 @@ function readGitignoreLines(file) {
|
|
|
6844
6870
|
}
|
|
6845
6871
|
}
|
|
6846
6872
|
function applyGitignorePlan(repositoryRoot, plan) {
|
|
6847
|
-
const file =
|
|
6873
|
+
const file = join11(realpathSync(resolve9(repositoryRoot, plan.path)), ".gitignore");
|
|
6848
6874
|
let existing = "";
|
|
6849
6875
|
try {
|
|
6850
6876
|
existing = readFileSync(file, "utf8");
|
|
@@ -6984,7 +7010,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
|
|
|
6984
7010
|
return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
|
|
6985
7011
|
}
|
|
6986
7012
|
if (real === anchorReal) {
|
|
6987
|
-
const anchorCanonical =
|
|
7013
|
+
const anchorCanonical = join11(real, CANONICAL_FILE);
|
|
6988
7014
|
const anchorState = anchorCanonicalState(anchorCanonical);
|
|
6989
7015
|
if (anchorState === "absent") {
|
|
6990
7016
|
return { ...base, isAnchor: true, reachable: true, canonicalPresent: false, files: [] };
|
|
@@ -7004,7 +7030,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
|
|
|
7004
7030
|
anchorCanonical,
|
|
7005
7031
|
"self"
|
|
7006
7032
|
).map((spec) => {
|
|
7007
|
-
const { state, actualTarget } = inspectSymlink(
|
|
7033
|
+
const { state, actualTarget } = inspectSymlink(join11(real, spec.name), spec.target);
|
|
7008
7034
|
return {
|
|
7009
7035
|
name: spec.name,
|
|
7010
7036
|
expectedTarget: spec.target,
|
|
@@ -7021,16 +7047,16 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
|
|
|
7021
7047
|
files: anchorFiles
|
|
7022
7048
|
};
|
|
7023
7049
|
}
|
|
7024
|
-
if (!existsSync(
|
|
7050
|
+
if (!existsSync(join11(real, ".git"))) {
|
|
7025
7051
|
return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
|
|
7026
7052
|
}
|
|
7027
|
-
const canonicalFile = isSelf ?
|
|
7053
|
+
const canonicalFile = isSelf ? join11(real, CANONICAL_FILE) : join11(anchorReal, "agents", basename7(real), CANONICAL_FILE);
|
|
7028
7054
|
if (!existsSync(canonicalFile)) {
|
|
7029
7055
|
return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
|
|
7030
7056
|
}
|
|
7031
7057
|
const files = expectedSymlinkTargets(real, canonicalFile, mode).map(
|
|
7032
7058
|
(spec) => {
|
|
7033
|
-
const { state, actualTarget } = inspectSymlink(
|
|
7059
|
+
const { state, actualTarget } = inspectSymlink(join11(real, spec.name), spec.target);
|
|
7034
7060
|
return {
|
|
7035
7061
|
name: spec.name,
|
|
7036
7062
|
expectedTarget: spec.target,
|
|
@@ -7059,7 +7085,7 @@ function applySymlinkPlan(repositoryRoot, plan) {
|
|
|
7059
7085
|
const created = [];
|
|
7060
7086
|
const failed = [];
|
|
7061
7087
|
for (const { name, target } of plan.toCreate) {
|
|
7062
|
-
const filePath =
|
|
7088
|
+
const filePath = join11(real, name);
|
|
7063
7089
|
try {
|
|
7064
7090
|
mkdirSync(dirname7(filePath), { recursive: true });
|
|
7065
7091
|
symlinkSync(target, filePath);
|
|
@@ -7093,7 +7119,7 @@ function gatherViewSymlinks(repositoryRoot, anchorReal, roster, viewDir) {
|
|
|
7093
7119
|
if (!existsSync(canonicalFile)) return { kind: "missing-canonical", viewName };
|
|
7094
7120
|
const files = expectedSymlinkTargets(viewDir, canonicalFile, "hub").map(
|
|
7095
7121
|
(spec) => {
|
|
7096
|
-
const { state, actualTarget } = inspectSymlink(
|
|
7122
|
+
const { state, actualTarget } = inspectSymlink(join11(viewDir, spec.name), spec.target);
|
|
7097
7123
|
return {
|
|
7098
7124
|
name: spec.name,
|
|
7099
7125
|
expectedTarget: spec.target,
|
|
@@ -7109,7 +7135,7 @@ function applyViewSymlinks(viewDir, files) {
|
|
|
7109
7135
|
const failed = [];
|
|
7110
7136
|
for (const f of files) {
|
|
7111
7137
|
if (f.state !== "missing") continue;
|
|
7112
|
-
const filePath =
|
|
7138
|
+
const filePath = join11(viewDir, f.name);
|
|
7113
7139
|
try {
|
|
7114
7140
|
mkdirSync(dirname7(filePath), { recursive: true });
|
|
7115
7141
|
symlinkSync(f.expectedTarget, filePath);
|
|
@@ -7331,7 +7357,7 @@ function resolveViewDir(repositoryRoot, viewPath) {
|
|
|
7331
7357
|
return realpathSync(abs);
|
|
7332
7358
|
} catch {
|
|
7333
7359
|
try {
|
|
7334
|
-
return
|
|
7360
|
+
return join11(realpathSync(dirname7(abs)), basename7(abs));
|
|
7335
7361
|
} catch {
|
|
7336
7362
|
return abs;
|
|
7337
7363
|
}
|
|
@@ -7349,7 +7375,7 @@ function gatherViewRepo(repositoryRoot, viewDir, entry) {
|
|
|
7349
7375
|
return { path: entry.path, reachable: false };
|
|
7350
7376
|
}
|
|
7351
7377
|
const linkName = basename7(repoReal);
|
|
7352
|
-
const { state, actualTarget } = inspectSymlink(
|
|
7378
|
+
const { state, actualTarget } = inspectSymlink(join11(viewDir, linkName), expectedTarget);
|
|
7353
7379
|
return {
|
|
7354
7380
|
path: entry.path,
|
|
7355
7381
|
reachable: true,
|
|
@@ -7363,7 +7389,7 @@ function applyViewPlan(viewDir, toCreate) {
|
|
|
7363
7389
|
const created = [];
|
|
7364
7390
|
const failed = [];
|
|
7365
7391
|
for (const { name, target } of toCreate) {
|
|
7366
|
-
const filePath =
|
|
7392
|
+
const filePath = join11(viewDir, name);
|
|
7367
7393
|
try {
|
|
7368
7394
|
mkdirSync(dirname7(filePath), { recursive: true });
|
|
7369
7395
|
symlinkSync(target, filePath);
|
|
@@ -7378,7 +7404,7 @@ var TOP_LEVEL_INSTRUCTION_FILES_LOWER = new Set(
|
|
|
7378
7404
|
INSTRUCTION_FILES.filter((f) => !f.includes("/")).map((f) => f.toLowerCase())
|
|
7379
7405
|
);
|
|
7380
7406
|
function classifyViewLink(viewDir, name, rosterRealpaths) {
|
|
7381
|
-
const filePath =
|
|
7407
|
+
const filePath = join11(viewDir, name);
|
|
7382
7408
|
let isLink;
|
|
7383
7409
|
try {
|
|
7384
7410
|
isLink = lstatSync(filePath).isSymbolicLink();
|
|
@@ -7407,7 +7433,7 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
|
|
|
7407
7433
|
if (!isDir) {
|
|
7408
7434
|
return { target, kind: existsSync(resolved) ? "non-repo" : "broken" };
|
|
7409
7435
|
}
|
|
7410
|
-
return { target, kind: existsSync(
|
|
7436
|
+
return { target, kind: existsSync(join11(resolved, ".git")) ? "repo" : "non-repo" };
|
|
7411
7437
|
}
|
|
7412
7438
|
function gatherExistingViewLinks(viewDir, rosterRealpaths) {
|
|
7413
7439
|
let names;
|
|
@@ -7432,7 +7458,7 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
|
|
|
7432
7458
|
const pruned = [];
|
|
7433
7459
|
const failed = [];
|
|
7434
7460
|
for (const { name } of toPrune) {
|
|
7435
|
-
const filePath =
|
|
7461
|
+
const filePath = join11(viewDir, name);
|
|
7436
7462
|
const c = classifyViewLink(viewDir, name, rosterRealpaths);
|
|
7437
7463
|
if (c === null || c.kind !== "repo") {
|
|
7438
7464
|
failed.push({
|
|
@@ -7645,10 +7671,10 @@ async function runProjectPreset(options, ctx = {}) {
|
|
|
7645
7671
|
}
|
|
7646
7672
|
}
|
|
7647
7673
|
function canonicalFileFor(anchorReal, canonicalName) {
|
|
7648
|
-
return
|
|
7674
|
+
return join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
7649
7675
|
}
|
|
7650
7676
|
function canonicalLabelFor(canonicalName) {
|
|
7651
|
-
return
|
|
7677
|
+
return join11("agents", canonicalName, CANONICAL_FILE);
|
|
7652
7678
|
}
|
|
7653
7679
|
async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
|
|
7654
7680
|
const declared = {
|
|
@@ -7669,7 +7695,7 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
|
|
|
7669
7695
|
if (real === anchorReal) {
|
|
7670
7696
|
return { ...declared, isAnchor: true, reachable: true, canonicalPresent: false };
|
|
7671
7697
|
}
|
|
7672
|
-
if (!existsSync(
|
|
7698
|
+
if (!existsSync(join11(real, ".git"))) {
|
|
7673
7699
|
return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
|
|
7674
7700
|
}
|
|
7675
7701
|
const canonicalName = basename7(real);
|
|
@@ -8051,24 +8077,24 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
|
|
|
8051
8077
|
const instructionFiles = [];
|
|
8052
8078
|
for (const name of INSTRUCTION_FILES) {
|
|
8053
8079
|
try {
|
|
8054
|
-
lstatSync(
|
|
8080
|
+
lstatSync(join11(real, name));
|
|
8055
8081
|
instructionFiles.push(name);
|
|
8056
8082
|
} catch {
|
|
8057
8083
|
}
|
|
8058
8084
|
}
|
|
8059
8085
|
let ignored;
|
|
8060
8086
|
try {
|
|
8061
|
-
ignored = new Set(readGitignoreLines(
|
|
8087
|
+
ignored = new Set(readGitignoreLines(join11(real, ".gitignore")).map((l) => l.trim()));
|
|
8062
8088
|
} catch {
|
|
8063
8089
|
ignored = /* @__PURE__ */ new Set();
|
|
8064
8090
|
}
|
|
8065
8091
|
const gitignorePatterns = INSTRUCTION_FILES.filter((p) => ignored.has(p) || ignored.has(`/${p}`));
|
|
8066
|
-
const canonical2 = existsSync(
|
|
8092
|
+
const canonical2 = existsSync(join11(anchorReal, "agents", canonicalName, CANONICAL_FILE));
|
|
8067
8093
|
let viewLink = false;
|
|
8068
8094
|
const viewPath = manifest.workspace.view;
|
|
8069
8095
|
if (viewPath !== void 0) {
|
|
8070
8096
|
try {
|
|
8071
|
-
lstatSync(
|
|
8097
|
+
lstatSync(join11(resolveViewDir(repositoryRoot, viewPath), canonicalName));
|
|
8072
8098
|
viewLink = true;
|
|
8073
8099
|
} catch {
|
|
8074
8100
|
}
|
|
@@ -8082,11 +8108,11 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
|
|
|
8082
8108
|
};
|
|
8083
8109
|
}
|
|
8084
8110
|
function teardownExpectedTargets(repoReal, anchorReal, canonicalName) {
|
|
8085
|
-
const canonicalFile =
|
|
8111
|
+
const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
8086
8112
|
return expectedSymlinkTargets(repoReal, canonicalFile);
|
|
8087
8113
|
}
|
|
8088
8114
|
function viewLinkPointsAt(viewDir, name, repoReal) {
|
|
8089
|
-
const filePath =
|
|
8115
|
+
const filePath = join11(viewDir, name);
|
|
8090
8116
|
try {
|
|
8091
8117
|
if (!lstatSync(filePath).isSymbolicLink()) return false;
|
|
8092
8118
|
const target = readlinkSync(filePath);
|
|
@@ -8097,7 +8123,7 @@ function viewLinkPointsAt(viewDir, name, repoReal) {
|
|
|
8097
8123
|
}
|
|
8098
8124
|
}
|
|
8099
8125
|
function viewLinkPointsAtPath(viewDir, name, expectedRepoPath) {
|
|
8100
|
-
const filePath =
|
|
8126
|
+
const filePath = join11(viewDir, name);
|
|
8101
8127
|
try {
|
|
8102
8128
|
if (!lstatSync(filePath).isSymbolicLink()) return false;
|
|
8103
8129
|
const target = readlinkSync(filePath);
|
|
@@ -8148,7 +8174,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
|
|
|
8148
8174
|
if (!isAnchor) {
|
|
8149
8175
|
if (repoReal !== void 0) {
|
|
8150
8176
|
for (const spec of teardownExpectedTargets(repoReal, anchorReal, canonicalName)) {
|
|
8151
|
-
const { state, actualTarget } = inspectSymlink(
|
|
8177
|
+
const { state, actualTarget } = inspectSymlink(join11(repoReal, spec.name), spec.target);
|
|
8152
8178
|
if (isSelf) {
|
|
8153
8179
|
if (state !== "missing")
|
|
8154
8180
|
items.push({
|
|
@@ -8185,7 +8211,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
|
|
|
8185
8211
|
}
|
|
8186
8212
|
let ignored;
|
|
8187
8213
|
try {
|
|
8188
|
-
ignored = new Set(readGitignoreLines(
|
|
8214
|
+
ignored = new Set(readGitignoreLines(join11(repoReal, ".gitignore")).map((l) => l.trim()));
|
|
8189
8215
|
for (const p of INSTRUCTION_FILES) {
|
|
8190
8216
|
if (ignored.has(p) || ignored.has(`/${p}`)) {
|
|
8191
8217
|
items.push({
|
|
@@ -8208,7 +8234,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
|
|
|
8208
8234
|
const viewPath = manifest.workspace.view;
|
|
8209
8235
|
if (viewPath !== void 0) {
|
|
8210
8236
|
const viewDir = resolveViewDir(repositoryRoot, viewPath);
|
|
8211
|
-
const linkPath =
|
|
8237
|
+
const linkPath = join11(viewDir, canonicalName);
|
|
8212
8238
|
let isLink = false;
|
|
8213
8239
|
try {
|
|
8214
8240
|
isLink = lstatSync(linkPath).isSymbolicLink();
|
|
@@ -8234,8 +8260,8 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
|
|
|
8234
8260
|
else items.push({ kind: "view-symlink", label: canonicalName, state: "removable" });
|
|
8235
8261
|
}
|
|
8236
8262
|
}
|
|
8237
|
-
const canonicalFile =
|
|
8238
|
-
const canonicalLabel =
|
|
8263
|
+
const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
8264
|
+
const canonicalLabel = join11("agents", canonicalName, CANONICAL_FILE);
|
|
8239
8265
|
let canonicalIsLink = false;
|
|
8240
8266
|
try {
|
|
8241
8267
|
canonicalIsLink = lstatSync(canonicalFile).isSymbolicLink();
|
|
@@ -8339,12 +8365,12 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
|
|
|
8339
8365
|
);
|
|
8340
8366
|
for (const item of removable.filter((i) => i.kind === "instruction-symlink")) {
|
|
8341
8367
|
const expected = expectedByName.get(item.label);
|
|
8342
|
-
if (repoReal === null || expected === void 0 || inspectSymlink(
|
|
8368
|
+
if (repoReal === null || expected === void 0 || inspectSymlink(join11(repoReal, item.label), expected).state !== "correct") {
|
|
8343
8369
|
changed(item.label);
|
|
8344
8370
|
continue;
|
|
8345
8371
|
}
|
|
8346
8372
|
try {
|
|
8347
|
-
unlinkSync(
|
|
8373
|
+
unlinkSync(join11(repoReal, item.label));
|
|
8348
8374
|
removed.push(item.label);
|
|
8349
8375
|
} catch (error) {
|
|
8350
8376
|
failed.push({ label: item.label, message: failureReason(error) });
|
|
@@ -8363,7 +8389,7 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
|
|
|
8363
8389
|
continue;
|
|
8364
8390
|
}
|
|
8365
8391
|
try {
|
|
8366
|
-
unlinkSync(
|
|
8392
|
+
unlinkSync(join11(viewDir, item.label));
|
|
8367
8393
|
removed.push(`view/${item.label}`);
|
|
8368
8394
|
} catch (error) {
|
|
8369
8395
|
failed.push({ label: `view/${item.label}`, message: failureReason(error) });
|
|
@@ -8371,7 +8397,7 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
|
|
|
8371
8397
|
}
|
|
8372
8398
|
const NOFOLLOW = fsConstants.O_NOFOLLOW ?? 0;
|
|
8373
8399
|
for (const item of removable.filter((i) => i.kind === "canonical-block")) {
|
|
8374
|
-
const canonicalFile =
|
|
8400
|
+
const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
8375
8401
|
try {
|
|
8376
8402
|
if (lstatSync(canonicalFile).isSymbolicLink()) {
|
|
8377
8403
|
changed(item.label);
|
|
@@ -8645,12 +8671,12 @@ function gatherRenameWiring(repositoryRoot, manifest, oldBasename) {
|
|
|
8645
8671
|
} catch {
|
|
8646
8672
|
return { canonicalDirOld: false, viewLinkOld: false };
|
|
8647
8673
|
}
|
|
8648
|
-
const canonicalDirOld = existsSync(
|
|
8674
|
+
const canonicalDirOld = existsSync(join11(anchorReal, "agents", oldBasename));
|
|
8649
8675
|
let viewLinkOld = false;
|
|
8650
8676
|
const viewPath = manifest.workspace.view;
|
|
8651
8677
|
if (viewPath !== void 0) {
|
|
8652
8678
|
try {
|
|
8653
|
-
lstatSync(
|
|
8679
|
+
lstatSync(join11(resolveViewDir(repositoryRoot, viewPath), oldBasename));
|
|
8654
8680
|
viewLinkOld = true;
|
|
8655
8681
|
} catch {
|
|
8656
8682
|
}
|
|
@@ -8995,7 +9021,7 @@ async function doRunProjectSeedAnchor(options, ctx) {
|
|
|
8995
9021
|
console.log("\u2139\uFE0F No repo roster declared \u2014 nothing to seed.");
|
|
8996
9022
|
return;
|
|
8997
9023
|
}
|
|
8998
|
-
const anchorDoc =
|
|
9024
|
+
const anchorDoc = join11(repositoryRoot, CANONICAL_FILE);
|
|
8999
9025
|
if (pathPresent(anchorDoc)) {
|
|
9000
9026
|
console.log(
|
|
9001
9027
|
`\u2705 The anchor's own \`${CANONICAL_FILE}\` already exists \u2014 hand-maintained, left untouched.`
|
|
@@ -9057,7 +9083,7 @@ function regularFileSpokes(repoReal) {
|
|
|
9057
9083
|
const out = [];
|
|
9058
9084
|
for (const spoke of ["CLAUDE.md", ".github/copilot-instructions.md"]) {
|
|
9059
9085
|
try {
|
|
9060
|
-
const st = lstatSync(
|
|
9086
|
+
const st = lstatSync(join11(repoReal, spoke));
|
|
9061
9087
|
if (!st.isSymbolicLink() && st.isFile()) out.push(spoke);
|
|
9062
9088
|
} catch {
|
|
9063
9089
|
}
|
|
@@ -9103,8 +9129,8 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argAbs, argReal, vie
|
|
|
9103
9129
|
};
|
|
9104
9130
|
}
|
|
9105
9131
|
const isAnchor = argReal === anchorReal;
|
|
9106
|
-
const reachable = existsSync(
|
|
9107
|
-
const canonicalFile =
|
|
9132
|
+
const reachable = existsSync(join11(argReal, ".git"));
|
|
9133
|
+
const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
9108
9134
|
return {
|
|
9109
9135
|
path,
|
|
9110
9136
|
declared,
|
|
@@ -9113,13 +9139,13 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argAbs, argReal, vie
|
|
|
9113
9139
|
reachable,
|
|
9114
9140
|
canonicalName,
|
|
9115
9141
|
...viewCanonicalName !== void 0 ? { viewCanonicalName } : {},
|
|
9116
|
-
agentsState: inspectAgentsState(
|
|
9142
|
+
agentsState: inspectAgentsState(join11(argReal, CANONICAL_FILE)),
|
|
9117
9143
|
canonicalExists: pathPresent(canonicalFile),
|
|
9118
9144
|
regularSpokes: regularFileSpokes(argReal)
|
|
9119
9145
|
};
|
|
9120
9146
|
}
|
|
9121
9147
|
function relocateAgentsFile(repoReal, canonicalFile) {
|
|
9122
|
-
const agentsFile =
|
|
9148
|
+
const agentsFile = join11(repoReal, CANONICAL_FILE);
|
|
9123
9149
|
try {
|
|
9124
9150
|
mkdirSync(dirname7(canonicalFile), { recursive: true });
|
|
9125
9151
|
} catch (error) {
|
|
@@ -9235,7 +9261,7 @@ async function doRunProjectRetrofit(repo, options, ctx) {
|
|
|
9235
9261
|
let failure;
|
|
9236
9262
|
let partial = false;
|
|
9237
9263
|
if (options.apply === true && plan.action === "relocate" && argReal !== void 0) {
|
|
9238
|
-
const canonicalFile =
|
|
9264
|
+
const canonicalFile = join11(anchorReal, "agents", plan.canonicalName, CANONICAL_FILE);
|
|
9239
9265
|
const res = relocateAgentsFile(argReal, canonicalFile);
|
|
9240
9266
|
if (res.ok) {
|
|
9241
9267
|
applied = true;
|
|
@@ -9627,7 +9653,7 @@ import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
|
|
|
9627
9653
|
// src/commands/refresh-watch.ts
|
|
9628
9654
|
import { readdir as readdir3, stat as stat6 } from "fs/promises";
|
|
9629
9655
|
import { homedir as homedir9 } from "os";
|
|
9630
|
-
import { join as
|
|
9656
|
+
import { join as join12 } from "path";
|
|
9631
9657
|
import {
|
|
9632
9658
|
assertApprovalStoreSafe as assertApprovalStoreSafe2,
|
|
9633
9659
|
assertLockStoreSafe as assertLockStoreSafe4,
|
|
@@ -9639,8 +9665,8 @@ var MIN_WATCH_INTERVAL_SEC = 5;
|
|
|
9639
9665
|
var MAX_WATCH_INTERVAL_SEC = 86400;
|
|
9640
9666
|
function watchedRoots(ctx) {
|
|
9641
9667
|
return [
|
|
9642
|
-
ctx.codexSessionsDir ??
|
|
9643
|
-
ctx.claudeProjectsDir ??
|
|
9668
|
+
ctx.codexSessionsDir ?? join12(homedir9(), ".codex", "sessions"),
|
|
9669
|
+
ctx.claudeProjectsDir ?? join12(homedir9(), ".claude", "projects")
|
|
9644
9670
|
];
|
|
9645
9671
|
}
|
|
9646
9672
|
async function scanSourceLogs(roots) {
|
|
@@ -9654,7 +9680,7 @@ async function scanSourceLogs(roots) {
|
|
|
9654
9680
|
throw new Error("Failed to read a source log directory", { cause: error });
|
|
9655
9681
|
}
|
|
9656
9682
|
for (const entry of entries) {
|
|
9657
|
-
const full =
|
|
9683
|
+
const full = join12(dir, entry.name);
|
|
9658
9684
|
if (entry.isDirectory()) {
|
|
9659
9685
|
await walk(full);
|
|
9660
9686
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
@@ -10500,7 +10526,7 @@ function unattachedLines(u) {
|
|
|
10500
10526
|
// src/commands/run.ts
|
|
10501
10527
|
import { mkdir as mkdir3, readFile as readFile6, rm as rm3 } from "fs/promises";
|
|
10502
10528
|
import { homedir as homedir11 } from "os";
|
|
10503
|
-
import { join as
|
|
10529
|
+
import { join as join13 } from "path";
|
|
10504
10530
|
import {
|
|
10505
10531
|
acquireLock as acquireLock5,
|
|
10506
10532
|
assertBasouRootSafe as assertBasouRootSafe13,
|
|
@@ -10515,6 +10541,7 @@ import {
|
|
|
10515
10541
|
findBasouSessionStartHook as findBasouSessionStartHook2,
|
|
10516
10542
|
getDiff,
|
|
10517
10543
|
getSnapshot as getSnapshot2,
|
|
10544
|
+
lstatSessionFile as lstatSessionFile2,
|
|
10518
10545
|
overwriteYamlFile as overwriteYamlFile2,
|
|
10519
10546
|
prefixedUlid as prefixedUlid4,
|
|
10520
10547
|
readManifest as readManifest12,
|
|
@@ -10522,6 +10549,7 @@ import {
|
|
|
10522
10549
|
resolveClaudeCodeCommand,
|
|
10523
10550
|
resolveCodexCommand,
|
|
10524
10551
|
SESSION_SCHEMA_VERSION as SESSION_SCHEMA_VERSION2,
|
|
10552
|
+
SessionFileNotAFileError as SessionFileNotAFileError4,
|
|
10525
10553
|
SessionSchema as SessionSchema2,
|
|
10526
10554
|
sanitizeRelatedFiles,
|
|
10527
10555
|
sanitizeWorkingDirectory as sanitizeWorkingDirectory2,
|
|
@@ -10581,12 +10609,12 @@ async function runTrackedTool(args, options, ctx, adapter) {
|
|
|
10581
10609
|
const manifest = await readManifest12(paths);
|
|
10582
10610
|
await assertSessionStoreSafe3(paths);
|
|
10583
10611
|
const sessionId = prefixedUlid4("ses");
|
|
10584
|
-
const sessionDir =
|
|
10612
|
+
const sessionDir = join13(paths.sessions, sessionId);
|
|
10585
10613
|
const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
|
|
10586
10614
|
await coreAppendChainedEvent2(paths, sessionId, event);
|
|
10587
10615
|
});
|
|
10588
10616
|
const startedAt = now().toISOString();
|
|
10589
|
-
const sessionYamlPath =
|
|
10617
|
+
const sessionYamlPath = join13(sessionDir, "session.yaml");
|
|
10590
10618
|
const session = buildInitialSession2({
|
|
10591
10619
|
id: sessionId,
|
|
10592
10620
|
command,
|
|
@@ -10637,7 +10665,7 @@ async function runTrackedTool(args, options, ctx, adapter) {
|
|
|
10637
10665
|
});
|
|
10638
10666
|
const runningLock = await acquireLock5(paths, "session", sessionId);
|
|
10639
10667
|
try {
|
|
10640
|
-
await mutateSessionYaml2(
|
|
10668
|
+
await mutateSessionYaml2(paths, sessionId, (s) => {
|
|
10641
10669
|
s.session.status = "running";
|
|
10642
10670
|
});
|
|
10643
10671
|
} finally {
|
|
@@ -10883,7 +10911,14 @@ function buildInitialSession2(input) {
|
|
|
10883
10911
|
}
|
|
10884
10912
|
};
|
|
10885
10913
|
}
|
|
10886
|
-
async function mutateSessionYaml2(
|
|
10914
|
+
async function mutateSessionYaml2(paths, sessionId, mutator) {
|
|
10915
|
+
const kind = await lstatSessionFile2(paths, sessionId, "session.yaml").catch((error) => {
|
|
10916
|
+
throw new Error("Failed to read YAML file", { cause: error });
|
|
10917
|
+
});
|
|
10918
|
+
if (kind === "symlink" || kind === "not_a_file") {
|
|
10919
|
+
throw new SessionFileNotAFileError4(sessionId, "session.yaml", kind);
|
|
10920
|
+
}
|
|
10921
|
+
const filePath = join13(paths.sessions, sessionId, "session.yaml");
|
|
10887
10922
|
const raw = await readYamlFile5(filePath);
|
|
10888
10923
|
const parsed = SessionSchema2.parse(raw);
|
|
10889
10924
|
mutator(parsed);
|
|
@@ -10951,7 +10986,7 @@ async function noteCodexHookStatusPreSpawn(_cwd, ctx) {
|
|
|
10951
10986
|
|
|
10952
10987
|
// src/commands/session.ts
|
|
10953
10988
|
import { readFile as readFile7 } from "fs/promises";
|
|
10954
|
-
import { basename as basename8, isAbsolute as isAbsolute5, join as
|
|
10989
|
+
import { basename as basename8, isAbsolute as isAbsolute5, join as join14, relative as relative4, sep as sep3 } from "path";
|
|
10955
10990
|
import {
|
|
10956
10991
|
acquireLock as acquireLock6,
|
|
10957
10992
|
appendEventToExistingSession as appendEventToExistingSession3,
|
|
@@ -10965,6 +11000,7 @@ import {
|
|
|
10965
11000
|
importSessionFromJson as importSessionFromJson2,
|
|
10966
11001
|
LOCAL_CLI_EVENT_SOURCE as LOCAL_CLI_EVENT_SOURCE4,
|
|
10967
11002
|
loadSessionEntries as loadSessionEntries2,
|
|
11003
|
+
lstatSessionFile as lstatSessionFile3,
|
|
10968
11004
|
normalizeSessionTimestamps,
|
|
10969
11005
|
readAllEvents,
|
|
10970
11006
|
readManifest as readManifest13,
|
|
@@ -10974,6 +11010,7 @@ import {
|
|
|
10974
11010
|
resolveSessionId as resolveSessionId3,
|
|
10975
11011
|
resolveTaskId,
|
|
10976
11012
|
SESSION_IMPORT_SCHEMA_VERSION as SESSION_IMPORT_SCHEMA_VERSION2,
|
|
11013
|
+
SessionFileNotAFileError as SessionFileNotAFileError5,
|
|
10977
11014
|
SessionImportPayloadSchema as SessionImportPayloadSchema2,
|
|
10978
11015
|
SessionSchema as SessionSchema3,
|
|
10979
11016
|
SessionStatusSchema,
|
|
@@ -11083,8 +11120,16 @@ async function doRunSessionShow(idInput, options, ctx) {
|
|
|
11083
11120
|
const paths = basouPaths19(repositoryRoot);
|
|
11084
11121
|
await assertWorkspaceInitialized12(paths.root);
|
|
11085
11122
|
const sessionId = await resolveSessionId3(paths, idInput);
|
|
11086
|
-
const sessionDir =
|
|
11087
|
-
const sessionYamlPath =
|
|
11123
|
+
const sessionDir = join14(paths.sessions, sessionId);
|
|
11124
|
+
const sessionYamlPath = join14(sessionDir, "session.yaml");
|
|
11125
|
+
const yamlKind = await lstatSessionFile3(paths, sessionId, "session.yaml").catch(
|
|
11126
|
+
(error) => {
|
|
11127
|
+
throw new Error("Failed to read session", { cause: error });
|
|
11128
|
+
}
|
|
11129
|
+
);
|
|
11130
|
+
if (yamlKind === "symlink" || yamlKind === "not_a_file") {
|
|
11131
|
+
throw new SessionFileNotAFileError5(sessionId, "session.yaml", yamlKind);
|
|
11132
|
+
}
|
|
11088
11133
|
let session;
|
|
11089
11134
|
try {
|
|
11090
11135
|
const raw = await readYamlFile6(sessionYamlPath);
|
|
@@ -11096,7 +11141,7 @@ async function doRunSessionShow(idInput, options, ctx) {
|
|
|
11096
11141
|
throw new Error("Failed to read session", { cause: error });
|
|
11097
11142
|
}
|
|
11098
11143
|
let eventsLostLines = 0;
|
|
11099
|
-
const events = await readAllEvents(
|
|
11144
|
+
const events = await readAllEvents(paths, sessionId, {
|
|
11100
11145
|
onWarning: (w) => {
|
|
11101
11146
|
if (w.kind === "malformed_json" || w.kind === "schema_violation") eventsLostLines++;
|
|
11102
11147
|
printReplayWarning(w, sessionId);
|
|
@@ -11614,7 +11659,7 @@ async function doRunSessionRechain(options, ctx) {
|
|
|
11614
11659
|
}
|
|
11615
11660
|
}
|
|
11616
11661
|
const tamperedCount = rows.filter(
|
|
11617
|
-
(r) => r.reason === "tampered" || r.reason === "symlink" || r.reason === "not_a_directory"
|
|
11662
|
+
(r) => r.reason === "tampered" || r.reason === "symlink" || r.reason === "not_a_directory" || r.reason === "events_not_a_file" || r.reason === "yaml_not_a_file"
|
|
11618
11663
|
).length;
|
|
11619
11664
|
const errorCount = rows.filter((r) => r.status === "error").length;
|
|
11620
11665
|
if (options.json === true) {
|
|
@@ -11642,6 +11687,10 @@ function renderRechainRow(row, dryRun) {
|
|
|
11642
11687
|
if (row.reason === "symlink" || row.reason === "not_a_directory") {
|
|
11643
11688
|
return `skipped (${row.reason === "symlink" ? "SYMLINK" : "NOT A DIRECTORY"} \u2014 not followed; inspect with 'basou verify')`;
|
|
11644
11689
|
}
|
|
11690
|
+
if (row.reason === "events_not_a_file" || row.reason === "yaml_not_a_file") {
|
|
11691
|
+
const file = row.reason === "events_not_a_file" ? "events.jsonl" : "session.yaml";
|
|
11692
|
+
return `skipped (${file} NOT A REGULAR FILE \u2014 not followed; inspect with 'basou verify')`;
|
|
11693
|
+
}
|
|
11645
11694
|
return `skipped (${row.reason})`;
|
|
11646
11695
|
case "error":
|
|
11647
11696
|
return `error (${row.message})`;
|
|
@@ -11850,7 +11899,6 @@ function formatVersionGateMessage(error) {
|
|
|
11850
11899
|
|
|
11851
11900
|
// src/commands/task.ts
|
|
11852
11901
|
import { readFile as readFile8 } from "fs/promises";
|
|
11853
|
-
import { join as join16 } from "path";
|
|
11854
11902
|
import {
|
|
11855
11903
|
archiveTask,
|
|
11856
11904
|
assertBasouRootSafe as assertBasouRootSafe17,
|
|
@@ -12175,9 +12223,8 @@ async function doRunTaskShow(idInput, options, ctx) {
|
|
|
12175
12223
|
const events = [];
|
|
12176
12224
|
const linkedSessionIds = new Set(doc.task.task.linked_sessions);
|
|
12177
12225
|
for (const s of sessions) {
|
|
12178
|
-
const sessionDir = join16(paths.sessions, s.sessionId);
|
|
12179
12226
|
try {
|
|
12180
|
-
for await (const ev of replayEvents3(
|
|
12227
|
+
for await (const ev of replayEvents3(paths, s.sessionId, {
|
|
12181
12228
|
onWarning: (w) => printReplayWarning(w, s.sessionId)
|
|
12182
12229
|
})) {
|
|
12183
12230
|
if ((ev.type === "task_created" || ev.type === "task_status_changed" || ev.type === "task_reconciled" || ev.type === "task_linkage_refreshed" || ev.type === "task_deleted" || ev.type === "task_archived") && ev.task_id === taskId) {
|
|
@@ -13070,7 +13117,7 @@ async function assertWorkspaceInitialized15(basouRoot) {
|
|
|
13070
13117
|
// src/commands/view.ts
|
|
13071
13118
|
import { spawn } from "child_process";
|
|
13072
13119
|
import { createHash as createHash2 } from "crypto";
|
|
13073
|
-
import { basename as basename11, dirname as dirname9, join as
|
|
13120
|
+
import { basename as basename11, dirname as dirname9, join as join17, resolve as resolve14 } from "path";
|
|
13074
13121
|
import {
|
|
13075
13122
|
assertBasouRootSafe as assertBasouRootSafe19,
|
|
13076
13123
|
basouPaths as basouPaths25,
|
|
@@ -13084,7 +13131,7 @@ import { InvalidArgumentError as InvalidArgumentError8 } from "commander";
|
|
|
13084
13131
|
import { createReadStream as createReadStream2 } from "fs";
|
|
13085
13132
|
import { readdir as readdir4, stat as stat7 } from "fs/promises";
|
|
13086
13133
|
import { homedir as homedir12 } from "os";
|
|
13087
|
-
import { basename as basename9, dirname as dirname8, join as
|
|
13134
|
+
import { basename as basename9, dirname as dirname8, join as join15 } from "path";
|
|
13088
13135
|
import { createInterface as createInterface2 } from "readline";
|
|
13089
13136
|
import { basouPaths as basouPaths24, readManifest as readManifest16 } from "@basou/core";
|
|
13090
13137
|
function uncapturedTotal(result) {
|
|
@@ -13096,8 +13143,8 @@ async function checkPortfolioCoverage(workspaces, ctx = {}) {
|
|
|
13096
13143
|
ctx.portfolioConfigPath
|
|
13097
13144
|
);
|
|
13098
13145
|
const listedDirs = new Set([...roots].map((root) => encodeProjectDir(root)));
|
|
13099
|
-
const claudeProjectsDir = ctx.claudeProjectsDir ??
|
|
13100
|
-
const codexSessionsDir = ctx.codexSessionsDir ??
|
|
13146
|
+
const claudeProjectsDir = ctx.claudeProjectsDir ?? join15(homedir12(), ".claude", "projects");
|
|
13147
|
+
const codexSessionsDir = ctx.codexSessionsDir ?? join15(homedir12(), ".codex", "sessions");
|
|
13101
13148
|
const tally = new Tally(roots);
|
|
13102
13149
|
const absentTrees = [];
|
|
13103
13150
|
const claudeFiles = await listClaudeTranscripts(claudeProjectsDir);
|
|
@@ -13235,7 +13282,7 @@ async function isDirEntry(parent, entry) {
|
|
|
13235
13282
|
if (entry.isDirectory()) return true;
|
|
13236
13283
|
if (!entry.isSymbolicLink()) return false;
|
|
13237
13284
|
try {
|
|
13238
|
-
return (await stat7(
|
|
13285
|
+
return (await stat7(join15(parent, entry.name))).isDirectory();
|
|
13239
13286
|
} catch {
|
|
13240
13287
|
return false;
|
|
13241
13288
|
}
|
|
@@ -13250,7 +13297,7 @@ async function listClaudeTranscripts(projectsRoot) {
|
|
|
13250
13297
|
const files = [];
|
|
13251
13298
|
for (const entry of entries) {
|
|
13252
13299
|
if (!await isDirEntry(projectsRoot, entry)) continue;
|
|
13253
|
-
const full =
|
|
13300
|
+
const full = join15(projectsRoot, entry.name);
|
|
13254
13301
|
let names;
|
|
13255
13302
|
try {
|
|
13256
13303
|
names = await readdir4(full);
|
|
@@ -13258,7 +13305,7 @@ async function listClaudeTranscripts(projectsRoot) {
|
|
|
13258
13305
|
continue;
|
|
13259
13306
|
}
|
|
13260
13307
|
for (const name of names) {
|
|
13261
|
-
if (name.endsWith(".jsonl")) files.push(
|
|
13308
|
+
if (name.endsWith(".jsonl")) files.push(join15(full, name));
|
|
13262
13309
|
}
|
|
13263
13310
|
}
|
|
13264
13311
|
return files.sort();
|
|
@@ -13273,7 +13320,7 @@ async function listCodexRollouts(sessionsRoot) {
|
|
|
13273
13320
|
return;
|
|
13274
13321
|
}
|
|
13275
13322
|
for (const entry of entries) {
|
|
13276
|
-
const full =
|
|
13323
|
+
const full = join15(dir, entry.name);
|
|
13277
13324
|
if (entry.isDirectory()) await walk(full);
|
|
13278
13325
|
else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) {
|
|
13279
13326
|
found.push(full);
|
|
@@ -13404,7 +13451,7 @@ function inertLines(result) {
|
|
|
13404
13451
|
// src/lib/portfolio-safety.ts
|
|
13405
13452
|
import { execFile as execFile2 } from "child_process";
|
|
13406
13453
|
import { lstat as lstat3, realpath as realpath5 } from "fs/promises";
|
|
13407
|
-
import { isAbsolute as isAbsolute6, join as
|
|
13454
|
+
import { isAbsolute as isAbsolute6, join as join16, relative as relative5, resolve as resolve12, sep as sep4 } from "path";
|
|
13408
13455
|
import { promisify as promisify2 } from "util";
|
|
13409
13456
|
import { readManifest as readManifest17 } from "@basou/core";
|
|
13410
13457
|
var execFileAsync2 = promisify2(execFile2);
|
|
@@ -13428,7 +13475,7 @@ function isBasouPath(p) {
|
|
|
13428
13475
|
async function inspectRepo(repoPath) {
|
|
13429
13476
|
let hasEntry = false;
|
|
13430
13477
|
try {
|
|
13431
|
-
await lstat3(
|
|
13478
|
+
await lstat3(join16(repoPath, ".basou"));
|
|
13432
13479
|
hasEntry = true;
|
|
13433
13480
|
} catch (error) {
|
|
13434
13481
|
if (errorCode2(error) !== "ENOENT") {
|
|
@@ -13582,7 +13629,7 @@ function formatSafetyReport(result) {
|
|
|
13582
13629
|
|
|
13583
13630
|
// src/lib/view-server.ts
|
|
13584
13631
|
import { createServer } from "http";
|
|
13585
|
-
import { basename as basename10,
|
|
13632
|
+
import { basename as basename10, resolve as resolve13 } from "path";
|
|
13586
13633
|
import {
|
|
13587
13634
|
boardLivePage,
|
|
13588
13635
|
boardPage,
|
|
@@ -13604,7 +13651,9 @@ import {
|
|
|
13604
13651
|
renderDecisions as renderDecisions3,
|
|
13605
13652
|
renderHandoff as renderHandoff3,
|
|
13606
13653
|
resolveViewLanguageFromPaths,
|
|
13654
|
+
SessionFileNotAFileError as SessionFileNotAFileError6,
|
|
13607
13655
|
SessionIdSchema,
|
|
13656
|
+
SessionNotADirectoryError,
|
|
13608
13657
|
summarizeOrientation,
|
|
13609
13658
|
tryRemoteUrl
|
|
13610
13659
|
} from "@basou/core";
|
|
@@ -15607,10 +15656,7 @@ async function sessionDetail(ws, sessionId) {
|
|
|
15607
15656
|
}
|
|
15608
15657
|
const entry = await inspectSessionEntry2(ws.paths, sessionId);
|
|
15609
15658
|
if (entry === "symlink" || entry === "not_a_directory") {
|
|
15610
|
-
throw new HttpError(
|
|
15611
|
-
404,
|
|
15612
|
-
`Session ${sessionId} is not a directory; a symlink or a file there is not followed`
|
|
15613
|
-
);
|
|
15659
|
+
throw new HttpError(404, new SessionNotADirectoryError(sessionId, entry).message);
|
|
15614
15660
|
}
|
|
15615
15661
|
let session;
|
|
15616
15662
|
try {
|
|
@@ -15619,10 +15665,11 @@ async function sessionDetail(ws, sessionId) {
|
|
|
15619
15665
|
if (error instanceof Error && error.message === "YAML file not found") {
|
|
15620
15666
|
throw new HttpError(404, "Session not found");
|
|
15621
15667
|
}
|
|
15668
|
+
if (error instanceof SessionFileNotAFileError6) throw new HttpError(404, error.message);
|
|
15622
15669
|
throw error;
|
|
15623
15670
|
}
|
|
15624
15671
|
try {
|
|
15625
|
-
const events = await readAllEvents2(
|
|
15672
|
+
const events = await readAllEvents2(ws.paths, sessionId);
|
|
15626
15673
|
return { session, events };
|
|
15627
15674
|
} catch {
|
|
15628
15675
|
return { session, events: [], degraded: true };
|
|
@@ -15887,7 +15934,7 @@ async function viewBoardOf(root, paths) {
|
|
|
15887
15934
|
own = void 0;
|
|
15888
15935
|
}
|
|
15889
15936
|
if (own?.visibility !== "private") return { why: "no_board" };
|
|
15890
|
-
return { recordsDir:
|
|
15937
|
+
return { recordsDir: join17(root, dirname9(DEFAULT_BOARD_PATH), "records") };
|
|
15891
15938
|
}
|
|
15892
15939
|
async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
|
|
15893
15940
|
const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve14(cwd, p) })) : await loadPortfolioConfig6(ctx.portfolioConfigPath);
|
|
@@ -16041,7 +16088,7 @@ function installExperimentalNotice(program, names = EXPERIMENTAL_COMMANDS, write
|
|
|
16041
16088
|
function readBuildStamp() {
|
|
16042
16089
|
if (false) return void 0;
|
|
16043
16090
|
try {
|
|
16044
|
-
return JSON.parse('{"version":"0.
|
|
16091
|
+
return JSON.parse('{"version":"0.70.0","commit":"60d302a","committedAt":"2026-10-16T17:39:30+09:00"}');
|
|
16045
16092
|
} catch {
|
|
16046
16093
|
return void 0;
|
|
16047
16094
|
}
|