| name | rust-rewrite |
| description | Rewrite a codebase (or a large module) from Zig / C / C++ / Go / TypeScript into Rust using the multi-phase method Bun used on itself — big-bang translate to rough drafts, make each crate compile by gating, dissolve the stubs into properly-layered real code, then drive tests to green. Internalizes Bun's real workflows (classify, draft-port, tier-and-gate, proper-port, panic-swarm, test-swarm, unsafe-audit) as runnable scripts. Use for rewrite-it-in-rust / RIIR / oxidize / port to Rust / migrate a codebase to Rust at the scale of hundreds of files across many crates. Triggers: rewrite in Rust, port to Rust, migrate to Rust, RIIR, oxidize, rust rewrite, language migration. |
rust-rewrite
Rewrite a codebase from Zig / C / C++ / Go / TypeScript into Rust, using the method Bun used on itself. Not one clean pass. Bun ran ~50 dynamic workflows across ~7 distinct types, and the shape that matters is: draft with stubs → make each crate compile by gating what doesn't build → dissolve the stubs into real, properly-layered code → drive tests to green. 535,496 lines of Zig → Rust in 11 days, ~100 crates, ~16,000 compiler errors resolved, zero tests deleted.
This skill internalizes those real workflows as runnable scripts in workflows/ — it doesn't just describe them and leave you to reinvent the prompts.
Why it isn't translate → review → fix
Splitting one compilation unit into ~100 crates creates dependency cycles the instant you split it, so you can't port bottom-up cleanly. Bun did the opposite of "keep it green": translate everything to rough drafts fast, let the workspace be red, make it compile crate-by-crate by gating (hiding behind #[cfg(any())]) whatever doesn't build yet, and only then go back and do the real, properly-layered port. Tests are driven to green last, at scale. A skill that models a single translate → review → fix loop is modeling the wrong thing.
First decide: is it worth it
Heavy machinery — Bun's run cost ~$165,000 in API and 11 days. Good fit: a whole library / many files becoming Rust, with tests to anchor equivalence. Bad fit: a few hundred lines (hand-write it); no tests and no plan to add them (nothing judges equivalence). The bigger the codebase and the fuller the tests, the more this pays off.
Before you scale: prep, then a trial
Bun didn't start by porting files. Two things come first.
Prep (~3 hours with Claude). Build the two artifacts every later phase reads: PORTING.md — the Zig→Rust idiom/type rulebook, written by talking the patterns through with Claude (it's semantic-gotchas.md + the policy in porting.md, made concrete for your source) — and LIFETIMES.tsv (produced by phase 0). Then run one adversarial-review pass over PORTING.md and LIFETIMES.tsv themselves to fix conflicting rules, before a single file is ported. A wrong rulebook is a wrong port ×1,448.
Trial. Before looping every file, run the full pipeline on 3 files (1 implementer + 2 reviewers + 1 fixer) and read the drafts. Scale to all files only once the trial is clean — cheap insurance against a broken prompt burning the whole run.
Phases
Real Bun workflow names in parentheses; each has a runnable script in workflows/.
- Classify lifetimes (
lifetime-classify) → LIFETIMES.tsv. Every pointer field → an ownership class, before any translation. 3-vote adversarial refute on the uncertain ones + a ~12% sample of the confident ones; hard-cap total agents. → lifetime-classification.md
- Draft port (
phase-a-port). Mechanical Zig→Rust drafts — stubs and todo!() are allowed here. Output path is computed by rule and overrides whatever the agent picks; big files are written in ≤800-line chunks; generated files are skipped with a 3-line stub. → porting.md
- Tier + gate (
phase-b*-tier). Get each crate to cargo check green by gating what doesn't compile behind #[cfg(any())], lowest dependency tier first; un-gating comes later. This is how ~100 crates / 16,000 errors converge — with a compiler-error playbook, not by finishing each file. → tiering-and-gating.md
- Proper port (
phase-e-proper-port). Dissolve the stubs into real code. Layering is the root fix: a todo!("blocked_on: X") almost always means a crate cycle — the fix is to move the symbol (or its type) to a shared lower crate, not to stub it. Exit gate: zero todo!/gate/stub. 2-vote verify with reject → re-port. → porting.md
- Panic swarm (
phase-c-panic-swarm). Link the binary, run a real command battery (each with timeout), dedup failures by panic location, fix each unique one in parallel, loop until green. → verification.md
- Test swarm (
phase-g-test-swarm). Survey the suite (completing / crashing / hanging), dedup crash signatures, one real fix per signature, 2-vote review whose whole job is "real fix, not a suppression (early-return / if-null-skip / #[allow])". Bun drove 972 failing files → 23 → 0 this way. → test-swarm.md
- Unsafe audit (
phase-d-unsafe-audit). Hunt aliasing-UB patterns (&mut * on aliased pointers, as *const then as *mut), fix per a raw-ptr / UnsafeCell / restructure menu, 2-vote guard against over-correction. → unsafe-audit.md
Rules that run through all of it
- Big-bang, converge via gating. The workspace is not green for most of the run (Bun's was
~65 errs oscillating). "Green" is held per-crate by gating; the workspace converges last. This is the opposite of an incremental rewrite, on purpose.
- Tests are the only arbiter of equivalence — zero skips, zero deletes. The mechanism that makes them pass at scale is the test swarm (phase 5), and its reviewer rejects suppressions.
- Take decisions away from the agents. Output paths are computed and overridden if an agent picks its own; pointer types come from
LIFETIMES.tsv; everyone reads one shared PORTING.md. Agents translate; they don't architect.
- Separate context for review — not "diff-only". The reviewer runs in a fresh context told to assume the code is wrong. During the port it reads the whole source + draft; diff-only review is for the later committed passes. "Separate context" is the invariant; "diff-only" is not.
- Concurrency is real. ~16 agents per worktree, up to ~64 across 4 worktrees (peak
~170 workflow instances). Work is sharded by i % NSHARDS; agents re-read before every edit and retry on race; a single cargo check daemon writes a log agents read instead of each building.
- Commit safely in a swarm. Path-scoped adds only — never a bare
git add -A / git add . (they sweep in coredumps/heapsnapshots that carry env secrets); a path-scoped git add -A 'src/' is fine; core.hooksPath=/dev/null; messages phase-x(area): <file>; a fixed working branch; push per the orchestrator.
references
- lifetime-classification.md — 11-class ownership taxonomy, 3-vote + 12% sample, agent cap
- tiering-and-gating.md — per-crate compile via
#[cfg(any())] gating, the un-gate pass, the compiler-error playbook
- porting.md — draft (stub-OK) vs proper port (layering-first, zero-stub); deterministic paths; chunked write; std-ban; allocator threading
- adversarial-review.md — separate-context review, 2-vote, "real fix not suppression", the verifier checklist
- test-swarm.md — survey → dedup by crash signature → real fix → 2-vote
- unsafe-audit.md — aliasing-UB patterns and the raw-ptr / UnsafeCell / restructure fix menu
- semantic-gotchas.md — Zig→Rust idiom map + the real porting bugs
- workflow-orchestration.md — runnable Workflow scripts for every phase (daemon, sharding, chunked write, commit rules)
- verification.md — tests / panic-swarm / fuzz / benchmark gates