@basou/cli 0.15.0 → 0.17.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 +232 -112
- package/dist/index.js.map +1 -1
- package/dist/program.js +232 -112
- package/dist/program.js.map +1 -1
- package/package.json +2 -2
package/dist/program.js
CHANGED
|
@@ -1804,13 +1804,15 @@ async function assertWorkspaceInitialized4(basouRoot) {
|
|
|
1804
1804
|
import { createReadStream } from "fs";
|
|
1805
1805
|
import { readdir, readFile as readFile2, rm, stat as stat2 } from "fs/promises";
|
|
1806
1806
|
import { homedir as homedir4 } from "os";
|
|
1807
|
-
import { basename as basename2, join as join4, resolve as resolve4 } from "path";
|
|
1807
|
+
import { basename as basename2, dirname, join as join4, resolve as resolve4 } from "path";
|
|
1808
1808
|
import { createInterface } from "readline";
|
|
1809
1809
|
import {
|
|
1810
|
+
AGENT_INFRA_DIRS,
|
|
1810
1811
|
assertBasouRootSafe as assertBasouRootSafe6,
|
|
1811
1812
|
basouPaths as basouPaths7,
|
|
1812
1813
|
CLAUDE_IMPORT_SOURCE,
|
|
1813
1814
|
CODEX_IMPORT_SOURCE,
|
|
1815
|
+
classifyFilesBySourceRoot,
|
|
1814
1816
|
claudeTranscriptToImportPayload,
|
|
1815
1817
|
codexRolloutToImportPayload,
|
|
1816
1818
|
enumerateSessionDirs,
|
|
@@ -1908,7 +1910,15 @@ async function doRunImportClaudeCode(options, ctx) {
|
|
|
1908
1910
|
}
|
|
1909
1911
|
};
|
|
1910
1912
|
});
|
|
1911
|
-
await importDerivedSessions(
|
|
1913
|
+
await importDerivedSessions(
|
|
1914
|
+
paths,
|
|
1915
|
+
manifest,
|
|
1916
|
+
options,
|
|
1917
|
+
CLAUDE_IMPORT_SOURCE,
|
|
1918
|
+
candidates,
|
|
1919
|
+
projectPaths,
|
|
1920
|
+
hasDeclaredBoundary(options, manifest)
|
|
1921
|
+
);
|
|
1912
1922
|
}
|
|
1913
1923
|
async function doRunImportCodex(options, ctx) {
|
|
1914
1924
|
assertSelector(options);
|
|
@@ -1933,7 +1943,18 @@ async function doRunImportCodex(options, ctx) {
|
|
|
1933
1943
|
});
|
|
1934
1944
|
}
|
|
1935
1945
|
}));
|
|
1936
|
-
await importDerivedSessions(
|
|
1946
|
+
await importDerivedSessions(
|
|
1947
|
+
paths,
|
|
1948
|
+
manifest,
|
|
1949
|
+
options,
|
|
1950
|
+
CODEX_IMPORT_SOURCE,
|
|
1951
|
+
candidates,
|
|
1952
|
+
projectPaths,
|
|
1953
|
+
hasDeclaredBoundary(options, manifest)
|
|
1954
|
+
);
|
|
1955
|
+
}
|
|
1956
|
+
function hasDeclaredBoundary(options, manifest) {
|
|
1957
|
+
return (options.project?.length ?? 0) > 0 || (manifest.import?.source_roots?.length ?? 0) > 0;
|
|
1937
1958
|
}
|
|
1938
1959
|
function assertSelector(options) {
|
|
1939
1960
|
if (options.session !== void 0 && options.all === true) {
|
|
@@ -1951,9 +1972,25 @@ async function resolveImportTarget(ctx) {
|
|
|
1951
1972
|
const manifest = await readManifest4(paths);
|
|
1952
1973
|
return { repositoryRoot, paths, manifest };
|
|
1953
1974
|
}
|
|
1954
|
-
async function importDerivedSessions(paths, manifest, options, sourceKind, candidates) {
|
|
1975
|
+
async function importDerivedSessions(paths, manifest, options, sourceKind, candidates, projectPaths, boundaryDeclared) {
|
|
1955
1976
|
const existingByExternalId = await loadExistingByExternalId(paths, sourceKind);
|
|
1956
1977
|
const seenThisRun = /* @__PURE__ */ new Set();
|
|
1978
|
+
const crossProjectCheck = boundaryDeclared;
|
|
1979
|
+
const crossProject = [];
|
|
1980
|
+
const noteCrossProject = async (externalId, payload) => {
|
|
1981
|
+
if (!crossProjectCheck) return;
|
|
1982
|
+
try {
|
|
1983
|
+
const scope = await classifyFilesBySourceRoot({
|
|
1984
|
+
files: payload.session.related_files ?? [],
|
|
1985
|
+
workingDirectory: payload.session.working_directory,
|
|
1986
|
+
sourceRoots: projectPaths,
|
|
1987
|
+
masterRoot: dirname(paths.root),
|
|
1988
|
+
extraInRoot: AGENT_INFRA_DIRS
|
|
1989
|
+
});
|
|
1990
|
+
if (scope.outOfRoot.length > 0) crossProject.push({ externalId, outOfRoot: scope.outOfRoot });
|
|
1991
|
+
} catch {
|
|
1992
|
+
}
|
|
1993
|
+
};
|
|
1957
1994
|
const results = [];
|
|
1958
1995
|
const counts = {
|
|
1959
1996
|
skippedNoAction: 0,
|
|
@@ -2010,6 +2047,7 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
|
|
|
2010
2047
|
}
|
|
2011
2048
|
counts.reimported++;
|
|
2012
2049
|
seenThisRun.add(externalId);
|
|
2050
|
+
await noteCrossProject(externalId, payload2);
|
|
2013
2051
|
continue;
|
|
2014
2052
|
}
|
|
2015
2053
|
const payload = validate(await toPayload());
|
|
@@ -2031,10 +2069,21 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
|
|
|
2031
2069
|
results.push(result);
|
|
2032
2070
|
seenThisRun.add(externalId);
|
|
2033
2071
|
sanitizedPaths += result.pathSanitizeReport.relatedFiles + (result.pathSanitizeReport.workingDirectoryRewritten ? 1 : 0);
|
|
2072
|
+
await noteCrossProject(externalId, payload);
|
|
2034
2073
|
}
|
|
2035
2074
|
if (sanitizedPaths > 0) {
|
|
2036
2075
|
console.error(`Imported sessions: ${sanitizedPaths} path(s) sanitized`);
|
|
2037
2076
|
}
|
|
2077
|
+
if (crossProject.length > 0) {
|
|
2078
|
+
const PATH_SAMPLE = 5;
|
|
2079
|
+
for (const { externalId, outOfRoot } of crossProject) {
|
|
2080
|
+
const sample = outOfRoot.slice(0, PATH_SAMPLE).join(", ");
|
|
2081
|
+
const more = outOfRoot.length > PATH_SAMPLE ? ` (... +${outOfRoot.length - PATH_SAMPLE} more)` : "";
|
|
2082
|
+
console.error(
|
|
2083
|
+
`basou: session ${externalId} edited ${outOfRoot.length} file(s) outside this project's source_roots: ${sample}${more} \u2014 they may belong to another project.`
|
|
2084
|
+
);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2038
2087
|
printImportResult(options, results, counts);
|
|
2039
2088
|
}
|
|
2040
2089
|
async function classifyReimport(priors, sourcePath, externalId, counts) {
|
|
@@ -2620,6 +2669,62 @@ import {
|
|
|
2620
2669
|
writeMarkdownFile as writeMarkdownFile4
|
|
2621
2670
|
} from "@basou/core";
|
|
2622
2671
|
|
|
2672
|
+
// src/lib/hosts-config.ts
|
|
2673
|
+
import { homedir as homedir5 } from "os";
|
|
2674
|
+
import { isAbsolute as isAbsolute2, join as join5, resolve as resolve6 } from "path";
|
|
2675
|
+
import { readYamlFile as readYamlFile4 } from "@basou/core";
|
|
2676
|
+
var DEFAULT_HOSTS_CONFIG_PATH = join5(homedir5(), ".basou", "hosts.yaml");
|
|
2677
|
+
function expandTilde2(p) {
|
|
2678
|
+
if (p === "~") return homedir5();
|
|
2679
|
+
if (p.startsWith("~/")) return join5(homedir5(), p.slice(2));
|
|
2680
|
+
return p;
|
|
2681
|
+
}
|
|
2682
|
+
function isRecord2(value) {
|
|
2683
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2684
|
+
}
|
|
2685
|
+
async function loadHostsConfig(configPath = DEFAULT_HOSTS_CONFIG_PATH) {
|
|
2686
|
+
let raw;
|
|
2687
|
+
try {
|
|
2688
|
+
raw = await readYamlFile4(configPath);
|
|
2689
|
+
} catch (error) {
|
|
2690
|
+
if (error instanceof Error && error.message === "YAML file not found") {
|
|
2691
|
+
return null;
|
|
2692
|
+
}
|
|
2693
|
+
if (error instanceof Error && error.message === "Failed to parse YAML content") {
|
|
2694
|
+
throw new Error("~/.basou/hosts.yaml is not valid YAML.");
|
|
2695
|
+
}
|
|
2696
|
+
throw error;
|
|
2697
|
+
}
|
|
2698
|
+
if (!isRecord2(raw) || !Array.isArray(raw.hosts)) {
|
|
2699
|
+
throw new Error("~/.basou/hosts.yaml must contain a 'hosts:' list.");
|
|
2700
|
+
}
|
|
2701
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
2702
|
+
const seenLabels = /* @__PURE__ */ new Set();
|
|
2703
|
+
const result = [];
|
|
2704
|
+
for (const entry of raw.hosts) {
|
|
2705
|
+
if (!isRecord2(entry) || typeof entry.label !== "string" || entry.label.trim().length === 0) {
|
|
2706
|
+
throw new Error("Each host needs a non-empty string 'label'.");
|
|
2707
|
+
}
|
|
2708
|
+
const label = entry.label.trim();
|
|
2709
|
+
if (typeof entry.path !== "string" || entry.path.trim().length === 0) {
|
|
2710
|
+
throw new Error("Each host needs a non-empty string 'path'.");
|
|
2711
|
+
}
|
|
2712
|
+
const expanded = expandTilde2(entry.path.trim());
|
|
2713
|
+
if (!isAbsolute2(expanded)) {
|
|
2714
|
+
throw new Error("Host paths must be absolute (or start with '~').");
|
|
2715
|
+
}
|
|
2716
|
+
const abs = resolve6(expanded);
|
|
2717
|
+
if (seenPaths.has(abs)) continue;
|
|
2718
|
+
if (seenLabels.has(label)) {
|
|
2719
|
+
throw new Error(`Duplicate host label '${label}'; each host needs a distinct label.`);
|
|
2720
|
+
}
|
|
2721
|
+
seenPaths.add(abs);
|
|
2722
|
+
seenLabels.add(label);
|
|
2723
|
+
result.push({ label, path: abs });
|
|
2724
|
+
}
|
|
2725
|
+
return result;
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2623
2728
|
// src/lib/provenance-actions.ts
|
|
2624
2729
|
import {
|
|
2625
2730
|
readMarkdownFile as readMarkdownFile3,
|
|
@@ -2826,14 +2931,29 @@ async function doRunOrient(options, ctx) {
|
|
|
2826
2931
|
if (ctx.claudeProjectsDir !== void 0) probeCtx.claudeProjectsDir = ctx.claudeProjectsDir;
|
|
2827
2932
|
if (ctx.codexSessionsDir !== void 0) probeCtx.codexSessionsDir = ctx.codexSessionsDir;
|
|
2828
2933
|
const staleness = await probeStaleness({ ctx: probeCtx, paths, nowIso });
|
|
2934
|
+
let federatedRoots = [];
|
|
2935
|
+
try {
|
|
2936
|
+
const hosts = await loadHostsConfig(ctx.hostsConfigPath);
|
|
2937
|
+
if (hosts !== null) {
|
|
2938
|
+
federatedRoots = hosts.map((h) => ({ paths: basouPaths9(h.path), host: h.label }));
|
|
2939
|
+
}
|
|
2940
|
+
} catch (error) {
|
|
2941
|
+
console.error(
|
|
2942
|
+
`basou: ignoring ~/.basou/hosts.yaml (${error instanceof Error ? error.message : String(error)}); showing local sessions only.`
|
|
2943
|
+
);
|
|
2944
|
+
}
|
|
2829
2945
|
const result = await renderOrientation2({
|
|
2830
2946
|
paths,
|
|
2831
2947
|
nowIso,
|
|
2832
2948
|
staleness,
|
|
2833
2949
|
verbose: options.verbose === true,
|
|
2950
|
+
federatedRoots,
|
|
2834
2951
|
onWarning: (w, sid) => printReplayWarning(w, sid),
|
|
2835
2952
|
onSessionSkip: (sid, reason) => printSessionSkip(sid, reason),
|
|
2836
|
-
onTaskSkip: (taskId, reason) => printTaskSkip(taskId, reason)
|
|
2953
|
+
onTaskSkip: (taskId, reason) => printTaskSkip(taskId, reason),
|
|
2954
|
+
onHostUnavailable: (host, error) => console.error(
|
|
2955
|
+
`basou: host '${host}' mirror unreadable (${error instanceof Error ? error.message : String(error)}); skipping it.`
|
|
2956
|
+
)
|
|
2837
2957
|
});
|
|
2838
2958
|
await writeMarkdownFile4(paths.files.orientation, `${result.body}
|
|
2839
2959
|
`);
|
|
@@ -2870,7 +2990,7 @@ import {
|
|
|
2870
2990
|
unlinkSync,
|
|
2871
2991
|
writeFileSync
|
|
2872
2992
|
} from "fs";
|
|
2873
|
-
import { basename as basename4, dirname, isAbsolute as
|
|
2993
|
+
import { basename as basename4, dirname as dirname2, isAbsolute as isAbsolute3, join as join6, relative as relative2, resolve as resolve7 } from "path";
|
|
2874
2994
|
import {
|
|
2875
2995
|
basouPaths as basouPaths10,
|
|
2876
2996
|
GENERATED_END,
|
|
@@ -3122,14 +3242,14 @@ async function runProjectAdopt(options, ctx = {}) {
|
|
|
3122
3242
|
}
|
|
3123
3243
|
}
|
|
3124
3244
|
function classifySourceRoot(repositoryRoot, declaredPath) {
|
|
3125
|
-
const absolute =
|
|
3245
|
+
const absolute = resolve7(repositoryRoot, declaredPath);
|
|
3126
3246
|
let real;
|
|
3127
3247
|
try {
|
|
3128
3248
|
real = realpathSync(absolute);
|
|
3129
3249
|
} catch {
|
|
3130
3250
|
return { path: declaredPath, kind: "unresolved" };
|
|
3131
3251
|
}
|
|
3132
|
-
return { path: declaredPath, kind: existsSync(
|
|
3252
|
+
return { path: declaredPath, kind: existsSync(join6(real, ".git")) ? "repo" : "non-repo" };
|
|
3133
3253
|
}
|
|
3134
3254
|
async function doRunProjectAdopt(options, ctx) {
|
|
3135
3255
|
const cwd = ctx.cwd ?? process.cwd();
|
|
@@ -3224,11 +3344,11 @@ async function gatherRepoWiring(repositoryRoot, entry) {
|
|
|
3224
3344
|
};
|
|
3225
3345
|
let real;
|
|
3226
3346
|
try {
|
|
3227
|
-
real = realpathSync(
|
|
3347
|
+
real = realpathSync(resolve7(repositoryRoot, entry.path));
|
|
3228
3348
|
} catch {
|
|
3229
3349
|
return { ...base, reachable: false, instructionFiles: [] };
|
|
3230
3350
|
}
|
|
3231
|
-
if (!existsSync(
|
|
3351
|
+
if (!existsSync(join6(real, ".git"))) {
|
|
3232
3352
|
return { ...base, reachable: false, instructionFiles: [] };
|
|
3233
3353
|
}
|
|
3234
3354
|
try {
|
|
@@ -3236,7 +3356,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
|
|
|
3236
3356
|
for (const name of INSTRUCTION_FILES) {
|
|
3237
3357
|
let present = true;
|
|
3238
3358
|
try {
|
|
3239
|
-
lstatSync(
|
|
3359
|
+
lstatSync(join6(real, name));
|
|
3240
3360
|
} catch {
|
|
3241
3361
|
present = false;
|
|
3242
3362
|
}
|
|
@@ -3329,14 +3449,14 @@ function gatherRepoGitignore(repositoryRoot, entry) {
|
|
|
3329
3449
|
};
|
|
3330
3450
|
let real;
|
|
3331
3451
|
try {
|
|
3332
|
-
real = realpathSync(
|
|
3452
|
+
real = realpathSync(resolve7(repositoryRoot, entry.path));
|
|
3333
3453
|
} catch {
|
|
3334
3454
|
return { ...base, reachable: false, currentLines: [] };
|
|
3335
3455
|
}
|
|
3336
|
-
if (!existsSync(
|
|
3456
|
+
if (!existsSync(join6(real, ".git"))) {
|
|
3337
3457
|
return { ...base, reachable: false, currentLines: [] };
|
|
3338
3458
|
}
|
|
3339
|
-
return { ...base, reachable: true, currentLines: readGitignoreLines(
|
|
3459
|
+
return { ...base, reachable: true, currentLines: readGitignoreLines(join6(real, ".gitignore")) };
|
|
3340
3460
|
}
|
|
3341
3461
|
function hasErrorCode(error) {
|
|
3342
3462
|
return error instanceof Error && typeof error.code === "string";
|
|
@@ -3350,7 +3470,7 @@ function readGitignoreLines(file) {
|
|
|
3350
3470
|
}
|
|
3351
3471
|
}
|
|
3352
3472
|
function applyGitignorePlan(repositoryRoot, plan) {
|
|
3353
|
-
const file =
|
|
3473
|
+
const file = join6(realpathSync(resolve7(repositoryRoot, plan.path)), ".gitignore");
|
|
3354
3474
|
let existing = "";
|
|
3355
3475
|
try {
|
|
3356
3476
|
existing = readFileSync(file, "utf8");
|
|
@@ -3462,23 +3582,23 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
|
|
|
3462
3582
|
const base = { path: entry.path };
|
|
3463
3583
|
let real;
|
|
3464
3584
|
try {
|
|
3465
|
-
real = realpathSync(
|
|
3585
|
+
real = realpathSync(resolve7(repositoryRoot, entry.path));
|
|
3466
3586
|
} catch {
|
|
3467
3587
|
return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
|
|
3468
3588
|
}
|
|
3469
3589
|
if (real === anchorReal) {
|
|
3470
3590
|
return { ...base, isAnchor: true, reachable: true, canonicalPresent: false, files: [] };
|
|
3471
3591
|
}
|
|
3472
|
-
if (!existsSync(
|
|
3592
|
+
if (!existsSync(join6(real, ".git"))) {
|
|
3473
3593
|
return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
|
|
3474
3594
|
}
|
|
3475
|
-
const canonicalFile =
|
|
3595
|
+
const canonicalFile = join6(anchorReal, "agents", basename4(real), CANONICAL_FILE);
|
|
3476
3596
|
if (!existsSync(canonicalFile)) {
|
|
3477
3597
|
return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
|
|
3478
3598
|
}
|
|
3479
3599
|
const files = expectedSymlinkTargets(real, canonicalFile).map(
|
|
3480
3600
|
(spec) => {
|
|
3481
|
-
const { state, actualTarget } = inspectSymlink(
|
|
3601
|
+
const { state, actualTarget } = inspectSymlink(join6(real, spec.name), spec.target);
|
|
3482
3602
|
return {
|
|
3483
3603
|
name: spec.name,
|
|
3484
3604
|
expectedTarget: spec.target,
|
|
@@ -3499,7 +3619,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
|
|
|
3499
3619
|
function applySymlinkPlan(repositoryRoot, plan) {
|
|
3500
3620
|
let real;
|
|
3501
3621
|
try {
|
|
3502
|
-
real = realpathSync(
|
|
3622
|
+
real = realpathSync(resolve7(repositoryRoot, plan.path));
|
|
3503
3623
|
} catch (error) {
|
|
3504
3624
|
const message = failureReason(error);
|
|
3505
3625
|
return { created: [], failed: plan.toCreate.map((c) => ({ file: c.name, message })) };
|
|
@@ -3507,9 +3627,9 @@ function applySymlinkPlan(repositoryRoot, plan) {
|
|
|
3507
3627
|
const created = [];
|
|
3508
3628
|
const failed = [];
|
|
3509
3629
|
for (const { name, target } of plan.toCreate) {
|
|
3510
|
-
const filePath =
|
|
3630
|
+
const filePath = join6(real, name);
|
|
3511
3631
|
try {
|
|
3512
|
-
mkdirSync(
|
|
3632
|
+
mkdirSync(dirname2(filePath), { recursive: true });
|
|
3513
3633
|
symlinkSync(target, filePath);
|
|
3514
3634
|
created.push(name);
|
|
3515
3635
|
} catch (error) {
|
|
@@ -3644,12 +3764,12 @@ async function runProjectWorkspace(options, ctx = {}) {
|
|
|
3644
3764
|
}
|
|
3645
3765
|
}
|
|
3646
3766
|
function resolveViewDir(repositoryRoot, viewPath) {
|
|
3647
|
-
const abs =
|
|
3767
|
+
const abs = resolve7(repositoryRoot, viewPath);
|
|
3648
3768
|
try {
|
|
3649
3769
|
return realpathSync(abs);
|
|
3650
3770
|
} catch {
|
|
3651
3771
|
try {
|
|
3652
|
-
return
|
|
3772
|
+
return join6(realpathSync(dirname2(abs)), basename4(abs));
|
|
3653
3773
|
} catch {
|
|
3654
3774
|
return abs;
|
|
3655
3775
|
}
|
|
@@ -3658,7 +3778,7 @@ function resolveViewDir(repositoryRoot, viewPath) {
|
|
|
3658
3778
|
function gatherViewRepo(repositoryRoot, viewDir, entry) {
|
|
3659
3779
|
let repoReal;
|
|
3660
3780
|
try {
|
|
3661
|
-
repoReal = realpathSync(
|
|
3781
|
+
repoReal = realpathSync(resolve7(repositoryRoot, entry.path));
|
|
3662
3782
|
} catch {
|
|
3663
3783
|
return { path: entry.path, reachable: false };
|
|
3664
3784
|
}
|
|
@@ -3667,7 +3787,7 @@ function gatherViewRepo(repositoryRoot, viewDir, entry) {
|
|
|
3667
3787
|
return { path: entry.path, reachable: false };
|
|
3668
3788
|
}
|
|
3669
3789
|
const linkName = basename4(repoReal);
|
|
3670
|
-
const { state, actualTarget } = inspectSymlink(
|
|
3790
|
+
const { state, actualTarget } = inspectSymlink(join6(viewDir, linkName), expectedTarget);
|
|
3671
3791
|
return {
|
|
3672
3792
|
path: entry.path,
|
|
3673
3793
|
reachable: true,
|
|
@@ -3681,9 +3801,9 @@ function applyViewPlan(viewDir, toCreate) {
|
|
|
3681
3801
|
const created = [];
|
|
3682
3802
|
const failed = [];
|
|
3683
3803
|
for (const { name, target } of toCreate) {
|
|
3684
|
-
const filePath =
|
|
3804
|
+
const filePath = join6(viewDir, name);
|
|
3685
3805
|
try {
|
|
3686
|
-
mkdirSync(
|
|
3806
|
+
mkdirSync(dirname2(filePath), { recursive: true });
|
|
3687
3807
|
symlinkSync(target, filePath);
|
|
3688
3808
|
created.push(name);
|
|
3689
3809
|
} catch (error) {
|
|
@@ -3696,7 +3816,7 @@ var TOP_LEVEL_INSTRUCTION_FILES_LOWER = new Set(
|
|
|
3696
3816
|
INSTRUCTION_FILES.filter((f) => !f.includes("/")).map((f) => f.toLowerCase())
|
|
3697
3817
|
);
|
|
3698
3818
|
function classifyViewLink(viewDir, name, rosterRealpaths) {
|
|
3699
|
-
const filePath =
|
|
3819
|
+
const filePath = join6(viewDir, name);
|
|
3700
3820
|
let isLink;
|
|
3701
3821
|
try {
|
|
3702
3822
|
isLink = lstatSync(filePath).isSymbolicLink();
|
|
@@ -3710,12 +3830,12 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
|
|
|
3710
3830
|
} catch {
|
|
3711
3831
|
return null;
|
|
3712
3832
|
}
|
|
3713
|
-
const resolved =
|
|
3833
|
+
const resolved = isAbsolute3(target) ? target : resolve7(viewDir, target);
|
|
3714
3834
|
try {
|
|
3715
3835
|
if (rosterRealpaths.has(realpathSync(resolved))) return null;
|
|
3716
3836
|
} catch {
|
|
3717
3837
|
}
|
|
3718
|
-
if (
|
|
3838
|
+
if (isAbsolute3(target)) return { target, kind: "absolute" };
|
|
3719
3839
|
let isDir = false;
|
|
3720
3840
|
try {
|
|
3721
3841
|
isDir = statSync(resolved).isDirectory();
|
|
@@ -3725,7 +3845,7 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
|
|
|
3725
3845
|
if (!isDir) {
|
|
3726
3846
|
return { target, kind: existsSync(resolved) ? "non-repo" : "broken" };
|
|
3727
3847
|
}
|
|
3728
|
-
return { target, kind: existsSync(
|
|
3848
|
+
return { target, kind: existsSync(join6(resolved, ".git")) ? "repo" : "non-repo" };
|
|
3729
3849
|
}
|
|
3730
3850
|
function gatherExistingViewLinks(viewDir, rosterRealpaths) {
|
|
3731
3851
|
let names;
|
|
@@ -3750,7 +3870,7 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
|
|
|
3750
3870
|
const pruned = [];
|
|
3751
3871
|
const failed = [];
|
|
3752
3872
|
for (const { name } of toPrune) {
|
|
3753
|
-
const filePath =
|
|
3873
|
+
const filePath = join6(viewDir, name);
|
|
3754
3874
|
const c = classifyViewLink(viewDir, name, rosterRealpaths);
|
|
3755
3875
|
if (c === null || c.kind !== "repo") {
|
|
3756
3876
|
failed.push({
|
|
@@ -3796,11 +3916,11 @@ async function doRunProjectWorkspace(options, ctx) {
|
|
|
3796
3916
|
} else {
|
|
3797
3917
|
const viewDir = resolveViewDir(repositoryRoot, viewPath);
|
|
3798
3918
|
const facts = roster.map((entry) => gatherViewRepo(repositoryRoot, viewDir, entry));
|
|
3799
|
-
const rosterNames = roster.map((entry) => basename4(
|
|
3919
|
+
const rosterNames = roster.map((entry) => basename4(resolve7(repositoryRoot, entry.path)));
|
|
3800
3920
|
const rosterRealpaths = /* @__PURE__ */ new Set();
|
|
3801
3921
|
for (const entry of roster) {
|
|
3802
3922
|
try {
|
|
3803
|
-
rosterRealpaths.add(realpathSync(
|
|
3923
|
+
rosterRealpaths.add(realpathSync(resolve7(repositoryRoot, entry.path)));
|
|
3804
3924
|
} catch {
|
|
3805
3925
|
}
|
|
3806
3926
|
}
|
|
@@ -3961,10 +4081,10 @@ async function runProjectPreset(options, ctx = {}) {
|
|
|
3961
4081
|
}
|
|
3962
4082
|
}
|
|
3963
4083
|
function canonicalFileFor(anchorReal, canonicalName) {
|
|
3964
|
-
return
|
|
4084
|
+
return join6(anchorReal, "agents", canonicalName, CANONICAL_FILE);
|
|
3965
4085
|
}
|
|
3966
4086
|
function canonicalLabelFor(canonicalName) {
|
|
3967
|
-
return
|
|
4087
|
+
return join6("agents", canonicalName, CANONICAL_FILE);
|
|
3968
4088
|
}
|
|
3969
4089
|
async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
|
|
3970
4090
|
const declared = {
|
|
@@ -3975,14 +4095,14 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
|
|
|
3975
4095
|
};
|
|
3976
4096
|
let real;
|
|
3977
4097
|
try {
|
|
3978
|
-
real = realpathSync(
|
|
4098
|
+
real = realpathSync(resolve7(repositoryRoot, entry.path));
|
|
3979
4099
|
} catch {
|
|
3980
4100
|
return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
|
|
3981
4101
|
}
|
|
3982
4102
|
if (real === anchorReal) {
|
|
3983
4103
|
return { ...declared, isAnchor: true, reachable: true, canonicalPresent: false };
|
|
3984
4104
|
}
|
|
3985
|
-
if (!existsSync(
|
|
4105
|
+
if (!existsSync(join6(real, ".git"))) {
|
|
3986
4106
|
return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
|
|
3987
4107
|
}
|
|
3988
4108
|
const canonicalName = basename4(real);
|
|
@@ -4030,7 +4150,7 @@ async function applyPresetPlan(anchorReal, plan) {
|
|
|
4030
4150
|
isLink = false;
|
|
4031
4151
|
}
|
|
4032
4152
|
if (isLink) throw new Error(`Canonical is a symlink in ${label}`);
|
|
4033
|
-
if (plan.action === "create") mkdirSync(
|
|
4153
|
+
if (plan.action === "create") mkdirSync(dirname2(file), { recursive: true });
|
|
4034
4154
|
const existing = await readMarkdownFile4(file);
|
|
4035
4155
|
await writeMarkdownFile5(file, renderWithMarkers4(existing, plan.desiredBlock, label));
|
|
4036
4156
|
}
|
|
@@ -4204,7 +4324,7 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
|
|
|
4204
4324
|
};
|
|
4205
4325
|
let real;
|
|
4206
4326
|
try {
|
|
4207
|
-
real = realpathSync(
|
|
4327
|
+
real = realpathSync(resolve7(repositoryRoot, target));
|
|
4208
4328
|
} catch {
|
|
4209
4329
|
return empty;
|
|
4210
4330
|
}
|
|
@@ -4213,24 +4333,24 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
|
|
|
4213
4333
|
const instructionFiles = [];
|
|
4214
4334
|
for (const name of INSTRUCTION_FILES) {
|
|
4215
4335
|
try {
|
|
4216
|
-
lstatSync(
|
|
4336
|
+
lstatSync(join6(real, name));
|
|
4217
4337
|
instructionFiles.push(name);
|
|
4218
4338
|
} catch {
|
|
4219
4339
|
}
|
|
4220
4340
|
}
|
|
4221
4341
|
let ignored;
|
|
4222
4342
|
try {
|
|
4223
|
-
ignored = new Set(readGitignoreLines(
|
|
4343
|
+
ignored = new Set(readGitignoreLines(join6(real, ".gitignore")).map((l) => l.trim()));
|
|
4224
4344
|
} catch {
|
|
4225
4345
|
ignored = /* @__PURE__ */ new Set();
|
|
4226
4346
|
}
|
|
4227
4347
|
const gitignorePatterns = INSTRUCTION_FILES.filter((p) => ignored.has(p) || ignored.has(`/${p}`));
|
|
4228
|
-
const canonical2 = existsSync(
|
|
4348
|
+
const canonical2 = existsSync(join6(anchorReal, "agents", canonicalName, CANONICAL_FILE));
|
|
4229
4349
|
let viewLink = false;
|
|
4230
4350
|
const viewPath = manifest.workspace.view;
|
|
4231
4351
|
if (viewPath !== void 0) {
|
|
4232
4352
|
try {
|
|
4233
|
-
lstatSync(
|
|
4353
|
+
lstatSync(join6(resolveViewDir(repositoryRoot, viewPath), canonicalName));
|
|
4234
4354
|
viewLink = true;
|
|
4235
4355
|
} catch {
|
|
4236
4356
|
}
|
|
@@ -4272,7 +4392,7 @@ async function doRunProjectArchive(target, options, ctx) {
|
|
|
4272
4392
|
const roster = manifest.repos ?? [];
|
|
4273
4393
|
let targetIsAnchor = false;
|
|
4274
4394
|
try {
|
|
4275
|
-
targetIsAnchor = realpathSync(
|
|
4395
|
+
targetIsAnchor = realpathSync(resolve7(repositoryRoot, target)) === realpathSync(repositoryRoot);
|
|
4276
4396
|
} catch {
|
|
4277
4397
|
targetIsAnchor = false;
|
|
4278
4398
|
}
|
|
@@ -4392,12 +4512,12 @@ function gatherRenameWiring(repositoryRoot, manifest, oldBasename) {
|
|
|
4392
4512
|
} catch {
|
|
4393
4513
|
return { canonicalDirOld: false, viewLinkOld: false };
|
|
4394
4514
|
}
|
|
4395
|
-
const canonicalDirOld = existsSync(
|
|
4515
|
+
const canonicalDirOld = existsSync(join6(anchorReal, "agents", oldBasename));
|
|
4396
4516
|
let viewLinkOld = false;
|
|
4397
4517
|
const viewPath = manifest.workspace.view;
|
|
4398
4518
|
if (viewPath !== void 0) {
|
|
4399
4519
|
try {
|
|
4400
|
-
lstatSync(
|
|
4520
|
+
lstatSync(join6(resolveViewDir(repositoryRoot, viewPath), oldBasename));
|
|
4401
4521
|
viewLinkOld = true;
|
|
4402
4522
|
} catch {
|
|
4403
4523
|
}
|
|
@@ -4423,7 +4543,7 @@ async function doRunProjectRename(oldPath, newPath, options, ctx) {
|
|
|
4423
4543
|
const roster = manifest.repos ?? [];
|
|
4424
4544
|
let oldIsAnchor = false;
|
|
4425
4545
|
try {
|
|
4426
|
-
oldIsAnchor = realpathSync(
|
|
4546
|
+
oldIsAnchor = realpathSync(resolve7(repositoryRoot, oldPath)) === realpathSync(repositoryRoot);
|
|
4427
4547
|
} catch {
|
|
4428
4548
|
oldIsAnchor = false;
|
|
4429
4549
|
}
|
|
@@ -4546,7 +4666,7 @@ import {
|
|
|
4546
4666
|
// src/lib/durable-write.ts
|
|
4547
4667
|
import { randomUUID } from "crypto";
|
|
4548
4668
|
import { lstat, open, rename, stat as stat3, unlink as unlink2 } from "fs/promises";
|
|
4549
|
-
import { basename as basename5, dirname as
|
|
4669
|
+
import { basename as basename5, dirname as dirname3, join as join7 } from "path";
|
|
4550
4670
|
async function assertNotSymlink(targetPath) {
|
|
4551
4671
|
try {
|
|
4552
4672
|
const st = await lstat(targetPath);
|
|
@@ -4561,8 +4681,8 @@ async function assertNotSymlink(targetPath) {
|
|
|
4561
4681
|
}
|
|
4562
4682
|
}
|
|
4563
4683
|
async function writeFileDurable(targetPath, content) {
|
|
4564
|
-
const dir =
|
|
4565
|
-
const tmpPath =
|
|
4684
|
+
const dir = dirname3(targetPath);
|
|
4685
|
+
const tmpPath = join7(dir, `.${basename5(targetPath)}.tmp.${randomUUID()}`);
|
|
4566
4686
|
let mode = 420;
|
|
4567
4687
|
try {
|
|
4568
4688
|
mode = (await stat3(targetPath)).mode & 511;
|
|
@@ -4597,25 +4717,25 @@ async function writeFileDurable(targetPath, content) {
|
|
|
4597
4717
|
}
|
|
4598
4718
|
|
|
4599
4719
|
// src/lib/protocols-config.ts
|
|
4600
|
-
import { homedir as
|
|
4601
|
-
import { isAbsolute as
|
|
4602
|
-
import { readYamlFile as
|
|
4603
|
-
var DEFAULT_PROTOCOLS_CONFIG_PATH =
|
|
4604
|
-
var DEFAULT_TARGET_PATH =
|
|
4720
|
+
import { homedir as homedir6 } from "os";
|
|
4721
|
+
import { isAbsolute as isAbsolute4, join as join8, resolve as resolve8 } from "path";
|
|
4722
|
+
import { readYamlFile as readYamlFile5 } from "@basou/core";
|
|
4723
|
+
var DEFAULT_PROTOCOLS_CONFIG_PATH = join8(homedir6(), ".basou", "protocols.yaml");
|
|
4724
|
+
var DEFAULT_TARGET_PATH = join8(homedir6(), ".claude", "CLAUDE.md");
|
|
4605
4725
|
var ALLOWED_TOP_KEYS = /* @__PURE__ */ new Set(["version", "protocols"]);
|
|
4606
4726
|
var ALLOWED_ENTRY_KEYS = /* @__PURE__ */ new Set(["source", "title"]);
|
|
4607
|
-
function
|
|
4608
|
-
if (p === "~") return
|
|
4609
|
-
if (p.startsWith("~/")) return
|
|
4727
|
+
function expandTilde3(p) {
|
|
4728
|
+
if (p === "~") return homedir6();
|
|
4729
|
+
if (p.startsWith("~/")) return join8(homedir6(), p.slice(2));
|
|
4610
4730
|
return p;
|
|
4611
4731
|
}
|
|
4612
|
-
function
|
|
4732
|
+
function isRecord3(value) {
|
|
4613
4733
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4614
4734
|
}
|
|
4615
4735
|
async function loadProtocolsConfig(configPath = DEFAULT_PROTOCOLS_CONFIG_PATH) {
|
|
4616
4736
|
let raw;
|
|
4617
4737
|
try {
|
|
4618
|
-
raw = await
|
|
4738
|
+
raw = await readYamlFile5(configPath);
|
|
4619
4739
|
} catch (error) {
|
|
4620
4740
|
if (error instanceof Error && error.message === "YAML file not found") {
|
|
4621
4741
|
throw new Error(
|
|
@@ -4627,7 +4747,7 @@ async function loadProtocolsConfig(configPath = DEFAULT_PROTOCOLS_CONFIG_PATH) {
|
|
|
4627
4747
|
}
|
|
4628
4748
|
throw error;
|
|
4629
4749
|
}
|
|
4630
|
-
if (!
|
|
4750
|
+
if (!isRecord3(raw) || !Array.isArray(raw.protocols)) {
|
|
4631
4751
|
throw new Error("~/.basou/protocols.yaml must contain a 'protocols:' list.");
|
|
4632
4752
|
}
|
|
4633
4753
|
for (const key of Object.keys(raw)) {
|
|
@@ -4640,7 +4760,7 @@ async function loadProtocolsConfig(configPath = DEFAULT_PROTOCOLS_CONFIG_PATH) {
|
|
|
4640
4760
|
const seen = /* @__PURE__ */ new Set();
|
|
4641
4761
|
const result = [];
|
|
4642
4762
|
for (const entry of raw.protocols) {
|
|
4643
|
-
if (!
|
|
4763
|
+
if (!isRecord3(entry)) {
|
|
4644
4764
|
throw new Error("Each protocol entry must be a mapping with a 'source' key.");
|
|
4645
4765
|
}
|
|
4646
4766
|
for (const key of Object.keys(entry)) {
|
|
@@ -4654,11 +4774,11 @@ async function loadProtocolsConfig(configPath = DEFAULT_PROTOCOLS_CONFIG_PATH) {
|
|
|
4654
4774
|
if (entry.title !== void 0 && (typeof entry.title !== "string" || entry.title.trim().length === 0)) {
|
|
4655
4775
|
throw new Error("A protocol entry 'title' must be a non-empty string when present.");
|
|
4656
4776
|
}
|
|
4657
|
-
const expanded =
|
|
4658
|
-
if (!
|
|
4777
|
+
const expanded = expandTilde3(entry.source.trim());
|
|
4778
|
+
if (!isAbsolute4(expanded)) {
|
|
4659
4779
|
throw new Error("Protocol 'source' paths must be absolute (or start with '~').");
|
|
4660
4780
|
}
|
|
4661
|
-
const abs =
|
|
4781
|
+
const abs = resolve8(expanded);
|
|
4662
4782
|
if (seen.has(abs)) {
|
|
4663
4783
|
throw new Error("Duplicate protocol source (each source path may appear only once).");
|
|
4664
4784
|
}
|
|
@@ -4858,16 +4978,16 @@ import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
|
|
|
4858
4978
|
|
|
4859
4979
|
// src/commands/refresh-watch.ts
|
|
4860
4980
|
import { readdir as readdir2, stat as stat4 } from "fs/promises";
|
|
4861
|
-
import { homedir as
|
|
4862
|
-
import { join as
|
|
4981
|
+
import { homedir as homedir7 } from "os";
|
|
4982
|
+
import { join as join9 } from "path";
|
|
4863
4983
|
import { findErrorCode as findErrorCode8 } from "@basou/core";
|
|
4864
4984
|
var DEFAULT_WATCH_INTERVAL_SEC = 30;
|
|
4865
4985
|
var MIN_WATCH_INTERVAL_SEC = 5;
|
|
4866
4986
|
var MAX_WATCH_INTERVAL_SEC = 86400;
|
|
4867
4987
|
function watchedRoots(ctx) {
|
|
4868
4988
|
return [
|
|
4869
|
-
ctx.codexSessionsDir ??
|
|
4870
|
-
ctx.claudeProjectsDir ??
|
|
4989
|
+
ctx.codexSessionsDir ?? join9(homedir7(), ".codex", "sessions"),
|
|
4990
|
+
ctx.claudeProjectsDir ?? join9(homedir7(), ".claude", "projects")
|
|
4871
4991
|
];
|
|
4872
4992
|
}
|
|
4873
4993
|
async function scanSourceLogs(roots) {
|
|
@@ -4881,7 +5001,7 @@ async function scanSourceLogs(roots) {
|
|
|
4881
5001
|
throw new Error("Failed to read a source log directory", { cause: error });
|
|
4882
5002
|
}
|
|
4883
5003
|
for (const entry of entries) {
|
|
4884
|
-
const full =
|
|
5004
|
+
const full = join9(dir, entry.name);
|
|
4885
5005
|
if (entry.isDirectory()) {
|
|
4886
5006
|
await walk(full);
|
|
4887
5007
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
@@ -4988,19 +5108,19 @@ function parseInterval(value) {
|
|
|
4988
5108
|
return seconds;
|
|
4989
5109
|
}
|
|
4990
5110
|
function abortableSleep(ms, signal) {
|
|
4991
|
-
return new Promise((
|
|
5111
|
+
return new Promise((resolve12) => {
|
|
4992
5112
|
if (signal.aborted) {
|
|
4993
|
-
|
|
5113
|
+
resolve12();
|
|
4994
5114
|
return;
|
|
4995
5115
|
}
|
|
4996
5116
|
let timer;
|
|
4997
5117
|
const onAbort = () => {
|
|
4998
5118
|
clearTimeout(timer);
|
|
4999
|
-
|
|
5119
|
+
resolve12();
|
|
5000
5120
|
};
|
|
5001
5121
|
timer = setTimeout(() => {
|
|
5002
5122
|
signal.removeEventListener("abort", onAbort);
|
|
5003
|
-
|
|
5123
|
+
resolve12();
|
|
5004
5124
|
}, ms);
|
|
5005
5125
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
5006
5126
|
});
|
|
@@ -5199,7 +5319,7 @@ async function assertWorkspaceInitialized8(basouRoot) {
|
|
|
5199
5319
|
}
|
|
5200
5320
|
|
|
5201
5321
|
// src/commands/report.ts
|
|
5202
|
-
import { isAbsolute as
|
|
5322
|
+
import { isAbsolute as isAbsolute5, resolve as resolve9 } from "path";
|
|
5203
5323
|
import {
|
|
5204
5324
|
assertBasouRootSafe as assertBasouRootSafe10,
|
|
5205
5325
|
basouPaths as basouPaths12,
|
|
@@ -5239,7 +5359,7 @@ async function doRunReportGenerate(options, ctx) {
|
|
|
5239
5359
|
onTaskSkip: (taskId, reason) => printTaskSkip(taskId, reason)
|
|
5240
5360
|
});
|
|
5241
5361
|
if (options.out !== void 0) {
|
|
5242
|
-
const outPath =
|
|
5362
|
+
const outPath = isAbsolute5(options.out) ? options.out : resolve9(cwd, options.out);
|
|
5243
5363
|
await writeMarkdownFile6(outPath, result.body);
|
|
5244
5364
|
const { sessions, decisions, tasks } = result.data;
|
|
5245
5365
|
console.error(
|
|
@@ -5402,8 +5522,8 @@ function renderReviewGaps(summary) {
|
|
|
5402
5522
|
|
|
5403
5523
|
// src/commands/run.ts
|
|
5404
5524
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
5405
|
-
import { homedir as
|
|
5406
|
-
import { join as
|
|
5525
|
+
import { homedir as homedir8 } from "os";
|
|
5526
|
+
import { join as join10 } from "path";
|
|
5407
5527
|
import {
|
|
5408
5528
|
acquireLock as acquireLock5,
|
|
5409
5529
|
assertBasouRootSafe as assertBasouRootSafe11,
|
|
@@ -5417,7 +5537,7 @@ import {
|
|
|
5417
5537
|
overwriteYamlFile as overwriteYamlFile2,
|
|
5418
5538
|
prefixedUlid as prefixedUlid4,
|
|
5419
5539
|
readManifest as readManifest7,
|
|
5420
|
-
readYamlFile as
|
|
5540
|
+
readYamlFile as readYamlFile6,
|
|
5421
5541
|
resolveClaudeCodeCommand,
|
|
5422
5542
|
resolveRepositoryRoot as resolveRepositoryRoot9,
|
|
5423
5543
|
SessionSchema as SessionSchema2,
|
|
@@ -5456,13 +5576,13 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
5456
5576
|
await assertBasouRootSafe11(paths.root);
|
|
5457
5577
|
const manifest = await readManifest7(paths);
|
|
5458
5578
|
const sessionId = prefixedUlid4("ses");
|
|
5459
|
-
const sessionDir =
|
|
5579
|
+
const sessionDir = join10(paths.sessions, sessionId);
|
|
5460
5580
|
await mkdir2(sessionDir, { recursive: true });
|
|
5461
5581
|
const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
|
|
5462
5582
|
await coreAppendChainedEvent2(paths, sessionId, event);
|
|
5463
5583
|
});
|
|
5464
5584
|
const startedAt = now().toISOString();
|
|
5465
|
-
const sessionYamlPath =
|
|
5585
|
+
const sessionYamlPath = join10(sessionDir, "session.yaml");
|
|
5466
5586
|
const session = buildInitialSession2({
|
|
5467
5587
|
id: sessionId,
|
|
5468
5588
|
command,
|
|
@@ -5588,7 +5708,7 @@ async function runClaudeCode(args, options, ctx = {}) {
|
|
|
5588
5708
|
const rawRelated = computeRelatedFiles(preSnapshot, postSnapshot, diff);
|
|
5589
5709
|
const relatedFiles = sanitizeRelatedFiles(rawRelated, {
|
|
5590
5710
|
workingDirectory: repoRoot,
|
|
5591
|
-
homedir:
|
|
5711
|
+
homedir: homedir8()
|
|
5592
5712
|
}).sanitized;
|
|
5593
5713
|
const finalStatus = decideFinalStatus2(result, signalReceived);
|
|
5594
5714
|
await appendEvent(sessionDir, {
|
|
@@ -5732,7 +5852,7 @@ function buildInitialSession2(input) {
|
|
|
5732
5852
|
source: { ...claudeCodeAdapterMetadata },
|
|
5733
5853
|
started_at: input.startedAt,
|
|
5734
5854
|
status: "initialized",
|
|
5735
|
-
working_directory: sanitizeWorkingDirectory2(input.cwd, { homedir:
|
|
5855
|
+
working_directory: sanitizeWorkingDirectory2(input.cwd, { homedir: homedir8() }),
|
|
5736
5856
|
invocation: {
|
|
5737
5857
|
command: input.command,
|
|
5738
5858
|
args: [...input.args],
|
|
@@ -5744,7 +5864,7 @@ function buildInitialSession2(input) {
|
|
|
5744
5864
|
};
|
|
5745
5865
|
}
|
|
5746
5866
|
async function mutateSessionYaml2(filePath, mutator) {
|
|
5747
|
-
const raw = await
|
|
5867
|
+
const raw = await readYamlFile6(filePath);
|
|
5748
5868
|
const parsed = SessionSchema2.parse(raw);
|
|
5749
5869
|
mutator(parsed);
|
|
5750
5870
|
const validated = SessionSchema2.parse(parsed);
|
|
@@ -5805,7 +5925,7 @@ async function resolveRepositoryRootForRun(cwd) {
|
|
|
5805
5925
|
|
|
5806
5926
|
// src/commands/session.ts
|
|
5807
5927
|
import { readFile as readFile4 } from "fs/promises";
|
|
5808
|
-
import { basename as basename6, isAbsolute as
|
|
5928
|
+
import { basename as basename6, isAbsolute as isAbsolute6, join as join11, relative as relative3 } from "path";
|
|
5809
5929
|
import {
|
|
5810
5930
|
acquireLock as acquireLock6,
|
|
5811
5931
|
appendEventToExistingSession as appendEventToExistingSession3,
|
|
@@ -5817,7 +5937,7 @@ import {
|
|
|
5817
5937
|
loadSessionEntries,
|
|
5818
5938
|
readAllEvents,
|
|
5819
5939
|
readManifest as readManifest8,
|
|
5820
|
-
readYamlFile as
|
|
5940
|
+
readYamlFile as readYamlFile7,
|
|
5821
5941
|
rechainSessionInPlace,
|
|
5822
5942
|
resolveSessionId as resolveSessionId3,
|
|
5823
5943
|
resolveTaskId,
|
|
@@ -5930,11 +6050,11 @@ async function doRunSessionShow(idInput, options, ctx) {
|
|
|
5930
6050
|
const paths = basouPaths15(repositoryRoot);
|
|
5931
6051
|
await assertWorkspaceInitialized10(paths.root);
|
|
5932
6052
|
const sessionId = await resolveSessionId3(paths, idInput);
|
|
5933
|
-
const sessionDir =
|
|
5934
|
-
const sessionYamlPath =
|
|
6053
|
+
const sessionDir = join11(paths.sessions, sessionId);
|
|
6054
|
+
const sessionYamlPath = join11(sessionDir, "session.yaml");
|
|
5935
6055
|
let session;
|
|
5936
6056
|
try {
|
|
5937
|
-
const raw = await
|
|
6057
|
+
const raw = await readYamlFile7(sessionYamlPath);
|
|
5938
6058
|
session = SessionSchema3.parse(raw);
|
|
5939
6059
|
} catch (error) {
|
|
5940
6060
|
if (findErrorCode11(error, "ENOENT")) {
|
|
@@ -6052,7 +6172,7 @@ function formatSessionWork(session, events, now) {
|
|
|
6052
6172
|
}
|
|
6053
6173
|
function formatWorkingDir(workingDir, repositoryRoot, options) {
|
|
6054
6174
|
if (options.fullPath === true) return workingDir;
|
|
6055
|
-
if (!
|
|
6175
|
+
if (!isAbsolute6(workingDir)) {
|
|
6056
6176
|
if (workingDir === ".") return "<repository_root>";
|
|
6057
6177
|
return workingDir;
|
|
6058
6178
|
}
|
|
@@ -6669,7 +6789,7 @@ async function resolveRepositoryRootForStatus(cwd) {
|
|
|
6669
6789
|
|
|
6670
6790
|
// src/commands/task.ts
|
|
6671
6791
|
import { readFile as readFile5 } from "fs/promises";
|
|
6672
|
-
import { join as
|
|
6792
|
+
import { join as join12 } from "path";
|
|
6673
6793
|
import {
|
|
6674
6794
|
archiveTask,
|
|
6675
6795
|
assertBasouRootSafe as assertBasouRootSafe15,
|
|
@@ -6996,7 +7116,7 @@ async function doRunTaskShow(idInput, options, ctx) {
|
|
|
6996
7116
|
const events = [];
|
|
6997
7117
|
const linkedSessionIds = new Set(doc.task.task.linked_sessions);
|
|
6998
7118
|
for (const s of sessions) {
|
|
6999
|
-
const sessionDir =
|
|
7119
|
+
const sessionDir = join12(paths.sessions, s.sessionId);
|
|
7000
7120
|
try {
|
|
7001
7121
|
for await (const ev of replayEvents2(sessionDir, {
|
|
7002
7122
|
onWarning: (w) => printReplayWarning(w, s.sessionId)
|
|
@@ -7899,7 +8019,7 @@ async function assertWorkspaceInitialized13(basouRoot) {
|
|
|
7899
8019
|
// src/commands/view.ts
|
|
7900
8020
|
import { spawn } from "child_process";
|
|
7901
8021
|
import { createHash } from "crypto";
|
|
7902
|
-
import { basename as basename7, resolve as
|
|
8022
|
+
import { basename as basename7, resolve as resolve11 } from "path";
|
|
7903
8023
|
import {
|
|
7904
8024
|
assertBasouRootSafe as assertBasouRootSafe17,
|
|
7905
8025
|
basouPaths as basouPaths20,
|
|
@@ -7912,7 +8032,7 @@ import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
|
|
|
7912
8032
|
// src/lib/portfolio-safety.ts
|
|
7913
8033
|
import { execFile } from "child_process";
|
|
7914
8034
|
import { lstat as lstat2, realpath as realpath2 } from "fs/promises";
|
|
7915
|
-
import { isAbsolute as
|
|
8035
|
+
import { isAbsolute as isAbsolute7, join as join13, relative as relative4, resolve as resolve10 } from "path";
|
|
7916
8036
|
import { promisify } from "util";
|
|
7917
8037
|
import { readManifest as readManifest11 } from "@basou/core";
|
|
7918
8038
|
var execFileAsync = promisify(execFile);
|
|
@@ -7923,12 +8043,12 @@ async function canonical(p) {
|
|
|
7923
8043
|
try {
|
|
7924
8044
|
return await realpath2(p);
|
|
7925
8045
|
} catch {
|
|
7926
|
-
return
|
|
8046
|
+
return resolve10(p);
|
|
7927
8047
|
}
|
|
7928
8048
|
}
|
|
7929
8049
|
function isInside(child, parent) {
|
|
7930
8050
|
const rel = relative4(parent, child);
|
|
7931
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
8051
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute7(rel);
|
|
7932
8052
|
}
|
|
7933
8053
|
function isBasouPath(p) {
|
|
7934
8054
|
return p === ".basou" || p.startsWith(".basou/") || p.includes("/.basou/") || p.endsWith("/.basou");
|
|
@@ -7936,7 +8056,7 @@ function isBasouPath(p) {
|
|
|
7936
8056
|
async function inspectRepo(repoPath) {
|
|
7937
8057
|
let hasEntry = false;
|
|
7938
8058
|
try {
|
|
7939
|
-
await lstat2(
|
|
8059
|
+
await lstat2(join13(repoPath, ".basou"));
|
|
7940
8060
|
hasEntry = true;
|
|
7941
8061
|
} catch (error) {
|
|
7942
8062
|
if (errorCode(error) !== "ENOENT") {
|
|
@@ -7985,7 +8105,7 @@ async function checkPortfolioSafety(workspaces) {
|
|
|
7985
8105
|
}
|
|
7986
8106
|
const monitored = /* @__PURE__ */ new Map();
|
|
7987
8107
|
for (const root of sourceRoots) {
|
|
7988
|
-
const display =
|
|
8108
|
+
const display = resolve10(ws.repoRoot, root);
|
|
7989
8109
|
const real = await canonical(display);
|
|
7990
8110
|
if (real !== wsReal) monitored.set(real, display);
|
|
7991
8111
|
}
|
|
@@ -8037,7 +8157,7 @@ function formatSafetyReport(result) {
|
|
|
8037
8157
|
|
|
8038
8158
|
// src/lib/view-server.ts
|
|
8039
8159
|
import { createServer } from "http";
|
|
8040
|
-
import { join as
|
|
8160
|
+
import { join as join14 } from "path";
|
|
8041
8161
|
import {
|
|
8042
8162
|
computeWorkStats as computeWorkStats2,
|
|
8043
8163
|
enumerateApprovals as enumerateApprovals2,
|
|
@@ -8694,7 +8814,7 @@ function startViewServer(opts) {
|
|
|
8694
8814
|
};
|
|
8695
8815
|
let boundPort = port;
|
|
8696
8816
|
const getPort = () => boundPort;
|
|
8697
|
-
return new Promise((
|
|
8817
|
+
return new Promise((resolve12, reject) => {
|
|
8698
8818
|
const server = createServer((req, res) => {
|
|
8699
8819
|
handleRequest(req, res, deps, getPort, runExclusive).catch((error) => {
|
|
8700
8820
|
sendError(res, error instanceof HttpError ? error.status : 500, pathlessMessage(error));
|
|
@@ -8705,7 +8825,7 @@ function startViewServer(opts) {
|
|
|
8705
8825
|
const address = server.address();
|
|
8706
8826
|
boundPort = isAddressInfo(address) ? address.port : port;
|
|
8707
8827
|
server.off("error", reject);
|
|
8708
|
-
|
|
8828
|
+
resolve12({
|
|
8709
8829
|
url: `http://${host}:${boundPort}`,
|
|
8710
8830
|
port: boundPort,
|
|
8711
8831
|
close: () => closeServer(server)
|
|
@@ -8717,8 +8837,8 @@ function isAddressInfo(value) {
|
|
|
8717
8837
|
return value !== null && typeof value === "object";
|
|
8718
8838
|
}
|
|
8719
8839
|
function closeServer(server) {
|
|
8720
|
-
return new Promise((
|
|
8721
|
-
server.close(() =>
|
|
8840
|
+
return new Promise((resolve12) => {
|
|
8841
|
+
server.close(() => resolve12());
|
|
8722
8842
|
server.closeAllConnections();
|
|
8723
8843
|
});
|
|
8724
8844
|
}
|
|
@@ -8992,7 +9112,7 @@ async function sessionDetail(ws, sessionId) {
|
|
|
8992
9112
|
throw error;
|
|
8993
9113
|
}
|
|
8994
9114
|
try {
|
|
8995
|
-
const events = await readAllEvents2(
|
|
9115
|
+
const events = await readAllEvents2(join14(ws.paths.sessions, sessionId));
|
|
8996
9116
|
return { session, events };
|
|
8997
9117
|
} catch {
|
|
8998
9118
|
return { session, events: [], degraded: true };
|
|
@@ -9212,12 +9332,12 @@ async function buildSingleDeps(ctx, cwd) {
|
|
|
9212
9332
|
return { workspaces: [entry], mode: "single", nowProvider: nowProviderOf(ctx) };
|
|
9213
9333
|
}
|
|
9214
9334
|
async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
|
|
9215
|
-
const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path:
|
|
9335
|
+
const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve11(cwd, p) })) : await loadPortfolioConfig(ctx.portfolioConfigPath);
|
|
9216
9336
|
const entries = [];
|
|
9217
9337
|
const seenPath = /* @__PURE__ */ new Set();
|
|
9218
9338
|
const seenKey = /* @__PURE__ */ new Set();
|
|
9219
9339
|
for (const spec of specs) {
|
|
9220
|
-
const repoRoot =
|
|
9340
|
+
const repoRoot = resolve11(spec.path);
|
|
9221
9341
|
if (seenPath.has(repoRoot)) continue;
|
|
9222
9342
|
seenPath.add(repoRoot);
|
|
9223
9343
|
const entry = await buildWorkspaceEntry(repoRoot, ctx, spec.label);
|
|
@@ -9289,7 +9409,7 @@ function openInBrowser(url, override) {
|
|
|
9289
9409
|
}
|
|
9290
9410
|
}
|
|
9291
9411
|
function waitForShutdown(signal) {
|
|
9292
|
-
return new Promise((
|
|
9412
|
+
return new Promise((resolve12) => {
|
|
9293
9413
|
const cleanup = () => {
|
|
9294
9414
|
process.off("SIGINT", onSignal);
|
|
9295
9415
|
process.off("SIGTERM", onSignal);
|
|
@@ -9297,18 +9417,18 @@ function waitForShutdown(signal) {
|
|
|
9297
9417
|
};
|
|
9298
9418
|
const onSignal = () => {
|
|
9299
9419
|
cleanup();
|
|
9300
|
-
|
|
9420
|
+
resolve12();
|
|
9301
9421
|
};
|
|
9302
9422
|
const onAbort = () => {
|
|
9303
9423
|
cleanup();
|
|
9304
|
-
|
|
9424
|
+
resolve12();
|
|
9305
9425
|
};
|
|
9306
9426
|
process.on("SIGINT", onSignal);
|
|
9307
9427
|
process.on("SIGTERM", onSignal);
|
|
9308
9428
|
if (signal !== void 0) {
|
|
9309
9429
|
if (signal.aborted) {
|
|
9310
9430
|
cleanup();
|
|
9311
|
-
|
|
9431
|
+
resolve12();
|
|
9312
9432
|
return;
|
|
9313
9433
|
}
|
|
9314
9434
|
signal.addEventListener("abort", onAbort);
|