@basou/cli 0.40.0 → 0.41.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 +253 -71
- package/dist/index.js.map +1 -1
- package/dist/program.js +253 -71
- package/dist/program.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -710,8 +710,8 @@ ${block}${markers.end}
|
|
|
710
710
|
return `${section.before}${markers.start}
|
|
711
711
|
${block}${markers.end}${section.after}`;
|
|
712
712
|
case "no_markers": {
|
|
713
|
-
const
|
|
714
|
-
return `${existing}${
|
|
713
|
+
const sep2 = existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
714
|
+
return `${existing}${sep2}${wrapped}`;
|
|
715
715
|
}
|
|
716
716
|
default:
|
|
717
717
|
throw new Error(
|
|
@@ -2241,7 +2241,7 @@ async function assertWorkspaceInitialized4(basouRoot) {
|
|
|
2241
2241
|
}
|
|
2242
2242
|
|
|
2243
2243
|
// src/commands/hook.ts
|
|
2244
|
-
import { open as open2, readFile as readFile3, realpath as
|
|
2244
|
+
import { open as open2, readFile as readFile3, realpath as realpath3, stat as stat4 } from "fs/promises";
|
|
2245
2245
|
import { homedir as homedir7 } from "os";
|
|
2246
2246
|
import { join as join9 } from "path";
|
|
2247
2247
|
import { fileURLToPath } from "url";
|
|
@@ -2255,7 +2255,7 @@ import {
|
|
|
2255
2255
|
ORIENTATION_END as ORIENTATION_END2,
|
|
2256
2256
|
ORIENTATION_START as ORIENTATION_START2,
|
|
2257
2257
|
parseMarkers as parseMarkers2,
|
|
2258
|
-
readMarkdownFile as
|
|
2258
|
+
readMarkdownFile as readMarkdownFile6,
|
|
2259
2259
|
removeSessionStartHook,
|
|
2260
2260
|
removeStopHook,
|
|
2261
2261
|
upsertSessionStartHook,
|
|
@@ -2381,6 +2381,137 @@ function describeCodexHookTrust(trust) {
|
|
|
2381
2381
|
}
|
|
2382
2382
|
}
|
|
2383
2383
|
|
|
2384
|
+
// src/lib/foreign-workspace-warn.ts
|
|
2385
|
+
import { realpath as realpath2 } from "fs/promises";
|
|
2386
|
+
import { dirname as dirname2 } from "path";
|
|
2387
|
+
import { readMarkdownFile as readMarkdownFile4 } from "@basou/core";
|
|
2388
|
+
|
|
2389
|
+
// src/lib/foreign-workspace-scan.ts
|
|
2390
|
+
import { homedir as osHomedir } from "os";
|
|
2391
|
+
import { basename as basename3, normalize, sep } from "path";
|
|
2392
|
+
var MIN_TOKEN_LENGTH = 4;
|
|
2393
|
+
function stripTrailingSep(p) {
|
|
2394
|
+
return p.length > 1 && p.endsWith(sep) ? p.slice(0, -sep.length) : p;
|
|
2395
|
+
}
|
|
2396
|
+
function toTildePath(absPath, home) {
|
|
2397
|
+
if (absPath === home) return "~";
|
|
2398
|
+
if (absPath.startsWith(home + sep)) return `~${absPath.slice(home.length)}`;
|
|
2399
|
+
return null;
|
|
2400
|
+
}
|
|
2401
|
+
function encodedSpelling(name) {
|
|
2402
|
+
return name.replace(/[^a-zA-Z0-9]/g, "-");
|
|
2403
|
+
}
|
|
2404
|
+
function directoryNameSpellings(dir) {
|
|
2405
|
+
const paired = /^(.+)-(planning|workspace)$/.exec(dir);
|
|
2406
|
+
const plain = paired !== null ? [`${paired[1]}-planning`, `${paired[1]}-workspace`] : [dir, `${dir}-workspace`];
|
|
2407
|
+
return [...plain, ...plain.map(encodedSpelling)];
|
|
2408
|
+
}
|
|
2409
|
+
function nameTokensFor(workspacePath, home) {
|
|
2410
|
+
const abs = stripTrailingSep(normalize(workspacePath));
|
|
2411
|
+
const tokens = /* @__PURE__ */ new Set([abs]);
|
|
2412
|
+
const tilde = toTildePath(abs, home);
|
|
2413
|
+
if (tilde !== null) tokens.add(tilde);
|
|
2414
|
+
for (const name of directoryNameSpellings(basename3(abs))) tokens.add(name);
|
|
2415
|
+
return [...tokens].filter((t) => t.length >= MIN_TOKEN_LENGTH);
|
|
2416
|
+
}
|
|
2417
|
+
function isSpellingOfSelf(workspacePath, selfPath) {
|
|
2418
|
+
const own = new Set(directoryNameSpellings(basename3(stripTrailingSep(normalize(selfPath)))));
|
|
2419
|
+
return directoryNameSpellings(basename3(stripTrailingSep(normalize(workspacePath)))).some(
|
|
2420
|
+
(s) => own.has(s)
|
|
2421
|
+
);
|
|
2422
|
+
}
|
|
2423
|
+
function scanForeignWorkspaceNames(input) {
|
|
2424
|
+
if (input.text.length === 0 || input.workspacePaths.length === 0) return [];
|
|
2425
|
+
const home = input.homedir ?? osHomedir();
|
|
2426
|
+
const own = input.selfPath === void 0 ? [] : nameTokensFor(input.selfPath, home);
|
|
2427
|
+
const lines = input.text.split("\n");
|
|
2428
|
+
const hits = [];
|
|
2429
|
+
for (const workspacePath of input.workspacePaths) {
|
|
2430
|
+
if (input.selfPath !== void 0 && isSpellingOfSelf(workspacePath, input.selfPath)) continue;
|
|
2431
|
+
const tokens = nameTokensFor(workspacePath, home).filter(
|
|
2432
|
+
(token) => !own.some((ownToken) => ownToken.includes(token))
|
|
2433
|
+
);
|
|
2434
|
+
if (tokens.length === 0) continue;
|
|
2435
|
+
const matched = /* @__PURE__ */ new Set();
|
|
2436
|
+
const matchedLines = [];
|
|
2437
|
+
for (const [index, line] of lines.entries()) {
|
|
2438
|
+
let lineMatched = false;
|
|
2439
|
+
for (const token of tokens) {
|
|
2440
|
+
if (line.includes(token)) {
|
|
2441
|
+
matched.add(token);
|
|
2442
|
+
lineMatched = true;
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
if (lineMatched) matchedLines.push(index + 1);
|
|
2446
|
+
}
|
|
2447
|
+
if (matchedLines.length === 0) continue;
|
|
2448
|
+
hits.push({
|
|
2449
|
+
workspacePath,
|
|
2450
|
+
tokens: [...matched].sort((a, b) => b.length - a.length || a.localeCompare(b)),
|
|
2451
|
+
lines: matchedLines
|
|
2452
|
+
});
|
|
2453
|
+
}
|
|
2454
|
+
return hits;
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
// src/lib/foreign-workspace-warn.ts
|
|
2458
|
+
var MAX_LISTED_LINES = 5;
|
|
2459
|
+
async function canonicalize2(path) {
|
|
2460
|
+
try {
|
|
2461
|
+
return await realpath2(path);
|
|
2462
|
+
} catch {
|
|
2463
|
+
return path;
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
async function findForeignWorkspaceNames(args) {
|
|
2467
|
+
let workspacePaths;
|
|
2468
|
+
let selfPath;
|
|
2469
|
+
try {
|
|
2470
|
+
const workspaces = await loadPortfolioConfig(args.configPath);
|
|
2471
|
+
workspacePaths = await Promise.all(workspaces.map((w) => canonicalize2(w.path)));
|
|
2472
|
+
selfPath = args.selfPath === void 0 ? void 0 : await canonicalize2(args.selfPath);
|
|
2473
|
+
} catch {
|
|
2474
|
+
return null;
|
|
2475
|
+
}
|
|
2476
|
+
const hits = scanForeignWorkspaceNames({
|
|
2477
|
+
text: args.text,
|
|
2478
|
+
workspacePaths,
|
|
2479
|
+
selfPath
|
|
2480
|
+
});
|
|
2481
|
+
if (hits.length === 0) return null;
|
|
2482
|
+
const lines = [...new Set(hits.flatMap((h) => h.lines))].sort((a, b) => a - b);
|
|
2483
|
+
return { workspaceCount: hits.length, lines };
|
|
2484
|
+
}
|
|
2485
|
+
function describeForeignWorkspaceLines(lines) {
|
|
2486
|
+
const listed = lines.slice(0, MAX_LISTED_LINES).join(", ");
|
|
2487
|
+
const rest = lines.length - MAX_LISTED_LINES;
|
|
2488
|
+
const numbers = rest > 0 ? `${listed} and ${rest} more` : listed;
|
|
2489
|
+
return `${lines.length === 1 ? "line" : "lines"} ${numbers}`;
|
|
2490
|
+
}
|
|
2491
|
+
function positionForeignWorkspaceWarning(report, where) {
|
|
2492
|
+
const subject = report.workspaceCount === 1 ? "names another registered workspace" : `names ${report.workspaceCount} other registered workspaces`;
|
|
2493
|
+
return `basou: this workspace's position ${subject} (${where}, ${describeForeignWorkspaceLines(report.lines)}). The position is handed to the agent session running in this workspace, so those names travel with it. Advisory only: nothing was withheld.`;
|
|
2494
|
+
}
|
|
2495
|
+
function protocolForeignWorkspaceWarning(report) {
|
|
2496
|
+
const subject = report.workspaceCount === 1 ? "names a registered workspace" : `names ${report.workspaceCount} registered workspaces`;
|
|
2497
|
+
return `basou: the protocol block ${subject} (block ${describeForeignWorkspaceLines(report.lines)}, counted from the block's first line, not the file's). Standing protocols are written to the user-global CLAUDE.md, which every project on this machine loads, so a workspace-specific name there reaches every workspace's sessions. Only the basou-managed block is checked; the rest of that file is not basou's to inspect. Advisory only: nothing was withheld.`;
|
|
2498
|
+
}
|
|
2499
|
+
async function warnIfPositionNamesOtherWorkspaces(args) {
|
|
2500
|
+
try {
|
|
2501
|
+
const body = await readMarkdownFile4(args.paths.files.orientation);
|
|
2502
|
+
if (body === null) return;
|
|
2503
|
+
const report = await findForeignWorkspaceNames({
|
|
2504
|
+
text: body,
|
|
2505
|
+
selfPath: dirname2(args.paths.root),
|
|
2506
|
+
configPath: args.configPath
|
|
2507
|
+
});
|
|
2508
|
+
if (report !== null) {
|
|
2509
|
+
console.error(positionForeignWorkspaceWarning(report, args.paths.files.orientation));
|
|
2510
|
+
}
|
|
2511
|
+
} catch {
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2384
2515
|
// src/commands/orient.ts
|
|
2385
2516
|
import {
|
|
2386
2517
|
assertBasouRootSafe as assertBasouRootSafe7,
|
|
@@ -2448,7 +2579,7 @@ async function loadHostsConfig(configPath = DEFAULT_HOSTS_CONFIG_PATH) {
|
|
|
2448
2579
|
|
|
2449
2580
|
// src/lib/provenance-actions.ts
|
|
2450
2581
|
import {
|
|
2451
|
-
readMarkdownFile as
|
|
2582
|
+
readMarkdownFile as readMarkdownFile5,
|
|
2452
2583
|
renderDecisions as renderDecisions2,
|
|
2453
2584
|
renderHandoff as renderHandoff2,
|
|
2454
2585
|
renderOrientation,
|
|
@@ -2460,7 +2591,7 @@ import {
|
|
|
2460
2591
|
import { createReadStream } from "fs";
|
|
2461
2592
|
import { readdir, readFile as readFile2, rm, stat as stat3 } from "fs/promises";
|
|
2462
2593
|
import { homedir as homedir6 } from "os";
|
|
2463
|
-
import { basename as
|
|
2594
|
+
import { basename as basename4, dirname as dirname3, join as join8, resolve as resolve5 } from "path";
|
|
2464
2595
|
import { createInterface } from "readline";
|
|
2465
2596
|
import {
|
|
2466
2597
|
AGENT_INFRA_DIRS as AGENT_INFRA_DIRS2,
|
|
@@ -2550,7 +2681,7 @@ async function doRunImportClaudeCode(options, ctx) {
|
|
|
2550
2681
|
const files = await selectTranscriptFiles(projectsRoot, projectPaths, options);
|
|
2551
2682
|
const projectSet = new Set(projectPaths);
|
|
2552
2683
|
const candidates = files.map((file) => {
|
|
2553
|
-
const externalId =
|
|
2684
|
+
const externalId = basename4(file, ".jsonl");
|
|
2554
2685
|
return {
|
|
2555
2686
|
externalId,
|
|
2556
2687
|
sourcePath: file,
|
|
@@ -2640,7 +2771,7 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
|
|
|
2640
2771
|
files: payload.session.related_files ?? [],
|
|
2641
2772
|
workingDirectory: payload.session.working_directory,
|
|
2642
2773
|
sourceRoots: projectPaths,
|
|
2643
|
-
masterRoot:
|
|
2774
|
+
masterRoot: dirname3(paths.root),
|
|
2644
2775
|
extraInRoot: AGENT_INFRA_DIRS2
|
|
2645
2776
|
});
|
|
2646
2777
|
if (scope.outOfRoot.length > 0) crossProject.push({ externalId, outOfRoot: scope.outOfRoot });
|
|
@@ -3158,7 +3289,7 @@ function importCodex(options, ctx) {
|
|
|
3158
3289
|
}
|
|
3159
3290
|
async function regenerateHandoff(paths, nowIso, callbacks) {
|
|
3160
3291
|
const result = await renderHandoff2({ paths, nowIso, ...callbacks });
|
|
3161
|
-
const existing = await
|
|
3292
|
+
const existing = await readMarkdownFile5(paths.files.handoff);
|
|
3162
3293
|
await writeMarkdownFile3(
|
|
3163
3294
|
paths.files.handoff,
|
|
3164
3295
|
renderWithMarkers3(existing, result.body, "handoff.md")
|
|
@@ -3172,7 +3303,7 @@ async function regenerateHandoff(paths, nowIso, callbacks) {
|
|
|
3172
3303
|
}
|
|
3173
3304
|
async function regenerateDecisions(paths, nowIso, callbacks) {
|
|
3174
3305
|
const result = await renderDecisions2({ paths, nowIso, ...callbacks });
|
|
3175
|
-
const existing = await
|
|
3306
|
+
const existing = await readMarkdownFile5(paths.files.decisions);
|
|
3176
3307
|
await writeMarkdownFile3(
|
|
3177
3308
|
paths.files.decisions,
|
|
3178
3309
|
renderWithMarkers3(existing, result.body, "decisions.md")
|
|
@@ -3282,6 +3413,19 @@ async function doRunOrient(options, ctx) {
|
|
|
3282
3413
|
} else {
|
|
3283
3414
|
console.log(result.body);
|
|
3284
3415
|
}
|
|
3416
|
+
await warnIfPositionNamesOtherWorkspaces2(result, ctx);
|
|
3417
|
+
}
|
|
3418
|
+
async function warnIfPositionNamesOtherWorkspaces2(result, ctx) {
|
|
3419
|
+
const report = await findForeignWorkspaceNames({
|
|
3420
|
+
text: result.body,
|
|
3421
|
+
selfPath: result.workspaceRoot,
|
|
3422
|
+
configPath: ctx.portfolioConfigPath
|
|
3423
|
+
});
|
|
3424
|
+
if (report !== null) {
|
|
3425
|
+
console.error(
|
|
3426
|
+
positionForeignWorkspaceWarning(report, basouPaths8(result.workspaceRoot).files.orientation)
|
|
3427
|
+
);
|
|
3428
|
+
}
|
|
3285
3429
|
}
|
|
3286
3430
|
async function renderOrientationForCwd(options, ctx) {
|
|
3287
3431
|
const cwd = ctx.cwd ?? process.cwd();
|
|
@@ -3327,6 +3471,7 @@ async function renderOrientationForRoot(repositoryRoot, options, ctx, behaviour)
|
|
|
3327
3471
|
`);
|
|
3328
3472
|
return {
|
|
3329
3473
|
body: result.body,
|
|
3474
|
+
workspaceRoot: repositoryRoot,
|
|
3330
3475
|
sessionCount: result.sessionCount,
|
|
3331
3476
|
inFlightTaskCount: result.inFlightTaskCount,
|
|
3332
3477
|
pendingApprovalsCount: result.pendingApprovalsCount,
|
|
@@ -3351,7 +3496,7 @@ function registerHookCommand(program2) {
|
|
|
3351
3496
|
"Hook handlers for AI coding tools (Claude Code, Codex): read a hook payload on stdin, emit the tool's hook output on stdout"
|
|
3352
3497
|
);
|
|
3353
3498
|
hook.command("session-start").description(
|
|
3354
|
-
"Codex
|
|
3499
|
+
"SessionStart hook (Codex; Claude Code too): print the current position of the workspace the session was opened in (read from the payload's cwd) so the tool adds it to that session's context. Stays silent outside a registered basou workspace, and when the position names another registered workspace; never fails the session."
|
|
3355
3500
|
).addHelpText("after", HOOK_SESSION_START_HELP).action(async () => {
|
|
3356
3501
|
await runHookSessionStart();
|
|
3357
3502
|
});
|
|
@@ -3440,6 +3585,18 @@ desktop app has bound a folder, when cwd is '/') gets nothing. That is how one
|
|
|
3440
3585
|
user-global hook serves every workspace without any workspace's position ever
|
|
3441
3586
|
being written where another workspace's session would read it.
|
|
3442
3587
|
|
|
3588
|
+
The hook stays silent in one more case: when the position it would print names
|
|
3589
|
+
ANOTHER registered workspace (a recorded path under it, a captured decision that
|
|
3590
|
+
mentions it). 'basou orient' and 'basou refresh' report that as a stderr
|
|
3591
|
+
advisory the operator can read; a hook has no reader for stderr and its stdout
|
|
3592
|
+
becomes the session's trusted context, so it withholds the position instead.
|
|
3593
|
+
Run 'basou refresh' to see which lines are responsible.
|
|
3594
|
+
|
|
3595
|
+
Claude Code's SessionStart hook sends the same kind of payload (a JSON object
|
|
3596
|
+
with 'cwd') and adds stdout to context the same way, so a Claude Code user may
|
|
3597
|
+
register this command in ~/.claude/settings.json in place of 'basou orient' to
|
|
3598
|
+
get both gates. basou does not install that one.
|
|
3599
|
+
|
|
3443
3600
|
Codex trusts hooks by hash. A newly installed or changed hook is skipped until
|
|
3444
3601
|
you review it: the interactive CLI asks at startup ("Hooks need review"), the
|
|
3445
3602
|
desktop app lists it under Settings -> Hooks. Non-interactive 'codex exec' skips
|
|
@@ -3530,6 +3687,14 @@ async function renderRegisteredWorkspacePosition(cwd, portfolioConfigPath = DEFA
|
|
|
3530
3687
|
throw new Error("The workspace is not registered in the portfolio; the hook stays silent.");
|
|
3531
3688
|
}
|
|
3532
3689
|
const rendered = await renderOrientationForRoot(root, {}, { cwd }, { write: false });
|
|
3690
|
+
const foreign = await findForeignWorkspaceNames({
|
|
3691
|
+
text: rendered.body,
|
|
3692
|
+
selfPath: root,
|
|
3693
|
+
configPath: portfolioConfigPath
|
|
3694
|
+
});
|
|
3695
|
+
if (foreign !== null) {
|
|
3696
|
+
throw new Error("The position names another registered workspace; the hook stays silent.");
|
|
3697
|
+
}
|
|
3533
3698
|
return { body: rendered.body };
|
|
3534
3699
|
}
|
|
3535
3700
|
async function isRegisteredWorkspace(root, portfolioConfigPath) {
|
|
@@ -3539,9 +3704,9 @@ async function isRegisteredWorkspace(root, portfolioConfigPath) {
|
|
|
3539
3704
|
} catch {
|
|
3540
3705
|
return false;
|
|
3541
3706
|
}
|
|
3542
|
-
const rootReal = await
|
|
3707
|
+
const rootReal = await realpath3(root).catch(() => root);
|
|
3543
3708
|
for (const entry of entries) {
|
|
3544
|
-
const entryReal = await
|
|
3709
|
+
const entryReal = await realpath3(entry.path).catch(() => null);
|
|
3545
3710
|
if (entryReal !== null && entryReal === rootReal) return true;
|
|
3546
3711
|
}
|
|
3547
3712
|
return false;
|
|
@@ -3785,7 +3950,7 @@ var DEFAULT_CODEX_FACE_PATH = join9(homedir7(), ".codex", "AGENTS.md");
|
|
|
3785
3950
|
var LEFTOVER_FACE_NOTE = (label) => `${label} still carries an orientation block rendered by an earlier basou (0.39 or before); every Codex session on this machine reads it. \`basou channel clear codex\` removes it.`;
|
|
3786
3951
|
async function faceHasLeftoverOrientationBlock(facePath) {
|
|
3787
3952
|
try {
|
|
3788
|
-
const existing = await
|
|
3953
|
+
const existing = await readMarkdownFile6(facePath);
|
|
3789
3954
|
if (existing === null) return false;
|
|
3790
3955
|
const section = parseMarkers2(existing, { start: ORIENTATION_START2, end: ORIENTATION_END2 });
|
|
3791
3956
|
return section.kind !== "no_markers";
|
|
@@ -3965,7 +4130,7 @@ async function codexHookTrustFor(hooksPath, location, configPath) {
|
|
|
3965
4130
|
}
|
|
3966
4131
|
|
|
3967
4132
|
// src/commands/init.ts
|
|
3968
|
-
import { basename as
|
|
4133
|
+
import { basename as basename5, relative, resolve as resolve6 } from "path";
|
|
3969
4134
|
import {
|
|
3970
4135
|
appendBasouGitignore,
|
|
3971
4136
|
createManifest,
|
|
@@ -4003,7 +4168,7 @@ async function runInit(options, ctx = {}) {
|
|
|
4003
4168
|
async function doRunInit(options, ctx) {
|
|
4004
4169
|
const cwd = ctx.cwd ?? process.cwd();
|
|
4005
4170
|
const repositoryRoot = await resolveRepositoryRootForInit(cwd);
|
|
4006
|
-
const workspaceName = options.name ??
|
|
4171
|
+
const workspaceName = options.name ?? basename5(repositoryRoot);
|
|
4007
4172
|
if (options.repoUrl !== void 0) {
|
|
4008
4173
|
console.error(
|
|
4009
4174
|
"Warning: --repo-url is deprecated and ignored (project.repository_url was removed); the flag will be removed at 1.0."
|
|
@@ -4329,7 +4494,7 @@ import {
|
|
|
4329
4494
|
writeFileSync,
|
|
4330
4495
|
writeSync
|
|
4331
4496
|
} from "fs";
|
|
4332
|
-
import { basename as
|
|
4497
|
+
import { basename as basename6, dirname as dirname4, isAbsolute as isAbsolute3, join as join11, relative as relative2, resolve as resolve7 } from "path";
|
|
4333
4498
|
import {
|
|
4334
4499
|
appendBasouGitignore as appendBasouGitignore2,
|
|
4335
4500
|
basouPaths as basouPaths10,
|
|
@@ -4348,7 +4513,7 @@ import {
|
|
|
4348
4513
|
planRosterAdoption,
|
|
4349
4514
|
planWorkspaceView,
|
|
4350
4515
|
readManifest as readManifest6,
|
|
4351
|
-
readMarkdownFile as
|
|
4516
|
+
readMarkdownFile as readMarkdownFile7,
|
|
4352
4517
|
reconcileSourceRoots,
|
|
4353
4518
|
removeMarkerSection as removeMarkerSection2,
|
|
4354
4519
|
renderAnchorStarter,
|
|
@@ -4960,9 +5125,9 @@ function applyGitignorePlan(repositoryRoot, plan) {
|
|
|
4960
5125
|
throw new Error("Failed to read .gitignore", { cause: error });
|
|
4961
5126
|
}
|
|
4962
5127
|
}
|
|
4963
|
-
const
|
|
5128
|
+
const sep2 = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
4964
5129
|
try {
|
|
4965
|
-
writeFileSync(file, `${existing}${
|
|
5130
|
+
writeFileSync(file, `${existing}${sep2}${plan.toAdd.join("\n")}
|
|
4966
5131
|
`);
|
|
4967
5132
|
} catch (error) {
|
|
4968
5133
|
throw new Error("Failed to write .gitignore", { cause: error });
|
|
@@ -5102,7 +5267,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
|
|
|
5102
5267
|
isAnchor: true,
|
|
5103
5268
|
reachable: true,
|
|
5104
5269
|
canonicalPresent: true,
|
|
5105
|
-
canonicalName:
|
|
5270
|
+
canonicalName: basename6(real),
|
|
5106
5271
|
files: [{ name: CANONICAL_FILE, expectedTarget: CANONICAL_FILE, state: "blocked" }]
|
|
5107
5272
|
};
|
|
5108
5273
|
}
|
|
@@ -5124,14 +5289,14 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
|
|
|
5124
5289
|
isAnchor: true,
|
|
5125
5290
|
reachable: true,
|
|
5126
5291
|
canonicalPresent: true,
|
|
5127
|
-
canonicalName:
|
|
5292
|
+
canonicalName: basename6(real),
|
|
5128
5293
|
files: anchorFiles
|
|
5129
5294
|
};
|
|
5130
5295
|
}
|
|
5131
5296
|
if (!existsSync2(join11(real, ".git"))) {
|
|
5132
5297
|
return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
|
|
5133
5298
|
}
|
|
5134
|
-
const canonicalFile = isSelf ? join11(real, CANONICAL_FILE) : join11(anchorReal, "agents",
|
|
5299
|
+
const canonicalFile = isSelf ? join11(real, CANONICAL_FILE) : join11(anchorReal, "agents", basename6(real), CANONICAL_FILE);
|
|
5135
5300
|
if (!existsSync2(canonicalFile)) {
|
|
5136
5301
|
return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
|
|
5137
5302
|
}
|
|
@@ -5151,7 +5316,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
|
|
|
5151
5316
|
isAnchor: false,
|
|
5152
5317
|
reachable: true,
|
|
5153
5318
|
canonicalPresent: true,
|
|
5154
|
-
canonicalName:
|
|
5319
|
+
canonicalName: basename6(real),
|
|
5155
5320
|
files
|
|
5156
5321
|
};
|
|
5157
5322
|
}
|
|
@@ -5168,7 +5333,7 @@ function applySymlinkPlan(repositoryRoot, plan) {
|
|
|
5168
5333
|
for (const { name, target } of plan.toCreate) {
|
|
5169
5334
|
const filePath = join11(real, name);
|
|
5170
5335
|
try {
|
|
5171
|
-
mkdirSync(
|
|
5336
|
+
mkdirSync(dirname4(filePath), { recursive: true });
|
|
5172
5337
|
symlinkSync(target, filePath);
|
|
5173
5338
|
created.push(name);
|
|
5174
5339
|
} catch (error) {
|
|
@@ -5184,16 +5349,16 @@ function viewCanonicalCollision(repositoryRoot, roster, viewName) {
|
|
|
5184
5349
|
for (const entry of roster) {
|
|
5185
5350
|
let name;
|
|
5186
5351
|
try {
|
|
5187
|
-
name =
|
|
5352
|
+
name = basename6(realpathSync(resolve7(repositoryRoot, entry.path)));
|
|
5188
5353
|
} catch {
|
|
5189
|
-
name =
|
|
5354
|
+
name = basename6(resolve7(repositoryRoot, entry.path));
|
|
5190
5355
|
}
|
|
5191
5356
|
if (name === viewName) return entry.path;
|
|
5192
5357
|
}
|
|
5193
5358
|
return void 0;
|
|
5194
5359
|
}
|
|
5195
5360
|
function gatherViewSymlinks(repositoryRoot, anchorReal, roster, viewDir) {
|
|
5196
|
-
const viewName =
|
|
5361
|
+
const viewName = basename6(viewDir);
|
|
5197
5362
|
const collision = viewCanonicalCollision(repositoryRoot, roster, viewName);
|
|
5198
5363
|
if (collision !== void 0) return { kind: "collision", viewName, repoPath: collision };
|
|
5199
5364
|
const canonicalFile = canonicalFileFor(anchorReal, viewName);
|
|
@@ -5218,7 +5383,7 @@ function applyViewSymlinks(viewDir, files) {
|
|
|
5218
5383
|
if (f.state !== "missing") continue;
|
|
5219
5384
|
const filePath = join11(viewDir, f.name);
|
|
5220
5385
|
try {
|
|
5221
|
-
mkdirSync(
|
|
5386
|
+
mkdirSync(dirname4(filePath), { recursive: true });
|
|
5222
5387
|
symlinkSync(f.expectedTarget, filePath);
|
|
5223
5388
|
created.push(f.name);
|
|
5224
5389
|
} catch (error) {
|
|
@@ -5438,7 +5603,7 @@ function resolveViewDir(repositoryRoot, viewPath) {
|
|
|
5438
5603
|
return realpathSync(abs);
|
|
5439
5604
|
} catch {
|
|
5440
5605
|
try {
|
|
5441
|
-
return join11(realpathSync(
|
|
5606
|
+
return join11(realpathSync(dirname4(abs)), basename6(abs));
|
|
5442
5607
|
} catch {
|
|
5443
5608
|
return abs;
|
|
5444
5609
|
}
|
|
@@ -5455,7 +5620,7 @@ function gatherViewRepo(repositoryRoot, viewDir, entry) {
|
|
|
5455
5620
|
if (expectedTarget === "" || expectedTarget === ".") {
|
|
5456
5621
|
return { path: entry.path, reachable: false };
|
|
5457
5622
|
}
|
|
5458
|
-
const linkName =
|
|
5623
|
+
const linkName = basename6(repoReal);
|
|
5459
5624
|
const { state, actualTarget } = inspectSymlink(join11(viewDir, linkName), expectedTarget);
|
|
5460
5625
|
return {
|
|
5461
5626
|
path: entry.path,
|
|
@@ -5472,7 +5637,7 @@ function applyViewPlan(viewDir, toCreate) {
|
|
|
5472
5637
|
for (const { name, target } of toCreate) {
|
|
5473
5638
|
const filePath = join11(viewDir, name);
|
|
5474
5639
|
try {
|
|
5475
|
-
mkdirSync(
|
|
5640
|
+
mkdirSync(dirname4(filePath), { recursive: true });
|
|
5476
5641
|
symlinkSync(target, filePath);
|
|
5477
5642
|
created.push(name);
|
|
5478
5643
|
} catch (error) {
|
|
@@ -5585,7 +5750,7 @@ async function doRunProjectWorkspace(options, ctx) {
|
|
|
5585
5750
|
} else {
|
|
5586
5751
|
const viewDir = resolveViewDir(repositoryRoot, viewPath);
|
|
5587
5752
|
const facts = roster.map((entry) => gatherViewRepo(repositoryRoot, viewDir, entry));
|
|
5588
|
-
const rosterNames = roster.map((entry) =>
|
|
5753
|
+
const rosterNames = roster.map((entry) => basename6(resolve7(repositoryRoot, entry.path)));
|
|
5589
5754
|
const rosterRealpaths = /* @__PURE__ */ new Set();
|
|
5590
5755
|
for (const entry of roster) {
|
|
5591
5756
|
try {
|
|
@@ -5779,10 +5944,10 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
|
|
|
5779
5944
|
if (!existsSync2(join11(real, ".git"))) {
|
|
5780
5945
|
return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
|
|
5781
5946
|
}
|
|
5782
|
-
const canonicalName =
|
|
5947
|
+
const canonicalName = basename6(real);
|
|
5783
5948
|
let content;
|
|
5784
5949
|
try {
|
|
5785
|
-
content = await
|
|
5950
|
+
content = await readMarkdownFile7(canonicalFileFor(anchorReal, canonicalName));
|
|
5786
5951
|
} catch {
|
|
5787
5952
|
return {
|
|
5788
5953
|
...declared,
|
|
@@ -5832,7 +5997,7 @@ function viewPresetReposFor(repositoryRoot, roster) {
|
|
|
5832
5997
|
} catch {
|
|
5833
5998
|
real = void 0;
|
|
5834
5999
|
}
|
|
5835
|
-
const name =
|
|
6000
|
+
const name = basename6(real ?? abs);
|
|
5836
6001
|
const isAnchor = real !== void 0 && anchorReal !== void 0 ? real === anchorReal : abs === repositoryRoot;
|
|
5837
6002
|
return {
|
|
5838
6003
|
name,
|
|
@@ -5890,8 +6055,8 @@ async function applyViewPreset(anchorReal, outcome) {
|
|
|
5890
6055
|
isLink = false;
|
|
5891
6056
|
}
|
|
5892
6057
|
if (isLink) throw new Error(`Canonical is a symlink in ${label}`);
|
|
5893
|
-
if (outcome.action === "create") mkdirSync(
|
|
5894
|
-
const existing = await
|
|
6058
|
+
if (outcome.action === "create") mkdirSync(dirname4(file), { recursive: true });
|
|
6059
|
+
const existing = await readMarkdownFile7(file);
|
|
5895
6060
|
await writeMarkdownFile5(file, renderWithMarkers4(existing, outcome.block, label));
|
|
5896
6061
|
}
|
|
5897
6062
|
async function applyPresetPlan(anchorReal, plan) {
|
|
@@ -5904,8 +6069,8 @@ async function applyPresetPlan(anchorReal, plan) {
|
|
|
5904
6069
|
isLink = false;
|
|
5905
6070
|
}
|
|
5906
6071
|
if (isLink) throw new Error(`Canonical is a symlink in ${label}`);
|
|
5907
|
-
if (plan.action === "create") mkdirSync(
|
|
5908
|
-
const existing = await
|
|
6072
|
+
if (plan.action === "create") mkdirSync(dirname4(file), { recursive: true });
|
|
6073
|
+
const existing = await readMarkdownFile7(file);
|
|
5909
6074
|
await writeMarkdownFile5(file, renderWithMarkers4(existing, plan.desiredBlock, label));
|
|
5910
6075
|
}
|
|
5911
6076
|
function presetFailureReason(error) {
|
|
@@ -5927,7 +6092,7 @@ async function doRunProjectPreset(options, ctx) {
|
|
|
5927
6092
|
const facts = [];
|
|
5928
6093
|
for (const entry of roster) facts.push(await gatherRepoPreset(repositoryRoot, anchorReal, entry));
|
|
5929
6094
|
const viewPath = manifest.workspace.view;
|
|
5930
|
-
const viewCanonicalName = roster.length > 0 && viewPath !== void 0 ?
|
|
6095
|
+
const viewCanonicalName = roster.length > 0 && viewPath !== void 0 ? basename6(resolveViewDir(repositoryRoot, viewPath)) : void 0;
|
|
5931
6096
|
const summary = summarizePresetPlan(
|
|
5932
6097
|
facts,
|
|
5933
6098
|
viewCanonicalName !== void 0 ? { viewCanonicalName } : void 0
|
|
@@ -6154,7 +6319,7 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
|
|
|
6154
6319
|
return empty;
|
|
6155
6320
|
}
|
|
6156
6321
|
const anchorReal = realpathSync(repositoryRoot);
|
|
6157
|
-
const canonicalName =
|
|
6322
|
+
const canonicalName = basename6(real);
|
|
6158
6323
|
const instructionFiles = [];
|
|
6159
6324
|
for (const name of INSTRUCTION_FILES) {
|
|
6160
6325
|
try {
|
|
@@ -6224,7 +6389,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
|
|
|
6224
6389
|
}
|
|
6225
6390
|
const isAnchor = repoReal !== void 0 && repoReal === anchorReal;
|
|
6226
6391
|
const targetAbs = resolve7(repositoryRoot, target);
|
|
6227
|
-
const canonicalName =
|
|
6392
|
+
const canonicalName = basename6(repoReal ?? targetAbs);
|
|
6228
6393
|
const roster = manifest.repos ?? [];
|
|
6229
6394
|
const declaredEntry = roster.find((r) => {
|
|
6230
6395
|
try {
|
|
@@ -6245,10 +6410,10 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
|
|
|
6245
6410
|
}
|
|
6246
6411
|
if (rReal !== null) {
|
|
6247
6412
|
if (repoReal !== void 0 && rReal === repoReal) return false;
|
|
6248
|
-
return
|
|
6413
|
+
return basename6(rReal).toLowerCase() === cnFold;
|
|
6249
6414
|
}
|
|
6250
6415
|
if (resolve7(repositoryRoot, r.path) === targetAbs) return false;
|
|
6251
|
-
return
|
|
6416
|
+
return basename6(resolve7(repositoryRoot, r.path)).toLowerCase() === cnFold;
|
|
6252
6417
|
});
|
|
6253
6418
|
const collisionNote = "shared with another repo of the same basename, so it cannot be removed (check manually)";
|
|
6254
6419
|
const items = [];
|
|
@@ -6713,7 +6878,7 @@ function renderProjectArchive(result) {
|
|
|
6713
6878
|
if (t.gitignorePatterns.length > 0)
|
|
6714
6879
|
items.push(`.gitignore instruction patterns (${t.gitignorePatterns.join(", ")})`);
|
|
6715
6880
|
if (t.canonical)
|
|
6716
|
-
items.push(`the anchor's canonical (agents/${
|
|
6881
|
+
items.push(`the anchor's canonical (agents/${basename6(result.target)}/AGENTS.md)`);
|
|
6717
6882
|
if (!t.inspected) {
|
|
6718
6883
|
lines.push(
|
|
6719
6884
|
"## Manual teardown (the repo could not be resolved on disk, so it was not inspected)"
|
|
@@ -6924,7 +7089,7 @@ function validateProjectName(name) {
|
|
|
6924
7089
|
async function doRunProjectNew(repos, options, ctx) {
|
|
6925
7090
|
const cwd = ctx.cwd ?? process.cwd();
|
|
6926
7091
|
const repositoryRoot = await resolveRepositoryRootForNew(cwd);
|
|
6927
|
-
const workspaceName =
|
|
7092
|
+
const workspaceName = basename6(repositoryRoot);
|
|
6928
7093
|
const productName = options.projectName !== void 0 ? validateProjectName(options.projectName) : void 0;
|
|
6929
7094
|
const viewStem = productName ?? workspaceName;
|
|
6930
7095
|
const viewOverridesProjectName = productName !== void 0 && typeof options.view === "string";
|
|
@@ -7123,10 +7288,10 @@ async function doRunProjectSeedAnchor(options, ctx) {
|
|
|
7123
7288
|
return;
|
|
7124
7289
|
}
|
|
7125
7290
|
const viewPath = manifest.workspace.view;
|
|
7126
|
-
const viewName = viewPath !== void 0 ?
|
|
7291
|
+
const viewName = viewPath !== void 0 ? basename6(resolveViewDir(repositoryRoot, viewPath)) : void 0;
|
|
7127
7292
|
const repos = viewPresetReposFor(repositoryRoot, roster);
|
|
7128
7293
|
const content = renderAnchorStarter({
|
|
7129
|
-
anchorName:
|
|
7294
|
+
anchorName: basename6(repositoryRoot),
|
|
7130
7295
|
...manifest.project?.name !== void 0 ? { projectName: manifest.project.name } : {},
|
|
7131
7296
|
...viewName !== void 0 ? { viewName } : {},
|
|
7132
7297
|
repos
|
|
@@ -7207,7 +7372,7 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argPath, argAbs, arg
|
|
|
7207
7372
|
const self = declaredEntry !== void 0 && instructionMode(declaredEntry) === "self";
|
|
7208
7373
|
const displayRel = argReal !== void 0 ? relative2(anchorReal, argReal) : relative2(repositoryRoot, argAbs);
|
|
7209
7374
|
const path = displayRel === "" ? "." : displayRel;
|
|
7210
|
-
const canonicalName =
|
|
7375
|
+
const canonicalName = basename6(argReal ?? argAbs);
|
|
7211
7376
|
if (argReal === void 0) {
|
|
7212
7377
|
return {
|
|
7213
7378
|
path,
|
|
@@ -7241,7 +7406,7 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argPath, argAbs, arg
|
|
|
7241
7406
|
function relocateAgentsFile(repoReal, canonicalFile) {
|
|
7242
7407
|
const agentsFile = join11(repoReal, CANONICAL_FILE);
|
|
7243
7408
|
try {
|
|
7244
|
-
mkdirSync(
|
|
7409
|
+
mkdirSync(dirname4(canonicalFile), { recursive: true });
|
|
7245
7410
|
} catch (error) {
|
|
7246
7411
|
return { ok: false, message: failureReason(error), partial: false };
|
|
7247
7412
|
}
|
|
@@ -7262,7 +7427,7 @@ function relocateAgentsFile(repoReal, canonicalFile) {
|
|
|
7262
7427
|
function gatherViewRetrofit(repositoryRoot, anchorReal, viewPath, roster) {
|
|
7263
7428
|
if (viewPath === void 0) return { kind: "no-view" };
|
|
7264
7429
|
const viewDir = resolveViewDir(repositoryRoot, viewPath);
|
|
7265
|
-
const viewName =
|
|
7430
|
+
const viewName = basename6(viewDir);
|
|
7266
7431
|
const collision = viewCanonicalCollision(repositoryRoot, roster, viewName);
|
|
7267
7432
|
if (collision !== void 0) return { kind: "collision", viewName, repoPath: collision };
|
|
7268
7433
|
const canonicalFile = canonicalFileFor(anchorReal, viewName);
|
|
@@ -7294,7 +7459,7 @@ async function applyViewRetrofit(anchorReal, outcome) {
|
|
|
7294
7459
|
isLink = false;
|
|
7295
7460
|
}
|
|
7296
7461
|
if (isLink) throw new Error(`Canonical is a symlink in ${label}`);
|
|
7297
|
-
const existing = await
|
|
7462
|
+
const existing = await readMarkdownFile7(file);
|
|
7298
7463
|
await writeMarkdownFile5(file, seedMarkers(existing, outcome.block, label));
|
|
7299
7464
|
}
|
|
7300
7465
|
async function doRunProjectRetrofit(repo, options, ctx) {
|
|
@@ -7341,7 +7506,7 @@ async function doRunProjectRetrofit(repo, options, ctx) {
|
|
|
7341
7506
|
} catch {
|
|
7342
7507
|
argReal = void 0;
|
|
7343
7508
|
}
|
|
7344
|
-
const viewCanonicalName = viewPath !== void 0 ?
|
|
7509
|
+
const viewCanonicalName = viewPath !== void 0 ? basename6(resolveViewDir(repositoryRoot, viewPath)) : void 0;
|
|
7345
7510
|
const facts = gatherRetrofit(
|
|
7346
7511
|
repositoryRoot,
|
|
7347
7512
|
anchorReal,
|
|
@@ -7557,7 +7722,7 @@ function renderProjectRetrofit(result) {
|
|
|
7557
7722
|
|
|
7558
7723
|
// src/commands/protocol.ts
|
|
7559
7724
|
import { readFile as readFile4 } from "fs/promises";
|
|
7560
|
-
import { PROTOCOL_END, PROTOCOL_START, parseMarkers as parseMarkers4, readMarkdownFile as
|
|
7725
|
+
import { PROTOCOL_END, PROTOCOL_START, parseMarkers as parseMarkers4, readMarkdownFile as readMarkdownFile8 } from "@basou/core";
|
|
7561
7726
|
|
|
7562
7727
|
// src/lib/protocols-config.ts
|
|
7563
7728
|
import { homedir as homedir8 } from "os";
|
|
@@ -7709,18 +7874,23 @@ ${body}` : body;
|
|
|
7709
7874
|
${sections.join("\n\n")}
|
|
7710
7875
|
`;
|
|
7711
7876
|
}
|
|
7712
|
-
async function doRunProtocolSync(options) {
|
|
7877
|
+
async function doRunProtocolSync(options, ctx = {}) {
|
|
7713
7878
|
const configPath = options.config ?? DEFAULT_PROTOCOLS_CONFIG_PATH;
|
|
7714
7879
|
const target = options.target ?? DEFAULT_TARGET_PATH;
|
|
7715
7880
|
const entries = await loadProtocolsConfig(configPath);
|
|
7716
7881
|
const sources = await readProtocolSources(entries);
|
|
7717
7882
|
const block = buildBlock(sources);
|
|
7883
|
+
const foreign = await findForeignWorkspaceNames({
|
|
7884
|
+
text: block,
|
|
7885
|
+
configPath: ctx.portfolioConfigPath
|
|
7886
|
+
});
|
|
7718
7887
|
const result = await syncMarkerBlock({
|
|
7719
7888
|
target,
|
|
7720
7889
|
markers: PROTOCOL_MARKERS,
|
|
7721
7890
|
block,
|
|
7722
7891
|
...options.dryRun === true ? { dryRun: true } : {}
|
|
7723
7892
|
});
|
|
7893
|
+
if (foreign !== null) console.error(protocolForeignWorkspaceWarning(foreign));
|
|
7724
7894
|
if (result.action === "unchanged") {
|
|
7725
7895
|
console.log(`The basou:protocols block is already up to date (${entries.length} protocol(s)).`);
|
|
7726
7896
|
return;
|
|
@@ -7742,7 +7912,7 @@ async function doRunProtocolList(options) {
|
|
|
7742
7912
|
const configPath = options.config ?? DEFAULT_PROTOCOLS_CONFIG_PATH;
|
|
7743
7913
|
const target = options.target ?? DEFAULT_TARGET_PATH;
|
|
7744
7914
|
const entries = await loadProtocolsConfig(configPath);
|
|
7745
|
-
const existing = await
|
|
7915
|
+
const existing = await readMarkdownFile8(target);
|
|
7746
7916
|
const installed = existing !== null && parseMarkers4(existing, PROTOCOL_MARKERS).kind === "ok";
|
|
7747
7917
|
console.log(`Declared protocols (${entries.length}):`);
|
|
7748
7918
|
for (const entry of entries) {
|
|
@@ -7975,7 +8145,7 @@ async function doRunRefreshPortfolio(options, ctx) {
|
|
|
7975
8145
|
for (const ws of workspaces) {
|
|
7976
8146
|
const label = ws.label ?? ws.path;
|
|
7977
8147
|
try {
|
|
7978
|
-
const { result } = await computeRefresh(
|
|
8148
|
+
const { result, paths } = await computeRefresh(
|
|
7979
8149
|
{ ...options, portfolio: false },
|
|
7980
8150
|
{ ...ctx, cwd: ws.path }
|
|
7981
8151
|
);
|
|
@@ -7985,6 +8155,7 @@ async function doRunRefreshPortfolio(options, ctx) {
|
|
|
7985
8155
|
## ${label} (${ws.path})`);
|
|
7986
8156
|
printRefreshSummary(result);
|
|
7987
8157
|
}
|
|
8158
|
+
await warnPosition(paths, result, ctx);
|
|
7988
8159
|
} catch (error) {
|
|
7989
8160
|
const message = error instanceof Error ? error.message : String(error);
|
|
7990
8161
|
rollup.push({ label, path: ws.path, status: "failed", error: message });
|
|
@@ -8068,8 +8239,16 @@ async function doRunRefresh(options, ctx) {
|
|
|
8068
8239
|
const line = await retiredChannelNotice(paths);
|
|
8069
8240
|
if (line !== null) console.log(line);
|
|
8070
8241
|
}
|
|
8242
|
+
await warnPosition(paths, result, ctx);
|
|
8071
8243
|
return reported;
|
|
8072
8244
|
}
|
|
8245
|
+
async function warnPosition(paths, result, ctx) {
|
|
8246
|
+
if (result.orientation.status !== "generated") return;
|
|
8247
|
+
await warnIfPositionNamesOtherWorkspaces({
|
|
8248
|
+
paths,
|
|
8249
|
+
configPath: ctx.portfolioConfigPath
|
|
8250
|
+
});
|
|
8251
|
+
}
|
|
8073
8252
|
async function retiredChannelNotice(paths) {
|
|
8074
8253
|
try {
|
|
8075
8254
|
const manifest = await readManifest7(paths);
|
|
@@ -9104,7 +9283,7 @@ async function noteCodexHookStatusPreSpawn(_cwd, ctx) {
|
|
|
9104
9283
|
|
|
9105
9284
|
// src/commands/session.ts
|
|
9106
9285
|
import { readFile as readFile7 } from "fs/promises";
|
|
9107
|
-
import { basename as
|
|
9286
|
+
import { basename as basename7, isAbsolute as isAbsolute6, join as join15, relative as relative3 } from "path";
|
|
9108
9287
|
import {
|
|
9109
9288
|
acquireLock as acquireLock6,
|
|
9110
9289
|
appendEventToExistingSession as appendEventToExistingSession3,
|
|
@@ -9612,7 +9791,7 @@ function printSessionImportResult(options, result) {
|
|
|
9612
9791
|
return;
|
|
9613
9792
|
}
|
|
9614
9793
|
console.log(
|
|
9615
|
-
`Imported session ${sid} (${result.eventCount} events) from ${
|
|
9794
|
+
`Imported session ${sid} (${result.eventCount} events) from ${basename7(options.from)}`
|
|
9616
9795
|
);
|
|
9617
9796
|
}
|
|
9618
9797
|
var NOTE_BODY_PREVIEW_LIMIT = 80;
|
|
@@ -11212,7 +11391,7 @@ async function assertWorkspaceInitialized14(basouRoot) {
|
|
|
11212
11391
|
// src/commands/view.ts
|
|
11213
11392
|
import { spawn } from "child_process";
|
|
11214
11393
|
import { createHash as createHash2 } from "crypto";
|
|
11215
|
-
import { basename as
|
|
11394
|
+
import { basename as basename10, resolve as resolve13 } from "path";
|
|
11216
11395
|
import {
|
|
11217
11396
|
assertBasouRootSafe as assertBasouRootSafe18,
|
|
11218
11397
|
basouPaths as basouPaths22,
|
|
@@ -11226,7 +11405,7 @@ import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
|
|
|
11226
11405
|
import { createReadStream as createReadStream2 } from "fs";
|
|
11227
11406
|
import { readdir as readdir3, stat as stat6 } from "fs/promises";
|
|
11228
11407
|
import { homedir as homedir12 } from "os";
|
|
11229
|
-
import { basename as
|
|
11408
|
+
import { basename as basename8, dirname as dirname5, join as join17 } from "path";
|
|
11230
11409
|
import { createInterface as createInterface2 } from "readline";
|
|
11231
11410
|
import { basouPaths as basouPaths21, readManifest as readManifest13, resolveRepositoryRoot as resolveRepositoryRoot14 } from "@basou/core";
|
|
11232
11411
|
function uncapturedTotal(result) {
|
|
@@ -11247,7 +11426,7 @@ async function checkPortfolioCoverage(workspaces, ctx = {}) {
|
|
|
11247
11426
|
file,
|
|
11248
11427
|
"claude-code",
|
|
11249
11428
|
claudeTranscriptCwd,
|
|
11250
|
-
listedDirs.has(
|
|
11429
|
+
listedDirs.has(basename8(dirname5(file)))
|
|
11251
11430
|
);
|
|
11252
11431
|
}
|
|
11253
11432
|
}
|
|
@@ -11536,7 +11715,7 @@ function inertLines(result) {
|
|
|
11536
11715
|
|
|
11537
11716
|
// src/lib/portfolio-safety.ts
|
|
11538
11717
|
import { execFile } from "child_process";
|
|
11539
|
-
import { lstat as lstat2, realpath as
|
|
11718
|
+
import { lstat as lstat2, realpath as realpath4 } from "fs/promises";
|
|
11540
11719
|
import { isAbsolute as isAbsolute7, join as join18, relative as relative4, resolve as resolve11 } from "path";
|
|
11541
11720
|
import { promisify } from "util";
|
|
11542
11721
|
import { readManifest as readManifest14 } from "@basou/core";
|
|
@@ -11546,7 +11725,7 @@ function errorCode(error) {
|
|
|
11546
11725
|
}
|
|
11547
11726
|
async function canonical(p) {
|
|
11548
11727
|
try {
|
|
11549
|
-
return await
|
|
11728
|
+
return await realpath4(p);
|
|
11550
11729
|
} catch {
|
|
11551
11730
|
return resolve11(p);
|
|
11552
11731
|
}
|
|
@@ -11715,7 +11894,7 @@ function formatSafetyReport(result) {
|
|
|
11715
11894
|
|
|
11716
11895
|
// src/lib/view-server.ts
|
|
11717
11896
|
import { createServer } from "http";
|
|
11718
|
-
import { basename as
|
|
11897
|
+
import { basename as basename9, join as join19, resolve as resolve12 } from "path";
|
|
11719
11898
|
import {
|
|
11720
11899
|
computeWorkStats as computeWorkStats2,
|
|
11721
11900
|
enumerateApprovals as enumerateApprovals2,
|
|
@@ -11726,7 +11905,7 @@ import {
|
|
|
11726
11905
|
loadTaskEntries as loadTaskEntries2,
|
|
11727
11906
|
readAllEvents as readAllEvents2,
|
|
11728
11907
|
readManifest as readManifest15,
|
|
11729
|
-
readMarkdownFile as
|
|
11908
|
+
readMarkdownFile as readMarkdownFile9,
|
|
11730
11909
|
readSessionYaml as readSessionYaml3,
|
|
11731
11910
|
readTaskFile as readTaskFile2,
|
|
11732
11911
|
renderDecisions as renderDecisions3,
|
|
@@ -12591,6 +12770,9 @@ async function handleWorkspacePost(res, sub, ws, body, deps, runExclusive) {
|
|
|
12591
12770
|
const result = await runExclusive(
|
|
12592
12771
|
() => refreshAll({ options: actionOptions, ctx: ws.importCtx, paths: ws.paths, nowIso })
|
|
12593
12772
|
);
|
|
12773
|
+
if (result.orientation.status === "generated") {
|
|
12774
|
+
await warnIfPositionNamesOtherWorkspaces({ paths: ws.paths });
|
|
12775
|
+
}
|
|
12594
12776
|
sendJson(res, 200, result);
|
|
12595
12777
|
return true;
|
|
12596
12778
|
}
|
|
@@ -12721,7 +12903,7 @@ async function rosterRepos(repoRoot, manifest, resolveRemoteUrl) {
|
|
|
12721
12903
|
const remote = await resolveRemoteUrl(abs);
|
|
12722
12904
|
const url = remote !== void 0 ? toBrowserUrl(remote) : null;
|
|
12723
12905
|
return {
|
|
12724
|
-
name:
|
|
12906
|
+
name: basename9(abs),
|
|
12725
12907
|
path: repo.path,
|
|
12726
12908
|
...url !== null ? { url } : {},
|
|
12727
12909
|
...repo.visibility !== void 0 ? { visibility: repo.visibility } : {}
|
|
@@ -12778,7 +12960,7 @@ async function taskDetail(ws, taskId) {
|
|
|
12778
12960
|
}
|
|
12779
12961
|
}
|
|
12780
12962
|
async function decisionsView(ws, nowProvider) {
|
|
12781
|
-
const fromDisk = await
|
|
12963
|
+
const fromDisk = await readMarkdownFile9(ws.paths.files.decisions);
|
|
12782
12964
|
if (fromDisk !== null) {
|
|
12783
12965
|
return { body: fromDisk, fromDisk: true };
|
|
12784
12966
|
}
|
|
@@ -12801,7 +12983,7 @@ async function approvalsView(ws, nowProvider) {
|
|
|
12801
12983
|
return { pending: await toViews(ids.pending), resolved: await toViews(ids.resolved) };
|
|
12802
12984
|
}
|
|
12803
12985
|
async function handoffView(ws, nowProvider) {
|
|
12804
|
-
const fromDisk = await
|
|
12986
|
+
const fromDisk = await readMarkdownFile9(ws.paths.files.handoff);
|
|
12805
12987
|
if (fromDisk !== null) {
|
|
12806
12988
|
return { body: fromDisk, fromDisk: true };
|
|
12807
12989
|
}
|
|
@@ -13036,7 +13218,7 @@ async function buildWorkspaceEntry(repoRoot, ctx, labelOverride) {
|
|
|
13036
13218
|
const notFound = error instanceof Error && error.message === "YAML file not found";
|
|
13037
13219
|
return {
|
|
13038
13220
|
key: `ws-${createHash2("sha1").update(repoRoot).digest("hex").slice(0, 12)}`,
|
|
13039
|
-
label: labelOverride ??
|
|
13221
|
+
label: labelOverride ?? basename10(repoRoot),
|
|
13040
13222
|
paths,
|
|
13041
13223
|
repoRoot,
|
|
13042
13224
|
importCtx,
|