@basou/cli 0.34.0 → 0.35.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
@@ -3616,11 +3616,104 @@ async function assertWorkspaceInitialized7(basouRoot) {
3616
3616
  }
3617
3617
  }
3618
3618
 
3619
+ // src/commands/portfolio.ts
3620
+ import { existsSync, statSync } from "fs";
3621
+ import { join as join9 } from "path";
3622
+ function registerPortfolioCommand(program) {
3623
+ program.command("portfolio").description(
3624
+ "List the workspaces you orient across (read-only): every planning master registered in ~/.basou/portfolio.yaml, with its path and whether it exists / is initialized. The headless text/JSON counterpart to the `basou view --portfolio` GUI \u2014 for discovering where a sibling project lives without opening a browser"
3625
+ ).argument("[action]", "optional literal `list` (the only, and default, action)").option("--json", "Output the result as JSON").option(
3626
+ "--check",
3627
+ "moved: the redundancy/footprint safety preflight is `basou view --portfolio --check` (this prints that pointer and exits)"
3628
+ ).option("-v, --verbose", "Show error causes").action(async (action, opts) => {
3629
+ await runPortfolioCommand(action, opts);
3630
+ });
3631
+ }
3632
+ function isDirectory(path) {
3633
+ try {
3634
+ return statSync(path).isDirectory();
3635
+ } catch {
3636
+ return false;
3637
+ }
3638
+ }
3639
+ async function runPortfolioCommand(action, options, ctx = {}) {
3640
+ if (options.check === true) {
3641
+ console.error(
3642
+ "`basou portfolio` is a read-only listing; it has no safety preflight.\nRun `basou view --portfolio --check` for the redundancy/footprint check."
3643
+ );
3644
+ process.exitCode = 1;
3645
+ return;
3646
+ }
3647
+ if (action !== void 0 && action !== "list") {
3648
+ console.error(
3649
+ `Unknown portfolio action '${action}'. Run \`basou portfolio\` or \`basou portfolio list\` to list the registered workspaces.`
3650
+ );
3651
+ process.exitCode = 1;
3652
+ return;
3653
+ }
3654
+ await runPortfolioList(options, ctx);
3655
+ }
3656
+ async function runPortfolioList(options, ctx = {}) {
3657
+ try {
3658
+ await doRunPortfolioList(options, ctx);
3659
+ } catch (error) {
3660
+ renderCliError(error, { verbose: isVerbose(options) });
3661
+ process.exitCode = 1;
3662
+ }
3663
+ }
3664
+ async function doRunPortfolioList(options, ctx) {
3665
+ const configPath = ctx.configPath ?? DEFAULT_PORTFOLIO_CONFIG_PATH;
3666
+ const pathExists2 = ctx.pathExists ?? ((p) => existsSync(p));
3667
+ const isInitialized = ctx.isInitialized ?? ((p) => isDirectory(join9(p, ".basou")));
3668
+ const workspaces = await loadPortfolioConfig(configPath);
3669
+ const result = {
3670
+ configPath,
3671
+ workspaces: workspaces.map((w) => {
3672
+ const exists = pathExists2(w.path);
3673
+ return {
3674
+ label: w.label ?? null,
3675
+ path: w.path,
3676
+ exists,
3677
+ initialized: exists && isInitialized(w.path)
3678
+ };
3679
+ })
3680
+ };
3681
+ if (options.json === true) {
3682
+ console.log(JSON.stringify(result));
3683
+ } else {
3684
+ console.log(renderPortfolioList(result));
3685
+ }
3686
+ return result;
3687
+ }
3688
+ var LABEL_COLUMN_CAP = 24;
3689
+ function renderPortfolioList(result) {
3690
+ const lines = [];
3691
+ const n = result.workspaces.length;
3692
+ lines.push("# Portfolio (workspaces you orient across)");
3693
+ lines.push("");
3694
+ lines.push(`${n} workspace${n === 1 ? "" : "s"} registered in ~/.basou/portfolio.yaml:`);
3695
+ lines.push("");
3696
+ const labelWidth = Math.min(
3697
+ LABEL_COLUMN_CAP,
3698
+ Math.max(0, ...result.workspaces.map((w) => (w.label ?? "(no label)").length))
3699
+ );
3700
+ for (const w of result.workspaces) {
3701
+ const label = (w.label ?? "(no label)").padEnd(labelWidth);
3702
+ const status = !w.exists ? "\u26A0 path not found" : w.initialized ? "\u2713 initialized" : "\u26A0 no .basou (not a planning master?)";
3703
+ lines.push(`- ${label} ${w.path} ${status}`);
3704
+ }
3705
+ lines.push("");
3706
+ lines.push(
3707
+ "Note: read-only listing of ~/.basou/portfolio.yaml. Run `basou view --portfolio` for the cross-workspace GUI, or `basou view --portfolio --check` for the redundancy/footprint safety preflight."
3708
+ );
3709
+ return lines.join("\n");
3710
+ }
3711
+
3619
3712
  // src/commands/project.ts
3620
3713
  import {
3621
3714
  closeSync,
3622
3715
  copyFileSync,
3623
- existsSync,
3716
+ existsSync as existsSync2,
3624
3717
  constants as fsConstants,
3625
3718
  ftruncateSync,
3626
3719
  lstatSync,
@@ -3630,13 +3723,13 @@ import {
3630
3723
  readFileSync,
3631
3724
  readlinkSync,
3632
3725
  realpathSync,
3633
- statSync,
3726
+ statSync as statSync2,
3634
3727
  symlinkSync,
3635
3728
  unlinkSync,
3636
3729
  writeFileSync,
3637
3730
  writeSync
3638
3731
  } from "fs";
3639
- import { basename as basename5, dirname as dirname3, isAbsolute as isAbsolute3, join as join9, relative as relative2, resolve as resolve7 } from "path";
3732
+ import { basename as basename5, dirname as dirname3, isAbsolute as isAbsolute3, join as join10, relative as relative2, resolve as resolve7 } from "path";
3640
3733
  import {
3641
3734
  appendBasouGitignore as appendBasouGitignore2,
3642
3735
  basouPaths as basouPaths10,
@@ -4018,7 +4111,7 @@ function classifySourceRoot(repositoryRoot, declaredPath) {
4018
4111
  } catch {
4019
4112
  return { path: declaredPath, kind: "unresolved" };
4020
4113
  }
4021
- return { path: declaredPath, kind: existsSync(join9(real, ".git")) ? "repo" : "non-repo" };
4114
+ return { path: declaredPath, kind: existsSync2(join10(real, ".git")) ? "repo" : "non-repo" };
4022
4115
  }
4023
4116
  async function doRunProjectAdopt(options, ctx) {
4024
4117
  const cwd = ctx.cwd ?? process.cwd();
@@ -4122,7 +4215,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
4122
4215
  } catch {
4123
4216
  return { ...base, reachable: false, instructionFiles: [] };
4124
4217
  }
4125
- if (!existsSync(join9(real, ".git"))) {
4218
+ if (!existsSync2(join10(real, ".git"))) {
4126
4219
  return { ...base, reachable: false, instructionFiles: [] };
4127
4220
  }
4128
4221
  try {
@@ -4130,7 +4223,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
4130
4223
  for (const name of INSTRUCTION_FILES) {
4131
4224
  let present = true;
4132
4225
  try {
4133
- lstatSync(join9(real, name));
4226
+ lstatSync(join10(real, name));
4134
4227
  } catch {
4135
4228
  present = false;
4136
4229
  }
@@ -4241,10 +4334,10 @@ function gatherRepoGitignore(repositoryRoot, entry) {
4241
4334
  } catch {
4242
4335
  return { ...base, reachable: false, currentLines: [] };
4243
4336
  }
4244
- if (!existsSync(join9(real, ".git"))) {
4337
+ if (!existsSync2(join10(real, ".git"))) {
4245
4338
  return { ...base, reachable: false, currentLines: [] };
4246
4339
  }
4247
- return { ...base, reachable: true, currentLines: readGitignoreLines(join9(real, ".gitignore")) };
4340
+ return { ...base, reachable: true, currentLines: readGitignoreLines(join10(real, ".gitignore")) };
4248
4341
  }
4249
4342
  function hasErrorCode(error) {
4250
4343
  return error instanceof Error && typeof error.code === "string";
@@ -4258,7 +4351,7 @@ function readGitignoreLines(file) {
4258
4351
  }
4259
4352
  }
4260
4353
  function applyGitignorePlan(repositoryRoot, plan) {
4261
- const file = join9(realpathSync(resolve7(repositoryRoot, plan.path)), ".gitignore");
4354
+ const file = join10(realpathSync(resolve7(repositoryRoot, plan.path)), ".gitignore");
4262
4355
  let existing = "";
4263
4356
  try {
4264
4357
  existing = readFileSync(file, "utf8");
@@ -4384,7 +4477,7 @@ function anchorCanonicalState(filePath) {
4384
4477
  if (hasErrorCode(error) && error.code === "ENOENT") return "absent";
4385
4478
  return "broken";
4386
4479
  }
4387
- if (st.isSymbolicLink()) return existsSync(filePath) ? "usable" : "broken";
4480
+ if (st.isSymbolicLink()) return existsSync2(filePath) ? "usable" : "broken";
4388
4481
  return st.isFile() ? "usable" : "broken";
4389
4482
  }
4390
4483
  function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
@@ -4398,7 +4491,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
4398
4491
  return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
4399
4492
  }
4400
4493
  if (real === anchorReal) {
4401
- const anchorCanonical = join9(real, CANONICAL_FILE);
4494
+ const anchorCanonical = join10(real, CANONICAL_FILE);
4402
4495
  const anchorState = anchorCanonicalState(anchorCanonical);
4403
4496
  if (anchorState === "absent") {
4404
4497
  return { ...base, isAnchor: true, reachable: true, canonicalPresent: false, files: [] };
@@ -4418,7 +4511,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
4418
4511
  anchorCanonical,
4419
4512
  "self"
4420
4513
  ).map((spec) => {
4421
- const { state, actualTarget } = inspectSymlink(join9(real, spec.name), spec.target);
4514
+ const { state, actualTarget } = inspectSymlink(join10(real, spec.name), spec.target);
4422
4515
  return {
4423
4516
  name: spec.name,
4424
4517
  expectedTarget: spec.target,
@@ -4435,16 +4528,16 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
4435
4528
  files: anchorFiles
4436
4529
  };
4437
4530
  }
4438
- if (!existsSync(join9(real, ".git"))) {
4531
+ if (!existsSync2(join10(real, ".git"))) {
4439
4532
  return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
4440
4533
  }
4441
- const canonicalFile = isSelf ? join9(real, CANONICAL_FILE) : join9(anchorReal, "agents", basename5(real), CANONICAL_FILE);
4442
- if (!existsSync(canonicalFile)) {
4534
+ const canonicalFile = isSelf ? join10(real, CANONICAL_FILE) : join10(anchorReal, "agents", basename5(real), CANONICAL_FILE);
4535
+ if (!existsSync2(canonicalFile)) {
4443
4536
  return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
4444
4537
  }
4445
4538
  const files = expectedSymlinkTargets(real, canonicalFile, mode).map(
4446
4539
  (spec) => {
4447
- const { state, actualTarget } = inspectSymlink(join9(real, spec.name), spec.target);
4540
+ const { state, actualTarget } = inspectSymlink(join10(real, spec.name), spec.target);
4448
4541
  return {
4449
4542
  name: spec.name,
4450
4543
  expectedTarget: spec.target,
@@ -4473,7 +4566,7 @@ function applySymlinkPlan(repositoryRoot, plan) {
4473
4566
  const created = [];
4474
4567
  const failed = [];
4475
4568
  for (const { name, target } of plan.toCreate) {
4476
- const filePath = join9(real, name);
4569
+ const filePath = join10(real, name);
4477
4570
  try {
4478
4571
  mkdirSync(dirname3(filePath), { recursive: true });
4479
4572
  symlinkSync(target, filePath);
@@ -4504,10 +4597,10 @@ function gatherViewSymlinks(repositoryRoot, anchorReal, roster, viewDir) {
4504
4597
  const collision = viewCanonicalCollision(repositoryRoot, roster, viewName);
4505
4598
  if (collision !== void 0) return { kind: "collision", viewName, repoPath: collision };
4506
4599
  const canonicalFile = canonicalFileFor(anchorReal, viewName);
4507
- if (!existsSync(canonicalFile)) return { kind: "missing-canonical", viewName };
4600
+ if (!existsSync2(canonicalFile)) return { kind: "missing-canonical", viewName };
4508
4601
  const files = expectedSymlinkTargets(viewDir, canonicalFile, "hub").map(
4509
4602
  (spec) => {
4510
- const { state, actualTarget } = inspectSymlink(join9(viewDir, spec.name), spec.target);
4603
+ const { state, actualTarget } = inspectSymlink(join10(viewDir, spec.name), spec.target);
4511
4604
  return {
4512
4605
  name: spec.name,
4513
4606
  expectedTarget: spec.target,
@@ -4523,7 +4616,7 @@ function applyViewSymlinks(viewDir, files) {
4523
4616
  const failed = [];
4524
4617
  for (const f of files) {
4525
4618
  if (f.state !== "missing") continue;
4526
- const filePath = join9(viewDir, f.name);
4619
+ const filePath = join10(viewDir, f.name);
4527
4620
  try {
4528
4621
  mkdirSync(dirname3(filePath), { recursive: true });
4529
4622
  symlinkSync(f.expectedTarget, filePath);
@@ -4745,7 +4838,7 @@ function resolveViewDir(repositoryRoot, viewPath) {
4745
4838
  return realpathSync(abs);
4746
4839
  } catch {
4747
4840
  try {
4748
- return join9(realpathSync(dirname3(abs)), basename5(abs));
4841
+ return join10(realpathSync(dirname3(abs)), basename5(abs));
4749
4842
  } catch {
4750
4843
  return abs;
4751
4844
  }
@@ -4763,7 +4856,7 @@ function gatherViewRepo(repositoryRoot, viewDir, entry) {
4763
4856
  return { path: entry.path, reachable: false };
4764
4857
  }
4765
4858
  const linkName = basename5(repoReal);
4766
- const { state, actualTarget } = inspectSymlink(join9(viewDir, linkName), expectedTarget);
4859
+ const { state, actualTarget } = inspectSymlink(join10(viewDir, linkName), expectedTarget);
4767
4860
  return {
4768
4861
  path: entry.path,
4769
4862
  reachable: true,
@@ -4777,7 +4870,7 @@ function applyViewPlan(viewDir, toCreate) {
4777
4870
  const created = [];
4778
4871
  const failed = [];
4779
4872
  for (const { name, target } of toCreate) {
4780
- const filePath = join9(viewDir, name);
4873
+ const filePath = join10(viewDir, name);
4781
4874
  try {
4782
4875
  mkdirSync(dirname3(filePath), { recursive: true });
4783
4876
  symlinkSync(target, filePath);
@@ -4792,7 +4885,7 @@ var TOP_LEVEL_INSTRUCTION_FILES_LOWER = new Set(
4792
4885
  INSTRUCTION_FILES.filter((f) => !f.includes("/")).map((f) => f.toLowerCase())
4793
4886
  );
4794
4887
  function classifyViewLink(viewDir, name, rosterRealpaths) {
4795
- const filePath = join9(viewDir, name);
4888
+ const filePath = join10(viewDir, name);
4796
4889
  let isLink;
4797
4890
  try {
4798
4891
  isLink = lstatSync(filePath).isSymbolicLink();
@@ -4814,14 +4907,14 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
4814
4907
  if (isAbsolute3(target)) return { target, kind: "absolute" };
4815
4908
  let isDir = false;
4816
4909
  try {
4817
- isDir = statSync(resolved).isDirectory();
4910
+ isDir = statSync2(resolved).isDirectory();
4818
4911
  } catch {
4819
4912
  isDir = false;
4820
4913
  }
4821
4914
  if (!isDir) {
4822
- return { target, kind: existsSync(resolved) ? "non-repo" : "broken" };
4915
+ return { target, kind: existsSync2(resolved) ? "non-repo" : "broken" };
4823
4916
  }
4824
- return { target, kind: existsSync(join9(resolved, ".git")) ? "repo" : "non-repo" };
4917
+ return { target, kind: existsSync2(join10(resolved, ".git")) ? "repo" : "non-repo" };
4825
4918
  }
4826
4919
  function gatherExistingViewLinks(viewDir, rosterRealpaths) {
4827
4920
  let names;
@@ -4846,7 +4939,7 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
4846
4939
  const pruned = [];
4847
4940
  const failed = [];
4848
4941
  for (const { name } of toPrune) {
4849
- const filePath = join9(viewDir, name);
4942
+ const filePath = join10(viewDir, name);
4850
4943
  const c = classifyViewLink(viewDir, name, rosterRealpaths);
4851
4944
  if (c === null || c.kind !== "repo") {
4852
4945
  failed.push({
@@ -5059,10 +5152,10 @@ async function runProjectPreset(options, ctx = {}) {
5059
5152
  }
5060
5153
  }
5061
5154
  function canonicalFileFor(anchorReal, canonicalName) {
5062
- return join9(anchorReal, "agents", canonicalName, CANONICAL_FILE);
5155
+ return join10(anchorReal, "agents", canonicalName, CANONICAL_FILE);
5063
5156
  }
5064
5157
  function canonicalLabelFor(canonicalName) {
5065
- return join9("agents", canonicalName, CANONICAL_FILE);
5158
+ return join10("agents", canonicalName, CANONICAL_FILE);
5066
5159
  }
5067
5160
  async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
5068
5161
  const declared = {
@@ -5083,7 +5176,7 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
5083
5176
  if (real === anchorReal) {
5084
5177
  return { ...declared, isAnchor: true, reachable: true, canonicalPresent: false };
5085
5178
  }
5086
- if (!existsSync(join9(real, ".git"))) {
5179
+ if (!existsSync2(join10(real, ".git"))) {
5087
5180
  return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
5088
5181
  }
5089
5182
  const canonicalName = basename5(real);
@@ -5465,24 +5558,24 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
5465
5558
  const instructionFiles = [];
5466
5559
  for (const name of INSTRUCTION_FILES) {
5467
5560
  try {
5468
- lstatSync(join9(real, name));
5561
+ lstatSync(join10(real, name));
5469
5562
  instructionFiles.push(name);
5470
5563
  } catch {
5471
5564
  }
5472
5565
  }
5473
5566
  let ignored;
5474
5567
  try {
5475
- ignored = new Set(readGitignoreLines(join9(real, ".gitignore")).map((l) => l.trim()));
5568
+ ignored = new Set(readGitignoreLines(join10(real, ".gitignore")).map((l) => l.trim()));
5476
5569
  } catch {
5477
5570
  ignored = /* @__PURE__ */ new Set();
5478
5571
  }
5479
5572
  const gitignorePatterns = INSTRUCTION_FILES.filter((p) => ignored.has(p) || ignored.has(`/${p}`));
5480
- const canonical2 = existsSync(join9(anchorReal, "agents", canonicalName, CANONICAL_FILE));
5573
+ const canonical2 = existsSync2(join10(anchorReal, "agents", canonicalName, CANONICAL_FILE));
5481
5574
  let viewLink = false;
5482
5575
  const viewPath = manifest.workspace.view;
5483
5576
  if (viewPath !== void 0) {
5484
5577
  try {
5485
- lstatSync(join9(resolveViewDir(repositoryRoot, viewPath), canonicalName));
5578
+ lstatSync(join10(resolveViewDir(repositoryRoot, viewPath), canonicalName));
5486
5579
  viewLink = true;
5487
5580
  } catch {
5488
5581
  }
@@ -5496,11 +5589,11 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
5496
5589
  };
5497
5590
  }
5498
5591
  function teardownExpectedTargets(repoReal, anchorReal, canonicalName) {
5499
- const canonicalFile = join9(anchorReal, "agents", canonicalName, CANONICAL_FILE);
5592
+ const canonicalFile = join10(anchorReal, "agents", canonicalName, CANONICAL_FILE);
5500
5593
  return expectedSymlinkTargets(repoReal, canonicalFile);
5501
5594
  }
5502
5595
  function viewLinkPointsAt(viewDir, name, repoReal) {
5503
- const filePath = join9(viewDir, name);
5596
+ const filePath = join10(viewDir, name);
5504
5597
  try {
5505
5598
  if (!lstatSync(filePath).isSymbolicLink()) return false;
5506
5599
  const target = readlinkSync(filePath);
@@ -5511,7 +5604,7 @@ function viewLinkPointsAt(viewDir, name, repoReal) {
5511
5604
  }
5512
5605
  }
5513
5606
  function viewLinkPointsAtPath(viewDir, name, expectedRepoPath) {
5514
- const filePath = join9(viewDir, name);
5607
+ const filePath = join10(viewDir, name);
5515
5608
  try {
5516
5609
  if (!lstatSync(filePath).isSymbolicLink()) return false;
5517
5610
  const target = readlinkSync(filePath);
@@ -5562,7 +5655,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
5562
5655
  if (!isAnchor) {
5563
5656
  if (repoReal !== void 0) {
5564
5657
  for (const spec of teardownExpectedTargets(repoReal, anchorReal, canonicalName)) {
5565
- const { state, actualTarget } = inspectSymlink(join9(repoReal, spec.name), spec.target);
5658
+ const { state, actualTarget } = inspectSymlink(join10(repoReal, spec.name), spec.target);
5566
5659
  if (isSelf) {
5567
5660
  if (state !== "missing")
5568
5661
  items.push({
@@ -5599,7 +5692,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
5599
5692
  }
5600
5693
  let ignored;
5601
5694
  try {
5602
- ignored = new Set(readGitignoreLines(join9(repoReal, ".gitignore")).map((l) => l.trim()));
5695
+ ignored = new Set(readGitignoreLines(join10(repoReal, ".gitignore")).map((l) => l.trim()));
5603
5696
  for (const p of INSTRUCTION_FILES) {
5604
5697
  if (ignored.has(p) || ignored.has(`/${p}`)) {
5605
5698
  items.push({
@@ -5622,7 +5715,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
5622
5715
  const viewPath = manifest.workspace.view;
5623
5716
  if (viewPath !== void 0) {
5624
5717
  const viewDir = resolveViewDir(repositoryRoot, viewPath);
5625
- const linkPath = join9(viewDir, canonicalName);
5718
+ const linkPath = join10(viewDir, canonicalName);
5626
5719
  let isLink = false;
5627
5720
  try {
5628
5721
  isLink = lstatSync(linkPath).isSymbolicLink();
@@ -5648,8 +5741,8 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
5648
5741
  else items.push({ kind: "view-symlink", label: canonicalName, state: "removable" });
5649
5742
  }
5650
5743
  }
5651
- const canonicalFile = join9(anchorReal, "agents", canonicalName, CANONICAL_FILE);
5652
- const canonicalLabel = join9("agents", canonicalName, CANONICAL_FILE);
5744
+ const canonicalFile = join10(anchorReal, "agents", canonicalName, CANONICAL_FILE);
5745
+ const canonicalLabel = join10("agents", canonicalName, CANONICAL_FILE);
5653
5746
  let canonicalIsLink = false;
5654
5747
  try {
5655
5748
  canonicalIsLink = lstatSync(canonicalFile).isSymbolicLink();
@@ -5663,7 +5756,7 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
5663
5756
  state: "foreign",
5664
5757
  note: "the canonical is a symlink (not generated)"
5665
5758
  });
5666
- } else if (existsSync(canonicalFile)) {
5759
+ } else if (existsSync2(canonicalFile)) {
5667
5760
  let content;
5668
5761
  try {
5669
5762
  content = readFileSync(canonicalFile, "utf8");
@@ -5753,12 +5846,12 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
5753
5846
  );
5754
5847
  for (const item of removable.filter((i) => i.kind === "instruction-symlink")) {
5755
5848
  const expected = expectedByName.get(item.label);
5756
- if (repoReal === null || expected === void 0 || inspectSymlink(join9(repoReal, item.label), expected).state !== "correct") {
5849
+ if (repoReal === null || expected === void 0 || inspectSymlink(join10(repoReal, item.label), expected).state !== "correct") {
5757
5850
  changed(item.label);
5758
5851
  continue;
5759
5852
  }
5760
5853
  try {
5761
- unlinkSync(join9(repoReal, item.label));
5854
+ unlinkSync(join10(repoReal, item.label));
5762
5855
  removed.push(item.label);
5763
5856
  } catch (error) {
5764
5857
  failed.push({ label: item.label, message: failureReason(error) });
@@ -5777,7 +5870,7 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
5777
5870
  continue;
5778
5871
  }
5779
5872
  try {
5780
- unlinkSync(join9(viewDir, item.label));
5873
+ unlinkSync(join10(viewDir, item.label));
5781
5874
  removed.push(`view/${item.label}`);
5782
5875
  } catch (error) {
5783
5876
  failed.push({ label: `view/${item.label}`, message: failureReason(error) });
@@ -5785,7 +5878,7 @@ function applyRepoTeardown(repositoryRoot, manifest, plan) {
5785
5878
  }
5786
5879
  const NOFOLLOW = fsConstants.O_NOFOLLOW ?? 0;
5787
5880
  for (const item of removable.filter((i) => i.kind === "canonical-block")) {
5788
- const canonicalFile = join9(anchorReal, "agents", canonicalName, CANONICAL_FILE);
5881
+ const canonicalFile = join10(anchorReal, "agents", canonicalName, CANONICAL_FILE);
5789
5882
  try {
5790
5883
  if (lstatSync(canonicalFile).isSymbolicLink()) {
5791
5884
  changed(item.label);
@@ -6059,12 +6152,12 @@ function gatherRenameWiring(repositoryRoot, manifest, oldBasename) {
6059
6152
  } catch {
6060
6153
  return { canonicalDirOld: false, viewLinkOld: false };
6061
6154
  }
6062
- const canonicalDirOld = existsSync(join9(anchorReal, "agents", oldBasename));
6155
+ const canonicalDirOld = existsSync2(join10(anchorReal, "agents", oldBasename));
6063
6156
  let viewLinkOld = false;
6064
6157
  const viewPath = manifest.workspace.view;
6065
6158
  if (viewPath !== void 0) {
6066
6159
  try {
6067
- lstatSync(join9(resolveViewDir(repositoryRoot, viewPath), oldBasename));
6160
+ lstatSync(join10(resolveViewDir(repositoryRoot, viewPath), oldBasename));
6068
6161
  viewLinkOld = true;
6069
6162
  } catch {
6070
6163
  }
@@ -6262,7 +6355,7 @@ async function doRunProjectNew(repos, options, ctx) {
6262
6355
  const viewPath = options.view === false ? null : options.view ?? `../${viewStem}-workspace`;
6263
6356
  const sourceRoots = [...rosterPaths, ...viewPath !== null ? [viewPath] : []];
6264
6357
  const paths = basouPaths10(repositoryRoot);
6265
- const existed = existsSync(paths.files.manifest);
6358
+ const existed = existsSync2(paths.files.manifest);
6266
6359
  const manifest = createManifest2({
6267
6360
  workspaceName,
6268
6361
  sourceRoots,
@@ -6422,7 +6515,7 @@ async function doRunProjectSeedAnchor(options, ctx) {
6422
6515
  console.log("\u2139\uFE0F No repo roster declared \u2014 nothing to seed.");
6423
6516
  return;
6424
6517
  }
6425
- const anchorDoc = join9(repositoryRoot, CANONICAL_FILE);
6518
+ const anchorDoc = join10(repositoryRoot, CANONICAL_FILE);
6426
6519
  if (pathPresent(anchorDoc)) {
6427
6520
  console.log(
6428
6521
  `\u2705 The anchor's own \`${CANONICAL_FILE}\` already exists \u2014 hand-maintained, left untouched.`
@@ -6484,7 +6577,7 @@ function regularFileSpokes(repoReal) {
6484
6577
  const out = [];
6485
6578
  for (const spoke of ["CLAUDE.md", ".github/copilot-instructions.md"]) {
6486
6579
  try {
6487
- const st = lstatSync(join9(repoReal, spoke));
6580
+ const st = lstatSync(join10(repoReal, spoke));
6488
6581
  if (!st.isSymbolicLink() && st.isFile()) out.push(spoke);
6489
6582
  } catch {
6490
6583
  }
@@ -6530,8 +6623,8 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argPath, argAbs, arg
6530
6623
  };
6531
6624
  }
6532
6625
  const isAnchor = argReal === anchorReal;
6533
- const reachable = existsSync(join9(argReal, ".git"));
6534
- const canonicalFile = join9(anchorReal, "agents", canonicalName, CANONICAL_FILE);
6626
+ const reachable = existsSync2(join10(argReal, ".git"));
6627
+ const canonicalFile = join10(anchorReal, "agents", canonicalName, CANONICAL_FILE);
6535
6628
  return {
6536
6629
  path,
6537
6630
  declared,
@@ -6540,13 +6633,13 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argPath, argAbs, arg
6540
6633
  reachable,
6541
6634
  canonicalName,
6542
6635
  ...viewCanonicalName !== void 0 ? { viewCanonicalName } : {},
6543
- agentsState: inspectAgentsState(join9(argReal, CANONICAL_FILE)),
6636
+ agentsState: inspectAgentsState(join10(argReal, CANONICAL_FILE)),
6544
6637
  canonicalExists: pathPresent(canonicalFile),
6545
6638
  regularSpokes: regularFileSpokes(argReal)
6546
6639
  };
6547
6640
  }
6548
6641
  function relocateAgentsFile(repoReal, canonicalFile) {
6549
- const agentsFile = join9(repoReal, CANONICAL_FILE);
6642
+ const agentsFile = join10(repoReal, CANONICAL_FILE);
6550
6643
  try {
6551
6644
  mkdirSync(dirname3(canonicalFile), { recursive: true });
6552
6645
  } catch (error) {
@@ -6663,7 +6756,7 @@ async function doRunProjectRetrofit(repo, options, ctx) {
6663
6756
  let failure;
6664
6757
  let partial = false;
6665
6758
  if (options.apply === true && plan.action === "relocate" && argReal !== void 0) {
6666
- const canonicalFile = join9(anchorReal, "agents", plan.canonicalName, CANONICAL_FILE);
6759
+ const canonicalFile = join10(anchorReal, "agents", plan.canonicalName, CANONICAL_FILE);
6667
6760
  const res = relocateAgentsFile(argReal, canonicalFile);
6668
6761
  if (res.ok) {
6669
6762
  applied = true;
@@ -6868,7 +6961,7 @@ import { PROTOCOL_END, PROTOCOL_START, parseMarkers as parseMarkers3, readMarkdo
6868
6961
 
6869
6962
  // src/lib/context-channel.ts
6870
6963
  import { homedir as homedir7 } from "os";
6871
- import { join as join10 } from "path";
6964
+ import { join as join11 } from "path";
6872
6965
  import {
6873
6966
  ORIENTATION_END,
6874
6967
  ORIENTATION_START,
@@ -6876,7 +6969,7 @@ import {
6876
6969
  readMarkdownFile as readMarkdownFile5,
6877
6970
  removeMarkerSection as removeMarkerSection2
6878
6971
  } from "@basou/core";
6879
- var CODEX_TARGET_PATH = join10(homedir7(), ".codex", "AGENTS.md");
6972
+ var CODEX_TARGET_PATH = join11(homedir7(), ".codex", "AGENTS.md");
6880
6973
  var ORIENTATION_MARKERS = { start: ORIENTATION_START, end: ORIENTATION_END };
6881
6974
  var ORIENTATION_MANAGED_NOTE = "<!-- Managed by basou: 'basou refresh' regenerates everything between the BASOU:ORIENTATION markers with the workspace's current position. This block is transient \u2014 it changes every refresh; do not edit it. -->";
6882
6975
  function buildTargetBody(existing, block, markers) {
@@ -6980,15 +7073,15 @@ async function renderOrientationToCodexChannel(opts) {
6980
7073
 
6981
7074
  // src/lib/protocols-config.ts
6982
7075
  import { homedir as homedir8 } from "os";
6983
- import { isAbsolute as isAbsolute4, join as join11, resolve as resolve8 } from "path";
7076
+ import { isAbsolute as isAbsolute4, join as join12, resolve as resolve8 } from "path";
6984
7077
  import { readYamlFile as readYamlFile5 } from "@basou/core";
6985
- var DEFAULT_PROTOCOLS_CONFIG_PATH = join11(homedir8(), ".basou", "protocols.yaml");
6986
- var DEFAULT_TARGET_PATH = join11(homedir8(), ".claude", "CLAUDE.md");
7078
+ var DEFAULT_PROTOCOLS_CONFIG_PATH = join12(homedir8(), ".basou", "protocols.yaml");
7079
+ var DEFAULT_TARGET_PATH = join12(homedir8(), ".claude", "CLAUDE.md");
6987
7080
  var ALLOWED_TOP_KEYS = /* @__PURE__ */ new Set(["version", "protocols"]);
6988
7081
  var ALLOWED_ENTRY_KEYS = /* @__PURE__ */ new Set(["source", "title"]);
6989
7082
  function expandTilde3(p) {
6990
7083
  if (p === "~") return homedir8();
6991
- if (p.startsWith("~/")) return join11(homedir8(), p.slice(2));
7084
+ if (p.startsWith("~/")) return join12(homedir8(), p.slice(2));
6992
7085
  return p;
6993
7086
  }
6994
7087
  function isRecord3(value) {
@@ -7193,15 +7286,15 @@ import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
7193
7286
  // src/commands/refresh-watch.ts
7194
7287
  import { readdir as readdir2, stat as stat5 } from "fs/promises";
7195
7288
  import { homedir as homedir9 } from "os";
7196
- import { join as join12 } from "path";
7289
+ import { join as join13 } from "path";
7197
7290
  import { findErrorCode as findErrorCode8 } from "@basou/core";
7198
7291
  var DEFAULT_WATCH_INTERVAL_SEC = 30;
7199
7292
  var MIN_WATCH_INTERVAL_SEC = 5;
7200
7293
  var MAX_WATCH_INTERVAL_SEC = 86400;
7201
7294
  function watchedRoots(ctx) {
7202
7295
  return [
7203
- ctx.codexSessionsDir ?? join12(homedir9(), ".codex", "sessions"),
7204
- ctx.claudeProjectsDir ?? join12(homedir9(), ".claude", "projects")
7296
+ ctx.codexSessionsDir ?? join13(homedir9(), ".codex", "sessions"),
7297
+ ctx.claudeProjectsDir ?? join13(homedir9(), ".claude", "projects")
7205
7298
  ];
7206
7299
  }
7207
7300
  async function scanSourceLogs(roots) {
@@ -7215,7 +7308,7 @@ async function scanSourceLogs(roots) {
7215
7308
  throw new Error("Failed to read a source log directory", { cause: error });
7216
7309
  }
7217
7310
  for (const entry of entries) {
7218
- const full = join12(dir, entry.name);
7311
+ const full = join13(dir, entry.name);
7219
7312
  if (entry.isDirectory()) {
7220
7313
  await walk(full);
7221
7314
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
@@ -7937,7 +8030,7 @@ function renderReviewGaps(summary) {
7937
8030
  // src/commands/run.ts
7938
8031
  import { mkdir as mkdir2 } from "fs/promises";
7939
8032
  import { homedir as homedir11 } from "os";
7940
- import { join as join13 } from "path";
8033
+ import { join as join14 } from "path";
7941
8034
  import {
7942
8035
  acquireLock as acquireLock5,
7943
8036
  assertBasouRootSafe as assertBasouRootSafe12,
@@ -8013,13 +8106,13 @@ async function runTrackedTool(args, options, ctx, adapter) {
8013
8106
  await assertBasouRootSafe12(paths.root);
8014
8107
  const manifest = await readManifest8(paths);
8015
8108
  const sessionId = prefixedUlid4("ses");
8016
- const sessionDir = join13(paths.sessions, sessionId);
8109
+ const sessionDir = join14(paths.sessions, sessionId);
8017
8110
  await mkdir2(sessionDir, { recursive: true });
8018
8111
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
8019
8112
  await coreAppendChainedEvent2(paths, sessionId, event);
8020
8113
  });
8021
8114
  const startedAt = now().toISOString();
8022
- const sessionYamlPath = join13(sessionDir, "session.yaml");
8115
+ const sessionYamlPath = join14(sessionDir, "session.yaml");
8023
8116
  const session = buildInitialSession2({
8024
8117
  id: sessionId,
8025
8118
  command,
@@ -8381,7 +8474,7 @@ async function syncCodexOrientationChannelPreSpawn(cwd, ctx) {
8381
8474
 
8382
8475
  // src/commands/session.ts
8383
8476
  import { readFile as readFile6 } from "fs/promises";
8384
- import { basename as basename6, isAbsolute as isAbsolute6, join as join14, relative as relative3 } from "path";
8477
+ import { basename as basename6, isAbsolute as isAbsolute6, join as join15, relative as relative3 } from "path";
8385
8478
  import {
8386
8479
  acquireLock as acquireLock6,
8387
8480
  appendEventToExistingSession as appendEventToExistingSession3,
@@ -8506,8 +8599,8 @@ async function doRunSessionShow(idInput, options, ctx) {
8506
8599
  const paths = basouPaths16(repositoryRoot);
8507
8600
  await assertWorkspaceInitialized11(paths.root);
8508
8601
  const sessionId = await resolveSessionId3(paths, idInput);
8509
- const sessionDir = join14(paths.sessions, sessionId);
8510
- const sessionYamlPath = join14(sessionDir, "session.yaml");
8602
+ const sessionDir = join15(paths.sessions, sessionId);
8603
+ const sessionYamlPath = join15(sessionDir, "session.yaml");
8511
8604
  let session;
8512
8605
  try {
8513
8606
  const raw = await readYamlFile7(sessionYamlPath);
@@ -9269,7 +9362,7 @@ function formatVersionGateMessage(error) {
9269
9362
 
9270
9363
  // src/commands/task.ts
9271
9364
  import { readFile as readFile7 } from "fs/promises";
9272
- import { join as join15 } from "path";
9365
+ import { join as join16 } from "path";
9273
9366
  import {
9274
9367
  archiveTask,
9275
9368
  assertBasouRootSafe as assertBasouRootSafe16,
@@ -9595,7 +9688,7 @@ async function doRunTaskShow(idInput, options, ctx) {
9595
9688
  const events = [];
9596
9689
  const linkedSessionIds = new Set(doc.task.task.linked_sessions);
9597
9690
  for (const s of sessions) {
9598
- const sessionDir = join15(paths.sessions, s.sessionId);
9691
+ const sessionDir = join16(paths.sessions, s.sessionId);
9599
9692
  try {
9600
9693
  for await (const ev of replayEvents3(sessionDir, {
9601
9694
  onWarning: (w) => printReplayWarning(w, s.sessionId)
@@ -10501,7 +10594,7 @@ import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
10501
10594
  // src/lib/portfolio-safety.ts
10502
10595
  import { execFile } from "child_process";
10503
10596
  import { lstat as lstat2, realpath as realpath2 } from "fs/promises";
10504
- import { isAbsolute as isAbsolute7, join as join16, relative as relative4, resolve as resolve11 } from "path";
10597
+ import { isAbsolute as isAbsolute7, join as join17, relative as relative4, resolve as resolve11 } from "path";
10505
10598
  import { promisify } from "util";
10506
10599
  import { readManifest as readManifest12 } from "@basou/core";
10507
10600
  var execFileAsync = promisify(execFile);
@@ -10525,7 +10618,7 @@ function isBasouPath(p) {
10525
10618
  async function inspectRepo(repoPath) {
10526
10619
  let hasEntry = false;
10527
10620
  try {
10528
- await lstat2(join16(repoPath, ".basou"));
10621
+ await lstat2(join17(repoPath, ".basou"));
10529
10622
  hasEntry = true;
10530
10623
  } catch (error) {
10531
10624
  if (errorCode(error) !== "ENOENT") {
@@ -10679,7 +10772,7 @@ function formatSafetyReport(result) {
10679
10772
 
10680
10773
  // src/lib/view-server.ts
10681
10774
  import { createServer } from "http";
10682
- import { basename as basename7, join as join17, resolve as resolve12 } from "path";
10775
+ import { basename as basename7, join as join18, resolve as resolve12 } from "path";
10683
10776
  import {
10684
10777
  computeWorkStats as computeWorkStats2,
10685
10778
  enumerateApprovals as enumerateApprovals2,
@@ -11719,7 +11812,7 @@ async function sessionDetail(ws, sessionId) {
11719
11812
  throw error;
11720
11813
  }
11721
11814
  try {
11722
- const events = await readAllEvents2(join17(ws.paths.sessions, sessionId));
11815
+ const events = await readAllEvents2(join18(ws.paths.sessions, sessionId));
11723
11816
  return { session, events };
11724
11817
  } catch {
11725
11818
  return { session, events: [], degraded: true };
@@ -12100,6 +12193,7 @@ function buildProgram() {
12100
12193
  registerDecisionsCommand(program);
12101
12194
  registerReportCommand(program);
12102
12195
  registerOrientCommand(program);
12196
+ registerPortfolioCommand(program);
12103
12197
  registerReviewCommand(program);
12104
12198
  registerReviewGapsCommand(program);
12105
12199
  registerProjectCommand(program);