@biffo/cli 0.266.1 → 0.267.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 +651 -38
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -2863,6 +2863,24 @@ function assertTargetFidelity(options) {
2863
2863
  findings.push({ path: entry.path, reason: "content-differs", source });
2864
2864
  }
2865
2865
  }
2866
+ for (const migration of options.migrations ?? []) {
2867
+ const path = `${MIGRATIONS_VERSIONS_DIR}/${migration.file}`;
2868
+ checked++;
2869
+ if (!blobs.has(path)) {
2870
+ findings.push({ path, reason: "absent-from-tag", source: "migration" });
2871
+ continue;
2872
+ }
2873
+ let tagText;
2874
+ try {
2875
+ tagText = git(["-C", options.templateRepo, "show", `${tag}:${path}`]);
2876
+ } catch {
2877
+ findings.push({ path, reason: "content-differs", source: "migration" });
2878
+ continue;
2879
+ }
2880
+ if (migrationBodyHash(tagText) !== migrationBodyHash(migration.content)) {
2881
+ findings.push({ path, reason: "content-differs", source: "migration" });
2882
+ }
2883
+ }
2866
2884
  return { checked, findings, unverifiable: null };
2867
2885
  }
2868
2886
  function fidelityFailure(report, toVersion) {
@@ -3456,8 +3474,14 @@ async function runCoreUpgradeResolved(options, deps, cleanups) {
3456
3474
  });
3457
3475
  const orphanRatchet = checkOrphanRatchet(plan.orphaned.length, readOrphanBaseline(options.cwd));
3458
3476
  const newSeams = findNewUndeclaredSeams(baseDir, theirsDir, options.cwd);
3477
+ const migrations = planMigrationCarry({
3478
+ templateDir: theirsDir,
3479
+ instanceDir: options.cwd,
3480
+ declined: readDeclinedMigrations(options.cwd)
3481
+ });
3459
3482
  const fidelity = assertTargetFidelity({
3460
3483
  entries: plan.entries,
3484
+ migrations: migrations.entries,
3461
3485
  templateRepo,
3462
3486
  toVersion,
3463
3487
  theirsDir,
@@ -3482,11 +3506,6 @@ async function runCoreUpgradeResolved(options, deps, cleanups) {
3482
3506
  `${String(plan.orphaned.length)} unsanctioned instance file(s) under a template-owned path (baseline ${String(orphanRatchet.baseline)}) \u2014 see the list above (#1026). Move new instance-written files under a sanctioned carve-out (services/api/tests/instance/, modules/**/instance/) or otherwise reduce the count, then re-run.`
3483
3507
  );
3484
3508
  }
3485
- const migrations = planMigrationCarry({
3486
- templateDir: theirsDir,
3487
- instanceDir: options.cwd,
3488
- declined: readDeclinedMigrations(options.cwd)
3489
- });
3490
3509
  const coreVersionCleanup = planCoreVersionCleanup(options.cwd);
3491
3510
  if (plan.changes.length === 0 && migrations.entries.length === 0) {
3492
3511
  log.success("Nothing to upgrade \u2014 the instance already matches the target for all core files.");
@@ -10218,8 +10237,8 @@ async function runOwnershipCheck(argv) {
10218
10237
  const { stdout } = await execa8("git", ["diff", "--cached", "--name-status"], { cwd: root });
10219
10238
  ({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
10220
10239
  if (messageFile) {
10221
- const { readFileSync: readFileSync32, existsSync: existsSync42 } = await import("fs");
10222
- if (existsSync42(messageFile)) commitMessage = readFileSync32(messageFile, "utf8");
10240
+ const { readFileSync: readFileSync32, existsSync: existsSync43 } = await import("fs");
10241
+ if (existsSync43(messageFile)) commitMessage = readFileSync32(messageFile, "utf8");
10223
10242
  }
10224
10243
  } else {
10225
10244
  const base = process.env["GITHUB_BASE_REF"] ?? args[0];
@@ -10709,13 +10728,420 @@ async function runPluginTerraformCheck() {
10709
10728
  }
10710
10729
 
10711
10730
  // src/scripts/check-plugin-tool-supply.ts
10712
- import { existsSync as existsSync39 } from "fs";
10731
+ import { existsSync as existsSync40 } from "fs";
10713
10732
  import { join as join43 } from "path";
10714
10733
  import { execa as execa12 } from "execa";
10715
10734
 
10716
10735
  // src/lib/plugin-tool-supply-audit.ts
10717
- import { readFileSync as readFileSync30, readdirSync as readdirSync18, statSync as statSync10 } from "fs";
10736
+ import { existsSync as existsSync39, readFileSync as readFileSync30, readdirSync as readdirSync18, statSync as statSync10 } from "fs";
10718
10737
  import { join as join42 } from "path";
10738
+
10739
+ // src/lib/openrouter-model-snapshot.ts
10740
+ var OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT = "2026-08-10T06:39:01Z";
10741
+ var OPENROUTER_MODEL_IDS = [
10742
+ "ai21/jamba-large-1.7",
10743
+ "aion-labs/aion-2.0",
10744
+ "aion-labs/aion-3.0",
10745
+ "aion-labs/aion-3.0-mini",
10746
+ "aion-labs/aion-rp-llama-3.1-8b",
10747
+ "allenai/olmo-3-32b-think",
10748
+ "amazon/nova-2-lite-v1",
10749
+ "amazon/nova-lite-v1",
10750
+ "amazon/nova-micro-v1",
10751
+ "amazon/nova-premier-v1",
10752
+ "amazon/nova-pro-v1",
10753
+ "anthracite-org/magnum-v4-72b",
10754
+ "anthropic/claude-3-haiku",
10755
+ "anthropic/claude-fable-5",
10756
+ "anthropic/claude-fable-5:batch",
10757
+ "anthropic/claude-haiku-4.5",
10758
+ "anthropic/claude-haiku-4.5:batch",
10759
+ "anthropic/claude-opus-4",
10760
+ "anthropic/claude-opus-4.1",
10761
+ "anthropic/claude-opus-4.1:batch",
10762
+ "anthropic/claude-opus-4.5",
10763
+ "anthropic/claude-opus-4.5:batch",
10764
+ "anthropic/claude-opus-4.6",
10765
+ "anthropic/claude-opus-4.6:batch",
10766
+ "anthropic/claude-opus-4.7",
10767
+ "anthropic/claude-opus-4.7-fast",
10768
+ "anthropic/claude-opus-4.7:batch",
10769
+ "anthropic/claude-opus-4.8",
10770
+ "anthropic/claude-opus-4.8-fast",
10771
+ "anthropic/claude-opus-4.8:batch",
10772
+ "anthropic/claude-opus-5",
10773
+ "anthropic/claude-opus-5-fast",
10774
+ "anthropic/claude-opus-5:batch",
10775
+ "anthropic/claude-sonnet-4",
10776
+ "anthropic/claude-sonnet-4.5",
10777
+ "anthropic/claude-sonnet-4.5:batch",
10778
+ "anthropic/claude-sonnet-4.6",
10779
+ "anthropic/claude-sonnet-4.6:batch",
10780
+ "anthropic/claude-sonnet-5",
10781
+ "anthropic/claude-sonnet-5:batch",
10782
+ "arcee-ai/trinity-large-thinking",
10783
+ "arcee-ai/virtuoso-large",
10784
+ "baidu/ernie-4.5-vl-424b-a47b",
10785
+ "bytedance-seed/seed-1.6",
10786
+ "bytedance-seed/seed-1.6-flash",
10787
+ "bytedance-seed/seed-2.0-lite",
10788
+ "bytedance-seed/seed-2.0-mini",
10789
+ "bytedance/ui-tars-1.5-7b",
10790
+ "cognitivecomputations/dolphin-mistral-24b-venice-edition",
10791
+ "cohere/command-a",
10792
+ "cohere/command-r-08-2024",
10793
+ "cohere/command-r-plus-08-2024",
10794
+ "cohere/command-r7b-12-2024",
10795
+ "cohere/north-mini-code:free",
10796
+ "deepcogito/cogito-v2.1-671b",
10797
+ "deepseek/deepseek-chat",
10798
+ "deepseek/deepseek-chat-v3-0324",
10799
+ "deepseek/deepseek-chat-v3.1",
10800
+ "deepseek/deepseek-r1",
10801
+ "deepseek/deepseek-r1-0528",
10802
+ "deepseek/deepseek-r1-distill-llama-70b",
10803
+ "deepseek/deepseek-v3.1-terminus",
10804
+ "deepseek/deepseek-v3.2",
10805
+ "deepseek/deepseek-v3.2-exp",
10806
+ "deepseek/deepseek-v4-flash",
10807
+ "deepseek/deepseek-v4-flash-0731",
10808
+ "deepseek/deepseek-v4-pro",
10809
+ "google/gemini-2.5-flash",
10810
+ "google/gemini-2.5-flash-image",
10811
+ "google/gemini-2.5-flash-lite",
10812
+ "google/gemini-2.5-flash-lite:batch",
10813
+ "google/gemini-2.5-flash:batch",
10814
+ "google/gemini-2.5-pro",
10815
+ "google/gemini-2.5-pro-preview",
10816
+ "google/gemini-2.5-pro-preview-05-06",
10817
+ "google/gemini-2.5-pro:batch",
10818
+ "google/gemini-3-flash-preview",
10819
+ "google/gemini-3-flash-preview:batch",
10820
+ "google/gemini-3-pro-image",
10821
+ "google/gemini-3-pro-image-preview",
10822
+ "google/gemini-3.1-flash-image",
10823
+ "google/gemini-3.1-flash-image-preview",
10824
+ "google/gemini-3.1-flash-lite",
10825
+ "google/gemini-3.1-flash-lite-image",
10826
+ "google/gemini-3.1-flash-lite-preview",
10827
+ "google/gemini-3.1-flash-lite:batch",
10828
+ "google/gemini-3.1-pro-preview",
10829
+ "google/gemini-3.1-pro-preview-customtools",
10830
+ "google/gemini-3.1-pro-preview:batch",
10831
+ "google/gemini-3.5-flash",
10832
+ "google/gemini-3.5-flash-lite",
10833
+ "google/gemini-3.5-flash-lite:batch",
10834
+ "google/gemini-3.5-flash:batch",
10835
+ "google/gemini-3.6-flash",
10836
+ "google/gemini-3.6-flash:batch",
10837
+ "google/gemma-2-27b-it",
10838
+ "google/gemma-3-12b-it",
10839
+ "google/gemma-3-27b-it",
10840
+ "google/gemma-3-4b-it",
10841
+ "google/gemma-3n-e4b-it",
10842
+ "google/gemma-4-26b-a4b-it",
10843
+ "google/gemma-4-26b-a4b-it:free",
10844
+ "google/gemma-4-31b-it",
10845
+ "google/gemma-4-31b-it:free",
10846
+ "google/lyria-3-clip-preview",
10847
+ "google/lyria-3-pro-preview",
10848
+ "gryphe/mythomax-l2-13b",
10849
+ "ibm-granite/granite-4.0-h-micro",
10850
+ "ibm-granite/granite-4.1-8b",
10851
+ "inception/mercury-2",
10852
+ "inclusionai/ling-2.6-1t",
10853
+ "inclusionai/ling-2.6-flash",
10854
+ "inclusionai/ling-3.0-flash",
10855
+ "inclusionai/ling-3.0-tiny:free",
10856
+ "inclusionai/ring-2.6-1t",
10857
+ "kwaipilot/kat-coder-air-v2.5",
10858
+ "kwaipilot/kat-coder-pro-v2",
10859
+ "kwaipilot/kat-coder-pro-v2.5",
10860
+ "mancer/weaver",
10861
+ "meituan/longcat-2.0",
10862
+ "meta-llama/llama-3.1-70b-instruct",
10863
+ "meta-llama/llama-3.1-8b-instruct",
10864
+ "meta-llama/llama-3.2-1b-instruct",
10865
+ "meta-llama/llama-3.2-3b-instruct",
10866
+ "meta-llama/llama-3.3-70b-instruct",
10867
+ "meta-llama/llama-4-maverick",
10868
+ "meta-llama/llama-4-scout",
10869
+ "meta-llama/llama-guard-4-12b",
10870
+ "meta/muse-spark-1.1",
10871
+ "meta/muse-spark-1.2",
10872
+ "microsoft/phi-4",
10873
+ "microsoft/wizardlm-2-8x22b",
10874
+ "minimax/minimax-01",
10875
+ "minimax/minimax-m1",
10876
+ "minimax/minimax-m2",
10877
+ "minimax/minimax-m2-her",
10878
+ "minimax/minimax-m2.1",
10879
+ "minimax/minimax-m2.5",
10880
+ "minimax/minimax-m2.7",
10881
+ "minimax/minimax-m3",
10882
+ "minimax/minimax-m3:batch",
10883
+ "mistralai/codestral-2508",
10884
+ "mistralai/ministral-14b-2512",
10885
+ "mistralai/ministral-3b-2512",
10886
+ "mistralai/ministral-8b-2512",
10887
+ "mistralai/mistral-large",
10888
+ "mistralai/mistral-large-2407",
10889
+ "mistralai/mistral-large-2512",
10890
+ "mistralai/mistral-medium-3",
10891
+ "mistralai/mistral-medium-3-5",
10892
+ "mistralai/mistral-medium-3.1",
10893
+ "mistralai/mistral-nemo",
10894
+ "mistralai/mistral-saba",
10895
+ "mistralai/mistral-small-24b-instruct-2501",
10896
+ "mistralai/mistral-small-2603",
10897
+ "mistralai/mistral-small-3.1-24b-instruct",
10898
+ "mistralai/mistral-small-3.2-24b-instruct",
10899
+ "mistralai/mixtral-8x22b-instruct",
10900
+ "mistralai/voxtral-small-24b-2507",
10901
+ "moonshotai/kimi-k2",
10902
+ "moonshotai/kimi-k2-0905",
10903
+ "moonshotai/kimi-k2-thinking",
10904
+ "moonshotai/kimi-k2.5",
10905
+ "moonshotai/kimi-k2.6",
10906
+ "moonshotai/kimi-k2.7-code",
10907
+ "moonshotai/kimi-k2.7-code:batch",
10908
+ "moonshotai/kimi-k3",
10909
+ "morph/morph-v3-fast",
10910
+ "morph/morph-v3-large",
10911
+ "nex-agi/nex-n2-mini",
10912
+ "nex-agi/nex-n2-pro",
10913
+ "nousresearch/hermes-3-llama-3.1-405b",
10914
+ "nousresearch/hermes-3-llama-3.1-70b",
10915
+ "nousresearch/hermes-4-405b",
10916
+ "nousresearch/hermes-4-70b",
10917
+ "nvidia/nemotron-3-nano-30b-a3b",
10918
+ "nvidia/nemotron-3-nano-30b-a3b:free",
10919
+ "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
10920
+ "nvidia/nemotron-3-super-120b-a12b",
10921
+ "nvidia/nemotron-3-super-120b-a12b:free",
10922
+ "nvidia/nemotron-3-ultra-550b-a55b",
10923
+ "nvidia/nemotron-3-ultra-550b-a55b:batch",
10924
+ "nvidia/nemotron-3-ultra-550b-a55b:free",
10925
+ "nvidia/nemotron-3.5-content-safety:free",
10926
+ "nvidia/nemotron-nano-12b-v2-vl:free",
10927
+ "nvidia/nemotron-nano-9b-v2:free",
10928
+ "openai/gpt-3.5-turbo",
10929
+ "openai/gpt-3.5-turbo-0613",
10930
+ "openai/gpt-3.5-turbo-16k",
10931
+ "openai/gpt-3.5-turbo-instruct",
10932
+ "openai/gpt-3.5-turbo:batch",
10933
+ "openai/gpt-4",
10934
+ "openai/gpt-4-turbo",
10935
+ "openai/gpt-4-turbo-preview",
10936
+ "openai/gpt-4-turbo:batch",
10937
+ "openai/gpt-4.1",
10938
+ "openai/gpt-4.1-mini",
10939
+ "openai/gpt-4.1-mini:batch",
10940
+ "openai/gpt-4.1-nano",
10941
+ "openai/gpt-4.1-nano:batch",
10942
+ "openai/gpt-4.1:batch",
10943
+ "openai/gpt-4o",
10944
+ "openai/gpt-4o-2024-05-13",
10945
+ "openai/gpt-4o-2024-08-06",
10946
+ "openai/gpt-4o-2024-11-20",
10947
+ "openai/gpt-4o-mini",
10948
+ "openai/gpt-4o-mini-2024-07-18",
10949
+ "openai/gpt-4o-mini:batch",
10950
+ "openai/gpt-4o:batch",
10951
+ "openai/gpt-5",
10952
+ "openai/gpt-5-codex:batch",
10953
+ "openai/gpt-5-image",
10954
+ "openai/gpt-5-image-mini",
10955
+ "openai/gpt-5-mini",
10956
+ "openai/gpt-5-mini:batch",
10957
+ "openai/gpt-5-nano",
10958
+ "openai/gpt-5-nano:batch",
10959
+ "openai/gpt-5-pro",
10960
+ "openai/gpt-5-pro:batch",
10961
+ "openai/gpt-5.1",
10962
+ "openai/gpt-5.1-codex",
10963
+ "openai/gpt-5.1-codex-max",
10964
+ "openai/gpt-5.1-codex-mini",
10965
+ "openai/gpt-5.1:batch",
10966
+ "openai/gpt-5.2",
10967
+ "openai/gpt-5.2-chat",
10968
+ "openai/gpt-5.2-codex",
10969
+ "openai/gpt-5.2-pro",
10970
+ "openai/gpt-5.2-pro:batch",
10971
+ "openai/gpt-5.2:batch",
10972
+ "openai/gpt-5.3-chat",
10973
+ "openai/gpt-5.3-codex",
10974
+ "openai/gpt-5.4",
10975
+ "openai/gpt-5.4-image-2",
10976
+ "openai/gpt-5.4-mini",
10977
+ "openai/gpt-5.4-mini:batch",
10978
+ "openai/gpt-5.4-nano",
10979
+ "openai/gpt-5.4-nano:batch",
10980
+ "openai/gpt-5.4-pro",
10981
+ "openai/gpt-5.4-pro:batch",
10982
+ "openai/gpt-5.4:batch",
10983
+ "openai/gpt-5.5",
10984
+ "openai/gpt-5.5-pro",
10985
+ "openai/gpt-5.5-pro:batch",
10986
+ "openai/gpt-5.5:batch",
10987
+ "openai/gpt-5.6-luna",
10988
+ "openai/gpt-5.6-luna-pro",
10989
+ "openai/gpt-5.6-luna-pro:batch",
10990
+ "openai/gpt-5.6-luna:batch",
10991
+ "openai/gpt-5.6-sol",
10992
+ "openai/gpt-5.6-sol-pro",
10993
+ "openai/gpt-5.6-sol-pro:batch",
10994
+ "openai/gpt-5.6-sol:batch",
10995
+ "openai/gpt-5.6-terra",
10996
+ "openai/gpt-5.6-terra-pro",
10997
+ "openai/gpt-5.6-terra-pro:batch",
10998
+ "openai/gpt-5.6-terra:batch",
10999
+ "openai/gpt-5:batch",
11000
+ "openai/gpt-audio",
11001
+ "openai/gpt-audio-mini",
11002
+ "openai/gpt-chat-latest",
11003
+ "openai/gpt-oss-120b",
11004
+ "openai/gpt-oss-20b",
11005
+ "openai/gpt-oss-20b:free",
11006
+ "openai/gpt-oss-safeguard-20b",
11007
+ "openai/o1",
11008
+ "openai/o1-pro",
11009
+ "openai/o1-pro:batch",
11010
+ "openai/o1:batch",
11011
+ "openai/o3",
11012
+ "openai/o3-mini",
11013
+ "openai/o3-mini-high",
11014
+ "openai/o3-mini-high:batch",
11015
+ "openai/o3-mini:batch",
11016
+ "openai/o3-pro",
11017
+ "openai/o3-pro:batch",
11018
+ "openai/o3:batch",
11019
+ "openai/o4-mini",
11020
+ "openai/o4-mini-high",
11021
+ "openai/o4-mini-high:batch",
11022
+ "openai/o4-mini:batch",
11023
+ "openrouter/auto",
11024
+ "openrouter/auto-beta",
11025
+ "openrouter/bodybuilder",
11026
+ "openrouter/free",
11027
+ "openrouter/fusion",
11028
+ "openrouter/pareto-code",
11029
+ "perceptron/perceptron-mk1",
11030
+ "perplexity/sonar",
11031
+ "perplexity/sonar-deep-research",
11032
+ "perplexity/sonar-pro",
11033
+ "perplexity/sonar-pro-search",
11034
+ "perplexity/sonar-reasoning-pro",
11035
+ "poolside/laguna-s-2.1",
11036
+ "poolside/laguna-s-2.1:free",
11037
+ "poolside/laguna-xs-2.1",
11038
+ "poolside/laguna-xs-2.1:free",
11039
+ "qwen/qwen-2.5-72b-instruct",
11040
+ "qwen/qwen-2.5-7b-instruct",
11041
+ "qwen/qwen-2.5-coder-32b-instruct",
11042
+ "qwen/qwen-plus",
11043
+ "qwen/qwen-plus-2025-07-28",
11044
+ "qwen/qwen-plus-2025-07-28:thinking",
11045
+ "qwen/qwen2.5-vl-72b-instruct",
11046
+ "qwen/qwen3-14b",
11047
+ "qwen/qwen3-235b-a22b",
11048
+ "qwen/qwen3-235b-a22b-2507",
11049
+ "qwen/qwen3-235b-a22b-thinking-2507",
11050
+ "qwen/qwen3-30b-a3b",
11051
+ "qwen/qwen3-30b-a3b-instruct-2507",
11052
+ "qwen/qwen3-30b-a3b-thinking-2507",
11053
+ "qwen/qwen3-32b",
11054
+ "qwen/qwen3-8b",
11055
+ "qwen/qwen3-coder",
11056
+ "qwen/qwen3-coder-30b-a3b-instruct",
11057
+ "qwen/qwen3-coder-flash",
11058
+ "qwen/qwen3-coder-next",
11059
+ "qwen/qwen3-coder-plus",
11060
+ "qwen/qwen3-max",
11061
+ "qwen/qwen3-max-thinking",
11062
+ "qwen/qwen3-next-80b-a3b-instruct",
11063
+ "qwen/qwen3-next-80b-a3b-thinking",
11064
+ "qwen/qwen3-vl-235b-a22b-instruct",
11065
+ "qwen/qwen3-vl-235b-a22b-thinking",
11066
+ "qwen/qwen3-vl-30b-a3b-instruct",
11067
+ "qwen/qwen3-vl-30b-a3b-thinking",
11068
+ "qwen/qwen3-vl-32b-instruct",
11069
+ "qwen/qwen3-vl-8b-instruct",
11070
+ "qwen/qwen3-vl-8b-thinking",
11071
+ "qwen/qwen3.5-122b-a10b",
11072
+ "qwen/qwen3.5-27b",
11073
+ "qwen/qwen3.5-35b-a3b",
11074
+ "qwen/qwen3.5-397b-a17b",
11075
+ "qwen/qwen3.5-9b",
11076
+ "qwen/qwen3.5-flash-02-23",
11077
+ "qwen/qwen3.5-plus-02-15",
11078
+ "qwen/qwen3.5-plus-20260420",
11079
+ "qwen/qwen3.6-27b",
11080
+ "qwen/qwen3.6-35b-a3b",
11081
+ "qwen/qwen3.6-flash",
11082
+ "qwen/qwen3.6-max-preview",
11083
+ "qwen/qwen3.6-plus",
11084
+ "qwen/qwen3.7-flash",
11085
+ "qwen/qwen3.7-max",
11086
+ "qwen/qwen3.7-plus",
11087
+ "qwen/qwen3.8-max",
11088
+ "rekaai/reka-edge",
11089
+ "rekaai/reka-flash-3",
11090
+ "relace/relace-apply-3",
11091
+ "relace/relace-search",
11092
+ "sakana/fugu-ultra",
11093
+ "sao10k/l3-lunaris-8b",
11094
+ "sao10k/l3.1-euryale-70b",
11095
+ "sao10k/l3.3-euryale-70b",
11096
+ "stepfun/step-3.5-flash",
11097
+ "stepfun/step-3.7-flash",
11098
+ "tencent/hunyuan-a13b-instruct",
11099
+ "tencent/hy3",
11100
+ "tencent/hy3-preview",
11101
+ "thedrummer/cydonia-24b-v4.1",
11102
+ "thedrummer/rocinante-12b",
11103
+ "thedrummer/skyfall-36b-v2",
11104
+ "thedrummer/unslopnemo-12b",
11105
+ "thinkingmachines/inkling",
11106
+ "thinkingmachines/inkling-small",
11107
+ "thinkingmachines/inkling:batch",
11108
+ "undi95/remm-slerp-l2-13b",
11109
+ "upstage/solar-pro-3",
11110
+ "writer/palmyra-x5",
11111
+ "x-ai/grok-4.20",
11112
+ "x-ai/grok-4.20-multi-agent",
11113
+ "x-ai/grok-4.3",
11114
+ "x-ai/grok-4.5",
11115
+ "x-ai/grok-build-0.1",
11116
+ "xiaomi/mimo-v2.5",
11117
+ "xiaomi/mimo-v2.5-pro",
11118
+ "z-ai/glm-4.5",
11119
+ "z-ai/glm-4.5-air",
11120
+ "z-ai/glm-4.5v",
11121
+ "z-ai/glm-4.6",
11122
+ "z-ai/glm-4.6v",
11123
+ "z-ai/glm-4.7",
11124
+ "z-ai/glm-4.7-flash",
11125
+ "z-ai/glm-5",
11126
+ "z-ai/glm-5-turbo",
11127
+ "z-ai/glm-5.1",
11128
+ "z-ai/glm-5.2",
11129
+ "z-ai/glm-5.2:batch",
11130
+ "z-ai/glm-5v-turbo",
11131
+ "~anthropic/claude-fable-latest",
11132
+ "~anthropic/claude-haiku-latest",
11133
+ "~anthropic/claude-opus-latest",
11134
+ "~anthropic/claude-sonnet-latest",
11135
+ "~deepseek/deepseek-v4-flash-latest",
11136
+ "~google/gemini-flash-latest",
11137
+ "~google/gemini-pro-latest",
11138
+ "~moonshotai/kimi-latest",
11139
+ "~openai/gpt-latest",
11140
+ "~openai/gpt-mini-latest",
11141
+ "~x-ai/grok-latest"
11142
+ ];
11143
+
11144
+ // src/lib/plugin-tool-supply-audit.ts
10719
11145
  function listDirs(root) {
10720
11146
  let entries;
10721
11147
  try {
@@ -10968,6 +11394,148 @@ function extractTerraformEnvKeys(tfText) {
10968
11394
  }
10969
11395
  return { keys: [...keys].sort(), rawMarkerCount, resolvedBlockCount };
10970
11396
  }
11397
+ var SETTINGS_STR_FIELD = /^[ \t]*([A-Za-z_][A-Za-z0-9_]*)\s*:\s*str\s*=\s*"([^"]*)"/gm;
11398
+ function extractSettingsModelFields(text) {
11399
+ const out = [];
11400
+ SETTINGS_STR_FIELD.lastIndex = 0;
11401
+ let m;
11402
+ while ((m = SETTINGS_STR_FIELD.exec(text)) !== null) {
11403
+ const [field, value] = [m[1], m[2]];
11404
+ if (field.toLowerCase().includes("model") && value.includes("/")) {
11405
+ out.push({ field, value });
11406
+ }
11407
+ }
11408
+ return out;
11409
+ }
11410
+ var MODEL_FIELD_NAME_MARKER = /"name"\s*:\s*"model"/g;
11411
+ var FIELD_DEFAULT = /"default"\s*:\s*"([^"]*)"/;
11412
+ var FIELD_OPTIONS_START = /"options"\s*:\s*\[/;
11413
+ var OPTION_VALUE = /"value"\s*:\s*"([^"]*)"/g;
11414
+ function extractCuratedModelFields(text) {
11415
+ const fields = [];
11416
+ let rawFieldCount = 0;
11417
+ MODEL_FIELD_NAME_MARKER.lastIndex = 0;
11418
+ let m;
11419
+ while ((m = MODEL_FIELD_NAME_MARKER.exec(text)) !== null) {
11420
+ rawFieldCount += 1;
11421
+ const start = m.index + m[0].length;
11422
+ const nextNameIdx = text.indexOf('"name"', start);
11423
+ const windowEnd = nextNameIdx === -1 ? text.length : nextNameIdx;
11424
+ const window = text.slice(start, windowEnd);
11425
+ const defaultMatch = FIELD_DEFAULT.exec(window);
11426
+ const defaultValue = defaultMatch ? defaultMatch[1] : null;
11427
+ const optionsMatch = FIELD_OPTIONS_START.exec(window);
11428
+ const optionValues = [];
11429
+ if (optionsMatch) {
11430
+ const openIdx = start + optionsMatch.index + optionsMatch[0].length - 1;
11431
+ const closeIdx = balancedSpanFrom(text, openIdx, "[", "]");
11432
+ if (closeIdx !== null) {
11433
+ const inner = text.slice(openIdx, closeIdx + 1);
11434
+ OPTION_VALUE.lastIndex = 0;
11435
+ let om;
11436
+ while ((om = OPTION_VALUE.exec(inner)) !== null) {
11437
+ optionValues.push(om[1]);
11438
+ }
11439
+ }
11440
+ }
11441
+ fields.push({ defaultValue, optionValues });
11442
+ }
11443
+ return { rawFieldCount, fields };
11444
+ }
11445
+ var MODEL_SNAPSHOT_MAX_AGE_DAYS = 45;
11446
+ function isSnapshotStale(fetchedAt, now) {
11447
+ const fetchedMs = new Date(fetchedAt).getTime();
11448
+ if (Number.isNaN(fetchedMs)) return true;
11449
+ const ageMs = now.getTime() - fetchedMs;
11450
+ return ageMs > MODEL_SNAPSHOT_MAX_AGE_DAYS * 24 * 60 * 60 * 1e3;
11451
+ }
11452
+ function normalizeModelId(id) {
11453
+ return id.endsWith(":online") ? id.slice(0, -":online".length) : id;
11454
+ }
11455
+ var CONFIG_PY_PATH = join42("services", "api", "src", "api", "config.py");
11456
+ var ORCHESTRATION_SCHEMA_PATH = join42(
11457
+ "services",
11458
+ "api",
11459
+ "src",
11460
+ "api",
11461
+ "schemas",
11462
+ "orchestration.py"
11463
+ );
11464
+ function auditDeclaredModelIds(repoRoot, options = {}) {
11465
+ const knownModelIds = options.knownModelIds ?? OPENROUTER_MODEL_IDS;
11466
+ const snapshotFetchedAt = options.snapshotFetchedAt ?? OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT;
11467
+ const now = options.now ?? /* @__PURE__ */ new Date();
11468
+ const configPath = join42(repoRoot, CONFIG_PY_PATH);
11469
+ const orchestrationPath = join42(repoRoot, ORCHESTRATION_SCHEMA_PATH);
11470
+ const configMissing = !existsSync39(configPath);
11471
+ const orchestrationSchemaMissing = !existsSync39(orchestrationPath);
11472
+ const knownSet = new Set(knownModelIds);
11473
+ const snapshotEmpty = knownModelIds.length === 0;
11474
+ const snapshotStale = isSnapshotStale(snapshotFetchedAt, now);
11475
+ const findings = [];
11476
+ const record = (source, rawValue) => {
11477
+ const normalized = normalizeModelId(rawValue);
11478
+ const ok2 = knownSet.has(normalized);
11479
+ findings.push({
11480
+ source,
11481
+ modelId: rawValue,
11482
+ normalizedModelId: normalized,
11483
+ status: ok2 ? "ok" : "unknown-model",
11484
+ detail: ok2 ? `"${rawValue}" is in the OpenRouter snapshot (${knownModelIds.length} id(s), fetched ${snapshotFetchedAt})` : `"${rawValue}" (normalized "${normalized}") is NOT in the OpenRouter snapshot (${knownModelIds.length} id(s), fetched ${snapshotFetchedAt}) \u2014 either the slug is wrong or the snapshot needs a refresh`
11485
+ });
11486
+ };
11487
+ let settingsBlind = false;
11488
+ if (!configMissing) {
11489
+ const settingsFields = extractSettingsModelFields(readFileSync30(configPath, "utf8"));
11490
+ if (settingsFields.length === 0) settingsBlind = true;
11491
+ for (const { field, value } of settingsFields) record(`${CONFIG_PY_PATH}#${field}`, value);
11492
+ }
11493
+ let curatedFieldsBlind = false;
11494
+ if (!orchestrationSchemaMissing) {
11495
+ const curated = extractCuratedModelFields(readFileSync30(orchestrationPath, "utf8"));
11496
+ if (curated.rawFieldCount > 0 && curated.fields.every((f) => f.defaultValue === null && f.optionValues.length === 0)) {
11497
+ curatedFieldsBlind = true;
11498
+ }
11499
+ curated.fields.forEach((field, i) => {
11500
+ if (field.defaultValue !== null) {
11501
+ record(`${ORCHESTRATION_SCHEMA_PATH}#model[${i}].default`, field.defaultValue);
11502
+ }
11503
+ field.optionValues.forEach((v, j) => {
11504
+ record(`${ORCHESTRATION_SCHEMA_PATH}#model[${i}].options[${j}]`, v);
11505
+ });
11506
+ });
11507
+ }
11508
+ const badFindings = findings.filter((f) => f.status !== "ok");
11509
+ const ok = !configMissing && !orchestrationSchemaMissing && !settingsBlind && !curatedFieldsBlind && !snapshotEmpty && !snapshotStale && badFindings.length === 0;
11510
+ const summaryParts = [
11511
+ `${findings.length} declared model id(s) checked against ${knownModelIds.length} known OpenRouter id(s) (snapshot fetched ${snapshotFetchedAt})`,
11512
+ `${badFindings.length} not ok`
11513
+ ];
11514
+ if (configMissing) summaryParts.push(`MISSING ${CONFIG_PY_PATH}`);
11515
+ if (orchestrationSchemaMissing) summaryParts.push(`MISSING ${ORCHESTRATION_SCHEMA_PATH}`);
11516
+ if (settingsBlind) summaryParts.push("SETTINGS EXTRACTOR BLIND");
11517
+ if (curatedFieldsBlind) summaryParts.push("CURATED-OPTIONS EXTRACTOR BLIND");
11518
+ if (snapshotEmpty) summaryParts.push("SNAPSHOT EMPTY");
11519
+ if (snapshotStale)
11520
+ summaryParts.push(
11521
+ `SNAPSHOT STALE (older than ${MODEL_SNAPSHOT_MAX_AGE_DAYS}d \u2014 run refresh-openrouter-model-snapshot.ts)`
11522
+ );
11523
+ return {
11524
+ configPath: CONFIG_PY_PATH,
11525
+ orchestrationSchemaPath: ORCHESTRATION_SCHEMA_PATH,
11526
+ configMissing,
11527
+ orchestrationSchemaMissing,
11528
+ settingsBlind,
11529
+ curatedFieldsBlind,
11530
+ snapshotFetchedAt,
11531
+ snapshotModelCount: knownModelIds.length,
11532
+ snapshotEmpty,
11533
+ snapshotStale,
11534
+ findings,
11535
+ ok,
11536
+ summary: summaryParts.join("; ")
11537
+ };
11538
+ }
10971
11539
  function discoverPluginDirs(pluginsRoot) {
10972
11540
  return listDirs(pluginsRoot).filter((name) => {
10973
11541
  try {
@@ -11115,37 +11683,82 @@ function auditPluginToolSupply(pluginsRoot) {
11115
11683
  // src/scripts/check-plugin-tool-supply.ts
11116
11684
  async function runPluginToolSupplyCheck() {
11117
11685
  const root = (await execa12("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
11686
+ let allOk = true;
11118
11687
  const pluginsRoot = join43(root, "services", "_plugins");
11119
- if (!existsSync39(pluginsRoot)) {
11688
+ if (!existsSync40(pluginsRoot)) {
11120
11689
  console.log("\u2713 plugin tool-supply guard: no services/_plugins/ \u2014 nothing to audit");
11121
- return;
11122
- }
11123
- const report = auditPluginToolSupply(pluginsRoot);
11124
- console.log(
11125
- `audited ${report.plugins.length} plugin dir(s), ${report.findings.length} declared tool(s) cross-checked, under ${pluginsRoot}`
11126
- );
11127
- if (!report.ok) {
11128
- console.error("\u2717 plugin tool-supply guard: an unsatisfiable tool grant found\n");
11129
- if (report.noPluginsFound) {
11130
- console.error(` NO PLUGINS FOUND under ${pluginsRoot} \u2014 cannot evaluate an empty world.`);
11131
- }
11132
- if (report.registryBlind) {
11133
- console.error(
11134
- " REGISTRY BLIND: raw source contains ToolDefinition( call site(s) but the extractor resolved none \u2014 the extractor broke, this is not evidence a plugin registers nothing."
11135
- );
11136
- }
11137
- if (report.terraformBlind) {
11138
- console.error(
11139
- " TERRAFORM BLIND: raw source contains environment_variables but the extractor resolved no keys \u2014 the extractor broke, this is not evidence of an empty Lambda."
11140
- );
11690
+ } else {
11691
+ const report = auditPluginToolSupply(pluginsRoot);
11692
+ console.log(
11693
+ `audited ${report.plugins.length} plugin dir(s), ${report.findings.length} declared tool(s) cross-checked, under ${pluginsRoot}`
11694
+ );
11695
+ if (!report.ok) {
11696
+ allOk = false;
11697
+ console.error("\u2717 plugin tool-supply guard: an unsatisfiable tool grant found\n");
11698
+ if (report.noPluginsFound) {
11699
+ console.error(` NO PLUGINS FOUND under ${pluginsRoot} \u2014 cannot evaluate an empty world.`);
11700
+ }
11701
+ if (report.registryBlind) {
11702
+ console.error(
11703
+ " REGISTRY BLIND: raw source contains ToolDefinition( call site(s) but the extractor resolved none \u2014 the extractor broke, this is not evidence a plugin registers nothing."
11704
+ );
11705
+ }
11706
+ if (report.terraformBlind) {
11707
+ console.error(
11708
+ " TERRAFORM BLIND: raw source contains environment_variables but the extractor resolved no keys \u2014 the extractor broke, this is not evidence of an empty Lambda."
11709
+ );
11710
+ }
11711
+ for (const f of report.findings.filter((f2) => f2.status !== "ok")) {
11712
+ console.error(` ${f.status.toUpperCase()} ${f.plugin}/${f.tool} ${f.detail}`);
11713
+ }
11714
+ } else {
11715
+ console.log(`\u2713 plugin tool-supply guard: ${report.summary}`);
11141
11716
  }
11142
- for (const f of report.findings.filter((f2) => f2.status !== "ok")) {
11143
- console.error(` ${f.status.toUpperCase()} ${f.plugin}/${f.tool} ${f.detail}`);
11717
+ }
11718
+ const servicesApiRoot = join43(root, "services", "api");
11719
+ if (!existsSync40(servicesApiRoot)) {
11720
+ console.log("\u2713 plugin model-id guard: no services/api/ \u2014 nothing to audit");
11721
+ } else {
11722
+ const modelReport = auditDeclaredModelIds(root);
11723
+ console.log(`audited ${modelReport.findings.length} declared model id(s)`);
11724
+ if (!modelReport.ok) {
11725
+ allOk = false;
11726
+ console.error("\u2717 plugin model-id guard: a declared model id does not match the provider\n");
11727
+ if (modelReport.configMissing) {
11728
+ console.error(` MISSING ${modelReport.configPath}`);
11729
+ }
11730
+ if (modelReport.orchestrationSchemaMissing) {
11731
+ console.error(` MISSING ${modelReport.orchestrationSchemaPath}`);
11732
+ }
11733
+ if (modelReport.settingsBlind) {
11734
+ console.error(
11735
+ ` SETTINGS EXTRACTOR BLIND: ${modelReport.configPath} exists but no model-named string field resolved \u2014 the extractor broke, this is not evidence there is no default.`
11736
+ );
11737
+ }
11738
+ if (modelReport.curatedFieldsBlind) {
11739
+ console.error(
11740
+ ` CURATED-OPTIONS EXTRACTOR BLIND: ${modelReport.orchestrationSchemaPath} has a "name": "model" field but no default/options resolved from it.`
11741
+ );
11742
+ }
11743
+ if (modelReport.snapshotEmpty) {
11744
+ console.error(" SNAPSHOT EMPTY: the committed OpenRouter snapshot has zero ids.");
11745
+ }
11746
+ if (modelReport.snapshotStale) {
11747
+ console.error(
11748
+ ` SNAPSHOT STALE: fetched ${modelReport.snapshotFetchedAt}, older than the refresh window \u2014 run refresh-openrouter-model-snapshot.ts and commit the result.`
11749
+ );
11750
+ }
11751
+ for (const f of modelReport.findings.filter((f2) => f2.status !== "ok")) {
11752
+ console.error(` ${f.status.toUpperCase()} ${f.source} ${f.detail}`);
11753
+ }
11754
+ } else {
11755
+ console.log(`\u2713 plugin model-id guard: ${modelReport.summary}`);
11144
11756
  }
11757
+ }
11758
+ if (!allOk) {
11145
11759
  console.error("\nSee biffo-template#822.");
11146
11760
  process.exit(1);
11147
11761
  }
11148
- console.log(`\u2713 plugin tool-supply guard: ${report.summary}`);
11149
11762
  }
11150
11763
 
11151
11764
  // src/scripts/check-release-subject.ts
@@ -11329,7 +11942,7 @@ function rawArgsAfter(subcommand) {
11329
11942
  }
11330
11943
 
11331
11944
  // src/commands/doctor.ts
11332
- import { existsSync as existsSync40, readFileSync as readFileSync31 } from "fs";
11945
+ import { existsSync as existsSync41, readFileSync as readFileSync31 } from "fs";
11333
11946
  import { join as join44, resolve as resolve18 } from "path";
11334
11947
  import chalk21 from "chalk";
11335
11948
  import { Command as Command24 } from "commander";
@@ -11506,7 +12119,7 @@ async function runDoctor(options, deps = { git: new GitAdapter() }) {
11506
12119
  }
11507
12120
  function readLocalCoreVersion(cwd) {
11508
12121
  const path = join44(cwd, INSTANCE_CORE_FILE);
11509
- if (!existsSync40(path)) return null;
12122
+ if (!existsSync41(path)) return null;
11510
12123
  try {
11511
12124
  return parseCoreRecord(readFileSync31(path, "utf8"));
11512
12125
  } catch {
@@ -11524,7 +12137,7 @@ function parseCoreRecord(contents) {
11524
12137
  }
11525
12138
  function readFossil(cwd) {
11526
12139
  const path = join44(cwd, CORE_VERSION_FILE);
11527
- if (!existsSync40(path)) return null;
12140
+ if (!existsSync41(path)) return null;
11528
12141
  try {
11529
12142
  const value = readFileSync31(path, "utf8").trim();
11530
12143
  return value === "" ? null : value;
@@ -11975,13 +12588,13 @@ import { fileURLToPath as fileURLToPath6 } from "url";
11975
12588
  import { Command as Command26 } from "commander";
11976
12589
 
11977
12590
  // src/lib/packaged-scripts.ts
11978
- import { existsSync as existsSync41 } from "fs";
12591
+ import { existsSync as existsSync42 } from "fs";
11979
12592
  import { dirname as dirname10, join as join45 } from "path";
11980
12593
  function findPackagedScript(startDir, relativePath) {
11981
12594
  let dir = startDir;
11982
12595
  for (; ; ) {
11983
12596
  const candidate = join45(dir, relativePath);
11984
- if (existsSync41(candidate)) return candidate;
12597
+ if (existsSync42(candidate)) return candidate;
11985
12598
  const parent = dirname10(dir);
11986
12599
  if (parent === dir) return null;
11987
12600
  dir = parent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biffo/cli",
3
- "version": "0.266.1",
3
+ "version": "0.267.0",
4
4
  "description": "Biffo project scaffolding CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -50,7 +50,8 @@
50
50
  "check:core-ownership": "tsx src/index.ts check ownership",
51
51
  "check:plugin-terraform": "tsx src/index.ts check plugin-terraform",
52
52
  "sync:core-tag": "tsx src/scripts/sync-core-tag.ts",
53
- "report:publish-failure": "tsx src/scripts/report-publish-failure.ts"
53
+ "report:publish-failure": "tsx src/scripts/report-publish-failure.ts",
54
+ "refresh:openrouter-models": "tsx src/scripts/refresh-openrouter-model-snapshot.ts"
54
55
  },
55
56
  "dependencies": {
56
57
  "@aws-sdk/client-cloudformation": "^3.726.1",