@biffo/cli 0.253.5 → 0.253.7

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.
Files changed (2) hide show
  1. package/dist/index.js +281 -228
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -570,8 +570,8 @@ async function runCoreStatus(options) {
570
570
 
571
571
  // src/commands/core-upgrade.ts
572
572
  import { execSync as execSync2 } from "child_process";
573
- import { existsSync as existsSync12, rmSync as rmSync5 } from "fs";
574
- import { join as join13, resolve as resolve3 } from "path";
573
+ import { existsSync as existsSync13, rmSync as rmSync5 } from "fs";
574
+ import { join as join14, resolve as resolve3 } from "path";
575
575
  import chalk4 from "chalk";
576
576
  import { execa as execa3 } from "execa";
577
577
  import { Command as Command3 } from "commander";
@@ -3146,6 +3146,32 @@ function describeFailures(outcomes) {
3146
3146
  );
3147
3147
  }
3148
3148
 
3149
+ // src/lib/instance-dependency-install.ts
3150
+ import { existsSync as existsSync12 } from "fs";
3151
+ import { join as join13 } from "path";
3152
+ function dependencyInstallSteps(instanceDir) {
3153
+ const steps = [{ ecosystem: "pnpm", command: ["pnpm", "install"] }];
3154
+ if (existsSync12(join13(instanceDir, "pyproject.toml"))) {
3155
+ steps.push({ ecosystem: "uv", command: ["uv", "sync"] });
3156
+ }
3157
+ return steps;
3158
+ }
3159
+ async function installInstanceDependencies(instanceDir, run, steps = dependencyInstallSteps(instanceDir)) {
3160
+ const outcomes = [];
3161
+ for (const step of steps) {
3162
+ const result = await run(step.command, instanceDir);
3163
+ const outcome = { step, ok: result.ok };
3164
+ if (result.error !== void 0) outcome.error = result.error;
3165
+ outcomes.push(outcome);
3166
+ }
3167
+ return outcomes;
3168
+ }
3169
+ function describeInstallFailures(outcomes) {
3170
+ return outcomes.filter((o) => !o.ok).map(
3171
+ (o) => `${o.step.ecosystem} dependency install failed: ${o.error ?? "unknown error"}. Run \`${o.step.command.join(" ")}\` in the instance before pushing, or the pre-push gate (\`scripts/verify.sh\`) will reject the push against a tree with no installed dependencies (#1040).`
3172
+ );
3173
+ }
3174
+
3149
3175
  // src/commands/core-upgrade.ts
3150
3176
  var MISSING_TEMPLATE_ROOT_GUIDANCE2 = "Pass --template-repo <path> to a biffo-template git checkout, e.g. `biffo core upgrade --template-repo /path/to/biffo-template`.";
3151
3177
  function resolveTemplateRepoFlag(templateRepo, template) {
@@ -3492,7 +3518,7 @@ async function buildCommitAndOpenPr(options, deps, plan, migrations, fromVersion
3492
3518
  );
3493
3519
  }
3494
3520
  const cleanedCoreVersion = coreVersionCleanup?.action === "delete";
3495
- if (cleanedCoreVersion && existsSync12(coreVersionCleanup.path)) {
3521
+ if (cleanedCoreVersion && existsSync13(coreVersionCleanup.path)) {
3496
3522
  rmSync5(coreVersionCleanup.path);
3497
3523
  log.info(
3498
3524
  coreVersionCleanup.stale ? `Deleted stale ${CORE_VERSION_FILE} (recorded ${coreVersionCleanup.found}, behind the version biffo.core.json records \u2014 this instance has moved past it) (#842).` : `Deleted orphaned ${CORE_VERSION_FILE} (inherited copy recording ${coreVersionCleanup.found}, superseded by biffo.core.json) \u2014 nothing reads it as an authority (#434).`
@@ -3507,10 +3533,26 @@ async function buildCommitAndOpenPr(options, deps, plan, migrations, fromVersion
3507
3533
  const carriedPrs = readCarriedPrs(options.templateRepo, fromVersion, toVersion);
3508
3534
  await git.add(options.cwd, ["-A"]);
3509
3535
  await git.commit(options.cwd, buildCommitMessage(fromVersion, toVersion, carriedPrs));
3536
+ const run = deps.runCommand ?? defaultRunCommand;
3537
+ const installOutcomes = await installInstanceDependencies(options.cwd, run);
3538
+ const installFailures = describeInstallFailures(installOutcomes);
3539
+ for (const message of installFailures) log.warn(message);
3510
3540
  log.step(3, 4, `Pushing ${branch}`);
3511
3541
  const pushOpts = { token };
3512
3542
  if (options.remote) pushOpts.remote = options.remote;
3513
- await git.push(options.cwd, branch, pushOpts);
3543
+ try {
3544
+ await git.push(options.cwd, branch, pushOpts);
3545
+ } catch (err) {
3546
+ const marker = carriedPrsSection(carriedPrs).join("\n").trim();
3547
+ log.error(
3548
+ `Push failed: ${err.message}
3549
+
3550
+ The branch ${branch} is committed locally. Push it and open the PR by hand \u2014 and ` + (marker.length > 0 ? `include this in the PR body so #767's time-to-feature metric can join on it:
3551
+
3552
+ ${marker}` : "this upgrade carries no template PRs, so no provenance marker is needed.")
3553
+ );
3554
+ throw err;
3555
+ }
3514
3556
  log.step(4, 4, "Opening pull request");
3515
3557
  const remoteUrl = await git.getRemoteUrl(options.cwd, options.remote);
3516
3558
  const { owner, repo } = parseGitHubRepo(remoteUrl);
@@ -3532,7 +3574,8 @@ async function buildCommitAndOpenPr(options, deps, plan, migrations, fromVersion
3532
3574
  breaking,
3533
3575
  cleanedCoreVersion ? coreVersionCleanup : null,
3534
3576
  carriedPrs,
3535
- newSeams
3577
+ newSeams,
3578
+ installFailures
3536
3579
  )
3537
3580
  });
3538
3581
  if (plan.conflicts.length > 0) {
@@ -3588,7 +3631,7 @@ function carriedPrNumbers(subjects) {
3588
3631
  }
3589
3632
  return [...new Set(numbers)].sort((a, b) => a - b);
3590
3633
  }
3591
- function buildPrBody(from, to, plan, migrations, base = GLOBAL_DISPATCH_REF, lockfiles = [], breaking = [], coreVersionCleanup = null, carriedPrs = [], newSeams = []) {
3634
+ function buildPrBody(from, to, plan, migrations, base = GLOBAL_DISPATCH_REF, lockfiles = [], breaking = [], coreVersionCleanup = null, carriedPrs = [], newSeams = [], installFailures = []) {
3592
3635
  const lines = [];
3593
3636
  if (breaking.length > 0) {
3594
3637
  lines.push(
@@ -3697,6 +3740,16 @@ function buildPrBody(from, to, plan, migrations, base = GLOBAL_DISPATCH_REF, loc
3697
3740
  ...lockfiles.map((o) => `- \`${o.trigger.lockfile}\` (\`${o.trigger.command.join(" ")}\`)`)
3698
3741
  );
3699
3742
  }
3743
+ if (installFailures.length > 0) {
3744
+ lines.push(
3745
+ "",
3746
+ "## \u26A0 Dependency install failed",
3747
+ "",
3748
+ "`pnpm install` (and `uv sync`, where this instance has Python) could not run on the machine that opened this PR, so it may not have been possible to verify this tree before pushing it (#1040):",
3749
+ "",
3750
+ ...installFailures.map((f) => `- ${f}`)
3751
+ );
3752
+ }
3700
3753
  if (plan.conflicts.length > 0) {
3701
3754
  lines.push(
3702
3755
  "",
@@ -3903,8 +3956,8 @@ function printBreakingChanges(breaking, applying) {
3903
3956
  }
3904
3957
  function versionOfCheckout(dir, explicit) {
3905
3958
  if (explicit) return explicit;
3906
- const file = join13(dir, CORE_VERSION_FILE);
3907
- if (existsSync12(file)) return readCoreVersionFile(file);
3959
+ const file = join14(dir, CORE_VERSION_FILE);
3960
+ if (existsSync13(file)) return readCoreVersionFile(file);
3908
3961
  throw new Error(
3909
3962
  `Cannot determine the core version of ${dir}: it has no ${CORE_VERSION_FILE}, and a checkout supplied explicitly is not resolved from a tag. Pass --to to state which version this tree is.`
3910
3963
  );
@@ -3912,8 +3965,8 @@ function versionOfCheckout(dir, explicit) {
3912
3965
  function latestCoreVersion(repo) {
3913
3966
  const fromTags = latestCoreVersionFromTags(repo);
3914
3967
  if (fromTags) return fromTags;
3915
- const file = join13(repo, CORE_VERSION_FILE);
3916
- if (existsSync12(file)) return readCoreVersionFile(file);
3968
+ const file = join14(repo, CORE_VERSION_FILE);
3969
+ if (existsSync13(file)) return readCoreVersionFile(file);
3917
3970
  throw new Error(
3918
3971
  `Cannot determine the template's core version: ${repo} has no core-v* tags and no ${CORE_VERSION_FILE}. Fetch tags (\`git fetch --tags\`) or pass --to explicitly.`
3919
3972
  );
@@ -3931,7 +3984,7 @@ coreCommand.addCommand(coreUpgradeCommand);
3931
3984
  import { Command as Command8 } from "commander";
3932
3985
 
3933
3986
  // src/commands/data-apply.ts
3934
- import { existsSync as existsSync14, readFileSync as readFileSync10 } from "fs";
3987
+ import { existsSync as existsSync15, readFileSync as readFileSync10 } from "fs";
3935
3988
  import { resolve as resolve4 } from "path";
3936
3989
  import chalk5 from "chalk";
3937
3990
  import { Command as Command5 } from "commander";
@@ -4382,7 +4435,7 @@ function isTemplatePlaceholderConfig(raw) {
4382
4435
 
4383
4436
  // src/lib/session.ts
4384
4437
  import {
4385
- existsSync as existsSync13,
4438
+ existsSync as existsSync14,
4386
4439
  mkdirSync as mkdirSync4,
4387
4440
  readdirSync as readdirSync4,
4388
4441
  readFileSync as readFileSync9,
@@ -4391,7 +4444,7 @@ import {
4391
4444
  writeFileSync as writeFileSync5
4392
4445
  } from "fs";
4393
4446
  import { homedir } from "os";
4394
- import { join as join14 } from "path";
4447
+ import { join as join15 } from "path";
4395
4448
  var LEGACY_STEP_ALIASES = {
4396
4449
  github_config: ["github_branches", "github_instance_files", "github_settings"]
4397
4450
  };
@@ -4400,14 +4453,14 @@ function hasCompleted(session, step) {
4400
4453
  return session.completedSteps.some((done) => LEGACY_STEP_ALIASES[done]?.includes(step) ?? false);
4401
4454
  }
4402
4455
  function sessionsDir() {
4403
- return process.env["BIFFO_SESSIONS_DIR"] ?? join14(homedir(), ".biffo", "sessions");
4456
+ return process.env["BIFFO_SESSIONS_DIR"] ?? join15(homedir(), ".biffo", "sessions");
4404
4457
  }
4405
4458
  function sessionPath(projectName) {
4406
- return join14(sessionsDir(), `${projectName}.json`);
4459
+ return join15(sessionsDir(), `${projectName}.json`);
4407
4460
  }
4408
4461
  function loadSession(projectName) {
4409
4462
  const path = sessionPath(projectName);
4410
- if (!existsSync13(path)) return null;
4463
+ if (!existsSync14(path)) return null;
4411
4464
  try {
4412
4465
  return JSON.parse(readFileSync9(path, "utf8"));
4413
4466
  } catch {
@@ -4416,23 +4469,23 @@ function loadSession(projectName) {
4416
4469
  }
4417
4470
  function findLatestSession() {
4418
4471
  const dir = sessionsDir();
4419
- if (!existsSync13(dir)) return null;
4472
+ if (!existsSync14(dir)) return null;
4420
4473
  const files = readdirSync4(dir).filter((f) => f.endsWith(".json"));
4421
4474
  if (files.length === 0) return null;
4422
4475
  const sorted = files.map((f) => {
4423
- const fullPath = join14(dir, f);
4424
- const mtime = existsSync13(fullPath) ? statSync2(fullPath).mtimeMs : -1;
4476
+ const fullPath = join15(dir, f);
4477
+ const mtime = existsSync14(fullPath) ? statSync2(fullPath).mtimeMs : -1;
4425
4478
  return { f, mtime };
4426
4479
  }).sort((a, b) => b.mtime - a.mtime);
4427
4480
  try {
4428
- return JSON.parse(readFileSync9(join14(dir, sorted[0].f), "utf8"));
4481
+ return JSON.parse(readFileSync9(join15(dir, sorted[0].f), "utf8"));
4429
4482
  } catch {
4430
4483
  return null;
4431
4484
  }
4432
4485
  }
4433
4486
  function saveSession(session) {
4434
4487
  const dir = sessionsDir();
4435
- if (!existsSync13(dir)) mkdirSync4(dir, { recursive: true });
4488
+ if (!existsSync14(dir)) mkdirSync4(dir, { recursive: true });
4436
4489
  const name = session.config.project?.name ?? "unknown";
4437
4490
  const prior = loadSession(name);
4438
4491
  if (prior) {
@@ -4454,19 +4507,19 @@ function markStepComplete(session, step) {
4454
4507
  }
4455
4508
  function deleteSession(projectName) {
4456
4509
  const path = sessionPath(projectName);
4457
- if (existsSync13(path)) rmSync6(path);
4510
+ if (existsSync14(path)) rmSync6(path);
4458
4511
  }
4459
4512
  function projectsDir() {
4460
- return process.env["BIFFO_PROJECTS_DIR"] ?? join14(homedir(), ".biffo", "projects");
4513
+ return process.env["BIFFO_PROJECTS_DIR"] ?? join15(homedir(), ".biffo", "projects");
4461
4514
  }
4462
4515
  function saveProjectConfig(config) {
4463
4516
  const dir = projectsDir();
4464
- if (!existsSync13(dir)) mkdirSync4(dir, { recursive: true });
4465
- writeFileSync5(join14(dir, `${config.project.name}.json`), JSON.stringify(config, null, 2));
4517
+ if (!existsSync14(dir)) mkdirSync4(dir, { recursive: true });
4518
+ writeFileSync5(join15(dir, `${config.project.name}.json`), JSON.stringify(config, null, 2));
4466
4519
  }
4467
4520
  function loadProjectConfig(name) {
4468
- const path = join14(projectsDir(), `${name}.json`);
4469
- if (!existsSync13(path)) return null;
4521
+ const path = join15(projectsDir(), `${name}.json`);
4522
+ if (!existsSync14(path)) return null;
4470
4523
  try {
4471
4524
  const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync9(path, "utf8")));
4472
4525
  return result.success ? result.data : null;
@@ -4475,15 +4528,15 @@ function loadProjectConfig(name) {
4475
4528
  }
4476
4529
  }
4477
4530
  function deleteProjectConfig(name) {
4478
- const path = join14(projectsDir(), `${name}.json`);
4479
- if (existsSync13(path)) rmSync6(path);
4531
+ const path = join15(projectsDir(), `${name}.json`);
4532
+ if (existsSync14(path)) rmSync6(path);
4480
4533
  }
4481
4534
  function listProjectConfigs() {
4482
4535
  const dir = projectsDir();
4483
- if (!existsSync13(dir)) return [];
4536
+ if (!existsSync14(dir)) return [];
4484
4537
  return readdirSync4(dir).filter((f) => f.endsWith(".json")).flatMap((f) => {
4485
4538
  try {
4486
- const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync9(join14(dir, f), "utf8")));
4539
+ const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync9(join15(dir, f), "utf8")));
4487
4540
  return result.success ? [result.data] : [];
4488
4541
  } catch {
4489
4542
  return [];
@@ -4572,7 +4625,7 @@ async function resolveConfig(options) {
4572
4625
  return cfg;
4573
4626
  }
4574
4627
  const localConfigPath = resolve4(process.cwd(), "biffo.config.json");
4575
- if (existsSync14(localConfigPath)) {
4628
+ if (existsSync15(localConfigPath)) {
4576
4629
  const raw = JSON.parse(readFileSync10(localConfigPath, "utf8"));
4577
4630
  const result = BiffoConfigSchema.safeParse(raw);
4578
4631
  if (result.success) return result.data;
@@ -4619,8 +4672,8 @@ async function resolveConfig(options) {
4619
4672
 
4620
4673
  // src/commands/data-import.ts
4621
4674
  import { execSync as execSync3 } from "child_process";
4622
- import { cpSync, existsSync as existsSync15, mkdirSync as mkdirSync5, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
4623
- import { join as join15, resolve as resolve5 } from "path";
4675
+ import { cpSync, existsSync as existsSync16, mkdirSync as mkdirSync5, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
4676
+ import { join as join16, resolve as resolve5 } from "path";
4624
4677
  import chalk6 from "chalk";
4625
4678
  import { Command as Command6 } from "commander";
4626
4679
  import inquirer2 from "inquirer";
@@ -4660,23 +4713,23 @@ async function runDataImport(name, options, deps) {
4660
4713
  `Invalid import name '${name}'. Use lowercase letters, numbers, and hyphens, starting with a letter.`
4661
4714
  );
4662
4715
  }
4663
- const servicesDir = join15(options.cwd, "services");
4664
- if (!existsSync15(servicesDir)) {
4716
+ const servicesDir = join16(options.cwd, "services");
4717
+ if (!existsSync16(servicesDir)) {
4665
4718
  throw new Error(
4666
4719
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
4667
4720
  );
4668
4721
  }
4669
- const targetDir = join15(options.cwd, "db", "imports", name);
4670
- if (existsSync15(targetDir)) {
4722
+ const targetDir = join16(options.cwd, "db", "imports", name);
4723
+ if (existsSync16(targetDir)) {
4671
4724
  throw new Error(
4672
4725
  `DDL import '${name}' is already present at db/imports/${name}/. Remove it first to re-import.`
4673
4726
  );
4674
4727
  }
4675
- const isLocalDir = existsSync15(options.source) && statSync3(options.source).isDirectory();
4728
+ const isLocalDir = existsSync16(options.source) && statSync3(options.source).isDirectory();
4676
4729
  let sourceDir;
4677
4730
  let cleanupClone = null;
4678
4731
  if (isLocalDir) {
4679
- sourceDir = options.path ? join15(options.source, options.path) : options.source;
4732
+ sourceDir = options.path ? join16(options.source, options.path) : options.source;
4680
4733
  } else {
4681
4734
  const token = options.token ?? await resolveDdlImportToken();
4682
4735
  log.info(`Cloning ${options.source}...`);
@@ -4684,10 +4737,10 @@ async function runDataImport(name, options, deps) {
4684
4737
  cleanupClone = () => {
4685
4738
  deps.git.cleanup(tmpDir);
4686
4739
  };
4687
- sourceDir = options.path ? join15(tmpDir, options.path) : tmpDir;
4740
+ sourceDir = options.path ? join16(tmpDir, options.path) : tmpDir;
4688
4741
  }
4689
4742
  try {
4690
- if (!existsSync15(sourceDir)) {
4743
+ if (!existsSync16(sourceDir)) {
4691
4744
  throw new Error(`Source directory does not exist: ${sourceDir}`);
4692
4745
  }
4693
4746
  const sqlFiles = readdirSync5(sourceDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".sql")).map((entry) => entry.name).sort();
@@ -4712,7 +4765,7 @@ async function runDataImport(name, options, deps) {
4712
4765
  }
4713
4766
  mkdirSync5(targetDir, { recursive: true });
4714
4767
  for (const file of sqlFiles) {
4715
- cpSync(join15(sourceDir, file), join15(targetDir, file));
4768
+ cpSync(join16(sourceDir, file), join16(targetDir, file));
4716
4769
  }
4717
4770
  log.success(`Imported ${String(sqlFiles.length)} .sql file(s) to db/imports/${name}/`);
4718
4771
  const commitMessage = `feat(data): import ${name} (${String(sqlFiles.length)} SQL file(s))`;
@@ -4764,8 +4817,8 @@ function printDryRun(name, sqlFiles) {
4764
4817
  }
4765
4818
 
4766
4819
  // src/commands/data-list.ts
4767
- import { existsSync as existsSync16, readdirSync as readdirSync6 } from "fs";
4768
- import { join as join16, resolve as resolve6 } from "path";
4820
+ import { existsSync as existsSync17, readdirSync as readdirSync6 } from "fs";
4821
+ import { join as join17, resolve as resolve6 } from "path";
4769
4822
  import chalk7 from "chalk";
4770
4823
  import { Command as Command7 } from "commander";
4771
4824
  var dataListCommand = new Command7("list").description("List DDL imports vendored in this project checkout").option("--cwd <path>", "Project root to scan (defaults to the current directory)").action(async (options) => {
@@ -4778,15 +4831,15 @@ var dataListCommand = new Command7("list").description("List DDL imports vendore
4778
4831
  }
4779
4832
  });
4780
4833
  async function runDataList(options) {
4781
- const importsDir = join16(options.cwd, "db", "imports");
4782
- if (!existsSync16(importsDir)) {
4834
+ const importsDir = join17(options.cwd, "db", "imports");
4835
+ if (!existsSync17(importsDir)) {
4783
4836
  console.log(chalk7.dim("\n No DDL imports in this checkout.\n"));
4784
4837
  return;
4785
4838
  }
4786
4839
  const candidates = readdirSync6(importsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
4787
4840
  const imports = [];
4788
4841
  for (const name of candidates) {
4789
- const fileCount = readdirSync6(join16(importsDir, name)).filter((f) => f.endsWith(".sql")).length;
4842
+ const fileCount = readdirSync6(join17(importsDir, name)).filter((f) => f.endsWith(".sql")).length;
4790
4843
  if (fileCount > 0) imports.push({ name, fileCount });
4791
4844
  }
4792
4845
  if (imports.length === 0) {
@@ -4816,7 +4869,7 @@ dataCommand.addCommand(dataListCommand);
4816
4869
 
4817
4870
  // src/commands/deploy.ts
4818
4871
  import { execSync as execSync4 } from "child_process";
4819
- import { existsSync as existsSync17, readFileSync as readFileSync11 } from "fs";
4872
+ import { existsSync as existsSync18, readFileSync as readFileSync11 } from "fs";
4820
4873
  import { resolve as resolve7 } from "path";
4821
4874
  import chalk8 from "chalk";
4822
4875
  import { Command as Command9 } from "commander";
@@ -5193,7 +5246,7 @@ async function resolveConfig2(options) {
5193
5246
  return cfg;
5194
5247
  }
5195
5248
  const localConfigPath = resolve7(process.cwd(), "biffo.config.json");
5196
- if (existsSync17(localConfigPath)) {
5249
+ if (existsSync18(localConfigPath)) {
5197
5250
  const raw = JSON.parse(readFileSync11(localConfigPath, "utf8"));
5198
5251
  const result = BiffoConfigSchema.safeParse(raw);
5199
5252
  if (result.success) return result.data;
@@ -5757,8 +5810,8 @@ import { Command as Command12 } from "commander";
5757
5810
  import inquirer5 from "inquirer";
5758
5811
 
5759
5812
  // src/lib/build-freshness.ts
5760
- import { existsSync as existsSync18, readdirSync as readdirSync7, statSync as statSync4 } from "fs";
5761
- import { dirname as dirname5, join as join17, relative as relative2, sep as sep2 } from "path";
5813
+ import { existsSync as existsSync19, readdirSync as readdirSync7, statSync as statSync4 } from "fs";
5814
+ import { dirname as dirname5, join as join18, relative as relative2, sep as sep2 } from "path";
5762
5815
  import { fileURLToPath as fileURLToPath3 } from "url";
5763
5816
  var SKIP_ENV_VAR = "BIFFO_SKIP_BUILD_FRESHNESS_CHECK";
5764
5817
  function checkBuildFreshness(options = {}) {
@@ -5772,7 +5825,7 @@ function checkBuildFreshness(options = {}) {
5772
5825
  if (!packageRoot) {
5773
5826
  return { status: "skipped", reason: `no package.json above ${moduleDir}`, newerSources: [] };
5774
5827
  }
5775
- const distDir = join17(packageRoot, "dist");
5828
+ const distDir = join18(packageRoot, "dist");
5776
5829
  if (!isInside(distDir, moduleDir)) {
5777
5830
  return {
5778
5831
  status: "skipped",
@@ -5780,16 +5833,16 @@ function checkBuildFreshness(options = {}) {
5780
5833
  newerSources: []
5781
5834
  };
5782
5835
  }
5783
- const srcDir = join17(packageRoot, "src");
5784
- if (!existsSync18(srcDir)) {
5836
+ const srcDir = join18(packageRoot, "src");
5837
+ if (!existsSync19(srcDir)) {
5785
5838
  return {
5786
5839
  status: "skipped",
5787
5840
  reason: "no src/ alongside dist/ \u2014 this is a shipped package",
5788
5841
  newerSources: []
5789
5842
  };
5790
5843
  }
5791
- const entry = join17(distDir, "index.js");
5792
- if (!existsSync18(entry)) {
5844
+ const entry = join18(distDir, "index.js");
5845
+ if (!existsSync19(entry)) {
5793
5846
  return { status: "skipped", reason: `${entry} not found`, newerSources: [] };
5794
5847
  }
5795
5848
  const builtAt = statSync4(entry).mtimeMs;
@@ -5833,7 +5886,7 @@ function collectSourceFiles(srcDir) {
5833
5886
  const found = [];
5834
5887
  const walk = (dir) => {
5835
5888
  for (const entry of readdirSync7(dir, { withFileTypes: true })) {
5836
- const full = join17(dir, entry.name);
5889
+ const full = join18(dir, entry.name);
5837
5890
  if (entry.isDirectory()) {
5838
5891
  if (entry.name === "node_modules") continue;
5839
5892
  walk(full);
@@ -5852,7 +5905,7 @@ function collectSourceFiles(srcDir) {
5852
5905
  function findPackageRoot(from) {
5853
5906
  let dir = from;
5854
5907
  for (; ; ) {
5855
- if (existsSync18(join17(dir, "package.json"))) return dir;
5908
+ if (existsSync19(join18(dir, "package.json"))) return dir;
5856
5909
  const parent = dirname5(dir);
5857
5910
  if (parent === dir) return null;
5858
5911
  dir = parent;
@@ -5866,9 +5919,9 @@ function isInside(parent, child) {
5866
5919
 
5867
5920
  // src/lib/credentials.ts
5868
5921
  import { execSync as execSync6 } from "child_process";
5869
- import { existsSync as existsSync19, readFileSync as readFileSync13 } from "fs";
5922
+ import { existsSync as existsSync20, readFileSync as readFileSync13 } from "fs";
5870
5923
  import { homedir as homedir2 } from "os";
5871
- import { join as join18 } from "path";
5924
+ import { join as join19 } from "path";
5872
5925
  import { GetCallerIdentityCommand as GetCallerIdentityCommand2, STSClient as STSClient2 } from "@aws-sdk/client-sts";
5873
5926
  import chalk10 from "chalk";
5874
5927
  import inquirer4 from "inquirer";
@@ -6047,10 +6100,10 @@ async function verifySelectedAwsCredentials(profile, region) {
6047
6100
  return sts.send(new GetCallerIdentityCommand2({}));
6048
6101
  }
6049
6102
  function discoverAwsProfiles() {
6050
- const files = [join18(homedir2(), ".aws", "credentials"), join18(homedir2(), ".aws", "config")];
6103
+ const files = [join19(homedir2(), ".aws", "credentials"), join19(homedir2(), ".aws", "config")];
6051
6104
  const profiles = /* @__PURE__ */ new Set();
6052
6105
  for (const file of files) {
6053
- if (!existsSync19(file)) continue;
6106
+ if (!existsSync20(file)) continue;
6054
6107
  const content = readFileSync13(file, "utf8");
6055
6108
  for (const match of content.matchAll(/^\s*\[([^\]]+)\]\s*$/gm)) {
6056
6109
  const section = match[1]?.trim();
@@ -6141,7 +6194,7 @@ var SiblingConfigSchema = z6.object({
6141
6194
 
6142
6195
  // src/lib/sibling-session.ts
6143
6196
  import {
6144
- existsSync as existsSync20,
6197
+ existsSync as existsSync21,
6145
6198
  mkdirSync as mkdirSync6,
6146
6199
  readdirSync as readdirSync8,
6147
6200
  readFileSync as readFileSync14,
@@ -6150,16 +6203,16 @@ import {
6150
6203
  writeFileSync as writeFileSync6
6151
6204
  } from "fs";
6152
6205
  import { homedir as homedir3 } from "os";
6153
- import { join as join19 } from "path";
6206
+ import { join as join20 } from "path";
6154
6207
  function sessionsDir2() {
6155
- return process.env["BIFFO_SIBLING_SESSIONS_DIR"] ?? join19(homedir3(), ".biffo", "sibling-sessions");
6208
+ return process.env["BIFFO_SIBLING_SESSIONS_DIR"] ?? join20(homedir3(), ".biffo", "sibling-sessions");
6156
6209
  }
6157
6210
  function sessionPath2(projectName) {
6158
- return join19(sessionsDir2(), `${projectName}.json`);
6211
+ return join20(sessionsDir2(), `${projectName}.json`);
6159
6212
  }
6160
6213
  function loadSiblingSession(projectName) {
6161
6214
  const path = sessionPath2(projectName);
6162
- if (!existsSync20(path)) return null;
6215
+ if (!existsSync21(path)) return null;
6163
6216
  try {
6164
6217
  return JSON.parse(readFileSync14(path, "utf8"));
6165
6218
  } catch {
@@ -6168,7 +6221,7 @@ function loadSiblingSession(projectName) {
6168
6221
  }
6169
6222
  function saveSiblingSession(session) {
6170
6223
  const dir = sessionsDir2();
6171
- if (!existsSync20(dir)) mkdirSync6(dir, { recursive: true });
6224
+ if (!existsSync21(dir)) mkdirSync6(dir, { recursive: true });
6172
6225
  const name = session.config.project?.name ?? "unknown";
6173
6226
  const prior = loadSiblingSession(name);
6174
6227
  if (prior) {
@@ -6190,30 +6243,30 @@ function markSiblingStepComplete(session, step) {
6190
6243
  }
6191
6244
  function deleteSiblingSession(projectName) {
6192
6245
  const path = sessionPath2(projectName);
6193
- if (existsSync20(path)) rmSync7(path);
6246
+ if (existsSync21(path)) rmSync7(path);
6194
6247
  }
6195
6248
 
6196
6249
  // src/commands/sibling-create.ts
6197
- import { cpSync as cpSync2, existsSync as existsSync21, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync4, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
6250
+ import { cpSync as cpSync2, existsSync as existsSync22, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync4, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
6198
6251
  import { tmpdir as tmpdir4 } from "os";
6199
- import { dirname as dirname6, join as join21, resolve as resolve9 } from "path";
6252
+ import { dirname as dirname6, join as join22, resolve as resolve9 } from "path";
6200
6253
  import { fileURLToPath as fileURLToPath4 } from "url";
6201
6254
  import chalk11 from "chalk";
6202
6255
  import { Command as Command11 } from "commander";
6203
6256
 
6204
6257
  // src/lib/skeleton-dotfiles.ts
6205
6258
  import { readdirSync as readdirSync9, renameSync } from "fs";
6206
- import { join as join20 } from "path";
6259
+ import { join as join21 } from "path";
6207
6260
  var PACKAGED_GITIGNORE = "_gitignore";
6208
6261
  var REAL_GITIGNORE = ".gitignore";
6209
6262
  function restorePackagedDotfiles(dir) {
6210
6263
  const restored = [];
6211
6264
  for (const entry of readdirSync9(dir, { withFileTypes: true })) {
6212
- const full = join20(dir, entry.name);
6265
+ const full = join21(dir, entry.name);
6213
6266
  if (entry.isDirectory()) {
6214
6267
  restored.push(...restorePackagedDotfiles(full));
6215
6268
  } else if (entry.name === PACKAGED_GITIGNORE) {
6216
- const target = join20(dir, REAL_GITIGNORE);
6269
+ const target = join21(dir, REAL_GITIGNORE);
6217
6270
  renameSync(full, target);
6218
6271
  restored.push(target);
6219
6272
  }
@@ -6258,7 +6311,7 @@ async function runSiblingCreateCommand(name, options) {
6258
6311
  printDryRun2(config, coreConfig, options.templateRoot);
6259
6312
  return;
6260
6313
  }
6261
- if (!existsSync21(options.templateRoot)) {
6314
+ if (!existsSync22(options.templateRoot)) {
6262
6315
  throw new Error(`Sibling template not found at ${options.templateRoot}`);
6263
6316
  }
6264
6317
  let session = null;
@@ -6548,7 +6601,7 @@ async function resolveCoreIdentity(coreAws, coreConfig, environments) {
6548
6601
  return coreIdentity;
6549
6602
  }
6550
6603
  async function pushSkeleton(git, skeletonRoot, cloneUrl, config, coreConfig, githubToken) {
6551
- const workDir = mkdtempSync4(join21(tmpdir4(), `biffo-sibling-${config.project.name}-`));
6604
+ const workDir = mkdtempSync4(join22(tmpdir4(), `biffo-sibling-${config.project.name}-`));
6552
6605
  try {
6553
6606
  writeSiblingTemplate(skeletonRoot, workDir, config, {
6554
6607
  coreProjectName: coreConfig.project.name,
@@ -6573,13 +6626,13 @@ async function pushSkeleton(git, skeletonRoot, cloneUrl, config, coreConfig, git
6573
6626
  }
6574
6627
  }
6575
6628
  function writeSiblingTemplate(templateRoot, targetDir, config, context) {
6576
- if (!existsSync21(templateRoot)) {
6629
+ if (!existsSync22(templateRoot)) {
6577
6630
  throw new Error(`Sibling template not found at ${templateRoot}`);
6578
6631
  }
6579
6632
  cpSync2(templateRoot, targetDir, { recursive: true });
6580
6633
  restorePackagedDotfiles(targetDir);
6581
6634
  writeFileSync7(
6582
- join21(targetDir, "biffo.sibling.json"),
6635
+ join22(targetDir, "biffo.sibling.json"),
6583
6636
  JSON.stringify(
6584
6637
  {
6585
6638
  name: config.project.name,
@@ -6598,11 +6651,11 @@ function writeSiblingTemplate(templateRoot, targetDir, config, context) {
6598
6651
  ) + "\n"
6599
6652
  );
6600
6653
  writeFileSync7(
6601
- join21(targetDir, ".biffo-shared-version"),
6654
+ join22(targetDir, ".biffo-shared-version"),
6602
6655
  `core-v${context.templateVersion.replace(/^core-v/, "")}
6603
6656
  `
6604
6657
  );
6605
- const envPath = join21(targetDir, "apps", "frontend", ".env.example");
6658
+ const envPath = join22(targetDir, "apps", "frontend", ".env.example");
6606
6659
  try {
6607
6660
  const path = basePathFor(context.pathPrefix);
6608
6661
  const content = readFileSync15(envPath, "utf8").replace(/^NEXT_PUBLIC_SIBLING_NAME=.*$/m, `NEXT_PUBLIC_SIBLING_NAME=${config.project.name}`).replace(/^NEXT_PUBLIC_SIBLING_PATH_PREFIX=.*$/m, `NEXT_PUBLIC_SIBLING_PATH_PREFIX=${path}`).replace(/^NEXT_PUBLIC_BASE_PATH=.*$/m, `NEXT_PUBLIC_BASE_PATH=${path}`);
@@ -6671,7 +6724,7 @@ function assertGitIdentity(identity) {
6671
6724
  );
6672
6725
  }
6673
6726
  function assertCoreSupportsSiblingRouting(cloneDir, coreRepo, pathPrefix = "x") {
6674
- const cdnVarsPath = join21(cloneDir, "modules", "cloud", "aws", "cdn", "variables.tf");
6727
+ const cdnVarsPath = join22(cloneDir, "modules", "cloud", "aws", "cdn", "variables.tf");
6675
6728
  let declaresSiblingOrigins = false;
6676
6729
  try {
6677
6730
  declaresSiblingOrigins = /variable\s+"sibling_origins"/.test(readFileSync15(cdnVarsPath, "utf8"));
@@ -6684,7 +6737,7 @@ function assertCoreSupportsSiblingRouting(cloneDir, coreRepo, pathPrefix = "x")
6684
6737
  );
6685
6738
  }
6686
6739
  if (!isRootPathPrefix(pathPrefix)) return;
6687
- const cdnMainPath = join21(cloneDir, "modules", "cloud", "aws", "cdn", "main.tf");
6740
+ const cdnMainPath = join22(cloneDir, "modules", "cloud", "aws", "cdn", "main.tf");
6688
6741
  let supportsRoot = false;
6689
6742
  try {
6690
6743
  supportsRoot = /root_sibling_registered/.test(readFileSync15(cdnMainPath, "utf8"));
@@ -6717,8 +6770,8 @@ async function registerWithCore(git, github, config, coreConfig, pathPrefix, git
6717
6770
  for (const env of config.environments) {
6718
6771
  const bucketName = siteBucketName(config.project.name, env, siblingAccountId);
6719
6772
  const domain = bucketRegionalDomain(bucketName, coreAwsRegion);
6720
- const relativePath = join21("infra", "environments", env, "siblings.auto.tfvars.json");
6721
- const filePath = join21(cloneDir, relativePath);
6773
+ const relativePath = join22("infra", "environments", env, "siblings.auto.tfvars.json");
6774
+ const filePath = join22(cloneDir, relativePath);
6722
6775
  const existing = readExistingSiblingOrigins(filePath);
6723
6776
  const siblings = upsertSiblingOrigin(existing.sibling_origins ?? [], {
6724
6777
  name,
@@ -6798,8 +6851,8 @@ function defaultSiblingTemplateRoot() {
6798
6851
  const start = dirname6(fileURLToPath4(import.meta.url));
6799
6852
  let dir = start;
6800
6853
  for (; ; ) {
6801
- const candidate = join21(dir, "_skeletons", "sibling-template");
6802
- if (existsSync21(candidate)) return candidate;
6854
+ const candidate = join22(dir, "_skeletons", "sibling-template");
6855
+ if (existsSync22(candidate)) return candidate;
6803
6856
  const parent = dirname6(dir);
6804
6857
  if (parent === dir) break;
6805
6858
  dir = parent;
@@ -7268,8 +7321,8 @@ async function promptForConfig(awsAccountId, awsRegion, awsProfile) {
7268
7321
  import { Command as Command20 } from "commander";
7269
7322
 
7270
7323
  // src/commands/plugin-create.ts
7271
- import { existsSync as existsSync24, readFileSync as readFileSync18, writeFileSync as writeFileSync9 } from "fs";
7272
- import { dirname as dirname8, join as join24, resolve as resolve11 } from "path";
7324
+ import { existsSync as existsSync25, readFileSync as readFileSync18, writeFileSync as writeFileSync9 } from "fs";
7325
+ import { dirname as dirname8, join as join25, resolve as resolve11 } from "path";
7273
7326
  import { fileURLToPath as fileURLToPath5 } from "url";
7274
7327
  import chalk13 from "chalk";
7275
7328
  import { Command as Command13 } from "commander";
@@ -7333,21 +7386,21 @@ function workflowCheckContexts(workflow) {
7333
7386
  }
7334
7387
 
7335
7388
  // src/lib/plugin-locations.ts
7336
- import { existsSync as existsSync22, readdirSync as readdirSync10 } from "fs";
7337
- import { join as join22 } from "path";
7389
+ import { existsSync as existsSync23, readdirSync as readdirSync10 } from "fs";
7390
+ import { join as join23 } from "path";
7338
7391
  var FIRST_PARTY_PLUGINS_DIR = "_plugins";
7339
7392
  var PLUGIN_MANIFEST_FILE = "biffo.plugin.json";
7340
7393
  function pluginDir(name, channel) {
7341
7394
  return channel === "first-party" ? `services/${FIRST_PARTY_PLUGINS_DIR}/${name}` : `services/${name}`;
7342
7395
  }
7343
7396
  function scanDir(absDir, relDir, channel) {
7344
- if (!existsSync22(absDir)) return [];
7397
+ if (!existsSync23(absDir)) return [];
7345
7398
  const found = [];
7346
7399
  for (const entry of readdirSync10(absDir, { withFileTypes: true })) {
7347
7400
  if (!entry.isDirectory()) continue;
7348
7401
  if (channel === "third-party" && entry.name === FIRST_PARTY_PLUGINS_DIR) continue;
7349
- const manifestPath = join22(absDir, entry.name, PLUGIN_MANIFEST_FILE);
7350
- if (!existsSync22(manifestPath)) continue;
7402
+ const manifestPath = join23(absDir, entry.name, PLUGIN_MANIFEST_FILE);
7403
+ if (!existsSync23(manifestPath)) continue;
7351
7404
  found.push({
7352
7405
  dirName: entry.name,
7353
7406
  relDir: `${relDir}/${entry.name}`,
@@ -7358,11 +7411,11 @@ function scanDir(absDir, relDir, channel) {
7358
7411
  return found;
7359
7412
  }
7360
7413
  function findInstalledPlugins(cwd) {
7361
- const servicesDir = join22(cwd, "services");
7414
+ const servicesDir = join23(cwd, "services");
7362
7415
  return [
7363
7416
  ...scanDir(servicesDir, "services", "third-party"),
7364
7417
  ...scanDir(
7365
- join22(servicesDir, FIRST_PARTY_PLUGINS_DIR),
7418
+ join23(servicesDir, FIRST_PARTY_PLUGINS_DIR),
7366
7419
  `services/${FIRST_PARTY_PLUGINS_DIR}`,
7367
7420
  "first-party"
7368
7421
  )
@@ -7565,13 +7618,13 @@ function validateManifest(raw) {
7565
7618
  // src/lib/plugin-scaffold.ts
7566
7619
  import {
7567
7620
  copyFileSync,
7568
- existsSync as existsSync23,
7621
+ existsSync as existsSync24,
7569
7622
  mkdirSync as mkdirSync8,
7570
7623
  readFileSync as readFileSync17,
7571
7624
  readdirSync as readdirSync11,
7572
7625
  writeFileSync as writeFileSync8
7573
7626
  } from "fs";
7574
- import { dirname as dirname7, join as join23 } from "path";
7627
+ import { dirname as dirname7, join as join24 } from "path";
7575
7628
  var STANDALONE_ONLY_ENTRIES = {
7576
7629
  ".github": "standalone-repo CI/release workflows \u2014 the host monorepo already runs lint/type/test/security over services/",
7577
7630
  "registry-schema.json": "the plugin-registry publishing schema, used when submitting a *published* plugin to the registry repo, not by an in-tree plugin"
@@ -7625,10 +7678,10 @@ function applySubstitutions(text, names) {
7625
7678
  var BINARY_EXTENSIONS = /\.(png|jpe?g|gif|ico|woff2?|ttf|zip|gz)$/i;
7626
7679
  function scaffoldPlugin(skeletonRoot, destDir, names, options = {}) {
7627
7680
  const layout = options.layout ?? "in-tree";
7628
- if (!existsSync23(skeletonRoot)) {
7681
+ if (!existsSync24(skeletonRoot)) {
7629
7682
  throw new Error(`Plugin skeleton not found at ${skeletonRoot}`);
7630
7683
  }
7631
- if (!existsSync23(join23(skeletonRoot, "terraform"))) {
7684
+ if (!existsSync24(join24(skeletonRoot, "terraform"))) {
7632
7685
  throw new Error(
7633
7686
  `Plugin skeleton at ${skeletonRoot} has no terraform/ directory. Refusing to scaffold a plugin that cannot receive events (issue #194) \u2014 the skeleton is broken.`
7634
7687
  );
@@ -7636,7 +7689,7 @@ function scaffoldPlugin(skeletonRoot, destDir, names, options = {}) {
7636
7689
  const skipped = [];
7637
7690
  const files = [];
7638
7691
  const walk = (relDir) => {
7639
- const absDir = join23(skeletonRoot, relDir);
7692
+ const absDir = join24(skeletonRoot, relDir);
7640
7693
  for (const entry of readdirSync11(absDir, { withFileTypes: true }).sort(
7641
7694
  (a, b) => a.name.localeCompare(b.name)
7642
7695
  )) {
@@ -7651,14 +7704,14 @@ function scaffoldPlugin(skeletonRoot, destDir, names, options = {}) {
7651
7704
  continue;
7652
7705
  }
7653
7706
  const destRel = applySubstitutions(relPath, names);
7654
- const destPath = join23(destDir, destRel);
7707
+ const destPath = join24(destDir, destRel);
7655
7708
  mkdirSync8(dirname7(destPath), { recursive: true });
7656
7709
  if (BINARY_EXTENSIONS.test(entry.name)) {
7657
- copyFileSync(join23(skeletonRoot, relPath), destPath);
7710
+ copyFileSync(join24(skeletonRoot, relPath), destPath);
7658
7711
  } else {
7659
7712
  writeFileSync8(
7660
7713
  destPath,
7661
- applySubstitutions(readFileSync17(join23(skeletonRoot, relPath), "utf8"), names)
7714
+ applySubstitutions(readFileSync17(join24(skeletonRoot, relPath), "utf8"), names)
7662
7715
  );
7663
7716
  }
7664
7717
  files.push(destRel);
@@ -7675,8 +7728,8 @@ function scaffoldPlugin(skeletonRoot, destDir, names, options = {}) {
7675
7728
  function findSkeletonRoot(startDir, skeleton) {
7676
7729
  let dir = startDir;
7677
7730
  for (; ; ) {
7678
- const candidate = join23(dir, "_skeletons", skeleton);
7679
- if (existsSync23(candidate)) return candidate;
7731
+ const candidate = join24(dir, "_skeletons", skeleton);
7732
+ if (existsSync24(candidate)) return candidate;
7680
7733
  const parent = dirname7(dir);
7681
7734
  if (parent === dir) return null;
7682
7735
  dir = parent;
@@ -7744,7 +7797,7 @@ async function runPluginCreate(name, options, deps) {
7744
7797
  reportBranchProtectionSummary();
7745
7798
  return;
7746
7799
  }
7747
- const isInstance = existsSync24(join24(options.cwd, INSTANCE_CORE_FILE));
7800
+ const isInstance = existsSync25(join25(options.cwd, INSTANCE_CORE_FILE));
7748
7801
  if (options.firstParty && isInstance) {
7749
7802
  throw new Error(
7750
7803
  `--first-party scaffolds into services/_plugins/, which is template-owned: \`biffo core upgrade\` three-way-merges it against the template on every upgrade, and the template has no '${names.slug}'. This checkout is a Biffo instance (${INSTANCE_CORE_FILE} is present), so your plugin belongs in the user-owned ${pluginDir(names.slug, "third-party")}/ \u2014 re-run without --first-party.`
@@ -7752,19 +7805,19 @@ async function runPluginCreate(name, options, deps) {
7752
7805
  }
7753
7806
  const channel = options.firstParty ? "first-party" : "third-party";
7754
7807
  const relDir = pluginDir(names.slug, channel);
7755
- const destDir = join24(options.cwd, relDir);
7756
- const servicesDir = join24(options.cwd, "services");
7757
- if (!existsSync24(servicesDir)) {
7808
+ const destDir = join25(options.cwd, relDir);
7809
+ const servicesDir = join25(options.cwd, "services");
7810
+ if (!existsSync25(servicesDir)) {
7758
7811
  throw new Error(
7759
7812
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
7760
7813
  );
7761
7814
  }
7762
- if (existsSync24(destDir)) {
7815
+ if (existsSync25(destDir)) {
7763
7816
  throw new Error(`${relDir}/ already exists. Choose a different name, or remove it first.`);
7764
7817
  }
7765
7818
  const here = dirname8(fileURLToPath5(import.meta.url));
7766
- const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join24(options.cwd, "_skeletons", "plugin-template");
7767
- if (!existsSync24(skeletonRoot)) {
7819
+ const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join25(options.cwd, "_skeletons", "plugin-template");
7820
+ if (!existsSync25(skeletonRoot)) {
7768
7821
  throw new Error(
7769
7822
  `Could not find the plugin skeleton (_skeletons/plugin-template/). Pass --skeleton <path> to point at it explicitly.`
7770
7823
  );
@@ -7779,7 +7832,7 @@ async function runPluginCreate(name, options, deps) {
7779
7832
  for (const { entry, reason } of skipped) {
7780
7833
  log.info(`Skipped ${entry} \u2014 ${reason}`);
7781
7834
  }
7782
- const manifestPath = join24(destDir, "biffo.plugin.json");
7835
+ const manifestPath = join25(destDir, "biffo.plugin.json");
7783
7836
  const manifest = validateManifest(JSON.parse(readFileSync18(manifestPath, "utf8")));
7784
7837
  if (manifest.name !== names.slug) {
7785
7838
  throw new Error(
@@ -7803,8 +7856,8 @@ async function runPluginCreate(name, options, deps) {
7803
7856
  printNextSteps(names, relDir, channel);
7804
7857
  }
7805
7858
  async function runStandaloneCreate(names, options, deps) {
7806
- const destDir = join24(options.cwd, names.dist);
7807
- if (existsSync24(destDir)) {
7859
+ const destDir = join25(options.cwd, names.dist);
7860
+ if (existsSync25(destDir)) {
7808
7861
  throw new Error(`${names.dist}/ already exists. Choose a different name, or remove it first.`);
7809
7862
  }
7810
7863
  const skeletonRoot = resolveSkeletonRoot(options);
@@ -7822,7 +7875,7 @@ async function runStandaloneCreate(names, options, deps) {
7822
7875
  restorePackagedDotfiles(destDir);
7823
7876
  log.success(`Scaffolded ${String(files.length)} file(s) into ${names.dist}/`);
7824
7877
  const manifest = validateManifest(
7825
- JSON.parse(readFileSync18(join24(destDir, "biffo.plugin.json"), "utf8"))
7878
+ JSON.parse(readFileSync18(join25(destDir, "biffo.plugin.json"), "utf8"))
7826
7879
  );
7827
7880
  if (manifest.name !== names.slug) {
7828
7881
  throw new Error(
@@ -7861,8 +7914,8 @@ async function createAndPushStandaloneRepo(org, names, destDir, options, deps) {
7861
7914
  await deps.git.push(destDir, "dev", { token });
7862
7915
  log.success(`Pushed dev to ${org}/${names.dist}`);
7863
7916
  await github.setDefaultBranch(org, names.dist, "dev");
7864
- const ciPath = join24(destDir, ".github", "workflows", "ci.yml");
7865
- const contexts = existsSync24(ciPath) ? workflowCheckContexts(readFileSync18(ciPath, "utf8")) : [];
7917
+ const ciPath = join25(destDir, ".github", "workflows", "ci.yml");
7918
+ const contexts = existsSync25(ciPath) ? workflowCheckContexts(readFileSync18(ciPath, "utf8")) : [];
7866
7919
  if (contexts.length === 0) {
7867
7920
  log.warn(
7868
7921
  `Could not determine required status checks from ${ciPath} \u2014 skipping branch protection. Configure it manually on dev once you know the CI job names.`
@@ -7891,7 +7944,7 @@ async function registerInRegistrySources(names, cloneUrl, token, deps) {
7891
7944
  let dir;
7892
7945
  try {
7893
7946
  dir = await deps.git.cloneForEditing(REGISTRY_REPO, "biffo-registry", token);
7894
- const path = join24(dir, "sources.json");
7947
+ const path = join25(dir, "sources.json");
7895
7948
  const file = JSON.parse(readFileSync18(path, "utf8"));
7896
7949
  const next = addSource(file, {
7897
7950
  name: names.slug,
@@ -8000,8 +8053,8 @@ function printStandaloneNextSteps(names, minor) {
8000
8053
  }
8001
8054
  function resolveSkeletonRoot(options) {
8002
8055
  const here = dirname8(fileURLToPath5(import.meta.url));
8003
- const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join24(options.cwd, "_skeletons", "plugin-template");
8004
- if (!existsSync24(skeletonRoot)) {
8056
+ const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join25(options.cwd, "_skeletons", "plugin-template");
8057
+ if (!existsSync25(skeletonRoot)) {
8005
8058
  throw new Error(
8006
8059
  `Could not find the plugin skeleton (_skeletons/plugin-template/). Pass --skeleton <path> to point at it explicitly.`
8007
8060
  );
@@ -8176,14 +8229,14 @@ function printEntry(entry) {
8176
8229
  }
8177
8230
 
8178
8231
  // src/commands/plugin-install.ts
8179
- import { cpSync as cpSync3, existsSync as existsSync26, mkdirSync as mkdirSync9, readFileSync as readFileSync20, statSync as statSync6 } from "fs";
8180
- import { basename, join as join27, relative as relative3, resolve as resolve12 } from "path";
8232
+ import { cpSync as cpSync3, existsSync as existsSync27, mkdirSync as mkdirSync9, readFileSync as readFileSync20, statSync as statSync6 } from "fs";
8233
+ import { basename, join as join28, relative as relative3, resolve as resolve12 } from "path";
8181
8234
  import chalk15 from "chalk";
8182
8235
  import { Command as Command15 } from "commander";
8183
8236
 
8184
8237
  // src/adapters/plugin-migrations/index.ts
8185
8238
  import { execa as execa4 } from "execa";
8186
- import { join as join25 } from "path";
8239
+ import { join as join26 } from "path";
8187
8240
  var PluginMigrationsAdapter = class {
8188
8241
  /**
8189
8242
  * Generates migration file(s) for `pluginNames` (every discovered
@@ -8192,22 +8245,22 @@ var PluginMigrationsAdapter = class {
8192
8245
  * or declared no tables.
8193
8246
  */
8194
8247
  async generate(cwd, pluginNames) {
8195
- const scriptPath = join25(cwd, "services", "api", "scripts", "generate_plugin_migrations.py");
8248
+ const scriptPath = join26(cwd, "services", "api", "scripts", "generate_plugin_migrations.py");
8196
8249
  const args = [
8197
8250
  "run",
8198
8251
  "python",
8199
8252
  scriptPath,
8200
8253
  "--services-root",
8201
- join25(cwd, "services"),
8254
+ join26(cwd, "services"),
8202
8255
  "--versions-dir",
8203
- join25(cwd, "services", "api", "migrations", "versions")
8256
+ join26(cwd, "services", "api", "migrations", "versions")
8204
8257
  ];
8205
8258
  for (const name of pluginNames ?? []) {
8206
8259
  args.push("--plugin", name);
8207
8260
  }
8208
8261
  let result;
8209
8262
  try {
8210
- result = await execa4("uv", args, { cwd: join25(cwd, "services", "api") });
8263
+ result = await execa4("uv", args, { cwd: join26(cwd, "services", "api") });
8211
8264
  } catch (err) {
8212
8265
  const cause = err;
8213
8266
  if (cause.code === "ENOENT") {
@@ -8224,8 +8277,8 @@ var PluginMigrationsAdapter = class {
8224
8277
  };
8225
8278
 
8226
8279
  // src/lib/plugin-workspace-sources.ts
8227
- import { existsSync as existsSync25, readdirSync as readdirSync12, readFileSync as readFileSync19, writeFileSync as writeFileSync10 } from "fs";
8228
- import { join as join26 } from "path";
8280
+ import { existsSync as existsSync26, readdirSync as readdirSync12, readFileSync as readFileSync19, writeFileSync as writeFileSync10 } from "fs";
8281
+ import { join as join27 } from "path";
8229
8282
  function readTomlStringArray(text, key) {
8230
8283
  const open = new RegExp(`^${key}\\s*=\\s*\\[`, "m").exec(text);
8231
8284
  if (!open) return [];
@@ -8269,8 +8322,8 @@ function readDependencyNames(text) {
8269
8322
  return readTomlStringArray(text, "dependencies").map((dep) => /^\s*([A-Za-z0-9._-]+)/.exec(dep)?.[1] ?? "").filter(Boolean);
8270
8323
  }
8271
8324
  function workspaceMemberNames(instanceRoot) {
8272
- const rootPyproject = join26(instanceRoot, "pyproject.toml");
8273
- if (!existsSync25(rootPyproject)) return /* @__PURE__ */ new Set();
8325
+ const rootPyproject = join27(instanceRoot, "pyproject.toml");
8326
+ if (!existsSync26(rootPyproject)) return /* @__PURE__ */ new Set();
8274
8327
  const text = readFileSync19(rootPyproject, "utf8");
8275
8328
  const members = readTomlStringArray(text, "members");
8276
8329
  const excluded = new Set(readTomlStringArray(text, "exclude"));
@@ -8280,7 +8333,7 @@ function workspaceMemberNames(instanceRoot) {
8280
8333
  const base = member.slice(0, -2);
8281
8334
  let entries;
8282
8335
  try {
8283
- entries = readdirSync12(join26(instanceRoot, base), { withFileTypes: true });
8336
+ entries = readdirSync12(join27(instanceRoot, base), { withFileTypes: true });
8284
8337
  } catch {
8285
8338
  continue;
8286
8339
  }
@@ -8294,8 +8347,8 @@ function workspaceMemberNames(instanceRoot) {
8294
8347
  }
8295
8348
  const names = /* @__PURE__ */ new Set();
8296
8349
  for (const dir of dirs) {
8297
- const pp = join26(instanceRoot, dir, "pyproject.toml");
8298
- if (!existsSync25(pp)) continue;
8350
+ const pp = join27(instanceRoot, dir, "pyproject.toml");
8351
+ if (!existsSync26(pp)) continue;
8299
8352
  const name = readProjectName(readFileSync19(pp, "utf8"));
8300
8353
  if (name) names.add(name);
8301
8354
  }
@@ -8307,7 +8360,7 @@ function existingWorkspaceSources(text) {
8307
8360
  );
8308
8361
  }
8309
8362
  function ensureWorkspaceSources(pluginPyprojectPath, memberNames) {
8310
- if (!existsSync25(pluginPyprojectPath) || memberNames.size === 0) return [];
8363
+ if (!existsSync26(pluginPyprojectPath) || memberNames.size === 0) return [];
8311
8364
  const text = readFileSync19(pluginPyprojectPath, "utf8");
8312
8365
  const already = existingWorkspaceSources(text);
8313
8366
  const toAdd = readDependencyNames(text).filter((n) => memberNames.has(n) && !already.has(n));
@@ -8374,14 +8427,14 @@ var LOCAL_COPY_EXCLUDES = /* @__PURE__ */ new Set([
8374
8427
  ".terraform"
8375
8428
  ]);
8376
8429
  function resolveLocalPlugin(localPath) {
8377
- if (!existsSync26(localPath)) {
8430
+ if (!existsSync27(localPath)) {
8378
8431
  throw new Error(`--local path does not exist: ${localPath}`);
8379
8432
  }
8380
8433
  if (!statSync6(localPath).isDirectory()) {
8381
8434
  throw new Error(`--local path is not a directory: ${localPath}`);
8382
8435
  }
8383
- const manifestPath = join27(localPath, "biffo.plugin.json");
8384
- if (!existsSync26(manifestPath)) {
8436
+ const manifestPath = join28(localPath, "biffo.plugin.json");
8437
+ if (!existsSync27(manifestPath)) {
8385
8438
  throw new Error(
8386
8439
  `${localPath} does not contain a biffo.plugin.json manifest at its root \u2014 is it a plugin directory? (Scaffold one with \`biffo plugin create <name>\`.)`
8387
8440
  );
@@ -8407,8 +8460,8 @@ function parsePluginTarget(target) {
8407
8460
  async function cloneAndValidatePlugin(entry, git) {
8408
8461
  const tmpDir = await git.cloneToTemp(entry.repo, `biffo-plugin-${entry.name}`);
8409
8462
  try {
8410
- const manifestPath = join27(tmpDir, "biffo.plugin.json");
8411
- if (!existsSync26(manifestPath)) {
8463
+ const manifestPath = join28(tmpDir, "biffo.plugin.json");
8464
+ if (!existsSync27(manifestPath)) {
8412
8465
  throw new Error(
8413
8466
  `Plugin repo ${entry.repo} does not contain a biffo.plugin.json manifest at its root.`
8414
8467
  );
@@ -8436,8 +8489,8 @@ async function runPluginInstall(target, options, deps) {
8436
8489
  `Nothing to install. Pass a registry target (e.g. \`biffo plugin install acme-crm@1.0\`) or a local plugin directory (\`biffo plugin install --local services/acme-crm\`).`
8437
8490
  );
8438
8491
  }
8439
- const servicesDir = join27(options.cwd, "services");
8440
- if (!existsSync26(servicesDir)) {
8492
+ const servicesDir = join28(options.cwd, "services");
8493
+ if (!existsSync27(servicesDir)) {
8441
8494
  throw new Error(
8442
8495
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
8443
8496
  );
@@ -8455,10 +8508,10 @@ async function runPluginInstall(target, options, deps) {
8455
8508
  }
8456
8509
  const pluginName = entry ? entry.name : source.name;
8457
8510
  const relTargetDir = pluginDir(pluginName, "third-party");
8458
- const targetDir = join27(options.cwd, relTargetDir);
8459
- const modulesDir = join27(options.cwd, "modules", "plugins", pluginName);
8511
+ const targetDir = join28(options.cwd, relTargetDir);
8512
+ const modulesDir = join28(options.cwd, "modules", "plugins", pluginName);
8460
8513
  const inTreeSource = options.local !== void 0 && resolve12(options.local) === resolve12(targetDir);
8461
- if (existsSync26(targetDir) && !inTreeSource) {
8514
+ if (existsSync27(targetDir) && !inTreeSource) {
8462
8515
  throw new Error(
8463
8516
  `Plugin '${pluginName}' is already installed at ${relTargetDir}/. Remove it first, or wait for a future 'biffo plugin upgrade' command.`
8464
8517
  );
@@ -8500,8 +8553,8 @@ async function runPluginInstall(target, options, deps) {
8500
8553
  });
8501
8554
  log.success(`Installed plugin source at ${relTargetDir}/`);
8502
8555
  }
8503
- const pluginPyproject = join27(targetDir, "pyproject.toml");
8504
- if (existsSync26(pluginPyproject)) {
8556
+ const pluginPyproject = join28(targetDir, "pyproject.toml");
8557
+ if (existsSync27(pluginPyproject)) {
8505
8558
  const sourced = ensureWorkspaceSources(pluginPyproject, workspaceMemberNames(options.cwd));
8506
8559
  if (sourced.length > 0) {
8507
8560
  log.info(
@@ -8510,8 +8563,8 @@ async function runPluginInstall(target, options, deps) {
8510
8563
  }
8511
8564
  }
8512
8565
  const stagePaths = [relTargetDir];
8513
- const tfSourceDir = join27(targetDir, "terraform");
8514
- if (existsSync26(tfSourceDir)) {
8566
+ const tfSourceDir = join28(targetDir, "terraform");
8567
+ if (existsSync27(tfSourceDir)) {
8515
8568
  mkdirSync9(modulesDir, { recursive: true });
8516
8569
  cpSync3(tfSourceDir, modulesDir, { recursive: true });
8517
8570
  stagePaths.push(`modules/plugins/${pluginName}`);
@@ -8605,8 +8658,8 @@ function printDryRun4(entry, source, relTargetDir, inTreeSource) {
8605
8658
  }
8606
8659
 
8607
8660
  // src/commands/plugin-list.ts
8608
- import { existsSync as existsSync27, readFileSync as readFileSync21 } from "fs";
8609
- import { join as join28, resolve as resolve13 } from "path";
8661
+ import { existsSync as existsSync28, readFileSync as readFileSync21 } from "fs";
8662
+ import { join as join29, resolve as resolve13 } from "path";
8610
8663
  import chalk16 from "chalk";
8611
8664
  import { Command as Command16 } from "commander";
8612
8665
  var pluginListCommand = new Command16("list").description("List plugins installed in this project checkout").option("--cwd <path>", "Project root to scan (defaults to the current directory)").action(async (options) => {
@@ -8619,8 +8672,8 @@ var pluginListCommand = new Command16("list").description("List plugins installe
8619
8672
  }
8620
8673
  });
8621
8674
  async function runPluginList(options) {
8622
- const servicesDir = join28(options.cwd, "services");
8623
- if (!existsSync27(servicesDir)) {
8675
+ const servicesDir = join29(options.cwd, "services");
8676
+ if (!existsSync28(servicesDir)) {
8624
8677
  throw new Error(
8625
8678
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
8626
8679
  );
@@ -8665,8 +8718,8 @@ async function runPluginList(options) {
8665
8718
  }
8666
8719
 
8667
8720
  // src/commands/plugin-sync-migrations.ts
8668
- import { existsSync as existsSync28 } from "fs";
8669
- import { join as join29, relative as relative4, resolve as resolve14 } from "path";
8721
+ import { existsSync as existsSync29 } from "fs";
8722
+ import { join as join30, relative as relative4, resolve as resolve14 } from "path";
8670
8723
  import chalk17 from "chalk";
8671
8724
  import { Command as Command17 } from "commander";
8672
8725
  var pluginSyncMigrationsCommand = new Command17("sync-migrations").description(
@@ -8687,11 +8740,11 @@ var pluginSyncMigrationsCommand = new Command17("sync-migrations").description(
8687
8740
  }
8688
8741
  );
8689
8742
  async function runPluginSyncMigrations(name, options, deps) {
8690
- const servicesDir = join29(options.cwd, "services");
8691
- if (!existsSync28(servicesDir)) {
8743
+ const servicesDir = join30(options.cwd, "services");
8744
+ if (!existsSync29(servicesDir)) {
8692
8745
  throw new Error(`${servicesDir} does not exist \u2014 is ${options.cwd} a Biffo project checkout?`);
8693
8746
  }
8694
- if (name && !existsSync28(join29(servicesDir, name, "biffo.plugin.json"))) {
8747
+ if (name && !existsSync29(join30(servicesDir, name, "biffo.plugin.json"))) {
8695
8748
  throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
8696
8749
  }
8697
8750
  if (options.dryRun) {
@@ -8727,8 +8780,8 @@ async function runPluginSyncMigrations(name, options, deps) {
8727
8780
  }
8728
8781
 
8729
8782
  // src/commands/plugin-uninstall.ts
8730
- import { existsSync as existsSync29, readFileSync as readFileSync22, rmSync as rmSync8 } from "fs";
8731
- import { join as join30, resolve as resolve15 } from "path";
8783
+ import { existsSync as existsSync30, readFileSync as readFileSync22, rmSync as rmSync8 } from "fs";
8784
+ import { join as join31, resolve as resolve15 } from "path";
8732
8785
  import chalk18 from "chalk";
8733
8786
  import { Command as Command18 } from "commander";
8734
8787
  import inquirer6 from "inquirer";
@@ -8760,16 +8813,16 @@ async function runPluginUninstall(name, options, deps) {
8760
8813
  if (!NAME_PATTERN2.test(name)) {
8761
8814
  throw new Error(`Invalid plugin name '${name}'. Expected a lowercase kebab-case slug.`);
8762
8815
  }
8763
- const servicesDir = join30(options.cwd, "services");
8764
- if (!existsSync29(servicesDir)) {
8816
+ const servicesDir = join31(options.cwd, "services");
8817
+ if (!existsSync30(servicesDir)) {
8765
8818
  throw new Error(
8766
8819
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
8767
8820
  );
8768
8821
  }
8769
- const targetDir = join30(servicesDir, name);
8770
- if (!existsSync29(targetDir)) {
8771
- const firstParty = join30(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
8772
- if (existsSync29(firstParty)) {
8822
+ const targetDir = join31(servicesDir, name);
8823
+ if (!existsSync30(targetDir)) {
8824
+ const firstParty = join31(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
8825
+ if (existsSync30(firstParty)) {
8773
8826
  throw new Error(
8774
8827
  `Plugin '${name}' is a first-party plugin at ${pluginDir(name, "first-party")}/, which is template-owned \u2014 \`biffo core upgrade\` would restore it on the next upgrade. Disable it instead by removing '${name}' from \`enabled_plugins\` in infra/environments/<env>/main.tf and re-applying.`
8775
8828
  );
@@ -8777,9 +8830,9 @@ async function runPluginUninstall(name, options, deps) {
8777
8830
  throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
8778
8831
  }
8779
8832
  const version = readInstalledVersion(targetDir);
8780
- const modulesDir = join30(options.cwd, "modules", "plugins", name);
8833
+ const modulesDir = join31(options.cwd, "modules", "plugins", name);
8781
8834
  const stagePaths = [`services/${name}`];
8782
- if (existsSync29(modulesDir)) {
8835
+ if (existsSync30(modulesDir)) {
8783
8836
  stagePaths.push(`modules/plugins/${name}`);
8784
8837
  }
8785
8838
  if (options.dryRun) {
@@ -8801,7 +8854,7 @@ async function runPluginUninstall(name, options, deps) {
8801
8854
  }
8802
8855
  rmSync8(targetDir, { recursive: true, force: true });
8803
8856
  log.success(`Removed services/${name}/`);
8804
- if (existsSync29(modulesDir)) {
8857
+ if (existsSync30(modulesDir)) {
8805
8858
  rmSync8(modulesDir, { recursive: true, force: true });
8806
8859
  log.success(`Removed modules/plugins/${name}/`);
8807
8860
  const wiring = syncPluginTerraform(options.cwd);
@@ -8838,8 +8891,8 @@ async function runPluginUninstall(name, options, deps) {
8838
8891
  }
8839
8892
  }
8840
8893
  function readInstalledVersion(targetDir) {
8841
- const manifestPath = join30(targetDir, "biffo.plugin.json");
8842
- if (!existsSync29(manifestPath)) return void 0;
8894
+ const manifestPath = join31(targetDir, "biffo.plugin.json");
8895
+ if (!existsSync30(manifestPath)) return void 0;
8843
8896
  try {
8844
8897
  return validateManifest(JSON.parse(readFileSync22(manifestPath, "utf8"))).version;
8845
8898
  } catch {
@@ -8874,8 +8927,8 @@ function printDryRun5(name, version, stagePaths, keepData) {
8874
8927
  }
8875
8928
 
8876
8929
  // src/commands/plugin-upgrade.ts
8877
- import { cpSync as cpSync4, existsSync as existsSync30, mkdirSync as mkdirSync10, readFileSync as readFileSync23, rmSync as rmSync9 } from "fs";
8878
- import { join as join31, relative as relative5, resolve as resolve16 } from "path";
8930
+ import { cpSync as cpSync4, existsSync as existsSync31, mkdirSync as mkdirSync10, readFileSync as readFileSync23, rmSync as rmSync9 } from "fs";
8931
+ import { join as join32, relative as relative5, resolve as resolve16 } from "path";
8879
8932
  import chalk19 from "chalk";
8880
8933
  import { Command as Command19 } from "commander";
8881
8934
  import inquirer7 from "inquirer";
@@ -8900,14 +8953,14 @@ var pluginUpgradeCommand = new Command19("upgrade").description(
8900
8953
  });
8901
8954
  async function runPluginUpgrade(target, options, deps) {
8902
8955
  const { name, minor } = parsePluginTarget(target);
8903
- const servicesDir = join31(options.cwd, "services");
8904
- if (!existsSync30(servicesDir)) {
8956
+ const servicesDir = join32(options.cwd, "services");
8957
+ if (!existsSync31(servicesDir)) {
8905
8958
  throw new Error(
8906
8959
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
8907
8960
  );
8908
8961
  }
8909
- const targetDir = join31(servicesDir, name);
8910
- if (!existsSync30(targetDir)) {
8962
+ const targetDir = join32(servicesDir, name);
8963
+ if (!existsSync31(targetDir)) {
8911
8964
  throw new Error(
8912
8965
  `Plugin '${name}' is not installed at services/${name}/. Use 'biffo plugin install ${name}@${minor}' instead.`
8913
8966
  );
@@ -8921,7 +8974,7 @@ async function runPluginUpgrade(target, options, deps) {
8921
8974
  `Plugin declares required_core_version '${entry.required_core_version}'. The CLI cannot verify this against your deployment \u2014 the Core API exposes no version endpoint and services/api/pyproject.toml's version is a static placeholder, not a real release marker. Confirm compatibility yourself before deploying.`
8922
8975
  );
8923
8976
  }
8924
- const modulesDir = join31(options.cwd, "modules", "plugins", entry.name);
8977
+ const modulesDir = join32(options.cwd, "modules", "plugins", entry.name);
8925
8978
  if (options.dryRun) {
8926
8979
  printDryRun6(entry, currentVersion);
8927
8980
  return;
@@ -8954,11 +9007,11 @@ async function runPluginUpgrade(target, options, deps) {
8954
9007
  cpSync4(tmpDir, targetDir, { recursive: true });
8955
9008
  log.success(`Upgraded plugin source at services/${entry.name}/`);
8956
9009
  const stagePaths = [`services/${entry.name}`];
8957
- if (existsSync30(modulesDir)) {
9010
+ if (existsSync31(modulesDir)) {
8958
9011
  rmSync9(modulesDir, { recursive: true, force: true });
8959
9012
  }
8960
- const tfSourceDir = join31(targetDir, "terraform");
8961
- if (existsSync30(tfSourceDir)) {
9013
+ const tfSourceDir = join32(targetDir, "terraform");
9014
+ if (existsSync31(tfSourceDir)) {
8962
9015
  mkdirSync10(modulesDir, { recursive: true });
8963
9016
  cpSync4(tfSourceDir, modulesDir, { recursive: true });
8964
9017
  stagePaths.push(`modules/plugins/${entry.name}`);
@@ -8992,8 +9045,8 @@ async function runPluginUpgrade(target, options, deps) {
8992
9045
  }
8993
9046
  }
8994
9047
  function readInstalledVersion2(targetDir) {
8995
- const manifestPath = join31(targetDir, "biffo.plugin.json");
8996
- if (!existsSync30(manifestPath)) return void 0;
9048
+ const manifestPath = join32(targetDir, "biffo.plugin.json");
9049
+ if (!existsSync31(manifestPath)) return void 0;
8997
9050
  try {
8998
9051
  return validateManifest(JSON.parse(readFileSync23(manifestPath, "utf8"))).version;
8999
9052
  } catch {
@@ -9037,7 +9090,7 @@ pluginCommand.addCommand(pluginInfoCommand);
9037
9090
  import { Command as Command22 } from "commander";
9038
9091
 
9039
9092
  // src/commands/sibling-check-identity.ts
9040
- import { existsSync as existsSync31, readFileSync as readFileSync24 } from "fs";
9093
+ import { existsSync as existsSync32, readFileSync as readFileSync24 } from "fs";
9041
9094
  import { resolve as resolve17 } from "path";
9042
9095
  import chalk20 from "chalk";
9043
9096
  import { Command as Command21 } from "commander";
@@ -9251,7 +9304,7 @@ async function resolveConfig4(options) {
9251
9304
  return cfg;
9252
9305
  }
9253
9306
  const localConfigPath = resolve17(process.cwd(), "biffo.config.json");
9254
- if (existsSync31(localConfigPath)) {
9307
+ if (existsSync32(localConfigPath)) {
9255
9308
  const raw = JSON.parse(readFileSync24(localConfigPath, "utf8"));
9256
9309
  const result = BiffoConfigSchema.safeParse(raw);
9257
9310
  if (result.success) return result.data;
@@ -9298,19 +9351,19 @@ siblingCommand.addCommand(siblingCheckIdentityCommand);
9298
9351
  import { Command as Command23 } from "commander";
9299
9352
 
9300
9353
  // src/scripts/check-adr-numbering.ts
9301
- import { existsSync as existsSync33 } from "fs";
9302
- import { join as join33 } from "path";
9354
+ import { existsSync as existsSync34 } from "fs";
9355
+ import { join as join34 } from "path";
9303
9356
  import { execa as execa5 } from "execa";
9304
9357
 
9305
9358
  // src/lib/adr-numbering-guard.ts
9306
- import { existsSync as existsSync32, readdirSync as readdirSync13, readFileSync as readFileSync25 } from "fs";
9307
- import { join as join32 } from "path";
9359
+ import { existsSync as existsSync33, readdirSync as readdirSync13, readFileSync as readFileSync25 } from "fs";
9360
+ import { join as join33 } from "path";
9308
9361
  var ADR_FILENAME = /^(\d{4})-.+\.md$/;
9309
9362
  var ALLOWLIST_FILENAME = ".numbering-allowlist";
9310
9363
  var TEMPLATE_ADR_RESERVED_UPTO = "0099";
9311
9364
  function readAdrNumberingAllowlist(adrDir) {
9312
- const path = join32(adrDir, ALLOWLIST_FILENAME);
9313
- if (!existsSync32(path)) return /* @__PURE__ */ new Set();
9365
+ const path = join33(adrDir, ALLOWLIST_FILENAME);
9366
+ if (!existsSync33(path)) return /* @__PURE__ */ new Set();
9314
9367
  const numbers = /* @__PURE__ */ new Set();
9315
9368
  for (const rawLine of readFileSync25(path, "utf8").split("\n")) {
9316
9369
  const line = rawLine.split("#")[0].trim();
@@ -9320,7 +9373,7 @@ function readAdrNumberingAllowlist(adrDir) {
9320
9373
  }
9321
9374
  function adrNumbersIn(adrDir) {
9322
9375
  const claims = /* @__PURE__ */ new Map();
9323
- if (!existsSync32(adrDir)) return claims;
9376
+ if (!existsSync33(adrDir)) return claims;
9324
9377
  for (const entry of readdirSync13(adrDir).sort()) {
9325
9378
  const match = ADR_FILENAME.exec(entry);
9326
9379
  if (!match) continue;
@@ -9375,8 +9428,8 @@ function formatAdrReservedRangeViolations(violations, reservedUpTo = TEMPLATE_AD
9375
9428
  // src/scripts/check-adr-numbering.ts
9376
9429
  async function runAdrNumberingCheck() {
9377
9430
  const root = (await execa5("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
9378
- const adrDir = join33(root, "docs", "ADR");
9379
- if (!existsSync33(adrDir)) {
9431
+ const adrDir = join34(root, "docs", "ADR");
9432
+ if (!existsSync34(adrDir)) {
9380
9433
  console.log("\u2713 ADR numbering guard: no docs/ADR/ directory \u2014 nothing to compare");
9381
9434
  return;
9382
9435
  }
@@ -9704,8 +9757,8 @@ async function runOwnershipCheck(argv) {
9704
9757
  const { stdout } = await execa7("git", ["diff", "--cached", "--name-status"], { cwd: root });
9705
9758
  ({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
9706
9759
  if (messageFile) {
9707
- const { readFileSync: readFileSync28, existsSync: existsSync39 } = await import("fs");
9708
- if (existsSync39(messageFile)) commitMessage = readFileSync28(messageFile, "utf8");
9760
+ const { readFileSync: readFileSync28, existsSync: existsSync40 } = await import("fs");
9761
+ if (existsSync40(messageFile)) commitMessage = readFileSync28(messageFile, "utf8");
9709
9762
  }
9710
9763
  } else {
9711
9764
  const base = process.env["GITHUB_BASE_REF"] ?? args[0];
@@ -9806,33 +9859,33 @@ ${BOLD}If the divergence is deliberate${OFF}
9806
9859
  }
9807
9860
 
9808
9861
  // src/scripts/check-plugin-collisions.ts
9809
- import { existsSync as existsSync35 } from "fs";
9810
- import { join as join35 } from "path";
9862
+ import { existsSync as existsSync36 } from "fs";
9863
+ import { join as join36 } from "path";
9811
9864
  import { execa as execa8 } from "execa";
9812
9865
 
9813
9866
  // src/lib/plugin-collision-guard.ts
9814
- import { existsSync as existsSync34, readdirSync as readdirSync14, statSync as statSync7 } from "fs";
9815
- import { join as join34 } from "path";
9867
+ import { existsSync as existsSync35, readdirSync as readdirSync14, statSync as statSync7 } from "fs";
9868
+ import { join as join35 } from "path";
9816
9869
  var PYTEST_SPECIAL = /* @__PURE__ */ new Set(["conftest.py"]);
9817
9870
  var IGNORED_DIRS = /* @__PURE__ */ new Set([".venv", "node_modules", "__pycache__", ".git", "dist", "build"]);
9818
9871
  function subdirectories(dir) {
9819
- if (!existsSync34(dir)) return [];
9872
+ if (!existsSync35(dir)) return [];
9820
9873
  return readdirSync14(dir).filter((entry) => {
9821
9874
  if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) return false;
9822
9875
  try {
9823
- return statSync7(join34(dir, entry)).isDirectory();
9876
+ return statSync7(join35(dir, entry)).isDirectory();
9824
9877
  } catch {
9825
9878
  return false;
9826
9879
  }
9827
9880
  });
9828
9881
  }
9829
9882
  function regularPackagesOf(pluginDir2) {
9830
- return subdirectories(pluginDir2).filter((name) => existsSync34(join34(pluginDir2, name, "__init__.py"))).sort();
9883
+ return subdirectories(pluginDir2).filter((name) => existsSync35(join35(pluginDir2, name, "__init__.py"))).sort();
9831
9884
  }
9832
9885
  function bareTestModulesOf(pluginDir2) {
9833
- const testsDir = join34(pluginDir2, "tests");
9834
- if (!existsSync34(testsDir)) return [];
9835
- if (existsSync34(join34(testsDir, "__init__.py"))) return [];
9886
+ const testsDir = join35(pluginDir2, "tests");
9887
+ if (!existsSync35(testsDir)) return [];
9888
+ if (existsSync35(join35(testsDir, "__init__.py"))) return [];
9836
9889
  return readdirSync14(testsDir).filter((f) => f.endsWith(".py") && !PYTEST_SPECIAL.has(f)).sort();
9837
9890
  }
9838
9891
  function findCollisions(servicesDir, pluginDirs) {
@@ -9841,7 +9894,7 @@ function findCollisions(servicesDir, pluginDirs) {
9841
9894
  const gather = (kind, namesOf) => {
9842
9895
  const claims = /* @__PURE__ */ new Map();
9843
9896
  for (const plugin of plugins) {
9844
- for (const name of namesOf(join34(servicesDir, plugin))) {
9897
+ for (const name of namesOf(join35(servicesDir, plugin))) {
9845
9898
  claims.set(name, [...claims.get(name) ?? [], plugin]);
9846
9899
  }
9847
9900
  }
@@ -9879,8 +9932,8 @@ function formatCollisions(collisions) {
9879
9932
  // src/scripts/check-plugin-collisions.ts
9880
9933
  async function runPluginCollisionCheck() {
9881
9934
  const root = (await execa8("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
9882
- const servicesDir = join35(root, "services");
9883
- if (!existsSync35(servicesDir)) {
9935
+ const servicesDir = join36(root, "services");
9936
+ if (!existsSync36(servicesDir)) {
9884
9937
  console.log("\u2713 plugin collision guard: no services/ directory \u2014 nothing to compare");
9885
9938
  return;
9886
9939
  }
@@ -9900,8 +9953,8 @@ async function runPluginCollisionCheck() {
9900
9953
  import { execa as execa9 } from "execa";
9901
9954
 
9902
9955
  // src/lib/plugin-terraform-guard.ts
9903
- import { existsSync as existsSync36, readFileSync as readFileSync26, readdirSync as readdirSync15 } from "fs";
9904
- import { dirname as dirname9, join as join36, relative as relative6, sep as sep3 } from "path";
9956
+ import { existsSync as existsSync37, readFileSync as readFileSync26, readdirSync as readdirSync15 } from "fs";
9957
+ import { dirname as dirname9, join as join37, relative as relative6, sep as sep3 } from "path";
9905
9958
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
9906
9959
  var PLUGIN_MANIFEST_FILE2 = "biffo.plugin.json";
9907
9960
  function findPluginManifests(root) {
@@ -9916,9 +9969,9 @@ function findPluginManifests(root) {
9916
9969
  for (const entry of entries) {
9917
9970
  if (entry.isDirectory()) {
9918
9971
  if (SKIP_DIRS.has(entry.name)) continue;
9919
- walk(join36(dir, entry.name));
9972
+ walk(join37(dir, entry.name));
9920
9973
  } else if (entry.isFile() && entry.name === PLUGIN_MANIFEST_FILE2) {
9921
- found.push(relative6(root, join36(dir, entry.name)).split(sep3).join("/"));
9974
+ found.push(relative6(root, join37(dir, entry.name)).split(sep3).join("/"));
9922
9975
  }
9923
9976
  }
9924
9977
  };
@@ -9943,14 +9996,14 @@ function readSubscriptions(absManifestPath) {
9943
9996
  }
9944
9997
  function checkPluginTerraform(root) {
9945
9998
  const violations = [];
9946
- const coreManifest = existsSync36(join36(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
9999
+ const coreManifest = existsSync37(join37(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
9947
10000
  for (const manifest of findPluginManifests(root)) {
9948
10001
  if (coreManifest && !isTemplateOwned(manifest, coreManifest)) continue;
9949
- const absManifest = join36(root, manifest);
10002
+ const absManifest = join37(root, manifest);
9950
10003
  const subscriptions = readSubscriptions(absManifest);
9951
10004
  if (subscriptions === null) continue;
9952
10005
  const pluginDir2 = dirname9(absManifest);
9953
- if (existsSync36(join36(pluginDir2, "terraform"))) continue;
10006
+ if (existsSync37(join37(pluginDir2, "terraform"))) continue;
9954
10007
  const relPluginDir = relative6(root, pluginDir2).split(sep3).join("/");
9955
10008
  violations.push({
9956
10009
  manifest,
@@ -10146,8 +10199,8 @@ function rawArgsAfter(subcommand) {
10146
10199
  }
10147
10200
 
10148
10201
  // src/commands/doctor.ts
10149
- import { existsSync as existsSync37, readFileSync as readFileSync27 } from "fs";
10150
- import { join as join37, resolve as resolve18 } from "path";
10202
+ import { existsSync as existsSync38, readFileSync as readFileSync27 } from "fs";
10203
+ import { join as join38, resolve as resolve18 } from "path";
10151
10204
  import chalk21 from "chalk";
10152
10205
  import { Command as Command24 } from "commander";
10153
10206
 
@@ -10322,8 +10375,8 @@ async function runDoctor(options, deps = { git: new GitAdapter() }) {
10322
10375
  return runDoctorChecks(facts);
10323
10376
  }
10324
10377
  function readLocalCoreVersion(cwd) {
10325
- const path = join37(cwd, INSTANCE_CORE_FILE);
10326
- if (!existsSync37(path)) return null;
10378
+ const path = join38(cwd, INSTANCE_CORE_FILE);
10379
+ if (!existsSync38(path)) return null;
10327
10380
  try {
10328
10381
  return parseCoreRecord(readFileSync27(path, "utf8"));
10329
10382
  } catch {
@@ -10340,8 +10393,8 @@ function parseCoreRecord(contents) {
10340
10393
  }
10341
10394
  }
10342
10395
  function readFossil(cwd) {
10343
- const path = join37(cwd, CORE_VERSION_FILE);
10344
- if (!existsSync37(path)) return null;
10396
+ const path = join38(cwd, CORE_VERSION_FILE);
10397
+ if (!existsSync38(path)) return null;
10345
10398
  try {
10346
10399
  const value = readFileSync27(path, "utf8").trim();
10347
10400
  return value === "" ? null : value;
@@ -10792,13 +10845,13 @@ import { fileURLToPath as fileURLToPath6 } from "url";
10792
10845
  import { Command as Command26 } from "commander";
10793
10846
 
10794
10847
  // src/lib/packaged-scripts.ts
10795
- import { existsSync as existsSync38 } from "fs";
10796
- import { dirname as dirname10, join as join38 } from "path";
10848
+ import { existsSync as existsSync39 } from "fs";
10849
+ import { dirname as dirname10, join as join39 } from "path";
10797
10850
  function findPackagedScript(startDir, relativePath) {
10798
10851
  let dir = startDir;
10799
10852
  for (; ; ) {
10800
- const candidate = join38(dir, relativePath);
10801
- if (existsSync38(candidate)) return candidate;
10853
+ const candidate = join39(dir, relativePath);
10854
+ if (existsSync39(candidate)) return candidate;
10802
10855
  const parent = dirname10(dir);
10803
10856
  if (parent === dir) return null;
10804
10857
  dir = parent;