@biffo/cli 0.309.2 → 0.309.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_skeletons/plugin-template/AGENTS.md +12 -4
- package/_skeletons/sibling-template/AGENTS.md +12 -4
- package/dist/index.js +142 -88
- package/package.json +1 -1
- package/scripts/verify.sh +24 -5
|
@@ -170,10 +170,18 @@ because `--auto` waits for ever on a check that cannot re-evaluate itself (a
|
|
|
170
170
|
|
|
171
171
|
## 6. Push honestly, and verify the remote has your commit
|
|
172
172
|
|
|
173
|
-
- **Push with the exit status visible
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
- **Push with the exit status visible, and record the upstream:**
|
|
174
|
+
`git push -u origin HEAD; echo $?`. A pipe (`git push | tail`) reports the
|
|
175
|
+
pipe's status, not git's — a rejected push then reads as success and the
|
|
176
|
+
commit is silently lost. Never trust a "pushed" message printed
|
|
177
|
+
unconditionally after a pipe.
|
|
178
|
+
- **The `-u` is not optional (biffo-template#1954).** `git worktree add -b
|
|
179
|
+
<branch> <path> origin/dev` sets the new branch's upstream to `origin/dev`
|
|
180
|
+
by default — a plain `git push origin HEAD`, with no `-u`, never corrects
|
|
181
|
+
it. A branch left tracking `origin/dev` can be pushed, merged, and have its
|
|
182
|
+
own remote copy deleted, while `git branch -vv` never reports it `[gone]`
|
|
183
|
+
(the ref it is actually tracking never goes anywhere) — so `doctor --fix`
|
|
184
|
+
and every other tool reading that marker stays permanently blind to it.
|
|
177
185
|
- **Confirm the remote actually has the commit before relying on it,** especially
|
|
178
186
|
before merge: `git log origin/<branch> -1`. A green PR page is not proof your
|
|
179
187
|
latest local commit reached it.
|
|
@@ -170,10 +170,18 @@ because `--auto` waits for ever on a check that cannot re-evaluate itself (a
|
|
|
170
170
|
|
|
171
171
|
## 6. Push honestly, and verify the remote has your commit
|
|
172
172
|
|
|
173
|
-
- **Push with the exit status visible
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
- **Push with the exit status visible, and record the upstream:**
|
|
174
|
+
`git push -u origin HEAD; echo $?`. A pipe (`git push | tail`) reports the
|
|
175
|
+
pipe's status, not git's — a rejected push then reads as success and the
|
|
176
|
+
commit is silently lost. Never trust a "pushed" message printed
|
|
177
|
+
unconditionally after a pipe.
|
|
178
|
+
- **The `-u` is not optional (biffo-template#1954).** `git worktree add -b
|
|
179
|
+
<branch> <path> origin/dev` sets the new branch's upstream to `origin/dev`
|
|
180
|
+
by default — a plain `git push origin HEAD`, with no `-u`, never corrects
|
|
181
|
+
it. A branch left tracking `origin/dev` can be pushed, merged, and have its
|
|
182
|
+
own remote copy deleted, while `git branch -vv` never reports it `[gone]`
|
|
183
|
+
(the ref it is actually tracking never goes anywhere) — so `doctor --fix`
|
|
184
|
+
and every other tool reading that marker stays permanently blind to it.
|
|
177
185
|
- **Confirm the remote actually has the commit before relying on it,** especially
|
|
178
186
|
before merge: `git log origin/<branch> -1`. A green PR page is not proof your
|
|
179
187
|
latest local commit reached it.
|
package/dist/index.js
CHANGED
|
@@ -1449,6 +1449,39 @@ var GitAdapter = class {
|
|
|
1449
1449
|
const { stdout, exitCode } = await execa("git", ["rev-parse", "HEAD"], { cwd, reject: false });
|
|
1450
1450
|
return exitCode === 0 ? stdout.trim() : null;
|
|
1451
1451
|
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Every local branch name that currently has a same-named remote-tracking
|
|
1454
|
+
* ref under `refs/remotes/origin/` (biffo-template#1954) — i.e. `git
|
|
1455
|
+
* for-each-ref refs/remotes/origin`, stripped of the `refs/remotes/origin/`
|
|
1456
|
+
* prefix, with the `HEAD` symref excluded (it names no branch).
|
|
1457
|
+
*
|
|
1458
|
+
* This is the fact `doctor --fix`'s reap-candidate detection needs instead
|
|
1459
|
+
* of trusting a branch's own *configured* upstream (`%(upstream:track)`,
|
|
1460
|
+
* i.e. `git branch -vv`'s `[gone]` marker): `git worktree add -b <branch>
|
|
1461
|
+
* <path> origin/dev` sets the new branch's upstream to `origin/dev` by
|
|
1462
|
+
* default, and a plain `git push origin HEAD` (no `-u`) never corrects
|
|
1463
|
+
* it — so a branch can be pushed, merged, and have its OWN remote copy
|
|
1464
|
+
* deleted, while its `[gone]` marker never fires because the thing it is
|
|
1465
|
+
* actually tracking (`origin/dev`) never goes anywhere. Checking for a
|
|
1466
|
+
* same-named ref sidesteps whatever the branch happens to be configured to
|
|
1467
|
+
* track entirely.
|
|
1468
|
+
*
|
|
1469
|
+
* Absence from this set does NOT by itself prove a branch's remote copy
|
|
1470
|
+
* was deleted — a branch never pushed under its own name is equally
|
|
1471
|
+
* absent. See `doctor-reaper.ts`'s `hasProvenGoneRemote` for why that
|
|
1472
|
+
* distinction is safe to skip here (unlike in `upgrade-branch-reaper.ts`,
|
|
1473
|
+
* which has no downstream verification and must not skip it).
|
|
1474
|
+
*/
|
|
1475
|
+
async listRemoteBranchNames(cwd) {
|
|
1476
|
+
const { stdout } = await execa(
|
|
1477
|
+
"git",
|
|
1478
|
+
["for-each-ref", "--format=%(refname:lstrip=3)", "refs/remotes/origin"],
|
|
1479
|
+
{ cwd }
|
|
1480
|
+
);
|
|
1481
|
+
return new Set(
|
|
1482
|
+
stdout.split("\n").map((line) => line.trim()).filter((name) => name !== "" && name !== "HEAD")
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1452
1485
|
/**
|
|
1453
1486
|
* `branch`'s own tip SHA, without checking it out — the bare-branch
|
|
1454
1487
|
* counterpart to `headSha` (#1682 milestone 2). A branch with no linked
|
|
@@ -12470,6 +12503,16 @@ function auditSiblingCoreDirectPaths(params) {
|
|
|
12470
12503
|
// src/scripts/check-core-direct-paths.ts
|
|
12471
12504
|
async function runCoreDirectPathsCheck(opts = {}) {
|
|
12472
12505
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
12506
|
+
const usingSelfCheckDefaults = !opts.sibling && !opts.frontendSrc && !opts.coreSrc;
|
|
12507
|
+
if (usingSelfCheckDefaults) {
|
|
12508
|
+
const ownership = classifyRepoOwnership(root);
|
|
12509
|
+
if (ownership === "satellite") {
|
|
12510
|
+
console.log(
|
|
12511
|
+
"\u2713 core-direct-paths guard: skipped \u2014 the self-check defaults compare this repo's own _skeletons/sibling-template scaffold against its own services/api/src, and a sibling/plugin repo has neither: no _skeletons/ at all, and its own services/api/ is an unrelated BFF, not core's route table. Pass --sibling/--frontend-src plus --estate (or --core-src) to audit a real sibling instead."
|
|
12512
|
+
);
|
|
12513
|
+
return;
|
|
12514
|
+
}
|
|
12515
|
+
}
|
|
12473
12516
|
const sibling = opts.sibling ?? "sibling-template (self-check)";
|
|
12474
12517
|
const frontendSrcDir = opts.frontendSrc ?? join48(root, "_skeletons", "sibling-template", "apps", "frontend", "src");
|
|
12475
12518
|
let coreApiSrcDir;
|
|
@@ -12583,8 +12626,8 @@ async function runOwnershipCheck(argv) {
|
|
|
12583
12626
|
const { stdout } = await execa("git", ["diff", "--cached", "--name-status"], { cwd: root });
|
|
12584
12627
|
({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
|
|
12585
12628
|
if (messageFile) {
|
|
12586
|
-
const { readFileSync: readFileSync48, existsSync:
|
|
12587
|
-
if (
|
|
12629
|
+
const { readFileSync: readFileSync48, existsSync: existsSync58 } = await import("fs");
|
|
12630
|
+
if (existsSync58(messageFile)) commitMessage = readFileSync48(messageFile, "utf8");
|
|
12588
12631
|
}
|
|
12589
12632
|
} else {
|
|
12590
12633
|
const base = process.env["GITHUB_BASE_REF"] ?? args[0];
|
|
@@ -14019,10 +14062,6 @@ async function runPipeTrapCheck() {
|
|
|
14019
14062
|
console.log(`\u2713 Pipe-trap guard: no status-bearing command is piped away`);
|
|
14020
14063
|
}
|
|
14021
14064
|
|
|
14022
|
-
// src/scripts/check-plugin-allowlist-convention.ts
|
|
14023
|
-
import { existsSync as existsSync50 } from "fs";
|
|
14024
|
-
import { join as join61 } from "path";
|
|
14025
|
-
|
|
14026
14065
|
// src/lib/plugin-allowlist-convention.ts
|
|
14027
14066
|
import { readFileSync as readFileSync41 } from "fs";
|
|
14028
14067
|
import { join as join60 } from "path";
|
|
@@ -14131,18 +14170,12 @@ Plugins would be rejected by require_service_principal (ADR-0009). Fix the glob,
|
|
|
14131
14170
|
}
|
|
14132
14171
|
|
|
14133
14172
|
// src/scripts/check-plugin-allowlist-convention.ts
|
|
14134
|
-
var REQUIRED_SOURCES = [
|
|
14135
|
-
COMPUTE_MAIN_TF,
|
|
14136
|
-
PLUGIN_TEMPLATE_MAIN_TF,
|
|
14137
|
-
ALLOWLIST_MAIN_TF,
|
|
14138
|
-
ALLOWLIST_VARIABLES_TF
|
|
14139
|
-
];
|
|
14140
14173
|
async function runPluginAllowlistConventionCheck() {
|
|
14141
14174
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
14142
|
-
const
|
|
14143
|
-
if (
|
|
14175
|
+
const ownership = classifyRepoOwnership(root);
|
|
14176
|
+
if (ownership === "satellite") {
|
|
14144
14177
|
console.log(
|
|
14145
|
-
`\xB7 Plugin-allowlist convention guard: not applicable \u2014
|
|
14178
|
+
`\xB7 Plugin-allowlist convention guard: not applicable \u2014 this is not a template/instance tree (${root}), so there is no modules/plugins/_template or modules/cloud/aws/plugin-allowlist Terraform to audit. A sibling app may carry its own unrelated modules/cloud/aws/compute for its own BFF; that is not this guard's concern. Skipping.`
|
|
14146
14179
|
);
|
|
14147
14180
|
return;
|
|
14148
14181
|
}
|
|
@@ -14170,32 +14203,32 @@ async function runPluginAllowlistConventionCheck() {
|
|
|
14170
14203
|
}
|
|
14171
14204
|
|
|
14172
14205
|
// src/scripts/check-plugin-collisions.ts
|
|
14173
|
-
import { existsSync as
|
|
14174
|
-
import { join as
|
|
14206
|
+
import { existsSync as existsSync51 } from "fs";
|
|
14207
|
+
import { join as join62 } from "path";
|
|
14175
14208
|
|
|
14176
14209
|
// src/lib/plugin-collision-guard.ts
|
|
14177
|
-
import { existsSync as
|
|
14178
|
-
import { join as
|
|
14210
|
+
import { existsSync as existsSync50, readdirSync as readdirSync24, statSync as statSync15 } from "fs";
|
|
14211
|
+
import { join as join61 } from "path";
|
|
14179
14212
|
var PYTEST_SPECIAL = /* @__PURE__ */ new Set(["conftest.py"]);
|
|
14180
14213
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([".venv", "node_modules", "__pycache__", ".git", "dist", "build"]);
|
|
14181
14214
|
function subdirectories(dir) {
|
|
14182
|
-
if (!
|
|
14215
|
+
if (!existsSync50(dir)) return [];
|
|
14183
14216
|
return readdirSync24(dir).filter((entry) => {
|
|
14184
14217
|
if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) return false;
|
|
14185
14218
|
try {
|
|
14186
|
-
return statSync15(
|
|
14219
|
+
return statSync15(join61(dir, entry)).isDirectory();
|
|
14187
14220
|
} catch {
|
|
14188
14221
|
return false;
|
|
14189
14222
|
}
|
|
14190
14223
|
});
|
|
14191
14224
|
}
|
|
14192
14225
|
function regularPackagesOf(pluginDir2) {
|
|
14193
|
-
return subdirectories(pluginDir2).filter((name) =>
|
|
14226
|
+
return subdirectories(pluginDir2).filter((name) => existsSync50(join61(pluginDir2, name, "__init__.py"))).sort();
|
|
14194
14227
|
}
|
|
14195
14228
|
function bareTestModulesOf(pluginDir2) {
|
|
14196
|
-
const testsDir =
|
|
14197
|
-
if (!
|
|
14198
|
-
if (
|
|
14229
|
+
const testsDir = join61(pluginDir2, "tests");
|
|
14230
|
+
if (!existsSync50(testsDir)) return [];
|
|
14231
|
+
if (existsSync50(join61(testsDir, "__init__.py"))) return [];
|
|
14199
14232
|
return readdirSync24(testsDir).filter((f) => f.endsWith(".py") && !PYTEST_SPECIAL.has(f)).sort();
|
|
14200
14233
|
}
|
|
14201
14234
|
function findCollisions(servicesDir, pluginDirs) {
|
|
@@ -14204,7 +14237,7 @@ function findCollisions(servicesDir, pluginDirs) {
|
|
|
14204
14237
|
const gather = (kind, namesOf) => {
|
|
14205
14238
|
const claims = /* @__PURE__ */ new Map();
|
|
14206
14239
|
for (const plugin of plugins) {
|
|
14207
|
-
for (const name of namesOf(
|
|
14240
|
+
for (const name of namesOf(join61(servicesDir, plugin))) {
|
|
14208
14241
|
claims.set(name, [...claims.get(name) ?? [], plugin]);
|
|
14209
14242
|
}
|
|
14210
14243
|
}
|
|
@@ -14242,8 +14275,8 @@ function formatCollisions(collisions) {
|
|
|
14242
14275
|
// src/scripts/check-plugin-collisions.ts
|
|
14243
14276
|
async function runPluginCollisionCheck() {
|
|
14244
14277
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
14245
|
-
const servicesDir =
|
|
14246
|
-
if (!
|
|
14278
|
+
const servicesDir = join62(root, "services");
|
|
14279
|
+
if (!existsSync51(servicesDir)) {
|
|
14247
14280
|
console.log("\u2713 plugin collision guard: no services/ directory \u2014 nothing to compare");
|
|
14248
14281
|
return;
|
|
14249
14282
|
}
|
|
@@ -14260,8 +14293,8 @@ async function runPluginCollisionCheck() {
|
|
|
14260
14293
|
}
|
|
14261
14294
|
|
|
14262
14295
|
// src/lib/plugin-terraform-guard.ts
|
|
14263
|
-
import { existsSync as
|
|
14264
|
-
import { dirname as dirname10, join as
|
|
14296
|
+
import { existsSync as existsSync52, readFileSync as readFileSync42, readdirSync as readdirSync25 } from "fs";
|
|
14297
|
+
import { dirname as dirname10, join as join63, relative as relative10, sep as sep4 } from "path";
|
|
14265
14298
|
var SKIP_DIRS4 = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
|
|
14266
14299
|
var PLUGIN_MANIFEST_FILE2 = "biffo.plugin.json";
|
|
14267
14300
|
function findPluginManifests(root) {
|
|
@@ -14276,9 +14309,9 @@ function findPluginManifests(root) {
|
|
|
14276
14309
|
for (const entry of entries) {
|
|
14277
14310
|
if (entry.isDirectory()) {
|
|
14278
14311
|
if (SKIP_DIRS4.has(entry.name)) continue;
|
|
14279
|
-
walk2(
|
|
14312
|
+
walk2(join63(dir, entry.name));
|
|
14280
14313
|
} else if (entry.isFile() && entry.name === PLUGIN_MANIFEST_FILE2) {
|
|
14281
|
-
found.push(relative10(root,
|
|
14314
|
+
found.push(relative10(root, join63(dir, entry.name)).split(sep4).join("/"));
|
|
14282
14315
|
}
|
|
14283
14316
|
}
|
|
14284
14317
|
};
|
|
@@ -14303,14 +14336,14 @@ function readSubscriptions(absManifestPath) {
|
|
|
14303
14336
|
}
|
|
14304
14337
|
function checkPluginTerraform(root) {
|
|
14305
14338
|
const violations = [];
|
|
14306
|
-
const coreManifest =
|
|
14339
|
+
const coreManifest = existsSync52(join63(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
|
|
14307
14340
|
for (const manifest of findPluginManifests(root)) {
|
|
14308
14341
|
if (coreManifest && !isTemplateOwned(manifest, coreManifest)) continue;
|
|
14309
|
-
const absManifest =
|
|
14342
|
+
const absManifest = join63(root, manifest);
|
|
14310
14343
|
const subscriptions = readSubscriptions(absManifest);
|
|
14311
14344
|
if (subscriptions === null) continue;
|
|
14312
14345
|
const pluginDir2 = dirname10(absManifest);
|
|
14313
|
-
if (
|
|
14346
|
+
if (existsSync52(join63(pluginDir2, "terraform"))) continue;
|
|
14314
14347
|
const relPluginDir = relative10(root, pluginDir2).split(sep4).join("/");
|
|
14315
14348
|
violations.push({
|
|
14316
14349
|
manifest,
|
|
@@ -14341,12 +14374,12 @@ async function runPluginTerraformCheck() {
|
|
|
14341
14374
|
}
|
|
14342
14375
|
|
|
14343
14376
|
// src/scripts/check-plugin-tool-supply.ts
|
|
14344
|
-
import { existsSync as
|
|
14345
|
-
import { join as
|
|
14377
|
+
import { existsSync as existsSync54 } from "fs";
|
|
14378
|
+
import { join as join65 } from "path";
|
|
14346
14379
|
|
|
14347
14380
|
// src/lib/plugin-tool-supply-audit.ts
|
|
14348
|
-
import { existsSync as
|
|
14349
|
-
import { join as
|
|
14381
|
+
import { existsSync as existsSync53, readFileSync as readFileSync43, readdirSync as readdirSync26, statSync as statSync16 } from "fs";
|
|
14382
|
+
import { join as join64 } from "path";
|
|
14350
14383
|
|
|
14351
14384
|
// src/lib/openrouter-model-snapshot.ts
|
|
14352
14385
|
var OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT = "2026-08-10T06:39:01Z";
|
|
@@ -14763,7 +14796,7 @@ function listDirs(root) {
|
|
|
14763
14796
|
}
|
|
14764
14797
|
return entries.filter((e) => {
|
|
14765
14798
|
try {
|
|
14766
|
-
return statSync16(
|
|
14799
|
+
return statSync16(join64(root, e)).isDirectory();
|
|
14767
14800
|
} catch {
|
|
14768
14801
|
return false;
|
|
14769
14802
|
}
|
|
@@ -14779,7 +14812,7 @@ function walkFiles2(root, accept, skipDir) {
|
|
|
14779
14812
|
return;
|
|
14780
14813
|
}
|
|
14781
14814
|
for (const entry of entries) {
|
|
14782
|
-
const p =
|
|
14815
|
+
const p = join64(dir, entry);
|
|
14783
14816
|
let st;
|
|
14784
14817
|
try {
|
|
14785
14818
|
st = statSync16(p);
|
|
@@ -14805,14 +14838,14 @@ function pluginPythonFiles(pluginDir2) {
|
|
|
14805
14838
|
);
|
|
14806
14839
|
}
|
|
14807
14840
|
function pluginTerraformFiles(pluginDir2) {
|
|
14808
|
-
const tfDir =
|
|
14841
|
+
const tfDir = join64(pluginDir2, "terraform");
|
|
14809
14842
|
let entries;
|
|
14810
14843
|
try {
|
|
14811
14844
|
entries = readdirSync26(tfDir);
|
|
14812
14845
|
} catch {
|
|
14813
14846
|
return [];
|
|
14814
14847
|
}
|
|
14815
|
-
return entries.filter((e) => e.endsWith(".tf")).map((e) =>
|
|
14848
|
+
return entries.filter((e) => e.endsWith(".tf")).map((e) => join64(tfDir, e)).sort();
|
|
14816
14849
|
}
|
|
14817
14850
|
function extractManifestTools(manifestText) {
|
|
14818
14851
|
let parsed;
|
|
@@ -15064,8 +15097,8 @@ function isSnapshotStale(fetchedAt, now) {
|
|
|
15064
15097
|
function normalizeModelId(id) {
|
|
15065
15098
|
return id.endsWith(":online") ? id.slice(0, -":online".length) : id;
|
|
15066
15099
|
}
|
|
15067
|
-
var CONFIG_PY_PATH =
|
|
15068
|
-
var ORCHESTRATION_SCHEMA_PATH =
|
|
15100
|
+
var CONFIG_PY_PATH = join64("services", "api", "src", "api", "config.py");
|
|
15101
|
+
var ORCHESTRATION_SCHEMA_PATH = join64(
|
|
15069
15102
|
"services",
|
|
15070
15103
|
"api",
|
|
15071
15104
|
"src",
|
|
@@ -15077,10 +15110,10 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
15077
15110
|
const knownModelIds = options.knownModelIds ?? OPENROUTER_MODEL_IDS;
|
|
15078
15111
|
const snapshotFetchedAt = options.snapshotFetchedAt ?? OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT;
|
|
15079
15112
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
15080
|
-
const configPath =
|
|
15081
|
-
const orchestrationPath =
|
|
15082
|
-
const configMissing = !
|
|
15083
|
-
const orchestrationSchemaMissing = !
|
|
15113
|
+
const configPath = join64(repoRoot, CONFIG_PY_PATH);
|
|
15114
|
+
const orchestrationPath = join64(repoRoot, ORCHESTRATION_SCHEMA_PATH);
|
|
15115
|
+
const configMissing = !existsSync53(configPath);
|
|
15116
|
+
const orchestrationSchemaMissing = !existsSync53(orchestrationPath);
|
|
15084
15117
|
const knownSet = new Set(knownModelIds);
|
|
15085
15118
|
const snapshotEmpty = knownModelIds.length === 0;
|
|
15086
15119
|
const snapshotStale = isSnapshotStale(snapshotFetchedAt, now);
|
|
@@ -15151,7 +15184,7 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
15151
15184
|
function discoverPluginDirs(pluginsRoot) {
|
|
15152
15185
|
return listDirs(pluginsRoot).filter((name) => {
|
|
15153
15186
|
try {
|
|
15154
|
-
return statSync16(
|
|
15187
|
+
return statSync16(join64(pluginsRoot, name, "biffo.plugin.json")).isFile();
|
|
15155
15188
|
} catch {
|
|
15156
15189
|
return false;
|
|
15157
15190
|
}
|
|
@@ -15164,8 +15197,8 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
15164
15197
|
let terraformBlind = false;
|
|
15165
15198
|
let totalDeclaredTools = 0;
|
|
15166
15199
|
for (const name of pluginNames) {
|
|
15167
|
-
const pluginDir2 =
|
|
15168
|
-
const manifestText = readFileSync43(
|
|
15200
|
+
const pluginDir2 = join64(pluginsRoot, name);
|
|
15201
|
+
const manifestText = readFileSync43(join64(pluginDir2, "biffo.plugin.json"), "utf8");
|
|
15169
15202
|
const manifest = extractManifestTools(manifestText);
|
|
15170
15203
|
if (manifest.parseError) {
|
|
15171
15204
|
findings.push({
|
|
@@ -15263,7 +15296,7 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
15263
15296
|
requiredEnvVars: envResult.envVars,
|
|
15264
15297
|
missingEnvVars: anyWired ? [] : envResult.envVars,
|
|
15265
15298
|
status: anyWired ? "ok" : "missing-env",
|
|
15266
|
-
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 ${
|
|
15299
|
+
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`
|
|
15267
15300
|
});
|
|
15268
15301
|
}
|
|
15269
15302
|
}
|
|
@@ -15295,9 +15328,16 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
15295
15328
|
// src/scripts/check-plugin-tool-supply.ts
|
|
15296
15329
|
async function runPluginToolSupplyCheck() {
|
|
15297
15330
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
15331
|
+
const ownership = classifyRepoOwnership(root);
|
|
15332
|
+
if (ownership === "satellite") {
|
|
15333
|
+
console.log(
|
|
15334
|
+
"\u2713 plugin tool-supply guard: skipped \u2014 this repo is not the template or an instance, so it holds no services/_plugins/ tool grants and no services/api/ model-id catalogue to audit (a sibling/plugin repo may carry an unrelated services/api/ of its own \u2014 a BFF, not the ADR-0002 Core API this guard checks)."
|
|
15335
|
+
);
|
|
15336
|
+
return;
|
|
15337
|
+
}
|
|
15298
15338
|
let allOk = true;
|
|
15299
|
-
const pluginsRoot =
|
|
15300
|
-
if (!
|
|
15339
|
+
const pluginsRoot = join65(root, "services", "_plugins");
|
|
15340
|
+
if (!existsSync54(pluginsRoot)) {
|
|
15301
15341
|
console.log("\u2713 plugin tool-supply guard: no services/_plugins/ \u2014 nothing to audit");
|
|
15302
15342
|
} else {
|
|
15303
15343
|
const report = auditPluginToolSupply(pluginsRoot);
|
|
@@ -15327,8 +15367,8 @@ async function runPluginToolSupplyCheck() {
|
|
|
15327
15367
|
console.log(`\u2713 plugin tool-supply guard: ${report.summary}`);
|
|
15328
15368
|
}
|
|
15329
15369
|
}
|
|
15330
|
-
const servicesApiRoot =
|
|
15331
|
-
if (!
|
|
15370
|
+
const servicesApiRoot = join65(root, "services", "api");
|
|
15371
|
+
if (!existsSync54(servicesApiRoot)) {
|
|
15332
15372
|
console.log("\u2713 plugin model-id guard: no services/api/ \u2014 nothing to audit");
|
|
15333
15373
|
} else {
|
|
15334
15374
|
const modelReport = auditDeclaredModelIds(root);
|
|
@@ -15705,12 +15745,12 @@ async function runSharedFileReductionCheck(args) {
|
|
|
15705
15745
|
}
|
|
15706
15746
|
|
|
15707
15747
|
// src/scripts/check-skeleton-drift.ts
|
|
15708
|
-
import { existsSync as
|
|
15709
|
-
import { join as
|
|
15748
|
+
import { existsSync as existsSync55, readdirSync as readdirSync28 } from "fs";
|
|
15749
|
+
import { join as join67 } from "path";
|
|
15710
15750
|
|
|
15711
15751
|
// src/lib/skeleton-drift-guard.ts
|
|
15712
15752
|
import { readFileSync as readFileSync46, readdirSync as readdirSync27, statSync as statSync17 } from "fs";
|
|
15713
|
-
import { join as
|
|
15753
|
+
import { join as join66 } from "path";
|
|
15714
15754
|
var isWorkflow = (rel) => rel.startsWith(".github/workflows/") && (rel.endsWith(".yml") || rel.endsWith(".yaml"));
|
|
15715
15755
|
var isRootLayout = (rel) => rel.endsWith("src/app/layout.tsx");
|
|
15716
15756
|
var uncommented = (contents) => contents.split("\n").filter((line) => !/^\s*(\/\/|\/\*|\*)/.test(line)).join("\n");
|
|
@@ -15774,7 +15814,7 @@ function walk(dir, base = dir) {
|
|
|
15774
15814
|
}
|
|
15775
15815
|
for (const entry of entries) {
|
|
15776
15816
|
if (entry === ".venv" || entry === "node_modules" || entry === ".git") continue;
|
|
15777
|
-
const abs =
|
|
15817
|
+
const abs = join66(dir, entry);
|
|
15778
15818
|
let isDir;
|
|
15779
15819
|
try {
|
|
15780
15820
|
isDir = statSync17(abs).isDirectory();
|
|
@@ -15796,7 +15836,7 @@ function auditSkeleton(skeletonRoot, name, rules = SKELETON_RULES) {
|
|
|
15796
15836
|
if (!rule.appliesTo(rel)) continue;
|
|
15797
15837
|
let contents;
|
|
15798
15838
|
try {
|
|
15799
|
-
contents = readFileSync46(
|
|
15839
|
+
contents = readFileSync46(join66(skeletonRoot, rel), "utf8");
|
|
15800
15840
|
} catch {
|
|
15801
15841
|
continue;
|
|
15802
15842
|
}
|
|
@@ -15825,23 +15865,23 @@ function formatViolations2(violations) {
|
|
|
15825
15865
|
|
|
15826
15866
|
// src/scripts/check-skeleton-drift.ts
|
|
15827
15867
|
function discoverSkeletons(root) {
|
|
15828
|
-
const skeletonsDir =
|
|
15868
|
+
const skeletonsDir = join67(root, "_skeletons");
|
|
15829
15869
|
let entries;
|
|
15830
15870
|
try {
|
|
15831
15871
|
entries = readdirSync28(skeletonsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
15832
15872
|
} catch {
|
|
15833
15873
|
return [];
|
|
15834
15874
|
}
|
|
15835
|
-
return entries.filter((name) =>
|
|
15875
|
+
return entries.filter((name) => existsSync55(join67(skeletonsDir, name, ".github", "workflows", "ci.yml"))).sort();
|
|
15836
15876
|
}
|
|
15837
15877
|
async function runSkeletonDriftCheck() {
|
|
15838
15878
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
15839
15879
|
const skeletons = discoverSkeletons(root);
|
|
15840
15880
|
let filesConsidered = 0;
|
|
15841
15881
|
for (const name of skeletons) {
|
|
15842
|
-
const skeletonRoot =
|
|
15882
|
+
const skeletonRoot = join67(root, "_skeletons", name);
|
|
15843
15883
|
filesConsidered += findWorkflowFiles(skeletonRoot).length;
|
|
15844
|
-
if (
|
|
15884
|
+
if (existsSync55(join67(skeletonRoot, "apps", "frontend", "src", "app", "layout.tsx"))) {
|
|
15845
15885
|
filesConsidered += 1;
|
|
15846
15886
|
}
|
|
15847
15887
|
}
|
|
@@ -15849,7 +15889,7 @@ async function runSkeletonDriftCheck() {
|
|
|
15849
15889
|
`audited ${skeletons.length} skeleton(s) (${skeletons.join(", ") || "none"}), ${filesConsidered} file(s) considered, under ${root}/_skeletons`
|
|
15850
15890
|
);
|
|
15851
15891
|
if (skeletons.length === 0) {
|
|
15852
|
-
if (!
|
|
15892
|
+
if (!existsSync55(join67(root, "_skeletons"))) {
|
|
15853
15893
|
console.log(
|
|
15854
15894
|
`\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.`
|
|
15855
15895
|
);
|
|
@@ -15861,7 +15901,7 @@ async function runSkeletonDriftCheck() {
|
|
|
15861
15901
|
process.exit(1);
|
|
15862
15902
|
}
|
|
15863
15903
|
const violations = skeletons.flatMap(
|
|
15864
|
-
(name) => auditSkeleton(
|
|
15904
|
+
(name) => auditSkeleton(join67(root, "_skeletons", name), name)
|
|
15865
15905
|
);
|
|
15866
15906
|
if (violations.length > 0) {
|
|
15867
15907
|
console.error("\u2717 Skeleton-drift guard: drift found between this repo and its scaffolding\n");
|
|
@@ -16062,8 +16102,8 @@ function rawArgsAfter(subcommand) {
|
|
|
16062
16102
|
}
|
|
16063
16103
|
|
|
16064
16104
|
// src/commands/doctor.ts
|
|
16065
|
-
import { existsSync as
|
|
16066
|
-
import { join as
|
|
16105
|
+
import { existsSync as existsSync56, readFileSync as readFileSync47 } from "fs";
|
|
16106
|
+
import { join as join68, resolve as resolve20 } from "path";
|
|
16067
16107
|
import chalk21 from "chalk";
|
|
16068
16108
|
import { Command as Command25 } from "commander";
|
|
16069
16109
|
|
|
@@ -16300,8 +16340,13 @@ function classifyReapCandidate(facts) {
|
|
|
16300
16340
|
return { action: "keep", reason: "unknown-pr-verdict" };
|
|
16301
16341
|
}
|
|
16302
16342
|
}
|
|
16303
|
-
function
|
|
16304
|
-
|
|
16343
|
+
function hasProvenGoneRemote(branch, remoteBranchNames) {
|
|
16344
|
+
return !remoteBranchNames.has(branch.name);
|
|
16345
|
+
}
|
|
16346
|
+
function findReapCandidates(branches, worktrees, remoteBranchNames) {
|
|
16347
|
+
const goneBranches = new Set(
|
|
16348
|
+
branches.filter((b) => hasProvenGoneRemote(b, remoteBranchNames)).map((b) => b.name)
|
|
16349
|
+
);
|
|
16305
16350
|
return worktrees.filter((w) => goneBranches.has(w.branch)).map((w) => ({ branch: w.branch, worktreePath: w.path }));
|
|
16306
16351
|
}
|
|
16307
16352
|
async function reapCandidate(cwd, candidate, deps) {
|
|
@@ -16345,8 +16390,8 @@ async function reapCandidate(cwd, candidate, deps) {
|
|
|
16345
16390
|
const worktreeRemoved = await git.removeWorktree(cwd, candidate.worktreePath);
|
|
16346
16391
|
return { candidate, verdict, worktreeRemoved, staleClaimCleared };
|
|
16347
16392
|
}
|
|
16348
|
-
async function reapAll(cwd, branches, worktrees, currentBranch, deps) {
|
|
16349
|
-
const candidates = findReapCandidates(branches, worktrees).filter(
|
|
16393
|
+
async function reapAll(cwd, branches, worktrees, currentBranch, deps, remoteBranchNames) {
|
|
16394
|
+
const candidates = findReapCandidates(branches, worktrees, remoteBranchNames).filter(
|
|
16350
16395
|
(c) => c.branch !== currentBranch
|
|
16351
16396
|
);
|
|
16352
16397
|
const outcomes = [];
|
|
@@ -16355,9 +16400,9 @@ async function reapAll(cwd, branches, worktrees, currentBranch, deps) {
|
|
|
16355
16400
|
}
|
|
16356
16401
|
return outcomes;
|
|
16357
16402
|
}
|
|
16358
|
-
function findBareBranchCandidates(branches, worktrees) {
|
|
16403
|
+
function findBareBranchCandidates(branches, worktrees, remoteBranchNames) {
|
|
16359
16404
|
const worktreeBranches = new Set(worktrees.map((w) => w.branch));
|
|
16360
|
-
return branches.filter((b) => b
|
|
16405
|
+
return branches.filter((b) => hasProvenGoneRemote(b, remoteBranchNames) && !worktreeBranches.has(b.name)).map((b) => ({ branch: b.name }));
|
|
16361
16406
|
}
|
|
16362
16407
|
async function reapBareBranch(cwd, candidate, deps) {
|
|
16363
16408
|
const { git, github } = deps;
|
|
@@ -16386,8 +16431,8 @@ async function reapBareBranch(cwd, candidate, deps) {
|
|
|
16386
16431
|
const branchDeleted = await git.deleteBranch(cwd, candidate.branch);
|
|
16387
16432
|
return { candidate, verdict, branchDeleted };
|
|
16388
16433
|
}
|
|
16389
|
-
async function reapAllBareBranches(cwd, branches, worktrees, currentBranch, deps) {
|
|
16390
|
-
const candidates = findBareBranchCandidates(branches, worktrees).filter(
|
|
16434
|
+
async function reapAllBareBranches(cwd, branches, worktrees, currentBranch, deps, remoteBranchNames) {
|
|
16435
|
+
const candidates = findBareBranchCandidates(branches, worktrees, remoteBranchNames).filter(
|
|
16391
16436
|
(c) => c.branch !== currentBranch
|
|
16392
16437
|
);
|
|
16393
16438
|
const outcomes = [];
|
|
@@ -16464,14 +16509,23 @@ async function gatherRepoFacts(options, deps) {
|
|
|
16464
16509
|
};
|
|
16465
16510
|
}
|
|
16466
16511
|
async function runDoctorFix(cwd, facts, deps = { git: new GitAdapter(), github: new GithubCliAdapter() }) {
|
|
16467
|
-
|
|
16512
|
+
const remoteBranchNames = await deps.git.listRemoteBranchNames(cwd);
|
|
16513
|
+
return reapAll(cwd, facts.branches, facts.worktrees, facts.currentBranch, deps, remoteBranchNames);
|
|
16468
16514
|
}
|
|
16469
16515
|
async function runDoctorFixBranches(cwd, facts, deps = { git: new GitAdapter(), github: new GithubCliAdapter() }) {
|
|
16470
|
-
|
|
16516
|
+
const remoteBranchNames = await deps.git.listRemoteBranchNames(cwd);
|
|
16517
|
+
return reapAllBareBranches(
|
|
16518
|
+
cwd,
|
|
16519
|
+
facts.branches,
|
|
16520
|
+
facts.worktrees,
|
|
16521
|
+
facts.currentBranch,
|
|
16522
|
+
deps,
|
|
16523
|
+
remoteBranchNames
|
|
16524
|
+
);
|
|
16471
16525
|
}
|
|
16472
16526
|
function readLocalCoreVersion(cwd) {
|
|
16473
|
-
const path =
|
|
16474
|
-
if (!
|
|
16527
|
+
const path = join68(cwd, INSTANCE_CORE_FILE);
|
|
16528
|
+
if (!existsSync56(path)) return null;
|
|
16475
16529
|
try {
|
|
16476
16530
|
return extractVersionField(readFileSync47(path, "utf8"));
|
|
16477
16531
|
} catch {
|
|
@@ -16493,8 +16547,8 @@ function extractVersionField(contents) {
|
|
|
16493
16547
|
return match?.[1] ?? null;
|
|
16494
16548
|
}
|
|
16495
16549
|
function readFossil(cwd) {
|
|
16496
|
-
const path =
|
|
16497
|
-
if (!
|
|
16550
|
+
const path = join68(cwd, CORE_VERSION_FILE);
|
|
16551
|
+
if (!existsSync56(path)) return null;
|
|
16498
16552
|
try {
|
|
16499
16553
|
const value = readFileSync47(path, "utf8").trim();
|
|
16500
16554
|
return value === "" ? null : value;
|
|
@@ -17033,13 +17087,13 @@ import { fileURLToPath as fileURLToPath6 } from "url";
|
|
|
17033
17087
|
import { Command as Command27 } from "commander";
|
|
17034
17088
|
|
|
17035
17089
|
// src/lib/packaged-scripts.ts
|
|
17036
|
-
import { existsSync as
|
|
17037
|
-
import { dirname as dirname11, join as
|
|
17090
|
+
import { existsSync as existsSync57 } from "fs";
|
|
17091
|
+
import { dirname as dirname11, join as join69 } from "path";
|
|
17038
17092
|
function findPackagedScript(startDir, relativePath) {
|
|
17039
17093
|
let dir = startDir;
|
|
17040
17094
|
for (; ; ) {
|
|
17041
|
-
const candidate =
|
|
17042
|
-
if (
|
|
17095
|
+
const candidate = join69(dir, relativePath);
|
|
17096
|
+
if (existsSync57(candidate)) return candidate;
|
|
17043
17097
|
const parent = dirname11(dir);
|
|
17044
17098
|
if (parent === dir) return null;
|
|
17045
17099
|
dir = parent;
|
package/package.json
CHANGED
package/scripts/verify.sh
CHANGED
|
@@ -1455,11 +1455,30 @@ if [ -f scripts/biffo.sh ]; then
|
|
|
1455
1455
|
# biffo.core.json's own version and compares against it -- but this repo
|
|
1456
1456
|
# never carries biffo.core.json (it is the template, not an instance), so
|
|
1457
1457
|
# the script itself detects that and exits 0 as a genuine skip (see its own
|
|
1458
|
-
# doc comment) rather than attempting a comparison it has no tree for.
|
|
1459
|
-
#
|
|
1460
|
-
#
|
|
1461
|
-
#
|
|
1462
|
-
|
|
1458
|
+
# doc comment) rather than attempting a comparison it has no tree for.
|
|
1459
|
+
#
|
|
1460
|
+
# Guarded with `[ -f ... ] &&`, unlike the line above, because this script
|
|
1461
|
+
# is a raw file path rather than a `scripts/biffo.sh check <name>` call
|
|
1462
|
+
# through the versioned CLI bridge -- every other check in this block goes
|
|
1463
|
+
# through that bridge, which resolves against the pinned, PACKAGED CLI and
|
|
1464
|
+
# so works from any repo. This one does not, and `scripts/
|
|
1465
|
+
# check-orphan-ratchet-instance.sh` is template-owned but never packaged
|
|
1466
|
+
# for a satellite: not in shared-files.json's `files` (never copied to a
|
|
1467
|
+
# sibling/plugin repo) and not in `PACKAGED_ROOT_ASSETS` (never bundled
|
|
1468
|
+
# into the npm tarball) -- it only ever reaches a real INSTANCE, via
|
|
1469
|
+
# `biffo core upgrade`'s ordinary three-way merge. A satellite running the
|
|
1470
|
+
# packaged `verify.sh` through `scripts/biffo.sh verify` therefore had no
|
|
1471
|
+
# such file on disk at all, and a bare `sh scripts/
|
|
1472
|
+
# check-orphan-ratchet-instance.sh` failed at the shell -- "cannot open
|
|
1473
|
+
# ... No such file" -- before the script's own biffo.core.json skip ever
|
|
1474
|
+
# got a chance to run (biffo-template#1943). The guard is true in every
|
|
1475
|
+
# checkout that actually carries the file (this repo, and any instance
|
|
1476
|
+
# that has upgraded far enough to have received it), so `verify.sh --list`'s
|
|
1477
|
+
# output here is unchanged and parity with ci.yml's own unconditional step
|
|
1478
|
+
# holds exactly as before; it is false only in a satellite, where this now
|
|
1479
|
+
# skips instead of crashing the rest of the gate.
|
|
1480
|
+
[ -f scripts/check-orphan-ratchet-instance.sh ] &&
|
|
1481
|
+
run_check orphan-ratchet-instance sh scripts/check-orphan-ratchet-instance.sh
|
|
1463
1482
|
run_check cognito-invite sh scripts/biffo.sh check cognito-invite-template
|
|
1464
1483
|
run_check lambda-output sh scripts/biffo.sh check lambda-output
|
|
1465
1484
|
run_check pipe-trap sh scripts/biffo.sh check pipe-trap
|