@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/program.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 sep = existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
714
- return `${existing}${sep}${wrapped}`;
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 realpath2, stat as stat4 } from "fs/promises";
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 readMarkdownFile5,
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 readMarkdownFile4,
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 basename3, dirname as dirname2, join as join8, resolve as resolve5 } from "path";
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 = basename3(file, ".jsonl");
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: dirname2(paths.root),
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 readMarkdownFile4(paths.files.handoff);
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 readMarkdownFile4(paths.files.decisions);
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(program) {
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 SessionStart hook: print the current position of the workspace Codex was opened in (read from the payload's cwd) so Codex adds it to that session's context. Stays silent outside a basou workspace; never fails the session."
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 realpath2(root).catch(() => root);
3707
+ const rootReal = await realpath3(root).catch(() => root);
3543
3708
  for (const entry of entries) {
3544
- const entryReal = await realpath2(entry.path).catch(() => null);
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 readMarkdownFile5(facePath);
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 basename4, relative, resolve as resolve6 } from "path";
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 ?? basename4(repositoryRoot);
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 basename5, dirname as dirname3, isAbsolute as isAbsolute3, join as join11, relative as relative2, resolve as resolve7 } from "path";
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 readMarkdownFile6,
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 sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
5128
+ const sep2 = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
4964
5129
  try {
4965
- writeFileSync(file, `${existing}${sep}${plan.toAdd.join("\n")}
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: basename5(real),
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: basename5(real),
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", basename5(real), CANONICAL_FILE);
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: basename5(real),
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(dirname3(filePath), { recursive: true });
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 = basename5(realpathSync(resolve7(repositoryRoot, entry.path)));
5352
+ name = basename6(realpathSync(resolve7(repositoryRoot, entry.path)));
5188
5353
  } catch {
5189
- name = basename5(resolve7(repositoryRoot, entry.path));
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 = basename5(viewDir);
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(dirname3(filePath), { recursive: true });
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(dirname3(abs)), basename5(abs));
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 = basename5(repoReal);
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(dirname3(filePath), { recursive: true });
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) => basename5(resolve7(repositoryRoot, entry.path)));
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 = basename5(real);
5947
+ const canonicalName = basename6(real);
5783
5948
  let content;
5784
5949
  try {
5785
- content = await readMarkdownFile6(canonicalFileFor(anchorReal, canonicalName));
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 = basename5(real ?? abs);
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(dirname3(file), { recursive: true });
5894
- const existing = await readMarkdownFile6(file);
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(dirname3(file), { recursive: true });
5908
- const existing = await readMarkdownFile6(file);
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 ? basename5(resolveViewDir(repositoryRoot, 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 = basename5(real);
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 = basename5(repoReal ?? targetAbs);
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 basename5(rReal).toLowerCase() === cnFold;
6413
+ return basename6(rReal).toLowerCase() === cnFold;
6249
6414
  }
6250
6415
  if (resolve7(repositoryRoot, r.path) === targetAbs) return false;
6251
- return basename5(resolve7(repositoryRoot, r.path)).toLowerCase() === cnFold;
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/${basename5(result.target)}/AGENTS.md)`);
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 = basename5(repositoryRoot);
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 ? basename5(resolveViewDir(repositoryRoot, 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: basename5(repositoryRoot),
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 = basename5(argReal ?? argAbs);
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(dirname3(canonicalFile), { recursive: true });
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 = basename5(viewDir);
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 readMarkdownFile6(file);
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 ? basename5(resolveViewDir(repositoryRoot, 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 readMarkdownFile7 } from "@basou/core";
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 readMarkdownFile7(target);
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 basename6, isAbsolute as isAbsolute6, join as join15, relative as relative3 } from "path";
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 ${basename6(options.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 basename9, resolve as resolve13 } from "path";
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 basename7, dirname as dirname4, join as join17 } from "path";
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(basename7(dirname4(file)))
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 realpath3 } from "fs/promises";
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 realpath3(p);
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 basename8, join as join19, resolve as resolve12 } from "path";
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 readMarkdownFile8,
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: basename8(abs),
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 readMarkdownFile8(ws.paths.files.decisions);
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 readMarkdownFile8(ws.paths.files.handoff);
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 ?? basename9(repoRoot),
13221
+ label: labelOverride ?? basename10(repoRoot),
13040
13222
  paths,
13041
13223
  repoRoot,
13042
13224
  importCtx,