@biffo/cli 0.315.9 → 0.316.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.
Files changed (2) hide show
  1. package/dist/index.js +439 -302
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -9311,8 +9311,8 @@ function printScopeSeamEntitlement(name) {
9311
9311
  }
9312
9312
 
9313
9313
  // src/commands/plugin-install.ts
9314
- import { cpSync as cpSync5, existsSync as existsSync31, mkdirSync as mkdirSync11, readFileSync as readFileSync23, statSync as statSync7, writeFileSync as writeFileSync12 } from "fs";
9315
- import { join as join33, relative as relative4, resolve as resolve12 } from "path";
9314
+ import { cpSync as cpSync5, existsSync as existsSync32, mkdirSync as mkdirSync11, readFileSync as readFileSync24, statSync as statSync7, writeFileSync as writeFileSync13 } from "fs";
9315
+ import { join as join34, relative as relative4, resolve as resolve12 } from "path";
9316
9316
  import chalk15 from "chalk";
9317
9317
  import { Command as Command15 } from "commander";
9318
9318
 
@@ -9409,9 +9409,122 @@ ${lines.join("\n")}
9409
9409
  Supply each with --config <name>=<value> (repeatable) and re-run install. For a 'secret', pass the SSM parameter PATH holding the credential (create the parameter first, e.g. \`aws ssm put-parameter --type SecureString ...\`), never the credential itself.`;
9410
9410
  }
9411
9411
 
9412
- // src/lib/plugin-provenance.ts
9412
+ // src/lib/plugin-frontend-registry.ts
9413
9413
  import { existsSync as existsSync28, readFileSync as readFileSync21, writeFileSync as writeFileSync10 } from "fs";
9414
9414
  import { join as join29 } from "path";
9415
+ var PLUGIN_REGISTRY_RELATIVE_PATH = "apps/frontend/src/lib/plugins.ts";
9416
+ var REGISTRY_START_MARKER = "// BIFFO-PLUGIN-REGISTRY:START \u2014 managed by `biffo plugin install`/`uninstall`. Do not hand-edit.";
9417
+ var REGISTRY_END_MARKER = "// BIFFO-PLUGIN-REGISTRY:END";
9418
+ function titleFromSlug(slug) {
9419
+ return slug.split("-").filter((word) => word.length > 0).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
9420
+ }
9421
+ function frontendUrlForSlug(slug) {
9422
+ return `/api/v1/plugins/${slug}/ui`;
9423
+ }
9424
+ function registryPath2(cwd) {
9425
+ return join29(cwd, PLUGIN_REGISTRY_RELATIVE_PATH);
9426
+ }
9427
+ function missingRegistryError(cwd, pluginSlug) {
9428
+ return new Error(
9429
+ `${PLUGIN_REGISTRY_RELATIVE_PATH} does not exist in ${cwd} \u2014 this sibling checkout has not adopted the dashboard dynamic-route pattern (ADR-0021 \xA72), so plugin "${pluginSlug}"'s \`user_frontend\` block has nowhere to register. Add the file with a managed \`INSTALLED_PLUGINS\` array (see plugin-frontend-registry.ts for the exact marker contract) before installing a user-facing plugin here, or install a plugin with no \`user_frontend\` block instead.`
9430
+ );
9431
+ }
9432
+ function malformedRegistryError(cwd, pluginSlug) {
9433
+ return new Error(
9434
+ `${PLUGIN_REGISTRY_RELATIVE_PATH} in ${cwd} does not carry the managed "${REGISTRY_START_MARKER}" / "${REGISTRY_END_MARKER}" region that plugin "${pluginSlug}"'s install needs to write into. Add that managed region around the INSTALLED_PLUGINS array contents (see plugin-frontend-registry.ts's module docstring) rather than hand-editing the array.`
9435
+ );
9436
+ }
9437
+ function findManagedRegion(source) {
9438
+ const startIdx = source.indexOf(REGISTRY_START_MARKER);
9439
+ if (startIdx === -1) return null;
9440
+ const bodyStart = startIdx + REGISTRY_START_MARKER.length;
9441
+ const endIdx = source.indexOf(REGISTRY_END_MARKER, bodyStart);
9442
+ if (endIdx === -1) return null;
9443
+ return {
9444
+ region: { before: source.slice(0, bodyStart), after: source.slice(endIdx) },
9445
+ body: source.slice(bodyStart, endIdx)
9446
+ };
9447
+ }
9448
+ var SLUG_FIELD_PATTERN = /\bslug\s*:\s*(['"])([a-zA-Z0-9_-]+)\1/;
9449
+ function extractSlug(entryRaw) {
9450
+ const match = SLUG_FIELD_PATTERN.exec(entryRaw);
9451
+ return match ? match[2] : null;
9452
+ }
9453
+ function splitEntries(body) {
9454
+ const entries = [];
9455
+ const n = body.length;
9456
+ let i = 0;
9457
+ while (i < n) {
9458
+ const ch = body[i];
9459
+ if (ch !== "{") {
9460
+ i++;
9461
+ continue;
9462
+ }
9463
+ const start = i + 1;
9464
+ let depth = 1;
9465
+ let j = start;
9466
+ let quote = null;
9467
+ while (j < n && depth > 0) {
9468
+ const c = body[j];
9469
+ if (quote) {
9470
+ if (c === "\\") {
9471
+ j += 2;
9472
+ continue;
9473
+ }
9474
+ if (c === quote) quote = null;
9475
+ } else if (c === '"' || c === "'" || c === "`") {
9476
+ quote = c;
9477
+ } else if (c === "{") {
9478
+ depth++;
9479
+ } else if (c === "}") {
9480
+ depth--;
9481
+ }
9482
+ j++;
9483
+ }
9484
+ const raw = body.slice(start, j - 1);
9485
+ entries.push({ raw, slug: extractSlug(raw) });
9486
+ i = j;
9487
+ }
9488
+ return entries;
9489
+ }
9490
+ function serializeEntryRaw(entry) {
9491
+ return `
9492
+ slug: ${JSON.stringify(entry.slug)},
9493
+ title: ${JSON.stringify(entry.title)},
9494
+ frontendUrl: ${JSON.stringify(entry.frontendUrl)},
9495
+ `;
9496
+ }
9497
+ function assertPluginRegistryReady(cwd, pluginSlug) {
9498
+ readManagedEntries(cwd, pluginSlug);
9499
+ }
9500
+ function readManagedEntries(cwd, pluginSlug) {
9501
+ const path = registryPath2(cwd);
9502
+ if (!existsSync28(path)) throw missingRegistryError(cwd, pluginSlug);
9503
+ const source = readFileSync21(path, "utf8");
9504
+ const found = findManagedRegion(source);
9505
+ if (!found) throw malformedRegistryError(cwd, pluginSlug);
9506
+ return { source, region: found.region, entries: splitEntries(found.body) };
9507
+ }
9508
+ function writeManagedEntries(cwd, region, entries) {
9509
+ const body = entries.length > 0 ? "\n" + entries.map((e) => ` {${e.raw}},
9510
+ `).join("") : "\n";
9511
+ writeFileSync10(registryPath2(cwd), region.before + body + region.after, "utf8");
9512
+ }
9513
+ function upsertPluginRegistryEntry(cwd, entry) {
9514
+ const { region, entries } = readManagedEntries(cwd, entry.slug);
9515
+ const preserved = entries.filter((e) => e.slug !== entry.slug);
9516
+ const next = { raw: serializeEntryRaw(entry), slug: entry.slug };
9517
+ writeManagedEntries(cwd, region, [...preserved, next]);
9518
+ }
9519
+ function removePluginRegistryEntry(cwd, pluginSlug) {
9520
+ const { region, entries } = readManagedEntries(cwd, pluginSlug);
9521
+ const next = entries.filter((e) => e.slug !== pluginSlug);
9522
+ writeManagedEntries(cwd, region, next);
9523
+ }
9524
+
9525
+ // src/lib/plugin-provenance.ts
9526
+ import { existsSync as existsSync29, readFileSync as readFileSync22, writeFileSync as writeFileSync11 } from "fs";
9527
+ import { join as join30 } from "path";
9415
9528
  var PLUGIN_PROVENANCE_FILENAME = ".biffo-plugin-provenance.json";
9416
9529
  function isPluginProvenance(value) {
9417
9530
  if (typeof value !== "object" || value === null) return false;
@@ -9419,11 +9532,11 @@ function isPluginProvenance(value) {
9419
9532
  return typeof v["origin"] === "string" && (typeof v["ref"] === "string" || v["ref"] === null) && (typeof v["sha"] === "string" || v["sha"] === null) && typeof v["recordedAt"] === "string" && typeof v["inTree"] === "boolean";
9420
9533
  }
9421
9534
  function readProvenance(pluginDir2) {
9422
- const path = join29(pluginDir2, PLUGIN_PROVENANCE_FILENAME);
9423
- if (!existsSync28(path)) return { status: "absent" };
9535
+ const path = join30(pluginDir2, PLUGIN_PROVENANCE_FILENAME);
9536
+ if (!existsSync29(path)) return { status: "absent" };
9424
9537
  let parsed;
9425
9538
  try {
9426
- parsed = JSON.parse(readFileSync21(path, "utf8"));
9539
+ parsed = JSON.parse(readFileSync22(path, "utf8"));
9427
9540
  } catch (err) {
9428
9541
  return {
9429
9542
  status: "invalid",
@@ -9439,7 +9552,7 @@ function readProvenance(pluginDir2) {
9439
9552
  return { status: "present", record: parsed };
9440
9553
  }
9441
9554
  function writePluginProvenance(pluginDir2, record) {
9442
- writeFileSync10(join29(pluginDir2, PLUGIN_PROVENANCE_FILENAME), `${JSON.stringify(record, null, 2)}
9555
+ writeFileSync11(join30(pluginDir2, PLUGIN_PROVENANCE_FILENAME), `${JSON.stringify(record, null, 2)}
9443
9556
  `);
9444
9557
  }
9445
9558
  function reconcileProvenance(previous, next) {
@@ -9489,8 +9602,8 @@ async function tryGit(cwd, args) {
9489
9602
  }
9490
9603
 
9491
9604
  // src/lib/plugin-seed-vendor.ts
9492
- import { cpSync as cpSync3, existsSync as existsSync29, mkdirSync as mkdirSync9, readdirSync as readdirSync12, rmSync as rmSync8 } from "fs";
9493
- import { join as join30 } from "path";
9605
+ import { cpSync as cpSync3, existsSync as existsSync30, mkdirSync as mkdirSync9, readdirSync as readdirSync12, rmSync as rmSync8 } from "fs";
9606
+ import { join as join31 } from "path";
9494
9607
  var VENDOR_PREFIX = "_plugin-";
9495
9608
  function pluginSeedImportDir(pluginName) {
9496
9609
  return `db/imports/${VENDOR_PREFIX}${pluginName}`;
@@ -9499,8 +9612,8 @@ function vendorPluginSeed(pluginSourceDir, manifest, cwd) {
9499
9612
  if (!manifest.seed) {
9500
9613
  return { vendored: false };
9501
9614
  }
9502
- const sourceSeedDir = join30(pluginSourceDir, manifest.seed.dir);
9503
- if (!existsSync29(sourceSeedDir)) {
9615
+ const sourceSeedDir = join31(pluginSourceDir, manifest.seed.dir);
9616
+ if (!existsSync30(sourceSeedDir)) {
9504
9617
  throw new Error(
9505
9618
  `${manifest.name}'s manifest declares seed.dir '${manifest.seed.dir}', but ${sourceSeedDir} does not exist in the plugin's source.`
9506
9619
  );
@@ -9512,11 +9625,11 @@ function vendorPluginSeed(pluginSourceDir, manifest, cwd) {
9512
9625
  );
9513
9626
  }
9514
9627
  const relTargetDir = pluginSeedImportDir(manifest.name);
9515
- const targetDir = join30(cwd, relTargetDir);
9628
+ const targetDir = join31(cwd, relTargetDir);
9516
9629
  rmSync8(targetDir, { recursive: true, force: true });
9517
9630
  mkdirSync9(targetDir, { recursive: true });
9518
9631
  for (const file of sqlFiles) {
9519
- cpSync3(join30(sourceSeedDir, file), join30(targetDir, file));
9632
+ cpSync3(join31(sourceSeedDir, file), join31(targetDir, file));
9520
9633
  }
9521
9634
  log.success(
9522
9635
  `Vendored ${sqlFiles.length} seed file(s) to ${relTargetDir}/ (baseline_tables: ${manifest.seed.baseline_tables.join(", ") || "none declared"})`
@@ -9529,7 +9642,7 @@ function vendorPluginSeed(pluginSourceDir, manifest, cwd) {
9529
9642
 
9530
9643
  // src/lib/plugin-source-copy.ts
9531
9644
  import { copyFileSync as copyFileSync2, cpSync as cpSync4, mkdirSync as mkdirSync10 } from "fs";
9532
- import { basename as basename2, dirname as dirname9, join as join31 } from "path";
9645
+ import { basename as basename2, dirname as dirname9, join as join32 } from "path";
9533
9646
  var LOCAL_COPY_EXCLUDES = /* @__PURE__ */ new Set([
9534
9647
  ".git",
9535
9648
  ".venv",
@@ -9545,9 +9658,9 @@ async function copyPluginSource(sourceDir, targetDir) {
9545
9658
  if (await isGitWorkingTree2(sourceDir)) {
9546
9659
  const files = await listGitFiles(sourceDir);
9547
9660
  for (const relPath of files) {
9548
- const destPath = join31(targetDir, relPath);
9661
+ const destPath = join32(targetDir, relPath);
9549
9662
  mkdirSync10(dirname9(destPath), { recursive: true });
9550
- copyFileSync2(join31(sourceDir, relPath), destPath);
9663
+ copyFileSync2(join32(sourceDir, relPath), destPath);
9551
9664
  }
9552
9665
  return { usedGitIgnoreRules: true };
9553
9666
  }
@@ -9579,8 +9692,8 @@ async function listGitFiles(dir) {
9579
9692
  }
9580
9693
 
9581
9694
  // src/lib/plugin-workspace-sources.ts
9582
- import { existsSync as existsSync30, readdirSync as readdirSync13, readFileSync as readFileSync22, writeFileSync as writeFileSync11 } from "fs";
9583
- import { join as join32 } from "path";
9695
+ import { existsSync as existsSync31, readdirSync as readdirSync13, readFileSync as readFileSync23, writeFileSync as writeFileSync12 } from "fs";
9696
+ import { join as join33 } from "path";
9584
9697
  function readTomlStringArray(text, key) {
9585
9698
  const open = new RegExp(`^${key}\\s*=\\s*\\[`, "m").exec(text);
9586
9699
  if (!open) return [];
@@ -9624,9 +9737,9 @@ function readDependencyNames(text) {
9624
9737
  return readTomlStringArray(text, "dependencies").map((dep) => /^\s*([A-Za-z0-9._-]+)/.exec(dep)?.[1] ?? "").filter(Boolean);
9625
9738
  }
9626
9739
  function workspaceMemberNames(instanceRoot) {
9627
- const rootPyproject = join32(instanceRoot, "pyproject.toml");
9628
- if (!existsSync30(rootPyproject)) return /* @__PURE__ */ new Set();
9629
- const text = readFileSync22(rootPyproject, "utf8");
9740
+ const rootPyproject = join33(instanceRoot, "pyproject.toml");
9741
+ if (!existsSync31(rootPyproject)) return /* @__PURE__ */ new Set();
9742
+ const text = readFileSync23(rootPyproject, "utf8");
9630
9743
  const members = readTomlStringArray(text, "members");
9631
9744
  const excluded = new Set(readTomlStringArray(text, "exclude"));
9632
9745
  const dirs = [];
@@ -9635,7 +9748,7 @@ function workspaceMemberNames(instanceRoot) {
9635
9748
  const base = member.slice(0, -2);
9636
9749
  let entries;
9637
9750
  try {
9638
- entries = readdirSync13(join32(instanceRoot, base), { withFileTypes: true });
9751
+ entries = readdirSync13(join33(instanceRoot, base), { withFileTypes: true });
9639
9752
  } catch {
9640
9753
  continue;
9641
9754
  }
@@ -9649,9 +9762,9 @@ function workspaceMemberNames(instanceRoot) {
9649
9762
  }
9650
9763
  const names = /* @__PURE__ */ new Set();
9651
9764
  for (const dir of dirs) {
9652
- const pp = join32(instanceRoot, dir, "pyproject.toml");
9653
- if (!existsSync30(pp)) continue;
9654
- const name = readProjectName(readFileSync22(pp, "utf8"));
9765
+ const pp = join33(instanceRoot, dir, "pyproject.toml");
9766
+ if (!existsSync31(pp)) continue;
9767
+ const name = readProjectName(readFileSync23(pp, "utf8"));
9655
9768
  if (name) names.add(name);
9656
9769
  }
9657
9770
  return names;
@@ -9662,8 +9775,8 @@ function existingWorkspaceSources(text) {
9662
9775
  );
9663
9776
  }
9664
9777
  function ensureWorkspaceSources(pluginPyprojectPath, memberNames) {
9665
- if (!existsSync30(pluginPyprojectPath) || memberNames.size === 0) return [];
9666
- const text = readFileSync22(pluginPyprojectPath, "utf8");
9778
+ if (!existsSync31(pluginPyprojectPath) || memberNames.size === 0) return [];
9779
+ const text = readFileSync23(pluginPyprojectPath, "utf8");
9667
9780
  const already = existingWorkspaceSources(text);
9668
9781
  const toAdd = readDependencyNames(text).filter((n) => memberNames.has(n) && !already.has(n));
9669
9782
  if (toAdd.length === 0) return [];
@@ -9683,12 +9796,12 @@ ${lines.join("\n")}${text.slice(insertAt)}`;
9683
9796
  ${lines.join("\n")}
9684
9797
  `;
9685
9798
  }
9686
- writeFileSync11(pluginPyprojectPath, updated);
9799
+ writeFileSync12(pluginPyprojectPath, updated);
9687
9800
  return toAdd;
9688
9801
  }
9689
9802
  function applyWorkspaceSources(targetDir, cwd, relTargetDir) {
9690
- const pluginPyproject = join32(targetDir, "pyproject.toml");
9691
- if (!existsSync30(pluginPyproject)) return;
9803
+ const pluginPyproject = join33(targetDir, "pyproject.toml");
9804
+ if (!existsSync31(pluginPyproject)) return;
9692
9805
  const sourced = ensureWorkspaceSources(pluginPyproject, workspaceMemberNames(cwd));
9693
9806
  if (sourced.length > 0) {
9694
9807
  log.info(
@@ -9734,14 +9847,14 @@ var pluginInstallCommand = new Command15("install").description(
9734
9847
  }
9735
9848
  );
9736
9849
  function resolveLocalPlugin(localPath) {
9737
- if (!existsSync31(localPath)) {
9850
+ if (!existsSync32(localPath)) {
9738
9851
  throw new Error(`--local path does not exist: ${localPath}`);
9739
9852
  }
9740
9853
  if (!statSync7(localPath).isDirectory()) {
9741
9854
  throw new Error(`--local path is not a directory: ${localPath}`);
9742
9855
  }
9743
- const manifestPath = join33(localPath, "biffo.plugin.json");
9744
- if (!existsSync31(manifestPath)) {
9856
+ const manifestPath = join34(localPath, "biffo.plugin.json");
9857
+ if (!existsSync32(manifestPath)) {
9745
9858
  throw new Error(
9746
9859
  `${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>\`.)`
9747
9860
  );
@@ -9767,8 +9880,8 @@ function parsePluginTarget(target) {
9767
9880
  async function cloneAndValidatePlugin(entry, git) {
9768
9881
  const tmpDir = await git.cloneToTemp(entry.repo, `biffo-plugin-${entry.name}`);
9769
9882
  try {
9770
- const manifestPath = join33(tmpDir, "biffo.plugin.json");
9771
- if (!existsSync31(manifestPath)) {
9883
+ const manifestPath = join34(tmpDir, "biffo.plugin.json");
9884
+ if (!existsSync32(manifestPath)) {
9772
9885
  throw new Error(
9773
9886
  `Plugin repo ${entry.repo} does not contain a biffo.plugin.json manifest at its root.`
9774
9887
  );
@@ -9796,8 +9909,8 @@ async function runPluginInstall(target, options, deps) {
9796
9909
  `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\`).`
9797
9910
  );
9798
9911
  }
9799
- const servicesDir = join33(options.cwd, "services");
9800
- if (!existsSync31(servicesDir)) {
9912
+ const servicesDir = join34(options.cwd, "services");
9913
+ if (!existsSync32(servicesDir)) {
9801
9914
  throw new Error(
9802
9915
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
9803
9916
  );
@@ -9815,10 +9928,10 @@ async function runPluginInstall(target, options, deps) {
9815
9928
  }
9816
9929
  const pluginName = entry ? entry.name : source.name;
9817
9930
  const relTargetDir = pluginDir(pluginName, "third-party");
9818
- const targetDir = join33(options.cwd, relTargetDir);
9819
- const modulesDir = join33(options.cwd, "modules", "plugins", pluginName);
9931
+ const targetDir = join34(options.cwd, relTargetDir);
9932
+ const modulesDir = join34(options.cwd, "modules", "plugins", pluginName);
9820
9933
  const inTreeSource = options.local !== void 0 && resolve12(options.local) === resolve12(targetDir);
9821
- if (existsSync31(targetDir) && !inTreeSource) {
9934
+ if (existsSync32(targetDir) && !inTreeSource) {
9822
9935
  throw new Error(
9823
9936
  `Plugin '${pluginName}' is already installed at ${relTargetDir}/. Remove it first, or wait for a future 'biffo plugin upgrade' command.`
9824
9937
  );
@@ -9850,7 +9963,7 @@ async function runPluginInstall(target, options, deps) {
9850
9963
  log.success(
9851
9964
  `Manifest valid \u2014 ${manifest.tables.length} table(s), ${manifest.api_routes.length} route(s)`
9852
9965
  );
9853
- const retiredShapeReasons = findRetiredFrontendShape(join33(source.sourceDir, "terraform"));
9966
+ const retiredShapeReasons = findRetiredFrontendShape(join34(source.sourceDir, "terraform"));
9854
9967
  if (retiredShapeReasons.length > 0) {
9855
9968
  throw new Error(retiredFrontendShapeError(pluginName, retiredShapeReasons));
9856
9969
  }
@@ -9862,6 +9975,9 @@ async function runPluginInstall(target, options, deps) {
9862
9975
  if (configSupply.missingRequired.length > 0) {
9863
9976
  throw new Error(missingRequiredConfigMessage(pluginName, configSupply.missingRequired));
9864
9977
  }
9978
+ if (manifest.user_frontend) {
9979
+ assertPluginRegistryReady(options.cwd, pluginName);
9980
+ }
9865
9981
  if (inTreeSource) {
9866
9982
  log.info(`${relTargetDir}/ is already in this checkout \u2014 installing in place.`);
9867
9983
  } else {
@@ -9874,8 +9990,8 @@ async function runPluginInstall(target, options, deps) {
9874
9990
  writePluginProvenance(targetDir, reconcileProvenance(previousProvenance, nextProvenance));
9875
9991
  applyWorkspaceSources(targetDir, options.cwd, relTargetDir);
9876
9992
  const stagePaths = [relTargetDir];
9877
- const tfSourceDir = join33(targetDir, "terraform");
9878
- if (existsSync31(tfSourceDir)) {
9993
+ const tfSourceDir = join34(targetDir, "terraform");
9994
+ if (existsSync32(tfSourceDir)) {
9879
9995
  mkdirSync11(modulesDir, { recursive: true });
9880
9996
  cpSync5(tfSourceDir, modulesDir, { recursive: true });
9881
9997
  stagePaths.push(`modules/plugins/${pluginName}`);
@@ -9921,8 +10037,8 @@ async function runPluginInstall(target, options, deps) {
9921
10037
  stagePaths.push(seedResult.stagedPath);
9922
10038
  }
9923
10039
  if (manifest.config.length > 0) {
9924
- const configFilePath = join33(targetDir, "biffo.plugin-config.json");
9925
- writeFileSync12(
10040
+ const configFilePath = join34(targetDir, "biffo.plugin-config.json");
10041
+ writeFileSync13(
9926
10042
  configFilePath,
9927
10043
  JSON.stringify(
9928
10044
  {
@@ -9945,6 +10061,15 @@ async function runPluginInstall(target, options, deps) {
9945
10061
  `Recorded ${configSupply.resolved.length}/${manifest.config.length} declared config value(s) at ${relative4(options.cwd, configFilePath)}`
9946
10062
  );
9947
10063
  }
10064
+ if (manifest.user_frontend) {
10065
+ upsertPluginRegistryEntry(options.cwd, {
10066
+ slug: pluginName,
10067
+ title: titleFromSlug(pluginName),
10068
+ frontendUrl: frontendUrlForSlug(pluginName)
10069
+ });
10070
+ stagePaths.push(PLUGIN_REGISTRY_RELATIVE_PATH);
10071
+ log.success(`Registered ${pluginName} in ${PLUGIN_REGISTRY_RELATIVE_PATH}`);
10072
+ }
9948
10073
  const commitMessage = `feat(plugins): install ${pluginName}@${source.version}`;
9949
10074
  await deps.git.add(options.cwd, stagePaths);
9950
10075
  await deps.git.commit(options.cwd, commitMessage);
@@ -9979,7 +10104,7 @@ function printConfigWiringInstructions(pluginName, resolved) {
9979
10104
  }
9980
10105
  function parseManifestFile(path) {
9981
10106
  try {
9982
- return JSON.parse(readFileSync23(path, "utf8"));
10107
+ return JSON.parse(readFileSync24(path, "utf8"));
9983
10108
  } catch (err) {
9984
10109
  throw new Error(`Could not parse ${path} as JSON: ${err.message}`);
9985
10110
  }
@@ -10033,8 +10158,8 @@ function printDryRun4(entry, source, relTargetDir, inTreeSource, suppliedConfig
10033
10158
  }
10034
10159
 
10035
10160
  // src/commands/plugin-list.ts
10036
- import { existsSync as existsSync32, readFileSync as readFileSync24 } from "fs";
10037
- import { join as join34, resolve as resolve13 } from "path";
10161
+ import { existsSync as existsSync33, readFileSync as readFileSync25 } from "fs";
10162
+ import { join as join35, resolve as resolve13 } from "path";
10038
10163
  import chalk16 from "chalk";
10039
10164
  import { Command as Command16 } from "commander";
10040
10165
  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) => {
@@ -10047,8 +10172,8 @@ var pluginListCommand = new Command16("list").description("List plugins installe
10047
10172
  }
10048
10173
  });
10049
10174
  async function runPluginList(options) {
10050
- const servicesDir = join34(options.cwd, "services");
10051
- if (!existsSync32(servicesDir)) {
10175
+ const servicesDir = join35(options.cwd, "services");
10176
+ if (!existsSync33(servicesDir)) {
10052
10177
  throw new Error(
10053
10178
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
10054
10179
  );
@@ -10056,7 +10181,7 @@ async function runPluginList(options) {
10056
10181
  const plugins = [];
10057
10182
  for (const location of findInstalledPlugins(options.cwd)) {
10058
10183
  try {
10059
- const manifest = validateManifest(JSON.parse(readFileSync24(location.manifestPath, "utf8")));
10184
+ const manifest = validateManifest(JSON.parse(readFileSync25(location.manifestPath, "utf8")));
10060
10185
  plugins.push({
10061
10186
  name: manifest.name,
10062
10187
  version: manifest.version,
@@ -10097,14 +10222,14 @@ import { resolve as resolve14 } from "path";
10097
10222
  import { Command as Command17 } from "commander";
10098
10223
 
10099
10224
  // src/lib/plugin-staleness.ts
10100
- import { existsSync as existsSync33, readFileSync as readFileSync25, readdirSync as readdirSync14, statSync as statSync8 } from "fs";
10101
- import { join as join35, relative as relative5 } from "path";
10225
+ import { existsSync as existsSync34, readFileSync as readFileSync26, readdirSync as readdirSync14, statSync as statSync8 } from "fs";
10226
+ import { join as join36, relative as relative5 } from "path";
10102
10227
  function discoverVendoredPlugins(servicesDir) {
10103
- if (!existsSync33(servicesDir)) return [];
10104
- return readdirSync14(servicesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith("_") && e.name !== "api").map((e) => e.name).filter((name) => existsSync33(join35(servicesDir, name, "biffo.plugin.json"))).sort();
10228
+ if (!existsSync34(servicesDir)) return [];
10229
+ return readdirSync14(servicesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith("_") && e.name !== "api").map((e) => e.name).filter((name) => existsSync34(join36(servicesDir, name, "biffo.plugin.json"))).sort();
10105
10230
  }
10106
10231
  async function checkPluginStaleness(cwd, deps) {
10107
- const servicesDir = join35(cwd, "services");
10232
+ const servicesDir = join36(cwd, "services");
10108
10233
  const names = discoverVendoredPlugins(servicesDir);
10109
10234
  let registryRepoByName = null;
10110
10235
  const resolveRegistryRepo = async (name) => {
@@ -10120,7 +10245,7 @@ async function checkPluginStaleness(cwd, deps) {
10120
10245
  };
10121
10246
  const results = [];
10122
10247
  for (const name of names) {
10123
- results.push(await checkOnePlugin(join35(servicesDir, name), name, resolveRegistryRepo, deps.git));
10248
+ results.push(await checkOnePlugin(join36(servicesDir, name), name, resolveRegistryRepo, deps.git));
10124
10249
  }
10125
10250
  return results;
10126
10251
  }
@@ -10146,7 +10271,7 @@ async function checkOnePlugin(pluginDir2, name, resolveRegistryRepo, git) {
10146
10271
  if (record?.sha && isFetchableUrl(record.origin)) {
10147
10272
  return checkViaProvenance(name, record, record.origin, git);
10148
10273
  }
10149
- const localOrigin = record && !isFetchableUrl(record.origin) && existsSync33(record.origin) ? record.origin : null;
10274
+ const localOrigin = record && !isFetchableUrl(record.origin) && existsSync34(record.origin) ? record.origin : null;
10150
10275
  if (localOrigin) {
10151
10276
  return checkViaContentDiff(name, pluginDir2, localOrigin, { isLocalDir: true }, git);
10152
10277
  }
@@ -10280,8 +10405,8 @@ async function countDifferingFiles(sourceDir, pluginDir2) {
10280
10405
  differing++;
10281
10406
  continue;
10282
10407
  }
10283
- const a = readFileSync25(join35(sourceDir, relPath));
10284
- const b = readFileSync25(join35(pluginDir2, relPath));
10408
+ const a = readFileSync26(join36(sourceDir, relPath));
10409
+ const b = readFileSync26(join36(pluginDir2, relPath));
10285
10410
  if (!a.equals(b)) differing++;
10286
10411
  }
10287
10412
  return differing;
@@ -10296,11 +10421,11 @@ function vendorFileList(dir) {
10296
10421
  return new Set(walkExcluding(dir, dir, LOCAL_COPY_EXCLUDES));
10297
10422
  }
10298
10423
  function walkExcluding(root, dir, excludes) {
10299
- if (!existsSync33(dir)) return [];
10424
+ if (!existsSync34(dir)) return [];
10300
10425
  const out = [];
10301
10426
  for (const entry of readdirSync14(dir)) {
10302
10427
  if (excludes.has(entry) || entry === ".git") continue;
10303
- const full = join35(dir, entry);
10428
+ const full = join36(dir, entry);
10304
10429
  let stat;
10305
10430
  try {
10306
10431
  stat = statSync8(full);
@@ -10357,8 +10482,8 @@ var pluginStalenessCommand = new Command17("staleness").description(
10357
10482
  });
10358
10483
 
10359
10484
  // src/commands/plugin-sync-migrations.ts
10360
- import { existsSync as existsSync34 } from "fs";
10361
- import { join as join36, relative as relative6, resolve as resolve15 } from "path";
10485
+ import { existsSync as existsSync35 } from "fs";
10486
+ import { join as join37, relative as relative6, resolve as resolve15 } from "path";
10362
10487
  import chalk17 from "chalk";
10363
10488
  import { Command as Command18 } from "commander";
10364
10489
  var pluginSyncMigrationsCommand = new Command18("sync-migrations").description(
@@ -10379,11 +10504,11 @@ var pluginSyncMigrationsCommand = new Command18("sync-migrations").description(
10379
10504
  }
10380
10505
  );
10381
10506
  async function runPluginSyncMigrations(name, options, deps) {
10382
- const servicesDir = join36(options.cwd, "services");
10383
- if (!existsSync34(servicesDir)) {
10507
+ const servicesDir = join37(options.cwd, "services");
10508
+ if (!existsSync35(servicesDir)) {
10384
10509
  throw new Error(`${servicesDir} does not exist \u2014 is ${options.cwd} a Biffo project checkout?`);
10385
10510
  }
10386
- if (name && !existsSync34(join36(servicesDir, name, "biffo.plugin.json"))) {
10511
+ if (name && !existsSync35(join37(servicesDir, name, "biffo.plugin.json"))) {
10387
10512
  throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
10388
10513
  }
10389
10514
  if (options.dryRun) {
@@ -10419,8 +10544,8 @@ async function runPluginSyncMigrations(name, options, deps) {
10419
10544
  }
10420
10545
 
10421
10546
  // src/commands/plugin-uninstall.ts
10422
- import { existsSync as existsSync35, readFileSync as readFileSync26, rmSync as rmSync9 } from "fs";
10423
- import { join as join37, resolve as resolve16 } from "path";
10547
+ import { existsSync as existsSync36, readFileSync as readFileSync27, rmSync as rmSync9 } from "fs";
10548
+ import { join as join38, resolve as resolve16 } from "path";
10424
10549
  import chalk18 from "chalk";
10425
10550
  import { Command as Command19 } from "commander";
10426
10551
  import inquirer6 from "inquirer";
@@ -10452,28 +10577,33 @@ async function runPluginUninstall(name, options, deps) {
10452
10577
  if (!NAME_PATTERN2.test(name)) {
10453
10578
  throw new Error(`Invalid plugin name '${name}'. Expected a lowercase kebab-case slug.`);
10454
10579
  }
10455
- const servicesDir = join37(options.cwd, "services");
10456
- if (!existsSync35(servicesDir)) {
10580
+ const servicesDir = join38(options.cwd, "services");
10581
+ if (!existsSync36(servicesDir)) {
10457
10582
  throw new Error(
10458
10583
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
10459
10584
  );
10460
10585
  }
10461
- const targetDir = join37(servicesDir, name);
10462
- if (!existsSync35(targetDir)) {
10463
- const firstParty = join37(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
10464
- if (existsSync35(firstParty)) {
10586
+ const targetDir = join38(servicesDir, name);
10587
+ if (!existsSync36(targetDir)) {
10588
+ const firstParty = join38(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
10589
+ if (existsSync36(firstParty)) {
10465
10590
  throw new Error(
10466
10591
  `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.`
10467
10592
  );
10468
10593
  }
10469
10594
  throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
10470
10595
  }
10471
- const version = readInstalledVersion(targetDir);
10472
- const modulesDir = join37(options.cwd, "modules", "plugins", name);
10596
+ const installedManifest = readInstalledManifest(targetDir);
10597
+ const version = installedManifest?.version;
10598
+ const modulesDir = join38(options.cwd, "modules", "plugins", name);
10473
10599
  const stagePaths = [`services/${name}`];
10474
- if (existsSync35(modulesDir)) {
10600
+ if (existsSync36(modulesDir)) {
10475
10601
  stagePaths.push(`modules/plugins/${name}`);
10476
10602
  }
10603
+ const hasUserFrontend = installedManifest?.user_frontend !== void 0;
10604
+ if (hasUserFrontend) {
10605
+ stagePaths.push(PLUGIN_REGISTRY_RELATIVE_PATH);
10606
+ }
10477
10607
  if (options.dryRun) {
10478
10608
  printDryRun5(name, version, stagePaths, options.keepData);
10479
10609
  return;
@@ -10491,7 +10621,10 @@ async function runPluginUninstall(name, options, deps) {
10491
10621
  `${options.cwd} is not a git repository \u2014 biffo plugin uninstall must be run from a Biffo project checkout.`
10492
10622
  );
10493
10623
  }
10494
- if (existsSync35(modulesDir)) {
10624
+ if (hasUserFrontend) {
10625
+ assertPluginRegistryReady(options.cwd, name);
10626
+ }
10627
+ if (existsSync36(modulesDir)) {
10495
10628
  const refs = findPluginModuleReferences(options.cwd, name).filter(
10496
10629
  (r) => !r.file.endsWith(`/${GENERATED_TF_FILE}`) && r.file !== GENERATED_TF_FILE
10497
10630
  );
@@ -10506,7 +10639,7 @@ Remove the reference(s) above first, then re-run uninstall.`
10506
10639
  }
10507
10640
  rmSync9(targetDir, { recursive: true, force: true });
10508
10641
  log.success(`Removed services/${name}/`);
10509
- if (existsSync35(modulesDir)) {
10642
+ if (existsSync36(modulesDir)) {
10510
10643
  rmSync9(modulesDir, { recursive: true, force: true });
10511
10644
  log.success(`Removed modules/plugins/${name}/`);
10512
10645
  const wiring = syncPluginTerraform(options.cwd);
@@ -10517,6 +10650,10 @@ Remove the reference(s) above first, then re-run uninstall.`
10517
10650
  );
10518
10651
  }
10519
10652
  }
10653
+ if (hasUserFrontend) {
10654
+ removePluginRegistryEntry(options.cwd, name);
10655
+ log.success(`Removed ${name} from ${PLUGIN_REGISTRY_RELATIVE_PATH}`);
10656
+ }
10520
10657
  const label = version ? `${name}@${version}` : name;
10521
10658
  const commitMessage = `chore(plugins): uninstall ${label}`;
10522
10659
  await deps.git.add(options.cwd, stagePaths);
@@ -10541,17 +10678,17 @@ Remove the reference(s) above first, then re-run uninstall.`
10541
10678
  "Any tables this plugin created remain in the database, and its migration file at services/api/migrations/versions/ is NOT removed (it is a permanent historical record \u2014 see notes). Dropping tables, if desired, requires a manual Alembic migration written against the Core API."
10542
10679
  );
10543
10680
  }
10544
- if (existsSync35(join37(options.cwd, pluginSeedImportDir(name)))) {
10681
+ if (existsSync36(join38(options.cwd, pluginSeedImportDir(name)))) {
10545
10682
  log.warn(
10546
10683
  `${pluginSeedImportDir(name)}/ (this plugin's vendored baseline-row seed, biffo-template#1554) was NOT removed either, for the same reason \u2014 see notes. Delete it by hand if you are certain the rows it applied should go too, but note nothing drops rows already applied to the database; that still needs a manual migration.`
10547
10684
  );
10548
10685
  }
10549
10686
  }
10550
- function readInstalledVersion(targetDir) {
10551
- const manifestPath = join37(targetDir, "biffo.plugin.json");
10552
- if (!existsSync35(manifestPath)) return void 0;
10687
+ function readInstalledManifest(targetDir) {
10688
+ const manifestPath = join38(targetDir, "biffo.plugin.json");
10689
+ if (!existsSync36(manifestPath)) return void 0;
10553
10690
  try {
10554
- return validateManifest(JSON.parse(readFileSync26(manifestPath, "utf8"))).version;
10691
+ return validateManifest(JSON.parse(readFileSync27(manifestPath, "utf8")));
10555
10692
  } catch {
10556
10693
  return void 0;
10557
10694
  }
@@ -10584,8 +10721,8 @@ function printDryRun5(name, version, stagePaths, keepData) {
10584
10721
  }
10585
10722
 
10586
10723
  // src/commands/plugin-upgrade.ts
10587
- import { cpSync as cpSync6, existsSync as existsSync36, mkdirSync as mkdirSync12, readFileSync as readFileSync27, rmSync as rmSync10 } from "fs";
10588
- import { join as join38, relative as relative7, resolve as resolve17 } from "path";
10724
+ import { cpSync as cpSync6, existsSync as existsSync37, mkdirSync as mkdirSync12, readFileSync as readFileSync28, rmSync as rmSync10 } from "fs";
10725
+ import { join as join39, relative as relative7, resolve as resolve17 } from "path";
10589
10726
  import chalk19 from "chalk";
10590
10727
  import { Command as Command20 } from "commander";
10591
10728
  import inquirer7 from "inquirer";
@@ -10632,8 +10769,8 @@ async function runPluginUpgrade(target, options, deps) {
10632
10769
  `Nothing to upgrade. Pass a registry target (e.g. \`biffo plugin upgrade acme-crm@1.1\`) or a local checkout to refresh from (\`biffo plugin upgrade --local ../acme-crm\`).`
10633
10770
  );
10634
10771
  }
10635
- const servicesDir = join38(options.cwd, "services");
10636
- if (!existsSync36(servicesDir)) {
10772
+ const servicesDir = join39(options.cwd, "services");
10773
+ if (!existsSync37(servicesDir)) {
10637
10774
  throw new Error(
10638
10775
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
10639
10776
  );
@@ -10642,13 +10779,13 @@ async function runPluginUpgrade(target, options, deps) {
10642
10779
  return runLocalPluginRefresh(options.local, options, deps);
10643
10780
  }
10644
10781
  const { name, minor } = parsePluginTarget(target);
10645
- const targetDir = join38(servicesDir, name);
10646
- if (!existsSync36(targetDir)) {
10782
+ const targetDir = join39(servicesDir, name);
10783
+ if (!existsSync37(targetDir)) {
10647
10784
  throw new Error(
10648
10785
  `Plugin '${name}' is not installed at services/${name}/. Use 'biffo plugin install ${name}@${minor}' instead.`
10649
10786
  );
10650
10787
  }
10651
- const currentVersion = readInstalledVersion2(targetDir);
10788
+ const currentVersion = readInstalledVersion(targetDir);
10652
10789
  log.info(`Resolving ${name}@${minor} from the plugin registry...`);
10653
10790
  const entry = await deps.registry.resolvePlugin(name, minor);
10654
10791
  log.success(`Resolved ${entry.name}@${entry.version} \u2014 ${entry.repo}`);
@@ -10657,7 +10794,7 @@ async function runPluginUpgrade(target, options, deps) {
10657
10794
  `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.`
10658
10795
  );
10659
10796
  }
10660
- const modulesDir = join38(options.cwd, "modules", "plugins", entry.name);
10797
+ const modulesDir = join39(options.cwd, "modules", "plugins", entry.name);
10661
10798
  if (options.dryRun) {
10662
10799
  printDryRun6(entry, currentVersion);
10663
10800
  return;
@@ -10685,11 +10822,11 @@ async function runPluginUpgrade(target, options, deps) {
10685
10822
  log.success(
10686
10823
  `Manifest valid \u2014 ${manifest.tables.length} table(s), ${manifest.api_routes.length} route(s)`
10687
10824
  );
10688
- const retiredShapeReasons = findRetiredFrontendShape(join38(tmpDir, "terraform"));
10825
+ const retiredShapeReasons = findRetiredFrontendShape(join39(tmpDir, "terraform"));
10689
10826
  if (retiredShapeReasons.length > 0) {
10690
10827
  throw new Error(retiredFrontendShapeError(entry.name, retiredShapeReasons));
10691
10828
  }
10692
- if (!existsSync36(join38(tmpDir, "terraform"))) {
10829
+ if (!existsSync37(join39(tmpDir, "terraform"))) {
10693
10830
  refuseIfModuleStillReferenced(options.cwd, modulesDir, entry.name);
10694
10831
  }
10695
10832
  const previousProvenance = readProvenance(targetDir);
@@ -10706,11 +10843,11 @@ async function runPluginUpgrade(target, options, deps) {
10706
10843
  applyWorkspaceSources(targetDir, options.cwd, `services/${entry.name}`);
10707
10844
  const newPyproject = readPyprojectIfPresent(targetDir);
10708
10845
  const stagePaths = [`services/${entry.name}`];
10709
- if (existsSync36(modulesDir)) {
10846
+ if (existsSync37(modulesDir)) {
10710
10847
  rmSync10(modulesDir, { recursive: true, force: true });
10711
10848
  }
10712
- const tfSourceDir = join38(targetDir, "terraform");
10713
- if (existsSync36(tfSourceDir)) {
10849
+ const tfSourceDir = join39(targetDir, "terraform");
10850
+ if (existsSync37(tfSourceDir)) {
10714
10851
  mkdirSync12(modulesDir, { recursive: true });
10715
10852
  cpSync6(tfSourceDir, modulesDir, { recursive: true });
10716
10853
  stagePaths.push(`modules/plugins/${entry.name}`);
@@ -10771,16 +10908,16 @@ async function runPluginUpgrade(target, options, deps) {
10771
10908
  async function runLocalPluginRefresh(localPath, options, deps) {
10772
10909
  const source = resolveLocalPlugin(localPath);
10773
10910
  log.success(`Resolved ${source.name}@${source.version} from ${source.origin}`);
10774
- const servicesDir = join38(options.cwd, "services");
10775
- const targetDir = join38(servicesDir, source.name);
10776
- if (!existsSync36(targetDir)) {
10911
+ const servicesDir = join39(options.cwd, "services");
10912
+ const targetDir = join39(servicesDir, source.name);
10913
+ if (!existsSync37(targetDir)) {
10777
10914
  throw new Error(
10778
10915
  `Plugin '${source.name}' is not installed at services/${source.name}/. Use 'biffo plugin install --local ${localPath}' instead.`
10779
10916
  );
10780
10917
  }
10781
10918
  const inTreeSource = resolve17(source.sourceDir) === resolve17(targetDir);
10782
- const currentVersion = readInstalledVersion2(targetDir);
10783
- const modulesDir = join38(options.cwd, "modules", "plugins", source.name);
10919
+ const currentVersion = readInstalledVersion(targetDir);
10920
+ const modulesDir = join39(options.cwd, "modules", "plugins", source.name);
10784
10921
  if (options.dryRun) {
10785
10922
  printLocalDryRun(source, currentVersion, inTreeSource);
10786
10923
  return;
@@ -10803,11 +10940,11 @@ async function runLocalPluginRefresh(localPath, options, deps) {
10803
10940
  log.success(
10804
10941
  `Manifest valid \u2014 ${manifest.tables.length} table(s), ${manifest.api_routes.length} route(s)`
10805
10942
  );
10806
- const retiredShapeReasons = findRetiredFrontendShape(join38(source.sourceDir, "terraform"));
10943
+ const retiredShapeReasons = findRetiredFrontendShape(join39(source.sourceDir, "terraform"));
10807
10944
  if (retiredShapeReasons.length > 0) {
10808
10945
  throw new Error(retiredFrontendShapeError(source.name, retiredShapeReasons));
10809
10946
  }
10810
- if (!existsSync36(join38(source.sourceDir, "terraform"))) {
10947
+ if (!existsSync37(join39(source.sourceDir, "terraform"))) {
10811
10948
  refuseIfModuleStillReferenced(options.cwd, modulesDir, source.name);
10812
10949
  }
10813
10950
  const previousProvenance = readProvenance(targetDir);
@@ -10827,11 +10964,11 @@ async function runLocalPluginRefresh(localPath, options, deps) {
10827
10964
  applyWorkspaceSources(targetDir, options.cwd, `services/${source.name}`);
10828
10965
  const newPyproject = readPyprojectIfPresent(targetDir);
10829
10966
  const stagePaths = [`services/${source.name}`];
10830
- if (existsSync36(modulesDir)) {
10967
+ if (existsSync37(modulesDir)) {
10831
10968
  rmSync10(modulesDir, { recursive: true, force: true });
10832
10969
  }
10833
- const tfSourceDir = join38(targetDir, "terraform");
10834
- if (existsSync36(tfSourceDir)) {
10970
+ const tfSourceDir = join39(targetDir, "terraform");
10971
+ if (existsSync37(tfSourceDir)) {
10835
10972
  mkdirSync12(modulesDir, { recursive: true });
10836
10973
  cpSync6(tfSourceDir, modulesDir, { recursive: true });
10837
10974
  stagePaths.push(`modules/plugins/${source.name}`);
@@ -10895,7 +11032,7 @@ async function runLocalPluginRefresh(localPath, options, deps) {
10895
11032
  }
10896
11033
  }
10897
11034
  function refuseIfModuleStillReferenced(cwd, modulesDir, name) {
10898
- if (!existsSync36(modulesDir)) return;
11035
+ if (!existsSync37(modulesDir)) return;
10899
11036
  const refs = findPluginModuleReferences(cwd, name);
10900
11037
  if (refs.length === 0) return;
10901
11038
  const refList = refs.map((r) => ` ${r.file}:${r.line} ${r.text}`).join("\n");
@@ -10956,8 +11093,8 @@ var defaultRunCommand2 = async (command, cwd) => {
10956
11093
  }
10957
11094
  };
10958
11095
  function readPyprojectIfPresent(targetDir) {
10959
- const path = join38(targetDir, "pyproject.toml");
10960
- return existsSync36(path) ? readFileSync27(path, "utf8") : null;
11096
+ const path = join39(targetDir, "pyproject.toml");
11097
+ return existsSync37(path) ? readFileSync28(path, "utf8") : null;
10961
11098
  }
10962
11099
  function dependenciesChanged(before, after) {
10963
11100
  if (before === after) return false;
@@ -10984,11 +11121,11 @@ function tomlTableBody(text, header) {
10984
11121
  const nextHeader = /^\[/m.exec(rest);
10985
11122
  return nextHeader ? rest.slice(0, nextHeader.index) : rest;
10986
11123
  }
10987
- function readInstalledVersion2(targetDir) {
10988
- const manifestPath = join38(targetDir, "biffo.plugin.json");
10989
- if (!existsSync36(manifestPath)) return void 0;
11124
+ function readInstalledVersion(targetDir) {
11125
+ const manifestPath = join39(targetDir, "biffo.plugin.json");
11126
+ if (!existsSync37(manifestPath)) return void 0;
10990
11127
  try {
10991
- return validateManifest(JSON.parse(readFileSync27(manifestPath, "utf8"))).version;
11128
+ return validateManifest(JSON.parse(readFileSync28(manifestPath, "utf8"))).version;
10992
11129
  } catch {
10993
11130
  return void 0;
10994
11131
  }
@@ -11071,7 +11208,7 @@ pluginCommand.addCommand(pluginStalenessCommand);
11071
11208
  import { Command as Command23 } from "commander";
11072
11209
 
11073
11210
  // src/commands/sibling-check-identity.ts
11074
- import { existsSync as existsSync37, readFileSync as readFileSync28 } from "fs";
11211
+ import { existsSync as existsSync38, readFileSync as readFileSync29 } from "fs";
11075
11212
  import { resolve as resolve18 } from "path";
11076
11213
  import chalk20 from "chalk";
11077
11214
  import { Command as Command22 } from "commander";
@@ -11265,7 +11402,7 @@ async function fetchPublishedIdentity(portalUrl) {
11265
11402
  }
11266
11403
  async function resolveConfig4(options) {
11267
11404
  if (options.config) {
11268
- const raw = JSON.parse(readFileSync28(resolve18(options.config), "utf8"));
11405
+ const raw = JSON.parse(readFileSync29(resolve18(options.config), "utf8"));
11269
11406
  const result = BiffoConfigSchema.safeParse(raw);
11270
11407
  if (!result.success) {
11271
11408
  log.error(`Invalid config at ${options.config}:`);
@@ -11285,8 +11422,8 @@ async function resolveConfig4(options) {
11285
11422
  return cfg;
11286
11423
  }
11287
11424
  const localConfigPath = resolve18(process.cwd(), "biffo.config.json");
11288
- if (existsSync37(localConfigPath)) {
11289
- const raw = JSON.parse(readFileSync28(localConfigPath, "utf8"));
11425
+ if (existsSync38(localConfigPath)) {
11426
+ const raw = JSON.parse(readFileSync29(localConfigPath, "utf8"));
11290
11427
  const result = BiffoConfigSchema.safeParse(raw);
11291
11428
  if (result.success) return result.data;
11292
11429
  if (isTemplatePlaceholderConfig(raw)) {
@@ -11332,20 +11469,20 @@ siblingCommand.addCommand(siblingCheckIdentityCommand);
11332
11469
  import { Command as Command24 } from "commander";
11333
11470
 
11334
11471
  // src/scripts/check-adr-numbering.ts
11335
- import { existsSync as existsSync39 } from "fs";
11336
- import { join as join40 } from "path";
11472
+ import { existsSync as existsSync40 } from "fs";
11473
+ import { join as join41 } from "path";
11337
11474
 
11338
11475
  // src/lib/adr-numbering-guard.ts
11339
- import { existsSync as existsSync38, readdirSync as readdirSync15, readFileSync as readFileSync29 } from "fs";
11340
- import { join as join39 } from "path";
11476
+ import { existsSync as existsSync39, readdirSync as readdirSync15, readFileSync as readFileSync30 } from "fs";
11477
+ import { join as join40 } from "path";
11341
11478
  var ADR_FILENAME = /^(\d{4})-.+\.md$/;
11342
11479
  var ALLOWLIST_FILENAME = ".numbering-allowlist";
11343
11480
  var TEMPLATE_ADR_RESERVED_UPTO = "0099";
11344
11481
  function readAdrNumberingAllowlist(adrDir) {
11345
- const path = join39(adrDir, ALLOWLIST_FILENAME);
11346
- if (!existsSync38(path)) return /* @__PURE__ */ new Set();
11482
+ const path = join40(adrDir, ALLOWLIST_FILENAME);
11483
+ if (!existsSync39(path)) return /* @__PURE__ */ new Set();
11347
11484
  const numbers = /* @__PURE__ */ new Set();
11348
- for (const rawLine of readFileSync29(path, "utf8").split("\n")) {
11485
+ for (const rawLine of readFileSync30(path, "utf8").split("\n")) {
11349
11486
  const line = rawLine.split("#")[0].trim();
11350
11487
  if (line) numbers.add(line);
11351
11488
  }
@@ -11353,7 +11490,7 @@ function readAdrNumberingAllowlist(adrDir) {
11353
11490
  }
11354
11491
  function adrNumbersIn(adrDir) {
11355
11492
  const claims = /* @__PURE__ */ new Map();
11356
- if (!existsSync38(adrDir)) return claims;
11493
+ if (!existsSync39(adrDir)) return claims;
11357
11494
  for (const entry of readdirSync15(adrDir).sort()) {
11358
11495
  const match = ADR_FILENAME.exec(entry);
11359
11496
  if (!match) continue;
@@ -11408,8 +11545,8 @@ function formatAdrReservedRangeViolations(violations, reservedUpTo = TEMPLATE_AD
11408
11545
  // src/scripts/check-adr-numbering.ts
11409
11546
  async function runAdrNumberingCheck() {
11410
11547
  const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
11411
- const adrDir = join40(root, "docs", "ADR");
11412
- if (!existsSync39(adrDir)) {
11548
+ const adrDir = join41(root, "docs", "ADR");
11549
+ if (!existsSync40(adrDir)) {
11413
11550
  console.log("\u2713 ADR numbering guard: no docs/ADR/ directory \u2014 nothing to compare");
11414
11551
  return;
11415
11552
  }
@@ -11447,8 +11584,8 @@ Already accepted? List it in docs/ADR/${ALLOWLIST_FILENAME} instead of leaving t
11447
11584
  }
11448
11585
 
11449
11586
  // src/lib/api-gateway-integration-guard.ts
11450
- import { readFileSync as readFileSync30, readdirSync as readdirSync16, statSync as statSync9 } from "fs";
11451
- import { join as join41 } from "path";
11587
+ import { readFileSync as readFileSync31, readdirSync as readdirSync16, statSync as statSync9 } from "fs";
11588
+ import { join as join42 } from "path";
11452
11589
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".terraform", ".worktrees", "dist"]);
11453
11590
  var MODULE_TYPE = "module";
11454
11591
  var INTEGRATION_TYPE = "aws_apigatewayv2_integration";
@@ -11530,7 +11667,7 @@ function walkTerraformFiles(root) {
11530
11667
  return;
11531
11668
  }
11532
11669
  for (const entry of entries) {
11533
- const p = join41(dir, entry);
11670
+ const p = join42(dir, entry);
11534
11671
  let st;
11535
11672
  try {
11536
11673
  st = statSync9(p);
@@ -11574,7 +11711,7 @@ function auditApiGatewayIntegrations(root) {
11574
11711
  let rawModuleCount = 0;
11575
11712
  let rawIntegrationCount = 0;
11576
11713
  for (const file of files) {
11577
- const text = readFileSync30(file, "utf8");
11714
+ const text = readFileSync31(file, "utf8");
11578
11715
  rawModuleCount += countRawResourceDeclarations(text, MODULE_TYPE);
11579
11716
  rawIntegrationCount += countRawResourceDeclarations(text, INTEGRATION_TYPE);
11580
11717
  moduleBlocks.push(...findModuleBlocks(text, file));
@@ -11946,18 +12083,18 @@ async function runBranchProtectionCheck(explicitRepo, options = {}) {
11946
12083
  }
11947
12084
 
11948
12085
  // src/lib/claim-invocation-parity.ts
11949
- import { existsSync as existsSync40, readFileSync as readFileSync31, readdirSync as readdirSync17 } from "fs";
11950
- import { join as join42 } from "path";
12086
+ import { existsSync as existsSync41, readFileSync as readFileSync32, readdirSync as readdirSync17 } from "fs";
12087
+ import { join as join43 } from "path";
11951
12088
  function distributedAgentsDocs(root) {
11952
12089
  const docs = [];
11953
- const own = join42(root, "AGENTS.md");
11954
- if (existsSync40(own)) docs.push({ path: "AGENTS.md", text: readFileSync31(own, "utf8") });
11955
- const skeletons = join42(root, "_skeletons");
11956
- if (existsSync40(skeletons)) {
12090
+ const own = join43(root, "AGENTS.md");
12091
+ if (existsSync41(own)) docs.push({ path: "AGENTS.md", text: readFileSync32(own, "utf8") });
12092
+ const skeletons = join43(root, "_skeletons");
12093
+ if (existsSync41(skeletons)) {
11957
12094
  for (const name of readdirSync17(skeletons).sort()) {
11958
- const abs = join42(skeletons, name, "AGENTS.md");
11959
- if (!existsSync40(abs)) continue;
11960
- docs.push({ path: `_skeletons/${name}/AGENTS.md`, text: readFileSync31(abs, "utf8") });
12095
+ const abs = join43(skeletons, name, "AGENTS.md");
12096
+ if (!existsSync41(abs)) continue;
12097
+ docs.push({ path: `_skeletons/${name}/AGENTS.md`, text: readFileSync32(abs, "utf8") });
11961
12098
  }
11962
12099
  }
11963
12100
  return docs;
@@ -12089,12 +12226,12 @@ async function runClaimInvocationCheck() {
12089
12226
  }
12090
12227
 
12091
12228
  // src/scripts/check-codeql-suppression.ts
12092
- import { existsSync as existsSync41 } from "fs";
12093
- import { join as join44, relative as relative8 } from "path";
12229
+ import { existsSync as existsSync42 } from "fs";
12230
+ import { join as join45, relative as relative8 } from "path";
12094
12231
 
12095
12232
  // src/lib/codeql-suppression-guard.ts
12096
- import { readdirSync as readdirSync18, readFileSync as readFileSync32, statSync as statSync10 } from "fs";
12097
- import { join as join43 } from "path";
12233
+ import { readdirSync as readdirSync18, readFileSync as readFileSync33, statSync as statSync10 } from "fs";
12234
+ import { join as join44 } from "path";
12098
12235
  var SKIP_DIRS2 = /* @__PURE__ */ new Set([
12099
12236
  ".git",
12100
12237
  ".worktrees",
@@ -12125,7 +12262,7 @@ function walkSourceFiles(root) {
12125
12262
  return;
12126
12263
  }
12127
12264
  for (const entry of entries) {
12128
- const p = join43(dir, entry);
12265
+ const p = join44(dir, entry);
12129
12266
  let st;
12130
12267
  try {
12131
12268
  st = statSync10(p);
@@ -12151,7 +12288,7 @@ function countSourceFiles(root) {
12151
12288
  function sweepCodeqlSuppressionComments(root) {
12152
12289
  const hits = [];
12153
12290
  for (const path of walkSourceFiles(root)) {
12154
- const text = readFileSync32(path, "utf8");
12291
+ const text = readFileSync33(path, "utf8");
12155
12292
  for (const line of findCodeqlSuppressionComments(text)) {
12156
12293
  hits.push({ path, line, text: text.split("\n")[line - 1] ?? "" });
12157
12294
  }
@@ -12162,8 +12299,8 @@ function sweepCodeqlSuppressionComments(root) {
12162
12299
  // src/scripts/check-codeql-suppression.ts
12163
12300
  async function runCodeqlSuppressionCheck() {
12164
12301
  const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
12165
- const scanRoot = join44(root, "cli", "src");
12166
- if (!existsSync41(scanRoot)) {
12302
+ const scanRoot = join45(root, "cli", "src");
12303
+ if (!existsSync42(scanRoot)) {
12167
12304
  console.log(
12168
12305
  "\u2014 codeql-suppression guard: skipped \u2014 no cli/src in this repo, so there is no CLI source to scan."
12169
12306
  );
@@ -12187,12 +12324,12 @@ async function runCodeqlSuppressionCheck() {
12187
12324
  }
12188
12325
 
12189
12326
  // src/scripts/check-cognito-invite-template.ts
12190
- import { existsSync as existsSync42 } from "fs";
12191
- import { join as join46 } from "path";
12327
+ import { existsSync as existsSync43 } from "fs";
12328
+ import { join as join47 } from "path";
12192
12329
 
12193
12330
  // src/lib/cognito-invite-template-guard.ts
12194
- import { readdirSync as readdirSync19, readFileSync as readFileSync33, statSync as statSync11 } from "fs";
12195
- import { join as join45 } from "path";
12331
+ import { readdirSync as readdirSync19, readFileSync as readFileSync34, statSync as statSync11 } from "fs";
12332
+ import { join as join46 } from "path";
12196
12333
  var REQUIRED_INVITE_MEMBERS = ["email_subject", "email_message", "sms_message"];
12197
12334
  var REQUIRED_INVITE_PLACEHOLDERS = ["{username}", "{####}"];
12198
12335
  var PLACEHOLDER_MEMBERS = ["email_message", "sms_message"];
@@ -12276,7 +12413,7 @@ function findModuleTerraformFiles(repoRoot) {
12276
12413
  for (const entry of entries) {
12277
12414
  if (entry === "node_modules" || entry === ".git" || entry === ".worktrees" || entry === ".venv")
12278
12415
  continue;
12279
- const full = join45(dir, entry);
12416
+ const full = join46(dir, entry);
12280
12417
  const rel = `${relative11}/${entry}`;
12281
12418
  let isDir;
12282
12419
  try {
@@ -12291,12 +12428,12 @@ function findModuleTerraformFiles(repoRoot) {
12291
12428
  }
12292
12429
  }
12293
12430
  };
12294
- walk2(join45(repoRoot, "modules"), "modules");
12431
+ walk2(join46(repoRoot, "modules"), "modules");
12295
12432
  return found.sort();
12296
12433
  }
12297
12434
  function checkCognitoInviteTemplates(repoRoot) {
12298
12435
  return findModuleTerraformFiles(repoRoot).flatMap(
12299
- (file) => checkInviteTemplateSource(file, readFileSync33(join45(repoRoot, file), "utf8"))
12436
+ (file) => checkInviteTemplateSource(file, readFileSync34(join46(repoRoot, file), "utf8"))
12300
12437
  );
12301
12438
  }
12302
12439
 
@@ -12306,7 +12443,7 @@ async function runCognitoInviteTemplateCheck() {
12306
12443
  const files = findModuleTerraformFiles(root);
12307
12444
  console.log(`audited ${files.length} .tf file(s) under modules/ under ${root}`);
12308
12445
  if (files.length === 0) {
12309
- if (!existsSync42(join46(root, "modules"))) {
12446
+ if (!existsSync43(join47(root, "modules"))) {
12310
12447
  console.log(
12311
12448
  `\xB7 Cognito invite template guard: not applicable \u2014 no modules/ directory under ${root} \u2014 this is not a template/instance tree (a satellite repo never carries the template-owned Terraform modules). Skipping.`
12312
12449
  );
@@ -12330,11 +12467,11 @@ async function runCognitoInviteTemplateCheck() {
12330
12467
  }
12331
12468
 
12332
12469
  // src/scripts/check-core-direct-paths.ts
12333
- import { join as join48 } from "path";
12470
+ import { join as join49 } from "path";
12334
12471
 
12335
12472
  // src/lib/core-direct-paths-audit.ts
12336
- import { existsSync as existsSync43, readFileSync as readFileSync34, readdirSync as readdirSync20, statSync as statSync12 } from "fs";
12337
- import { join as join47 } from "path";
12473
+ import { existsSync as existsSync44, readFileSync as readFileSync35, readdirSync as readdirSync20, statSync as statSync12 } from "fs";
12474
+ import { join as join48 } from "path";
12338
12475
  var EXTERNAL_BASE_IDENTIFIERS = ["CORE_API_URL"];
12339
12476
  var API_ROUTE_PREFIX = "/api/v1";
12340
12477
  var TEST_FILE_SUFFIXES = [".test.ts", ".test.tsx", ".spec.ts", ".spec.tsx"];
@@ -12498,7 +12635,7 @@ function walkFiles(root, accept, skipDir) {
12498
12635
  return;
12499
12636
  }
12500
12637
  for (const entry of entries) {
12501
- const p = join47(dir, entry);
12638
+ const p = join48(dir, entry);
12502
12639
  let st;
12503
12640
  try {
12504
12641
  st = statSync12(p);
@@ -12528,7 +12665,7 @@ function auditFrontendExtraction(frontendSrcDir, externalBases = EXTERNAL_BASE_I
12528
12665
  const extracted = [];
12529
12666
  let rawTotal = 0;
12530
12667
  for (const file of files) {
12531
- const text = readFileSync34(file, "utf8");
12668
+ const text = readFileSync35(file, "utf8");
12532
12669
  rawTotal += countRawExternalOccurrences(text, externalBases);
12533
12670
  extracted.push(...extractCoreDirectPaths(text, file, externalBases));
12534
12671
  }
@@ -12577,7 +12714,7 @@ function auditCoreRouteExtraction(apiSrcDir) {
12577
12714
  const prefixSet = /* @__PURE__ */ new Set();
12578
12715
  let rawApiRouterCount = 0;
12579
12716
  for (const file of files) {
12580
- const text = readFileSync34(file, "utf8");
12717
+ const text = readFileSync35(file, "utf8");
12581
12718
  const extraction = extractCoreRoutePrefixes(text);
12582
12719
  rawApiRouterCount += extraction.rawApiRouterCount;
12583
12720
  for (const p of extraction.prefixes) prefixSet.add(normalizePrefix(p));
@@ -12593,10 +12730,10 @@ function pathMatchesAnyCorePrefix(normalized, corePrefixes, apiRoutePrefix = API
12593
12730
  }
12594
12731
  function resolveSiblingCoreSrc(params) {
12595
12732
  const { estateDir, sibling } = params;
12596
- const configPath = join47(estateDir, sibling, "biffo.sibling.json");
12733
+ const configPath = join48(estateDir, sibling, "biffo.sibling.json");
12597
12734
  let raw;
12598
12735
  try {
12599
- raw = readFileSync34(configPath, "utf8");
12736
+ raw = readFileSync35(configPath, "utf8");
12600
12737
  } catch (err) {
12601
12738
  throw new Error(
12602
12739
  `cannot resolve ${sibling}'s core: ${configPath} does not exist or is unreadable (${err.message}) -- refusing to guess which core serves this sibling.`
@@ -12616,8 +12753,8 @@ function resolveSiblingCoreSrc(params) {
12616
12753
  `cannot resolve ${sibling}'s core: ${configPath} has no non-empty "core_project" field.`
12617
12754
  );
12618
12755
  }
12619
- const coreApiSrcDir = join47(estateDir, coreProject, "services", "api", "src");
12620
- if (!existsSync43(coreApiSrcDir)) {
12756
+ const coreApiSrcDir = join48(estateDir, coreProject, "services", "api", "src");
12757
+ if (!existsSync44(coreApiSrcDir)) {
12621
12758
  throw new Error(
12622
12759
  `cannot resolve ${sibling}'s core: biffo.sibling.json names core_project "${coreProject}", but ${coreApiSrcDir} does not exist -- the instance is missing from this estate checkout, not merely unmatched. Refusing to silently skip ${sibling} and shrink the audit's denominator.`
12623
12760
  );
@@ -12670,7 +12807,7 @@ async function runCoreDirectPathsCheck(opts = {}) {
12670
12807
  }
12671
12808
  }
12672
12809
  const sibling = opts.sibling ?? "sibling-template (self-check)";
12673
- const frontendSrcDir = opts.frontendSrc ?? join48(root, "_skeletons", "sibling-template", "apps", "frontend", "src");
12810
+ const frontendSrcDir = opts.frontendSrc ?? join49(root, "_skeletons", "sibling-template", "apps", "frontend", "src");
12674
12811
  let coreApiSrcDir;
12675
12812
  let coreProject = null;
12676
12813
  if (opts.coreSrc) {
@@ -12686,7 +12823,7 @@ async function runCoreDirectPathsCheck(opts = {}) {
12686
12823
  coreApiSrcDir = resolution.coreApiSrcDir;
12687
12824
  coreProject = resolution.coreProject;
12688
12825
  } else {
12689
- coreApiSrcDir = join48(root, "services", "api", "src");
12826
+ coreApiSrcDir = join49(root, "services", "api", "src");
12690
12827
  }
12691
12828
  const report = auditSiblingCoreDirectPaths({ sibling, frontendSrcDir, coreApiSrcDir });
12692
12829
  console.log(
@@ -12813,8 +12950,8 @@ async function runOwnershipCheck(argv) {
12813
12950
  const { stdout } = await execa("git", ["diff", "--cached", "--name-status"], { cwd: root });
12814
12951
  ({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
12815
12952
  if (messageFile) {
12816
- const { readFileSync: readFileSync48, existsSync: existsSync58 } = await import("fs");
12817
- if (existsSync58(messageFile)) commitMessage = readFileSync48(messageFile, "utf8");
12953
+ const { readFileSync: readFileSync49, existsSync: existsSync59 } = await import("fs");
12954
+ if (existsSync59(messageFile)) commitMessage = readFileSync49(messageFile, "utf8");
12818
12955
  }
12819
12956
  } else {
12820
12957
  const base = process.env["GITHUB_BASE_REF"] ?? args[0];
@@ -12937,21 +13074,21 @@ ${BOLD}If the divergence is deliberate${OFF}
12937
13074
  }
12938
13075
 
12939
13076
  // src/scripts/check-distribution-inventory.ts
12940
- import { existsSync as existsSync45 } from "fs";
12941
- import { join as join50 } from "path";
13077
+ import { existsSync as existsSync46 } from "fs";
13078
+ import { join as join51 } from "path";
12942
13079
 
12943
13080
  // src/lib/distribution-inventory.ts
12944
- import { existsSync as existsSync44, readFileSync as readFileSync35 } from "fs";
12945
- import { join as join49 } from "path";
13081
+ import { existsSync as existsSync45, readFileSync as readFileSync36 } from "fs";
13082
+ import { join as join50 } from "path";
12946
13083
  var INVENTORY_FILENAME = "distribution-inventory.json";
12947
13084
  function loadDistributionInventory(root) {
12948
- const path = join49(root, INVENTORY_FILENAME);
12949
- if (!existsSync44(path)) {
13085
+ const path = join50(root, INVENTORY_FILENAME);
13086
+ if (!existsSync45(path)) {
12950
13087
  throw new Error(
12951
13088
  `${INVENTORY_FILENAME} not found at ${root} -- expected it beside core-manifest.json`
12952
13089
  );
12953
13090
  }
12954
- return JSON.parse(readFileSync35(path, "utf8"));
13091
+ return JSON.parse(readFileSync36(path, "utf8"));
12955
13092
  }
12956
13093
  function validateInventory(inventory) {
12957
13094
  const violations = [];
@@ -13111,7 +13248,7 @@ async function runDistributionInventoryCheck(root) {
13111
13248
  );
13112
13249
  return;
13113
13250
  }
13114
- if (!existsSync45(join50(repoRoot, INVENTORY_FILENAME))) {
13251
+ if (!existsSync46(join51(repoRoot, INVENTORY_FILENAME))) {
13115
13252
  console.log(
13116
13253
  `\u2713 distribution-inventory: skipped \u2014 no ${INVENTORY_FILENAME} at ${repoRoot}. This is a template-only registry; a checkout that legitimately has none has nothing for this guard to check.`
13117
13254
  );
@@ -13137,7 +13274,7 @@ async function runDistributionInventoryCheck(root) {
13137
13274
  }
13138
13275
 
13139
13276
  // src/scripts/check-distribution-remote-state.ts
13140
- import { join as join51 } from "path";
13277
+ import { join as join52 } from "path";
13141
13278
  async function ghExecCommand(file, args) {
13142
13279
  const result = await execa(file, args, { reject: false });
13143
13280
  return { stdout: String(result.stdout ?? ""), exitCode: result.exitCode ?? null };
@@ -13152,7 +13289,7 @@ async function runDistributionRemoteStateCheck(root) {
13152
13289
  }
13153
13290
  }
13154
13291
  console.log(
13155
- `distribution-remote-state: examined ${assertions.length} remote content assertion(s) declared across ${inventory.entries.length} inventory entries (${join51(repoRoot, "distribution-inventory.json")})`
13292
+ `distribution-remote-state: examined ${assertions.length} remote content assertion(s) declared across ${inventory.entries.length} inventory entries (${join52(repoRoot, "distribution-inventory.json")})`
13156
13293
  );
13157
13294
  if (assertions.length === 0) {
13158
13295
  console.log("\u2713 distribution-remote-state: nothing declared to check");
@@ -13197,8 +13334,8 @@ ${assertion.ref}`;
13197
13334
  }
13198
13335
 
13199
13336
  // src/lib/eventbridge-log-permission-guard.ts
13200
- import { readFileSync as readFileSync36, readdirSync as readdirSync21, statSync as statSync13 } from "fs";
13201
- import { join as join52 } from "path";
13337
+ import { readFileSync as readFileSync37, readdirSync as readdirSync21, statSync as statSync13 } from "fs";
13338
+ import { join as join53 } from "path";
13202
13339
  var SKIP_DIRS3 = /* @__PURE__ */ new Set(["node_modules", ".git", ".terraform", ".worktrees", "dist"]);
13203
13340
  var EVENT_TARGET_TYPE = "aws_cloudwatch_event_target";
13204
13341
  var LOG_RESOURCE_POLICY_TYPE = "aws_cloudwatch_log_resource_policy";
@@ -13275,7 +13412,7 @@ function walkTerraformFiles2(root) {
13275
13412
  return;
13276
13413
  }
13277
13414
  for (const entry of entries) {
13278
- const p = join52(dir, entry);
13415
+ const p = join53(dir, entry);
13279
13416
  let st;
13280
13417
  try {
13281
13418
  st = statSync13(p);
@@ -13318,7 +13455,7 @@ function auditEventBridgeLogPermissions(root) {
13318
13455
  let rawEventTargetCount = 0;
13319
13456
  let rawLogPolicyCount = 0;
13320
13457
  for (const file of files) {
13321
- const text = readFileSync36(file, "utf8");
13458
+ const text = readFileSync37(file, "utf8");
13322
13459
  rawEventTargetCount += countRawResourceDeclarations2(text, EVENT_TARGET_TYPE);
13323
13460
  rawLogPolicyCount += countRawResourceDeclarations2(text, LOG_RESOURCE_POLICY_TYPE);
13324
13461
  eventTargetBlocks.push(...findResourceBlocks2(text, file, EVENT_TARGET_TYPE));
@@ -13415,8 +13552,8 @@ async function runEventBridgeLogPermissionCheck() {
13415
13552
  }
13416
13553
 
13417
13554
  // src/scripts/check-instance-adoption.ts
13418
- import { existsSync as existsSync46 } from "fs";
13419
- import { join as join53 } from "path";
13555
+ import { existsSync as existsSync47 } from "fs";
13556
+ import { join as join54 } from "path";
13420
13557
  async function runInstanceAdoptionCheck(opts = {}) {
13421
13558
  if (!opts.instanceDir) {
13422
13559
  console.error(
@@ -13424,7 +13561,7 @@ async function runInstanceAdoptionCheck(opts = {}) {
13424
13561
  );
13425
13562
  process.exit(2);
13426
13563
  }
13427
- if (!existsSync46(opts.instanceDir)) {
13564
+ if (!existsSync47(opts.instanceDir)) {
13428
13565
  console.error(
13429
13566
  `\u2717 instance-adoption guard: --instance-dir ${opts.instanceDir} does not exist \u2014 cannot tell whether it is adopted, and that is not the same as a clean pass.`
13430
13567
  );
@@ -13432,7 +13569,7 @@ async function runInstanceAdoptionCheck(opts = {}) {
13432
13569
  }
13433
13570
  const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
13434
13571
  const theirsDir = opts.theirsDir ?? root;
13435
- const instanceLabel = opts.instance ?? join53(opts.instanceDir).split("/").filter(Boolean).pop();
13572
+ const instanceLabel = opts.instance ?? join54(opts.instanceDir).split("/").filter(Boolean).pop();
13436
13573
  const report = checkInstanceAdoption(theirsDir, opts.instanceDir);
13437
13574
  console.log(
13438
13575
  `examined ${report.examinedInstances} instance (${instanceLabel}) against ${report.registeredPairs} registered pair(s), ${report.applicablePairs} applicable to this instance, against template tree ${theirsDir}`
@@ -13454,12 +13591,12 @@ async function runInstanceAdoptionCheck(opts = {}) {
13454
13591
  }
13455
13592
 
13456
13593
  // src/lib/lambda-output-guard.ts
13457
- import { readFileSync as readFileSync38 } from "fs";
13458
- import { join as join55 } from "path";
13594
+ import { readFileSync as readFileSync39 } from "fs";
13595
+ import { join as join56 } from "path";
13459
13596
 
13460
13597
  // src/lib/terraform-input-guard.ts
13461
- import { existsSync as existsSync47, readdirSync as readdirSync22, readFileSync as readFileSync37, statSync as statSync14 } from "fs";
13462
- import { join as join54 } from "path";
13598
+ import { existsSync as existsSync48, readdirSync as readdirSync22, readFileSync as readFileSync38, statSync as statSync14 } from "fs";
13599
+ import { join as join55 } from "path";
13463
13600
  var GUARDED_SUBCOMMANDS = [
13464
13601
  "init",
13465
13602
  "plan",
@@ -13473,7 +13610,7 @@ function stripComments2(source) {
13473
13610
  return source.split("\n").map((line) => line.replace(/(^|\s)#.*$/, "$1")).join("\n");
13474
13611
  }
13475
13612
  function vendoredPluginServiceDirs(repoRoot) {
13476
- const servicesDir = join54(repoRoot, "services");
13613
+ const servicesDir = join55(repoRoot, "services");
13477
13614
  const result = /* @__PURE__ */ new Set();
13478
13615
  let entries;
13479
13616
  try {
@@ -13482,9 +13619,9 @@ function vendoredPluginServiceDirs(repoRoot) {
13482
13619
  return result;
13483
13620
  }
13484
13621
  for (const entry of entries) {
13485
- const full = join54(servicesDir, entry);
13486
- if (!existsSync47(full) || !statSync14(full).isDirectory()) continue;
13487
- if (existsSync47(join54(full, "biffo.plugin.json"))) {
13622
+ const full = join55(servicesDir, entry);
13623
+ if (!existsSync48(full) || !statSync14(full).isDirectory()) continue;
13624
+ if (existsSync48(join55(full, "biffo.plugin.json"))) {
13488
13625
  result.add(entry);
13489
13626
  }
13490
13627
  }
@@ -13506,7 +13643,7 @@ function findWorkflowFiles(repoRoot) {
13506
13643
  if (entry === ".github" && relative11.startsWith("services/") && vendoredPluginDirs.has(relative11.slice("services/".length))) {
13507
13644
  continue;
13508
13645
  }
13509
- const full = join54(dir, entry);
13646
+ const full = join55(dir, entry);
13510
13647
  const rel = relative11 ? `${relative11}/${entry}` : entry;
13511
13648
  let isDir;
13512
13649
  try {
@@ -13556,7 +13693,7 @@ function checkWorkflowSource(file, rawSource) {
13556
13693
  }
13557
13694
  function checkTerraformInput(repoRoot) {
13558
13695
  return findWorkflowFiles(repoRoot).flatMap(
13559
- (file) => checkWorkflowSource(file, readFileSync37(join54(repoRoot, file), "utf8"))
13696
+ (file) => checkWorkflowSource(file, readFileSync38(join55(repoRoot, file), "utf8"))
13560
13697
  );
13561
13698
  }
13562
13699
 
@@ -13614,7 +13751,7 @@ function checkWorkflowSource2(file, rawSource) {
13614
13751
  }
13615
13752
  function checkLambdaOutput(repoRoot) {
13616
13753
  return findWorkflowFiles(repoRoot).flatMap(
13617
- (file) => checkWorkflowSource2(file, readFileSync38(join55(repoRoot, file), "utf8"))
13754
+ (file) => checkWorkflowSource2(file, readFileSync39(join56(repoRoot, file), "utf8"))
13618
13755
  );
13619
13756
  }
13620
13757
 
@@ -13642,8 +13779,8 @@ async function runLambdaOutputCheck() {
13642
13779
  }
13643
13780
 
13644
13781
  // src/scripts/check-migration-body-change.ts
13645
- import { existsSync as existsSync48 } from "fs";
13646
- import { join as join56 } from "path";
13782
+ import { existsSync as existsSync49 } from "fs";
13783
+ import { join as join57 } from "path";
13647
13784
 
13648
13785
  // src/lib/migration-body-change-guard.ts
13649
13786
  function checkMigrationBodyChangeMarkers(diffs) {
@@ -13725,7 +13862,7 @@ async function runMigrationBodyChangeCheck(argv) {
13725
13862
  );
13726
13863
  return;
13727
13864
  }
13728
- if (!existsSync48(join56(root, MIGRATIONS_VERSIONS_DIR))) {
13865
+ if (!existsSync49(join57(root, MIGRATIONS_VERSIONS_DIR))) {
13729
13866
  console.log(`\u2713 migration body-change guard: no ${MIGRATIONS_VERSIONS_DIR} in this repo.`);
13730
13867
  return;
13731
13868
  }
@@ -13831,8 +13968,8 @@ ${BOLD2}What to do${OFF2}
13831
13968
  }
13832
13969
 
13833
13970
  // src/scripts/check-orphan-ratchet.ts
13834
- import { existsSync as existsSync49 } from "fs";
13835
- import { basename as basename3, join as join57 } from "path";
13971
+ import { existsSync as existsSync50 } from "fs";
13972
+ import { basename as basename3, join as join58 } from "path";
13836
13973
  function reportOrphan(entry, manifest) {
13837
13974
  console.error(` ${entry.path}`);
13838
13975
  const { templateOwnedMatch, nearestUserOwnedEntries } = explainOwnership(entry.path, manifest);
@@ -13854,7 +13991,7 @@ function reportOrphan(entry, manifest) {
13854
13991
  }
13855
13992
  }
13856
13993
  async function runOrphanRatchetCheck(opts = {}) {
13857
- if (opts.instanceDir !== void 0 && !existsSync49(opts.instanceDir)) {
13994
+ if (opts.instanceDir !== void 0 && !existsSync50(opts.instanceDir)) {
13858
13995
  console.error(
13859
13996
  `\u2717 orphan-ratchet guard: --instance-dir ${opts.instanceDir} does not exist \u2014 cannot tell whether it carries unsanctioned files, and that is not the same as a clean pass.`
13860
13997
  );
@@ -13871,7 +14008,7 @@ async function runOrphanRatchetCheck(opts = {}) {
13871
14008
  "self-check mode: --instance-dir was not given, so it defaulted to --theirs-dir (this repo's own root) alongside --base-dir \u2014 all three trees are the same, so this run can only ever find zero orphans by construction. See this script's doc comment. Pass a real instance tree with --instance-dir for a check that can actually find something. biffo-template#1714."
13872
14009
  );
13873
14010
  }
13874
- if (!existsSync49(join57(theirsDir, CORE_MANIFEST_FILE))) {
14011
+ if (!existsSync50(join58(theirsDir, CORE_MANIFEST_FILE))) {
13875
14012
  console.log(
13876
14013
  `orphan-ratchet guard (${label}): no ${CORE_MANIFEST_FILE} in ${theirsDir} \u2014 this is not a Biffo template/instance tree (a satellite repo never carries one), so there is no ownership manifest to classify paths against. Not applicable; skipping.`
13877
14014
  );
@@ -13902,8 +14039,8 @@ baseline was ${String(ratchet.baseline)}, now ${String(ratchet.count)}. See biff
13902
14039
  }
13903
14040
 
13904
14041
  // src/lib/ownership-header-claim-guard.ts
13905
- import { readFileSync as readFileSync39 } from "fs";
13906
- import { join as join58 } from "path";
14042
+ import { readFileSync as readFileSync40 } from "fs";
14043
+ import { join as join59 } from "path";
13907
14044
  var OWNERSHIP_HEADER_SWEEP_DIRS = [
13908
14045
  "scripts",
13909
14046
  ".githooks",
@@ -14112,7 +14249,7 @@ function sweepOwnershipHeaderClaims(root, options = {}) {
14112
14249
  for (const rel of files) {
14113
14250
  let content;
14114
14251
  try {
14115
- content = readFileSync39(join58(root, rel), "utf8");
14252
+ content = readFileSync40(join59(root, rel), "utf8");
14116
14253
  } catch {
14117
14254
  continue;
14118
14255
  }
@@ -14187,8 +14324,8 @@ async function runOwnershipHeaderClaimCheck() {
14187
14324
  }
14188
14325
 
14189
14326
  // src/scripts/check-pipe-trap.ts
14190
- import { readFileSync as readFileSync40, readdirSync as readdirSync23 } from "fs";
14191
- import { join as join59, relative as relative9 } from "path";
14327
+ import { readFileSync as readFileSync41, readdirSync as readdirSync23 } from "fs";
14328
+ import { join as join60, relative as relative9 } from "path";
14192
14329
 
14193
14330
  // src/lib/pipe-trap-guard.ts
14194
14331
  var STATUS_BEARING = [
@@ -14284,7 +14421,7 @@ function findPipeTraps(source) {
14284
14421
  function shellFiles(root) {
14285
14422
  const out = [];
14286
14423
  for (const dir of ["scripts", ".githooks"]) {
14287
- const full = join59(root, dir);
14424
+ const full = join60(root, dir);
14288
14425
  let entries;
14289
14426
  try {
14290
14427
  entries = readdirSync23(full, { withFileTypes: true });
@@ -14294,7 +14431,7 @@ function shellFiles(root) {
14294
14431
  for (const entry of entries) {
14295
14432
  if (!entry.isFile()) continue;
14296
14433
  if (dir === "scripts" && !entry.name.endsWith(".sh")) continue;
14297
- out.push(join59(full, entry.name));
14434
+ out.push(join60(full, entry.name));
14298
14435
  }
14299
14436
  }
14300
14437
  return out;
@@ -14310,7 +14447,7 @@ async function runPipeTrapCheck() {
14310
14447
  process.exit(1);
14311
14448
  }
14312
14449
  const findings = files.flatMap(
14313
- (file) => findPipeTraps(readFileSync40(file, "utf8")).map(
14450
+ (file) => findPipeTraps(readFileSync41(file, "utf8")).map(
14314
14451
  (t) => `${relative9(root, file)}:${t.line} ${t.text}
14315
14452
  ${t.reason}`
14316
14453
  )
@@ -14327,8 +14464,8 @@ async function runPipeTrapCheck() {
14327
14464
  }
14328
14465
 
14329
14466
  // src/lib/plugin-allowlist-convention.ts
14330
- import { readFileSync as readFileSync41 } from "fs";
14331
- import { join as join60 } from "path";
14467
+ import { readFileSync as readFileSync42 } from "fs";
14468
+ import { join as join61 } from "path";
14332
14469
  var COMPUTE_MAIN_TF = "modules/cloud/aws/compute/main.tf";
14333
14470
  var PLUGIN_TEMPLATE_MAIN_TF = "modules/plugins/_template/main.tf";
14334
14471
  var ALLOWLIST_MAIN_TF = "modules/cloud/aws/plugin-allowlist/main.tf";
@@ -14339,7 +14476,7 @@ var PLUGIN = "<plugin>";
14339
14476
  var ACCOUNT = "<account>";
14340
14477
  function read(repoRoot, relative11) {
14341
14478
  try {
14342
- return readFileSync41(join60(repoRoot, relative11), "utf8");
14479
+ return readFileSync42(join61(repoRoot, relative11), "utf8");
14343
14480
  } catch {
14344
14481
  throw new Error(`plugin-allowlist drift guard: cannot read ${relative11}`);
14345
14482
  }
@@ -14467,32 +14604,32 @@ async function runPluginAllowlistConventionCheck() {
14467
14604
  }
14468
14605
 
14469
14606
  // src/scripts/check-plugin-collisions.ts
14470
- import { existsSync as existsSync51 } from "fs";
14471
- import { join as join62 } from "path";
14607
+ import { existsSync as existsSync52 } from "fs";
14608
+ import { join as join63 } from "path";
14472
14609
 
14473
14610
  // src/lib/plugin-collision-guard.ts
14474
- import { existsSync as existsSync50, readdirSync as readdirSync24, statSync as statSync15 } from "fs";
14475
- import { join as join61 } from "path";
14611
+ import { existsSync as existsSync51, readdirSync as readdirSync24, statSync as statSync15 } from "fs";
14612
+ import { join as join62 } from "path";
14476
14613
  var PYTEST_SPECIAL = /* @__PURE__ */ new Set(["conftest.py"]);
14477
14614
  var IGNORED_DIRS = /* @__PURE__ */ new Set([".venv", "node_modules", "__pycache__", ".git", "dist", "build"]);
14478
14615
  function subdirectories(dir) {
14479
- if (!existsSync50(dir)) return [];
14616
+ if (!existsSync51(dir)) return [];
14480
14617
  return readdirSync24(dir).filter((entry) => {
14481
14618
  if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) return false;
14482
14619
  try {
14483
- return statSync15(join61(dir, entry)).isDirectory();
14620
+ return statSync15(join62(dir, entry)).isDirectory();
14484
14621
  } catch {
14485
14622
  return false;
14486
14623
  }
14487
14624
  });
14488
14625
  }
14489
14626
  function regularPackagesOf(pluginDir2) {
14490
- return subdirectories(pluginDir2).filter((name) => existsSync50(join61(pluginDir2, name, "__init__.py"))).sort();
14627
+ return subdirectories(pluginDir2).filter((name) => existsSync51(join62(pluginDir2, name, "__init__.py"))).sort();
14491
14628
  }
14492
14629
  function bareTestModulesOf(pluginDir2) {
14493
- const testsDir = join61(pluginDir2, "tests");
14494
- if (!existsSync50(testsDir)) return [];
14495
- if (existsSync50(join61(testsDir, "__init__.py"))) return [];
14630
+ const testsDir = join62(pluginDir2, "tests");
14631
+ if (!existsSync51(testsDir)) return [];
14632
+ if (existsSync51(join62(testsDir, "__init__.py"))) return [];
14496
14633
  return readdirSync24(testsDir).filter((f) => f.endsWith(".py") && !PYTEST_SPECIAL.has(f)).sort();
14497
14634
  }
14498
14635
  function findCollisions(servicesDir, pluginDirs) {
@@ -14501,7 +14638,7 @@ function findCollisions(servicesDir, pluginDirs) {
14501
14638
  const gather = (kind, namesOf) => {
14502
14639
  const claims = /* @__PURE__ */ new Map();
14503
14640
  for (const plugin of plugins) {
14504
- for (const name of namesOf(join61(servicesDir, plugin))) {
14641
+ for (const name of namesOf(join62(servicesDir, plugin))) {
14505
14642
  claims.set(name, [...claims.get(name) ?? [], plugin]);
14506
14643
  }
14507
14644
  }
@@ -14539,8 +14676,8 @@ function formatCollisions(collisions) {
14539
14676
  // src/scripts/check-plugin-collisions.ts
14540
14677
  async function runPluginCollisionCheck() {
14541
14678
  const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
14542
- const servicesDir = join62(root, "services");
14543
- if (!existsSync51(servicesDir)) {
14679
+ const servicesDir = join63(root, "services");
14680
+ if (!existsSync52(servicesDir)) {
14544
14681
  console.log("\u2713 plugin collision guard: no services/ directory \u2014 nothing to compare");
14545
14682
  return;
14546
14683
  }
@@ -14557,8 +14694,8 @@ async function runPluginCollisionCheck() {
14557
14694
  }
14558
14695
 
14559
14696
  // src/lib/plugin-terraform-guard.ts
14560
- import { existsSync as existsSync52, readFileSync as readFileSync42, readdirSync as readdirSync25 } from "fs";
14561
- import { dirname as dirname10, join as join63, relative as relative10, sep as sep4 } from "path";
14697
+ import { existsSync as existsSync53, readFileSync as readFileSync43, readdirSync as readdirSync25 } from "fs";
14698
+ import { dirname as dirname10, join as join64, relative as relative10, sep as sep4 } from "path";
14562
14699
  var SKIP_DIRS4 = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
14563
14700
  var PLUGIN_MANIFEST_FILE2 = "biffo.plugin.json";
14564
14701
  function findPluginManifests(root) {
@@ -14573,9 +14710,9 @@ function findPluginManifests(root) {
14573
14710
  for (const entry of entries) {
14574
14711
  if (entry.isDirectory()) {
14575
14712
  if (SKIP_DIRS4.has(entry.name)) continue;
14576
- walk2(join63(dir, entry.name));
14713
+ walk2(join64(dir, entry.name));
14577
14714
  } else if (entry.isFile() && entry.name === PLUGIN_MANIFEST_FILE2) {
14578
- found.push(relative10(root, join63(dir, entry.name)).split(sep4).join("/"));
14715
+ found.push(relative10(root, join64(dir, entry.name)).split(sep4).join("/"));
14579
14716
  }
14580
14717
  }
14581
14718
  };
@@ -14585,7 +14722,7 @@ function findPluginManifests(root) {
14585
14722
  function readSubscriptions(absManifestPath) {
14586
14723
  let parsed;
14587
14724
  try {
14588
- parsed = JSON.parse(readFileSync42(absManifestPath, "utf8"));
14725
+ parsed = JSON.parse(readFileSync43(absManifestPath, "utf8"));
14589
14726
  } catch {
14590
14727
  return null;
14591
14728
  }
@@ -14600,14 +14737,14 @@ function readSubscriptions(absManifestPath) {
14600
14737
  }
14601
14738
  function checkPluginTerraform(root) {
14602
14739
  const violations = [];
14603
- const coreManifest = existsSync52(join63(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
14740
+ const coreManifest = existsSync53(join64(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
14604
14741
  for (const manifest of findPluginManifests(root)) {
14605
14742
  if (coreManifest && !isTemplateOwned(manifest, coreManifest)) continue;
14606
- const absManifest = join63(root, manifest);
14743
+ const absManifest = join64(root, manifest);
14607
14744
  const subscriptions = readSubscriptions(absManifest);
14608
14745
  if (subscriptions === null) continue;
14609
14746
  const pluginDir2 = dirname10(absManifest);
14610
- if (existsSync52(join63(pluginDir2, "terraform"))) continue;
14747
+ if (existsSync53(join64(pluginDir2, "terraform"))) continue;
14611
14748
  const relPluginDir = relative10(root, pluginDir2).split(sep4).join("/");
14612
14749
  violations.push({
14613
14750
  manifest,
@@ -14638,12 +14775,12 @@ async function runPluginTerraformCheck() {
14638
14775
  }
14639
14776
 
14640
14777
  // src/scripts/check-plugin-tool-supply.ts
14641
- import { existsSync as existsSync54 } from "fs";
14642
- import { join as join65 } from "path";
14778
+ import { existsSync as existsSync55 } from "fs";
14779
+ import { join as join66 } from "path";
14643
14780
 
14644
14781
  // src/lib/plugin-tool-supply-audit.ts
14645
- import { existsSync as existsSync53, readFileSync as readFileSync43, readdirSync as readdirSync26, statSync as statSync16 } from "fs";
14646
- import { join as join64 } from "path";
14782
+ import { existsSync as existsSync54, readFileSync as readFileSync44, readdirSync as readdirSync26, statSync as statSync16 } from "fs";
14783
+ import { join as join65 } from "path";
14647
14784
 
14648
14785
  // src/lib/openrouter-model-snapshot.ts
14649
14786
  var OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT = "2026-08-10T06:39:01Z";
@@ -15060,7 +15197,7 @@ function listDirs(root) {
15060
15197
  }
15061
15198
  return entries.filter((e) => {
15062
15199
  try {
15063
- return statSync16(join64(root, e)).isDirectory();
15200
+ return statSync16(join65(root, e)).isDirectory();
15064
15201
  } catch {
15065
15202
  return false;
15066
15203
  }
@@ -15076,7 +15213,7 @@ function walkFiles2(root, accept, skipDir) {
15076
15213
  return;
15077
15214
  }
15078
15215
  for (const entry of entries) {
15079
- const p = join64(dir, entry);
15216
+ const p = join65(dir, entry);
15080
15217
  let st;
15081
15218
  try {
15082
15219
  st = statSync16(p);
@@ -15102,14 +15239,14 @@ function pluginPythonFiles(pluginDir2) {
15102
15239
  );
15103
15240
  }
15104
15241
  function pluginTerraformFiles(pluginDir2) {
15105
- const tfDir = join64(pluginDir2, "terraform");
15242
+ const tfDir = join65(pluginDir2, "terraform");
15106
15243
  let entries;
15107
15244
  try {
15108
15245
  entries = readdirSync26(tfDir);
15109
15246
  } catch {
15110
15247
  return [];
15111
15248
  }
15112
- return entries.filter((e) => e.endsWith(".tf")).map((e) => join64(tfDir, e)).sort();
15249
+ return entries.filter((e) => e.endsWith(".tf")).map((e) => join65(tfDir, e)).sort();
15113
15250
  }
15114
15251
  function extractManifestTools(manifestText) {
15115
15252
  let parsed;
@@ -15361,8 +15498,8 @@ function isSnapshotStale(fetchedAt, now) {
15361
15498
  function normalizeModelId(id) {
15362
15499
  return id.endsWith(":online") ? id.slice(0, -":online".length) : id;
15363
15500
  }
15364
- var CONFIG_PY_PATH = join64("services", "api", "src", "api", "config.py");
15365
- var ORCHESTRATION_SCHEMA_PATH = join64(
15501
+ var CONFIG_PY_PATH = join65("services", "api", "src", "api", "config.py");
15502
+ var ORCHESTRATION_SCHEMA_PATH = join65(
15366
15503
  "services",
15367
15504
  "api",
15368
15505
  "src",
@@ -15374,10 +15511,10 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
15374
15511
  const knownModelIds = options.knownModelIds ?? OPENROUTER_MODEL_IDS;
15375
15512
  const snapshotFetchedAt = options.snapshotFetchedAt ?? OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT;
15376
15513
  const now = options.now ?? /* @__PURE__ */ new Date();
15377
- const configPath = join64(repoRoot, CONFIG_PY_PATH);
15378
- const orchestrationPath = join64(repoRoot, ORCHESTRATION_SCHEMA_PATH);
15379
- const configMissing = !existsSync53(configPath);
15380
- const orchestrationSchemaMissing = !existsSync53(orchestrationPath);
15514
+ const configPath = join65(repoRoot, CONFIG_PY_PATH);
15515
+ const orchestrationPath = join65(repoRoot, ORCHESTRATION_SCHEMA_PATH);
15516
+ const configMissing = !existsSync54(configPath);
15517
+ const orchestrationSchemaMissing = !existsSync54(orchestrationPath);
15381
15518
  const knownSet = new Set(knownModelIds);
15382
15519
  const snapshotEmpty = knownModelIds.length === 0;
15383
15520
  const snapshotStale = isSnapshotStale(snapshotFetchedAt, now);
@@ -15395,13 +15532,13 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
15395
15532
  };
15396
15533
  let settingsBlind = false;
15397
15534
  if (!configMissing) {
15398
- const settingsFields = extractSettingsModelFields(readFileSync43(configPath, "utf8"));
15535
+ const settingsFields = extractSettingsModelFields(readFileSync44(configPath, "utf8"));
15399
15536
  if (settingsFields.length === 0) settingsBlind = true;
15400
15537
  for (const { field, value } of settingsFields) record(`${CONFIG_PY_PATH}#${field}`, value);
15401
15538
  }
15402
15539
  let curatedFieldsBlind = false;
15403
15540
  if (!orchestrationSchemaMissing) {
15404
- const curated = extractCuratedModelFields(readFileSync43(orchestrationPath, "utf8"));
15541
+ const curated = extractCuratedModelFields(readFileSync44(orchestrationPath, "utf8"));
15405
15542
  if (curated.rawFieldCount > 0 && curated.fields.every((f) => f.defaultValue === null && f.optionValues.length === 0)) {
15406
15543
  curatedFieldsBlind = true;
15407
15544
  }
@@ -15448,7 +15585,7 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
15448
15585
  function discoverPluginDirs(pluginsRoot) {
15449
15586
  return listDirs(pluginsRoot).filter((name) => {
15450
15587
  try {
15451
- return statSync16(join64(pluginsRoot, name, "biffo.plugin.json")).isFile();
15588
+ return statSync16(join65(pluginsRoot, name, "biffo.plugin.json")).isFile();
15452
15589
  } catch {
15453
15590
  return false;
15454
15591
  }
@@ -15461,8 +15598,8 @@ function auditPluginToolSupply(pluginsRoot) {
15461
15598
  let terraformBlind = false;
15462
15599
  let totalDeclaredTools = 0;
15463
15600
  for (const name of pluginNames) {
15464
- const pluginDir2 = join64(pluginsRoot, name);
15465
- const manifestText = readFileSync43(join64(pluginDir2, "biffo.plugin.json"), "utf8");
15601
+ const pluginDir2 = join65(pluginsRoot, name);
15602
+ const manifestText = readFileSync44(join65(pluginDir2, "biffo.plugin.json"), "utf8");
15466
15603
  const manifest = extractManifestTools(manifestText);
15467
15604
  if (manifest.parseError) {
15468
15605
  findings.push({
@@ -15480,13 +15617,13 @@ function auditPluginToolSupply(pluginsRoot) {
15480
15617
  totalDeclaredTools += manifest.tools.length;
15481
15618
  const pySources = pluginPythonFiles(pluginDir2).map((f) => ({
15482
15619
  file: f,
15483
- text: readFileSync43(f, "utf8")
15620
+ text: readFileSync44(f, "utf8")
15484
15621
  }));
15485
15622
  const resolver = buildSymbolResolver(pySources);
15486
15623
  const registry = extractToolRegistryEntries(pySources, resolver);
15487
15624
  if (registry.rawToolDefinitionCount > 0 && registry.entries.length === 0) registryBlind = true;
15488
15625
  const tfFiles = pluginTerraformFiles(pluginDir2);
15489
- const tfText = tfFiles.map((f) => readFileSync43(f, "utf8")).join("\n");
15626
+ const tfText = tfFiles.map((f) => readFileSync44(f, "utf8")).join("\n");
15490
15627
  const terraform = extractTerraformEnvKeys(tfText);
15491
15628
  if (terraform.rawMarkerCount > 0 && terraform.resolvedBlockCount === 0) terraformBlind = true;
15492
15629
  for (const toolName of manifest.tools) {
@@ -15560,7 +15697,7 @@ function auditPluginToolSupply(pluginsRoot) {
15560
15697
  requiredEnvVars: envResult.envVars,
15561
15698
  missingEnvVars: anyWired ? [] : envResult.envVars,
15562
15699
  status: anyWired ? "ok" : "missing-env",
15563
- detail: anyWired ? `${entry.predicate}() is satisfiable: at least one of ${JSON.stringify(envResult.envVars)} is wired in Terraform` : `${entry.predicate}() reads ${JSON.stringify(envResult.envVars)} \u2014 NONE of these are wired by any environment_variables block under ${join64(pluginDir2, "terraform")}, so this deployment can never supply it`
15700
+ detail: anyWired ? `${entry.predicate}() is satisfiable: at least one of ${JSON.stringify(envResult.envVars)} is wired in Terraform` : `${entry.predicate}() reads ${JSON.stringify(envResult.envVars)} \u2014 NONE of these are wired by any environment_variables block under ${join65(pluginDir2, "terraform")}, so this deployment can never supply it`
15564
15701
  });
15565
15702
  }
15566
15703
  }
@@ -15600,8 +15737,8 @@ async function runPluginToolSupplyCheck() {
15600
15737
  return;
15601
15738
  }
15602
15739
  let allOk = true;
15603
- const pluginsRoot = join65(root, "services", "_plugins");
15604
- if (!existsSync54(pluginsRoot)) {
15740
+ const pluginsRoot = join66(root, "services", "_plugins");
15741
+ if (!existsSync55(pluginsRoot)) {
15605
15742
  console.log("\u2713 plugin tool-supply guard: no services/_plugins/ \u2014 nothing to audit");
15606
15743
  } else {
15607
15744
  const report = auditPluginToolSupply(pluginsRoot);
@@ -15631,8 +15768,8 @@ async function runPluginToolSupplyCheck() {
15631
15768
  console.log(`\u2713 plugin tool-supply guard: ${report.summary}`);
15632
15769
  }
15633
15770
  }
15634
- const servicesApiRoot = join65(root, "services", "api");
15635
- if (!existsSync54(servicesApiRoot)) {
15771
+ const servicesApiRoot = join66(root, "services", "api");
15772
+ if (!existsSync55(servicesApiRoot)) {
15636
15773
  console.log("\u2713 plugin model-id guard: no services/api/ \u2014 nothing to audit");
15637
15774
  } else {
15638
15775
  const modelReport = auditDeclaredModelIds(root);
@@ -15806,10 +15943,10 @@ async function runReleaseSubjectCheck(argv) {
15806
15943
  }
15807
15944
 
15808
15945
  // src/scripts/check-shared-file-reduction.ts
15809
- import { readFileSync as readFileSync45 } from "fs";
15946
+ import { readFileSync as readFileSync46 } from "fs";
15810
15947
 
15811
15948
  // src/lib/shared-file-reduction-guard.ts
15812
- import { readFileSync as readFileSync44 } from "fs";
15949
+ import { readFileSync as readFileSync45 } from "fs";
15813
15950
  var LEAF_TEST_CALLS = /* @__PURE__ */ new Set(["it", "test"]);
15814
15951
  var SUITE_CALLS = /* @__PURE__ */ new Set(["describe", "suite"]);
15815
15952
  var TEST_FILE_PATTERN = /\.(test|spec)\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/;
@@ -15936,7 +16073,7 @@ function formatReductionReport(report) {
15936
16073
  // src/scripts/check-shared-file-reduction.ts
15937
16074
  function readStdin() {
15938
16075
  try {
15939
- return readFileSync45(0, "utf8");
16076
+ return readFileSync46(0, "utf8");
15940
16077
  } catch {
15941
16078
  return "";
15942
16079
  }
@@ -15953,15 +16090,15 @@ function pairsFromTsv(tsv) {
15953
16090
  const [target, existingPath, incomingPath] = fields;
15954
16091
  pairs.push({
15955
16092
  target,
15956
- existing: readFileSync45(existingPath, "utf8"),
15957
- incoming: readFileSync45(incomingPath, "utf8")
16093
+ existing: readFileSync46(existingPath, "utf8"),
16094
+ incoming: readFileSync46(incomingPath, "utf8")
15958
16095
  });
15959
16096
  }
15960
16097
  return pairs;
15961
16098
  }
15962
16099
  function loadAccepted(manifestPath) {
15963
16100
  if (!manifestPath) return {};
15964
- const parsed = JSON.parse(readFileSync45(manifestPath, "utf8"));
16101
+ const parsed = JSON.parse(readFileSync46(manifestPath, "utf8"));
15965
16102
  return parsed.acceptedReductions ?? {};
15966
16103
  }
15967
16104
  async function runSharedFileReductionCheck(args) {
@@ -15969,13 +16106,13 @@ async function runSharedFileReductionCheck(args) {
15969
16106
  let accepted;
15970
16107
  try {
15971
16108
  if (args.pairs) {
15972
- pairs = pairsFromTsv(args.pairs === "-" ? readStdin() : readFileSync45(args.pairs, "utf8"));
16109
+ pairs = pairsFromTsv(args.pairs === "-" ? readStdin() : readFileSync46(args.pairs, "utf8"));
15973
16110
  } else if (args.target && args.existing && args.incoming) {
15974
16111
  pairs = [
15975
16112
  {
15976
16113
  target: args.target,
15977
- existing: readFileSync45(args.existing, "utf8"),
15978
- incoming: readFileSync45(args.incoming, "utf8")
16114
+ existing: readFileSync46(args.existing, "utf8"),
16115
+ incoming: readFileSync46(args.incoming, "utf8")
15979
16116
  }
15980
16117
  ];
15981
16118
  } else {
@@ -16009,12 +16146,12 @@ async function runSharedFileReductionCheck(args) {
16009
16146
  }
16010
16147
 
16011
16148
  // src/scripts/check-skeleton-drift.ts
16012
- import { existsSync as existsSync55, readdirSync as readdirSync28 } from "fs";
16013
- import { join as join67 } from "path";
16149
+ import { existsSync as existsSync56, readdirSync as readdirSync28 } from "fs";
16150
+ import { join as join68 } from "path";
16014
16151
 
16015
16152
  // src/lib/skeleton-drift-guard.ts
16016
- import { readFileSync as readFileSync46, readdirSync as readdirSync27, statSync as statSync17 } from "fs";
16017
- import { join as join66 } from "path";
16153
+ import { readFileSync as readFileSync47, readdirSync as readdirSync27, statSync as statSync17 } from "fs";
16154
+ import { join as join67 } from "path";
16018
16155
  var isWorkflow = (rel) => rel.startsWith(".github/workflows/") && (rel.endsWith(".yml") || rel.endsWith(".yaml"));
16019
16156
  var isRootLayout = (rel) => rel.endsWith("src/app/layout.tsx");
16020
16157
  var uncommented = (contents) => contents.split("\n").filter((line) => !/^\s*(\/\/|\/\*|\*)/.test(line)).join("\n");
@@ -16078,7 +16215,7 @@ function walk(dir, base = dir) {
16078
16215
  }
16079
16216
  for (const entry of entries) {
16080
16217
  if (entry === ".venv" || entry === "node_modules" || entry === ".git") continue;
16081
- const abs = join66(dir, entry);
16218
+ const abs = join67(dir, entry);
16082
16219
  let isDir;
16083
16220
  try {
16084
16221
  isDir = statSync17(abs).isDirectory();
@@ -16100,7 +16237,7 @@ function auditSkeleton(skeletonRoot, name, rules = SKELETON_RULES) {
16100
16237
  if (!rule.appliesTo(rel)) continue;
16101
16238
  let contents;
16102
16239
  try {
16103
- contents = readFileSync46(join66(skeletonRoot, rel), "utf8");
16240
+ contents = readFileSync47(join67(skeletonRoot, rel), "utf8");
16104
16241
  } catch {
16105
16242
  continue;
16106
16243
  }
@@ -16129,23 +16266,23 @@ function formatViolations2(violations) {
16129
16266
 
16130
16267
  // src/scripts/check-skeleton-drift.ts
16131
16268
  function discoverSkeletons(root) {
16132
- const skeletonsDir = join67(root, "_skeletons");
16269
+ const skeletonsDir = join68(root, "_skeletons");
16133
16270
  let entries;
16134
16271
  try {
16135
16272
  entries = readdirSync28(skeletonsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
16136
16273
  } catch {
16137
16274
  return [];
16138
16275
  }
16139
- return entries.filter((name) => existsSync55(join67(skeletonsDir, name, ".github", "workflows", "ci.yml"))).sort();
16276
+ return entries.filter((name) => existsSync56(join68(skeletonsDir, name, ".github", "workflows", "ci.yml"))).sort();
16140
16277
  }
16141
16278
  async function runSkeletonDriftCheck() {
16142
16279
  const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
16143
16280
  const skeletons = discoverSkeletons(root);
16144
16281
  let filesConsidered = 0;
16145
16282
  for (const name of skeletons) {
16146
- const skeletonRoot = join67(root, "_skeletons", name);
16283
+ const skeletonRoot = join68(root, "_skeletons", name);
16147
16284
  filesConsidered += findWorkflowFiles(skeletonRoot).length;
16148
- if (existsSync55(join67(skeletonRoot, "apps", "frontend", "src", "app", "layout.tsx"))) {
16285
+ if (existsSync56(join68(skeletonRoot, "apps", "frontend", "src", "app", "layout.tsx"))) {
16149
16286
  filesConsidered += 1;
16150
16287
  }
16151
16288
  }
@@ -16153,7 +16290,7 @@ async function runSkeletonDriftCheck() {
16153
16290
  `audited ${skeletons.length} skeleton(s) (${skeletons.join(", ") || "none"}), ${filesConsidered} file(s) considered, under ${root}/_skeletons`
16154
16291
  );
16155
16292
  if (skeletons.length === 0) {
16156
- if (!existsSync55(join67(root, "_skeletons"))) {
16293
+ if (!existsSync56(join68(root, "_skeletons"))) {
16157
16294
  console.log(
16158
16295
  `\xB7 Skeleton-drift guard: no _skeletons/ directory under ${root} \u2014 this is not a repo that ships scaffolding (a satellite repo never carries one). Not applicable; skipping.`
16159
16296
  );
@@ -16165,7 +16302,7 @@ async function runSkeletonDriftCheck() {
16165
16302
  process.exit(1);
16166
16303
  }
16167
16304
  const violations = skeletons.flatMap(
16168
- (name) => auditSkeleton(join67(root, "_skeletons", name), name)
16305
+ (name) => auditSkeleton(join68(root, "_skeletons", name), name)
16169
16306
  );
16170
16307
  if (violations.length > 0) {
16171
16308
  console.error("\u2717 Skeleton-drift guard: drift found between this repo and its scaffolding\n");
@@ -16366,8 +16503,8 @@ function rawArgsAfter(subcommand) {
16366
16503
  }
16367
16504
 
16368
16505
  // src/commands/doctor.ts
16369
- import { existsSync as existsSync56, readFileSync as readFileSync47 } from "fs";
16370
- import { join as join69, resolve as resolve20 } from "path";
16506
+ import { existsSync as existsSync57, readFileSync as readFileSync48 } from "fs";
16507
+ import { join as join70, resolve as resolve20 } from "path";
16371
16508
  import chalk21 from "chalk";
16372
16509
  import { Command as Command25 } from "commander";
16373
16510
 
@@ -16708,11 +16845,11 @@ async function reapAllBareBranches(cwd, branches, worktrees, currentBranch, deps
16708
16845
 
16709
16846
  // src/lib/scratch-clone-scan.ts
16710
16847
  import { readdirSync as readdirSync29, statSync as statSync18 } from "fs";
16711
- import { join as join68 } from "path";
16848
+ import { join as join69 } from "path";
16712
16849
  var INTEGRATION_BRANCH = "dev";
16713
16850
  function isPlainCloneDir(path) {
16714
16851
  try {
16715
- return statSync18(join68(path, ".git")).isDirectory();
16852
+ return statSync18(join69(path, ".git")).isDirectory();
16716
16853
  } catch {
16717
16854
  return false;
16718
16855
  }
@@ -16727,7 +16864,7 @@ async function findScratchCloneCandidates(estateRoot, deps) {
16727
16864
  const names = readdirSync29(estateRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
16728
16865
  const candidates = [];
16729
16866
  for (const name of names) {
16730
- const path = join68(estateRoot, name);
16867
+ const path = join69(estateRoot, name);
16731
16868
  if (!isPlainCloneDir(path)) continue;
16732
16869
  let branch;
16733
16870
  try {
@@ -16907,10 +17044,10 @@ function printScratchCloneReports(estateRoot, reports) {
16907
17044
  );
16908
17045
  }
16909
17046
  function readLocalCoreVersion(cwd) {
16910
- const path = join69(cwd, INSTANCE_CORE_FILE);
16911
- if (!existsSync56(path)) return null;
17047
+ const path = join70(cwd, INSTANCE_CORE_FILE);
17048
+ if (!existsSync57(path)) return null;
16912
17049
  try {
16913
- return extractVersionField(readFileSync47(path, "utf8"));
17050
+ return extractVersionField(readFileSync48(path, "utf8"));
16914
17051
  } catch {
16915
17052
  return null;
16916
17053
  }
@@ -16930,10 +17067,10 @@ function extractVersionField(contents) {
16930
17067
  return match?.[1] ?? null;
16931
17068
  }
16932
17069
  function readFossil(cwd) {
16933
- const path = join69(cwd, CORE_VERSION_FILE);
16934
- if (!existsSync56(path)) return null;
17070
+ const path = join70(cwd, CORE_VERSION_FILE);
17071
+ if (!existsSync57(path)) return null;
16935
17072
  try {
16936
- const value = readFileSync47(path, "utf8").trim();
17073
+ const value = readFileSync48(path, "utf8").trim();
16937
17074
  return value === "" ? null : value;
16938
17075
  } catch {
16939
17076
  return null;
@@ -17471,13 +17608,13 @@ import { fileURLToPath as fileURLToPath6 } from "url";
17471
17608
  import { Command as Command27 } from "commander";
17472
17609
 
17473
17610
  // src/lib/packaged-scripts.ts
17474
- import { existsSync as existsSync57 } from "fs";
17475
- import { dirname as dirname11, join as join70 } from "path";
17611
+ import { existsSync as existsSync58 } from "fs";
17612
+ import { dirname as dirname11, join as join71 } from "path";
17476
17613
  function findPackagedScript(startDir, relativePath) {
17477
17614
  let dir = startDir;
17478
17615
  for (; ; ) {
17479
- const candidate = join70(dir, relativePath);
17480
- if (existsSync57(candidate)) return candidate;
17616
+ const candidate = join71(dir, relativePath);
17617
+ if (existsSync58(candidate)) return candidate;
17481
17618
  const parent = dirname11(dir);
17482
17619
  if (parent === dir) return null;
17483
17620
  dir = parent;