@basou/cli 0.14.1 → 0.16.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 +744 -270
- package/dist/index.js.map +1 -1
- package/dist/program.js +744 -270
- package/dist/program.js.map +1 -1
- package/package.json +2 -2
package/dist/program.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;
|
|
675
|
+
try {
|
|
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;
|
|
658
720
|
try {
|
|
659
|
-
|
|
660
|
-
onRedirect: ({ via, root }) => console.error(`Resolved workspace view to ${
|
|
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,12 +2614,68 @@ 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
|
|
2481
2621
|
} from "@basou/core";
|
|
2482
2622
|
|
|
2623
|
+
// src/lib/hosts-config.ts
|
|
2624
|
+
import { homedir as homedir5 } from "os";
|
|
2625
|
+
import { isAbsolute as isAbsolute2, join as join5, resolve as resolve6 } from "path";
|
|
2626
|
+
import { readYamlFile as readYamlFile4 } from "@basou/core";
|
|
2627
|
+
var DEFAULT_HOSTS_CONFIG_PATH = join5(homedir5(), ".basou", "hosts.yaml");
|
|
2628
|
+
function expandTilde2(p) {
|
|
2629
|
+
if (p === "~") return homedir5();
|
|
2630
|
+
if (p.startsWith("~/")) return join5(homedir5(), p.slice(2));
|
|
2631
|
+
return p;
|
|
2632
|
+
}
|
|
2633
|
+
function isRecord2(value) {
|
|
2634
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2635
|
+
}
|
|
2636
|
+
async function loadHostsConfig(configPath = DEFAULT_HOSTS_CONFIG_PATH) {
|
|
2637
|
+
let raw;
|
|
2638
|
+
try {
|
|
2639
|
+
raw = await readYamlFile4(configPath);
|
|
2640
|
+
} catch (error) {
|
|
2641
|
+
if (error instanceof Error && error.message === "YAML file not found") {
|
|
2642
|
+
return null;
|
|
2643
|
+
}
|
|
2644
|
+
if (error instanceof Error && error.message === "Failed to parse YAML content") {
|
|
2645
|
+
throw new Error("~/.basou/hosts.yaml is not valid YAML.");
|
|
2646
|
+
}
|
|
2647
|
+
throw error;
|
|
2648
|
+
}
|
|
2649
|
+
if (!isRecord2(raw) || !Array.isArray(raw.hosts)) {
|
|
2650
|
+
throw new Error("~/.basou/hosts.yaml must contain a 'hosts:' list.");
|
|
2651
|
+
}
|
|
2652
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
2653
|
+
const seenLabels = /* @__PURE__ */ new Set();
|
|
2654
|
+
const result = [];
|
|
2655
|
+
for (const entry of raw.hosts) {
|
|
2656
|
+
if (!isRecord2(entry) || typeof entry.label !== "string" || entry.label.trim().length === 0) {
|
|
2657
|
+
throw new Error("Each host needs a non-empty string 'label'.");
|
|
2658
|
+
}
|
|
2659
|
+
const label = entry.label.trim();
|
|
2660
|
+
if (typeof entry.path !== "string" || entry.path.trim().length === 0) {
|
|
2661
|
+
throw new Error("Each host needs a non-empty string 'path'.");
|
|
2662
|
+
}
|
|
2663
|
+
const expanded = expandTilde2(entry.path.trim());
|
|
2664
|
+
if (!isAbsolute2(expanded)) {
|
|
2665
|
+
throw new Error("Host paths must be absolute (or start with '~').");
|
|
2666
|
+
}
|
|
2667
|
+
const abs = resolve6(expanded);
|
|
2668
|
+
if (seenPaths.has(abs)) continue;
|
|
2669
|
+
if (seenLabels.has(label)) {
|
|
2670
|
+
throw new Error(`Duplicate host label '${label}'; each host needs a distinct label.`);
|
|
2671
|
+
}
|
|
2672
|
+
seenPaths.add(abs);
|
|
2673
|
+
seenLabels.add(label);
|
|
2674
|
+
result.push({ label, path: abs });
|
|
2675
|
+
}
|
|
2676
|
+
return result;
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2483
2679
|
// src/lib/provenance-actions.ts
|
|
2484
2680
|
import {
|
|
2485
2681
|
readMarkdownFile as readMarkdownFile3,
|
|
@@ -2679,21 +2875,36 @@ async function runOrient(options, ctx = {}) {
|
|
|
2679
2875
|
async function doRunOrient(options, ctx) {
|
|
2680
2876
|
const cwd = ctx.cwd ?? process.cwd();
|
|
2681
2877
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "orient");
|
|
2682
|
-
const paths =
|
|
2878
|
+
const paths = basouPaths9(repositoryRoot);
|
|
2683
2879
|
await assertWorkspaceInitialized7(paths.root);
|
|
2684
2880
|
const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
2685
2881
|
const probeCtx = { cwd: repositoryRoot };
|
|
2686
2882
|
if (ctx.claudeProjectsDir !== void 0) probeCtx.claudeProjectsDir = ctx.claudeProjectsDir;
|
|
2687
2883
|
if (ctx.codexSessionsDir !== void 0) probeCtx.codexSessionsDir = ctx.codexSessionsDir;
|
|
2688
2884
|
const staleness = await probeStaleness({ ctx: probeCtx, paths, nowIso });
|
|
2885
|
+
let federatedRoots = [];
|
|
2886
|
+
try {
|
|
2887
|
+
const hosts = await loadHostsConfig(ctx.hostsConfigPath);
|
|
2888
|
+
if (hosts !== null) {
|
|
2889
|
+
federatedRoots = hosts.map((h) => ({ paths: basouPaths9(h.path), host: h.label }));
|
|
2890
|
+
}
|
|
2891
|
+
} catch (error) {
|
|
2892
|
+
console.error(
|
|
2893
|
+
`basou: ignoring ~/.basou/hosts.yaml (${error instanceof Error ? error.message : String(error)}); showing local sessions only.`
|
|
2894
|
+
);
|
|
2895
|
+
}
|
|
2689
2896
|
const result = await renderOrientation2({
|
|
2690
2897
|
paths,
|
|
2691
2898
|
nowIso,
|
|
2692
2899
|
staleness,
|
|
2693
2900
|
verbose: options.verbose === true,
|
|
2901
|
+
federatedRoots,
|
|
2694
2902
|
onWarning: (w, sid) => printReplayWarning(w, sid),
|
|
2695
2903
|
onSessionSkip: (sid, reason) => printSessionSkip(sid, reason),
|
|
2696
|
-
onTaskSkip: (taskId, reason) => printTaskSkip(taskId, reason)
|
|
2904
|
+
onTaskSkip: (taskId, reason) => printTaskSkip(taskId, reason),
|
|
2905
|
+
onHostUnavailable: (host, error) => console.error(
|
|
2906
|
+
`basou: host '${host}' mirror unreadable (${error instanceof Error ? error.message : String(error)}); skipping it.`
|
|
2907
|
+
)
|
|
2697
2908
|
});
|
|
2698
2909
|
await writeMarkdownFile4(paths.files.orientation, `${result.body}
|
|
2699
2910
|
`);
|
|
@@ -2730,9 +2941,9 @@ import {
|
|
|
2730
2941
|
unlinkSync,
|
|
2731
2942
|
writeFileSync
|
|
2732
2943
|
} from "fs";
|
|
2733
|
-
import { basename as
|
|
2944
|
+
import { basename as basename4, dirname, isAbsolute as isAbsolute3, join as join6, relative as relative2, resolve as resolve7 } from "path";
|
|
2734
2945
|
import {
|
|
2735
|
-
basouPaths as
|
|
2946
|
+
basouPaths as basouPaths10,
|
|
2736
2947
|
GENERATED_END,
|
|
2737
2948
|
GENERATED_START,
|
|
2738
2949
|
isGitNotFound,
|
|
@@ -2743,7 +2954,7 @@ import {
|
|
|
2743
2954
|
planRename,
|
|
2744
2955
|
planRosterAdoption,
|
|
2745
2956
|
planWorkspaceView,
|
|
2746
|
-
readManifest as
|
|
2957
|
+
readManifest as readManifest6,
|
|
2747
2958
|
readMarkdownFile as readMarkdownFile4,
|
|
2748
2959
|
reconcileSourceRoots,
|
|
2749
2960
|
renderWithMarkers as renderWithMarkers4,
|
|
@@ -2850,8 +3061,8 @@ function preservedUnknownLines(fields) {
|
|
|
2850
3061
|
async function doRunProjectCheck(options, ctx) {
|
|
2851
3062
|
const cwd = ctx.cwd ?? process.cwd();
|
|
2852
3063
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "project check");
|
|
2853
|
-
const paths =
|
|
2854
|
-
const manifest = await
|
|
3064
|
+
const paths = basouPaths10(repositoryRoot);
|
|
3065
|
+
const manifest = await readManifest6(paths);
|
|
2855
3066
|
const summary = summarizeRosterDrift({
|
|
2856
3067
|
...manifest.repos !== void 0 ? { repos: manifest.repos } : {},
|
|
2857
3068
|
sourceRoots: effectiveSourceRoots(manifest)
|
|
@@ -2912,8 +3123,8 @@ async function runProjectSync(options, ctx = {}) {
|
|
|
2912
3123
|
async function doRunProjectSync(options, ctx) {
|
|
2913
3124
|
const cwd = ctx.cwd ?? process.cwd();
|
|
2914
3125
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "project sync");
|
|
2915
|
-
const paths =
|
|
2916
|
-
const manifest = await
|
|
3126
|
+
const paths = basouPaths10(repositoryRoot);
|
|
3127
|
+
const manifest = await readManifest6(paths);
|
|
2917
3128
|
const hasRoster = manifest.repos !== void 0 && manifest.repos.length > 0;
|
|
2918
3129
|
const reconcile = reconcileSourceRoots({
|
|
2919
3130
|
...manifest.repos !== void 0 ? { repos: manifest.repos } : {},
|
|
@@ -2982,20 +3193,20 @@ async function runProjectAdopt(options, ctx = {}) {
|
|
|
2982
3193
|
}
|
|
2983
3194
|
}
|
|
2984
3195
|
function classifySourceRoot(repositoryRoot, declaredPath) {
|
|
2985
|
-
const absolute =
|
|
3196
|
+
const absolute = resolve7(repositoryRoot, declaredPath);
|
|
2986
3197
|
let real;
|
|
2987
3198
|
try {
|
|
2988
3199
|
real = realpathSync(absolute);
|
|
2989
3200
|
} catch {
|
|
2990
3201
|
return { path: declaredPath, kind: "unresolved" };
|
|
2991
3202
|
}
|
|
2992
|
-
return { path: declaredPath, kind: existsSync(
|
|
3203
|
+
return { path: declaredPath, kind: existsSync(join6(real, ".git")) ? "repo" : "non-repo" };
|
|
2993
3204
|
}
|
|
2994
3205
|
async function doRunProjectAdopt(options, ctx) {
|
|
2995
3206
|
const cwd = ctx.cwd ?? process.cwd();
|
|
2996
3207
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "project adopt");
|
|
2997
|
-
const paths =
|
|
2998
|
-
const manifest = await
|
|
3208
|
+
const paths = basouPaths10(repositoryRoot);
|
|
3209
|
+
const manifest = await readManifest6(paths);
|
|
2999
3210
|
const alreadyDeclared = manifest.repos !== void 0 && manifest.repos.length > 0;
|
|
3000
3211
|
const candidates = effectiveSourceRoots(manifest).map(
|
|
3001
3212
|
(r) => classifySourceRoot(repositoryRoot, r)
|
|
@@ -3084,11 +3295,11 @@ async function gatherRepoWiring(repositoryRoot, entry) {
|
|
|
3084
3295
|
};
|
|
3085
3296
|
let real;
|
|
3086
3297
|
try {
|
|
3087
|
-
real = realpathSync(
|
|
3298
|
+
real = realpathSync(resolve7(repositoryRoot, entry.path));
|
|
3088
3299
|
} catch {
|
|
3089
3300
|
return { ...base, reachable: false, instructionFiles: [] };
|
|
3090
3301
|
}
|
|
3091
|
-
if (!existsSync(
|
|
3302
|
+
if (!existsSync(join6(real, ".git"))) {
|
|
3092
3303
|
return { ...base, reachable: false, instructionFiles: [] };
|
|
3093
3304
|
}
|
|
3094
3305
|
try {
|
|
@@ -3096,7 +3307,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
|
|
|
3096
3307
|
for (const name of INSTRUCTION_FILES) {
|
|
3097
3308
|
let present = true;
|
|
3098
3309
|
try {
|
|
3099
|
-
lstatSync(
|
|
3310
|
+
lstatSync(join6(real, name));
|
|
3100
3311
|
} catch {
|
|
3101
3312
|
present = false;
|
|
3102
3313
|
}
|
|
@@ -3111,8 +3322,8 @@ async function gatherRepoWiring(repositoryRoot, entry) {
|
|
|
3111
3322
|
async function doRunProjectWiring(options, ctx) {
|
|
3112
3323
|
const cwd = ctx.cwd ?? process.cwd();
|
|
3113
3324
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "project wiring");
|
|
3114
|
-
const paths =
|
|
3115
|
-
const manifest = await
|
|
3325
|
+
const paths = basouPaths10(repositoryRoot);
|
|
3326
|
+
const manifest = await readManifest6(paths);
|
|
3116
3327
|
const roster = manifest.repos ?? [];
|
|
3117
3328
|
const facts = [];
|
|
3118
3329
|
for (const entry of roster) facts.push(await gatherRepoWiring(repositoryRoot, entry));
|
|
@@ -3189,14 +3400,14 @@ function gatherRepoGitignore(repositoryRoot, entry) {
|
|
|
3189
3400
|
};
|
|
3190
3401
|
let real;
|
|
3191
3402
|
try {
|
|
3192
|
-
real = realpathSync(
|
|
3403
|
+
real = realpathSync(resolve7(repositoryRoot, entry.path));
|
|
3193
3404
|
} catch {
|
|
3194
3405
|
return { ...base, reachable: false, currentLines: [] };
|
|
3195
3406
|
}
|
|
3196
|
-
if (!existsSync(
|
|
3407
|
+
if (!existsSync(join6(real, ".git"))) {
|
|
3197
3408
|
return { ...base, reachable: false, currentLines: [] };
|
|
3198
3409
|
}
|
|
3199
|
-
return { ...base, reachable: true, currentLines: readGitignoreLines(
|
|
3410
|
+
return { ...base, reachable: true, currentLines: readGitignoreLines(join6(real, ".gitignore")) };
|
|
3200
3411
|
}
|
|
3201
3412
|
function hasErrorCode(error) {
|
|
3202
3413
|
return error instanceof Error && typeof error.code === "string";
|
|
@@ -3210,7 +3421,7 @@ function readGitignoreLines(file) {
|
|
|
3210
3421
|
}
|
|
3211
3422
|
}
|
|
3212
3423
|
function applyGitignorePlan(repositoryRoot, plan) {
|
|
3213
|
-
const file =
|
|
3424
|
+
const file = join6(realpathSync(resolve7(repositoryRoot, plan.path)), ".gitignore");
|
|
3214
3425
|
let existing = "";
|
|
3215
3426
|
try {
|
|
3216
3427
|
existing = readFileSync(file, "utf8");
|
|
@@ -3230,8 +3441,8 @@ function applyGitignorePlan(repositoryRoot, plan) {
|
|
|
3230
3441
|
async function doRunProjectGitignore(options, ctx) {
|
|
3231
3442
|
const cwd = ctx.cwd ?? process.cwd();
|
|
3232
3443
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "project gitignore");
|
|
3233
|
-
const paths =
|
|
3234
|
-
const manifest = await
|
|
3444
|
+
const paths = basouPaths10(repositoryRoot);
|
|
3445
|
+
const manifest = await readManifest6(paths);
|
|
3235
3446
|
const roster = manifest.repos ?? [];
|
|
3236
3447
|
const facts = roster.map((entry) => gatherRepoGitignore(repositoryRoot, entry));
|
|
3237
3448
|
const summary = planGitignore({ repos: facts, required: [...INSTRUCTION_FILES] });
|
|
@@ -3322,23 +3533,23 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
|
|
|
3322
3533
|
const base = { path: entry.path };
|
|
3323
3534
|
let real;
|
|
3324
3535
|
try {
|
|
3325
|
-
real = realpathSync(
|
|
3536
|
+
real = realpathSync(resolve7(repositoryRoot, entry.path));
|
|
3326
3537
|
} catch {
|
|
3327
3538
|
return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
|
|
3328
3539
|
}
|
|
3329
3540
|
if (real === anchorReal) {
|
|
3330
3541
|
return { ...base, isAnchor: true, reachable: true, canonicalPresent: false, files: [] };
|
|
3331
3542
|
}
|
|
3332
|
-
if (!existsSync(
|
|
3543
|
+
if (!existsSync(join6(real, ".git"))) {
|
|
3333
3544
|
return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
|
|
3334
3545
|
}
|
|
3335
|
-
const canonicalFile =
|
|
3546
|
+
const canonicalFile = join6(anchorReal, "agents", basename4(real), CANONICAL_FILE);
|
|
3336
3547
|
if (!existsSync(canonicalFile)) {
|
|
3337
3548
|
return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
|
|
3338
3549
|
}
|
|
3339
3550
|
const files = expectedSymlinkTargets(real, canonicalFile).map(
|
|
3340
3551
|
(spec) => {
|
|
3341
|
-
const { state, actualTarget } = inspectSymlink(
|
|
3552
|
+
const { state, actualTarget } = inspectSymlink(join6(real, spec.name), spec.target);
|
|
3342
3553
|
return {
|
|
3343
3554
|
name: spec.name,
|
|
3344
3555
|
expectedTarget: spec.target,
|
|
@@ -3352,14 +3563,14 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
|
|
|
3352
3563
|
isAnchor: false,
|
|
3353
3564
|
reachable: true,
|
|
3354
3565
|
canonicalPresent: true,
|
|
3355
|
-
canonicalName:
|
|
3566
|
+
canonicalName: basename4(real),
|
|
3356
3567
|
files
|
|
3357
3568
|
};
|
|
3358
3569
|
}
|
|
3359
3570
|
function applySymlinkPlan(repositoryRoot, plan) {
|
|
3360
3571
|
let real;
|
|
3361
3572
|
try {
|
|
3362
|
-
real = realpathSync(
|
|
3573
|
+
real = realpathSync(resolve7(repositoryRoot, plan.path));
|
|
3363
3574
|
} catch (error) {
|
|
3364
3575
|
const message = failureReason(error);
|
|
3365
3576
|
return { created: [], failed: plan.toCreate.map((c) => ({ file: c.name, message })) };
|
|
@@ -3367,7 +3578,7 @@ function applySymlinkPlan(repositoryRoot, plan) {
|
|
|
3367
3578
|
const created = [];
|
|
3368
3579
|
const failed = [];
|
|
3369
3580
|
for (const { name, target } of plan.toCreate) {
|
|
3370
|
-
const filePath =
|
|
3581
|
+
const filePath = join6(real, name);
|
|
3371
3582
|
try {
|
|
3372
3583
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
3373
3584
|
symlinkSync(target, filePath);
|
|
@@ -3384,8 +3595,8 @@ function failureReason(error) {
|
|
|
3384
3595
|
async function doRunProjectSymlinks(options, ctx) {
|
|
3385
3596
|
const cwd = ctx.cwd ?? process.cwd();
|
|
3386
3597
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "project symlinks");
|
|
3387
|
-
const paths =
|
|
3388
|
-
const manifest = await
|
|
3598
|
+
const paths = basouPaths10(repositoryRoot);
|
|
3599
|
+
const manifest = await readManifest6(paths);
|
|
3389
3600
|
const roster = manifest.repos ?? [];
|
|
3390
3601
|
const anchorReal = realpathSync(repositoryRoot);
|
|
3391
3602
|
const facts = roster.map((entry) => gatherRepoSymlinks(repositoryRoot, anchorReal, entry));
|
|
@@ -3504,12 +3715,12 @@ async function runProjectWorkspace(options, ctx = {}) {
|
|
|
3504
3715
|
}
|
|
3505
3716
|
}
|
|
3506
3717
|
function resolveViewDir(repositoryRoot, viewPath) {
|
|
3507
|
-
const abs =
|
|
3718
|
+
const abs = resolve7(repositoryRoot, viewPath);
|
|
3508
3719
|
try {
|
|
3509
3720
|
return realpathSync(abs);
|
|
3510
3721
|
} catch {
|
|
3511
3722
|
try {
|
|
3512
|
-
return
|
|
3723
|
+
return join6(realpathSync(dirname(abs)), basename4(abs));
|
|
3513
3724
|
} catch {
|
|
3514
3725
|
return abs;
|
|
3515
3726
|
}
|
|
@@ -3518,7 +3729,7 @@ function resolveViewDir(repositoryRoot, viewPath) {
|
|
|
3518
3729
|
function gatherViewRepo(repositoryRoot, viewDir, entry) {
|
|
3519
3730
|
let repoReal;
|
|
3520
3731
|
try {
|
|
3521
|
-
repoReal = realpathSync(
|
|
3732
|
+
repoReal = realpathSync(resolve7(repositoryRoot, entry.path));
|
|
3522
3733
|
} catch {
|
|
3523
3734
|
return { path: entry.path, reachable: false };
|
|
3524
3735
|
}
|
|
@@ -3526,8 +3737,8 @@ function gatherViewRepo(repositoryRoot, viewDir, entry) {
|
|
|
3526
3737
|
if (expectedTarget === "" || expectedTarget === ".") {
|
|
3527
3738
|
return { path: entry.path, reachable: false };
|
|
3528
3739
|
}
|
|
3529
|
-
const linkName =
|
|
3530
|
-
const { state, actualTarget } = inspectSymlink(
|
|
3740
|
+
const linkName = basename4(repoReal);
|
|
3741
|
+
const { state, actualTarget } = inspectSymlink(join6(viewDir, linkName), expectedTarget);
|
|
3531
3742
|
return {
|
|
3532
3743
|
path: entry.path,
|
|
3533
3744
|
reachable: true,
|
|
@@ -3541,7 +3752,7 @@ function applyViewPlan(viewDir, toCreate) {
|
|
|
3541
3752
|
const created = [];
|
|
3542
3753
|
const failed = [];
|
|
3543
3754
|
for (const { name, target } of toCreate) {
|
|
3544
|
-
const filePath =
|
|
3755
|
+
const filePath = join6(viewDir, name);
|
|
3545
3756
|
try {
|
|
3546
3757
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
3547
3758
|
symlinkSync(target, filePath);
|
|
@@ -3556,7 +3767,7 @@ var TOP_LEVEL_INSTRUCTION_FILES_LOWER = new Set(
|
|
|
3556
3767
|
INSTRUCTION_FILES.filter((f) => !f.includes("/")).map((f) => f.toLowerCase())
|
|
3557
3768
|
);
|
|
3558
3769
|
function classifyViewLink(viewDir, name, rosterRealpaths) {
|
|
3559
|
-
const filePath =
|
|
3770
|
+
const filePath = join6(viewDir, name);
|
|
3560
3771
|
let isLink;
|
|
3561
3772
|
try {
|
|
3562
3773
|
isLink = lstatSync(filePath).isSymbolicLink();
|
|
@@ -3570,12 +3781,12 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
|
|
|
3570
3781
|
} catch {
|
|
3571
3782
|
return null;
|
|
3572
3783
|
}
|
|
3573
|
-
const resolved =
|
|
3784
|
+
const resolved = isAbsolute3(target) ? target : resolve7(viewDir, target);
|
|
3574
3785
|
try {
|
|
3575
3786
|
if (rosterRealpaths.has(realpathSync(resolved))) return null;
|
|
3576
3787
|
} catch {
|
|
3577
3788
|
}
|
|
3578
|
-
if (
|
|
3789
|
+
if (isAbsolute3(target)) return { target, kind: "absolute" };
|
|
3579
3790
|
let isDir = false;
|
|
3580
3791
|
try {
|
|
3581
3792
|
isDir = statSync(resolved).isDirectory();
|
|
@@ -3585,7 +3796,7 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
|
|
|
3585
3796
|
if (!isDir) {
|
|
3586
3797
|
return { target, kind: existsSync(resolved) ? "non-repo" : "broken" };
|
|
3587
3798
|
}
|
|
3588
|
-
return { target, kind: existsSync(
|
|
3799
|
+
return { target, kind: existsSync(join6(resolved, ".git")) ? "repo" : "non-repo" };
|
|
3589
3800
|
}
|
|
3590
3801
|
function gatherExistingViewLinks(viewDir, rosterRealpaths) {
|
|
3591
3802
|
let names;
|
|
@@ -3610,7 +3821,7 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
|
|
|
3610
3821
|
const pruned = [];
|
|
3611
3822
|
const failed = [];
|
|
3612
3823
|
for (const { name } of toPrune) {
|
|
3613
|
-
const filePath =
|
|
3824
|
+
const filePath = join6(viewDir, name);
|
|
3614
3825
|
const c = classifyViewLink(viewDir, name, rosterRealpaths);
|
|
3615
3826
|
if (c === null || c.kind !== "repo") {
|
|
3616
3827
|
failed.push({
|
|
@@ -3631,8 +3842,8 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
|
|
|
3631
3842
|
async function doRunProjectWorkspace(options, ctx) {
|
|
3632
3843
|
const cwd = ctx.cwd ?? process.cwd();
|
|
3633
3844
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "project workspace");
|
|
3634
|
-
const paths =
|
|
3635
|
-
const manifest = await
|
|
3845
|
+
const paths = basouPaths10(repositoryRoot);
|
|
3846
|
+
const manifest = await readManifest6(paths);
|
|
3636
3847
|
const viewPath = manifest.workspace.view;
|
|
3637
3848
|
const roster = manifest.repos ?? [];
|
|
3638
3849
|
let result;
|
|
@@ -3656,11 +3867,11 @@ async function doRunProjectWorkspace(options, ctx) {
|
|
|
3656
3867
|
} else {
|
|
3657
3868
|
const viewDir = resolveViewDir(repositoryRoot, viewPath);
|
|
3658
3869
|
const facts = roster.map((entry) => gatherViewRepo(repositoryRoot, viewDir, entry));
|
|
3659
|
-
const rosterNames = roster.map((entry) =>
|
|
3870
|
+
const rosterNames = roster.map((entry) => basename4(resolve7(repositoryRoot, entry.path)));
|
|
3660
3871
|
const rosterRealpaths = /* @__PURE__ */ new Set();
|
|
3661
3872
|
for (const entry of roster) {
|
|
3662
3873
|
try {
|
|
3663
|
-
rosterRealpaths.add(realpathSync(
|
|
3874
|
+
rosterRealpaths.add(realpathSync(resolve7(repositoryRoot, entry.path)));
|
|
3664
3875
|
} catch {
|
|
3665
3876
|
}
|
|
3666
3877
|
}
|
|
@@ -3821,10 +4032,10 @@ async function runProjectPreset(options, ctx = {}) {
|
|
|
3821
4032
|
}
|
|
3822
4033
|
}
|
|
3823
4034
|
function canonicalFileFor(anchorReal, canonicalName) {
|
|
3824
|
-
return
|
|
4035
|
+
return join6(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
3825
4036
|
}
|
|
3826
4037
|
function canonicalLabelFor(canonicalName) {
|
|
3827
|
-
return
|
|
4038
|
+
return join6("agents", canonicalName, CANONICAL_FILE);
|
|
3828
4039
|
}
|
|
3829
4040
|
async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
|
|
3830
4041
|
const declared = {
|
|
@@ -3835,17 +4046,17 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
|
|
|
3835
4046
|
};
|
|
3836
4047
|
let real;
|
|
3837
4048
|
try {
|
|
3838
|
-
real = realpathSync(
|
|
4049
|
+
real = realpathSync(resolve7(repositoryRoot, entry.path));
|
|
3839
4050
|
} catch {
|
|
3840
4051
|
return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
|
|
3841
4052
|
}
|
|
3842
4053
|
if (real === anchorReal) {
|
|
3843
4054
|
return { ...declared, isAnchor: true, reachable: true, canonicalPresent: false };
|
|
3844
4055
|
}
|
|
3845
|
-
if (!existsSync(
|
|
4056
|
+
if (!existsSync(join6(real, ".git"))) {
|
|
3846
4057
|
return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
|
|
3847
4058
|
}
|
|
3848
|
-
const canonicalName =
|
|
4059
|
+
const canonicalName = basename4(real);
|
|
3849
4060
|
let content;
|
|
3850
4061
|
try {
|
|
3851
4062
|
content = await readMarkdownFile4(canonicalFileFor(anchorReal, canonicalName));
|
|
@@ -3906,8 +4117,8 @@ function presetFailureReason(error) {
|
|
|
3906
4117
|
async function doRunProjectPreset(options, ctx) {
|
|
3907
4118
|
const cwd = ctx.cwd ?? process.cwd();
|
|
3908
4119
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "project preset");
|
|
3909
|
-
const paths =
|
|
3910
|
-
const manifest = await
|
|
4120
|
+
const paths = basouPaths10(repositoryRoot);
|
|
4121
|
+
const manifest = await readManifest6(paths);
|
|
3911
4122
|
const roster = manifest.repos ?? [];
|
|
3912
4123
|
const anchorReal = realpathSync(repositoryRoot);
|
|
3913
4124
|
const facts = [];
|
|
@@ -4064,33 +4275,33 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
|
|
|
4064
4275
|
};
|
|
4065
4276
|
let real;
|
|
4066
4277
|
try {
|
|
4067
|
-
real = realpathSync(
|
|
4278
|
+
real = realpathSync(resolve7(repositoryRoot, target));
|
|
4068
4279
|
} catch {
|
|
4069
4280
|
return empty;
|
|
4070
4281
|
}
|
|
4071
4282
|
const anchorReal = realpathSync(repositoryRoot);
|
|
4072
|
-
const canonicalName =
|
|
4283
|
+
const canonicalName = basename4(real);
|
|
4073
4284
|
const instructionFiles = [];
|
|
4074
4285
|
for (const name of INSTRUCTION_FILES) {
|
|
4075
4286
|
try {
|
|
4076
|
-
lstatSync(
|
|
4287
|
+
lstatSync(join6(real, name));
|
|
4077
4288
|
instructionFiles.push(name);
|
|
4078
4289
|
} catch {
|
|
4079
4290
|
}
|
|
4080
4291
|
}
|
|
4081
4292
|
let ignored;
|
|
4082
4293
|
try {
|
|
4083
|
-
ignored = new Set(readGitignoreLines(
|
|
4294
|
+
ignored = new Set(readGitignoreLines(join6(real, ".gitignore")).map((l) => l.trim()));
|
|
4084
4295
|
} catch {
|
|
4085
4296
|
ignored = /* @__PURE__ */ new Set();
|
|
4086
4297
|
}
|
|
4087
4298
|
const gitignorePatterns = INSTRUCTION_FILES.filter((p) => ignored.has(p) || ignored.has(`/${p}`));
|
|
4088
|
-
const canonical2 = existsSync(
|
|
4299
|
+
const canonical2 = existsSync(join6(anchorReal, "agents", canonicalName, CANONICAL_FILE));
|
|
4089
4300
|
let viewLink = false;
|
|
4090
4301
|
const viewPath = manifest.workspace.view;
|
|
4091
4302
|
if (viewPath !== void 0) {
|
|
4092
4303
|
try {
|
|
4093
|
-
lstatSync(
|
|
4304
|
+
lstatSync(join6(resolveViewDir(repositoryRoot, viewPath), canonicalName));
|
|
4094
4305
|
viewLink = true;
|
|
4095
4306
|
} catch {
|
|
4096
4307
|
}
|
|
@@ -4127,12 +4338,12 @@ function buildArchivedManifest(manifest, plan, updatedAt) {
|
|
|
4127
4338
|
async function doRunProjectArchive(target, options, ctx) {
|
|
4128
4339
|
const cwd = ctx.cwd ?? process.cwd();
|
|
4129
4340
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "project archive");
|
|
4130
|
-
const paths =
|
|
4131
|
-
const manifest = await
|
|
4341
|
+
const paths = basouPaths10(repositoryRoot);
|
|
4342
|
+
const manifest = await readManifest6(paths);
|
|
4132
4343
|
const roster = manifest.repos ?? [];
|
|
4133
4344
|
let targetIsAnchor = false;
|
|
4134
4345
|
try {
|
|
4135
|
-
targetIsAnchor = realpathSync(
|
|
4346
|
+
targetIsAnchor = realpathSync(resolve7(repositoryRoot, target)) === realpathSync(repositoryRoot);
|
|
4136
4347
|
} catch {
|
|
4137
4348
|
targetIsAnchor = false;
|
|
4138
4349
|
}
|
|
@@ -4217,7 +4428,7 @@ function renderProjectArchive(result) {
|
|
|
4217
4428
|
if (t.instructionFiles.length > 0) items.push(`\u6307\u793A\u66F8(${t.instructionFiles.join(", ")})`);
|
|
4218
4429
|
if (t.gitignorePatterns.length > 0)
|
|
4219
4430
|
items.push(`.gitignore \u306E\u6307\u793A\u66F8\u30D1\u30BF\u30FC\u30F3(${t.gitignorePatterns.join(", ")})`);
|
|
4220
|
-
if (t.canonical) items.push(`anchor \u306E canonical(agents/${
|
|
4431
|
+
if (t.canonical) items.push(`anchor \u306E canonical(agents/${basename4(result.target)}/AGENTS.md)`);
|
|
4221
4432
|
if (!t.inspected) {
|
|
4222
4433
|
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
4434
|
lines.push(
|
|
@@ -4252,12 +4463,12 @@ function gatherRenameWiring(repositoryRoot, manifest, oldBasename) {
|
|
|
4252
4463
|
} catch {
|
|
4253
4464
|
return { canonicalDirOld: false, viewLinkOld: false };
|
|
4254
4465
|
}
|
|
4255
|
-
const canonicalDirOld = existsSync(
|
|
4466
|
+
const canonicalDirOld = existsSync(join6(anchorReal, "agents", oldBasename));
|
|
4256
4467
|
let viewLinkOld = false;
|
|
4257
4468
|
const viewPath = manifest.workspace.view;
|
|
4258
4469
|
if (viewPath !== void 0) {
|
|
4259
4470
|
try {
|
|
4260
|
-
lstatSync(
|
|
4471
|
+
lstatSync(join6(resolveViewDir(repositoryRoot, viewPath), oldBasename));
|
|
4261
4472
|
viewLinkOld = true;
|
|
4262
4473
|
} catch {
|
|
4263
4474
|
}
|
|
@@ -4278,12 +4489,12 @@ function buildRenamedManifest(manifest, plan, updatedAt) {
|
|
|
4278
4489
|
async function doRunProjectRename(oldPath, newPath, options, ctx) {
|
|
4279
4490
|
const cwd = ctx.cwd ?? process.cwd();
|
|
4280
4491
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "project rename");
|
|
4281
|
-
const paths =
|
|
4282
|
-
const manifest = await
|
|
4492
|
+
const paths = basouPaths10(repositoryRoot);
|
|
4493
|
+
const manifest = await readManifest6(paths);
|
|
4283
4494
|
const roster = manifest.repos ?? [];
|
|
4284
4495
|
let oldIsAnchor = false;
|
|
4285
4496
|
try {
|
|
4286
|
-
oldIsAnchor = realpathSync(
|
|
4497
|
+
oldIsAnchor = realpathSync(resolve7(repositoryRoot, oldPath)) === realpathSync(repositoryRoot);
|
|
4287
4498
|
} catch {
|
|
4288
4499
|
oldIsAnchor = false;
|
|
4289
4500
|
}
|
|
@@ -4393,79 +4604,341 @@ function renderProjectRename(result) {
|
|
|
4393
4604
|
return lines.join("\n");
|
|
4394
4605
|
}
|
|
4395
4606
|
|
|
4396
|
-
// src/commands/
|
|
4397
|
-
import {
|
|
4398
|
-
import {
|
|
4607
|
+
// src/commands/protocol.ts
|
|
4608
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
4609
|
+
import {
|
|
4610
|
+
PROTOCOL_END,
|
|
4611
|
+
PROTOCOL_START,
|
|
4612
|
+
parseMarkers as parseMarkers2,
|
|
4613
|
+
readMarkdownFile as readMarkdownFile5,
|
|
4614
|
+
removeMarkerSection
|
|
4615
|
+
} from "@basou/core";
|
|
4399
4616
|
|
|
4400
|
-
// src/lib/
|
|
4401
|
-
import {
|
|
4402
|
-
import {
|
|
4403
|
-
import {
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4617
|
+
// src/lib/durable-write.ts
|
|
4618
|
+
import { randomUUID } from "crypto";
|
|
4619
|
+
import { lstat, open, rename, stat as stat3, unlink as unlink2 } from "fs/promises";
|
|
4620
|
+
import { basename as basename5, dirname as dirname2, join as join7 } from "path";
|
|
4621
|
+
async function assertNotSymlink(targetPath) {
|
|
4622
|
+
try {
|
|
4623
|
+
const st = await lstat(targetPath);
|
|
4624
|
+
if (st.isSymbolicLink()) {
|
|
4625
|
+
throw new Error(
|
|
4626
|
+
"Refusing to write through a symlink. Replace the symlinked target with a regular file (or remove it) and retry."
|
|
4627
|
+
);
|
|
4628
|
+
}
|
|
4629
|
+
} catch (error) {
|
|
4630
|
+
if (error instanceof Error && error.code === "ENOENT") return;
|
|
4631
|
+
throw error;
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4634
|
+
async function writeFileDurable(targetPath, content) {
|
|
4635
|
+
const dir = dirname2(targetPath);
|
|
4636
|
+
const tmpPath = join7(dir, `.${basename5(targetPath)}.tmp.${randomUUID()}`);
|
|
4637
|
+
let mode = 420;
|
|
4638
|
+
try {
|
|
4639
|
+
mode = (await stat3(targetPath)).mode & 511;
|
|
4640
|
+
} catch (error) {
|
|
4641
|
+
if (!(error instanceof Error && error.code === "ENOENT")) {
|
|
4642
|
+
throw error;
|
|
4643
|
+
}
|
|
4644
|
+
}
|
|
4645
|
+
let handle;
|
|
4646
|
+
try {
|
|
4647
|
+
handle = await open(tmpPath, "wx", mode);
|
|
4648
|
+
await handle.writeFile(content, "utf8");
|
|
4649
|
+
await handle.chmod(mode);
|
|
4650
|
+
await handle.sync();
|
|
4651
|
+
await handle.close();
|
|
4652
|
+
handle = void 0;
|
|
4653
|
+
await rename(tmpPath, targetPath);
|
|
4654
|
+
} catch (error) {
|
|
4655
|
+
if (handle) await handle.close().catch(() => void 0);
|
|
4656
|
+
await unlink2(tmpPath).catch(() => void 0);
|
|
4657
|
+
throw error;
|
|
4658
|
+
}
|
|
4659
|
+
try {
|
|
4660
|
+
const dirHandle = await open(dir, "r");
|
|
4661
|
+
try {
|
|
4662
|
+
await dirHandle.sync();
|
|
4663
|
+
} finally {
|
|
4664
|
+
await dirHandle.close();
|
|
4665
|
+
}
|
|
4666
|
+
} catch {
|
|
4667
|
+
}
|
|
4668
|
+
}
|
|
4669
|
+
|
|
4670
|
+
// src/lib/protocols-config.ts
|
|
4671
|
+
import { homedir as homedir6 } from "os";
|
|
4672
|
+
import { isAbsolute as isAbsolute4, join as join8, resolve as resolve8 } from "path";
|
|
4673
|
+
import { readYamlFile as readYamlFile5 } from "@basou/core";
|
|
4674
|
+
var DEFAULT_PROTOCOLS_CONFIG_PATH = join8(homedir6(), ".basou", "protocols.yaml");
|
|
4675
|
+
var DEFAULT_TARGET_PATH = join8(homedir6(), ".claude", "CLAUDE.md");
|
|
4676
|
+
var ALLOWED_TOP_KEYS = /* @__PURE__ */ new Set(["version", "protocols"]);
|
|
4677
|
+
var ALLOWED_ENTRY_KEYS = /* @__PURE__ */ new Set(["source", "title"]);
|
|
4678
|
+
function expandTilde3(p) {
|
|
4679
|
+
if (p === "~") return homedir6();
|
|
4680
|
+
if (p.startsWith("~/")) return join8(homedir6(), p.slice(2));
|
|
4408
4681
|
return p;
|
|
4409
4682
|
}
|
|
4410
|
-
function
|
|
4683
|
+
function isRecord3(value) {
|
|
4411
4684
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4412
4685
|
}
|
|
4413
|
-
async function
|
|
4686
|
+
async function loadProtocolsConfig(configPath = DEFAULT_PROTOCOLS_CONFIG_PATH) {
|
|
4414
4687
|
let raw;
|
|
4415
4688
|
try {
|
|
4416
|
-
raw = await
|
|
4689
|
+
raw = await readYamlFile5(configPath);
|
|
4417
4690
|
} catch (error) {
|
|
4418
4691
|
if (error instanceof Error && error.message === "YAML file not found") {
|
|
4419
4692
|
throw new Error(
|
|
4420
|
-
"No
|
|
4693
|
+
"No protocols config at ~/.basou/protocols.yaml. Create one (a 'protocols:' list of source markdown paths) before running 'basou protocol sync'."
|
|
4421
4694
|
);
|
|
4422
4695
|
}
|
|
4423
4696
|
if (error instanceof Error && error.message === "Failed to parse YAML content") {
|
|
4424
|
-
throw new Error("~/.basou/
|
|
4697
|
+
throw new Error("~/.basou/protocols.yaml is not valid YAML.");
|
|
4425
4698
|
}
|
|
4426
4699
|
throw error;
|
|
4427
4700
|
}
|
|
4428
|
-
if (!
|
|
4429
|
-
throw new Error("~/.basou/
|
|
4701
|
+
if (!isRecord3(raw) || !Array.isArray(raw.protocols)) {
|
|
4702
|
+
throw new Error("~/.basou/protocols.yaml must contain a 'protocols:' list.");
|
|
4703
|
+
}
|
|
4704
|
+
for (const key of Object.keys(raw)) {
|
|
4705
|
+
if (!ALLOWED_TOP_KEYS.has(key)) {
|
|
4706
|
+
throw new Error(
|
|
4707
|
+
`~/.basou/protocols.yaml has an unknown key '${key}' (allowed: version, protocols).`
|
|
4708
|
+
);
|
|
4709
|
+
}
|
|
4430
4710
|
}
|
|
4431
4711
|
const seen = /* @__PURE__ */ new Set();
|
|
4432
4712
|
const result = [];
|
|
4433
|
-
for (const entry of raw.
|
|
4434
|
-
if (!
|
|
4435
|
-
throw new Error("Each
|
|
4713
|
+
for (const entry of raw.protocols) {
|
|
4714
|
+
if (!isRecord3(entry)) {
|
|
4715
|
+
throw new Error("Each protocol entry must be a mapping with a 'source' key.");
|
|
4436
4716
|
}
|
|
4437
|
-
|
|
4438
|
-
|
|
4717
|
+
for (const key of Object.keys(entry)) {
|
|
4718
|
+
if (!ALLOWED_ENTRY_KEYS.has(key)) {
|
|
4719
|
+
throw new Error(`A protocol entry has an unknown key '${key}' (allowed: source, title).`);
|
|
4720
|
+
}
|
|
4439
4721
|
}
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
);
|
|
4722
|
+
if (typeof entry.source !== "string" || entry.source.trim().length === 0) {
|
|
4723
|
+
throw new Error("Each protocol entry needs a non-empty string 'source'.");
|
|
4724
|
+
}
|
|
4725
|
+
if (entry.title !== void 0 && (typeof entry.title !== "string" || entry.title.trim().length === 0)) {
|
|
4726
|
+
throw new Error("A protocol entry 'title' must be a non-empty string when present.");
|
|
4727
|
+
}
|
|
4728
|
+
const expanded = expandTilde3(entry.source.trim());
|
|
4729
|
+
if (!isAbsolute4(expanded)) {
|
|
4730
|
+
throw new Error("Protocol 'source' paths must be absolute (or start with '~').");
|
|
4731
|
+
}
|
|
4732
|
+
const abs = resolve8(expanded);
|
|
4733
|
+
if (seen.has(abs)) {
|
|
4734
|
+
throw new Error("Duplicate protocol source (each source path may appear only once).");
|
|
4445
4735
|
}
|
|
4446
|
-
const abs = resolve5(expanded);
|
|
4447
|
-
if (seen.has(abs)) continue;
|
|
4448
4736
|
seen.add(abs);
|
|
4449
|
-
result.push(
|
|
4737
|
+
result.push(
|
|
4738
|
+
entry.title !== void 0 ? { source: abs, title: entry.title.trim() } : { source: abs }
|
|
4739
|
+
);
|
|
4450
4740
|
}
|
|
4451
4741
|
if (result.length === 0) {
|
|
4452
|
-
throw new Error("~/.basou/
|
|
4742
|
+
throw new Error("~/.basou/protocols.yaml has no protocols.");
|
|
4453
4743
|
}
|
|
4454
4744
|
return result;
|
|
4455
4745
|
}
|
|
4456
4746
|
|
|
4747
|
+
// src/commands/protocol.ts
|
|
4748
|
+
var PROTOCOL_MARKERS = { start: PROTOCOL_START, end: PROTOCOL_END };
|
|
4749
|
+
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. -->";
|
|
4750
|
+
function registerProtocolCommand(program) {
|
|
4751
|
+
const protocol = program.command("protocol").description("Manage the basou-managed standing-protocol block in the global CLAUDE.md");
|
|
4752
|
+
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) => {
|
|
4753
|
+
await runProtocolSync(opts);
|
|
4754
|
+
});
|
|
4755
|
+
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) => {
|
|
4756
|
+
await runProtocolList(opts);
|
|
4757
|
+
});
|
|
4758
|
+
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) => {
|
|
4759
|
+
await runProtocolUnsync(opts);
|
|
4760
|
+
});
|
|
4761
|
+
}
|
|
4762
|
+
async function runProtocolSync(options) {
|
|
4763
|
+
try {
|
|
4764
|
+
await doRunProtocolSync(options);
|
|
4765
|
+
} catch (error) {
|
|
4766
|
+
renderCliError(error, { verbose: isVerbose(options) });
|
|
4767
|
+
process.exitCode = 1;
|
|
4768
|
+
}
|
|
4769
|
+
}
|
|
4770
|
+
async function runProtocolList(options) {
|
|
4771
|
+
try {
|
|
4772
|
+
await doRunProtocolList(options);
|
|
4773
|
+
} catch (error) {
|
|
4774
|
+
renderCliError(error, { verbose: isVerbose(options) });
|
|
4775
|
+
process.exitCode = 1;
|
|
4776
|
+
}
|
|
4777
|
+
}
|
|
4778
|
+
async function runProtocolUnsync(options) {
|
|
4779
|
+
try {
|
|
4780
|
+
await doRunProtocolUnsync(options);
|
|
4781
|
+
} catch (error) {
|
|
4782
|
+
renderCliError(error, { verbose: isVerbose(options) });
|
|
4783
|
+
process.exitCode = 1;
|
|
4784
|
+
}
|
|
4785
|
+
}
|
|
4786
|
+
async function readProtocolSources(entries) {
|
|
4787
|
+
const out = [];
|
|
4788
|
+
for (const entry of entries) {
|
|
4789
|
+
let content;
|
|
4790
|
+
try {
|
|
4791
|
+
content = await readFile3(entry.source, "utf8");
|
|
4792
|
+
} catch (error) {
|
|
4793
|
+
if (error instanceof Error && error.code === "ENOENT") {
|
|
4794
|
+
throw new Error(
|
|
4795
|
+
"A protocol source file does not exist. Check the 'source' paths in ~/.basou/protocols.yaml.",
|
|
4796
|
+
{ cause: error }
|
|
4797
|
+
);
|
|
4798
|
+
}
|
|
4799
|
+
throw new Error("Failed to read a protocol source file.", { cause: error });
|
|
4800
|
+
}
|
|
4801
|
+
for (const line of content.split(/\r?\n/)) {
|
|
4802
|
+
if (line === PROTOCOL_START || line === PROTOCOL_END) {
|
|
4803
|
+
throw new Error(
|
|
4804
|
+
"A protocol source contains a BASOU:PROTOCOLS marker line, which would corrupt the managed block. Remove that line from the source."
|
|
4805
|
+
);
|
|
4806
|
+
}
|
|
4807
|
+
}
|
|
4808
|
+
out.push({ entry, content });
|
|
4809
|
+
}
|
|
4810
|
+
return out;
|
|
4811
|
+
}
|
|
4812
|
+
function buildBlock(sources) {
|
|
4813
|
+
const sections = sources.map(({ entry, content }) => {
|
|
4814
|
+
const body = content.replace(/\s+$/, "");
|
|
4815
|
+
return entry.title !== void 0 ? `## ${entry.title}
|
|
4816
|
+
|
|
4817
|
+
${body}` : body;
|
|
4818
|
+
});
|
|
4819
|
+
return `${MANAGED_NOTE}
|
|
4820
|
+
|
|
4821
|
+
${sections.join("\n\n")}
|
|
4822
|
+
`;
|
|
4823
|
+
}
|
|
4824
|
+
function buildTargetBody(existing, block) {
|
|
4825
|
+
const wrapped = `${PROTOCOL_START}
|
|
4826
|
+
${block}${PROTOCOL_END}
|
|
4827
|
+
`;
|
|
4828
|
+
if (existing === null || existing === "") return wrapped;
|
|
4829
|
+
const section = parseMarkers2(existing, PROTOCOL_MARKERS);
|
|
4830
|
+
switch (section.kind) {
|
|
4831
|
+
case "ok":
|
|
4832
|
+
return `${section.before}${PROTOCOL_START}
|
|
4833
|
+
${block}${PROTOCOL_END}${section.after}`;
|
|
4834
|
+
case "no_markers": {
|
|
4835
|
+
const sep = existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
4836
|
+
return `${existing}${sep}${wrapped}`;
|
|
4837
|
+
}
|
|
4838
|
+
default:
|
|
4839
|
+
throw new Error(
|
|
4840
|
+
"The BASOU:PROTOCOLS markers in the target are malformed (a marker is missing, duplicated, or out of order). Fix or remove them, then retry."
|
|
4841
|
+
);
|
|
4842
|
+
}
|
|
4843
|
+
}
|
|
4844
|
+
async function backupOnce(target, existing) {
|
|
4845
|
+
if (existing === null) return;
|
|
4846
|
+
const bak = `${target}.basou-bak`;
|
|
4847
|
+
const already = await readMarkdownFile5(bak);
|
|
4848
|
+
if (already !== null) return;
|
|
4849
|
+
await writeFileDurable(bak, existing);
|
|
4850
|
+
}
|
|
4851
|
+
async function doRunProtocolSync(options) {
|
|
4852
|
+
const configPath = options.config ?? DEFAULT_PROTOCOLS_CONFIG_PATH;
|
|
4853
|
+
const target = options.target ?? DEFAULT_TARGET_PATH;
|
|
4854
|
+
const entries = await loadProtocolsConfig(configPath);
|
|
4855
|
+
const sources = await readProtocolSources(entries);
|
|
4856
|
+
const block = buildBlock(sources);
|
|
4857
|
+
await assertNotSymlink(target);
|
|
4858
|
+
const existing = await readMarkdownFile5(target);
|
|
4859
|
+
const newBody = buildTargetBody(existing, block);
|
|
4860
|
+
if (newBody === existing) {
|
|
4861
|
+
console.log(`The basou:protocols block is already up to date (${entries.length} protocol(s)).`);
|
|
4862
|
+
return;
|
|
4863
|
+
}
|
|
4864
|
+
const hadBlock = existing !== null && parseMarkers2(existing, PROTOCOL_MARKERS).kind === "ok";
|
|
4865
|
+
if (options.dryRun === true) {
|
|
4866
|
+
console.log(
|
|
4867
|
+
`[dry-run] Would ${hadBlock ? "update" : "install"} the basou:protocols block (${entries.length} protocol(s)).`
|
|
4868
|
+
);
|
|
4869
|
+
for (const { entry } of sources) {
|
|
4870
|
+
console.log(` - ${entry.title ?? entry.source}`);
|
|
4871
|
+
}
|
|
4872
|
+
return;
|
|
4873
|
+
}
|
|
4874
|
+
const recheck = await readMarkdownFile5(target);
|
|
4875
|
+
if (recheck !== existing) {
|
|
4876
|
+
throw new Error(
|
|
4877
|
+
"The target changed during sync; aborting so a concurrent edit is not overwritten. Re-run 'basou protocol sync'."
|
|
4878
|
+
);
|
|
4879
|
+
}
|
|
4880
|
+
await backupOnce(target, existing);
|
|
4881
|
+
await writeFileDurable(target, newBody);
|
|
4882
|
+
console.log(
|
|
4883
|
+
`${hadBlock ? "Updated" : "Installed"} the basou:protocols block in the global CLAUDE.md (${entries.length} protocol(s)).`
|
|
4884
|
+
);
|
|
4885
|
+
}
|
|
4886
|
+
async function doRunProtocolList(options) {
|
|
4887
|
+
const configPath = options.config ?? DEFAULT_PROTOCOLS_CONFIG_PATH;
|
|
4888
|
+
const target = options.target ?? DEFAULT_TARGET_PATH;
|
|
4889
|
+
const entries = await loadProtocolsConfig(configPath);
|
|
4890
|
+
const existing = await readMarkdownFile5(target);
|
|
4891
|
+
const installed = existing !== null && parseMarkers2(existing, PROTOCOL_MARKERS).kind === "ok";
|
|
4892
|
+
console.log(`Declared protocols (${entries.length}):`);
|
|
4893
|
+
for (const entry of entries) {
|
|
4894
|
+
console.log(` - ${entry.title ?? entry.source}`);
|
|
4895
|
+
}
|
|
4896
|
+
console.log(installed ? "Block: installed in the global CLAUDE.md." : "Block: not installed.");
|
|
4897
|
+
}
|
|
4898
|
+
async function doRunProtocolUnsync(options) {
|
|
4899
|
+
const target = options.target ?? DEFAULT_TARGET_PATH;
|
|
4900
|
+
await assertNotSymlink(target);
|
|
4901
|
+
const existing = await readMarkdownFile5(target);
|
|
4902
|
+
if (existing === null) {
|
|
4903
|
+
console.log("No target file; nothing to remove.");
|
|
4904
|
+
return;
|
|
4905
|
+
}
|
|
4906
|
+
const newBody = removeMarkerSection(existing, "CLAUDE.md", PROTOCOL_MARKERS);
|
|
4907
|
+
if (newBody === existing) {
|
|
4908
|
+
console.log("No basou:protocols block found; nothing removed.");
|
|
4909
|
+
return;
|
|
4910
|
+
}
|
|
4911
|
+
if (options.dryRun === true) {
|
|
4912
|
+
console.log("[dry-run] Would remove the basou:protocols block from the global CLAUDE.md.");
|
|
4913
|
+
return;
|
|
4914
|
+
}
|
|
4915
|
+
const recheck = await readMarkdownFile5(target);
|
|
4916
|
+
if (recheck !== existing) {
|
|
4917
|
+
throw new Error(
|
|
4918
|
+
"The target changed during unsync; aborting so a concurrent edit is not overwritten. Re-run 'basou protocol unsync'."
|
|
4919
|
+
);
|
|
4920
|
+
}
|
|
4921
|
+
await backupOnce(target, existing);
|
|
4922
|
+
await writeFileDurable(target, newBody);
|
|
4923
|
+
console.log("Removed the basou:protocols block from the global CLAUDE.md.");
|
|
4924
|
+
}
|
|
4925
|
+
|
|
4926
|
+
// src/commands/refresh.ts
|
|
4927
|
+
import { assertBasouRootSafe as assertBasouRootSafe9, basouPaths as basouPaths11, findErrorCode as findErrorCode9 } from "@basou/core";
|
|
4928
|
+
import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
|
|
4929
|
+
|
|
4457
4930
|
// src/commands/refresh-watch.ts
|
|
4458
|
-
import { readdir as readdir2, stat as
|
|
4459
|
-
import { homedir as
|
|
4460
|
-
import { join as
|
|
4931
|
+
import { readdir as readdir2, stat as stat4 } from "fs/promises";
|
|
4932
|
+
import { homedir as homedir7 } from "os";
|
|
4933
|
+
import { join as join9 } from "path";
|
|
4461
4934
|
import { findErrorCode as findErrorCode8 } from "@basou/core";
|
|
4462
4935
|
var DEFAULT_WATCH_INTERVAL_SEC = 30;
|
|
4463
4936
|
var MIN_WATCH_INTERVAL_SEC = 5;
|
|
4464
4937
|
var MAX_WATCH_INTERVAL_SEC = 86400;
|
|
4465
4938
|
function watchedRoots(ctx) {
|
|
4466
4939
|
return [
|
|
4467
|
-
ctx.codexSessionsDir ??
|
|
4468
|
-
ctx.claudeProjectsDir ??
|
|
4940
|
+
ctx.codexSessionsDir ?? join9(homedir7(), ".codex", "sessions"),
|
|
4941
|
+
ctx.claudeProjectsDir ?? join9(homedir7(), ".claude", "projects")
|
|
4469
4942
|
];
|
|
4470
4943
|
}
|
|
4471
4944
|
async function scanSourceLogs(roots) {
|
|
@@ -4479,12 +4952,12 @@ async function scanSourceLogs(roots) {
|
|
|
4479
4952
|
throw new Error("Failed to read a source log directory", { cause: error });
|
|
4480
4953
|
}
|
|
4481
4954
|
for (const entry of entries) {
|
|
4482
|
-
const full =
|
|
4955
|
+
const full = join9(dir, entry.name);
|
|
4483
4956
|
if (entry.isDirectory()) {
|
|
4484
4957
|
await walk(full);
|
|
4485
4958
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
4486
4959
|
try {
|
|
4487
|
-
const info = await
|
|
4960
|
+
const info = await stat4(full);
|
|
4488
4961
|
out.set(full, { mtimeMs: info.mtimeMs, size: info.size });
|
|
4489
4962
|
} catch (error) {
|
|
4490
4963
|
if (findErrorCode8(error, "ENOENT")) continue;
|
|
@@ -4586,19 +5059,19 @@ function parseInterval(value) {
|
|
|
4586
5059
|
return seconds;
|
|
4587
5060
|
}
|
|
4588
5061
|
function abortableSleep(ms, signal) {
|
|
4589
|
-
return new Promise((
|
|
5062
|
+
return new Promise((resolve12) => {
|
|
4590
5063
|
if (signal.aborted) {
|
|
4591
|
-
|
|
5064
|
+
resolve12();
|
|
4592
5065
|
return;
|
|
4593
5066
|
}
|
|
4594
5067
|
let timer;
|
|
4595
5068
|
const onAbort = () => {
|
|
4596
5069
|
clearTimeout(timer);
|
|
4597
|
-
|
|
5070
|
+
resolve12();
|
|
4598
5071
|
};
|
|
4599
5072
|
timer = setTimeout(() => {
|
|
4600
5073
|
signal.removeEventListener("abort", onAbort);
|
|
4601
|
-
|
|
5074
|
+
resolve12();
|
|
4602
5075
|
}, ms);
|
|
4603
5076
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
4604
5077
|
});
|
|
@@ -4689,7 +5162,7 @@ async function doRunRefreshWatch(options, ctx) {
|
|
|
4689
5162
|
if (options.force === true) throw new Error("--watch cannot be combined with --force.");
|
|
4690
5163
|
const cwd = ctx.cwd ?? process.cwd();
|
|
4691
5164
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "refresh");
|
|
4692
|
-
const paths =
|
|
5165
|
+
const paths = basouPaths11(repositoryRoot);
|
|
4693
5166
|
await assertWorkspaceInitialized8(paths.root);
|
|
4694
5167
|
const intervalMs = (options.interval ?? DEFAULT_WATCH_INTERVAL_SEC) * 1e3;
|
|
4695
5168
|
const controller = new AbortController();
|
|
@@ -4717,7 +5190,7 @@ async function doRunRefreshWatch(options, ctx) {
|
|
|
4717
5190
|
async function computeRefresh(options, ctx) {
|
|
4718
5191
|
const cwd = ctx.cwd ?? process.cwd();
|
|
4719
5192
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "refresh");
|
|
4720
|
-
const paths =
|
|
5193
|
+
const paths = basouPaths11(repositoryRoot);
|
|
4721
5194
|
await assertWorkspaceInitialized8(paths.root);
|
|
4722
5195
|
const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
4723
5196
|
return refreshAll({
|
|
@@ -4797,10 +5270,10 @@ async function assertWorkspaceInitialized8(basouRoot) {
|
|
|
4797
5270
|
}
|
|
4798
5271
|
|
|
4799
5272
|
// src/commands/report.ts
|
|
4800
|
-
import { isAbsolute as
|
|
5273
|
+
import { isAbsolute as isAbsolute5, resolve as resolve9 } from "path";
|
|
4801
5274
|
import {
|
|
4802
5275
|
assertBasouRootSafe as assertBasouRootSafe10,
|
|
4803
|
-
basouPaths as
|
|
5276
|
+
basouPaths as basouPaths12,
|
|
4804
5277
|
findErrorCode as findErrorCode10,
|
|
4805
5278
|
renderReport,
|
|
4806
5279
|
resolveRepositoryRoot as resolveRepositoryRoot8,
|
|
@@ -4825,7 +5298,7 @@ async function runReportGenerate(options, ctx = {}) {
|
|
|
4825
5298
|
async function doRunReportGenerate(options, ctx) {
|
|
4826
5299
|
const cwd = ctx.cwd ?? process.cwd();
|
|
4827
5300
|
const repositoryRoot = await resolveRepositoryRootForReport(cwd);
|
|
4828
|
-
const paths =
|
|
5301
|
+
const paths = basouPaths12(repositoryRoot);
|
|
4829
5302
|
await assertWorkspaceInitialized9(paths.root);
|
|
4830
5303
|
const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
4831
5304
|
const result = await renderReport({
|
|
@@ -4837,7 +5310,7 @@ async function doRunReportGenerate(options, ctx) {
|
|
|
4837
5310
|
onTaskSkip: (taskId, reason) => printTaskSkip(taskId, reason)
|
|
4838
5311
|
});
|
|
4839
5312
|
if (options.out !== void 0) {
|
|
4840
|
-
const outPath =
|
|
5313
|
+
const outPath = isAbsolute5(options.out) ? options.out : resolve9(cwd, options.out);
|
|
4841
5314
|
await writeMarkdownFile6(outPath, result.body);
|
|
4842
5315
|
const { sessions, decisions, tasks } = result.data;
|
|
4843
5316
|
console.error(
|
|
@@ -4876,7 +5349,7 @@ async function assertWorkspaceInitialized9(basouRoot) {
|
|
|
4876
5349
|
|
|
4877
5350
|
// src/commands/review-gaps.ts
|
|
4878
5351
|
import {
|
|
4879
|
-
basouPaths as
|
|
5352
|
+
basouPaths as basouPaths13,
|
|
4880
5353
|
findReviewGaps
|
|
4881
5354
|
} from "@basou/core";
|
|
4882
5355
|
import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
|
|
@@ -4917,7 +5390,7 @@ async function runReviewGaps(options, ctx = {}) {
|
|
|
4917
5390
|
async function doRunReviewGaps(options, ctx) {
|
|
4918
5391
|
const cwd = ctx.cwd ?? process.cwd();
|
|
4919
5392
|
const repositoryRoot = await resolveBasouRootForCommand(cwd, "review-gaps");
|
|
4920
|
-
const paths =
|
|
5393
|
+
const paths = basouPaths13(repositoryRoot);
|
|
4921
5394
|
const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
4922
5395
|
const summary = await findReviewGaps({
|
|
4923
5396
|
paths,
|
|
@@ -5000,12 +5473,12 @@ function renderReviewGaps(summary) {
|
|
|
5000
5473
|
|
|
5001
5474
|
// src/commands/run.ts
|
|
5002
5475
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
5003
|
-
import { homedir as
|
|
5004
|
-
import { join as
|
|
5476
|
+
import { homedir as homedir8 } from "os";
|
|
5477
|
+
import { join as join10 } from "path";
|
|
5005
5478
|
import {
|
|
5006
5479
|
acquireLock as acquireLock5,
|
|
5007
5480
|
assertBasouRootSafe as assertBasouRootSafe11,
|
|
5008
|
-
basouPaths as
|
|
5481
|
+
basouPaths as basouPaths14,
|
|
5009
5482
|
ChildProcessRunner as ChildProcessRunner2,
|
|
5010
5483
|
claudeCodeAdapterMetadata,
|
|
5011
5484
|
appendChainedEvent as coreAppendChainedEvent2,
|
|
@@ -5014,8 +5487,8 @@ import {
|
|
|
5014
5487
|
getSnapshot as getSnapshot2,
|
|
5015
5488
|
overwriteYamlFile as overwriteYamlFile2,
|
|
5016
5489
|
prefixedUlid as prefixedUlid4,
|
|
5017
|
-
readManifest as
|
|
5018
|
-
readYamlFile as
|
|
5490
|
+
readManifest as readManifest7,
|
|
5491
|
+
readYamlFile as readYamlFile6,
|
|
5019
5492
|
resolveClaudeCodeCommand,
|
|
5020
5493
|
resolveRepositoryRoot as resolveRepositoryRoot9,
|
|
5021
5494
|
SessionSchema as SessionSchema2,
|
|
@@ -5050,17 +5523,17 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
5050
5523
|
const { command } = await resolveCommand();
|
|
5051
5524
|
const cwd = options.cwd ?? process.cwd();
|
|
5052
5525
|
const repoRoot = await resolveRepositoryRootForRun(cwd);
|
|
5053
|
-
const paths =
|
|
5526
|
+
const paths = basouPaths14(repoRoot);
|
|
5054
5527
|
await assertBasouRootSafe11(paths.root);
|
|
5055
|
-
const manifest = await
|
|
5528
|
+
const manifest = await readManifest7(paths);
|
|
5056
5529
|
const sessionId = prefixedUlid4("ses");
|
|
5057
|
-
const sessionDir =
|
|
5530
|
+
const sessionDir = join10(paths.sessions, sessionId);
|
|
5058
5531
|
await mkdir2(sessionDir, { recursive: true });
|
|
5059
5532
|
const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
|
|
5060
5533
|
await coreAppendChainedEvent2(paths, sessionId, event);
|
|
5061
5534
|
});
|
|
5062
5535
|
const startedAt = now().toISOString();
|
|
5063
|
-
const sessionYamlPath =
|
|
5536
|
+
const sessionYamlPath = join10(sessionDir, "session.yaml");
|
|
5064
5537
|
const session = buildInitialSession2({
|
|
5065
5538
|
id: sessionId,
|
|
5066
5539
|
command,
|
|
@@ -5186,7 +5659,7 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
5186
5659
|
const rawRelated = computeRelatedFiles(preSnapshot, postSnapshot, diff);
|
|
5187
5660
|
const relatedFiles = sanitizeRelatedFiles(rawRelated, {
|
|
5188
5661
|
workingDirectory: repoRoot,
|
|
5189
|
-
homedir:
|
|
5662
|
+
homedir: homedir8()
|
|
5190
5663
|
}).sanitized;
|
|
5191
5664
|
const finalStatus = decideFinalStatus2(result, signalReceived);
|
|
5192
5665
|
await appendEvent(sessionDir, {
|
|
@@ -5330,7 +5803,7 @@ function buildInitialSession2(input) {
|
|
|
5330
5803
|
source: { ...claudeCodeAdapterMetadata },
|
|
5331
5804
|
started_at: input.startedAt,
|
|
5332
5805
|
status: "initialized",
|
|
5333
|
-
working_directory: sanitizeWorkingDirectory2(input.cwd, { homedir:
|
|
5806
|
+
working_directory: sanitizeWorkingDirectory2(input.cwd, { homedir: homedir8() }),
|
|
5334
5807
|
invocation: {
|
|
5335
5808
|
command: input.command,
|
|
5336
5809
|
args: [...input.args],
|
|
@@ -5342,7 +5815,7 @@ function buildInitialSession2(input) {
|
|
|
5342
5815
|
};
|
|
5343
5816
|
}
|
|
5344
5817
|
async function mutateSessionYaml2(filePath, mutator) {
|
|
5345
|
-
const raw = await
|
|
5818
|
+
const raw = await readYamlFile6(filePath);
|
|
5346
5819
|
const parsed = SessionSchema2.parse(raw);
|
|
5347
5820
|
mutator(parsed);
|
|
5348
5821
|
const validated = SessionSchema2.parse(parsed);
|
|
@@ -5402,20 +5875,20 @@ async function resolveRepositoryRootForRun(cwd) {
|
|
|
5402
5875
|
}
|
|
5403
5876
|
|
|
5404
5877
|
// src/commands/session.ts
|
|
5405
|
-
import { readFile as
|
|
5406
|
-
import { basename as
|
|
5878
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
5879
|
+
import { basename as basename6, isAbsolute as isAbsolute6, join as join11, relative as relative3 } from "path";
|
|
5407
5880
|
import {
|
|
5408
5881
|
acquireLock as acquireLock6,
|
|
5409
5882
|
appendEventToExistingSession as appendEventToExistingSession3,
|
|
5410
5883
|
assertBasouRootSafe as assertBasouRootSafe12,
|
|
5411
|
-
basouPaths as
|
|
5884
|
+
basouPaths as basouPaths15,
|
|
5412
5885
|
enumerateSessionDirs as enumerateSessionDirs2,
|
|
5413
5886
|
findErrorCode as findErrorCode11,
|
|
5414
5887
|
importSessionFromJson as importSessionFromJson2,
|
|
5415
5888
|
loadSessionEntries,
|
|
5416
5889
|
readAllEvents,
|
|
5417
|
-
readManifest as
|
|
5418
|
-
readYamlFile as
|
|
5890
|
+
readManifest as readManifest8,
|
|
5891
|
+
readYamlFile as readYamlFile7,
|
|
5419
5892
|
rechainSessionInPlace,
|
|
5420
5893
|
resolveSessionId as resolveSessionId3,
|
|
5421
5894
|
resolveTaskId,
|
|
@@ -5473,7 +5946,7 @@ async function runSessionList(options, ctx = {}) {
|
|
|
5473
5946
|
async function doRunSessionList(options, ctx) {
|
|
5474
5947
|
const cwd = ctx.cwd ?? process.cwd();
|
|
5475
5948
|
const repositoryRoot = await resolveRepositoryRootForSession(cwd, "list");
|
|
5476
|
-
const paths =
|
|
5949
|
+
const paths = basouPaths15(repositoryRoot);
|
|
5477
5950
|
await assertWorkspaceInitialized10(paths.root);
|
|
5478
5951
|
const now = /* @__PURE__ */ new Date();
|
|
5479
5952
|
const records = (await loadSessionEntries(paths, {
|
|
@@ -5525,14 +5998,14 @@ async function runSessionShow(idInput, options, ctx = {}) {
|
|
|
5525
5998
|
async function doRunSessionShow(idInput, options, ctx) {
|
|
5526
5999
|
const cwd = ctx.cwd ?? process.cwd();
|
|
5527
6000
|
const repositoryRoot = await resolveRepositoryRootForSession(cwd, "show");
|
|
5528
|
-
const paths =
|
|
6001
|
+
const paths = basouPaths15(repositoryRoot);
|
|
5529
6002
|
await assertWorkspaceInitialized10(paths.root);
|
|
5530
6003
|
const sessionId = await resolveSessionId3(paths, idInput);
|
|
5531
|
-
const sessionDir =
|
|
5532
|
-
const sessionYamlPath =
|
|
6004
|
+
const sessionDir = join11(paths.sessions, sessionId);
|
|
6005
|
+
const sessionYamlPath = join11(sessionDir, "session.yaml");
|
|
5533
6006
|
let session;
|
|
5534
6007
|
try {
|
|
5535
|
-
const raw = await
|
|
6008
|
+
const raw = await readYamlFile7(sessionYamlPath);
|
|
5536
6009
|
session = SessionSchema3.parse(raw);
|
|
5537
6010
|
} catch (error) {
|
|
5538
6011
|
if (findErrorCode11(error, "ENOENT")) {
|
|
@@ -5650,7 +6123,7 @@ function formatSessionWork(session, events, now) {
|
|
|
5650
6123
|
}
|
|
5651
6124
|
function formatWorkingDir(workingDir, repositoryRoot, options) {
|
|
5652
6125
|
if (options.fullPath === true) return workingDir;
|
|
5653
|
-
if (!
|
|
6126
|
+
if (!isAbsolute6(workingDir)) {
|
|
5654
6127
|
if (workingDir === ".") return "<repository_root>";
|
|
5655
6128
|
return workingDir;
|
|
5656
6129
|
}
|
|
@@ -5813,9 +6286,9 @@ async function runSessionImport(options, ctx = {}) {
|
|
|
5813
6286
|
async function doRunSessionImport(options, ctx) {
|
|
5814
6287
|
const cwd = ctx.cwd ?? process.cwd();
|
|
5815
6288
|
const repositoryRoot = await resolveRepositoryRootForSession(cwd, "import");
|
|
5816
|
-
const paths =
|
|
6289
|
+
const paths = basouPaths15(repositoryRoot);
|
|
5817
6290
|
await assertWorkspaceInitialized10(paths.root);
|
|
5818
|
-
const manifest = await
|
|
6291
|
+
const manifest = await readManifest8(paths);
|
|
5819
6292
|
const rawBody = await readInputFile(options.from);
|
|
5820
6293
|
const json = parseJsonStrict(rawBody);
|
|
5821
6294
|
const parsed = SessionImportPayloadSchema2.safeParse(json);
|
|
@@ -5842,7 +6315,7 @@ async function doRunSessionImport(options, ctx) {
|
|
|
5842
6315
|
}
|
|
5843
6316
|
async function readInputFile(path) {
|
|
5844
6317
|
try {
|
|
5845
|
-
return await
|
|
6318
|
+
return await readFile4(path, "utf8");
|
|
5846
6319
|
} catch (error) {
|
|
5847
6320
|
if (findErrorCode11(error, "ENOENT")) {
|
|
5848
6321
|
throw new Error("Import source not found", { cause: error });
|
|
@@ -5900,7 +6373,7 @@ function printSessionImportResult(options, result) {
|
|
|
5900
6373
|
return;
|
|
5901
6374
|
}
|
|
5902
6375
|
console.log(
|
|
5903
|
-
`Imported session ${sid} (${result.eventCount} events) from ${
|
|
6376
|
+
`Imported session ${sid} (${result.eventCount} events) from ${basename6(options.from)}`
|
|
5904
6377
|
);
|
|
5905
6378
|
}
|
|
5906
6379
|
var NOTE_BODY_PREVIEW_LIMIT = 80;
|
|
@@ -5927,7 +6400,7 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
|
|
|
5927
6400
|
}
|
|
5928
6401
|
const cwd = ctx.cwd ?? process.cwd();
|
|
5929
6402
|
const repositoryRoot = await resolveRepositoryRootForSession(cwd, "note");
|
|
5930
|
-
const paths =
|
|
6403
|
+
const paths = basouPaths15(repositoryRoot);
|
|
5931
6404
|
await assertWorkspaceInitialized10(paths.root);
|
|
5932
6405
|
const sessionId = await resolveSessionId3(paths, sessionIdInput);
|
|
5933
6406
|
const body = hasBody ? options.body : await readNoteFile(options.fromFile);
|
|
@@ -5959,7 +6432,7 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
|
|
|
5959
6432
|
}
|
|
5960
6433
|
async function readNoteFile(path) {
|
|
5961
6434
|
try {
|
|
5962
|
-
return await
|
|
6435
|
+
return await readFile4(path, "utf8");
|
|
5963
6436
|
} catch (error) {
|
|
5964
6437
|
if (findErrorCode11(error, "ENOENT")) {
|
|
5965
6438
|
throw new Error("Note source not found", { cause: error });
|
|
@@ -6009,7 +6482,7 @@ async function doRunSessionRechain(options, ctx) {
|
|
|
6009
6482
|
}
|
|
6010
6483
|
const cwd = ctx.cwd ?? process.cwd();
|
|
6011
6484
|
const repositoryRoot = await resolveRepositoryRootForSession(cwd, "rechain");
|
|
6012
|
-
const paths =
|
|
6485
|
+
const paths = basouPaths15(repositoryRoot);
|
|
6013
6486
|
await assertWorkspaceInitialized10(paths.root);
|
|
6014
6487
|
const sessionIds = options.session !== void 0 ? [await resolveSessionId3(paths, options.session)] : await enumerateSessionDirs2(paths);
|
|
6015
6488
|
const dryRun = options.dryRun === true;
|
|
@@ -6064,7 +6537,7 @@ function renderRechainRow(row, dryRun) {
|
|
|
6064
6537
|
// src/commands/stats.ts
|
|
6065
6538
|
import {
|
|
6066
6539
|
assertBasouRootSafe as assertBasouRootSafe13,
|
|
6067
|
-
basouPaths as
|
|
6540
|
+
basouPaths as basouPaths16,
|
|
6068
6541
|
computeWorkStats,
|
|
6069
6542
|
findErrorCode as findErrorCode12,
|
|
6070
6543
|
resolveRepositoryRoot as resolveRepositoryRoot10
|
|
@@ -6085,7 +6558,7 @@ async function runStats(options, ctx = {}) {
|
|
|
6085
6558
|
async function doRunStats(options, ctx) {
|
|
6086
6559
|
const cwd = ctx.cwd ?? process.cwd();
|
|
6087
6560
|
const repositoryRoot = await resolveRepositoryRootForStats(cwd);
|
|
6088
|
-
const paths =
|
|
6561
|
+
const paths = basouPaths16(repositoryRoot);
|
|
6089
6562
|
await assertWorkspaceInitialized11(paths.root);
|
|
6090
6563
|
const now = ctx.nowProvider?.() ?? /* @__PURE__ */ new Date();
|
|
6091
6564
|
const result = await computeWorkStats({
|
|
@@ -6194,10 +6667,10 @@ async function assertWorkspaceInitialized11(basouRoot) {
|
|
|
6194
6667
|
// src/commands/status.ts
|
|
6195
6668
|
import {
|
|
6196
6669
|
assertBasouRootSafe as assertBasouRootSafe14,
|
|
6197
|
-
basouPaths as
|
|
6670
|
+
basouPaths as basouPaths17,
|
|
6198
6671
|
buildStatusSnapshot,
|
|
6199
6672
|
findErrorCode as findErrorCode13,
|
|
6200
|
-
readManifest as
|
|
6673
|
+
readManifest as readManifest9,
|
|
6201
6674
|
resolveRepositoryRoot as resolveRepositoryRoot11,
|
|
6202
6675
|
writeStatus
|
|
6203
6676
|
} from "@basou/core";
|
|
@@ -6217,7 +6690,7 @@ async function runStatus(options, ctx = {}) {
|
|
|
6217
6690
|
async function doRunStatus(options, ctx) {
|
|
6218
6691
|
const cwd = ctx.cwd ?? process.cwd();
|
|
6219
6692
|
const repositoryRoot = await resolveRepositoryRootForStatus(cwd);
|
|
6220
|
-
const paths =
|
|
6693
|
+
const paths = basouPaths17(repositoryRoot);
|
|
6221
6694
|
try {
|
|
6222
6695
|
await assertBasouRootSafe14(paths.root);
|
|
6223
6696
|
} catch (error) {
|
|
@@ -6228,7 +6701,7 @@ async function doRunStatus(options, ctx) {
|
|
|
6228
6701
|
}
|
|
6229
6702
|
let manifest;
|
|
6230
6703
|
try {
|
|
6231
|
-
manifest = await
|
|
6704
|
+
manifest = await readManifest9(paths);
|
|
6232
6705
|
} catch (error) {
|
|
6233
6706
|
if (findErrorCode13(error, "ENOENT")) {
|
|
6234
6707
|
throw new Error("Workspace not initialized. Run 'basou init' first.");
|
|
@@ -6266,12 +6739,12 @@ async function resolveRepositoryRootForStatus(cwd) {
|
|
|
6266
6739
|
}
|
|
6267
6740
|
|
|
6268
6741
|
// src/commands/task.ts
|
|
6269
|
-
import { readFile as
|
|
6270
|
-
import { join as
|
|
6742
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
6743
|
+
import { join as join12 } from "path";
|
|
6271
6744
|
import {
|
|
6272
6745
|
archiveTask,
|
|
6273
6746
|
assertBasouRootSafe as assertBasouRootSafe15,
|
|
6274
|
-
basouPaths as
|
|
6747
|
+
basouPaths as basouPaths18,
|
|
6275
6748
|
createTaskWithEvent,
|
|
6276
6749
|
deleteTask,
|
|
6277
6750
|
editTask,
|
|
@@ -6280,7 +6753,7 @@ import {
|
|
|
6280
6753
|
loadSessionEntries as loadSessionEntries2,
|
|
6281
6754
|
loadTaskEntries,
|
|
6282
6755
|
prefixedUlid as prefixedUlid5,
|
|
6283
|
-
readManifest as
|
|
6756
|
+
readManifest as readManifest10,
|
|
6284
6757
|
readTaskFile,
|
|
6285
6758
|
readTaskFileWithArchiveFallback,
|
|
6286
6759
|
reconcileAllTasks,
|
|
@@ -6373,7 +6846,7 @@ async function doRunTaskNew(options, ctx) {
|
|
|
6373
6846
|
}
|
|
6374
6847
|
const cwd = ctx.cwd ?? process.cwd();
|
|
6375
6848
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "new");
|
|
6376
|
-
const paths =
|
|
6849
|
+
const paths = basouPaths18(repositoryRoot);
|
|
6377
6850
|
await assertWorkspaceInitialized12(paths.root);
|
|
6378
6851
|
const description = options.description !== void 0 ? options.description : options.fromFile !== void 0 ? await readDescriptionFile(options.fromFile) : "";
|
|
6379
6852
|
const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
|
|
@@ -6408,7 +6881,7 @@ async function doRunTaskNew(options, ctx) {
|
|
|
6408
6881
|
});
|
|
6409
6882
|
return;
|
|
6410
6883
|
}
|
|
6411
|
-
const manifest = await
|
|
6884
|
+
const manifest = await readManifest10(paths);
|
|
6412
6885
|
const result = await createTaskWithEvent({
|
|
6413
6886
|
mode: "ad-hoc",
|
|
6414
6887
|
paths,
|
|
@@ -6482,7 +6955,7 @@ async function runTaskList(options, ctx = {}) {
|
|
|
6482
6955
|
async function doRunTaskList(options, ctx) {
|
|
6483
6956
|
const cwd = ctx.cwd ?? process.cwd();
|
|
6484
6957
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "list");
|
|
6485
|
-
const paths =
|
|
6958
|
+
const paths = basouPaths18(repositoryRoot);
|
|
6486
6959
|
await assertWorkspaceInitialized12(paths.root);
|
|
6487
6960
|
const entries = await loadTaskEntries(paths, {
|
|
6488
6961
|
onSkip: (id, reason) => printTaskSkip(id, reason)
|
|
@@ -6586,7 +7059,7 @@ async function runTaskShow(idInput, options, ctx = {}) {
|
|
|
6586
7059
|
async function doRunTaskShow(idInput, options, ctx) {
|
|
6587
7060
|
const cwd = ctx.cwd ?? process.cwd();
|
|
6588
7061
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "show");
|
|
6589
|
-
const paths =
|
|
7062
|
+
const paths = basouPaths18(repositoryRoot);
|
|
6590
7063
|
await assertWorkspaceInitialized12(paths.root);
|
|
6591
7064
|
const taskId = await resolveTaskId2(paths, idInput, { includeArchived: true });
|
|
6592
7065
|
const { doc, archived } = await readTaskFileWithArchiveFallback(paths, taskId);
|
|
@@ -6594,7 +7067,7 @@ async function doRunTaskShow(idInput, options, ctx) {
|
|
|
6594
7067
|
const events = [];
|
|
6595
7068
|
const linkedSessionIds = new Set(doc.task.task.linked_sessions);
|
|
6596
7069
|
for (const s of sessions) {
|
|
6597
|
-
const sessionDir =
|
|
7070
|
+
const sessionDir = join12(paths.sessions, s.sessionId);
|
|
6598
7071
|
try {
|
|
6599
7072
|
for await (const ev of replayEvents2(sessionDir, {
|
|
6600
7073
|
onWarning: (w) => printReplayWarning(w, s.sessionId)
|
|
@@ -6730,7 +7203,7 @@ async function doRunTaskStatus(taskIdInput, newStatusInput, options, ctx) {
|
|
|
6730
7203
|
const newStatus = parseTaskStatusPositional(newStatusInput);
|
|
6731
7204
|
const cwd = ctx.cwd ?? process.cwd();
|
|
6732
7205
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "status");
|
|
6733
|
-
const paths =
|
|
7206
|
+
const paths = basouPaths18(repositoryRoot);
|
|
6734
7207
|
await assertWorkspaceInitialized12(paths.root);
|
|
6735
7208
|
const taskId = await resolveTaskId2(paths, taskIdInput);
|
|
6736
7209
|
const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
|
|
@@ -6756,7 +7229,7 @@ async function doRunTaskStatus(taskIdInput, newStatusInput, options, ctx) {
|
|
|
6756
7229
|
});
|
|
6757
7230
|
return;
|
|
6758
7231
|
}
|
|
6759
|
-
const manifest = await
|
|
7232
|
+
const manifest = await readManifest10(paths);
|
|
6760
7233
|
const result = await updateTaskStatusWithEvent({
|
|
6761
7234
|
mode: "ad-hoc",
|
|
6762
7235
|
paths,
|
|
@@ -6807,9 +7280,9 @@ async function runTaskReconcile(options, ctx = {}) {
|
|
|
6807
7280
|
async function doRunTaskReconcile(options, ctx) {
|
|
6808
7281
|
const cwd = ctx.cwd ?? process.cwd();
|
|
6809
7282
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "reconcile");
|
|
6810
|
-
const paths =
|
|
7283
|
+
const paths = basouPaths18(repositoryRoot);
|
|
6811
7284
|
await assertWorkspaceInitialized12(paths.root);
|
|
6812
|
-
const manifest = await
|
|
7285
|
+
const manifest = await readManifest10(paths);
|
|
6813
7286
|
const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
|
|
6814
7287
|
const write = options.write === true;
|
|
6815
7288
|
const verbose = isVerbose(options);
|
|
@@ -6987,9 +7460,9 @@ async function doRunTaskRefreshLinkage(taskIdInput, options, ctx) {
|
|
|
6987
7460
|
}
|
|
6988
7461
|
const cwd = ctx.cwd ?? process.cwd();
|
|
6989
7462
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "refresh-linkage");
|
|
6990
|
-
const paths =
|
|
7463
|
+
const paths = basouPaths18(repositoryRoot);
|
|
6991
7464
|
await assertWorkspaceInitialized12(paths.root);
|
|
6992
|
-
const manifest = await
|
|
7465
|
+
const manifest = await readManifest10(paths);
|
|
6993
7466
|
const taskId = await resolveTaskId2(paths, taskIdInput);
|
|
6994
7467
|
const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
|
|
6995
7468
|
const write = options.write === true;
|
|
@@ -7067,9 +7540,9 @@ async function doRunTaskEdit(taskIdInput, options, ctx) {
|
|
|
7067
7540
|
}
|
|
7068
7541
|
const cwd = ctx.cwd ?? process.cwd();
|
|
7069
7542
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "edit");
|
|
7070
|
-
const paths =
|
|
7543
|
+
const paths = basouPaths18(repositoryRoot);
|
|
7071
7544
|
await assertWorkspaceInitialized12(paths.root);
|
|
7072
|
-
const manifest = await
|
|
7545
|
+
const manifest = await readManifest10(paths);
|
|
7073
7546
|
const taskId = await resolveTaskId2(paths, taskIdInput);
|
|
7074
7547
|
const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
|
|
7075
7548
|
const occurredAt = now.toISOString();
|
|
@@ -7123,9 +7596,9 @@ async function doRunTaskDelete(taskIdInput, options, ctx) {
|
|
|
7123
7596
|
}
|
|
7124
7597
|
const cwd = ctx.cwd ?? process.cwd();
|
|
7125
7598
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "delete");
|
|
7126
|
-
const paths =
|
|
7599
|
+
const paths = basouPaths18(repositoryRoot);
|
|
7127
7600
|
await assertWorkspaceInitialized12(paths.root);
|
|
7128
|
-
const manifest = await
|
|
7601
|
+
const manifest = await readManifest10(paths);
|
|
7129
7602
|
const taskId = await resolveTaskId2(paths, taskIdInput);
|
|
7130
7603
|
if (options.yes !== true) {
|
|
7131
7604
|
await confirmDestructiveAction("delete", taskId);
|
|
@@ -7168,9 +7641,9 @@ async function doRunTaskArchive(taskIdInput, options, ctx) {
|
|
|
7168
7641
|
}
|
|
7169
7642
|
const cwd = ctx.cwd ?? process.cwd();
|
|
7170
7643
|
const repositoryRoot = await resolveRepositoryRootForTask(cwd, "archive");
|
|
7171
|
-
const paths =
|
|
7644
|
+
const paths = basouPaths18(repositoryRoot);
|
|
7172
7645
|
await assertWorkspaceInitialized12(paths.root);
|
|
7173
|
-
const manifest = await
|
|
7646
|
+
const manifest = await readManifest10(paths);
|
|
7174
7647
|
const taskId = await resolveTaskId2(paths, taskIdInput);
|
|
7175
7648
|
if (options.yes !== true) {
|
|
7176
7649
|
await confirmDestructiveAction("archive", taskId);
|
|
@@ -7282,7 +7755,7 @@ function parsePositiveInt2(raw) {
|
|
|
7282
7755
|
}
|
|
7283
7756
|
async function readDescriptionFile(path) {
|
|
7284
7757
|
try {
|
|
7285
|
-
return await
|
|
7758
|
+
return await readFile5(path, "utf8");
|
|
7286
7759
|
} catch (error) {
|
|
7287
7760
|
if (findErrorCode14(error, "ENOENT")) {
|
|
7288
7761
|
throw new Error("Description source not found", { cause: error });
|
|
@@ -7399,7 +7872,7 @@ function maxLen3(values, floor) {
|
|
|
7399
7872
|
// src/commands/verify.ts
|
|
7400
7873
|
import {
|
|
7401
7874
|
assertBasouRootSafe as assertBasouRootSafe16,
|
|
7402
|
-
basouPaths as
|
|
7875
|
+
basouPaths as basouPaths19,
|
|
7403
7876
|
enumerateSessionDirs as enumerateSessionDirs3,
|
|
7404
7877
|
findErrorCode as findErrorCode15,
|
|
7405
7878
|
resolveRepositoryRoot as resolveRepositoryRoot13,
|
|
@@ -7425,7 +7898,7 @@ async function doRunVerify(options, ctx) {
|
|
|
7425
7898
|
}
|
|
7426
7899
|
const cwd = ctx.cwd ?? process.cwd();
|
|
7427
7900
|
const repositoryRoot = await resolveRepositoryRootForVerify(cwd);
|
|
7428
|
-
const paths =
|
|
7901
|
+
const paths = basouPaths19(repositoryRoot);
|
|
7429
7902
|
await assertWorkspaceInitialized13(paths.root);
|
|
7430
7903
|
const sessionIds = options.session !== void 0 ? [await resolveSessionId5(paths, options.session)] : await enumerateSessionDirs3(paths);
|
|
7431
7904
|
const rows = [];
|
|
@@ -7497,36 +7970,36 @@ async function assertWorkspaceInitialized13(basouRoot) {
|
|
|
7497
7970
|
// src/commands/view.ts
|
|
7498
7971
|
import { spawn } from "child_process";
|
|
7499
7972
|
import { createHash } from "crypto";
|
|
7500
|
-
import { basename as
|
|
7973
|
+
import { basename as basename7, resolve as resolve11 } from "path";
|
|
7501
7974
|
import {
|
|
7502
7975
|
assertBasouRootSafe as assertBasouRootSafe17,
|
|
7503
|
-
basouPaths as
|
|
7976
|
+
basouPaths as basouPaths20,
|
|
7504
7977
|
findErrorCode as findErrorCode17,
|
|
7505
|
-
readManifest as
|
|
7978
|
+
readManifest as readManifest13,
|
|
7506
7979
|
resolveRepositoryRoot as resolveRepositoryRoot14
|
|
7507
7980
|
} from "@basou/core";
|
|
7508
7981
|
import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
|
|
7509
7982
|
|
|
7510
7983
|
// src/lib/portfolio-safety.ts
|
|
7511
7984
|
import { execFile } from "child_process";
|
|
7512
|
-
import { lstat, realpath } from "fs/promises";
|
|
7513
|
-
import { isAbsolute as
|
|
7985
|
+
import { lstat as lstat2, realpath as realpath2 } from "fs/promises";
|
|
7986
|
+
import { isAbsolute as isAbsolute7, join as join13, relative as relative4, resolve as resolve10 } from "path";
|
|
7514
7987
|
import { promisify } from "util";
|
|
7515
|
-
import { readManifest as
|
|
7988
|
+
import { readManifest as readManifest11 } from "@basou/core";
|
|
7516
7989
|
var execFileAsync = promisify(execFile);
|
|
7517
7990
|
function errorCode(error) {
|
|
7518
7991
|
return error instanceof Error ? error.code : void 0;
|
|
7519
7992
|
}
|
|
7520
7993
|
async function canonical(p) {
|
|
7521
7994
|
try {
|
|
7522
|
-
return await
|
|
7995
|
+
return await realpath2(p);
|
|
7523
7996
|
} catch {
|
|
7524
|
-
return
|
|
7997
|
+
return resolve10(p);
|
|
7525
7998
|
}
|
|
7526
7999
|
}
|
|
7527
8000
|
function isInside(child, parent) {
|
|
7528
8001
|
const rel = relative4(parent, child);
|
|
7529
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
8002
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute7(rel);
|
|
7530
8003
|
}
|
|
7531
8004
|
function isBasouPath(p) {
|
|
7532
8005
|
return p === ".basou" || p.startsWith(".basou/") || p.includes("/.basou/") || p.endsWith("/.basou");
|
|
@@ -7534,7 +8007,7 @@ function isBasouPath(p) {
|
|
|
7534
8007
|
async function inspectRepo(repoPath) {
|
|
7535
8008
|
let hasEntry = false;
|
|
7536
8009
|
try {
|
|
7537
|
-
await
|
|
8010
|
+
await lstat2(join13(repoPath, ".basou"));
|
|
7538
8011
|
hasEntry = true;
|
|
7539
8012
|
} catch (error) {
|
|
7540
8013
|
if (errorCode(error) !== "ENOENT") {
|
|
@@ -7565,7 +8038,7 @@ async function checkPortfolioSafety(workspaces) {
|
|
|
7565
8038
|
const wsReal = await canonical(ws.repoRoot);
|
|
7566
8039
|
let sourceRoots = [];
|
|
7567
8040
|
try {
|
|
7568
|
-
const manifest = await
|
|
8041
|
+
const manifest = await readManifest11(ws.paths);
|
|
7569
8042
|
sourceRoots = manifest.import?.source_roots ?? [];
|
|
7570
8043
|
} catch (error) {
|
|
7571
8044
|
if (error instanceof Error && error.message === "YAML file not found") {
|
|
@@ -7583,7 +8056,7 @@ async function checkPortfolioSafety(workspaces) {
|
|
|
7583
8056
|
}
|
|
7584
8057
|
const monitored = /* @__PURE__ */ new Map();
|
|
7585
8058
|
for (const root of sourceRoots) {
|
|
7586
|
-
const display =
|
|
8059
|
+
const display = resolve10(ws.repoRoot, root);
|
|
7587
8060
|
const real = await canonical(display);
|
|
7588
8061
|
if (real !== wsReal) monitored.set(real, display);
|
|
7589
8062
|
}
|
|
@@ -7635,7 +8108,7 @@ function formatSafetyReport(result) {
|
|
|
7635
8108
|
|
|
7636
8109
|
// src/lib/view-server.ts
|
|
7637
8110
|
import { createServer } from "http";
|
|
7638
|
-
import { join as
|
|
8111
|
+
import { join as join14 } from "path";
|
|
7639
8112
|
import {
|
|
7640
8113
|
computeWorkStats as computeWorkStats2,
|
|
7641
8114
|
enumerateApprovals as enumerateApprovals2,
|
|
@@ -7645,8 +8118,8 @@ import {
|
|
|
7645
8118
|
loadSessionEntries as loadSessionEntries3,
|
|
7646
8119
|
loadTaskEntries as loadTaskEntries2,
|
|
7647
8120
|
readAllEvents as readAllEvents2,
|
|
7648
|
-
readManifest as
|
|
7649
|
-
readMarkdownFile as
|
|
8121
|
+
readManifest as readManifest12,
|
|
8122
|
+
readMarkdownFile as readMarkdownFile6,
|
|
7650
8123
|
readSessionYaml as readSessionYaml3,
|
|
7651
8124
|
readTaskFile as readTaskFile2,
|
|
7652
8125
|
renderDecisions as renderDecisions3,
|
|
@@ -8292,7 +8765,7 @@ function startViewServer(opts) {
|
|
|
8292
8765
|
};
|
|
8293
8766
|
let boundPort = port;
|
|
8294
8767
|
const getPort = () => boundPort;
|
|
8295
|
-
return new Promise((
|
|
8768
|
+
return new Promise((resolve12, reject) => {
|
|
8296
8769
|
const server = createServer((req, res) => {
|
|
8297
8770
|
handleRequest(req, res, deps, getPort, runExclusive).catch((error) => {
|
|
8298
8771
|
sendError(res, error instanceof HttpError ? error.status : 500, pathlessMessage(error));
|
|
@@ -8303,7 +8776,7 @@ function startViewServer(opts) {
|
|
|
8303
8776
|
const address = server.address();
|
|
8304
8777
|
boundPort = isAddressInfo(address) ? address.port : port;
|
|
8305
8778
|
server.off("error", reject);
|
|
8306
|
-
|
|
8779
|
+
resolve12({
|
|
8307
8780
|
url: `http://${host}:${boundPort}`,
|
|
8308
8781
|
port: boundPort,
|
|
8309
8782
|
close: () => closeServer(server)
|
|
@@ -8315,8 +8788,8 @@ function isAddressInfo(value) {
|
|
|
8315
8788
|
return value !== null && typeof value === "object";
|
|
8316
8789
|
}
|
|
8317
8790
|
function closeServer(server) {
|
|
8318
|
-
return new Promise((
|
|
8319
|
-
server.close(() =>
|
|
8791
|
+
return new Promise((resolve12) => {
|
|
8792
|
+
server.close(() => resolve12());
|
|
8320
8793
|
server.closeAllConnections();
|
|
8321
8794
|
});
|
|
8322
8795
|
}
|
|
@@ -8533,7 +9006,7 @@ async function captureStaleness(ws, nowIso) {
|
|
|
8533
9006
|
async function overview(ws, nowProvider) {
|
|
8534
9007
|
let manifest;
|
|
8535
9008
|
try {
|
|
8536
|
-
manifest = await
|
|
9009
|
+
manifest = await readManifest12(ws.paths);
|
|
8537
9010
|
} catch (error) {
|
|
8538
9011
|
if (findErrorCode16(error, "ENOENT")) {
|
|
8539
9012
|
return { initialized: false, repoRoot: ws.repoRoot };
|
|
@@ -8590,7 +9063,7 @@ async function sessionDetail(ws, sessionId) {
|
|
|
8590
9063
|
throw error;
|
|
8591
9064
|
}
|
|
8592
9065
|
try {
|
|
8593
|
-
const events = await readAllEvents2(
|
|
9066
|
+
const events = await readAllEvents2(join14(ws.paths.sessions, sessionId));
|
|
8594
9067
|
return { session, events };
|
|
8595
9068
|
} catch {
|
|
8596
9069
|
return { session, events: [], degraded: true };
|
|
@@ -8612,7 +9085,7 @@ async function taskDetail(ws, taskId) {
|
|
|
8612
9085
|
}
|
|
8613
9086
|
}
|
|
8614
9087
|
async function decisionsView(ws, nowProvider) {
|
|
8615
|
-
const fromDisk = await
|
|
9088
|
+
const fromDisk = await readMarkdownFile6(ws.paths.files.decisions);
|
|
8616
9089
|
if (fromDisk !== null) {
|
|
8617
9090
|
return { body: fromDisk, fromDisk: true };
|
|
8618
9091
|
}
|
|
@@ -8635,7 +9108,7 @@ async function approvalsView(ws, nowProvider) {
|
|
|
8635
9108
|
return { pending: await toViews(ids.pending), resolved: await toViews(ids.resolved) };
|
|
8636
9109
|
}
|
|
8637
9110
|
async function handoffView(ws, nowProvider) {
|
|
8638
|
-
const fromDisk = await
|
|
9111
|
+
const fromDisk = await readMarkdownFile6(ws.paths.files.handoff);
|
|
8639
9112
|
if (fromDisk !== null) {
|
|
8640
9113
|
return { body: fromDisk, fromDisk: true };
|
|
8641
9114
|
}
|
|
@@ -8804,18 +9277,18 @@ async function doRunView(options, ctx) {
|
|
|
8804
9277
|
}
|
|
8805
9278
|
async function buildSingleDeps(ctx, cwd) {
|
|
8806
9279
|
const repositoryRoot = await resolveRepositoryRootForView(cwd);
|
|
8807
|
-
const paths =
|
|
9280
|
+
const paths = basouPaths20(repositoryRoot);
|
|
8808
9281
|
await assertWorkspaceInitialized14(paths.root);
|
|
8809
9282
|
const entry = await buildWorkspaceEntry(repositoryRoot, ctx);
|
|
8810
9283
|
return { workspaces: [entry], mode: "single", nowProvider: nowProviderOf(ctx) };
|
|
8811
9284
|
}
|
|
8812
9285
|
async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
|
|
8813
|
-
const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path:
|
|
9286
|
+
const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve11(cwd, p) })) : await loadPortfolioConfig(ctx.portfolioConfigPath);
|
|
8814
9287
|
const entries = [];
|
|
8815
9288
|
const seenPath = /* @__PURE__ */ new Set();
|
|
8816
9289
|
const seenKey = /* @__PURE__ */ new Set();
|
|
8817
9290
|
for (const spec of specs) {
|
|
8818
|
-
const repoRoot =
|
|
9291
|
+
const repoRoot = resolve11(spec.path);
|
|
8819
9292
|
if (seenPath.has(repoRoot)) continue;
|
|
8820
9293
|
seenPath.add(repoRoot);
|
|
8821
9294
|
const entry = await buildWorkspaceEntry(repoRoot, ctx, spec.label);
|
|
@@ -8828,14 +9301,14 @@ async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
|
|
|
8828
9301
|
return { workspaces: entries, mode: "portfolio", nowProvider: nowProviderOf(ctx) };
|
|
8829
9302
|
}
|
|
8830
9303
|
async function buildWorkspaceEntry(repoRoot, ctx, labelOverride) {
|
|
8831
|
-
const paths =
|
|
9304
|
+
const paths = basouPaths20(repoRoot);
|
|
8832
9305
|
const importCtx = {
|
|
8833
9306
|
cwd: repoRoot,
|
|
8834
9307
|
...ctx.claudeProjectsDir !== void 0 ? { claudeProjectsDir: ctx.claudeProjectsDir } : {},
|
|
8835
9308
|
...ctx.codexSessionsDir !== void 0 ? { codexSessionsDir: ctx.codexSessionsDir } : {}
|
|
8836
9309
|
};
|
|
8837
9310
|
try {
|
|
8838
|
-
const manifest = await
|
|
9311
|
+
const manifest = await readManifest13(paths);
|
|
8839
9312
|
return {
|
|
8840
9313
|
key: manifest.workspace.id,
|
|
8841
9314
|
label: labelOverride ?? manifest.workspace.name,
|
|
@@ -8848,7 +9321,7 @@ async function buildWorkspaceEntry(repoRoot, ctx, labelOverride) {
|
|
|
8848
9321
|
const notFound = error instanceof Error && error.message === "YAML file not found";
|
|
8849
9322
|
return {
|
|
8850
9323
|
key: `ws-${createHash("sha1").update(repoRoot).digest("hex").slice(0, 12)}`,
|
|
8851
|
-
label: labelOverride ??
|
|
9324
|
+
label: labelOverride ?? basename7(repoRoot),
|
|
8852
9325
|
paths,
|
|
8853
9326
|
repoRoot,
|
|
8854
9327
|
importCtx,
|
|
@@ -8887,7 +9360,7 @@ function openInBrowser(url, override) {
|
|
|
8887
9360
|
}
|
|
8888
9361
|
}
|
|
8889
9362
|
function waitForShutdown(signal) {
|
|
8890
|
-
return new Promise((
|
|
9363
|
+
return new Promise((resolve12) => {
|
|
8891
9364
|
const cleanup = () => {
|
|
8892
9365
|
process.off("SIGINT", onSignal);
|
|
8893
9366
|
process.off("SIGTERM", onSignal);
|
|
@@ -8895,18 +9368,18 @@ function waitForShutdown(signal) {
|
|
|
8895
9368
|
};
|
|
8896
9369
|
const onSignal = () => {
|
|
8897
9370
|
cleanup();
|
|
8898
|
-
|
|
9371
|
+
resolve12();
|
|
8899
9372
|
};
|
|
8900
9373
|
const onAbort = () => {
|
|
8901
9374
|
cleanup();
|
|
8902
|
-
|
|
9375
|
+
resolve12();
|
|
8903
9376
|
};
|
|
8904
9377
|
process.on("SIGINT", onSignal);
|
|
8905
9378
|
process.on("SIGTERM", onSignal);
|
|
8906
9379
|
if (signal !== void 0) {
|
|
8907
9380
|
if (signal.aborted) {
|
|
8908
9381
|
cleanup();
|
|
8909
|
-
|
|
9382
|
+
resolve12();
|
|
8910
9383
|
return;
|
|
8911
9384
|
}
|
|
8912
9385
|
signal.addEventListener("abort", onAbort);
|
|
@@ -8963,6 +9436,7 @@ function buildProgram() {
|
|
|
8963
9436
|
registerOrientCommand(program);
|
|
8964
9437
|
registerReviewGapsCommand(program);
|
|
8965
9438
|
registerProjectCommand(program);
|
|
9439
|
+
registerProtocolCommand(program);
|
|
8966
9440
|
return program;
|
|
8967
9441
|
}
|
|
8968
9442
|
export {
|