Problem
scripts/migrations/_lib.js ships makeSeededProviderTierMigration to generalize the "retire a seeded provider model tier" shape, but no migration actually calls it — 153-claude-default-sonnet-5.js and 206-claude-default-opus-5.js each hand-roll the identical read → exact-match → rewrite models → swap tier pointers → write scaffolding inline.
337-claude-sonnet-5-additive.js is now a third hand-rolled variant, and it exists precisely because the exact-match policy those two share silently skips any record whose models list the user extended. An install that had appended one id to claude-code kept the retired claude-sonnet-4-6 while the shipped seed and its sibling claude-code-tui record had moved to claude-sonnet-5, and claude has no models subcommand to refresh it — so the reviewer/task model pickers reading that record could not offer the current tier at all.
The next tier retirement hits the same fork (exact-match rewrite vs. additive insert) and, on this trajectory, writes a fourth copy of the same scaffolding.
Work
Add an additive-insertion factory to scripts/migrations/_lib.js beside makeSeededProviderTierMigration, expressing 337's operation as config:
- Input: a targets table of
{ id, retired, current } (the four seeded Claude records' shape), plus log copy.
- Behavior: for each target, splice
current into models immediately after retired when retired is listed and current is not — regardless of what else the list holds. Tier pointers left alone (they point at an id that still resolves; a curated list is where re-pointing would override a deliberate choice).
- Idempotent: a record already listing
current is untouched, so a second run and a freshly seeded install are both no-ops.
- Share
readProvidersDoc / writeJsonAtomic with the two existing families, and keep the same { ok, reason, updated } result shape.
Then port 337-claude-sonnet-5-additive.js onto it and fold its test into the factory's. Leave 153/206 frozen — rewriting an applied migration changes what it did to existing data.
Document the choice in _lib.js's header alongside family 7: exact-match rewrite retires an id from a pristine seeded list; additive insert offers a new id on a list the user curated.
Done when
- A new tier bump can be written as a config table with no scaffolding copy.
- 337 is expressed through the factory with its behavior and tests unchanged.
_lib.js's header explains when to reach for which of the two.
Problem
scripts/migrations/_lib.jsshipsmakeSeededProviderTierMigrationto generalize the "retire a seeded provider model tier" shape, but no migration actually calls it —153-claude-default-sonnet-5.jsand206-claude-default-opus-5.jseach hand-roll the identical read → exact-match → rewritemodels→ swap tier pointers → write scaffolding inline.337-claude-sonnet-5-additive.jsis now a third hand-rolled variant, and it exists precisely because the exact-match policy those two share silently skips any record whosemodelslist the user extended. An install that had appended one id toclaude-codekept the retiredclaude-sonnet-4-6while the shipped seed and its siblingclaude-code-tuirecord had moved toclaude-sonnet-5, andclaudehas nomodelssubcommand to refresh it — so the reviewer/task model pickers reading that record could not offer the current tier at all.The next tier retirement hits the same fork (exact-match rewrite vs. additive insert) and, on this trajectory, writes a fourth copy of the same scaffolding.
Work
Add an additive-insertion factory to
scripts/migrations/_lib.jsbesidemakeSeededProviderTierMigration, expressing 337's operation as config:{ id, retired, current }(the four seeded Claude records' shape), plus log copy.currentintomodelsimmediately afterretiredwhenretiredis listed andcurrentis not — regardless of what else the list holds. Tier pointers left alone (they point at an id that still resolves; a curated list is where re-pointing would override a deliberate choice).currentis untouched, so a second run and a freshly seeded install are both no-ops.readProvidersDoc/writeJsonAtomicwith the two existing families, and keep the same{ ok, reason, updated }result shape.Then port
337-claude-sonnet-5-additive.jsonto it and fold its test into the factory's. Leave 153/206 frozen — rewriting an applied migration changes what it did to existing data.Document the choice in
_lib.js's header alongside family 7: exact-match rewrite retires an id from a pristine seeded list; additive insert offers a new id on a list the user curated.Done when
_lib.js's header explains when to reach for which of the two.