day8

re-frame-migration

Migrates an existing re-frame v1.x ClojureScript codebase to re-frame2. Swaps the artefact coord (re-frame/re-frame → day8/re-frame2 + a substrate adapter), applies the mechanical (Type A) rewrites from MIGRATION.md automatically, and flags the judgment-call (Type B) call sites for human review before touching them. **Do not use** for: greenfield bootstrap, writing v2 application code, live v2-app inspection, static critique, devtools tours, or porting re-frame2 itself — see the full routing table in `skills/README.md` §Skill routing for the right sibling skill. Trigger on phrasing like "migrate to re-frame2", "upgrade re-frame", "v1 to v2", "what breaks under re-frame2", or any prompt referencing a v1 surface (re-frame.db, dispatch-with, reg-global-interceptor, reg-sub-raw, ^:flush-dom, re-frame.alpha, re-frame-test, old top-level :dispatch / :dispatch-n effect-map keys), or a v1 add-on library (http-fx / :http-xhrio, async-flow-fx / :async-flow).

day8 52 Updated 1mo ago

Resources

7
GitHub

Install

npx skillscat add day8/re-frame2/re-frame-migration

Install via the SkillsCat registry.

SKILL.md

re-frame-migration

Helps an author migrate a ClojureScript codebase from re-frame v1.x to re-frame2. When done, the project depends on day8/re-frame2 + a substrate adapter, Type A rewrites have been applied, and every Type B call site has been surfaced with the relevant rule cited.

The mental model (read this first)

v1→v2 is mostly M-0 (the coord swap) plus mechanical rewrites — most apps need little. Bump the dep, compile, and a large fraction of the codebase already runs. The real hazard is the other axis: v2 moves a class of failures the compiler cannot catch to runtime — a cleanly-compiling rewrite that boots wrong (a {:db fresh} boot that carries a retired :rf/runtime key and throws :rf.error/legacy-runtime-root, a signal-fn reg-sub that registers as a :parametric sub then throws at first materialization, an unfolded top-level :dispatch that refuses its whole event pre-commit with an always-on :rf.error/effect-map-shape). So the discipline is: sweep the silent-failure rules exhaustively up front (grep every site — never march-the-wall, the compile gives you no wall to hit), then smoke-test the booted app live (read app-db + machine snapshots). "Compiles" is the start of verification, not the end. Everything below is organised around where the danger is: the cardinal rules are the invariants, the phases are the route, and two axes tell you what each rule needs — Type A/B (auto or ask?) and loud/silent (will anything tell me if I miss it?).

The authoritative rule corpus — M-rules (required) and O-rules (opt-in modernisations) — lives in `MIGRATION.md`. ("MIGRATION.md" is this skill's shorthand, not a filename — it means migration/from-re-frame-v1/README.md, and every later "MIGRATION.md" means that one file.) That URL is the CITATION; the reading route is a pinned local checkout of day8/re-frame2 at an author-supplied path, verified by structure and not by name — see `references/setup.md` §Pin the migration corpus before reading it, which is also why you must not fetch it from GitHub at runtime. The corpus does not ship inside this package. Do not duplicate that content here. Load it from the pinned checkout when you start and treat it as the source of truth.

When NOT to use

Full skill-disambiguation matrix: `skills/README.md` §Skill routing — single source. In brief: not for greenfield bootstrap, authoring on an already-v2 project, live-runtime inspection, porting re-frame2 itself, or spec / design-rationale reading.

Exit this skill when the project compiles, tests pass, the boot smoke-test comes back clean (the done-bar is the smoke-test, not the compile — Phase 4), and Type B items have been resolved.

Cardinal rules (the invariants)

Five invariants. Hold them in your head; everything else is procedure that lives in a leaf.

  1. `MIGRATION.md` is the source of truth — don't invent rules. Every rewrite cites a rule id (M-N or O-N). If a call site doesn't match any rule and isn't on the preserved list, stop and ask / flag for human review — never guess a rule. (Genuinely ambiguous rule → file an upstream day8/re-frame2 issue; don't edit MIGRATION.md inline.)
  2. Type A is automatic; Type B is asked-first. Type A is mechanical, unambiguous, observably identical — apply without prompting (after the sweep-level announcement, below). Type B depends on intent the skill can't recover statically — identify, explain the risk, wait for the author's decision. (The orthogonal loud/silent axis — see `breaking-changes.md` — tells you how a missed rule will surface, not whether to ask.)
  3. Smallest correct diff. Don't refactor for style, don't rename what the author didn't ask to rename, don't add features (frames, schemas, machines, reg-view) unless the author asked for the O-rules. Apply M-rules in MIGRATION.md order (M-0 first); JVM interop (.clj test runners, fixtures) is in scope.
  4. Announce, then sweep. Migration is destructive — Type A rewrites edit source in place — so before a multi-file sweep post the rule ("M-8 — fold top-level :dispatch into :fx"), the count of files matched, and a one-line example diff shape; then proceed. An explicit request to migrate the project authorised the Type-A work: the announcement is a progress update the author can scope-limit, not a checkpoint that waits for a second acknowledgement. Type B stays asked-first (rule 2), and the unresolved Type-B decisions are presented in one batch at the end of the sweep (`sequencing.md`), not one interruption per site.
  5. The skill runs the gates it discovers. Find the project's own noninteractive install / compile / test commands in its files (deps.edn aliases, shadow-cljs.edn builds, package.json scripts, project.clj, bb.edn, the README), run them, and record the exact command and result in the report — never print a command for the author to run and paste back. The host agent's own permission model and sandbox govern execution; the skill adds no ceremony of its own, and runs nothing destructive, privileged, deploy-shaped, or unrelated to the migration. The one gate that may need a human is the Phase-4 boot smoke-test: drive it yourself when a runtime is connected (a re-frame2-pair MCP, a shadow-cljs nREPL); otherwise hand over the compact checklist in `runtime-smoke-test.md` and report the smoke as pending — not "migration complete". The corpus-provenance checks (rev-parse / remote get-url / ls-tree — `references/setup.md` §Pin the migration corpus) and the codebase rg inventories run under the same allow-list.

Recipes (demoted from the rules — load when you need them): the single-import contract + off-contract-ns rewrite is M-1 (`breaking-changes.md`); the shell-injection-safe upstream-issue filing recipe is `references/issue-filing.md` (+ the `../README.md` §`allowed-tools` baseline).

The migration workflow

Two pre-flight phases plus six phases. Each links to a leaf for the detail; the SKILL.md carries only the workflow shape.

Phase 0a — Pre-flight: INVENTORY-AND-PLAN. Gate: run before everything — before any dep edit, any compile, the floor gate. Inventory the v1 add-on libraries + app features, scan their source (jars, git/source deps, vendored) for removed/moved v2 surfaces, and write a per-item plan (item → rule(s) → forced-vs-optional → disposition → replacement). Why: the breakages live in dependency source a compile reaches one namespace at a time — planning the sweep up front beats marching the wall. This phase is the umbrella that drives the floor gate, the off-contract-ns principle (M-1), the console compile-gate, and the classpath-clean check. → `references/inventory-and-plan.md`.

Phase 0b — Pre-flight: the React-19 / Reagent-2 floor gate. Gate: run before any dep edit; GO/NO-GO blocker. re-frame2's adapters target React 19 (the Reagent bridge runs on Reagent 2.x). Why: for React-17/18 + Reagent-1.x projects this is the largest, riskiest part of the migration and the blocking case must surface here, not inside a failed post-swap compile. Run the six checks and record an explicit go/no-go: downstream React-lib audit; component-library React-19 build; legacy ReactDOM.render scan; CLJS/shadow-cljs/Closure toolchain skew; CI/test-runner browser floor; go/no-go. The one NO-GO: a component library with neither a declared React-19 release nor an empirical runtime pass — STOP and surface the four-option decision (wait for a release / replace / vendor-or-patch / force React 19 + verify empirically at runtime; an empirical pass is a valid GO). Toolchain skew (Check 4) and a stale CI-browser pin (Check 5) are mechanical bumps carried into M-0, never NO-GOs. Each check's full procedure → `references/floor-gate.md` §The React-19 / Reagent-2 floor gate. The Phase-0a inventory feeds this gate.

Phase 1 — Orient. Read the project's dep file (deps.edn / project.clj / shadow-cljs.edn / bb.edn), then `MIGRATION.md` Part 1, then the project's test-suite shape. → `references/setup.md` for the M-0 dep swap.

Phase 2 — Bump the dep (M-0). Swap re-frame/re-frame → day8/re-frame2 + a substrate-adapter artefact (day8/re-frame2-reagent unless told otherwise), at the author-supplied <v2-version> (never auto-pick "latest"). Carry every Phase-0b GO-state bump into this same pass — React/Reagent, any component-lib, and the shadow-cljs/CLJS toolchain (Check 4) — so the post-M-0 compile runs against the fully-current toolchain (an older shadow-cljs detonates the first compile with a cryptic NoSuchFieldError). Then install and compile before applying any other rules — most codebases need nothing more. The skill makes the dep-file + package.json edits, runs the project's own install + compile commands, and reads the result (cardinal rule 5). → `references/setup.md` for per-build-tool shapes and the adapter picker.

Devtools (10x → Xray) — a tracked deliverable. If Phase-0a detected day8.re-frame/re-frame-10x, the 10x → Xray swap is a REQUIRED deliverable with no M-N id (nothing in the sweep reminds you) — track it as its own two-stage item: drop the dead 10x preload at M-0 so the compile gate is reachable, mount Xray post-M-40. Done-state is the app ON Xray (no 10x ⇒ optional, never forced). Full deliverable + drop/add/host shape + peer-deps + parity matrix → `references/xray-replaces-10x.md` and the Done checklist below.

Phase 3 — Apply the planned sweep. Always carry forward the Phase-0a plan: forced add-on/classpath blockers, silent-fail rules, and M-70 are applied or triaged whether Phase 2 compiled cleanly or not — they compile clean and never surface as a compile failure. If the compile/test also surfaced failures, additionally walk those M-rules in order. (A clean compile does NOT route straight to the report; the planned silent-fail hits still apply.) If the Phase-0a inventory found pre-release-v2 surfaces (trace/epoch listeners, :invoke machine keys, the legacy frame-affordance family — the codebase is a pre-rename v2 build, not a v1 one), the governing rule index is `references/pre-rename-upgrades.md`; a true v1→v2 migration never trips those rules.

Forced here: a v1 add-on does NOT compile on v2. http-fx, async-flow-fx, undo, forward-events-fx :refer the removed re-frame.core/console and fail to compile the moment re-frame2 is on the classpath (no shim) — a required compile-gate pre-step, not "modernise later." What's forced is acting at all (convert, or remove if the feature's gone); the idiomatic v2 replacement is the opt-in Phase-5 step. → `references/breaking-changes.md` §v1 add-on libraries fail to COMPILE on v2.

Also forced here for a subscribing view tree: ACT on every subscribing plain-fn view (M-11). Under EP-0002 a (defn …) Reagent view carries no :contextType, so a bare subscribe / dispatch in its render raises :rf.error/no-frame-context (no :rf/default floor) — the app won't boot until every site is fixed, and it is compile-silent (compiles 0/0, crashes at first render; only the Phase-4 boot smoke-test catches a miss). The per-site fix is corpus M-11's Type-B choice, asked per cardinal rule 2: convert defn → reg-view (recommended; typically the largest single conversion) or carry the frame explicitly (a targeted (rf/capture-frame frame-id) / {:frame …} opt — a bare no-arg capture-frame in the unregistered fn re-raises). Size it in Phase-0a; act on subscribers only. Mechanics → `references/guided-views-m11.md` §Executing the conversion.

  • `references/sequencing.md` — recommended order, restated so an interrupted migration can resume.
  • `references/auto-call-site-rewrites.md` — Type A: per-call-site mechanical rewrites (ns requires, effect-map, dispatch shapes).
  • `references/auto-cross-cutting.md` — Type A: cross-cutting renames, interceptor cleanup (incl. M-70 chains → metadata :interceptors), view / hiccup rewrites, init wiring, per-feature artefact adds.
  • `references/guided-handlers-state.md` — Type B: handler / db-seeding / error-handler / machine-spawn / Reagent-surface walkthroughs (M-3, M-5, M-10, M-12, M-13, M-14, M-15, M-15b, M-34, M-42).
  • `references/guided-views-m11.md` — Type B: the M-11 sweep on its own — subscribing plain (defn …) Reagent views → reg-view (Form-1/2/3, the defmulti shape, the reg-view* capture-once contract) and the async listener / timer / error-handler class. Typically the largest single conversion in a migration.
  • `references/guided-interceptors-subs.md` — Type B: interceptor / subscription / payload / observer walkthroughs (M-17, M-18, M-71, M-19, M-21, M-23, M-26).
  • `references/error-events.md` — pointer to `spec/009-Instrumentation.md` §Error event catalogue, the single source for the :rf.error/* / :rf.warning/* / :rf.fx/* / … categories. Load when wiring error observability (the stream-parameterized register-listener! — :errors for production egress, :trace for the dev raw trace). There is no frame-level :on-error recovery policy in v2; recovery is framework-owned (M-13, M-17, M-26).
  • `references/causal-world-inputs.md` — EP-0010 recording rule + EP-0017 reshape (M-72): a durable write fed by an ambient host read (Date.now, random-uuid, js/location, localStorage, a non-recordable :now cofx, …) must move to a declared recordable coeffect (:rf/time-ms) / the payload; and inject-cofx is removed, :rf.world/inputs → flat :rf.cofx. Silent for the durable-read judgment (invisible to compile and smoke-test, replay-only); loud for the removed inject-cofx. Run the up-front grep here; diagnostic + host-transient reads stay ambient.
  • `references/breaking-changes.md` — one-page index of every M-/O-rule by trigger surface; grep here to find the rule id.

Phase 4 — Verify. "Compiles" is NOT the done-bar. v2 moves a large class of v1 failures from compile-time to RUNTIME — a clean compile means "the rewrites parse," not "the app boots and runs." Several legitimate-looking, cleanly-compiling rewrites fail at boot (signal-fn reg-sub, a missing per-feature artefact, a (when …) nil-thread losing seed state — full list in `references/silent-runtime-failures.md`); none show in the build log, all need live app-db inspection to find. Others are loud the first time the handler runs but still invisible to the compile — an unfolded M-8 top-level key now refuses its whole event pre-commit rather than being dropped in silence. So Phase 4 is three steps, not one:

  1. Recompile — run the project's exact compile command for its shape (shadow-cljs compile app, clj -M:test, the npm script) and read the output (cardinal rule 5).
  2. Re-run the unit tests (re-baseline render counts per M-12; no new failures).
  3. Run the BOOT SMOKE-TEST with live introspection — boot a dev build, then read the live frame's app-db + machine snapshots ([:rf.runtime/machines :snapshots]), deref the first-screen subs, dispatch one event per feature surface and re-read the affected slot, and scan the boot trace for :rf.error/* / :rf.warning/*. Cheapest tool: the re-frame2-pair MCP / a shadow-cljs nREPL — every silent failure is invisible in the build log and shows only in the running state. Drive it yourself when such a runtime is connected; with none, hand the programmer the compact checklist and report the smoke as pending. Done = this loop comes back clean, not the compile. → `references/runtime-smoke-test.md` (the smoke-test loop + the done-bar gates) and `references/silent-runtime-failures.md` (the silent-failure checklist).

If any step fails, find the rule, apply it, re-run the gate.

Phase 5 — Opt-in modernisations (only if asked). Walk the O-N rules in MIGRATION.md (O-1 rich metadata, O-2 reg-view, O-3 Malli, O-4 frames, O-8 routing, O-13 substrate move, O-15 :spawn-all). The three v1 add-on-library conversions are the highest-value O-rules — the idiomatic v2 replacement for the add-ons Phase 3 forced you to remove-or-convert: O-16 (async-flow-fx / :async-flow → reg-machine state machines — `references/async-flow-to-machines.md`), O-17 (http-fx / :http-xhrio → :rf.http/managed — `references/http-fx-to-managed-http.md`), O-18 (security + operational logging sweep on the M-13/M-17 observer surfaces). Each is Type B (ask first), detected by Maven coord + fx-key fingerprint (`references/breaking-changes.md`). The conversion path is opt-in here; acting on the broken add-on was forced at Phase 3. One further opt-in has no O-N slot yet but its own guide: shipclojure/re-frame-query + hand-rolled Pattern-RemoteData caches → re-frame2 resources (reg-resource / :rf.resource/*, Spec 016) — a query lib does not break the compile (its handlers migrate under M-73 regardless); Type B re-modelling, surfaced from Phase-0a: `migration/from-re-frame-v1/re-frame-query-to-resources.md`. Remaining O-rules are never auto-applied. (O-5 was promoted to M-51 — binary fx is required, not opt-in.)

Phase 6 — Report. Produce the migration report per MIGRATION.md Part 2 §"Output format for your report". → `references/output-format.md` — the format restated with one filled-in example.

Scaling to a large migration (opt-in)

The phases above assume one session walks them — the right default for most migrations. A genuinely large migration (Phase-0a inventory comes back with ~30 source files and rule-families colliding inside the same files) needs a scheduling layer on top, or the sweep fragments into merge conflicts and silent reverts the moment the work is split. That methodology — the one-file-one-owner partition, the Wave-0 id-contract, the bridge-handler idiom, wave sequencing, and the single all-or-nothing post-sweep compile gate — is about how you partition and sequence the work, not any particular execution harness. It composes with cardinal rule 4 (the partition + wave plan is the mass-rewrite announcement) and does not replace the phases — it schedules the Phase-3 sweep. The execution substrate is a separate choice: walk the units solo on a local branch, or fan them out across workers in isolated worktrees (each runs the single post-sweep gate itself — cardinal rule 5 is unchanged). Route here only when the migration is large → `references/orchestrating-a-large-migration.md`.

Boot & init (the one topic that crosses every phase)

Boot is where a real migration hits the most friction, because v2 changes app boot structurally. Get these four facts right together and you prevent an entire class of silent runtime breakage. (Each links to the leaf that owns the detail; this is the map, not a restatement.)

  1. init! + an app frame are action 0 (M-40) — a v1 app has NEITHER. (rf/init! <adapter>) installs the runtime but creates no frame; create an explicit app frame and establish it as a scope (make-frame + with-frame / frame-provider {:frame …}, or the root frame-root {:id …} ENSURE mount) before the first dispatch / render, or a scope-less boot dispatch throws :rf.error/no-frame-context. → `references/auto-cross-cutting.md` §Boot-sequence invariant.
  2. Seed via the frame's :initial-events, not a top-level app-db poke (M-15). They run inside the frame's scope (sidestepping the no-frame-context trap); seed with [:rf/set-db {…}]. The v1 top-level (reset! re-frame.db/app-db …) is also an M-1 off-contract-ns site. → `references/guided-handlers-state.md` §M-15.
  3. A wholesale {:db fresh} replace is now safe — but strip any :rf/runtime key (M-15b). Framework runtime moved to a separate partition a :db return can't touch (the v1 "boot wipes the runtime" footgun is gone); the residual hazard is a :db carrying the retired :rf/runtime root, which throws :rf.error/legacy-runtime-root (loud). → `references/guided-handlers-state.md` §M-15b.
  4. A singleton boot-machine needs an explicit start + address. If you converted v1 boot orchestration (async-flow-fx) to a machine (O-16), eager-start it from the frame's :initial-events (dispatch the reserved :rf.machine/start marker) and address it by its registered id — (rf/dispatch [:app/boot …]). A singleton is not :spawned: there is one, it is known at registration time, and the id IS the address. → `spec/Pattern-Boot.md` §Worked example; mechanics in the O-16 corpus companion `async-flow-fx-to-reg-machine.md` (routed via `references/async-flow-to-machines.md`).

Facts #1 and #4 are silent at compile (fact #1 is also on the Phase 4 checklist); fact #3 is loud. Verify the silent ones with the boot smoke-test, not the compiler.

The public multi-frame model is image -> frame -> event stream. An image is the selected registration set a frame runs (its instruction set), a frame is the isolated execution context (its memory), the event stream is the program. For a single-frame migration target you do not name an image — the ordinary reg-* path writes the default registration source and your one frame (the M-40 / M-15 boot above is all you wire) resolves the implicit default image over it. Reach for explicit images only when the app genuinely runs two isolated surfaces on one page, or wants isolated test/story frames. Authoring detail: the re-frame2 skill's references/fundamentals/frames.md.

Kickoff (paste-ready)

For delegating the migration to a fresh Claude session: `references/kickoff-prompt.md`. The author drops it into a session opened in the root of their v1 project; the session loads this skill and walks the two pre-flight phases plus six phases, surfacing Type B checkpoints.

Done checklist

  • Inventory-and-plan produced (Phase 0a): every v1 add-on (incl. transitives / git-source / vendored) and app feature inventoried, each add-on's source scanned for v2-broken surfaces, any re-frame-10x hit recorded as a REQUIRED Xray-swap deliverable (no M-N id — caught only here), and a per-item plan written.
  • React-19 / Reagent-2 floor gate cleared (Phase 0b): React-lib compat audited; component-library React-19 support confirmed (declared release or empirical runtime pass); ReactDOM.render flagged; shadow-cljs/Closure toolchain skew checked; CI test-runner browser bumped; go/no-go recorded.
  • re-frame/re-frame removed from every dep file; day8/re-frame2 + adapter at a matching VERSION.
  • Project compiles cleanly with re-frame2 on the classpath.
  • Every tripped M-rule has been applied (Type A) or resolved by the author (Type B).
  • Existing test suite passes (or fails identically to pre-migration — no new failures).
  • Boot smoke-test passed (Phase 4 step 3) — "compiles" is not the done-bar. Driven through a connected runtime, or run by the programmer from the skill's checklist and reported as pending until they confirm. App booted in a dev build; live app-db + machine snapshots read (runtime-db [:rf.runtime/machines :snapshots] carries every expected boot/singleton machine); first-screen subs deref to real values (not nil); one event per feature surface dispatched and the affected slot re-read; boot trace scanned for :rf.error/* / :rf.warning/* (incl. the hard error :rf.error/legacy-runtime-root if a handler still writes a :rf/runtime app-db root). None of the silent-runtime-failure modes (`references/silent-runtime-failures.md`) present.
  • Xray replaces 10x (REQUIRED deliverable when Phase-0a detected 10x) — BOTH halves landed, not just the M-0 drop. For a project that used day8.re-frame/re-frame-10x: 10x dep + preload dropped at M-0 AND Xray dep + preload + [data-rf-xray-host] host + the npm peer-deps (@xyflow/react, elkjs) wired post-M-40, panel verified (Ctrl+Shift+C). DONE-state: the app is ON Xray — a checked box here means stage 2 landed, not merely that the dead preload was removed. (No re-frame-10x in the project ⇒ nothing to check; don't add devtools the app never had.)
  • Migration report (per MIGRATION.md Part 2 / references/output-format.md) produced and shared.
  • Items flagged for human review are explicitly listed in the report.

Hand off: "Migration complete. Switch to re-frame2 for new application code, or re-frame2-pair for live inspection. If you want Fresco views, reagent-migration is the optional second step — staying on Reagent is a first-class, fully-supported choice. The opt-in modernisations (O-N rules) are available whenever you want them — not required to be on v2."

Anti-patterns

  • Don't apply Type B rewrites silently — the Type A / Type B distinction exists precisely because Type B changes can break working code (e.g. M-3 run-to-completion semantics).
  • Don't bump unrelated dependencies — keep the diff to what the migration forces. The explicit exemption: the Phase-0b GO-state edits ride into the M-0 pass — React → 19, Reagent → 2.x (only if pinned directly), floor-gate-approved component-lib bumps, the shadow-cljs (Check 4) bump, any explicit ClojureScript pin, plus the Xray npm peer-deps (@xyflow/react, elkjs) when the 10x→Xray swap fires. MIGRATION.md Part 2's "do not introduce new dependencies" rule carries the same carve-out, so the two texts agree and the corpus-wins precedence has nothing to resolve here. Banned: bumping a random utility/UI dep "while you're in there." Record every non-re-frame dep you changed, and the gate that justified it, in the report (cardinal rule 3 + `output-format.md`).
  • Don't add -schemas / -machines / -routing "to be safe" — the artefact split is pay-as-you-go (M-27 through M-33).
  • Don't migrate plain-Reagent fns to reg-view reflexively — that's O-2 (opt-in). The one forced case is M-11 (the Phase-3 gate above): a subscribing plain (defn …) can't read the provider frame and throws :rf.error/no-frame-context (EP-0002; no :rf/default fall-through), fixed by reg-view or by carrying the frame in explicitly (a targeted (rf/capture-frame frame-id) / a {:frame …} op opt / a handle captured in a frame-aware ancestor and threaded down) or by giving the fn its own with-frame scope — a bare no-arg (rf/capture-frame) in the unregistered fn re-raises. A non-subscribing / self-scoping fn needs no change; don't over-convert. (A callback that escapes the render scope is a separate, additive async-frame-loss hazard.)
  • Don't touch re-frame-test namespaces eagerly — renamed to re-frame.test-support (M-25); apply as a mechanical pass. Don't rewrite test bodies unless they trip a separate rule.
  • Don't treat a clean compile as "done." The done-bar is a clean boot smoke-test, not a clean compile — v2 relocates a class of failures to runtime that only a live-introspection boot smoke-test catches (Phase 4; `references/runtime-smoke-test.md`).
  • Don't claim "migrated" before the report is written — the report is the contract.

(The "announce, then sweep" and "the skill runs the gates it discovers" rules are Cardinal rules 4 and 5 — owned there, not restated here.)


Authoritative breaking-change list: `MIGRATION.md`. v1 line: re-frame. Full skill-disambiguation matrix: `skills/README.md` §Skill routing — single source.