| name | re-frame-migration |
| description | 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. 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). **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.
|
| allowed-tools | ["Bash(rg *)","Bash(rg -l *)","Bash(git -C * rev-parse *)","Bash(git -C * remote get-url *)","Bash(git -C * ls-tree *)","Bash(gh issue list *)","Bash(gh issue view *)","Bash(gh issue create *)","Read","Edit","Write","Grep","Glob"] |
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, a dropped top-level :dispatch). 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 — the link opens migration/from-re-frame-v1/README.md, the file you actually read; every later "MIGRATION.md" means that one file.) Do not duplicate that content here. Load it 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.
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.)
- 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.)
- 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.
- Announce before a mass rewrite. Migration is destructive — Type A rewrites edit source in place. Before touching files, announce the sweep: the rule ("M-8 — fold top-level
:dispatch into :fx"), the count of files matched, and a one-line example diff shape; then pause for the author to abort or scope-limit. The gate is the sweep-level announcement, not per-file confirmation.
- The author runs builds, tests, and smoke-tests — not the skill. Compile /
npm test / clj -M:test / browser smoke are arbitrary-code execution against the author's machine and deps (a v1 project may pull a compromised transitive dep at compile time). The skill prints the exact command and waits for the pasted result; it never invokes them. The boundary is arbitrary-code execution, not any shell command: read-only provenance is a narrower class the skill DOES run itself — the corpus-pin checks (rev-parse HEAD = commit, remote get-url origin = remote NAME, ls-tree <SHA> implementation/… = multi-artifact STRUCTURE — see references/setup.md §Pin the migration corpus) and codebase greps (rg) inspect state without executing project code, so they're allow-listed (Bash(rg *) + the three scoped git -C * entries rev-parse / remote get-url / ls-tree).
Delegated / orchestrated / CI execution — the sandboxed-executor exception. "The author" in rule 5 may be a human, a sandboxed autonomous worker, or a CI runner — the trust boundary is arbitrary-code execution, not who types the command. Interactive: the human runs build / test / install / smoke on their own machine; rule 5's default holds (print the command, wait for the pasted result). Orchestrated / CI: the worker's isolated worktree (or the CI runner) is itself an isolated execution environment, so it runs the printed commands inside its own sandbox — never against a human's machine — and posts command + result into the PR body / CI log for ratification instead of stalling on a paste no human is there to give. Type-B checkpoints and the boot-smoke verdict stay the human's decision, surfaced asynchronously at PR review. This does not license running builds on an interactive author's machine — rule 5's default stands.
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 in skills/shared/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/setup.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 ask the author to compile before applying any other rules — most codebases need nothing more. The skill makes the dep-file + package.json edits and prints the compile + npm install commands; the author runs them (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-handlers-state.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 / view / db-seeding / error-handler / machine-spawn / Reagent-surface walkthroughs (M-3, M-5, M-10, M-11, M-12, M-13, M-14, M-15, M-34, M-42).
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 silently at boot (signal-fn reg-sub, a missing per-feature artefact, a dropped M-8 top-level key, a (when …) nil-thread losing seed state — full list in references/runtime-smoke-test.md); none show in the build log, all need live app-db inspection to find. So Phase 4 is three steps, not one:
- The author recompiles (the skill prints the exact compile command for the project shape —
shadow-cljs compile app, clj -M:test, the npm script — and waits for the pasted output; cardinal rule 5).
- The author re-runs unit tests (re-baseline render counts per M-12; no new failures).
- The author runs a 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. Done = this loop comes back clean, not the compile. → references/runtime-smoke-test.md (silent-failure checklist + smoke-test loop).
If any step fails, find the rule, apply it, ask the author to re-verify. The skill never executes build/test commands — see cardinal rule 5.
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-9 machines (:system-id), O-13/O-14 substrate moves, 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 (then cardinal rule 5's orchestrated mode keeps the partition collision-free). 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.)
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.
- 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.
- 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.
- 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 in the boot fn and address it by :system-id. → 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
Hand off: "Migration complete. Switch to re-frame2 for new application code, or re-frame2-pair for live inspection. 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. 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 before a mass rewrite" and "author runs builds/tests, not the skill" 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.