@basou/cli 0.14.0 → 0.15.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 +672 -269
- package/dist/index.js.map +1 -1
- package/dist/program.js +672 -269
- package/dist/program.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -634,18 +634,18 @@ function printNoApprovals(options) {
|
|
|
634
634
|
|
|
635
635
|
// src/commands/decision.ts
|
|
636
636
|
import { readFile } from "fs/promises";
|
|
637
|
-
import { homedir } from "os";
|
|
638
|
-
import { resolve } from "path";
|
|
637
|
+
import { homedir as homedir2 } from "os";
|
|
638
|
+
import { resolve as resolve3 } from "path";
|
|
639
639
|
import {
|
|
640
640
|
acquireLock as acquireLock2,
|
|
641
641
|
appendEventToExistingSession,
|
|
642
642
|
assertBasouRootSafe as assertBasouRootSafe2,
|
|
643
|
-
basouPaths as
|
|
643
|
+
basouPaths as basouPaths3,
|
|
644
644
|
createAdHocSessionWithEvent,
|
|
645
645
|
findErrorCode as findErrorCode2,
|
|
646
646
|
isValidPrefixedId,
|
|
647
647
|
prefixedUlid as prefixedUlid2,
|
|
648
|
-
readManifest,
|
|
648
|
+
readManifest as readManifest2,
|
|
649
649
|
resolveRepositoryRoot as resolveRepositoryRoot2,
|
|
650
650
|
resolveSessionId,
|
|
651
651
|
sanitizePath
|
|
@@ -653,11 +653,73 @@ import {
|
|
|
653
653
|
import { InvalidArgumentError } from "commander";
|
|
654
654
|
|
|
655
655
|
// src/lib/repo-root.ts
|
|
656
|
-
import {
|
|
657
|
-
|
|
656
|
+
import { realpath, stat } from "fs/promises";
|
|
657
|
+
import { basename, resolve as resolve2 } from "path";
|
|
658
|
+
import { basouPaths as basouPaths2, readManifest, resolveBasouRepositoryRoot } from "@basou/core";
|
|
659
|
+
|
|
660
|
+
// src/lib/portfolio-config.ts
|
|
661
|
+
import { homedir } from "os";
|
|
662
|
+
import { isAbsolute, join as join2, resolve } from "path";
|
|
663
|
+
import { readYamlFile as readYamlFile2 } from "@basou/core";
|
|
664
|
+
var DEFAULT_PORTFOLIO_CONFIG_PATH = join2(homedir(), ".basou", "portfolio.yaml");
|
|
665
|
+
function expandTilde(p) {
|
|
666
|
+
if (p === "~") return homedir();
|
|
667
|
+
if (p.startsWith("~/")) return join2(homedir(), p.slice(2));
|
|
668
|
+
return p;
|
|
669
|
+
}
|
|
670
|
+
function isRecord(value) {
|
|
671
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
672
|
+
}
|
|
673
|
+
async function loadPortfolioConfig(configPath = DEFAULT_PORTFOLIO_CONFIG_PATH) {
|
|
674
|
+
let raw;
|
|
658
675
|
try {
|
|
659
|
-
|
|
660
|
-
|
|
676
|
+
raw = await readYamlFile2(configPath);
|
|
677
|
+
} catch (error) {
|
|
678
|
+
if (error instanceof Error && error.message === "YAML file not found") {
|
|
679
|
+
throw new Error(
|
|
680
|
+
"No portfolio config at ~/.basou/portfolio.yaml. Create one (a 'workspaces:' list of repo paths) or pass --workspace <path>."
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
if (error instanceof Error && error.message === "Failed to parse YAML content") {
|
|
684
|
+
throw new Error("~/.basou/portfolio.yaml is not valid YAML.");
|
|
685
|
+
}
|
|
686
|
+
throw error;
|
|
687
|
+
}
|
|
688
|
+
if (!isRecord(raw) || !Array.isArray(raw.workspaces)) {
|
|
689
|
+
throw new Error("~/.basou/portfolio.yaml must contain a 'workspaces:' list.");
|
|
690
|
+
}
|
|
691
|
+
const seen = /* @__PURE__ */ new Set();
|
|
692
|
+
const result = [];
|
|
693
|
+
for (const entry of raw.workspaces) {
|
|
694
|
+
if (!isRecord(entry) || typeof entry.path !== "string" || entry.path.trim().length === 0) {
|
|
695
|
+
throw new Error("Each portfolio workspace needs a non-empty string 'path'.");
|
|
696
|
+
}
|
|
697
|
+
if (entry.label !== void 0 && typeof entry.label !== "string") {
|
|
698
|
+
throw new Error("A portfolio workspace 'label' must be a string when present.");
|
|
699
|
+
}
|
|
700
|
+
const expanded = expandTilde(entry.path.trim());
|
|
701
|
+
if (!isAbsolute(expanded)) {
|
|
702
|
+
throw new Error(
|
|
703
|
+
"Portfolio workspace paths must be absolute (or start with '~'); use --workspace for relative ad-hoc paths."
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
const abs = resolve(expanded);
|
|
707
|
+
if (seen.has(abs)) continue;
|
|
708
|
+
seen.add(abs);
|
|
709
|
+
result.push(entry.label !== void 0 ? { path: abs, label: entry.label } : { path: abs });
|
|
710
|
+
}
|
|
711
|
+
if (result.length === 0) {
|
|
712
|
+
throw new Error("~/.basou/portfolio.yaml has no workspaces.");
|
|
713
|
+
}
|
|
714
|
+
return result;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// src/lib/repo-root.ts
|
|
718
|
+
async function resolveBasouRootForCommand(cwd, commandName, opts = {}) {
|
|
719
|
+
let root;
|
|
720
|
+
try {
|
|
721
|
+
root = await resolveBasouRepositoryRoot(cwd, {
|
|
722
|
+
onRedirect: ({ via, root: root2 }) => console.error(`Resolved workspace view to ${root2} (via ${via}).`)
|
|
661
723
|
});
|
|
662
724
|
} catch (error) {
|
|
663
725
|
if (error instanceof Error && error.message === "Not a git repository") {
|
|
@@ -668,6 +730,84 @@ async function resolveBasouRootForCommand(cwd, commandName) {
|
|
|
668
730
|
}
|
|
669
731
|
throw error;
|
|
670
732
|
}
|
|
733
|
+
if (!await hasBasouStore(root)) {
|
|
734
|
+
const master = await resolveMemberToMaster(
|
|
735
|
+
root,
|
|
736
|
+
opts.portfolioConfigPath ?? DEFAULT_PORTFOLIO_CONFIG_PATH
|
|
737
|
+
);
|
|
738
|
+
if (master !== void 0) {
|
|
739
|
+
console.error(
|
|
740
|
+
`Resolved portfolio member to ${master.root} (via portfolio: ${master.label}).`
|
|
741
|
+
);
|
|
742
|
+
return master.root;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return root;
|
|
746
|
+
}
|
|
747
|
+
async function hasBasouStore(root) {
|
|
748
|
+
try {
|
|
749
|
+
return (await stat(basouPaths2(root).root)).isDirectory();
|
|
750
|
+
} catch {
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
async function resolveMemberToMaster(repoRoot, configPath) {
|
|
755
|
+
let workspaces;
|
|
756
|
+
try {
|
|
757
|
+
workspaces = await loadPortfolioConfig(configPath);
|
|
758
|
+
} catch (error) {
|
|
759
|
+
if (!(error instanceof Error) || !error.message.startsWith("No portfolio config at")) {
|
|
760
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
761
|
+
console.error(`Ignoring ~/.basou/portfolio.yaml: ${detail}`);
|
|
762
|
+
}
|
|
763
|
+
return void 0;
|
|
764
|
+
}
|
|
765
|
+
const memberReal = await realpathOrNull(repoRoot);
|
|
766
|
+
if (memberReal === null) return void 0;
|
|
767
|
+
const claimants = /* @__PURE__ */ new Map();
|
|
768
|
+
const seenMaster = /* @__PURE__ */ new Set();
|
|
769
|
+
for (const ws of workspaces) {
|
|
770
|
+
const masterReal = await realpathOrNull(ws.path);
|
|
771
|
+
if (masterReal === null) continue;
|
|
772
|
+
if (masterReal === memberReal) continue;
|
|
773
|
+
if (seenMaster.has(masterReal)) continue;
|
|
774
|
+
seenMaster.add(masterReal);
|
|
775
|
+
let manifest;
|
|
776
|
+
try {
|
|
777
|
+
manifest = await readManifest(basouPaths2(masterReal));
|
|
778
|
+
} catch (error) {
|
|
779
|
+
if (error instanceof Error && error.message !== "YAML file not found") {
|
|
780
|
+
console.error(
|
|
781
|
+
`Skipping portfolio workspace '${ws.label ?? basename(masterReal)}': could not read its manifest (${error.message}).`
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
786
|
+
const sourceRoots = manifest.import?.source_roots ?? ["."];
|
|
787
|
+
for (const sr of sourceRoots) {
|
|
788
|
+
const real = await realpathOrNull(resolve2(masterReal, sr));
|
|
789
|
+
if (real !== null && real === memberReal) {
|
|
790
|
+
claimants.set(masterReal, { root: masterReal, label: ws.label ?? basename(masterReal) });
|
|
791
|
+
break;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
const matched = [...claimants.values()];
|
|
796
|
+
if (matched.length === 1) return matched[0];
|
|
797
|
+
if (matched.length > 1) {
|
|
798
|
+
const names = matched.map((c) => c.label).join(", ");
|
|
799
|
+
throw new Error(
|
|
800
|
+
`This repository is declared as a source root by ${matched.length} portfolio workspaces (${names}). Disambiguate in ~/.basou/portfolio.yaml so only one aggregates it.`
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
return void 0;
|
|
804
|
+
}
|
|
805
|
+
async function realpathOrNull(p) {
|
|
806
|
+
try {
|
|
807
|
+
return await realpath(p);
|
|
808
|
+
} catch {
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
671
811
|
}
|
|
672
812
|
|
|
673
813
|
// src/commands/decision.ts
|
|
@@ -742,7 +882,7 @@ async function runDecisionRecord(options, ctx = {}) {
|
|
|
742
882
|
async function doRunDecisionRecord(options, ctx) {
|
|
743
883
|
const cwd = ctx.cwd ?? process.cwd();
|
|
744
884
|
const repositoryRoot = await resolveRepositoryRootForDecision(cwd);
|
|
745
|
-
const paths =
|
|
885
|
+
const paths = basouPaths3(repositoryRoot);
|
|
746
886
|
await assertWorkspaceInitialized2(paths.root);
|
|
747
887
|
const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
|
|
748
888
|
const occurredAt = now.toISOString();
|
|
@@ -780,7 +920,7 @@ async function doRunDecisionRecord(options, ctx) {
|
|
|
780
920
|
});
|
|
781
921
|
return;
|
|
782
922
|
}
|
|
783
|
-
const manifest = await
|
|
923
|
+
const manifest = await readManifest2(paths);
|
|
784
924
|
const adHoc = await createAdHocSessionWithEvent({
|
|
785
925
|
paths,
|
|
786
926
|
manifest,
|
|
@@ -827,7 +967,7 @@ async function runDecisionCapture(options, ctx = {}) {
|
|
|
827
967
|
async function doRunDecisionCapture(options, ctx) {
|
|
828
968
|
const cwd = ctx.cwd ?? process.cwd();
|
|
829
969
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "decision capture");
|
|
830
|
-
const paths =
|
|
970
|
+
const paths = basouPaths3(repositoryRoot);
|
|
831
971
|
await assertWorkspaceInitialized2(paths.root);
|
|
832
972
|
const raw = await readCaptureInput(options, ctx);
|
|
833
973
|
const decisions = parseCaptureInput(raw);
|
|
@@ -838,12 +978,12 @@ async function doRunDecisionCapture(options, ctx) {
|
|
|
838
978
|
const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
|
|
839
979
|
const occurredAt = now.toISOString();
|
|
840
980
|
const decisionIds = decisions.map(() => prefixedUlid2("decision"));
|
|
841
|
-
const manifest = await
|
|
981
|
+
const manifest = await readManifest2(paths);
|
|
842
982
|
const invocationArgs = options.file !== void 0 ? [
|
|
843
983
|
"--file",
|
|
844
|
-
sanitizePath(
|
|
984
|
+
sanitizePath(resolve3(cwd, options.file), {
|
|
845
985
|
workingDirectory: repositoryRoot,
|
|
846
|
-
homedir:
|
|
986
|
+
homedir: homedir2()
|
|
847
987
|
})
|
|
848
988
|
] : [];
|
|
849
989
|
const adHoc = await createAdHocSessionWithEvent({
|
|
@@ -1201,7 +1341,7 @@ async function assertWorkspaceInitialized2(basouRoot) {
|
|
|
1201
1341
|
// src/commands/decisions.ts
|
|
1202
1342
|
import {
|
|
1203
1343
|
assertBasouRootSafe as assertBasouRootSafe3,
|
|
1204
|
-
basouPaths as
|
|
1344
|
+
basouPaths as basouPaths4,
|
|
1205
1345
|
findErrorCode as findErrorCode3,
|
|
1206
1346
|
readMarkdownFile,
|
|
1207
1347
|
renderDecisions,
|
|
@@ -1227,7 +1367,7 @@ async function doRunDecisionsGenerate(options, ctx) {
|
|
|
1227
1367
|
void options;
|
|
1228
1368
|
const cwd = ctx.cwd ?? process.cwd();
|
|
1229
1369
|
const repositoryRoot = await resolveRepositoryRootForDecisions(cwd);
|
|
1230
|
-
const paths =
|
|
1370
|
+
const paths = basouPaths4(repositoryRoot);
|
|
1231
1371
|
await assertWorkspaceInitialized3(paths.root);
|
|
1232
1372
|
const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
1233
1373
|
const result = await renderDecisions({
|
|
@@ -1267,12 +1407,12 @@ async function assertWorkspaceInitialized3(basouRoot) {
|
|
|
1267
1407
|
|
|
1268
1408
|
// src/commands/exec.ts
|
|
1269
1409
|
import { mkdir } from "fs/promises";
|
|
1270
|
-
import { homedir as
|
|
1271
|
-
import { join as
|
|
1410
|
+
import { homedir as homedir3 } from "os";
|
|
1411
|
+
import { join as join3 } from "path";
|
|
1272
1412
|
import {
|
|
1273
1413
|
acquireLock as acquireLock3,
|
|
1274
1414
|
assertBasouRootSafe as assertBasouRootSafe4,
|
|
1275
|
-
basouPaths as
|
|
1415
|
+
basouPaths as basouPaths5,
|
|
1276
1416
|
ChildProcessRunner,
|
|
1277
1417
|
appendChainedEvent as coreAppendChainedEvent,
|
|
1278
1418
|
finalizeSessionYaml,
|
|
@@ -1280,8 +1420,8 @@ import {
|
|
|
1280
1420
|
overwriteYamlFile,
|
|
1281
1421
|
parseDuration,
|
|
1282
1422
|
prefixedUlid as prefixedUlid3,
|
|
1283
|
-
readManifest as
|
|
1284
|
-
readYamlFile as
|
|
1423
|
+
readManifest as readManifest3,
|
|
1424
|
+
readYamlFile as readYamlFile3,
|
|
1285
1425
|
resolveRepositoryRoot as resolveRepositoryRoot4,
|
|
1286
1426
|
SessionSchema,
|
|
1287
1427
|
sanitizeWorkingDirectory,
|
|
@@ -1304,17 +1444,17 @@ async function runExec(command, args, options, ctx = {}) {
|
|
|
1304
1444
|
const cwd = options.cwd ?? process.cwd();
|
|
1305
1445
|
const timeout_ms = options.timeout !== void 0 ? parseDuration(options.timeout) : void 0;
|
|
1306
1446
|
const repoRoot = await resolveRepositoryRootForExec(cwd);
|
|
1307
|
-
const paths =
|
|
1447
|
+
const paths = basouPaths5(repoRoot);
|
|
1308
1448
|
await assertBasouRootSafe4(paths.root);
|
|
1309
|
-
const manifest = await
|
|
1449
|
+
const manifest = await readManifest3(paths);
|
|
1310
1450
|
const sessionId = prefixedUlid3("ses");
|
|
1311
|
-
const sessionDir =
|
|
1451
|
+
const sessionDir = join3(paths.sessions, sessionId);
|
|
1312
1452
|
await mkdir(sessionDir, { recursive: true });
|
|
1313
1453
|
const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
|
|
1314
1454
|
await coreAppendChainedEvent(paths, sessionId, event);
|
|
1315
1455
|
});
|
|
1316
1456
|
const startedAt = now().toISOString();
|
|
1317
|
-
const sessionYamlPath =
|
|
1457
|
+
const sessionYamlPath = join3(sessionDir, "session.yaml");
|
|
1318
1458
|
const session = buildInitialSession({
|
|
1319
1459
|
id: sessionId,
|
|
1320
1460
|
command,
|
|
@@ -1519,7 +1659,7 @@ function buildInitialSession(input) {
|
|
|
1519
1659
|
source: { kind: "terminal", version: "0.1.0" },
|
|
1520
1660
|
started_at: input.startedAt,
|
|
1521
1661
|
status: "initialized",
|
|
1522
|
-
working_directory: sanitizeWorkingDirectory(input.cwd, { homedir:
|
|
1662
|
+
working_directory: sanitizeWorkingDirectory(input.cwd, { homedir: homedir3() }),
|
|
1523
1663
|
invocation: {
|
|
1524
1664
|
command: input.command,
|
|
1525
1665
|
args: [...input.args],
|
|
@@ -1531,7 +1671,7 @@ function buildInitialSession(input) {
|
|
|
1531
1671
|
};
|
|
1532
1672
|
}
|
|
1533
1673
|
async function mutateSessionYaml(filePath, mutator) {
|
|
1534
|
-
const raw = await
|
|
1674
|
+
const raw = await readYamlFile3(filePath);
|
|
1535
1675
|
const parsed = SessionSchema.parse(raw);
|
|
1536
1676
|
mutator(parsed);
|
|
1537
1677
|
const validated = SessionSchema.parse(parsed);
|
|
@@ -1593,7 +1733,7 @@ async function resolveRepositoryRootForExec(cwd) {
|
|
|
1593
1733
|
// src/commands/handoff.ts
|
|
1594
1734
|
import {
|
|
1595
1735
|
assertBasouRootSafe as assertBasouRootSafe5,
|
|
1596
|
-
basouPaths as
|
|
1736
|
+
basouPaths as basouPaths6,
|
|
1597
1737
|
findErrorCode as findErrorCode4,
|
|
1598
1738
|
readMarkdownFile as readMarkdownFile2,
|
|
1599
1739
|
renderHandoff,
|
|
@@ -1619,7 +1759,7 @@ async function doRunHandoffGenerate(options, ctx) {
|
|
|
1619
1759
|
void options;
|
|
1620
1760
|
const cwd = ctx.cwd ?? process.cwd();
|
|
1621
1761
|
const repositoryRoot = await resolveRepositoryRootForHandoff(cwd);
|
|
1622
|
-
const paths =
|
|
1762
|
+
const paths = basouPaths6(repositoryRoot);
|
|
1623
1763
|
await assertWorkspaceInitialized4(paths.root);
|
|
1624
1764
|
const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
1625
1765
|
const result = await renderHandoff({
|
|
@@ -1662,13 +1802,13 @@ async function assertWorkspaceInitialized4(basouRoot) {
|
|
|
1662
1802
|
|
|
1663
1803
|
// src/commands/import.ts
|
|
1664
1804
|
import { createReadStream } from "fs";
|
|
1665
|
-
import { readdir, readFile as readFile2, rm, stat } from "fs/promises";
|
|
1666
|
-
import { homedir as
|
|
1667
|
-
import { basename, join as
|
|
1805
|
+
import { readdir, readFile as readFile2, rm, stat as stat2 } from "fs/promises";
|
|
1806
|
+
import { homedir as homedir4 } from "os";
|
|
1807
|
+
import { basename as basename2, join as join4, resolve as resolve4 } from "path";
|
|
1668
1808
|
import { createInterface } from "readline";
|
|
1669
1809
|
import {
|
|
1670
1810
|
assertBasouRootSafe as assertBasouRootSafe6,
|
|
1671
|
-
basouPaths as
|
|
1811
|
+
basouPaths as basouPaths7,
|
|
1672
1812
|
CLAUDE_IMPORT_SOURCE,
|
|
1673
1813
|
CODEX_IMPORT_SOURCE,
|
|
1674
1814
|
claudeTranscriptToImportPayload,
|
|
@@ -1676,7 +1816,7 @@ import {
|
|
|
1676
1816
|
enumerateSessionDirs,
|
|
1677
1817
|
findErrorCode as findErrorCode5,
|
|
1678
1818
|
importSessionFromJson,
|
|
1679
|
-
readManifest as
|
|
1819
|
+
readManifest as readManifest4,
|
|
1680
1820
|
readSessionYaml as readSessionYaml2,
|
|
1681
1821
|
reimportPreservingId,
|
|
1682
1822
|
resolveRepositoryRoot as resolveRepositoryRoot6,
|
|
@@ -1732,10 +1872,10 @@ function resolveSourceRoots(args) {
|
|
|
1732
1872
|
const { projectFlags, manifest, repoRoot, cwd } = args;
|
|
1733
1873
|
let resolved;
|
|
1734
1874
|
if (projectFlags.length > 0) {
|
|
1735
|
-
resolved = projectFlags.map((p) =>
|
|
1875
|
+
resolved = projectFlags.map((p) => resolve4(cwd, p));
|
|
1736
1876
|
} else {
|
|
1737
1877
|
const roots = manifest.import?.source_roots;
|
|
1738
|
-
resolved = roots !== void 0 && roots.length > 0 ? roots.map((r) =>
|
|
1878
|
+
resolved = roots !== void 0 && roots.length > 0 ? roots.map((r) => resolve4(repoRoot, r)) : [repoRoot];
|
|
1739
1879
|
}
|
|
1740
1880
|
return [...new Set(resolved)];
|
|
1741
1881
|
}
|
|
@@ -1748,11 +1888,11 @@ async function doRunImportClaudeCode(options, ctx) {
|
|
|
1748
1888
|
repoRoot: repositoryRoot,
|
|
1749
1889
|
cwd: ctx.cwd ?? process.cwd()
|
|
1750
1890
|
});
|
|
1751
|
-
const projectsRoot = ctx.claudeProjectsDir ??
|
|
1891
|
+
const projectsRoot = ctx.claudeProjectsDir ?? join4(homedir4(), ".claude", "projects");
|
|
1752
1892
|
const files = await selectTranscriptFiles(projectsRoot, projectPaths, options);
|
|
1753
1893
|
const projectSet = new Set(projectPaths);
|
|
1754
1894
|
const candidates = files.map((file) => {
|
|
1755
|
-
const externalId =
|
|
1895
|
+
const externalId = basename2(file, ".jsonl");
|
|
1756
1896
|
return {
|
|
1757
1897
|
externalId,
|
|
1758
1898
|
sourcePath: file,
|
|
@@ -1779,7 +1919,7 @@ async function doRunImportCodex(options, ctx) {
|
|
|
1779
1919
|
repoRoot: repositoryRoot,
|
|
1780
1920
|
cwd: ctx.cwd ?? process.cwd()
|
|
1781
1921
|
});
|
|
1782
|
-
const sessionsRoot = ctx.codexSessionsDir ??
|
|
1922
|
+
const sessionsRoot = ctx.codexSessionsDir ?? join4(homedir4(), ".codex", "sessions");
|
|
1783
1923
|
const rollouts = await discoverCodexRollouts(sessionsRoot, projectPaths, options);
|
|
1784
1924
|
const candidates = rollouts.map(({ file, externalId }) => ({
|
|
1785
1925
|
externalId,
|
|
@@ -1806,9 +1946,9 @@ function assertSelector(options) {
|
|
|
1806
1946
|
async function resolveImportTarget(ctx) {
|
|
1807
1947
|
const cwd = ctx.cwd ?? process.cwd();
|
|
1808
1948
|
const repositoryRoot = await resolveRepositoryRootForImport(cwd);
|
|
1809
|
-
const paths =
|
|
1949
|
+
const paths = basouPaths7(repositoryRoot);
|
|
1810
1950
|
await assertWorkspaceInitialized5(paths.root);
|
|
1811
|
-
const manifest = await
|
|
1951
|
+
const manifest = await readManifest4(paths);
|
|
1812
1952
|
return { repositoryRoot, paths, manifest };
|
|
1813
1953
|
}
|
|
1814
1954
|
async function importDerivedSessions(paths, manifest, options, sourceKind, candidates) {
|
|
@@ -1880,7 +2020,7 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
|
|
|
1880
2020
|
if (priors.length > 0 && options.force === true) {
|
|
1881
2021
|
if (options.dryRun !== true) {
|
|
1882
2022
|
for (const { sessionId } of priors) {
|
|
1883
|
-
await rm(
|
|
2023
|
+
await rm(join4(paths.sessions, sessionId), { recursive: true, force: true });
|
|
1884
2024
|
}
|
|
1885
2025
|
}
|
|
1886
2026
|
counts.replaced++;
|
|
@@ -1980,7 +2120,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
|
|
|
1980
2120
|
if (options.session !== void 0) {
|
|
1981
2121
|
const matches = [];
|
|
1982
2122
|
for (const projectPath of projectPaths) {
|
|
1983
|
-
const file =
|
|
2123
|
+
const file = join4(projectsRoot, encodeProjectDir(projectPath), `${options.session}.jsonl`);
|
|
1984
2124
|
if (await pathExists(file)) matches.push(file);
|
|
1985
2125
|
}
|
|
1986
2126
|
if (matches.length === 0) {
|
|
@@ -1991,7 +2131,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
|
|
|
1991
2131
|
const files = [];
|
|
1992
2132
|
let anyDirFound = false;
|
|
1993
2133
|
for (const projectPath of projectPaths) {
|
|
1994
|
-
const transcriptDir =
|
|
2134
|
+
const transcriptDir = join4(projectsRoot, encodeProjectDir(projectPath));
|
|
1995
2135
|
let entries;
|
|
1996
2136
|
try {
|
|
1997
2137
|
entries = await readdir(transcriptDir);
|
|
@@ -2001,7 +2141,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
|
|
|
2001
2141
|
}
|
|
2002
2142
|
anyDirFound = true;
|
|
2003
2143
|
for (const name of entries) {
|
|
2004
|
-
if (name.endsWith(".jsonl")) files.push(
|
|
2144
|
+
if (name.endsWith(".jsonl")) files.push(join4(transcriptDir, name));
|
|
2005
2145
|
}
|
|
2006
2146
|
}
|
|
2007
2147
|
if (!anyDirFound) {
|
|
@@ -2011,7 +2151,7 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
|
|
|
2011
2151
|
}
|
|
2012
2152
|
async function pathExists(file) {
|
|
2013
2153
|
try {
|
|
2014
|
-
await
|
|
2154
|
+
await stat2(file);
|
|
2015
2155
|
return true;
|
|
2016
2156
|
} catch (error) {
|
|
2017
2157
|
if (findErrorCode5(error, "ENOENT")) return false;
|
|
@@ -2020,7 +2160,7 @@ async function pathExists(file) {
|
|
|
2020
2160
|
}
|
|
2021
2161
|
async function statSize(file) {
|
|
2022
2162
|
try {
|
|
2023
|
-
return (await
|
|
2163
|
+
return (await stat2(file)).size;
|
|
2024
2164
|
} catch (error) {
|
|
2025
2165
|
if (findErrorCode5(error, "ENOENT")) return void 0;
|
|
2026
2166
|
throw error;
|
|
@@ -2058,7 +2198,7 @@ async function findRolloutFiles(sessionsRoot) {
|
|
|
2058
2198
|
throw new Error("Failed to read Codex sessions directory", { cause: error });
|
|
2059
2199
|
}
|
|
2060
2200
|
for (const entry of entries) {
|
|
2061
|
-
const full =
|
|
2201
|
+
const full = join4(dir, entry.name);
|
|
2062
2202
|
if (entry.isDirectory()) {
|
|
2063
2203
|
await walk(full, false);
|
|
2064
2204
|
} else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) {
|
|
@@ -2236,7 +2376,7 @@ async function assertWorkspaceInitialized5(basouRoot) {
|
|
|
2236
2376
|
}
|
|
2237
2377
|
|
|
2238
2378
|
// src/commands/init.ts
|
|
2239
|
-
import { basename as
|
|
2379
|
+
import { basename as basename3, relative, resolve as resolve5 } from "path";
|
|
2240
2380
|
import {
|
|
2241
2381
|
appendBasouGitignore,
|
|
2242
2382
|
createManifest,
|
|
@@ -2275,7 +2415,7 @@ async function runInit(options, ctx = {}) {
|
|
|
2275
2415
|
async function doRunInit(options, ctx) {
|
|
2276
2416
|
const cwd = ctx.cwd ?? process.cwd();
|
|
2277
2417
|
const repositoryRoot = await resolveRepositoryRootForInit(cwd);
|
|
2278
|
-
const workspaceName = options.name ??
|
|
2418
|
+
const workspaceName = options.name ?? basename3(repositoryRoot);
|
|
2279
2419
|
let repositoryUrl;
|
|
2280
2420
|
if (options.repoUrl !== void 0) {
|
|
2281
2421
|
repositoryUrl = options.repoUrl === "" ? null : options.repoUrl;
|
|
@@ -2283,7 +2423,7 @@ async function doRunInit(options, ctx) {
|
|
|
2283
2423
|
repositoryUrl = await tryRemoteUrl(repositoryRoot);
|
|
2284
2424
|
}
|
|
2285
2425
|
const sourceRoots = (options.sourceRoot ?? []).map((p) => {
|
|
2286
|
-
const rel = relative(repositoryRoot,
|
|
2426
|
+
const rel = relative(repositoryRoot, resolve5(cwd, p));
|
|
2287
2427
|
return rel === "" ? "." : rel;
|
|
2288
2428
|
});
|
|
2289
2429
|
const paths = await ensureBasouDirectory(repositoryRoot);
|
|
@@ -2330,10 +2470,10 @@ import {
|
|
|
2330
2470
|
acquireLock as acquireLock4,
|
|
2331
2471
|
appendEventToExistingSession as appendEventToExistingSession2,
|
|
2332
2472
|
assertBasouRootSafe as assertBasouRootSafe7,
|
|
2333
|
-
basouPaths as
|
|
2473
|
+
basouPaths as basouPaths8,
|
|
2334
2474
|
createAdHocSessionWithEvent as createAdHocSessionWithEvent2,
|
|
2335
2475
|
findErrorCode as findErrorCode6,
|
|
2336
|
-
readManifest as
|
|
2476
|
+
readManifest as readManifest5,
|
|
2337
2477
|
resolveSessionId as resolveSessionId2
|
|
2338
2478
|
} from "@basou/core";
|
|
2339
2479
|
import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
|
|
@@ -2364,7 +2504,7 @@ async function doRunNote(body, options, ctx) {
|
|
|
2364
2504
|
}
|
|
2365
2505
|
const cwd = ctx.cwd ?? process.cwd();
|
|
2366
2506
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "note");
|
|
2367
|
-
const paths =
|
|
2507
|
+
const paths = basouPaths8(repositoryRoot);
|
|
2368
2508
|
await assertWorkspaceInitialized6(paths.root);
|
|
2369
2509
|
const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
|
|
2370
2510
|
const occurredAt = now.toISOString();
|
|
@@ -2391,7 +2531,7 @@ async function doRunNote(body, options, ctx) {
|
|
|
2391
2531
|
});
|
|
2392
2532
|
return;
|
|
2393
2533
|
}
|
|
2394
|
-
const manifest = await
|
|
2534
|
+
const manifest = await readManifest5(paths);
|
|
2395
2535
|
const adHoc = await createAdHocSessionWithEvent2({
|
|
2396
2536
|
paths,
|
|
2397
2537
|
manifest,
|
|
@@ -2474,7 +2614,7 @@ async function assertWorkspaceInitialized6(basouRoot) {
|
|
|
2474
2614
|
// src/commands/orient.ts
|
|
2475
2615
|
import {
|
|
2476
2616
|
assertBasouRootSafe as assertBasouRootSafe8,
|
|
2477
|
-
basouPaths as
|
|
2617
|
+
basouPaths as basouPaths9,
|
|
2478
2618
|
findErrorCode as findErrorCode7,
|
|
2479
2619
|
renderOrientation as renderOrientation2,
|
|
2480
2620
|
writeMarkdownFile as writeMarkdownFile4
|
|
@@ -2679,7 +2819,7 @@ async function runOrient(options, ctx = {}) {
|
|
|
2679
2819
|
async function doRunOrient(options, ctx) {
|
|
2680
2820
|
const cwd = ctx.cwd ?? process.cwd();
|
|
2681
2821
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "orient");
|
|
2682
|
-
const paths =
|
|
2822
|
+
const paths = basouPaths9(repositoryRoot);
|
|
2683
2823
|
await assertWorkspaceInitialized7(paths.root);
|
|
2684
2824
|
const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
2685
2825
|
const probeCtx = { cwd: repositoryRoot };
|
|
@@ -2730,9 +2870,9 @@ import {
|
|
|
2730
2870
|
unlinkSync,
|
|
2731
2871
|
writeFileSync
|
|
2732
2872
|
} from "fs";
|
|
2733
|
-
import { basename as
|
|
2873
|
+
import { basename as basename4, dirname, isAbsolute as isAbsolute2, join as join5, relative as relative2, resolve as resolve6 } from "path";
|
|
2734
2874
|
import {
|
|
2735
|
-
basouPaths as
|
|
2875
|
+
basouPaths as basouPaths10,
|
|
2736
2876
|
GENERATED_END,
|
|
2737
2877
|
GENERATED_START,
|
|
2738
2878
|
isGitNotFound,
|
|
@@ -2743,7 +2883,7 @@ import {
|
|
|
2743
2883
|
planRename,
|
|
2744
2884
|
planRosterAdoption,
|
|
2745
2885
|
planWorkspaceView,
|
|
2746
|
-
readManifest as
|
|
2886
|
+
readManifest as readManifest6,
|
|
2747
2887
|
readMarkdownFile as readMarkdownFile4,
|
|
2748
2888
|
reconcileSourceRoots,
|
|
2749
2889
|
renderWithMarkers as renderWithMarkers4,
|
|
@@ -2850,8 +2990,8 @@ function preservedUnknownLines(fields) {
|
|
|
2850
2990
|
async function doRunProjectCheck(options, ctx) {
|
|
2851
2991
|
const cwd = ctx.cwd ?? process.cwd();
|
|
2852
2992
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "project check");
|
|
2853
|
-
const paths =
|
|
2854
|
-
const manifest = await
|
|
2993
|
+
const paths = basouPaths10(repositoryRoot);
|
|
2994
|
+
const manifest = await readManifest6(paths);
|
|
2855
2995
|
const summary = summarizeRosterDrift({
|
|
2856
2996
|
...manifest.repos !== void 0 ? { repos: manifest.repos } : {},
|
|
2857
2997
|
sourceRoots: effectiveSourceRoots(manifest)
|
|
@@ -2912,8 +3052,8 @@ async function runProjectSync(options, ctx = {}) {
|
|
|
2912
3052
|
async function doRunProjectSync(options, ctx) {
|
|
2913
3053
|
const cwd = ctx.cwd ?? process.cwd();
|
|
2914
3054
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "project sync");
|
|
2915
|
-
const paths =
|
|
2916
|
-
const manifest = await
|
|
3055
|
+
const paths = basouPaths10(repositoryRoot);
|
|
3056
|
+
const manifest = await readManifest6(paths);
|
|
2917
3057
|
const hasRoster = manifest.repos !== void 0 && manifest.repos.length > 0;
|
|
2918
3058
|
const reconcile = reconcileSourceRoots({
|
|
2919
3059
|
...manifest.repos !== void 0 ? { repos: manifest.repos } : {},
|
|
@@ -2982,20 +3122,20 @@ async function runProjectAdopt(options, ctx = {}) {
|
|
|
2982
3122
|
}
|
|
2983
3123
|
}
|
|
2984
3124
|
function classifySourceRoot(repositoryRoot, declaredPath) {
|
|
2985
|
-
const absolute =
|
|
3125
|
+
const absolute = resolve6(repositoryRoot, declaredPath);
|
|
2986
3126
|
let real;
|
|
2987
3127
|
try {
|
|
2988
3128
|
real = realpathSync(absolute);
|
|
2989
3129
|
} catch {
|
|
2990
3130
|
return { path: declaredPath, kind: "unresolved" };
|
|
2991
3131
|
}
|
|
2992
|
-
return { path: declaredPath, kind: existsSync(
|
|
3132
|
+
return { path: declaredPath, kind: existsSync(join5(real, ".git")) ? "repo" : "non-repo" };
|
|
2993
3133
|
}
|
|
2994
3134
|
async function doRunProjectAdopt(options, ctx) {
|
|
2995
3135
|
const cwd = ctx.cwd ?? process.cwd();
|
|
2996
3136
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "project adopt");
|
|
2997
|
-
const paths =
|
|
2998
|
-
const manifest = await
|
|
3137
|
+
const paths = basouPaths10(repositoryRoot);
|
|
3138
|
+
const manifest = await readManifest6(paths);
|
|
2999
3139
|
const alreadyDeclared = manifest.repos !== void 0 && manifest.repos.length > 0;
|
|
3000
3140
|
const candidates = effectiveSourceRoots(manifest).map(
|
|
3001
3141
|
(r) => classifySourceRoot(repositoryRoot, r)
|
|
@@ -3084,11 +3224,11 @@ async function gatherRepoWiring(repositoryRoot, entry) {
|
|
|
3084
3224
|
};
|
|
3085
3225
|
let real;
|
|
3086
3226
|
try {
|
|
3087
|
-
real = realpathSync(
|
|
3227
|
+
real = realpathSync(resolve6(repositoryRoot, entry.path));
|
|
3088
3228
|
} catch {
|
|
3089
3229
|
return { ...base, reachable: false, instructionFiles: [] };
|
|
3090
3230
|
}
|
|
3091
|
-
if (!existsSync(
|
|
3231
|
+
if (!existsSync(join5(real, ".git"))) {
|
|
3092
3232
|
return { ...base, reachable: false, instructionFiles: [] };
|
|
3093
3233
|
}
|
|
3094
3234
|
try {
|
|
@@ -3096,7 +3236,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
|
|
|
3096
3236
|
for (const name of INSTRUCTION_FILES) {
|
|
3097
3237
|
let present = true;
|
|
3098
3238
|
try {
|
|
3099
|
-
lstatSync(
|
|
3239
|
+
lstatSync(join5(real, name));
|
|
3100
3240
|
} catch {
|
|
3101
3241
|
present = false;
|
|
3102
3242
|
}
|
|
@@ -3111,8 +3251,8 @@ async function gatherRepoWiring(repositoryRoot, entry) {
|
|
|
3111
3251
|
async function doRunProjectWiring(options, ctx) {
|
|
3112
3252
|
const cwd = ctx.cwd ?? process.cwd();
|
|
3113
3253
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "project wiring");
|
|
3114
|
-
const paths =
|
|
3115
|
-
const manifest = await
|
|
3254
|
+
const paths = basouPaths10(repositoryRoot);
|
|
3255
|
+
const manifest = await readManifest6(paths);
|
|
3116
3256
|
const roster = manifest.repos ?? [];
|
|
3117
3257
|
const facts = [];
|
|
3118
3258
|
for (const entry of roster) facts.push(await gatherRepoWiring(repositoryRoot, entry));
|
|
@@ -3189,14 +3329,14 @@ function gatherRepoGitignore(repositoryRoot, entry) {
|
|
|
3189
3329
|
};
|
|
3190
3330
|
let real;
|
|
3191
3331
|
try {
|
|
3192
|
-
real = realpathSync(
|
|
3332
|
+
real = realpathSync(resolve6(repositoryRoot, entry.path));
|
|
3193
3333
|
} catch {
|
|
3194
3334
|
return { ...base, reachable: false, currentLines: [] };
|
|
3195
3335
|
}
|
|
3196
|
-
if (!existsSync(
|
|
3336
|
+
if (!existsSync(join5(real, ".git"))) {
|
|
3197
3337
|
return { ...base, reachable: false, currentLines: [] };
|
|
3198
3338
|
}
|
|
3199
|
-
return { ...base, reachable: true, currentLines: readGitignoreLines(
|
|
3339
|
+
return { ...base, reachable: true, currentLines: readGitignoreLines(join5(real, ".gitignore")) };
|
|
3200
3340
|
}
|
|
3201
3341
|
function hasErrorCode(error) {
|
|
3202
3342
|
return error instanceof Error && typeof error.code === "string";
|
|
@@ -3210,7 +3350,7 @@ function readGitignoreLines(file) {
|
|
|
3210
3350
|
}
|
|
3211
3351
|
}
|
|
3212
3352
|
function applyGitignorePlan(repositoryRoot, plan) {
|
|
3213
|
-
const file =
|
|
3353
|
+
const file = join5(realpathSync(resolve6(repositoryRoot, plan.path)), ".gitignore");
|
|
3214
3354
|
let existing = "";
|
|
3215
3355
|
try {
|
|
3216
3356
|
existing = readFileSync(file, "utf8");
|
|
@@ -3230,8 +3370,8 @@ function applyGitignorePlan(repositoryRoot, plan) {
|
|
|
3230
3370
|
async function doRunProjectGitignore(options, ctx) {
|
|
3231
3371
|
const cwd = ctx.cwd ?? process.cwd();
|
|
3232
3372
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "project gitignore");
|
|
3233
|
-
const paths =
|
|
3234
|
-
const manifest = await
|
|
3373
|
+
const paths = basouPaths10(repositoryRoot);
|
|
3374
|
+
const manifest = await readManifest6(paths);
|
|
3235
3375
|
const roster = manifest.repos ?? [];
|
|
3236
3376
|
const facts = roster.map((entry) => gatherRepoGitignore(repositoryRoot, entry));
|
|
3237
3377
|
const summary = planGitignore({ repos: facts, required: [...INSTRUCTION_FILES] });
|
|
@@ -3322,23 +3462,23 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
|
|
|
3322
3462
|
const base = { path: entry.path };
|
|
3323
3463
|
let real;
|
|
3324
3464
|
try {
|
|
3325
|
-
real = realpathSync(
|
|
3465
|
+
real = realpathSync(resolve6(repositoryRoot, entry.path));
|
|
3326
3466
|
} catch {
|
|
3327
3467
|
return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
|
|
3328
3468
|
}
|
|
3329
3469
|
if (real === anchorReal) {
|
|
3330
3470
|
return { ...base, isAnchor: true, reachable: true, canonicalPresent: false, files: [] };
|
|
3331
3471
|
}
|
|
3332
|
-
if (!existsSync(
|
|
3472
|
+
if (!existsSync(join5(real, ".git"))) {
|
|
3333
3473
|
return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
|
|
3334
3474
|
}
|
|
3335
|
-
const canonicalFile =
|
|
3475
|
+
const canonicalFile = join5(anchorReal, "agents", basename4(real), CANONICAL_FILE);
|
|
3336
3476
|
if (!existsSync(canonicalFile)) {
|
|
3337
3477
|
return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
|
|
3338
3478
|
}
|
|
3339
3479
|
const files = expectedSymlinkTargets(real, canonicalFile).map(
|
|
3340
3480
|
(spec) => {
|
|
3341
|
-
const { state, actualTarget } = inspectSymlink(
|
|
3481
|
+
const { state, actualTarget } = inspectSymlink(join5(real, spec.name), spec.target);
|
|
3342
3482
|
return {
|
|
3343
3483
|
name: spec.name,
|
|
3344
3484
|
expectedTarget: spec.target,
|
|
@@ -3352,14 +3492,14 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
|
|
|
3352
3492
|
isAnchor: false,
|
|
3353
3493
|
reachable: true,
|
|
3354
3494
|
canonicalPresent: true,
|
|
3355
|
-
canonicalName:
|
|
3495
|
+
canonicalName: basename4(real),
|
|
3356
3496
|
files
|
|
3357
3497
|
};
|
|
3358
3498
|
}
|
|
3359
3499
|
function applySymlinkPlan(repositoryRoot, plan) {
|
|
3360
3500
|
let real;
|
|
3361
3501
|
try {
|
|
3362
|
-
real = realpathSync(
|
|
3502
|
+
real = realpathSync(resolve6(repositoryRoot, plan.path));
|
|
3363
3503
|
} catch (error) {
|
|
3364
3504
|
const message = failureReason(error);
|
|
3365
3505
|
return { created: [], failed: plan.toCreate.map((c) => ({ file: c.name, message })) };
|
|
@@ -3367,7 +3507,7 @@ function applySymlinkPlan(repositoryRoot, plan) {
|
|
|
3367
3507
|
const created = [];
|
|
3368
3508
|
const failed = [];
|
|
3369
3509
|
for (const { name, target } of plan.toCreate) {
|
|
3370
|
-
const filePath =
|
|
3510
|
+
const filePath = join5(real, name);
|
|
3371
3511
|
try {
|
|
3372
3512
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
3373
3513
|
symlinkSync(target, filePath);
|
|
@@ -3384,8 +3524,8 @@ function failureReason(error) {
|
|
|
3384
3524
|
async function doRunProjectSymlinks(options, ctx) {
|
|
3385
3525
|
const cwd = ctx.cwd ?? process.cwd();
|
|
3386
3526
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "project symlinks");
|
|
3387
|
-
const paths =
|
|
3388
|
-
const manifest = await
|
|
3527
|
+
const paths = basouPaths10(repositoryRoot);
|
|
3528
|
+
const manifest = await readManifest6(paths);
|
|
3389
3529
|
const roster = manifest.repos ?? [];
|
|
3390
3530
|
const anchorReal = realpathSync(repositoryRoot);
|
|
3391
3531
|
const facts = roster.map((entry) => gatherRepoSymlinks(repositoryRoot, anchorReal, entry));
|
|
@@ -3504,12 +3644,12 @@ async function runProjectWorkspace(options, ctx = {}) {
|
|
|
3504
3644
|
}
|
|
3505
3645
|
}
|
|
3506
3646
|
function resolveViewDir(repositoryRoot, viewPath) {
|
|
3507
|
-
const abs =
|
|
3647
|
+
const abs = resolve6(repositoryRoot, viewPath);
|
|
3508
3648
|
try {
|
|
3509
3649
|
return realpathSync(abs);
|
|
3510
3650
|
} catch {
|
|
3511
3651
|
try {
|
|
3512
|
-
return
|
|
3652
|
+
return join5(realpathSync(dirname(abs)), basename4(abs));
|
|
3513
3653
|
} catch {
|
|
3514
3654
|
return abs;
|
|
3515
3655
|
}
|
|
@@ -3518,7 +3658,7 @@ function resolveViewDir(repositoryRoot, viewPath) {
|
|
|
3518
3658
|
function gatherViewRepo(repositoryRoot, viewDir, entry) {
|
|
3519
3659
|
let repoReal;
|
|
3520
3660
|
try {
|
|
3521
|
-
repoReal = realpathSync(
|
|
3661
|
+
repoReal = realpathSync(resolve6(repositoryRoot, entry.path));
|
|
3522
3662
|
} catch {
|
|
3523
3663
|
return { path: entry.path, reachable: false };
|
|
3524
3664
|
}
|
|
@@ -3526,8 +3666,8 @@ function gatherViewRepo(repositoryRoot, viewDir, entry) {
|
|
|
3526
3666
|
if (expectedTarget === "" || expectedTarget === ".") {
|
|
3527
3667
|
return { path: entry.path, reachable: false };
|
|
3528
3668
|
}
|
|
3529
|
-
const linkName =
|
|
3530
|
-
const { state, actualTarget } = inspectSymlink(
|
|
3669
|
+
const linkName = basename4(repoReal);
|
|
3670
|
+
const { state, actualTarget } = inspectSymlink(join5(viewDir, linkName), expectedTarget);
|
|
3531
3671
|
return {
|
|
3532
3672
|
path: entry.path,
|
|
3533
3673
|
reachable: true,
|
|
@@ -3541,7 +3681,7 @@ function applyViewPlan(viewDir, toCreate) {
|
|
|
3541
3681
|
const created = [];
|
|
3542
3682
|
const failed = [];
|
|
3543
3683
|
for (const { name, target } of toCreate) {
|
|
3544
|
-
const filePath =
|
|
3684
|
+
const filePath = join5(viewDir, name);
|
|
3545
3685
|
try {
|
|
3546
3686
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
3547
3687
|
symlinkSync(target, filePath);
|
|
@@ -3556,7 +3696,7 @@ var TOP_LEVEL_INSTRUCTION_FILES_LOWER = new Set(
|
|
|
3556
3696
|
INSTRUCTION_FILES.filter((f) => !f.includes("/")).map((f) => f.toLowerCase())
|
|
3557
3697
|
);
|
|
3558
3698
|
function classifyViewLink(viewDir, name, rosterRealpaths) {
|
|
3559
|
-
const filePath =
|
|
3699
|
+
const filePath = join5(viewDir, name);
|
|
3560
3700
|
let isLink;
|
|
3561
3701
|
try {
|
|
3562
3702
|
isLink = lstatSync(filePath).isSymbolicLink();
|
|
@@ -3570,12 +3710,12 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
|
|
|
3570
3710
|
} catch {
|
|
3571
3711
|
return null;
|
|
3572
3712
|
}
|
|
3573
|
-
const resolved =
|
|
3713
|
+
const resolved = isAbsolute2(target) ? target : resolve6(viewDir, target);
|
|
3574
3714
|
try {
|
|
3575
3715
|
if (rosterRealpaths.has(realpathSync(resolved))) return null;
|
|
3576
3716
|
} catch {
|
|
3577
3717
|
}
|
|
3578
|
-
if (
|
|
3718
|
+
if (isAbsolute2(target)) return { target, kind: "absolute" };
|
|
3579
3719
|
let isDir = false;
|
|
3580
3720
|
try {
|
|
3581
3721
|
isDir = statSync(resolved).isDirectory();
|
|
@@ -3585,7 +3725,7 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
|
|
|
3585
3725
|
if (!isDir) {
|
|
3586
3726
|
return { target, kind: existsSync(resolved) ? "non-repo" : "broken" };
|
|
3587
3727
|
}
|
|
3588
|
-
return { target, kind: existsSync(
|
|
3728
|
+
return { target, kind: existsSync(join5(resolved, ".git")) ? "repo" : "non-repo" };
|
|
3589
3729
|
}
|
|
3590
3730
|
function gatherExistingViewLinks(viewDir, rosterRealpaths) {
|
|
3591
3731
|
let names;
|
|
@@ -3610,7 +3750,7 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
|
|
|
3610
3750
|
const pruned = [];
|
|
3611
3751
|
const failed = [];
|
|
3612
3752
|
for (const { name } of toPrune) {
|
|
3613
|
-
const filePath =
|
|
3753
|
+
const filePath = join5(viewDir, name);
|
|
3614
3754
|
const c = classifyViewLink(viewDir, name, rosterRealpaths);
|
|
3615
3755
|
if (c === null || c.kind !== "repo") {
|
|
3616
3756
|
failed.push({
|
|
@@ -3631,8 +3771,8 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
|
|
|
3631
3771
|
async function doRunProjectWorkspace(options, ctx) {
|
|
3632
3772
|
const cwd = ctx.cwd ?? process.cwd();
|
|
3633
3773
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "project workspace");
|
|
3634
|
-
const paths =
|
|
3635
|
-
const manifest = await
|
|
3774
|
+
const paths = basouPaths10(repositoryRoot);
|
|
3775
|
+
const manifest = await readManifest6(paths);
|
|
3636
3776
|
const viewPath = manifest.workspace.view;
|
|
3637
3777
|
const roster = manifest.repos ?? [];
|
|
3638
3778
|
let result;
|
|
@@ -3656,11 +3796,11 @@ async function doRunProjectWorkspace(options, ctx) {
|
|
|
3656
3796
|
} else {
|
|
3657
3797
|
const viewDir = resolveViewDir(repositoryRoot, viewPath);
|
|
3658
3798
|
const facts = roster.map((entry) => gatherViewRepo(repositoryRoot, viewDir, entry));
|
|
3659
|
-
const rosterNames = roster.map((entry) =>
|
|
3799
|
+
const rosterNames = roster.map((entry) => basename4(resolve6(repositoryRoot, entry.path)));
|
|
3660
3800
|
const rosterRealpaths = /* @__PURE__ */ new Set();
|
|
3661
3801
|
for (const entry of roster) {
|
|
3662
3802
|
try {
|
|
3663
|
-
rosterRealpaths.add(realpathSync(
|
|
3803
|
+
rosterRealpaths.add(realpathSync(resolve6(repositoryRoot, entry.path)));
|
|
3664
3804
|
} catch {
|
|
3665
3805
|
}
|
|
3666
3806
|
}
|
|
@@ -3821,10 +3961,10 @@ async function runProjectPreset(options, ctx = {}) {
|
|
|
3821
3961
|
}
|
|
3822
3962
|
}
|
|
3823
3963
|
function canonicalFileFor(anchorReal, canonicalName) {
|
|
3824
|
-
return
|
|
3964
|
+
return join5(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
3825
3965
|
}
|
|
3826
3966
|
function canonicalLabelFor(canonicalName) {
|
|
3827
|
-
return
|
|
3967
|
+
return join5("agents", canonicalName, CANONICAL_FILE);
|
|
3828
3968
|
}
|
|
3829
3969
|
async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
|
|
3830
3970
|
const declared = {
|
|
@@ -3835,17 +3975,17 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
|
|
|
3835
3975
|
};
|
|
3836
3976
|
let real;
|
|
3837
3977
|
try {
|
|
3838
|
-
real = realpathSync(
|
|
3978
|
+
real = realpathSync(resolve6(repositoryRoot, entry.path));
|
|
3839
3979
|
} catch {
|
|
3840
3980
|
return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
|
|
3841
3981
|
}
|
|
3842
3982
|
if (real === anchorReal) {
|
|
3843
3983
|
return { ...declared, isAnchor: true, reachable: true, canonicalPresent: false };
|
|
3844
3984
|
}
|
|
3845
|
-
if (!existsSync(
|
|
3985
|
+
if (!existsSync(join5(real, ".git"))) {
|
|
3846
3986
|
return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
|
|
3847
3987
|
}
|
|
3848
|
-
const canonicalName =
|
|
3988
|
+
const canonicalName = basename4(real);
|
|
3849
3989
|
let content;
|
|
3850
3990
|
try {
|
|
3851
3991
|
content = await readMarkdownFile4(canonicalFileFor(anchorReal, canonicalName));
|
|
@@ -3906,8 +4046,8 @@ function presetFailureReason(error) {
|
|
|
3906
4046
|
async function doRunProjectPreset(options, ctx) {
|
|
3907
4047
|
const cwd = ctx.cwd ?? process.cwd();
|
|
3908
4048
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "project preset");
|
|
3909
|
-
const paths =
|
|
3910
|
-
const manifest = await
|
|
4049
|
+
const paths = basouPaths10(repositoryRoot);
|
|
4050
|
+
const manifest = await readManifest6(paths);
|
|
3911
4051
|
const roster = manifest.repos ?? [];
|
|
3912
4052
|
const anchorReal = realpathSync(repositoryRoot);
|
|
3913
4053
|
const facts = [];
|
|
@@ -4064,33 +4204,33 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
|
|
|
4064
4204
|
};
|
|
4065
4205
|
let real;
|
|
4066
4206
|
try {
|
|
4067
|
-
real = realpathSync(
|
|
4207
|
+
real = realpathSync(resolve6(repositoryRoot, target));
|
|
4068
4208
|
} catch {
|
|
4069
4209
|
return empty;
|
|
4070
4210
|
}
|
|
4071
4211
|
const anchorReal = realpathSync(repositoryRoot);
|
|
4072
|
-
const canonicalName =
|
|
4212
|
+
const canonicalName = basename4(real);
|
|
4073
4213
|
const instructionFiles = [];
|
|
4074
4214
|
for (const name of INSTRUCTION_FILES) {
|
|
4075
4215
|
try {
|
|
4076
|
-
lstatSync(
|
|
4216
|
+
lstatSync(join5(real, name));
|
|
4077
4217
|
instructionFiles.push(name);
|
|
4078
4218
|
} catch {
|
|
4079
4219
|
}
|
|
4080
4220
|
}
|
|
4081
4221
|
let ignored;
|
|
4082
4222
|
try {
|
|
4083
|
-
ignored = new Set(readGitignoreLines(
|
|
4223
|
+
ignored = new Set(readGitignoreLines(join5(real, ".gitignore")).map((l) => l.trim()));
|
|
4084
4224
|
} catch {
|
|
4085
4225
|
ignored = /* @__PURE__ */ new Set();
|
|
4086
4226
|
}
|
|
4087
4227
|
const gitignorePatterns = INSTRUCTION_FILES.filter((p) => ignored.has(p) || ignored.has(`/${p}`));
|
|
4088
|
-
const canonical2 = existsSync(
|
|
4228
|
+
const canonical2 = existsSync(join5(anchorReal, "agents", canonicalName, CANONICAL_FILE));
|
|
4089
4229
|
let viewLink = false;
|
|
4090
4230
|
const viewPath = manifest.workspace.view;
|
|
4091
4231
|
if (viewPath !== void 0) {
|
|
4092
4232
|
try {
|
|
4093
|
-
lstatSync(
|
|
4233
|
+
lstatSync(join5(resolveViewDir(repositoryRoot, viewPath), canonicalName));
|
|
4094
4234
|
viewLink = true;
|
|
4095
4235
|
} catch {
|
|
4096
4236
|
}
|
|
@@ -4127,12 +4267,12 @@ function buildArchivedManifest(manifest, plan, updatedAt) {
|
|
|
4127
4267
|
async function doRunProjectArchive(target, options, ctx) {
|
|
4128
4268
|
const cwd = ctx.cwd ?? process.cwd();
|
|
4129
4269
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "project archive");
|
|
4130
|
-
const paths =
|
|
4131
|
-
const manifest = await
|
|
4270
|
+
const paths = basouPaths10(repositoryRoot);
|
|
4271
|
+
const manifest = await readManifest6(paths);
|
|
4132
4272
|
const roster = manifest.repos ?? [];
|
|
4133
4273
|
let targetIsAnchor = false;
|
|
4134
4274
|
try {
|
|
4135
|
-
targetIsAnchor = realpathSync(
|
|
4275
|
+
targetIsAnchor = realpathSync(resolve6(repositoryRoot, target)) === realpathSync(repositoryRoot);
|
|
4136
4276
|
} catch {
|
|
4137
4277
|
targetIsAnchor = false;
|
|
4138
4278
|
}
|
|
@@ -4217,7 +4357,7 @@ function renderProjectArchive(result) {
|
|
|
4217
4357
|
if (t.instructionFiles.length > 0) items.push(`\u6307\u793A\u66F8(${t.instructionFiles.join(", ")})`);
|
|
4218
4358
|
if (t.gitignorePatterns.length > 0)
|
|
4219
4359
|
items.push(`.gitignore \u306E\u6307\u793A\u66F8\u30D1\u30BF\u30FC\u30F3(${t.gitignorePatterns.join(", ")})`);
|
|
4220
|
-
if (t.canonical) items.push(`anchor \u306E canonical(agents/${
|
|
4360
|
+
if (t.canonical) items.push(`anchor \u306E canonical(agents/${basename4(result.target)}/AGENTS.md)`);
|
|
4221
4361
|
if (!t.inspected) {
|
|
4222
4362
|
lines.push("## \u624B\u52D5 teardown(repo \u304C\u30C7\u30A3\u30B9\u30AF\u4E0A\u306B\u89E3\u6C7A\u3067\u304D\u306A\u3044\u305F\u3081\u672A\u691C\u67FB)");
|
|
4223
4363
|
lines.push(
|
|
@@ -4252,12 +4392,12 @@ function gatherRenameWiring(repositoryRoot, manifest, oldBasename) {
|
|
|
4252
4392
|
} catch {
|
|
4253
4393
|
return { canonicalDirOld: false, viewLinkOld: false };
|
|
4254
4394
|
}
|
|
4255
|
-
const canonicalDirOld = existsSync(
|
|
4395
|
+
const canonicalDirOld = existsSync(join5(anchorReal, "agents", oldBasename));
|
|
4256
4396
|
let viewLinkOld = false;
|
|
4257
4397
|
const viewPath = manifest.workspace.view;
|
|
4258
4398
|
if (viewPath !== void 0) {
|
|
4259
4399
|
try {
|
|
4260
|
-
lstatSync(
|
|
4400
|
+
lstatSync(join5(resolveViewDir(repositoryRoot, viewPath), oldBasename));
|
|
4261
4401
|
viewLinkOld = true;
|
|
4262
4402
|
} catch {
|
|
4263
4403
|
}
|
|
@@ -4278,12 +4418,12 @@ function buildRenamedManifest(manifest, plan, updatedAt) {
|
|
|
4278
4418
|
async function doRunProjectRename(oldPath, newPath, options, ctx) {
|
|
4279
4419
|
const cwd = ctx.cwd ?? process.cwd();
|
|
4280
4420
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "project rename");
|
|
4281
|
-
const paths =
|
|
4282
|
-
const manifest = await
|
|
4421
|
+
const paths = basouPaths10(repositoryRoot);
|
|
4422
|
+
const manifest = await readManifest6(paths);
|
|
4283
4423
|
const roster = manifest.repos ?? [];
|
|
4284
4424
|
let oldIsAnchor = false;
|
|
4285
4425
|
try {
|
|
4286
|
-
oldIsAnchor = realpathSync(
|
|
4426
|
+
oldIsAnchor = realpathSync(resolve6(repositoryRoot, oldPath)) === realpathSync(repositoryRoot);
|
|
4287
4427
|
} catch {
|
|
4288
4428
|
oldIsAnchor = false;
|
|
4289
4429
|
}
|
|
@@ -4393,79 +4533,341 @@ function renderProjectRename(result) {
|
|
|
4393
4533
|
return lines.join("\n");
|
|
4394
4534
|
}
|
|
4395
4535
|
|
|
4396
|
-
// src/commands/
|
|
4397
|
-
import {
|
|
4398
|
-
import {
|
|
4536
|
+
// src/commands/protocol.ts
|
|
4537
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
4538
|
+
import {
|
|
4539
|
+
PROTOCOL_END,
|
|
4540
|
+
PROTOCOL_START,
|
|
4541
|
+
parseMarkers as parseMarkers2,
|
|
4542
|
+
readMarkdownFile as readMarkdownFile5,
|
|
4543
|
+
removeMarkerSection
|
|
4544
|
+
} from "@basou/core";
|
|
4399
4545
|
|
|
4400
|
-
// src/lib/
|
|
4401
|
-
import {
|
|
4402
|
-
import {
|
|
4403
|
-
import {
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4546
|
+
// src/lib/durable-write.ts
|
|
4547
|
+
import { randomUUID } from "crypto";
|
|
4548
|
+
import { lstat, open, rename, stat as stat3, unlink as unlink2 } from "fs/promises";
|
|
4549
|
+
import { basename as basename5, dirname as dirname2, join as join6 } from "path";
|
|
4550
|
+
async function assertNotSymlink(targetPath) {
|
|
4551
|
+
try {
|
|
4552
|
+
const st = await lstat(targetPath);
|
|
4553
|
+
if (st.isSymbolicLink()) {
|
|
4554
|
+
throw new Error(
|
|
4555
|
+
"Refusing to write through a symlink. Replace the symlinked target with a regular file (or remove it) and retry."
|
|
4556
|
+
);
|
|
4557
|
+
}
|
|
4558
|
+
} catch (error) {
|
|
4559
|
+
if (error instanceof Error && error.code === "ENOENT") return;
|
|
4560
|
+
throw error;
|
|
4561
|
+
}
|
|
4562
|
+
}
|
|
4563
|
+
async function writeFileDurable(targetPath, content) {
|
|
4564
|
+
const dir = dirname2(targetPath);
|
|
4565
|
+
const tmpPath = join6(dir, `.${basename5(targetPath)}.tmp.${randomUUID()}`);
|
|
4566
|
+
let mode = 420;
|
|
4567
|
+
try {
|
|
4568
|
+
mode = (await stat3(targetPath)).mode & 511;
|
|
4569
|
+
} catch (error) {
|
|
4570
|
+
if (!(error instanceof Error && error.code === "ENOENT")) {
|
|
4571
|
+
throw error;
|
|
4572
|
+
}
|
|
4573
|
+
}
|
|
4574
|
+
let handle;
|
|
4575
|
+
try {
|
|
4576
|
+
handle = await open(tmpPath, "wx", mode);
|
|
4577
|
+
await handle.writeFile(content, "utf8");
|
|
4578
|
+
await handle.chmod(mode);
|
|
4579
|
+
await handle.sync();
|
|
4580
|
+
await handle.close();
|
|
4581
|
+
handle = void 0;
|
|
4582
|
+
await rename(tmpPath, targetPath);
|
|
4583
|
+
} catch (error) {
|
|
4584
|
+
if (handle) await handle.close().catch(() => void 0);
|
|
4585
|
+
await unlink2(tmpPath).catch(() => void 0);
|
|
4586
|
+
throw error;
|
|
4587
|
+
}
|
|
4588
|
+
try {
|
|
4589
|
+
const dirHandle = await open(dir, "r");
|
|
4590
|
+
try {
|
|
4591
|
+
await dirHandle.sync();
|
|
4592
|
+
} finally {
|
|
4593
|
+
await dirHandle.close();
|
|
4594
|
+
}
|
|
4595
|
+
} catch {
|
|
4596
|
+
}
|
|
4597
|
+
}
|
|
4598
|
+
|
|
4599
|
+
// src/lib/protocols-config.ts
|
|
4600
|
+
import { homedir as homedir5 } from "os";
|
|
4601
|
+
import { isAbsolute as isAbsolute3, join as join7, resolve as resolve7 } from "path";
|
|
4602
|
+
import { readYamlFile as readYamlFile4 } from "@basou/core";
|
|
4603
|
+
var DEFAULT_PROTOCOLS_CONFIG_PATH = join7(homedir5(), ".basou", "protocols.yaml");
|
|
4604
|
+
var DEFAULT_TARGET_PATH = join7(homedir5(), ".claude", "CLAUDE.md");
|
|
4605
|
+
var ALLOWED_TOP_KEYS = /* @__PURE__ */ new Set(["version", "protocols"]);
|
|
4606
|
+
var ALLOWED_ENTRY_KEYS = /* @__PURE__ */ new Set(["source", "title"]);
|
|
4607
|
+
function expandTilde2(p) {
|
|
4608
|
+
if (p === "~") return homedir5();
|
|
4609
|
+
if (p.startsWith("~/")) return join7(homedir5(), p.slice(2));
|
|
4408
4610
|
return p;
|
|
4409
4611
|
}
|
|
4410
|
-
function
|
|
4612
|
+
function isRecord2(value) {
|
|
4411
4613
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4412
4614
|
}
|
|
4413
|
-
async function
|
|
4615
|
+
async function loadProtocolsConfig(configPath = DEFAULT_PROTOCOLS_CONFIG_PATH) {
|
|
4414
4616
|
let raw;
|
|
4415
4617
|
try {
|
|
4416
|
-
raw = await
|
|
4618
|
+
raw = await readYamlFile4(configPath);
|
|
4417
4619
|
} catch (error) {
|
|
4418
4620
|
if (error instanceof Error && error.message === "YAML file not found") {
|
|
4419
4621
|
throw new Error(
|
|
4420
|
-
"No
|
|
4622
|
+
"No protocols config at ~/.basou/protocols.yaml. Create one (a 'protocols:' list of source markdown paths) before running 'basou protocol sync'."
|
|
4421
4623
|
);
|
|
4422
4624
|
}
|
|
4423
4625
|
if (error instanceof Error && error.message === "Failed to parse YAML content") {
|
|
4424
|
-
throw new Error("~/.basou/
|
|
4626
|
+
throw new Error("~/.basou/protocols.yaml is not valid YAML.");
|
|
4425
4627
|
}
|
|
4426
4628
|
throw error;
|
|
4427
4629
|
}
|
|
4428
|
-
if (!
|
|
4429
|
-
throw new Error("~/.basou/
|
|
4630
|
+
if (!isRecord2(raw) || !Array.isArray(raw.protocols)) {
|
|
4631
|
+
throw new Error("~/.basou/protocols.yaml must contain a 'protocols:' list.");
|
|
4632
|
+
}
|
|
4633
|
+
for (const key of Object.keys(raw)) {
|
|
4634
|
+
if (!ALLOWED_TOP_KEYS.has(key)) {
|
|
4635
|
+
throw new Error(
|
|
4636
|
+
`~/.basou/protocols.yaml has an unknown key '${key}' (allowed: version, protocols).`
|
|
4637
|
+
);
|
|
4638
|
+
}
|
|
4430
4639
|
}
|
|
4431
4640
|
const seen = /* @__PURE__ */ new Set();
|
|
4432
4641
|
const result = [];
|
|
4433
|
-
for (const entry of raw.
|
|
4434
|
-
if (!
|
|
4435
|
-
throw new Error("Each
|
|
4642
|
+
for (const entry of raw.protocols) {
|
|
4643
|
+
if (!isRecord2(entry)) {
|
|
4644
|
+
throw new Error("Each protocol entry must be a mapping with a 'source' key.");
|
|
4436
4645
|
}
|
|
4437
|
-
|
|
4438
|
-
|
|
4646
|
+
for (const key of Object.keys(entry)) {
|
|
4647
|
+
if (!ALLOWED_ENTRY_KEYS.has(key)) {
|
|
4648
|
+
throw new Error(`A protocol entry has an unknown key '${key}' (allowed: source, title).`);
|
|
4649
|
+
}
|
|
4439
4650
|
}
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
);
|
|
4651
|
+
if (typeof entry.source !== "string" || entry.source.trim().length === 0) {
|
|
4652
|
+
throw new Error("Each protocol entry needs a non-empty string 'source'.");
|
|
4653
|
+
}
|
|
4654
|
+
if (entry.title !== void 0 && (typeof entry.title !== "string" || entry.title.trim().length === 0)) {
|
|
4655
|
+
throw new Error("A protocol entry 'title' must be a non-empty string when present.");
|
|
4656
|
+
}
|
|
4657
|
+
const expanded = expandTilde2(entry.source.trim());
|
|
4658
|
+
if (!isAbsolute3(expanded)) {
|
|
4659
|
+
throw new Error("Protocol 'source' paths must be absolute (or start with '~').");
|
|
4660
|
+
}
|
|
4661
|
+
const abs = resolve7(expanded);
|
|
4662
|
+
if (seen.has(abs)) {
|
|
4663
|
+
throw new Error("Duplicate protocol source (each source path may appear only once).");
|
|
4445
4664
|
}
|
|
4446
|
-
const abs = resolve5(expanded);
|
|
4447
|
-
if (seen.has(abs)) continue;
|
|
4448
4665
|
seen.add(abs);
|
|
4449
|
-
result.push(
|
|
4666
|
+
result.push(
|
|
4667
|
+
entry.title !== void 0 ? { source: abs, title: entry.title.trim() } : { source: abs }
|
|
4668
|
+
);
|
|
4450
4669
|
}
|
|
4451
4670
|
if (result.length === 0) {
|
|
4452
|
-
throw new Error("~/.basou/
|
|
4671
|
+
throw new Error("~/.basou/protocols.yaml has no protocols.");
|
|
4453
4672
|
}
|
|
4454
4673
|
return result;
|
|
4455
4674
|
}
|
|
4456
4675
|
|
|
4676
|
+
// src/commands/protocol.ts
|
|
4677
|
+
var PROTOCOL_MARKERS = { start: PROTOCOL_START, end: PROTOCOL_END };
|
|
4678
|
+
var MANAGED_NOTE = "<!-- Managed by basou: 'basou protocol sync' regenerates everything between the BASOU:PROTOCOLS markers from ~/.basou/protocols.yaml. Manual edits inside the block are overwritten; edit the source files instead. -->";
|
|
4679
|
+
function registerProtocolCommand(program2) {
|
|
4680
|
+
const protocol = program2.command("protocol").description("Manage the basou-managed standing-protocol block in the global CLAUDE.md");
|
|
4681
|
+
protocol.command("sync").description("Render declared protocols into the global CLAUDE.md (creates/updates the block)").option("--config <path>", "Path to protocols.yaml (default ~/.basou/protocols.yaml)").option("--target <path>", "Override the target file (intended for tests)").option("--dry-run", "Print what would change without writing").option("-v, --verbose", "Show error causes").action(async (opts) => {
|
|
4682
|
+
await runProtocolSync(opts);
|
|
4683
|
+
});
|
|
4684
|
+
protocol.command("list").description("List declared protocols and whether the block is installed").option("--config <path>", "Path to protocols.yaml (default ~/.basou/protocols.yaml)").option("--target <path>", "Override the target file (intended for tests)").option("-v, --verbose", "Show error causes").action(async (opts) => {
|
|
4685
|
+
await runProtocolList(opts);
|
|
4686
|
+
});
|
|
4687
|
+
protocol.command("unsync").description("Remove the basou-managed protocol block from the global CLAUDE.md").option("--target <path>", "Override the target file (intended for tests)").option("--dry-run", "Print what would change without writing").option("-v, --verbose", "Show error causes").action(async (opts) => {
|
|
4688
|
+
await runProtocolUnsync(opts);
|
|
4689
|
+
});
|
|
4690
|
+
}
|
|
4691
|
+
async function runProtocolSync(options) {
|
|
4692
|
+
try {
|
|
4693
|
+
await doRunProtocolSync(options);
|
|
4694
|
+
} catch (error) {
|
|
4695
|
+
renderCliError(error, { verbose: isVerbose(options) });
|
|
4696
|
+
process.exitCode = 1;
|
|
4697
|
+
}
|
|
4698
|
+
}
|
|
4699
|
+
async function runProtocolList(options) {
|
|
4700
|
+
try {
|
|
4701
|
+
await doRunProtocolList(options);
|
|
4702
|
+
} catch (error) {
|
|
4703
|
+
renderCliError(error, { verbose: isVerbose(options) });
|
|
4704
|
+
process.exitCode = 1;
|
|
4705
|
+
}
|
|
4706
|
+
}
|
|
4707
|
+
async function runProtocolUnsync(options) {
|
|
4708
|
+
try {
|
|
4709
|
+
await doRunProtocolUnsync(options);
|
|
4710
|
+
} catch (error) {
|
|
4711
|
+
renderCliError(error, { verbose: isVerbose(options) });
|
|
4712
|
+
process.exitCode = 1;
|
|
4713
|
+
}
|
|
4714
|
+
}
|
|
4715
|
+
async function readProtocolSources(entries) {
|
|
4716
|
+
const out = [];
|
|
4717
|
+
for (const entry of entries) {
|
|
4718
|
+
let content;
|
|
4719
|
+
try {
|
|
4720
|
+
content = await readFile3(entry.source, "utf8");
|
|
4721
|
+
} catch (error) {
|
|
4722
|
+
if (error instanceof Error && error.code === "ENOENT") {
|
|
4723
|
+
throw new Error(
|
|
4724
|
+
"A protocol source file does not exist. Check the 'source' paths in ~/.basou/protocols.yaml.",
|
|
4725
|
+
{ cause: error }
|
|
4726
|
+
);
|
|
4727
|
+
}
|
|
4728
|
+
throw new Error("Failed to read a protocol source file.", { cause: error });
|
|
4729
|
+
}
|
|
4730
|
+
for (const line of content.split(/\r?\n/)) {
|
|
4731
|
+
if (line === PROTOCOL_START || line === PROTOCOL_END) {
|
|
4732
|
+
throw new Error(
|
|
4733
|
+
"A protocol source contains a BASOU:PROTOCOLS marker line, which would corrupt the managed block. Remove that line from the source."
|
|
4734
|
+
);
|
|
4735
|
+
}
|
|
4736
|
+
}
|
|
4737
|
+
out.push({ entry, content });
|
|
4738
|
+
}
|
|
4739
|
+
return out;
|
|
4740
|
+
}
|
|
4741
|
+
function buildBlock(sources) {
|
|
4742
|
+
const sections = sources.map(({ entry, content }) => {
|
|
4743
|
+
const body = content.replace(/\s+$/, "");
|
|
4744
|
+
return entry.title !== void 0 ? `## ${entry.title}
|
|
4745
|
+
|
|
4746
|
+
${body}` : body;
|
|
4747
|
+
});
|
|
4748
|
+
return `${MANAGED_NOTE}
|
|
4749
|
+
|
|
4750
|
+
${sections.join("\n\n")}
|
|
4751
|
+
`;
|
|
4752
|
+
}
|
|
4753
|
+
function buildTargetBody(existing, block) {
|
|
4754
|
+
const wrapped = `${PROTOCOL_START}
|
|
4755
|
+
${block}${PROTOCOL_END}
|
|
4756
|
+
`;
|
|
4757
|
+
if (existing === null || existing === "") return wrapped;
|
|
4758
|
+
const section = parseMarkers2(existing, PROTOCOL_MARKERS);
|
|
4759
|
+
switch (section.kind) {
|
|
4760
|
+
case "ok":
|
|
4761
|
+
return `${section.before}${PROTOCOL_START}
|
|
4762
|
+
${block}${PROTOCOL_END}${section.after}`;
|
|
4763
|
+
case "no_markers": {
|
|
4764
|
+
const sep = existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
4765
|
+
return `${existing}${sep}${wrapped}`;
|
|
4766
|
+
}
|
|
4767
|
+
default:
|
|
4768
|
+
throw new Error(
|
|
4769
|
+
"The BASOU:PROTOCOLS markers in the target are malformed (a marker is missing, duplicated, or out of order). Fix or remove them, then retry."
|
|
4770
|
+
);
|
|
4771
|
+
}
|
|
4772
|
+
}
|
|
4773
|
+
async function backupOnce(target, existing) {
|
|
4774
|
+
if (existing === null) return;
|
|
4775
|
+
const bak = `${target}.basou-bak`;
|
|
4776
|
+
const already = await readMarkdownFile5(bak);
|
|
4777
|
+
if (already !== null) return;
|
|
4778
|
+
await writeFileDurable(bak, existing);
|
|
4779
|
+
}
|
|
4780
|
+
async function doRunProtocolSync(options) {
|
|
4781
|
+
const configPath = options.config ?? DEFAULT_PROTOCOLS_CONFIG_PATH;
|
|
4782
|
+
const target = options.target ?? DEFAULT_TARGET_PATH;
|
|
4783
|
+
const entries = await loadProtocolsConfig(configPath);
|
|
4784
|
+
const sources = await readProtocolSources(entries);
|
|
4785
|
+
const block = buildBlock(sources);
|
|
4786
|
+
await assertNotSymlink(target);
|
|
4787
|
+
const existing = await readMarkdownFile5(target);
|
|
4788
|
+
const newBody = buildTargetBody(existing, block);
|
|
4789
|
+
if (newBody === existing) {
|
|
4790
|
+
console.log(`The basou:protocols block is already up to date (${entries.length} protocol(s)).`);
|
|
4791
|
+
return;
|
|
4792
|
+
}
|
|
4793
|
+
const hadBlock = existing !== null && parseMarkers2(existing, PROTOCOL_MARKERS).kind === "ok";
|
|
4794
|
+
if (options.dryRun === true) {
|
|
4795
|
+
console.log(
|
|
4796
|
+
`[dry-run] Would ${hadBlock ? "update" : "install"} the basou:protocols block (${entries.length} protocol(s)).`
|
|
4797
|
+
);
|
|
4798
|
+
for (const { entry } of sources) {
|
|
4799
|
+
console.log(` - ${entry.title ?? entry.source}`);
|
|
4800
|
+
}
|
|
4801
|
+
return;
|
|
4802
|
+
}
|
|
4803
|
+
const recheck = await readMarkdownFile5(target);
|
|
4804
|
+
if (recheck !== existing) {
|
|
4805
|
+
throw new Error(
|
|
4806
|
+
"The target changed during sync; aborting so a concurrent edit is not overwritten. Re-run 'basou protocol sync'."
|
|
4807
|
+
);
|
|
4808
|
+
}
|
|
4809
|
+
await backupOnce(target, existing);
|
|
4810
|
+
await writeFileDurable(target, newBody);
|
|
4811
|
+
console.log(
|
|
4812
|
+
`${hadBlock ? "Updated" : "Installed"} the basou:protocols block in the global CLAUDE.md (${entries.length} protocol(s)).`
|
|
4813
|
+
);
|
|
4814
|
+
}
|
|
4815
|
+
async function doRunProtocolList(options) {
|
|
4816
|
+
const configPath = options.config ?? DEFAULT_PROTOCOLS_CONFIG_PATH;
|
|
4817
|
+
const target = options.target ?? DEFAULT_TARGET_PATH;
|
|
4818
|
+
const entries = await loadProtocolsConfig(configPath);
|
|
4819
|
+
const existing = await readMarkdownFile5(target);
|
|
4820
|
+
const installed = existing !== null && parseMarkers2(existing, PROTOCOL_MARKERS).kind === "ok";
|
|
4821
|
+
console.log(`Declared protocols (${entries.length}):`);
|
|
4822
|
+
for (const entry of entries) {
|
|
4823
|
+
console.log(` - ${entry.title ?? entry.source}`);
|
|
4824
|
+
}
|
|
4825
|
+
console.log(installed ? "Block: installed in the global CLAUDE.md." : "Block: not installed.");
|
|
4826
|
+
}
|
|
4827
|
+
async function doRunProtocolUnsync(options) {
|
|
4828
|
+
const target = options.target ?? DEFAULT_TARGET_PATH;
|
|
4829
|
+
await assertNotSymlink(target);
|
|
4830
|
+
const existing = await readMarkdownFile5(target);
|
|
4831
|
+
if (existing === null) {
|
|
4832
|
+
console.log("No target file; nothing to remove.");
|
|
4833
|
+
return;
|
|
4834
|
+
}
|
|
4835
|
+
const newBody = removeMarkerSection(existing, "CLAUDE.md", PROTOCOL_MARKERS);
|
|
4836
|
+
if (newBody === existing) {
|
|
4837
|
+
console.log("No basou:protocols block found; nothing removed.");
|
|
4838
|
+
return;
|
|
4839
|
+
}
|
|
4840
|
+
if (options.dryRun === true) {
|
|
4841
|
+
console.log("[dry-run] Would remove the basou:protocols block from the global CLAUDE.md.");
|
|
4842
|
+
return;
|
|
4843
|
+
}
|
|
4844
|
+
const recheck = await readMarkdownFile5(target);
|
|
4845
|
+
if (recheck !== existing) {
|
|
4846
|
+
throw new Error(
|
|
4847
|
+
"The target changed during unsync; aborting so a concurrent edit is not overwritten. Re-run 'basou protocol unsync'."
|
|
4848
|
+
);
|
|
4849
|
+
}
|
|
4850
|
+
await backupOnce(target, existing);
|
|
4851
|
+
await writeFileDurable(target, newBody);
|
|
4852
|
+
console.log("Removed the basou:protocols block from the global CLAUDE.md.");
|
|
4853
|
+
}
|
|
4854
|
+
|
|
4855
|
+
// src/commands/refresh.ts
|
|
4856
|
+
import { assertBasouRootSafe as assertBasouRootSafe9, basouPaths as basouPaths11, findErrorCode as findErrorCode9 } from "@basou/core";
|
|
4857
|
+
import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
|
|
4858
|
+
|
|
4457
4859
|
// src/commands/refresh-watch.ts
|
|
4458
|
-
import { readdir as readdir2, stat as
|
|
4459
|
-
import { homedir as
|
|
4460
|
-
import { join as
|
|
4860
|
+
import { readdir as readdir2, stat as stat4 } from "fs/promises";
|
|
4861
|
+
import { homedir as homedir6 } from "os";
|
|
4862
|
+
import { join as join8 } from "path";
|
|
4461
4863
|
import { findErrorCode as findErrorCode8 } from "@basou/core";
|
|
4462
4864
|
var DEFAULT_WATCH_INTERVAL_SEC = 30;
|
|
4463
4865
|
var MIN_WATCH_INTERVAL_SEC = 5;
|
|
4464
4866
|
var MAX_WATCH_INTERVAL_SEC = 86400;
|
|
4465
4867
|
function watchedRoots(ctx) {
|
|
4466
4868
|
return [
|
|
4467
|
-
ctx.codexSessionsDir ??
|
|
4468
|
-
ctx.claudeProjectsDir ??
|
|
4869
|
+
ctx.codexSessionsDir ?? join8(homedir6(), ".codex", "sessions"),
|
|
4870
|
+
ctx.claudeProjectsDir ?? join8(homedir6(), ".claude", "projects")
|
|
4469
4871
|
];
|
|
4470
4872
|
}
|
|
4471
4873
|
async function scanSourceLogs(roots) {
|
|
@@ -4479,12 +4881,12 @@ async function scanSourceLogs(roots) {
|
|
|
4479
4881
|
throw new Error("Failed to read a source log directory", { cause: error });
|
|
4480
4882
|
}
|
|
4481
4883
|
for (const entry of entries) {
|
|
4482
|
-
const full =
|
|
4884
|
+
const full = join8(dir, entry.name);
|
|
4483
4885
|
if (entry.isDirectory()) {
|
|
4484
4886
|
await walk(full);
|
|
4485
4887
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
4486
4888
|
try {
|
|
4487
|
-
const info = await
|
|
4889
|
+
const info = await stat4(full);
|
|
4488
4890
|
out.set(full, { mtimeMs: info.mtimeMs, size: info.size });
|
|
4489
4891
|
} catch (error) {
|
|
4490
4892
|
if (findErrorCode8(error, "ENOENT")) continue;
|
|
@@ -4586,19 +4988,19 @@ function parseInterval(value) {
|
|
|
4586
4988
|
return seconds;
|
|
4587
4989
|
}
|
|
4588
4990
|
function abortableSleep(ms, signal) {
|
|
4589
|
-
return new Promise((
|
|
4991
|
+
return new Promise((resolve11) => {
|
|
4590
4992
|
if (signal.aborted) {
|
|
4591
|
-
|
|
4993
|
+
resolve11();
|
|
4592
4994
|
return;
|
|
4593
4995
|
}
|
|
4594
4996
|
let timer;
|
|
4595
4997
|
const onAbort = () => {
|
|
4596
4998
|
clearTimeout(timer);
|
|
4597
|
-
|
|
4999
|
+
resolve11();
|
|
4598
5000
|
};
|
|
4599
5001
|
timer = setTimeout(() => {
|
|
4600
5002
|
signal.removeEventListener("abort", onAbort);
|
|
4601
|
-
|
|
5003
|
+
resolve11();
|
|
4602
5004
|
}, ms);
|
|
4603
5005
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
4604
5006
|
});
|
|
@@ -4689,7 +5091,7 @@ async function doRunRefreshWatch(options, ctx) {
|
|
|
4689
5091
|
if (options.force === true) throw new Error("--watch cannot be combined with --force.");
|
|
4690
5092
|
const cwd = ctx.cwd ?? process.cwd();
|
|
4691
5093
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "refresh");
|
|
4692
|
-
const paths =
|
|
5094
|
+
const paths = basouPaths11(repositoryRoot);
|
|
4693
5095
|
await assertWorkspaceInitialized8(paths.root);
|
|
4694
5096
|
const intervalMs = (options.interval ?? DEFAULT_WATCH_INTERVAL_SEC) * 1e3;
|
|
4695
5097
|
const controller = new AbortController();
|
|
@@ -4717,7 +5119,7 @@ async function doRunRefreshWatch(options, ctx) {
|
|
|
4717
5119
|
async function computeRefresh(options, ctx) {
|
|
4718
5120
|
const cwd = ctx.cwd ?? process.cwd();
|
|
4719
5121
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "refresh");
|
|
4720
|
-
const paths =
|
|
5122
|
+
const paths = basouPaths11(repositoryRoot);
|
|
4721
5123
|
await assertWorkspaceInitialized8(paths.root);
|
|
4722
5124
|
const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
4723
5125
|
return refreshAll({
|
|
@@ -4797,10 +5199,10 @@ async function assertWorkspaceInitialized8(basouRoot) {
|
|
|
4797
5199
|
}
|
|
4798
5200
|
|
|
4799
5201
|
// src/commands/report.ts
|
|
4800
|
-
import { isAbsolute as
|
|
5202
|
+
import { isAbsolute as isAbsolute4, resolve as resolve8 } from "path";
|
|
4801
5203
|
import {
|
|
4802
5204
|
assertBasouRootSafe as assertBasouRootSafe10,
|
|
4803
|
-
basouPaths as
|
|
5205
|
+
basouPaths as basouPaths12,
|
|
4804
5206
|
findErrorCode as findErrorCode10,
|
|
4805
5207
|
renderReport,
|
|
4806
5208
|
resolveRepositoryRoot as resolveRepositoryRoot8,
|
|
@@ -4825,7 +5227,7 @@ async function runReportGenerate(options, ctx = {}) {
|
|
|
4825
5227
|
async function doRunReportGenerate(options, ctx) {
|
|
4826
5228
|
const cwd = ctx.cwd ?? process.cwd();
|
|
4827
5229
|
const repositoryRoot = await resolveRepositoryRootForReport(cwd);
|
|
4828
|
-
const paths =
|
|
5230
|
+
const paths = basouPaths12(repositoryRoot);
|
|
4829
5231
|
await assertWorkspaceInitialized9(paths.root);
|
|
4830
5232
|
const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
4831
5233
|
const result = await renderReport({
|
|
@@ -4837,7 +5239,7 @@ async function doRunReportGenerate(options, ctx) {
|
|
|
4837
5239
|
onTaskSkip: (taskId, reason) => printTaskSkip(taskId, reason)
|
|
4838
5240
|
});
|
|
4839
5241
|
if (options.out !== void 0) {
|
|
4840
|
-
const outPath =
|
|
5242
|
+
const outPath = isAbsolute4(options.out) ? options.out : resolve8(cwd, options.out);
|
|
4841
5243
|
await writeMarkdownFile6(outPath, result.body);
|
|
4842
5244
|
const { sessions, decisions, tasks } = result.data;
|
|
4843
5245
|
console.error(
|
|
@@ -4876,7 +5278,7 @@ async function assertWorkspaceInitialized9(basouRoot) {
|
|
|
4876
5278
|
|
|
4877
5279
|
// src/commands/review-gaps.ts
|
|
4878
5280
|
import {
|
|
4879
|
-
basouPaths as
|
|
5281
|
+
basouPaths as basouPaths13,
|
|
4880
5282
|
findReviewGaps
|
|
4881
5283
|
} from "@basou/core";
|
|
4882
5284
|
import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
|
|
@@ -4917,7 +5319,7 @@ async function runReviewGaps(options, ctx = {}) {
|
|
|
4917
5319
|
async function doRunReviewGaps(options, ctx) {
|
|
4918
5320
|
const cwd = ctx.cwd ?? process.cwd();
|
|
4919
5321
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "review-gaps");
|
|
4920
|
-
const paths =
|
|
5322
|
+
const paths = basouPaths13(repositoryRoot);
|
|
4921
5323
|
const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
4922
5324
|
const summary = await findReviewGaps({
|
|
4923
5325
|
paths,
|
|
@@ -5000,12 +5402,12 @@ function renderReviewGaps(summary) {
|
|
|
5000
5402
|
|
|
5001
5403
|
// src/commands/run.ts
|
|
5002
5404
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
5003
|
-
import { homedir as
|
|
5004
|
-
import { join as
|
|
5405
|
+
import { homedir as homedir7 } from "os";
|
|
5406
|
+
import { join as join9 } from "path";
|
|
5005
5407
|
import {
|
|
5006
5408
|
acquireLock as acquireLock5,
|
|
5007
5409
|
assertBasouRootSafe as assertBasouRootSafe11,
|
|
5008
|
-
basouPaths as
|
|
5410
|
+
basouPaths as basouPaths14,
|
|
5009
5411
|
ChildProcessRunner as ChildProcessRunner2,
|
|
5010
5412
|
claudeCodeAdapterMetadata,
|
|
5011
5413
|
appendChainedEvent as coreAppendChainedEvent2,
|
|
@@ -5014,8 +5416,8 @@ import {
|
|
|
5014
5416
|
getSnapshot as getSnapshot2,
|
|
5015
5417
|
overwriteYamlFile as overwriteYamlFile2,
|
|
5016
5418
|
prefixedUlid as prefixedUlid4,
|
|
5017
|
-
readManifest as
|
|
5018
|
-
readYamlFile as
|
|
5419
|
+
readManifest as readManifest7,
|
|
5420
|
+
readYamlFile as readYamlFile5,
|
|
5019
5421
|
resolveClaudeCodeCommand,
|
|
5020
5422
|
resolveRepositoryRoot as resolveRepositoryRoot9,
|
|
5021
5423
|
SessionSchema as SessionSchema2,
|
|
@@ -5050,17 +5452,17 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
5050
5452
|
const { command } = await resolveCommand();
|
|
5051
5453
|
const cwd = options.cwd ?? process.cwd();
|
|
5052
5454
|
const repoRoot = await resolveRepositoryRootForRun(cwd);
|
|
5053
|
-
const paths =
|
|
5455
|
+
const paths = basouPaths14(repoRoot);
|
|
5054
5456
|
await assertBasouRootSafe11(paths.root);
|
|
5055
|
-
const manifest = await
|
|
5457
|
+
const manifest = await readManifest7(paths);
|
|
5056
5458
|
const sessionId = prefixedUlid4("ses");
|
|
5057
|
-
const sessionDir =
|
|
5459
|
+
const sessionDir = join9(paths.sessions, sessionId);
|
|
5058
5460
|
await mkdir2(sessionDir, { recursive: true });
|
|
5059
5461
|
const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
|
|
5060
5462
|
await coreAppendChainedEvent2(paths, sessionId, event);
|
|
5061
5463
|
});
|
|
5062
5464
|
const startedAt = now().toISOString();
|
|
5063
|
-
const sessionYamlPath =
|
|
5465
|
+
const sessionYamlPath = join9(sessionDir, "session.yaml");
|
|
5064
5466
|
const session = buildInitialSession2({
|
|
5065
5467
|
id: sessionId,
|
|
5066
5468
|
command,
|
|
@@ -5186,7 +5588,7 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
5186
5588
|
const rawRelated = computeRelatedFiles(preSnapshot, postSnapshot, diff);
|
|
5187
5589
|
const relatedFiles = sanitizeRelatedFiles(rawRelated, {
|
|
5188
5590
|
workingDirectory: repoRoot,
|
|
5189
|
-
homedir:
|
|
5591
|
+
homedir: homedir7()
|
|
5190
5592
|
}).sanitized;
|
|
5191
5593
|
const finalStatus = decideFinalStatus2(result, signalReceived);
|
|
5192
5594
|
await appendEvent(sessionDir, {
|
|
@@ -5330,7 +5732,7 @@ function buildInitialSession2(input) {
|
|
|
5330
5732
|
source: { ...claudeCodeAdapterMetadata },
|
|
5331
5733
|
started_at: input.startedAt,
|
|
5332
5734
|
status: "initialized",
|
|
5333
|
-
working_directory: sanitizeWorkingDirectory2(input.cwd, { homedir:
|
|
5735
|
+
working_directory: sanitizeWorkingDirectory2(input.cwd, { homedir: homedir7() }),
|
|
5334
5736
|
invocation: {
|
|
5335
5737
|
command: input.command,
|
|
5336
5738
|
args: [...input.args],
|
|
@@ -5342,7 +5744,7 @@ function buildInitialSession2(input) {
|
|
|
5342
5744
|
};
|
|
5343
5745
|
}
|
|
5344
5746
|
async function mutateSessionYaml2(filePath, mutator) {
|
|
5345
|
-
const raw = await
|
|
5747
|
+
const raw = await readYamlFile5(filePath);
|
|
5346
5748
|
const parsed = SessionSchema2.parse(raw);
|
|
5347
5749
|
mutator(parsed);
|
|
5348
5750
|
const validated = SessionSchema2.parse(parsed);
|
|
@@ -5402,20 +5804,20 @@ async function resolveRepositoryRootForRun(cwd) {
|
|
|
5402
5804
|
}
|
|
5403
5805
|
|
|
5404
5806
|
// src/commands/session.ts
|
|
5405
|
-
import { readFile as
|
|
5406
|
-
import { basename as
|
|
5807
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
5808
|
+
import { basename as basename6, isAbsolute as isAbsolute5, join as join10, relative as relative3 } from "path";
|
|
5407
5809
|
import {
|
|
5408
5810
|
acquireLock as acquireLock6,
|
|
5409
5811
|
appendEventToExistingSession as appendEventToExistingSession3,
|
|
5410
5812
|
assertBasouRootSafe as assertBasouRootSafe12,
|
|
5411
|
-
basouPaths as
|
|
5813
|
+
basouPaths as basouPaths15,
|
|
5412
5814
|
enumerateSessionDirs as enumerateSessionDirs2,
|
|
5413
5815
|
findErrorCode as findErrorCode11,
|
|
5414
5816
|
importSessionFromJson as importSessionFromJson2,
|
|
5415
5817
|
loadSessionEntries,
|
|
5416
5818
|
readAllEvents,
|
|
5417
|
-
readManifest as
|
|
5418
|
-
readYamlFile as
|
|
5819
|
+
readManifest as readManifest8,
|
|
5820
|
+
readYamlFile as readYamlFile6,
|
|
5419
5821
|
rechainSessionInPlace,
|
|
5420
5822
|
resolveSessionId as resolveSessionId3,
|
|
5421
5823
|
resolveTaskId,
|
|
@@ -5473,7 +5875,7 @@ async function runSessionList(options, ctx = {}) {
|
|
|
5473
5875
|
async function doRunSessionList(options, ctx) {
|
|
5474
5876
|
const cwd = ctx.cwd ?? process.cwd();
|
|
5475
5877
|
const repositoryRoot = await resolveRepositoryRootForSession(cwd, "list");
|
|
5476
|
-
const paths =
|
|
5878
|
+
const paths = basouPaths15(repositoryRoot);
|
|
5477
5879
|
await assertWorkspaceInitialized10(paths.root);
|
|
5478
5880
|
const now = /* @__PURE__ */ new Date();
|
|
5479
5881
|
const records = (await loadSessionEntries(paths, {
|
|
@@ -5525,14 +5927,14 @@ async function runSessionShow(idInput, options, ctx = {}) {
|
|
|
5525
5927
|
async function doRunSessionShow(idInput, options, ctx) {
|
|
5526
5928
|
const cwd = ctx.cwd ?? process.cwd();
|
|
5527
5929
|
const repositoryRoot = await resolveRepositoryRootForSession(cwd, "show");
|
|
5528
|
-
const paths =
|
|
5930
|
+
const paths = basouPaths15(repositoryRoot);
|
|
5529
5931
|
await assertWorkspaceInitialized10(paths.root);
|
|
5530
5932
|
const sessionId = await resolveSessionId3(paths, idInput);
|
|
5531
|
-
const sessionDir =
|
|
5532
|
-
const sessionYamlPath =
|
|
5933
|
+
const sessionDir = join10(paths.sessions, sessionId);
|
|
5934
|
+
const sessionYamlPath = join10(sessionDir, "session.yaml");
|
|
5533
5935
|
let session;
|
|
5534
5936
|
try {
|
|
5535
|
-
const raw = await
|
|
5937
|
+
const raw = await readYamlFile6(sessionYamlPath);
|
|
5536
5938
|
session = SessionSchema3.parse(raw);
|
|
5537
5939
|
} catch (error) {
|
|
5538
5940
|
if (findErrorCode11(error, "ENOENT")) {
|
|
@@ -5650,7 +6052,7 @@ function formatSessionWork(session, events, now) {
|
|
|
5650
6052
|
}
|
|
5651
6053
|
function formatWorkingDir(workingDir, repositoryRoot, options) {
|
|
5652
6054
|
if (options.fullPath === true) return workingDir;
|
|
5653
|
-
if (!
|
|
6055
|
+
if (!isAbsolute5(workingDir)) {
|
|
5654
6056
|
if (workingDir === ".") return "<repository_root>";
|
|
5655
6057
|
return workingDir;
|
|
5656
6058
|
}
|
|
@@ -5813,9 +6215,9 @@ async function runSessionImport(options, ctx = {}) {
|
|
|
5813
6215
|
async function doRunSessionImport(options, ctx) {
|
|
5814
6216
|
const cwd = ctx.cwd ?? process.cwd();
|
|
5815
6217
|
const repositoryRoot = await resolveRepositoryRootForSession(cwd, "import");
|
|
5816
|
-
const paths =
|
|
6218
|
+
const paths = basouPaths15(repositoryRoot);
|
|
5817
6219
|
await assertWorkspaceInitialized10(paths.root);
|
|
5818
|
-
const manifest = await
|
|
6220
|
+
const manifest = await readManifest8(paths);
|
|
5819
6221
|
const rawBody = await readInputFile(options.from);
|
|
5820
6222
|
const json = parseJsonStrict(rawBody);
|
|
5821
6223
|
const parsed = SessionImportPayloadSchema2.safeParse(json);
|
|
@@ -5842,7 +6244,7 @@ async function doRunSessionImport(options, ctx) {
|
|
|
5842
6244
|
}
|
|
5843
6245
|
async function readInputFile(path) {
|
|
5844
6246
|
try {
|
|
5845
|
-
return await
|
|
6247
|
+
return await readFile4(path, "utf8");
|
|
5846
6248
|
} catch (error) {
|
|
5847
6249
|
if (findErrorCode11(error, "ENOENT")) {
|
|
5848
6250
|
throw new Error("Import source not found", { cause: error });
|
|
@@ -5900,7 +6302,7 @@ function printSessionImportResult(options, result) {
|
|
|
5900
6302
|
return;
|
|
5901
6303
|
}
|
|
5902
6304
|
console.log(
|
|
5903
|
-
`Imported session ${sid} (${result.eventCount} events) from ${
|
|
6305
|
+
`Imported session ${sid} (${result.eventCount} events) from ${basename6(options.from)}`
|
|
5904
6306
|
);
|
|
5905
6307
|
}
|
|
5906
6308
|
var NOTE_BODY_PREVIEW_LIMIT = 80;
|
|
@@ -5927,7 +6329,7 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
|
|
|
5927
6329
|
}
|
|
5928
6330
|
const cwd = ctx.cwd ?? process.cwd();
|
|
5929
6331
|
const repositoryRoot = await resolveRepositoryRootForSession(cwd, "note");
|
|
5930
|
-
const paths =
|
|
6332
|
+
const paths = basouPaths15(repositoryRoot);
|
|
5931
6333
|
await assertWorkspaceInitialized10(paths.root);
|
|
5932
6334
|
const sessionId = await resolveSessionId3(paths, sessionIdInput);
|
|
5933
6335
|
const body = hasBody ? options.body : await readNoteFile(options.fromFile);
|
|
@@ -5959,7 +6361,7 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
|
|
|
5959
6361
|
}
|
|
5960
6362
|
async function readNoteFile(path) {
|
|
5961
6363
|
try {
|
|
5962
|
-
return await
|
|
6364
|
+
return await readFile4(path, "utf8");
|
|
5963
6365
|
} catch (error) {
|
|
5964
6366
|
if (findErrorCode11(error, "ENOENT")) {
|
|
5965
6367
|
throw new Error("Note source not found", { cause: error });
|
|
@@ -6009,7 +6411,7 @@ async function doRunSessionRechain(options, ctx) {
|
|
|
6009
6411
|
}
|
|
6010
6412
|
const cwd = ctx.cwd ?? process.cwd();
|
|
6011
6413
|
const repositoryRoot = await resolveRepositoryRootForSession(cwd, "rechain");
|
|
6012
|
-
const paths =
|
|
6414
|
+
const paths = basouPaths15(repositoryRoot);
|
|
6013
6415
|
await assertWorkspaceInitialized10(paths.root);
|
|
6014
6416
|
const sessionIds = options.session !== void 0 ? [await resolveSessionId3(paths, options.session)] : await enumerateSessionDirs2(paths);
|
|
6015
6417
|
const dryRun = options.dryRun === true;
|
|
@@ -6064,7 +6466,7 @@ function renderRechainRow(row, dryRun) {
|
|
|
6064
6466
|
// src/commands/stats.ts
|
|
6065
6467
|
import {
|
|
6066
6468
|
assertBasouRootSafe as assertBasouRootSafe13,
|
|
6067
|
-
basouPaths as
|
|
6469
|
+
basouPaths as basouPaths16,
|
|
6068
6470
|
computeWorkStats,
|
|
6069
6471
|
findErrorCode as findErrorCode12,
|
|
6070
6472
|
resolveRepositoryRoot as resolveRepositoryRoot10
|
|
@@ -6085,7 +6487,7 @@ async function runStats(options, ctx = {}) {
|
|
|
6085
6487
|
async function doRunStats(options, ctx) {
|
|
6086
6488
|
const cwd = ctx.cwd ?? process.cwd();
|
|
6087
6489
|
const repositoryRoot = await resolveRepositoryRootForStats(cwd);
|
|
6088
|
-
const paths =
|
|
6490
|
+
const paths = basouPaths16(repositoryRoot);
|
|
6089
6491
|
await assertWorkspaceInitialized11(paths.root);
|
|
6090
6492
|
const now = ctx.nowProvider?.() ?? /* @__PURE__ */ new Date();
|
|
6091
6493
|
const result = await computeWorkStats({
|
|
@@ -6194,10 +6596,10 @@ async function assertWorkspaceInitialized11(basouRoot) {
|
|
|
6194
6596
|
// src/commands/status.ts
|
|
6195
6597
|
import {
|
|
6196
6598
|
assertBasouRootSafe as assertBasouRootSafe14,
|
|
6197
|
-
basouPaths as
|
|
6599
|
+
basouPaths as basouPaths17,
|
|
6198
6600
|
buildStatusSnapshot,
|
|
6199
6601
|
findErrorCode as findErrorCode13,
|
|
6200
|
-
readManifest as
|
|
6602
|
+
readManifest as readManifest9,
|
|
6201
6603
|
resolveRepositoryRoot as resolveRepositoryRoot11,
|
|
6202
6604
|
writeStatus
|
|
6203
6605
|
} from "@basou/core";
|
|
@@ -6217,7 +6619,7 @@ async function runStatus(options, ctx = {}) {
|
|
|
6217
6619
|
async function doRunStatus(options, ctx) {
|
|
6218
6620
|
const cwd = ctx.cwd ?? process.cwd();
|
|
6219
6621
|
const repositoryRoot = await resolveRepositoryRootForStatus(cwd);
|
|
6220
|
-
const paths =
|
|
6622
|
+
const paths = basouPaths17(repositoryRoot);
|
|
6221
6623
|
try {
|
|
6222
6624
|
await assertBasouRootSafe14(paths.root);
|
|
6223
6625
|
} catch (error) {
|
|
@@ -6228,7 +6630,7 @@ async function doRunStatus(options, ctx) {
|
|
|
6228
6630
|
}
|
|
6229
6631
|
let manifest;
|
|
6230
6632
|
try {
|
|
6231
|
-
manifest = await
|
|
6633
|
+
manifest = await readManifest9(paths);
|
|
6232
6634
|
} catch (error) {
|
|
6233
6635
|
if (findErrorCode13(error, "ENOENT")) {
|
|
6234
6636
|
throw new Error("Workspace not initialized. Run 'basou init' first.");
|
|
@@ -6266,12 +6668,12 @@ async function resolveRepositoryRootForStatus(cwd) {
|
|
|
6266
6668
|
}
|
|
6267
6669
|
|
|
6268
6670
|
// src/commands/task.ts
|
|
6269
|
-
import { readFile as
|
|
6270
|
-
import { join as
|
|
6671
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
6672
|
+
import { join as join11 } from "path";
|
|
6271
6673
|
import {
|
|
6272
6674
|
archiveTask,
|
|
6273
6675
|
assertBasouRootSafe as assertBasouRootSafe15,
|
|
6274
|
-
basouPaths as
|
|
6676
|
+
basouPaths as basouPaths18,
|
|
6275
6677
|
createTaskWithEvent,
|
|
6276
6678
|
deleteTask,
|
|
6277
6679
|
editTask,
|
|
@@ -6280,7 +6682,7 @@ import {
|
|
|
6280
6682
|
loadSessionEntries as loadSessionEntries2,
|
|
6281
6683
|
loadTaskEntries,
|
|
6282
6684
|
prefixedUlid as prefixedUlid5,
|
|
6283
|
-
readManifest as
|
|
6685
|
+
readManifest as readManifest10,
|
|
6284
6686
|
readTaskFile,
|
|
6285
6687
|
readTaskFileWithArchiveFallback,
|
|
6286
6688
|
reconcileAllTasks,
|
|
@@ -6373,7 +6775,7 @@ async function doRunTaskNew(options, ctx) {
|
|
|
6373
6775
|
}
|
|
6374
6776
|
const cwd = ctx.cwd ?? process.cwd();
|
|
6375
6777
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "new");
|
|
6376
|
-
const paths =
|
|
6778
|
+
const paths = basouPaths18(repositoryRoot);
|
|
6377
6779
|
await assertWorkspaceInitialized12(paths.root);
|
|
6378
6780
|
const description = options.description !== void 0 ? options.description : options.fromFile !== void 0 ? await readDescriptionFile(options.fromFile) : "";
|
|
6379
6781
|
const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
|
|
@@ -6408,7 +6810,7 @@ async function doRunTaskNew(options, ctx) {
|
|
|
6408
6810
|
});
|
|
6409
6811
|
return;
|
|
6410
6812
|
}
|
|
6411
|
-
const manifest = await
|
|
6813
|
+
const manifest = await readManifest10(paths);
|
|
6412
6814
|
const result = await createTaskWithEvent({
|
|
6413
6815
|
mode: "ad-hoc",
|
|
6414
6816
|
paths,
|
|
@@ -6482,7 +6884,7 @@ async function runTaskList(options, ctx = {}) {
|
|
|
6482
6884
|
async function doRunTaskList(options, ctx) {
|
|
6483
6885
|
const cwd = ctx.cwd ?? process.cwd();
|
|
6484
6886
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "list");
|
|
6485
|
-
const paths =
|
|
6887
|
+
const paths = basouPaths18(repositoryRoot);
|
|
6486
6888
|
await assertWorkspaceInitialized12(paths.root);
|
|
6487
6889
|
const entries = await loadTaskEntries(paths, {
|
|
6488
6890
|
onSkip: (id, reason) => printTaskSkip(id, reason)
|
|
@@ -6586,7 +6988,7 @@ async function runTaskShow(idInput, options, ctx = {}) {
|
|
|
6586
6988
|
async function doRunTaskShow(idInput, options, ctx) {
|
|
6587
6989
|
const cwd = ctx.cwd ?? process.cwd();
|
|
6588
6990
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "show");
|
|
6589
|
-
const paths =
|
|
6991
|
+
const paths = basouPaths18(repositoryRoot);
|
|
6590
6992
|
await assertWorkspaceInitialized12(paths.root);
|
|
6591
6993
|
const taskId = await resolveTaskId2(paths, idInput, { includeArchived: true });
|
|
6592
6994
|
const { doc, archived } = await readTaskFileWithArchiveFallback(paths, taskId);
|
|
@@ -6594,7 +6996,7 @@ async function doRunTaskShow(idInput, options, ctx) {
|
|
|
6594
6996
|
const events = [];
|
|
6595
6997
|
const linkedSessionIds = new Set(doc.task.task.linked_sessions);
|
|
6596
6998
|
for (const s of sessions) {
|
|
6597
|
-
const sessionDir =
|
|
6999
|
+
const sessionDir = join11(paths.sessions, s.sessionId);
|
|
6598
7000
|
try {
|
|
6599
7001
|
for await (const ev of replayEvents2(sessionDir, {
|
|
6600
7002
|
onWarning: (w) => printReplayWarning(w, s.sessionId)
|
|
@@ -6730,7 +7132,7 @@ async function doRunTaskStatus(taskIdInput, newStatusInput, options, ctx) {
|
|
|
6730
7132
|
const newStatus = parseTaskStatusPositional(newStatusInput);
|
|
6731
7133
|
const cwd = ctx.cwd ?? process.cwd();
|
|
6732
7134
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "status");
|
|
6733
|
-
const paths =
|
|
7135
|
+
const paths = basouPaths18(repositoryRoot);
|
|
6734
7136
|
await assertWorkspaceInitialized12(paths.root);
|
|
6735
7137
|
const taskId = await resolveTaskId2(paths, taskIdInput);
|
|
6736
7138
|
const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
|
|
@@ -6756,7 +7158,7 @@ async function doRunTaskStatus(taskIdInput, newStatusInput, options, ctx) {
|
|
|
6756
7158
|
});
|
|
6757
7159
|
return;
|
|
6758
7160
|
}
|
|
6759
|
-
const manifest = await
|
|
7161
|
+
const manifest = await readManifest10(paths);
|
|
6760
7162
|
const result = await updateTaskStatusWithEvent({
|
|
6761
7163
|
mode: "ad-hoc",
|
|
6762
7164
|
paths,
|
|
@@ -6807,9 +7209,9 @@ async function runTaskReconcile(options, ctx = {}) {
|
|
|
6807
7209
|
async function doRunTaskReconcile(options, ctx) {
|
|
6808
7210
|
const cwd = ctx.cwd ?? process.cwd();
|
|
6809
7211
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "reconcile");
|
|
6810
|
-
const paths =
|
|
7212
|
+
const paths = basouPaths18(repositoryRoot);
|
|
6811
7213
|
await assertWorkspaceInitialized12(paths.root);
|
|
6812
|
-
const manifest = await
|
|
7214
|
+
const manifest = await readManifest10(paths);
|
|
6813
7215
|
const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
|
|
6814
7216
|
const write = options.write === true;
|
|
6815
7217
|
const verbose = isVerbose(options);
|
|
@@ -6987,9 +7389,9 @@ async function doRunTaskRefreshLinkage(taskIdInput, options, ctx) {
|
|
|
6987
7389
|
}
|
|
6988
7390
|
const cwd = ctx.cwd ?? process.cwd();
|
|
6989
7391
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "refresh-linkage");
|
|
6990
|
-
const paths =
|
|
7392
|
+
const paths = basouPaths18(repositoryRoot);
|
|
6991
7393
|
await assertWorkspaceInitialized12(paths.root);
|
|
6992
|
-
const manifest = await
|
|
7394
|
+
const manifest = await readManifest10(paths);
|
|
6993
7395
|
const taskId = await resolveTaskId2(paths, taskIdInput);
|
|
6994
7396
|
const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
|
|
6995
7397
|
const write = options.write === true;
|
|
@@ -7067,9 +7469,9 @@ async function doRunTaskEdit(taskIdInput, options, ctx) {
|
|
|
7067
7469
|
}
|
|
7068
7470
|
const cwd = ctx.cwd ?? process.cwd();
|
|
7069
7471
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "edit");
|
|
7070
|
-
const paths =
|
|
7472
|
+
const paths = basouPaths18(repositoryRoot);
|
|
7071
7473
|
await assertWorkspaceInitialized12(paths.root);
|
|
7072
|
-
const manifest = await
|
|
7474
|
+
const manifest = await readManifest10(paths);
|
|
7073
7475
|
const taskId = await resolveTaskId2(paths, taskIdInput);
|
|
7074
7476
|
const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
|
|
7075
7477
|
const occurredAt = now.toISOString();
|
|
@@ -7123,9 +7525,9 @@ async function doRunTaskDelete(taskIdInput, options, ctx) {
|
|
|
7123
7525
|
}
|
|
7124
7526
|
const cwd = ctx.cwd ?? process.cwd();
|
|
7125
7527
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "delete");
|
|
7126
|
-
const paths =
|
|
7528
|
+
const paths = basouPaths18(repositoryRoot);
|
|
7127
7529
|
await assertWorkspaceInitialized12(paths.root);
|
|
7128
|
-
const manifest = await
|
|
7530
|
+
const manifest = await readManifest10(paths);
|
|
7129
7531
|
const taskId = await resolveTaskId2(paths, taskIdInput);
|
|
7130
7532
|
if (options.yes !== true) {
|
|
7131
7533
|
await confirmDestructiveAction("delete", taskId);
|
|
@@ -7168,9 +7570,9 @@ async function doRunTaskArchive(taskIdInput, options, ctx) {
|
|
|
7168
7570
|
}
|
|
7169
7571
|
const cwd = ctx.cwd ?? process.cwd();
|
|
7170
7572
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "archive");
|
|
7171
|
-
const paths =
|
|
7573
|
+
const paths = basouPaths18(repositoryRoot);
|
|
7172
7574
|
await assertWorkspaceInitialized12(paths.root);
|
|
7173
|
-
const manifest = await
|
|
7575
|
+
const manifest = await readManifest10(paths);
|
|
7174
7576
|
const taskId = await resolveTaskId2(paths, taskIdInput);
|
|
7175
7577
|
if (options.yes !== true) {
|
|
7176
7578
|
await confirmDestructiveAction("archive", taskId);
|
|
@@ -7282,7 +7684,7 @@ function parsePositiveInt2(raw) {
|
|
|
7282
7684
|
}
|
|
7283
7685
|
async function readDescriptionFile(path) {
|
|
7284
7686
|
try {
|
|
7285
|
-
return await
|
|
7687
|
+
return await readFile5(path, "utf8");
|
|
7286
7688
|
} catch (error) {
|
|
7287
7689
|
if (findErrorCode14(error, "ENOENT")) {
|
|
7288
7690
|
throw new Error("Description source not found", { cause: error });
|
|
@@ -7399,7 +7801,7 @@ function maxLen3(values, floor) {
|
|
|
7399
7801
|
// src/commands/verify.ts
|
|
7400
7802
|
import {
|
|
7401
7803
|
assertBasouRootSafe as assertBasouRootSafe16,
|
|
7402
|
-
basouPaths as
|
|
7804
|
+
basouPaths as basouPaths19,
|
|
7403
7805
|
enumerateSessionDirs as enumerateSessionDirs3,
|
|
7404
7806
|
findErrorCode as findErrorCode15,
|
|
7405
7807
|
resolveRepositoryRoot as resolveRepositoryRoot13,
|
|
@@ -7425,7 +7827,7 @@ async function doRunVerify(options, ctx) {
|
|
|
7425
7827
|
}
|
|
7426
7828
|
const cwd = ctx.cwd ?? process.cwd();
|
|
7427
7829
|
const repositoryRoot = await resolveRepositoryRootForVerify(cwd);
|
|
7428
|
-
const paths =
|
|
7830
|
+
const paths = basouPaths19(repositoryRoot);
|
|
7429
7831
|
await assertWorkspaceInitialized13(paths.root);
|
|
7430
7832
|
const sessionIds = options.session !== void 0 ? [await resolveSessionId5(paths, options.session)] : await enumerateSessionDirs3(paths);
|
|
7431
7833
|
const rows = [];
|
|
@@ -7497,36 +7899,36 @@ async function assertWorkspaceInitialized13(basouRoot) {
|
|
|
7497
7899
|
// src/commands/view.ts
|
|
7498
7900
|
import { spawn } from "child_process";
|
|
7499
7901
|
import { createHash } from "crypto";
|
|
7500
|
-
import { basename as
|
|
7902
|
+
import { basename as basename7, resolve as resolve10 } from "path";
|
|
7501
7903
|
import {
|
|
7502
7904
|
assertBasouRootSafe as assertBasouRootSafe17,
|
|
7503
|
-
basouPaths as
|
|
7905
|
+
basouPaths as basouPaths20,
|
|
7504
7906
|
findErrorCode as findErrorCode17,
|
|
7505
|
-
readManifest as
|
|
7907
|
+
readManifest as readManifest13,
|
|
7506
7908
|
resolveRepositoryRoot as resolveRepositoryRoot14
|
|
7507
7909
|
} from "@basou/core";
|
|
7508
7910
|
import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
|
|
7509
7911
|
|
|
7510
7912
|
// src/lib/portfolio-safety.ts
|
|
7511
7913
|
import { execFile } from "child_process";
|
|
7512
|
-
import { lstat, realpath } from "fs/promises";
|
|
7513
|
-
import { isAbsolute as
|
|
7914
|
+
import { lstat as lstat2, realpath as realpath2 } from "fs/promises";
|
|
7915
|
+
import { isAbsolute as isAbsolute6, join as join12, relative as relative4, resolve as resolve9 } from "path";
|
|
7514
7916
|
import { promisify } from "util";
|
|
7515
|
-
import { readManifest as
|
|
7917
|
+
import { readManifest as readManifest11 } from "@basou/core";
|
|
7516
7918
|
var execFileAsync = promisify(execFile);
|
|
7517
7919
|
function errorCode(error) {
|
|
7518
7920
|
return error instanceof Error ? error.code : void 0;
|
|
7519
7921
|
}
|
|
7520
7922
|
async function canonical(p) {
|
|
7521
7923
|
try {
|
|
7522
|
-
return await
|
|
7924
|
+
return await realpath2(p);
|
|
7523
7925
|
} catch {
|
|
7524
|
-
return
|
|
7926
|
+
return resolve9(p);
|
|
7525
7927
|
}
|
|
7526
7928
|
}
|
|
7527
7929
|
function isInside(child, parent) {
|
|
7528
7930
|
const rel = relative4(parent, child);
|
|
7529
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
7931
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute6(rel);
|
|
7530
7932
|
}
|
|
7531
7933
|
function isBasouPath(p) {
|
|
7532
7934
|
return p === ".basou" || p.startsWith(".basou/") || p.includes("/.basou/") || p.endsWith("/.basou");
|
|
@@ -7534,7 +7936,7 @@ function isBasouPath(p) {
|
|
|
7534
7936
|
async function inspectRepo(repoPath) {
|
|
7535
7937
|
let hasEntry = false;
|
|
7536
7938
|
try {
|
|
7537
|
-
await
|
|
7939
|
+
await lstat2(join12(repoPath, ".basou"));
|
|
7538
7940
|
hasEntry = true;
|
|
7539
7941
|
} catch (error) {
|
|
7540
7942
|
if (errorCode(error) !== "ENOENT") {
|
|
@@ -7565,7 +7967,7 @@ async function checkPortfolioSafety(workspaces) {
|
|
|
7565
7967
|
const wsReal = await canonical(ws.repoRoot);
|
|
7566
7968
|
let sourceRoots = [];
|
|
7567
7969
|
try {
|
|
7568
|
-
const manifest = await
|
|
7970
|
+
const manifest = await readManifest11(ws.paths);
|
|
7569
7971
|
sourceRoots = manifest.import?.source_roots ?? [];
|
|
7570
7972
|
} catch (error) {
|
|
7571
7973
|
if (error instanceof Error && error.message === "YAML file not found") {
|
|
@@ -7583,7 +7985,7 @@ async function checkPortfolioSafety(workspaces) {
|
|
|
7583
7985
|
}
|
|
7584
7986
|
const monitored = /* @__PURE__ */ new Map();
|
|
7585
7987
|
for (const root of sourceRoots) {
|
|
7586
|
-
const display =
|
|
7988
|
+
const display = resolve9(ws.repoRoot, root);
|
|
7587
7989
|
const real = await canonical(display);
|
|
7588
7990
|
if (real !== wsReal) monitored.set(real, display);
|
|
7589
7991
|
}
|
|
@@ -7635,7 +8037,7 @@ function formatSafetyReport(result) {
|
|
|
7635
8037
|
|
|
7636
8038
|
// src/lib/view-server.ts
|
|
7637
8039
|
import { createServer } from "http";
|
|
7638
|
-
import { join as
|
|
8040
|
+
import { join as join13 } from "path";
|
|
7639
8041
|
import {
|
|
7640
8042
|
computeWorkStats as computeWorkStats2,
|
|
7641
8043
|
enumerateApprovals as enumerateApprovals2,
|
|
@@ -7645,8 +8047,8 @@ import {
|
|
|
7645
8047
|
loadSessionEntries as loadSessionEntries3,
|
|
7646
8048
|
loadTaskEntries as loadTaskEntries2,
|
|
7647
8049
|
readAllEvents as readAllEvents2,
|
|
7648
|
-
readManifest as
|
|
7649
|
-
readMarkdownFile as
|
|
8050
|
+
readManifest as readManifest12,
|
|
8051
|
+
readMarkdownFile as readMarkdownFile6,
|
|
7650
8052
|
readSessionYaml as readSessionYaml3,
|
|
7651
8053
|
readTaskFile as readTaskFile2,
|
|
7652
8054
|
renderDecisions as renderDecisions3,
|
|
@@ -8292,7 +8694,7 @@ function startViewServer(opts) {
|
|
|
8292
8694
|
};
|
|
8293
8695
|
let boundPort = port;
|
|
8294
8696
|
const getPort = () => boundPort;
|
|
8295
|
-
return new Promise((
|
|
8697
|
+
return new Promise((resolve11, reject) => {
|
|
8296
8698
|
const server = createServer((req, res) => {
|
|
8297
8699
|
handleRequest(req, res, deps, getPort, runExclusive).catch((error) => {
|
|
8298
8700
|
sendError(res, error instanceof HttpError ? error.status : 500, pathlessMessage(error));
|
|
@@ -8303,7 +8705,7 @@ function startViewServer(opts) {
|
|
|
8303
8705
|
const address = server.address();
|
|
8304
8706
|
boundPort = isAddressInfo(address) ? address.port : port;
|
|
8305
8707
|
server.off("error", reject);
|
|
8306
|
-
|
|
8708
|
+
resolve11({
|
|
8307
8709
|
url: `http://${host}:${boundPort}`,
|
|
8308
8710
|
port: boundPort,
|
|
8309
8711
|
close: () => closeServer(server)
|
|
@@ -8315,8 +8717,8 @@ function isAddressInfo(value) {
|
|
|
8315
8717
|
return value !== null && typeof value === "object";
|
|
8316
8718
|
}
|
|
8317
8719
|
function closeServer(server) {
|
|
8318
|
-
return new Promise((
|
|
8319
|
-
server.close(() =>
|
|
8720
|
+
return new Promise((resolve11) => {
|
|
8721
|
+
server.close(() => resolve11());
|
|
8320
8722
|
server.closeAllConnections();
|
|
8321
8723
|
});
|
|
8322
8724
|
}
|
|
@@ -8533,7 +8935,7 @@ async function captureStaleness(ws, nowIso) {
|
|
|
8533
8935
|
async function overview(ws, nowProvider) {
|
|
8534
8936
|
let manifest;
|
|
8535
8937
|
try {
|
|
8536
|
-
manifest = await
|
|
8938
|
+
manifest = await readManifest12(ws.paths);
|
|
8537
8939
|
} catch (error) {
|
|
8538
8940
|
if (findErrorCode16(error, "ENOENT")) {
|
|
8539
8941
|
return { initialized: false, repoRoot: ws.repoRoot };
|
|
@@ -8590,7 +8992,7 @@ async function sessionDetail(ws, sessionId) {
|
|
|
8590
8992
|
throw error;
|
|
8591
8993
|
}
|
|
8592
8994
|
try {
|
|
8593
|
-
const events = await readAllEvents2(
|
|
8995
|
+
const events = await readAllEvents2(join13(ws.paths.sessions, sessionId));
|
|
8594
8996
|
return { session, events };
|
|
8595
8997
|
} catch {
|
|
8596
8998
|
return { session, events: [], degraded: true };
|
|
@@ -8612,7 +9014,7 @@ async function taskDetail(ws, taskId) {
|
|
|
8612
9014
|
}
|
|
8613
9015
|
}
|
|
8614
9016
|
async function decisionsView(ws, nowProvider) {
|
|
8615
|
-
const fromDisk = await
|
|
9017
|
+
const fromDisk = await readMarkdownFile6(ws.paths.files.decisions);
|
|
8616
9018
|
if (fromDisk !== null) {
|
|
8617
9019
|
return { body: fromDisk, fromDisk: true };
|
|
8618
9020
|
}
|
|
@@ -8635,7 +9037,7 @@ async function approvalsView(ws, nowProvider) {
|
|
|
8635
9037
|
return { pending: await toViews(ids.pending), resolved: await toViews(ids.resolved) };
|
|
8636
9038
|
}
|
|
8637
9039
|
async function handoffView(ws, nowProvider) {
|
|
8638
|
-
const fromDisk = await
|
|
9040
|
+
const fromDisk = await readMarkdownFile6(ws.paths.files.handoff);
|
|
8639
9041
|
if (fromDisk !== null) {
|
|
8640
9042
|
return { body: fromDisk, fromDisk: true };
|
|
8641
9043
|
}
|
|
@@ -8804,18 +9206,18 @@ async function doRunView(options, ctx) {
|
|
|
8804
9206
|
}
|
|
8805
9207
|
async function buildSingleDeps(ctx, cwd) {
|
|
8806
9208
|
const repositoryRoot = await resolveRepositoryRootForView(cwd);
|
|
8807
|
-
const paths =
|
|
9209
|
+
const paths = basouPaths20(repositoryRoot);
|
|
8808
9210
|
await assertWorkspaceInitialized14(paths.root);
|
|
8809
9211
|
const entry = await buildWorkspaceEntry(repositoryRoot, ctx);
|
|
8810
9212
|
return { workspaces: [entry], mode: "single", nowProvider: nowProviderOf(ctx) };
|
|
8811
9213
|
}
|
|
8812
9214
|
async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
|
|
8813
|
-
const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path:
|
|
9215
|
+
const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve10(cwd, p) })) : await loadPortfolioConfig(ctx.portfolioConfigPath);
|
|
8814
9216
|
const entries = [];
|
|
8815
9217
|
const seenPath = /* @__PURE__ */ new Set();
|
|
8816
9218
|
const seenKey = /* @__PURE__ */ new Set();
|
|
8817
9219
|
for (const spec of specs) {
|
|
8818
|
-
const repoRoot =
|
|
9220
|
+
const repoRoot = resolve10(spec.path);
|
|
8819
9221
|
if (seenPath.has(repoRoot)) continue;
|
|
8820
9222
|
seenPath.add(repoRoot);
|
|
8821
9223
|
const entry = await buildWorkspaceEntry(repoRoot, ctx, spec.label);
|
|
@@ -8828,14 +9230,14 @@ async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
|
|
|
8828
9230
|
return { workspaces: entries, mode: "portfolio", nowProvider: nowProviderOf(ctx) };
|
|
8829
9231
|
}
|
|
8830
9232
|
async function buildWorkspaceEntry(repoRoot, ctx, labelOverride) {
|
|
8831
|
-
const paths =
|
|
9233
|
+
const paths = basouPaths20(repoRoot);
|
|
8832
9234
|
const importCtx = {
|
|
8833
9235
|
cwd: repoRoot,
|
|
8834
9236
|
...ctx.claudeProjectsDir !== void 0 ? { claudeProjectsDir: ctx.claudeProjectsDir } : {},
|
|
8835
9237
|
...ctx.codexSessionsDir !== void 0 ? { codexSessionsDir: ctx.codexSessionsDir } : {}
|
|
8836
9238
|
};
|
|
8837
9239
|
try {
|
|
8838
|
-
const manifest = await
|
|
9240
|
+
const manifest = await readManifest13(paths);
|
|
8839
9241
|
return {
|
|
8840
9242
|
key: manifest.workspace.id,
|
|
8841
9243
|
label: labelOverride ?? manifest.workspace.name,
|
|
@@ -8848,7 +9250,7 @@ async function buildWorkspaceEntry(repoRoot, ctx, labelOverride) {
|
|
|
8848
9250
|
const notFound = error instanceof Error && error.message === "YAML file not found";
|
|
8849
9251
|
return {
|
|
8850
9252
|
key: `ws-${createHash("sha1").update(repoRoot).digest("hex").slice(0, 12)}`,
|
|
8851
|
-
label: labelOverride ??
|
|
9253
|
+
label: labelOverride ?? basename7(repoRoot),
|
|
8852
9254
|
paths,
|
|
8853
9255
|
repoRoot,
|
|
8854
9256
|
importCtx,
|
|
@@ -8887,7 +9289,7 @@ function openInBrowser(url, override) {
|
|
|
8887
9289
|
}
|
|
8888
9290
|
}
|
|
8889
9291
|
function waitForShutdown(signal) {
|
|
8890
|
-
return new Promise((
|
|
9292
|
+
return new Promise((resolve11) => {
|
|
8891
9293
|
const cleanup = () => {
|
|
8892
9294
|
process.off("SIGINT", onSignal);
|
|
8893
9295
|
process.off("SIGTERM", onSignal);
|
|
@@ -8895,18 +9297,18 @@ function waitForShutdown(signal) {
|
|
|
8895
9297
|
};
|
|
8896
9298
|
const onSignal = () => {
|
|
8897
9299
|
cleanup();
|
|
8898
|
-
|
|
9300
|
+
resolve11();
|
|
8899
9301
|
};
|
|
8900
9302
|
const onAbort = () => {
|
|
8901
9303
|
cleanup();
|
|
8902
|
-
|
|
9304
|
+
resolve11();
|
|
8903
9305
|
};
|
|
8904
9306
|
process.on("SIGINT", onSignal);
|
|
8905
9307
|
process.on("SIGTERM", onSignal);
|
|
8906
9308
|
if (signal !== void 0) {
|
|
8907
9309
|
if (signal.aborted) {
|
|
8908
9310
|
cleanup();
|
|
8909
|
-
|
|
9311
|
+
resolve11();
|
|
8910
9312
|
return;
|
|
8911
9313
|
}
|
|
8912
9314
|
signal.addEventListener("abort", onAbort);
|
|
@@ -8963,6 +9365,7 @@ function buildProgram() {
|
|
|
8963
9365
|
registerOrientCommand(program2);
|
|
8964
9366
|
registerReviewGapsCommand(program2);
|
|
8965
9367
|
registerProjectCommand(program2);
|
|
9368
|
+
registerProtocolCommand(program2);
|
|
8966
9369
|
return program2;
|
|
8967
9370
|
}
|
|
8968
9371
|
|