@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/index.js
CHANGED
|
@@ -95,7 +95,7 @@ function printSessionSkip(sid, reason) {
|
|
|
95
95
|
const short = shortSessionId(sid);
|
|
96
96
|
if (reason === "events_jsonl_unreadable") {
|
|
97
97
|
console.error(`Warning: skipped suspect check for ${short}: events.jsonl unreadable`);
|
|
98
|
-
} else if (reason === "session_dir_not_directory") {
|
|
98
|
+
} else if (reason === "session_dir_not_directory" || reason === "session_yaml_not_a_file") {
|
|
99
99
|
console.error(`Skipped ${sid}: ${reason}`);
|
|
100
100
|
} else {
|
|
101
101
|
console.error(`Skipped ${short}: ${reason}`);
|
|
@@ -116,6 +116,11 @@ function printSessionListSkip(sid, reason) {
|
|
|
116
116
|
case "session_dir_not_directory":
|
|
117
117
|
console.error(`Skipped ${sid}: not a directory (a symlink or a file is not followed)`);
|
|
118
118
|
break;
|
|
119
|
+
case "session_yaml_not_a_file":
|
|
120
|
+
console.error(
|
|
121
|
+
`Skipped ${sid}: session.yaml is not a regular file (a symlink or anything else is not followed)`
|
|
122
|
+
);
|
|
123
|
+
break;
|
|
119
124
|
}
|
|
120
125
|
}
|
|
121
126
|
function printTaskSkip(taskId, reason) {
|
|
@@ -152,7 +157,8 @@ import {
|
|
|
152
157
|
prefixedUlid,
|
|
153
158
|
readSessionYaml,
|
|
154
159
|
readYamlFile,
|
|
155
|
-
replayEvents
|
|
160
|
+
replayEvents,
|
|
161
|
+
SessionFileNotAFileError
|
|
156
162
|
} from "@basou/core";
|
|
157
163
|
|
|
158
164
|
// src/lib/repo-root.ts
|
|
@@ -425,7 +431,7 @@ async function doRunApprovalShow(idInput, options, ctx) {
|
|
|
425
431
|
`Warning: session ${sessionId} is not a directory (a symlink or a file is not followed); its events are not shown`
|
|
426
432
|
);
|
|
427
433
|
} else {
|
|
428
|
-
for await (const ev of replayEvents(
|
|
434
|
+
for await (const ev of replayEvents(paths, sessionId, {
|
|
429
435
|
onWarning: (w) => printReplayWarning(w, sessionId)
|
|
430
436
|
})) {
|
|
431
437
|
if (isApprovalEvent(ev) && ev.approval_id === id) {
|
|
@@ -525,8 +531,7 @@ async function doRunApprovalResolve(idInput, options, ctx, decision) {
|
|
|
525
531
|
const sessionLock = await acquireLock(paths, "session", approval.session_id);
|
|
526
532
|
try {
|
|
527
533
|
await assertSessionDirSafe(paths, approval.session_id);
|
|
528
|
-
const
|
|
529
|
-
for await (const ev of replayEvents(sessionDir, {
|
|
534
|
+
for await (const ev of replayEvents(paths, approval.session_id, {
|
|
530
535
|
onWarning: (w) => printReplayWarning(w, approval.session_id)
|
|
531
536
|
})) {
|
|
532
537
|
if (isApprovalEvent(ev) && ev.approval_id === approval.id && (ev.type === "approval_approved" || ev.type === "approval_rejected" || ev.type === "approval_expired")) {
|
|
@@ -539,7 +544,8 @@ async function doRunApprovalResolve(idInput, options, ctx, decision) {
|
|
|
539
544
|
let sessionStatus = null;
|
|
540
545
|
try {
|
|
541
546
|
sessionStatus = (await readSessionYaml(paths, approval.session_id)).session.status;
|
|
542
|
-
} catch {
|
|
547
|
+
} catch (error) {
|
|
548
|
+
if (error instanceof SessionFileNotAFileError) throw error;
|
|
543
549
|
sessionStatus = null;
|
|
544
550
|
}
|
|
545
551
|
const attachable = sessionStatus === "initialized" || sessionStatus === "running" || sessionStatus === "waiting_approval";
|
|
@@ -1048,6 +1054,7 @@ import {
|
|
|
1048
1054
|
readSessionYaml as readSessionYaml2,
|
|
1049
1055
|
reimportPreservingId,
|
|
1050
1056
|
SESSION_IMPORT_SCHEMA_VERSION,
|
|
1057
|
+
SessionFileNotAFileError as SessionFileNotAFileError2,
|
|
1051
1058
|
SessionImportPayloadSchema
|
|
1052
1059
|
} from "@basou/core";
|
|
1053
1060
|
|
|
@@ -1221,10 +1228,11 @@ async function resolveImportTarget(ctx) {
|
|
|
1221
1228
|
async function importDerivedSessions(paths, manifest, options, sourceKind, candidates, projectPaths, boundaryDeclared) {
|
|
1222
1229
|
await assertSessionStoreSafe(paths);
|
|
1223
1230
|
if (options.dryRun !== true) await assertLockStoreSafe2(paths);
|
|
1224
|
-
const {
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1231
|
+
const {
|
|
1232
|
+
byExternalId: existingByExternalId,
|
|
1233
|
+
notDirectories,
|
|
1234
|
+
yamlNotFiles
|
|
1235
|
+
} = await loadExistingByExternalId(paths, sourceKind);
|
|
1228
1236
|
let notDirectoriesWarned = false;
|
|
1229
1237
|
const seenThisRun = /* @__PURE__ */ new Set();
|
|
1230
1238
|
const crossProjectCheck = boundaryDeclared;
|
|
@@ -1317,12 +1325,20 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
|
|
|
1317
1325
|
}
|
|
1318
1326
|
counts.replaced++;
|
|
1319
1327
|
}
|
|
1320
|
-
if (priors.length === 0 && notDirectories.length > 0 && !notDirectoriesWarned) {
|
|
1328
|
+
if (priors.length === 0 && (notDirectories.length > 0 || yamlNotFiles.length > 0) && !notDirectoriesWarned) {
|
|
1321
1329
|
notDirectoriesWarned = true;
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1330
|
+
if (notDirectories.length > 0) {
|
|
1331
|
+
const one = notDirectories.length === 1;
|
|
1332
|
+
console.error(
|
|
1333
|
+
`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`
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
if (yamlNotFiles.length > 0) {
|
|
1337
|
+
const one = yamlNotFiles.length === 1;
|
|
1338
|
+
console.error(
|
|
1339
|
+
`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`
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1326
1342
|
}
|
|
1327
1343
|
const result = await importSessionFromJson(paths, manifest, payload, {
|
|
1328
1344
|
dryRun: options.dryRun === true
|
|
@@ -1404,13 +1420,15 @@ async function loadExistingByExternalId(paths, sourceKind) {
|
|
|
1404
1420
|
try {
|
|
1405
1421
|
({ dirs: sessionIds, notDirectories } = await enumerateSessionEntries(paths));
|
|
1406
1422
|
} catch {
|
|
1407
|
-
return { byExternalId, notDirectories: [] };
|
|
1423
|
+
return { byExternalId, notDirectories: [], yamlNotFiles: [] };
|
|
1408
1424
|
}
|
|
1425
|
+
const yamlNotFiles = [];
|
|
1409
1426
|
for (const sessionId of sessionIds) {
|
|
1410
1427
|
let session;
|
|
1411
1428
|
try {
|
|
1412
1429
|
session = await readSessionYaml2(paths, sessionId);
|
|
1413
|
-
} catch {
|
|
1430
|
+
} catch (error) {
|
|
1431
|
+
if (error instanceof SessionFileNotAFileError2) yamlNotFiles.push(sessionId);
|
|
1414
1432
|
continue;
|
|
1415
1433
|
}
|
|
1416
1434
|
if (session.session.source.kind !== sourceKind) continue;
|
|
@@ -1425,7 +1443,7 @@ async function loadExistingByExternalId(paths, sourceKind) {
|
|
|
1425
1443
|
const match = typeof label === "string" ? label.match(/^claude-code import (\S+)$/) : null;
|
|
1426
1444
|
if (match?.[1] !== void 0) add(match[1], prior);
|
|
1427
1445
|
}
|
|
1428
|
-
return { byExternalId, notDirectories };
|
|
1446
|
+
return { byExternalId, notDirectories, yamlNotFiles };
|
|
1429
1447
|
}
|
|
1430
1448
|
async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
|
|
1431
1449
|
if (options.session !== void 0) {
|
|
@@ -2959,7 +2977,7 @@ async function doRunChannelClear(face, options) {
|
|
|
2959
2977
|
|
|
2960
2978
|
// src/commands/decision.ts
|
|
2961
2979
|
import { homedir as homedir3 } from "os";
|
|
2962
|
-
import {
|
|
2980
|
+
import { resolve as resolve4 } from "path";
|
|
2963
2981
|
import {
|
|
2964
2982
|
AGENT_INFRA_DIRS as AGENT_INFRA_DIRS2,
|
|
2965
2983
|
acquireLock as acquireLock2,
|
|
@@ -3540,9 +3558,8 @@ function isDecisionId(value) {
|
|
|
3540
3558
|
async function decisionExists(paths, decisionId) {
|
|
3541
3559
|
const entries = await loadSessionEntries(paths, { now: /* @__PURE__ */ new Date() });
|
|
3542
3560
|
for (const entry of entries) {
|
|
3543
|
-
const sessionDir = join7(paths.sessions, entry.sessionId);
|
|
3544
3561
|
try {
|
|
3545
|
-
for await (const ev of replayEvents2(
|
|
3562
|
+
for await (const ev of replayEvents2(paths, entry.sessionId, {})) {
|
|
3546
3563
|
if (ev.type === "decision_recorded" && ev.decision_id === decisionId) return true;
|
|
3547
3564
|
}
|
|
3548
3565
|
} catch {
|
|
@@ -3996,7 +4013,7 @@ async function assertWorkspaceInitialized5(basouRoot) {
|
|
|
3996
4013
|
// src/commands/exec.ts
|
|
3997
4014
|
import { mkdir as mkdir2, rm as rm2 } from "fs/promises";
|
|
3998
4015
|
import { homedir as homedir4 } from "os";
|
|
3999
|
-
import { join as
|
|
4016
|
+
import { join as join7 } from "path";
|
|
4000
4017
|
import {
|
|
4001
4018
|
acquireLock as acquireLock3,
|
|
4002
4019
|
assertBasouRootSafe as assertBasouRootSafe6,
|
|
@@ -4007,12 +4024,14 @@ import {
|
|
|
4007
4024
|
EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION3,
|
|
4008
4025
|
finalizeSessionYaml,
|
|
4009
4026
|
getSnapshot,
|
|
4027
|
+
lstatSessionFile,
|
|
4010
4028
|
overwriteYamlFile,
|
|
4011
4029
|
parseDuration,
|
|
4012
4030
|
prefixedUlid as prefixedUlid3,
|
|
4013
4031
|
readManifest as readManifest6,
|
|
4014
4032
|
readYamlFile as readYamlFile2,
|
|
4015
4033
|
SESSION_SCHEMA_VERSION,
|
|
4034
|
+
SessionFileNotAFileError as SessionFileNotAFileError3,
|
|
4016
4035
|
SessionSchema,
|
|
4017
4036
|
sanitizeWorkingDirectory,
|
|
4018
4037
|
writeObservedDuration,
|
|
@@ -4058,12 +4077,12 @@ async function runExec(command, args, options, ctx = {}) {
|
|
|
4058
4077
|
const manifest = await readManifest6(paths);
|
|
4059
4078
|
await assertSessionStoreSafe2(paths);
|
|
4060
4079
|
const sessionId = prefixedUlid3("ses");
|
|
4061
|
-
const sessionDir =
|
|
4080
|
+
const sessionDir = join7(paths.sessions, sessionId);
|
|
4062
4081
|
const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
|
|
4063
4082
|
await coreAppendChainedEvent(paths, sessionId, event);
|
|
4064
4083
|
});
|
|
4065
4084
|
const startedAt = now().toISOString();
|
|
4066
|
-
const sessionYamlPath =
|
|
4085
|
+
const sessionYamlPath = join7(sessionDir, "session.yaml");
|
|
4067
4086
|
const session = buildInitialSession({
|
|
4068
4087
|
id: sessionId,
|
|
4069
4088
|
command,
|
|
@@ -4112,7 +4131,7 @@ async function runExec(command, args, options, ctx = {}) {
|
|
|
4112
4131
|
});
|
|
4113
4132
|
const runningLock = await acquireLock3(paths, "session", sessionId);
|
|
4114
4133
|
try {
|
|
4115
|
-
await mutateSessionYaml(
|
|
4134
|
+
await mutateSessionYaml(paths, sessionId, (s) => {
|
|
4116
4135
|
s.session.status = "running";
|
|
4117
4136
|
});
|
|
4118
4137
|
} finally {
|
|
@@ -4289,7 +4308,14 @@ function buildInitialSession(input) {
|
|
|
4289
4308
|
}
|
|
4290
4309
|
};
|
|
4291
4310
|
}
|
|
4292
|
-
async function mutateSessionYaml(
|
|
4311
|
+
async function mutateSessionYaml(paths, sessionId, mutator) {
|
|
4312
|
+
const kind = await lstatSessionFile(paths, sessionId, "session.yaml").catch((error) => {
|
|
4313
|
+
throw new Error("Failed to read YAML file", { cause: error });
|
|
4314
|
+
});
|
|
4315
|
+
if (kind === "symlink" || kind === "not_a_file") {
|
|
4316
|
+
throw new SessionFileNotAFileError3(sessionId, "session.yaml", kind);
|
|
4317
|
+
}
|
|
4318
|
+
const filePath = join7(paths.sessions, sessionId, "session.yaml");
|
|
4293
4319
|
const raw = await readYamlFile2(filePath);
|
|
4294
4320
|
const parsed = SessionSchema.parse(raw);
|
|
4295
4321
|
mutator(parsed);
|
|
@@ -4412,7 +4438,7 @@ async function assertWorkspaceInitialized6(basouRoot) {
|
|
|
4412
4438
|
import { execFile } from "child_process";
|
|
4413
4439
|
import { open as open2, readFile as readFile4, realpath as realpath4, stat as stat4 } from "fs/promises";
|
|
4414
4440
|
import { homedir as homedir7 } from "os";
|
|
4415
|
-
import { join as
|
|
4441
|
+
import { join as join10 } from "path";
|
|
4416
4442
|
import { fileURLToPath } from "url";
|
|
4417
4443
|
import { promisify } from "util";
|
|
4418
4444
|
import {
|
|
@@ -4573,15 +4599,15 @@ function describeCodexHookTrust(trust) {
|
|
|
4573
4599
|
|
|
4574
4600
|
// src/lib/protocols-config.ts
|
|
4575
4601
|
import { homedir as homedir5 } from "os";
|
|
4576
|
-
import { isAbsolute, join as
|
|
4602
|
+
import { isAbsolute, join as join8, resolve as resolve5 } from "path";
|
|
4577
4603
|
import { readYamlFile as readYamlFile3 } from "@basou/core";
|
|
4578
|
-
var DEFAULT_PROTOCOLS_CONFIG_PATH =
|
|
4579
|
-
var DEFAULT_TARGET_PATH =
|
|
4604
|
+
var DEFAULT_PROTOCOLS_CONFIG_PATH = join8(homedir5(), ".basou", "protocols.yaml");
|
|
4605
|
+
var DEFAULT_TARGET_PATH = join8(homedir5(), ".claude", "CLAUDE.md");
|
|
4580
4606
|
var ALLOWED_TOP_KEYS = /* @__PURE__ */ new Set(["version", "protocols"]);
|
|
4581
4607
|
var ALLOWED_ENTRY_KEYS = /* @__PURE__ */ new Set(["source", "title"]);
|
|
4582
4608
|
function expandTilde(p) {
|
|
4583
4609
|
if (p === "~") return homedir5();
|
|
4584
|
-
if (p.startsWith("~/")) return
|
|
4610
|
+
if (p.startsWith("~/")) return join8(homedir5(), p.slice(2));
|
|
4585
4611
|
return p;
|
|
4586
4612
|
}
|
|
4587
4613
|
function isRecord(value) {
|
|
@@ -4659,12 +4685,12 @@ import {
|
|
|
4659
4685
|
|
|
4660
4686
|
// src/lib/hosts-config.ts
|
|
4661
4687
|
import { homedir as homedir6 } from "os";
|
|
4662
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4688
|
+
import { isAbsolute as isAbsolute2, join as join9, resolve as resolve6 } from "path";
|
|
4663
4689
|
import { readYamlFile as readYamlFile4 } from "@basou/core";
|
|
4664
|
-
var DEFAULT_HOSTS_CONFIG_PATH =
|
|
4690
|
+
var DEFAULT_HOSTS_CONFIG_PATH = join9(homedir6(), ".basou", "hosts.yaml");
|
|
4665
4691
|
function expandTilde2(p) {
|
|
4666
4692
|
if (p === "~") return homedir6();
|
|
4667
|
-
if (p.startsWith("~/")) return
|
|
4693
|
+
if (p.startsWith("~/")) return join9(homedir6(), p.slice(2));
|
|
4668
4694
|
return p;
|
|
4669
4695
|
}
|
|
4670
4696
|
function isRecord2(value) {
|
|
@@ -5248,7 +5274,7 @@ function parseMinEdits(raw) {
|
|
|
5248
5274
|
if (raw === void 0 || !/^\d+$/.test(raw)) return void 0;
|
|
5249
5275
|
return Number(raw);
|
|
5250
5276
|
}
|
|
5251
|
-
var DEFAULT_CLAUDE_SETTINGS_PATH =
|
|
5277
|
+
var DEFAULT_CLAUDE_SETTINGS_PATH = join10(homedir7(), ".claude", "settings.json");
|
|
5252
5278
|
function resolveCliEntry() {
|
|
5253
5279
|
return fileURLToPath(import.meta.url);
|
|
5254
5280
|
}
|
|
@@ -5605,7 +5631,7 @@ function describeHookMode(tiers) {
|
|
|
5605
5631
|
const gates = tiers.review ? "capture + review" : "capture";
|
|
5606
5632
|
return `${enforcement}, ${gates}`;
|
|
5607
5633
|
}
|
|
5608
|
-
var DEFAULT_CODEX_FACE_PATH =
|
|
5634
|
+
var DEFAULT_CODEX_FACE_PATH = join10(homedir7(), ".codex", "AGENTS.md");
|
|
5609
5635
|
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.`;
|
|
5610
5636
|
async function faceHasLeftoverOrientationBlock(facePath) {
|
|
5611
5637
|
try {
|
|
@@ -5617,8 +5643,8 @@ async function faceHasLeftoverOrientationBlock(facePath) {
|
|
|
5617
5643
|
return false;
|
|
5618
5644
|
}
|
|
5619
5645
|
}
|
|
5620
|
-
var DEFAULT_CODEX_HOOKS_PATH =
|
|
5621
|
-
var DEFAULT_CODEX_CONFIG_PATH =
|
|
5646
|
+
var DEFAULT_CODEX_HOOKS_PATH = join10(homedir7(), ".codex", "hooks.json");
|
|
5647
|
+
var DEFAULT_CODEX_CONFIG_PATH = join10(homedir7(), ".codex", "config.toml");
|
|
5622
5648
|
async function readHooksFile(path) {
|
|
5623
5649
|
let raw;
|
|
5624
5650
|
try {
|
|
@@ -6225,7 +6251,7 @@ import {
|
|
|
6225
6251
|
writeFileSync,
|
|
6226
6252
|
writeSync
|
|
6227
6253
|
} from "fs";
|
|
6228
|
-
import { basename as basename7, dirname as dirname7, isAbsolute as isAbsolute3, join as
|
|
6254
|
+
import { basename as basename7, dirname as dirname7, isAbsolute as isAbsolute3, join as join11, relative as relative3, resolve as resolve9 } from "path";
|
|
6229
6255
|
import {
|
|
6230
6256
|
appendBasouGitignore as appendBasouGitignore2,
|
|
6231
6257
|
basouPaths as basouPaths13,
|
|
@@ -6606,7 +6632,7 @@ function classifySourceRoot(repositoryRoot, declaredPath) {
|
|
|
6606
6632
|
} catch {
|
|
6607
6633
|
return { path: declaredPath, kind: "unresolved" };
|
|
6608
6634
|
}
|
|
6609
|
-
return { path: declaredPath, kind: existsSync(
|
|
6635
|
+
return { path: declaredPath, kind: existsSync(join11(real, ".git")) ? "repo" : "non-repo" };
|
|
6610
6636
|
}
|
|
6611
6637
|
async function doRunProjectAdopt(options, ctx) {
|
|
6612
6638
|
const cwd = ctx.cwd ?? process.cwd();
|
|
@@ -6710,7 +6736,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
|
|
|
6710
6736
|
} catch {
|
|
6711
6737
|
return { ...base, reachable: false, instructionFiles: [] };
|
|
6712
6738
|
}
|
|
6713
|
-
if (!existsSync(
|
|
6739
|
+
if (!existsSync(join11(real, ".git"))) {
|
|
6714
6740
|
return { ...base, reachable: false, instructionFiles: [] };
|
|
6715
6741
|
}
|
|
6716
6742
|
try {
|
|
@@ -6718,7 +6744,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
|
|
|
6718
6744
|
for (const name of INSTRUCTION_FILES) {
|
|
6719
6745
|
let present = true;
|
|
6720
6746
|
try {
|
|
6721
|
-
lstatSync(
|
|
6747
|
+
lstatSync(join11(real, name));
|
|
6722
6748
|
} catch {
|
|
6723
6749
|
present = false;
|
|
6724
6750
|
}
|
|
@@ -6829,10 +6855,10 @@ function gatherRepoGitignore(repositoryRoot, entry) {
|
|
|
6829
6855
|
} catch {
|
|
6830
6856
|
return { ...base, reachable: false, currentLines: [] };
|
|
6831
6857
|
}
|
|
6832
|
-
if (!existsSync(
|
|
6858
|
+
if (!existsSync(join11(real, ".git"))) {
|
|
6833
6859
|
return { ...base, reachable: false, currentLines: [] };
|
|
6834
6860
|
}
|
|
6835
|
-
return { ...base, reachable: true, currentLines: readGitignoreLines(
|
|
6861
|
+
return { ...base, reachable: true, currentLines: readGitignoreLines(join11(real, ".gitignore")) };
|
|
6836
6862
|
}
|
|
6837
6863
|
function hasErrorCode(error) {
|
|
6838
6864
|
return error instanceof Error && typeof error.code === "string";
|
|
@@ -6846,7 +6872,7 @@ function readGitignoreLines(file) {
|
|
|
6846
6872
|
}
|
|
6847
6873
|
}
|
|
6848
6874
|
function applyGitignorePlan(repositoryRoot, plan) {
|
|
6849
|
-
const file =
|
|
6875
|
+
const file = join11(realpathSync(resolve9(repositoryRoot, plan.path)), ".gitignore");
|
|
6850
6876
|
let existing = "";
|
|
6851
6877
|
try {
|
|
6852
6878
|
existing = readFileSync(file, "utf8");
|
|
@@ -6986,7 +7012,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
|
|
|
6986
7012
|
return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
|
|
6987
7013
|
}
|
|
6988
7014
|
if (real === anchorReal) {
|
|
6989
|
-
const anchorCanonical =
|
|
7015
|
+
const anchorCanonical = join11(real, CANONICAL_FILE);
|
|
6990
7016
|
const anchorState = anchorCanonicalState(anchorCanonical);
|
|
6991
7017
|
if (anchorState === "absent") {
|
|
6992
7018
|
return { ...base, isAnchor: true, reachable: true, canonicalPresent: false, files: [] };
|
|
@@ -7006,7 +7032,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
|
|
|
7006
7032
|
anchorCanonical,
|
|
7007
7033
|
"self"
|
|
7008
7034
|
).map((spec) => {
|
|
7009
|
-
const { state, actualTarget } = inspectSymlink(
|
|
7035
|
+
const { state, actualTarget } = inspectSymlink(join11(real, spec.name), spec.target);
|
|
7010
7036
|
return {
|
|
7011
7037
|
name: spec.name,
|
|
7012
7038
|
expectedTarget: spec.target,
|
|
@@ -7023,16 +7049,16 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
|
|
|
7023
7049
|
files: anchorFiles
|
|
7024
7050
|
};
|
|
7025
7051
|
}
|
|
7026
|
-
if (!existsSync(
|
|
7052
|
+
if (!existsSync(join11(real, ".git"))) {
|
|
7027
7053
|
return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
|
|
7028
7054
|
}
|
|
7029
|
-
const canonicalFile = isSelf ?
|
|
7055
|
+
const canonicalFile = isSelf ? join11(real, CANONICAL_FILE) : join11(anchorReal, "agents", basename7(real), CANONICAL_FILE);
|
|
7030
7056
|
if (!existsSync(canonicalFile)) {
|
|
7031
7057
|
return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
|
|
7032
7058
|
}
|
|
7033
7059
|
const files = expectedSymlinkTargets(real, canonicalFile, mode).map(
|
|
7034
7060
|
(spec) => {
|
|
7035
|
-
const { state, actualTarget } = inspectSymlink(
|
|
7061
|
+
const { state, actualTarget } = inspectSymlink(join11(real, spec.name), spec.target);
|
|
7036
7062
|
return {
|
|
7037
7063
|
name: spec.name,
|
|
7038
7064
|
expectedTarget: spec.target,
|
|
@@ -7061,7 +7087,7 @@ function applySymlinkPlan(repositoryRoot, plan) {
|
|
|
7061
7087
|
const created = [];
|
|
7062
7088
|
const failed = [];
|
|
7063
7089
|
for (const { name, target } of plan.toCreate) {
|
|
7064
|
-
const filePath =
|
|
7090
|
+
const filePath = join11(real, name);
|
|
7065
7091
|
try {
|
|
7066
7092
|
mkdirSync(dirname7(filePath), { recursive: true });
|
|
7067
7093
|
symlinkSync(target, filePath);
|
|
@@ -7095,7 +7121,7 @@ function gatherViewSymlinks(repositoryRoot, anchorReal, roster, viewDir) {
|
|
|
7095
7121
|
if (!existsSync(canonicalFile)) return { kind: "missing-canonical", viewName };
|
|
7096
7122
|
const files = expectedSymlinkTargets(viewDir, canonicalFile, "hub").map(
|
|
7097
7123
|
(spec) => {
|
|
7098
|
-
const { state, actualTarget } = inspectSymlink(
|
|
7124
|
+
const { state, actualTarget } = inspectSymlink(join11(viewDir, spec.name), spec.target);
|
|
7099
7125
|
return {
|
|
7100
7126
|
name: spec.name,
|
|
7101
7127
|
expectedTarget: spec.target,
|
|
@@ -7111,7 +7137,7 @@ function applyViewSymlinks(viewDir, files) {
|
|
|
7111
7137
|
const failed = [];
|
|
7112
7138
|
for (const f of files) {
|
|
7113
7139
|
if (f.state !== "missing") continue;
|
|
7114
|
-
const filePath =
|
|
7140
|
+
const filePath = join11(viewDir, f.name);
|
|
7115
7141
|
try {
|
|
7116
7142
|
mkdirSync(dirname7(filePath), { recursive: true });
|
|
7117
7143
|
symlinkSync(f.expectedTarget, filePath);
|
|
@@ -7333,7 +7359,7 @@ function resolveViewDir(repositoryRoot, viewPath) {
|
|
|
7333
7359
|
return realpathSync(abs);
|
|
7334
7360
|
} catch {
|
|
7335
7361
|
try {
|
|
7336
|
-
return
|
|
7362
|
+
return join11(realpathSync(dirname7(abs)), basename7(abs));
|
|
7337
7363
|
} catch {
|
|
7338
7364
|
return abs;
|
|
7339
7365
|
}
|
|
@@ -7351,7 +7377,7 @@ function gatherViewRepo(repositoryRoot, viewDir, entry) {
|
|
|
7351
7377
|
return { path: entry.path, reachable: false };
|
|
7352
7378
|
}
|
|
7353
7379
|
const linkName = basename7(repoReal);
|
|
7354
|
-
const { state, actualTarget } = inspectSymlink(
|
|
7380
|
+
const { state, actualTarget } = inspectSymlink(join11(viewDir, linkName), expectedTarget);
|
|
7355
7381
|
return {
|
|
7356
7382
|
path: entry.path,
|
|
7357
7383
|
reachable: true,
|
|
@@ -7365,7 +7391,7 @@ function applyViewPlan(viewDir, toCreate) {
|
|
|
7365
7391
|
const created = [];
|
|
7366
7392
|
const failed = [];
|
|
7367
7393
|
for (const { name, target } of toCreate) {
|
|
7368
|
-
const filePath =
|
|
7394
|
+
const filePath = join11(viewDir, name);
|
|
7369
7395
|
try {
|
|
7370
7396
|
mkdirSync(dirname7(filePath), { recursive: true });
|
|
7371
7397
|
symlinkSync(target, filePath);
|
|
@@ -7380,7 +7406,7 @@ var TOP_LEVEL_INSTRUCTION_FILES_LOWER = new Set(
|
|
|
7380
7406
|
INSTRUCTION_FILES.filter((f) => !f.includes("/")).map((f) => f.toLowerCase())
|
|
7381
7407
|
);
|
|
7382
7408
|
function classifyViewLink(viewDir, name, rosterRealpaths) {
|
|
7383
|
-
const filePath =
|
|
7409
|
+
const filePath = join11(viewDir, name);
|
|
7384
7410
|
let isLink;
|
|
7385
7411
|
try {
|
|
7386
7412
|
isLink = lstatSync(filePath).isSymbolicLink();
|
|
@@ -7409,7 +7435,7 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
|
|
|
7409
7435
|
if (!isDir) {
|
|
7410
7436
|
return { target, kind: existsSync(resolved) ? "non-repo" : "broken" };
|
|
7411
7437
|
}
|
|
7412
|
-
return { target, kind: existsSync(
|
|
7438
|
+
return { target, kind: existsSync(join11(resolved, ".git")) ? "repo" : "non-repo" };
|
|
7413
7439
|
}
|
|
7414
7440
|
function gatherExistingViewLinks(viewDir, rosterRealpaths) {
|
|
7415
7441
|
let names;
|
|
@@ -7434,7 +7460,7 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
|
|
|
7434
7460
|
const pruned = [];
|
|
7435
7461
|
const failed = [];
|
|
7436
7462
|
for (const { name } of toPrune) {
|
|
7437
|
-
const filePath =
|
|
7463
|
+
const filePath = join11(viewDir, name);
|
|
7438
7464
|
const c = classifyViewLink(viewDir, name, rosterRealpaths);
|
|
7439
7465
|
if (c === null || c.kind !== "repo") {
|
|
7440
7466
|
failed.push({
|
|
@@ -7647,10 +7673,10 @@ async function runProjectPreset(options, ctx = {}) {
|
|
|
7647
7673
|
}
|
|
7648
7674
|
}
|
|
7649
7675
|
function canonicalFileFor(anchorReal, canonicalName) {
|
|
7650
|
-
return
|
|
7676
|
+
return join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
7651
7677
|
}
|
|
7652
7678
|
function canonicalLabelFor(canonicalName) {
|
|
7653
|
-
return
|
|
7679
|
+
return join11("agents", canonicalName, CANONICAL_FILE);
|
|
7654
7680
|
}
|
|
7655
7681
|
async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
|
|
7656
7682
|
const declared = {
|
|
@@ -7671,7 +7697,7 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
|
|
|
7671
7697
|
if (real === anchorReal) {
|
|
7672
7698
|
return { ...declared, isAnchor: true, reachable: true, canonicalPresent: false };
|
|
7673
7699
|
}
|
|
7674
|
-
if (!existsSync(
|
|
7700
|
+
if (!existsSync(join11(real, ".git"))) {
|
|
7675
7701
|
return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
|
|
7676
7702
|
}
|
|
7677
7703
|
const canonicalName = basename7(real);
|
|
@@ -8053,24 +8079,24 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
|
|
|
8053
8079
|
const instructionFiles = [];
|
|
8054
8080
|
for (const name of INSTRUCTION_FILES) {
|
|
8055
8081
|
try {
|
|
8056
|
-
lstatSync(
|
|
8082
|
+
lstatSync(join11(real, name));
|
|
8057
8083
|
instructionFiles.push(name);
|
|
8058
8084
|
} catch {
|
|
8059
8085
|
}
|
|
8060
8086
|
}
|
|
8061
8087
|
let ignored;
|
|
8062
8088
|
try {
|
|
8063
|
-
ignored = new Set(readGitignoreLines(
|
|
8089
|
+
ignored = new Set(readGitignoreLines(join11(real, ".gitignore")).map((l) => l.trim()));
|
|
8064
8090
|
} catch {
|
|
8065
8091
|
ignored = /* @__PURE__ */ new Set();
|
|
8066
8092
|
}
|
|
8067
8093
|
const gitignorePatterns = INSTRUCTION_FILES.filter((p) => ignored.has(p) || ignored.has(`/${p}`));
|
|
8068
|
-
const canonical2 = existsSync(
|
|
8094
|
+
const canonical2 = existsSync(join11(anchorReal, "agents", canonicalName, CANONICAL_FILE));
|
|
8069
8095
|
let viewLink = false;
|
|
8070
8096
|
const viewPath = manifest.workspace.view;
|
|
8071
8097
|
if (viewPath !== void 0) {
|
|
8072
8098
|
try {
|
|
8073
|
-
lstatSync(
|
|
8099
|
+
lstatSync(join11(resolveViewDir(repositoryRoot, viewPath), canonicalName));
|
|
8074
8100
|
viewLink = true;
|
|
8075
8101
|
} catch {
|
|
8076
8102
|
}
|
|
@@ -8084,11 +8110,11 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
|
|
|
8084
8110
|
};
|
|
8085
8111
|
}
|
|
8086
8112
|
function teardownExpectedTargets(repoReal, anchorReal, canonicalName) {
|
|
8087
|
-
const canonicalFile =
|
|
8113
|
+
const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
8088
8114
|
return expectedSymlinkTargets(repoReal, canonicalFile);
|
|
8089
8115
|
}
|
|
8090
8116
|
function viewLinkPointsAt(viewDir, name, repoReal) {
|
|
8091
|
-
const filePath =
|
|
8117
|
+
const filePath = join11(viewDir, name);
|
|
8092
8118
|
try {
|
|
8093
8119
|
if (!lstatSync(filePath).isSymbolicLink()) return false;
|
|
8094
8120
|
const target = readlinkSync(filePath);
|
|
@@ -8099,7 +8125,7 @@ function viewLinkPointsAt(viewDir, name, repoReal) {
|
|
|
8099
8125
|
}
|
|
8100
8126
|
}
|
|
8101
8127
|
function viewLinkPointsAtPath(viewDir, name, expectedRepoPath) {
|
|
8102
|
-
const filePath =
|
|
8128
|
+
const filePath = join11(viewDir, name);
|
|
8103
8129
|
try {
|
|
8104
8130
|
if (!lstatSync(filePath).isSymbolicLink()) return false;
|
|
8105
8131
|
const target = readlinkSync(filePath);
|
|
@@ -8150,7 +8176,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
|
|
|
8150
8176
|
if (!isAnchor) {
|
|
8151
8177
|
if (repoReal !== void 0) {
|
|
8152
8178
|
for (const spec of teardownExpectedTargets(repoReal, anchorReal, canonicalName)) {
|
|
8153
|
-
const { state, actualTarget } = inspectSymlink(
|
|
8179
|
+
const { state, actualTarget } = inspectSymlink(join11(repoReal, spec.name), spec.target);
|
|
8154
8180
|
if (isSelf) {
|
|
8155
8181
|
if (state !== "missing")
|
|
8156
8182
|
items.push({
|
|
@@ -8187,7 +8213,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
|
|
|
8187
8213
|
}
|
|
8188
8214
|
let ignored;
|
|
8189
8215
|
try {
|
|
8190
|
-
ignored = new Set(readGitignoreLines(
|
|
8216
|
+
ignored = new Set(readGitignoreLines(join11(repoReal, ".gitignore")).map((l) => l.trim()));
|
|
8191
8217
|
for (const p of INSTRUCTION_FILES) {
|
|
8192
8218
|
if (ignored.has(p) || ignored.has(`/${p}`)) {
|
|
8193
8219
|
items.push({
|
|
@@ -8210,7 +8236,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
|
|
|
8210
8236
|
const viewPath = manifest.workspace.view;
|
|
8211
8237
|
if (viewPath !== void 0) {
|
|
8212
8238
|
const viewDir = resolveViewDir(repositoryRoot, viewPath);
|
|
8213
|
-
const linkPath =
|
|
8239
|
+
const linkPath = join11(viewDir, canonicalName);
|
|
8214
8240
|
let isLink = false;
|
|
8215
8241
|
try {
|
|
8216
8242
|
isLink = lstatSync(linkPath).isSymbolicLink();
|
|
@@ -8236,8 +8262,8 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
|
|
|
8236
8262
|
else items.push({ kind: "view-symlink", label: canonicalName, state: "removable" });
|
|
8237
8263
|
}
|
|
8238
8264
|
}
|
|
8239
|
-
const canonicalFile =
|
|
8240
|
-
const canonicalLabel =
|
|
8265
|
+
const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
8266
|
+
const canonicalLabel = join11("agents", canonicalName, CANONICAL_FILE);
|
|
8241
8267
|
let canonicalIsLink = false;
|
|
8242
8268
|
try {
|
|
8243
8269
|
canonicalIsLink = lstatSync(canonicalFile).isSymbolicLink();
|
|
@@ -8341,12 +8367,12 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
|
|
|
8341
8367
|
);
|
|
8342
8368
|
for (const item of removable.filter((i) => i.kind === "instruction-symlink")) {
|
|
8343
8369
|
const expected = expectedByName.get(item.label);
|
|
8344
|
-
if (repoReal === null || expected === void 0 || inspectSymlink(
|
|
8370
|
+
if (repoReal === null || expected === void 0 || inspectSymlink(join11(repoReal, item.label), expected).state !== "correct") {
|
|
8345
8371
|
changed(item.label);
|
|
8346
8372
|
continue;
|
|
8347
8373
|
}
|
|
8348
8374
|
try {
|
|
8349
|
-
unlinkSync(
|
|
8375
|
+
unlinkSync(join11(repoReal, item.label));
|
|
8350
8376
|
removed.push(item.label);
|
|
8351
8377
|
} catch (error) {
|
|
8352
8378
|
failed.push({ label: item.label, message: failureReason(error) });
|
|
@@ -8365,7 +8391,7 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
|
|
|
8365
8391
|
continue;
|
|
8366
8392
|
}
|
|
8367
8393
|
try {
|
|
8368
|
-
unlinkSync(
|
|
8394
|
+
unlinkSync(join11(viewDir, item.label));
|
|
8369
8395
|
removed.push(`view/${item.label}`);
|
|
8370
8396
|
} catch (error) {
|
|
8371
8397
|
failed.push({ label: `view/${item.label}`, message: failureReason(error) });
|
|
@@ -8373,7 +8399,7 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
|
|
|
8373
8399
|
}
|
|
8374
8400
|
const NOFOLLOW = fsConstants.O_NOFOLLOW ?? 0;
|
|
8375
8401
|
for (const item of removable.filter((i) => i.kind === "canonical-block")) {
|
|
8376
|
-
const canonicalFile =
|
|
8402
|
+
const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
8377
8403
|
try {
|
|
8378
8404
|
if (lstatSync(canonicalFile).isSymbolicLink()) {
|
|
8379
8405
|
changed(item.label);
|
|
@@ -8647,12 +8673,12 @@ function gatherRenameWiring(repositoryRoot, manifest, oldBasename) {
|
|
|
8647
8673
|
} catch {
|
|
8648
8674
|
return { canonicalDirOld: false, viewLinkOld: false };
|
|
8649
8675
|
}
|
|
8650
|
-
const canonicalDirOld = existsSync(
|
|
8676
|
+
const canonicalDirOld = existsSync(join11(anchorReal, "agents", oldBasename));
|
|
8651
8677
|
let viewLinkOld = false;
|
|
8652
8678
|
const viewPath = manifest.workspace.view;
|
|
8653
8679
|
if (viewPath !== void 0) {
|
|
8654
8680
|
try {
|
|
8655
|
-
lstatSync(
|
|
8681
|
+
lstatSync(join11(resolveViewDir(repositoryRoot, viewPath), oldBasename));
|
|
8656
8682
|
viewLinkOld = true;
|
|
8657
8683
|
} catch {
|
|
8658
8684
|
}
|
|
@@ -8997,7 +9023,7 @@ async function doRunProjectSeedAnchor(options, ctx) {
|
|
|
8997
9023
|
console.log("\u2139\uFE0F No repo roster declared \u2014 nothing to seed.");
|
|
8998
9024
|
return;
|
|
8999
9025
|
}
|
|
9000
|
-
const anchorDoc =
|
|
9026
|
+
const anchorDoc = join11(repositoryRoot, CANONICAL_FILE);
|
|
9001
9027
|
if (pathPresent(anchorDoc)) {
|
|
9002
9028
|
console.log(
|
|
9003
9029
|
`\u2705 The anchor's own \`${CANONICAL_FILE}\` already exists \u2014 hand-maintained, left untouched.`
|
|
@@ -9059,7 +9085,7 @@ function regularFileSpokes(repoReal) {
|
|
|
9059
9085
|
const out = [];
|
|
9060
9086
|
for (const spoke of ["CLAUDE.md", ".github/copilot-instructions.md"]) {
|
|
9061
9087
|
try {
|
|
9062
|
-
const st = lstatSync(
|
|
9088
|
+
const st = lstatSync(join11(repoReal, spoke));
|
|
9063
9089
|
if (!st.isSymbolicLink() && st.isFile()) out.push(spoke);
|
|
9064
9090
|
} catch {
|
|
9065
9091
|
}
|
|
@@ -9105,8 +9131,8 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argAbs, argReal, vie
|
|
|
9105
9131
|
};
|
|
9106
9132
|
}
|
|
9107
9133
|
const isAnchor = argReal === anchorReal;
|
|
9108
|
-
const reachable = existsSync(
|
|
9109
|
-
const canonicalFile =
|
|
9134
|
+
const reachable = existsSync(join11(argReal, ".git"));
|
|
9135
|
+
const canonicalFile = join11(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
9110
9136
|
return {
|
|
9111
9137
|
path,
|
|
9112
9138
|
declared,
|
|
@@ -9115,13 +9141,13 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argAbs, argReal, vie
|
|
|
9115
9141
|
reachable,
|
|
9116
9142
|
canonicalName,
|
|
9117
9143
|
...viewCanonicalName !== void 0 ? { viewCanonicalName } : {},
|
|
9118
|
-
agentsState: inspectAgentsState(
|
|
9144
|
+
agentsState: inspectAgentsState(join11(argReal, CANONICAL_FILE)),
|
|
9119
9145
|
canonicalExists: pathPresent(canonicalFile),
|
|
9120
9146
|
regularSpokes: regularFileSpokes(argReal)
|
|
9121
9147
|
};
|
|
9122
9148
|
}
|
|
9123
9149
|
function relocateAgentsFile(repoReal, canonicalFile) {
|
|
9124
|
-
const agentsFile =
|
|
9150
|
+
const agentsFile = join11(repoReal, CANONICAL_FILE);
|
|
9125
9151
|
try {
|
|
9126
9152
|
mkdirSync(dirname7(canonicalFile), { recursive: true });
|
|
9127
9153
|
} catch (error) {
|
|
@@ -9237,7 +9263,7 @@ async function doRunProjectRetrofit(repo, options, ctx) {
|
|
|
9237
9263
|
let failure;
|
|
9238
9264
|
let partial = false;
|
|
9239
9265
|
if (options.apply === true && plan.action === "relocate" && argReal !== void 0) {
|
|
9240
|
-
const canonicalFile =
|
|
9266
|
+
const canonicalFile = join11(anchorReal, "agents", plan.canonicalName, CANONICAL_FILE);
|
|
9241
9267
|
const res = relocateAgentsFile(argReal, canonicalFile);
|
|
9242
9268
|
if (res.ok) {
|
|
9243
9269
|
applied = true;
|
|
@@ -9629,7 +9655,7 @@ import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
|
|
|
9629
9655
|
// src/commands/refresh-watch.ts
|
|
9630
9656
|
import { readdir as readdir3, stat as stat6 } from "fs/promises";
|
|
9631
9657
|
import { homedir as homedir9 } from "os";
|
|
9632
|
-
import { join as
|
|
9658
|
+
import { join as join12 } from "path";
|
|
9633
9659
|
import {
|
|
9634
9660
|
assertApprovalStoreSafe as assertApprovalStoreSafe2,
|
|
9635
9661
|
assertLockStoreSafe as assertLockStoreSafe4,
|
|
@@ -9641,8 +9667,8 @@ var MIN_WATCH_INTERVAL_SEC = 5;
|
|
|
9641
9667
|
var MAX_WATCH_INTERVAL_SEC = 86400;
|
|
9642
9668
|
function watchedRoots(ctx) {
|
|
9643
9669
|
return [
|
|
9644
|
-
ctx.codexSessionsDir ??
|
|
9645
|
-
ctx.claudeProjectsDir ??
|
|
9670
|
+
ctx.codexSessionsDir ?? join12(homedir9(), ".codex", "sessions"),
|
|
9671
|
+
ctx.claudeProjectsDir ?? join12(homedir9(), ".claude", "projects")
|
|
9646
9672
|
];
|
|
9647
9673
|
}
|
|
9648
9674
|
async function scanSourceLogs(roots) {
|
|
@@ -9656,7 +9682,7 @@ async function scanSourceLogs(roots) {
|
|
|
9656
9682
|
throw new Error("Failed to read a source log directory", { cause: error });
|
|
9657
9683
|
}
|
|
9658
9684
|
for (const entry of entries) {
|
|
9659
|
-
const full =
|
|
9685
|
+
const full = join12(dir, entry.name);
|
|
9660
9686
|
if (entry.isDirectory()) {
|
|
9661
9687
|
await walk(full);
|
|
9662
9688
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
@@ -10502,7 +10528,7 @@ function unattachedLines(u) {
|
|
|
10502
10528
|
// src/commands/run.ts
|
|
10503
10529
|
import { mkdir as mkdir3, readFile as readFile6, rm as rm3 } from "fs/promises";
|
|
10504
10530
|
import { homedir as homedir11 } from "os";
|
|
10505
|
-
import { join as
|
|
10531
|
+
import { join as join13 } from "path";
|
|
10506
10532
|
import {
|
|
10507
10533
|
acquireLock as acquireLock5,
|
|
10508
10534
|
assertBasouRootSafe as assertBasouRootSafe13,
|
|
@@ -10517,6 +10543,7 @@ import {
|
|
|
10517
10543
|
findBasouSessionStartHook as findBasouSessionStartHook2,
|
|
10518
10544
|
getDiff,
|
|
10519
10545
|
getSnapshot as getSnapshot2,
|
|
10546
|
+
lstatSessionFile as lstatSessionFile2,
|
|
10520
10547
|
overwriteYamlFile as overwriteYamlFile2,
|
|
10521
10548
|
prefixedUlid as prefixedUlid4,
|
|
10522
10549
|
readManifest as readManifest12,
|
|
@@ -10524,6 +10551,7 @@ import {
|
|
|
10524
10551
|
resolveClaudeCodeCommand,
|
|
10525
10552
|
resolveCodexCommand,
|
|
10526
10553
|
SESSION_SCHEMA_VERSION as SESSION_SCHEMA_VERSION2,
|
|
10554
|
+
SessionFileNotAFileError as SessionFileNotAFileError4,
|
|
10527
10555
|
SessionSchema as SessionSchema2,
|
|
10528
10556
|
sanitizeRelatedFiles,
|
|
10529
10557
|
sanitizeWorkingDirectory as sanitizeWorkingDirectory2,
|
|
@@ -10583,12 +10611,12 @@ async function runTrackedTool(args, options, ctx, adapter) {
|
|
|
10583
10611
|
const manifest = await readManifest12(paths);
|
|
10584
10612
|
await assertSessionStoreSafe3(paths);
|
|
10585
10613
|
const sessionId = prefixedUlid4("ses");
|
|
10586
|
-
const sessionDir =
|
|
10614
|
+
const sessionDir = join13(paths.sessions, sessionId);
|
|
10587
10615
|
const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
|
|
10588
10616
|
await coreAppendChainedEvent2(paths, sessionId, event);
|
|
10589
10617
|
});
|
|
10590
10618
|
const startedAt = now().toISOString();
|
|
10591
|
-
const sessionYamlPath =
|
|
10619
|
+
const sessionYamlPath = join13(sessionDir, "session.yaml");
|
|
10592
10620
|
const session = buildInitialSession2({
|
|
10593
10621
|
id: sessionId,
|
|
10594
10622
|
command,
|
|
@@ -10639,7 +10667,7 @@ async function runTrackedTool(args, options, ctx, adapter) {
|
|
|
10639
10667
|
});
|
|
10640
10668
|
const runningLock = await acquireLock5(paths, "session", sessionId);
|
|
10641
10669
|
try {
|
|
10642
|
-
await mutateSessionYaml2(
|
|
10670
|
+
await mutateSessionYaml2(paths, sessionId, (s) => {
|
|
10643
10671
|
s.session.status = "running";
|
|
10644
10672
|
});
|
|
10645
10673
|
} finally {
|
|
@@ -10885,7 +10913,14 @@ function buildInitialSession2(input) {
|
|
|
10885
10913
|
}
|
|
10886
10914
|
};
|
|
10887
10915
|
}
|
|
10888
|
-
async function mutateSessionYaml2(
|
|
10916
|
+
async function mutateSessionYaml2(paths, sessionId, mutator) {
|
|
10917
|
+
const kind = await lstatSessionFile2(paths, sessionId, "session.yaml").catch((error) => {
|
|
10918
|
+
throw new Error("Failed to read YAML file", { cause: error });
|
|
10919
|
+
});
|
|
10920
|
+
if (kind === "symlink" || kind === "not_a_file") {
|
|
10921
|
+
throw new SessionFileNotAFileError4(sessionId, "session.yaml", kind);
|
|
10922
|
+
}
|
|
10923
|
+
const filePath = join13(paths.sessions, sessionId, "session.yaml");
|
|
10889
10924
|
const raw = await readYamlFile5(filePath);
|
|
10890
10925
|
const parsed = SessionSchema2.parse(raw);
|
|
10891
10926
|
mutator(parsed);
|
|
@@ -10953,7 +10988,7 @@ async function noteCodexHookStatusPreSpawn(_cwd, ctx) {
|
|
|
10953
10988
|
|
|
10954
10989
|
// src/commands/session.ts
|
|
10955
10990
|
import { readFile as readFile7 } from "fs/promises";
|
|
10956
|
-
import { basename as basename8, isAbsolute as isAbsolute5, join as
|
|
10991
|
+
import { basename as basename8, isAbsolute as isAbsolute5, join as join14, relative as relative4, sep as sep3 } from "path";
|
|
10957
10992
|
import {
|
|
10958
10993
|
acquireLock as acquireLock6,
|
|
10959
10994
|
appendEventToExistingSession as appendEventToExistingSession3,
|
|
@@ -10967,6 +11002,7 @@ import {
|
|
|
10967
11002
|
importSessionFromJson as importSessionFromJson2,
|
|
10968
11003
|
LOCAL_CLI_EVENT_SOURCE as LOCAL_CLI_EVENT_SOURCE4,
|
|
10969
11004
|
loadSessionEntries as loadSessionEntries2,
|
|
11005
|
+
lstatSessionFile as lstatSessionFile3,
|
|
10970
11006
|
normalizeSessionTimestamps,
|
|
10971
11007
|
readAllEvents,
|
|
10972
11008
|
readManifest as readManifest13,
|
|
@@ -10976,6 +11012,7 @@ import {
|
|
|
10976
11012
|
resolveSessionId as resolveSessionId3,
|
|
10977
11013
|
resolveTaskId,
|
|
10978
11014
|
SESSION_IMPORT_SCHEMA_VERSION as SESSION_IMPORT_SCHEMA_VERSION2,
|
|
11015
|
+
SessionFileNotAFileError as SessionFileNotAFileError5,
|
|
10979
11016
|
SessionImportPayloadSchema as SessionImportPayloadSchema2,
|
|
10980
11017
|
SessionSchema as SessionSchema3,
|
|
10981
11018
|
SessionStatusSchema,
|
|
@@ -11085,8 +11122,16 @@ async function doRunSessionShow(idInput, options, ctx) {
|
|
|
11085
11122
|
const paths = basouPaths19(repositoryRoot);
|
|
11086
11123
|
await assertWorkspaceInitialized12(paths.root);
|
|
11087
11124
|
const sessionId = await resolveSessionId3(paths, idInput);
|
|
11088
|
-
const sessionDir =
|
|
11089
|
-
const sessionYamlPath =
|
|
11125
|
+
const sessionDir = join14(paths.sessions, sessionId);
|
|
11126
|
+
const sessionYamlPath = join14(sessionDir, "session.yaml");
|
|
11127
|
+
const yamlKind = await lstatSessionFile3(paths, sessionId, "session.yaml").catch(
|
|
11128
|
+
(error) => {
|
|
11129
|
+
throw new Error("Failed to read session", { cause: error });
|
|
11130
|
+
}
|
|
11131
|
+
);
|
|
11132
|
+
if (yamlKind === "symlink" || yamlKind === "not_a_file") {
|
|
11133
|
+
throw new SessionFileNotAFileError5(sessionId, "session.yaml", yamlKind);
|
|
11134
|
+
}
|
|
11090
11135
|
let session;
|
|
11091
11136
|
try {
|
|
11092
11137
|
const raw = await readYamlFile6(sessionYamlPath);
|
|
@@ -11098,7 +11143,7 @@ async function doRunSessionShow(idInput, options, ctx) {
|
|
|
11098
11143
|
throw new Error("Failed to read session", { cause: error });
|
|
11099
11144
|
}
|
|
11100
11145
|
let eventsLostLines = 0;
|
|
11101
|
-
const events = await readAllEvents(
|
|
11146
|
+
const events = await readAllEvents(paths, sessionId, {
|
|
11102
11147
|
onWarning: (w) => {
|
|
11103
11148
|
if (w.kind === "malformed_json" || w.kind === "schema_violation") eventsLostLines++;
|
|
11104
11149
|
printReplayWarning(w, sessionId);
|
|
@@ -11616,7 +11661,7 @@ async function doRunSessionRechain(options, ctx) {
|
|
|
11616
11661
|
}
|
|
11617
11662
|
}
|
|
11618
11663
|
const tamperedCount = rows.filter(
|
|
11619
|
-
(r) => r.reason === "tampered" || r.reason === "symlink" || r.reason === "not_a_directory"
|
|
11664
|
+
(r) => r.reason === "tampered" || r.reason === "symlink" || r.reason === "not_a_directory" || r.reason === "events_not_a_file" || r.reason === "yaml_not_a_file"
|
|
11620
11665
|
).length;
|
|
11621
11666
|
const errorCount = rows.filter((r) => r.status === "error").length;
|
|
11622
11667
|
if (options.json === true) {
|
|
@@ -11644,6 +11689,10 @@ function renderRechainRow(row, dryRun) {
|
|
|
11644
11689
|
if (row.reason === "symlink" || row.reason === "not_a_directory") {
|
|
11645
11690
|
return `skipped (${row.reason === "symlink" ? "SYMLINK" : "NOT A DIRECTORY"} \u2014 not followed; inspect with 'basou verify')`;
|
|
11646
11691
|
}
|
|
11692
|
+
if (row.reason === "events_not_a_file" || row.reason === "yaml_not_a_file") {
|
|
11693
|
+
const file = row.reason === "events_not_a_file" ? "events.jsonl" : "session.yaml";
|
|
11694
|
+
return `skipped (${file} NOT A REGULAR FILE \u2014 not followed; inspect with 'basou verify')`;
|
|
11695
|
+
}
|
|
11647
11696
|
return `skipped (${row.reason})`;
|
|
11648
11697
|
case "error":
|
|
11649
11698
|
return `error (${row.message})`;
|
|
@@ -11852,7 +11901,6 @@ function formatVersionGateMessage(error) {
|
|
|
11852
11901
|
|
|
11853
11902
|
// src/commands/task.ts
|
|
11854
11903
|
import { readFile as readFile8 } from "fs/promises";
|
|
11855
|
-
import { join as join16 } from "path";
|
|
11856
11904
|
import {
|
|
11857
11905
|
archiveTask,
|
|
11858
11906
|
assertBasouRootSafe as assertBasouRootSafe17,
|
|
@@ -12177,9 +12225,8 @@ async function doRunTaskShow(idInput, options, ctx) {
|
|
|
12177
12225
|
const events = [];
|
|
12178
12226
|
const linkedSessionIds = new Set(doc.task.task.linked_sessions);
|
|
12179
12227
|
for (const s of sessions) {
|
|
12180
|
-
const sessionDir = join16(paths.sessions, s.sessionId);
|
|
12181
12228
|
try {
|
|
12182
|
-
for await (const ev of replayEvents3(
|
|
12229
|
+
for await (const ev of replayEvents3(paths, s.sessionId, {
|
|
12183
12230
|
onWarning: (w) => printReplayWarning(w, s.sessionId)
|
|
12184
12231
|
})) {
|
|
12185
12232
|
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) {
|
|
@@ -13072,7 +13119,7 @@ async function assertWorkspaceInitialized15(basouRoot) {
|
|
|
13072
13119
|
// src/commands/view.ts
|
|
13073
13120
|
import { spawn } from "child_process";
|
|
13074
13121
|
import { createHash as createHash2 } from "crypto";
|
|
13075
|
-
import { basename as basename11, dirname as dirname9, join as
|
|
13122
|
+
import { basename as basename11, dirname as dirname9, join as join17, resolve as resolve14 } from "path";
|
|
13076
13123
|
import {
|
|
13077
13124
|
assertBasouRootSafe as assertBasouRootSafe19,
|
|
13078
13125
|
basouPaths as basouPaths25,
|
|
@@ -13086,7 +13133,7 @@ import { InvalidArgumentError as InvalidArgumentError8 } from "commander";
|
|
|
13086
13133
|
import { createReadStream as createReadStream2 } from "fs";
|
|
13087
13134
|
import { readdir as readdir4, stat as stat7 } from "fs/promises";
|
|
13088
13135
|
import { homedir as homedir12 } from "os";
|
|
13089
|
-
import { basename as basename9, dirname as dirname8, join as
|
|
13136
|
+
import { basename as basename9, dirname as dirname8, join as join15 } from "path";
|
|
13090
13137
|
import { createInterface as createInterface2 } from "readline";
|
|
13091
13138
|
import { basouPaths as basouPaths24, readManifest as readManifest16 } from "@basou/core";
|
|
13092
13139
|
function uncapturedTotal(result) {
|
|
@@ -13098,8 +13145,8 @@ async function checkPortfolioCoverage(workspaces, ctx = {}) {
|
|
|
13098
13145
|
ctx.portfolioConfigPath
|
|
13099
13146
|
);
|
|
13100
13147
|
const listedDirs = new Set([...roots].map((root) => encodeProjectDir(root)));
|
|
13101
|
-
const claudeProjectsDir = ctx.claudeProjectsDir ??
|
|
13102
|
-
const codexSessionsDir = ctx.codexSessionsDir ??
|
|
13148
|
+
const claudeProjectsDir = ctx.claudeProjectsDir ?? join15(homedir12(), ".claude", "projects");
|
|
13149
|
+
const codexSessionsDir = ctx.codexSessionsDir ?? join15(homedir12(), ".codex", "sessions");
|
|
13103
13150
|
const tally = new Tally(roots);
|
|
13104
13151
|
const absentTrees = [];
|
|
13105
13152
|
const claudeFiles = await listClaudeTranscripts(claudeProjectsDir);
|
|
@@ -13237,7 +13284,7 @@ async function isDirEntry(parent, entry) {
|
|
|
13237
13284
|
if (entry.isDirectory()) return true;
|
|
13238
13285
|
if (!entry.isSymbolicLink()) return false;
|
|
13239
13286
|
try {
|
|
13240
|
-
return (await stat7(
|
|
13287
|
+
return (await stat7(join15(parent, entry.name))).isDirectory();
|
|
13241
13288
|
} catch {
|
|
13242
13289
|
return false;
|
|
13243
13290
|
}
|
|
@@ -13252,7 +13299,7 @@ async function listClaudeTranscripts(projectsRoot) {
|
|
|
13252
13299
|
const files = [];
|
|
13253
13300
|
for (const entry of entries) {
|
|
13254
13301
|
if (!await isDirEntry(projectsRoot, entry)) continue;
|
|
13255
|
-
const full =
|
|
13302
|
+
const full = join15(projectsRoot, entry.name);
|
|
13256
13303
|
let names;
|
|
13257
13304
|
try {
|
|
13258
13305
|
names = await readdir4(full);
|
|
@@ -13260,7 +13307,7 @@ async function listClaudeTranscripts(projectsRoot) {
|
|
|
13260
13307
|
continue;
|
|
13261
13308
|
}
|
|
13262
13309
|
for (const name of names) {
|
|
13263
|
-
if (name.endsWith(".jsonl")) files.push(
|
|
13310
|
+
if (name.endsWith(".jsonl")) files.push(join15(full, name));
|
|
13264
13311
|
}
|
|
13265
13312
|
}
|
|
13266
13313
|
return files.sort();
|
|
@@ -13275,7 +13322,7 @@ async function listCodexRollouts(sessionsRoot) {
|
|
|
13275
13322
|
return;
|
|
13276
13323
|
}
|
|
13277
13324
|
for (const entry of entries) {
|
|
13278
|
-
const full =
|
|
13325
|
+
const full = join15(dir, entry.name);
|
|
13279
13326
|
if (entry.isDirectory()) await walk(full);
|
|
13280
13327
|
else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) {
|
|
13281
13328
|
found.push(full);
|
|
@@ -13406,7 +13453,7 @@ function inertLines(result) {
|
|
|
13406
13453
|
// src/lib/portfolio-safety.ts
|
|
13407
13454
|
import { execFile as execFile2 } from "child_process";
|
|
13408
13455
|
import { lstat as lstat3, realpath as realpath5 } from "fs/promises";
|
|
13409
|
-
import { isAbsolute as isAbsolute6, join as
|
|
13456
|
+
import { isAbsolute as isAbsolute6, join as join16, relative as relative5, resolve as resolve12, sep as sep4 } from "path";
|
|
13410
13457
|
import { promisify as promisify2 } from "util";
|
|
13411
13458
|
import { readManifest as readManifest17 } from "@basou/core";
|
|
13412
13459
|
var execFileAsync2 = promisify2(execFile2);
|
|
@@ -13430,7 +13477,7 @@ function isBasouPath(p) {
|
|
|
13430
13477
|
async function inspectRepo(repoPath) {
|
|
13431
13478
|
let hasEntry = false;
|
|
13432
13479
|
try {
|
|
13433
|
-
await lstat3(
|
|
13480
|
+
await lstat3(join16(repoPath, ".basou"));
|
|
13434
13481
|
hasEntry = true;
|
|
13435
13482
|
} catch (error) {
|
|
13436
13483
|
if (errorCode2(error) !== "ENOENT") {
|
|
@@ -13584,7 +13631,7 @@ function formatSafetyReport(result) {
|
|
|
13584
13631
|
|
|
13585
13632
|
// src/lib/view-server.ts
|
|
13586
13633
|
import { createServer } from "http";
|
|
13587
|
-
import { basename as basename10,
|
|
13634
|
+
import { basename as basename10, resolve as resolve13 } from "path";
|
|
13588
13635
|
import {
|
|
13589
13636
|
boardLivePage,
|
|
13590
13637
|
boardPage,
|
|
@@ -13606,7 +13653,9 @@ import {
|
|
|
13606
13653
|
renderDecisions as renderDecisions3,
|
|
13607
13654
|
renderHandoff as renderHandoff3,
|
|
13608
13655
|
resolveViewLanguageFromPaths,
|
|
13656
|
+
SessionFileNotAFileError as SessionFileNotAFileError6,
|
|
13609
13657
|
SessionIdSchema,
|
|
13658
|
+
SessionNotADirectoryError,
|
|
13610
13659
|
summarizeOrientation,
|
|
13611
13660
|
tryRemoteUrl
|
|
13612
13661
|
} from "@basou/core";
|
|
@@ -15609,10 +15658,7 @@ async function sessionDetail(ws, sessionId) {
|
|
|
15609
15658
|
}
|
|
15610
15659
|
const entry = await inspectSessionEntry2(ws.paths, sessionId);
|
|
15611
15660
|
if (entry === "symlink" || entry === "not_a_directory") {
|
|
15612
|
-
throw new HttpError(
|
|
15613
|
-
404,
|
|
15614
|
-
`Session ${sessionId} is not a directory; a symlink or a file there is not followed`
|
|
15615
|
-
);
|
|
15661
|
+
throw new HttpError(404, new SessionNotADirectoryError(sessionId, entry).message);
|
|
15616
15662
|
}
|
|
15617
15663
|
let session;
|
|
15618
15664
|
try {
|
|
@@ -15621,10 +15667,11 @@ async function sessionDetail(ws, sessionId) {
|
|
|
15621
15667
|
if (error instanceof Error && error.message === "YAML file not found") {
|
|
15622
15668
|
throw new HttpError(404, "Session not found");
|
|
15623
15669
|
}
|
|
15670
|
+
if (error instanceof SessionFileNotAFileError6) throw new HttpError(404, error.message);
|
|
15624
15671
|
throw error;
|
|
15625
15672
|
}
|
|
15626
15673
|
try {
|
|
15627
|
-
const events = await readAllEvents2(
|
|
15674
|
+
const events = await readAllEvents2(ws.paths, sessionId);
|
|
15628
15675
|
return { session, events };
|
|
15629
15676
|
} catch {
|
|
15630
15677
|
return { session, events: [], degraded: true };
|
|
@@ -15889,7 +15936,7 @@ async function viewBoardOf(root, paths) {
|
|
|
15889
15936
|
own = void 0;
|
|
15890
15937
|
}
|
|
15891
15938
|
if (own?.visibility !== "private") return { why: "no_board" };
|
|
15892
|
-
return { recordsDir:
|
|
15939
|
+
return { recordsDir: join17(root, dirname9(DEFAULT_BOARD_PATH), "records") };
|
|
15893
15940
|
}
|
|
15894
15941
|
async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
|
|
15895
15942
|
const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve14(cwd, p) })) : await loadPortfolioConfig6(ctx.portfolioConfigPath);
|
|
@@ -16043,7 +16090,7 @@ function installExperimentalNotice(program2, names = EXPERIMENTAL_COMMANDS, writ
|
|
|
16043
16090
|
function readBuildStamp() {
|
|
16044
16091
|
if (false) return void 0;
|
|
16045
16092
|
try {
|
|
16046
|
-
return JSON.parse('{"version":"0.
|
|
16093
|
+
return JSON.parse('{"version":"0.70.0","commit":"60d302a","committedAt":"2026-10-16T17:39:30+09:00"}');
|
|
16047
16094
|
} catch {
|
|
16048
16095
|
return void 0;
|
|
16049
16096
|
}
|