docs: revise jerboa-on-jerboa around dogfooding and veneers

ober

8ee4e321fe7ef32eb94da2d03526f4f3dd640a21

diff --git a/docs/jerboa-on-jerboa.md b/docs/jerboa-on-jerboa.md
index 12e151b..48ff003 100644
--- a/docs/jerboa-on-jerboa.md
+++ b/docs/jerboa-on-jerboa.md
@@ -1,305 +1,286 @@
 # Jerboa on Jerboa
 
-A staged plan to rewrite as much of Jerboa as possible *in Jerboa itself*.
+A staged plan to make the editing experience of Jerboa *be* Jerboa.
 
-## Thesis
+## The problem
 
-Today, Jerboa is a Chez-hosted language: `.ss` source is translated to `.sls`
-and executed on Chez Scheme. The language implementation and the standard
-library are written in R6RS `.sls`. The character of "Jerboa" — `def`,
-`defstruct`, `match`, `[...]` lists, `{...}` hashes, `keyword:` args — appears
-only in user code (tests, examples, the `jerboa-*` companion repos).
+Jerboa has a substantial, distinctive feature set: `def` with destructuring,
+`defstruct`, `match`, `[...]` list literals, `{...}` hash literals, `keyword:`
+args, `try-catch`, `defmethod`.
 
-This is backwards. A language's stdlib is the most-read corpus of code in
-that language. If our stdlib is R6RS, then *Jerboa* is a syntactic veneer
-applied to user code, not a language. Self-hosting — in the form of an
-idiomatic stdlib and tooling layer written in `.ss` — is what makes Jerboa
-its own thing.
+**None of these features are used by the language's own implementation.**
 
-This document is a plan, not a commitment. It is meant to make the work
-concrete enough that a contributor can pick a stage and execute it.
+When you open any file in `lib/`, you see R6RS Chez: `define`,
+`define-record-type`, `(library ...)`, `let-values`, `cond` chains, alists.
+The Jerboa idiom lives entirely in user code — `tests/`, `examples/`,
+companion repos. The language's *own* corpus, the canonical example of "how
+Jerboa is written," is a Chez codebase wearing a Jerboa name on the import
+path.
 
-## What "self-hosted" means here
+This is the thing we are fixing. When editing code under `lib/`, you should
+almost always see Jerboa, not Chez.
 
-We use the term in the standard staged-bootstrap sense, the way GCC, Rust,
-and Clojure use it:
+## The principle: editing surface, not purity
 
-- **Stage 0**: the language's reader/translator/core macros, written in the
-  host language (Chez R6RS). This *must exist* — something has to read the
-  first `.ss` file.
-- **Stage N**: the same components, re-expressed in the language itself. The
-  stage-0 implementation is retained as bootstrap, not deleted.
+We are not chasing self-hosting purity. We are not trying to replace Chez as
+the runtime. We are not racing toward a byte-identical fixed-point bootstrap.
 
-We are not trying to replace Chez Scheme as the runtime. The WASM backend in
-`lib/jerboa/wasm/` is a separate question and out of scope for this plan.
+We are chasing one number: **the fraction of `lib/` you can read as Jerboa.**
 
-## Current state (2026-05-13)
-
-Lines of code, excluding `lib/thunderchez/` (now removed):
+That goal is satisfied by a tactic earlier plans treated as a fallback:
+**the veneer pattern.** If a module genuinely must contain raw Chez code
+(FFI declarations, `foreign-procedure`, host-record manipulation, primitive
+macros), the Chez code lives in a private `_internal.sls` next to a public
+`.ss` file that re-exports it. Even if the `.ss` is a "silly" one-line
+re-export per binding, that is the right outcome: the import path users and
+contributors touch is `.ss`, and the editing surface is Jerboa.
 
-| Layer                                       | Files | LOC      |
-| ------------------------------------------- | ----- | -------- |
-| Stdlib (`lib/std/**/*.sls`)                 | ~510  | ~140,000 |
-| Language impl (`lib/jerboa/**/*.sls`)       | ~30   | ~14,000  |
-| Jerboa source (`tests/`, `examples/`, etc.) | ~400  | ~72,000  |
+We are willing to write a lot of silly veneers. That is the cost of having a
+language whose own stdlib is written in the language.
 
-The 154k lines of `.sls` are the entire language implementation plus stdlib.
-The 72k lines of `.ss` are user-level code that *consumes* Jerboa but doesn't
-*define* it.
+## What this is NOT
 
-## The bootstrap floor
+- Not "replace Chez Scheme as the runtime." Chez stays.
+- Not "rewrite the compiler in itself for its own sake." Stage-0 stays
+  in-tree for bootstrap, forever if it has to.
+- Not "rebuild the WASM backend on WASM." Out of scope.
+- Not "byte-identical reproducible stage-N." That is its own problem; we
+  are not paying for it here.
 
-These files cannot be written in Jerboa — they define what Jerboa is.
+## What this IS
 
-| File                              | LOC  | Why it must stay R6RS                                        |
-| --------------------------------- | ---- | ------------------------------------------------------------ |
-| `lib/jerboa/reader.sls`           | 893  | Parses `.ss` syntax — needs to exist before any `.ss` runs   |
-| `lib/jerboa/core.sls`             | 1248 | Defines `def`, `defstruct`, `match`, `try-catch` macros      |
-| `lib/jerboa/translator.sls`       | 811  | `.ss` → `.sls` transforms                                    |
-| `lib/jerboa/runtime.sls`          | 426  | Hash/keyword/method-dispatch primitives on Chez records      |
-| `lib/jerboa/prelude*.sls`         | 712  | Re-exports that wire the above together                      |
-| `lib/jerboa/ffi.sls`              | 137  | Wraps Chez `foreign-procedure`                               |
+Three things, in order:
 
-**Subtotal: ~4,200 LOC of irreducible stage-0.**
+1. **Establish the veneer pattern** so we can claim any file as `.ss` even
+   when its guts are Chez.
+2. **Aggressively migrate `lib/` to `.ss`** with the veneer pattern as
+   escape hatch.
+3. **Bind it with a hard rule going forward**: new code in `lib/` is `.ss`,
+   enforced by CI.
 
-Note: stage-N versions of `reader.sls`, `translator.sls`, and even pieces of
-`core.sls` *can* be written in Jerboa once Jerboa exists. The stage-0 files
-are then retained only for bootstrap. This is Stage 3 below, and is the most
-ambitious step.
+## Current state (2026-05-13)
 
-## What's actually rewritable
+```
+lib/std/**/*.sls       ~510 files,  ~140,000 LOC   ← stdlib, ~all R6RS
+lib/jerboa/**/*.sls    ~30  files,   ~14,000 LOC   ← language impl, ~all R6RS
+tests/, examples/      ~400 files,   ~72,000 LOC   ← Jerboa source (.ss)
+```
 
-### Tier A — pure logic, would be meaningfully better in Jerboa
+`.ss` LOC under `lib/`: **~0%.**
 
-Files in `lib/jerboa/` outside the bootstrap floor:
+That is the number to move.
 
-| Module                                          | LOC  | Notes                                                                  |
-| ----------------------------------------------- | ---- | ---------------------------------------------------------------------- |
-| `pkg.sls` + `lock.sls` + `registry.sls`         | 498  | Package manager. Manifests, semver, lockfiles — natural fit for `defstruct` + `match`. |
-| `cache.sls`                                     | 146  | Content-addressed compile cache.                                       |
-| `build.sls` + `build/musl.sls`                  | 1000 | Build orchestration.                                                   |
-| `hot.sls`                                       | 120  | File-watcher.                                                          |
-| `embed.sls`                                     | 190  | Sandbox API.                                                           |
-| `cross.sls`                                     | 159  | Target config.                                                         |
-| `clojure.sls` + `cloj.sls`                      | 594  | Clojure compat. Already a re-export layer; minimal logic.              |
-| `wasm/*`                                        | ~6300 | WASM backend. Pure data manipulation over WASM module structures.     |
+## Target end-state
 
-**Tier A subtotal: ~9,000 LOC.** None of this depends on Chez-specific
-features that aren't already exposed through the Jerboa prelude.
+- **`.ss` file fraction under `lib/`: >90%.**
+- **Every public import path under `lib/std/**` is a `.ss` file.** Chez-only
+  modules live behind `_internal.sls` / `_chez.sls` private names.
+- **Every file under `lib/jerboa/` except the bootstrap kernel is `.ss`.**
+- **CI fails any PR adding a new non-private `.sls` to `lib/`.**
 
-### Tier B — stdlib pure code
+## What truly must stay `.sls`
 
-Most of `lib/std/**` is pure code that doesn't depend on R6RS specifically.
-Rough breakdown of the 140k LOC:
+The irreducible minimum — what *cannot* be `.ss` because it implements the
+machinery that makes `.ss` runnable in the first place:
 
-| Subtree                                  | LOC    | Self-hostable? |
-| ---------------------------------------- | ------ | -------------- |
-| `std/net/` (HTTP, URI, DNS, TLS glue)   | 14,841 | Mostly yes; some TLS/socket FFI stays stage-0 |
-| `std/misc/`                              | 12,799 | Yes — utility code         |
-| `std/text/` (JSON, YAML, regex, markup)  | 7,219 | Yes                        |
-| `std/srfi/`                              | 6,575 | Yes                        |
-| `std/security/`, `std/secure/`           | 8,570 | Mostly yes                 |
-| `std/typed/`                             | 4,052 | Yes                        |
-| `std/actor/`, `std/csp/`, `std/concur/`  | 7,328 | Yes — built on top of Chez threading primitives |
-| `std/os/`                                | 6,792 | Partial — syscall wrappers stay; higher-level path/process logic can move |
-| `std/ffi/`                               | 3,574 | **No** — these *are* the FFI bindings |
-| Top-level (`csv`, `time`, `transducer`, etc.) | 41,441 | Yes |
+| File                        | LOC  | Why                                                          |
+| --------------------------- | ---- | ------------------------------------------------------------ |
+| `lib/jerboa/reader.sls`     | 893  | Parses `.ss`. Must exist before any `.ss` loads.             |
+| `lib/jerboa/core.sls`       | 1248 | Bootstrap macros for `def`/`defstruct`/`match`/etc.          |
+| `lib/jerboa/translator.sls` | 811  | Bootstrap `.ss` → `.sls`.                                    |
 
-**Tier B rough estimate: ~120k LOC self-hostable**, ~10k FFI/syscall glue
-stays as stage-0 `.sls`.
+**Hard bootstrap floor at stage 0: ~3,000 LOC.**
 
-### Total
+Later, *parts* of `core.sls` and `translator.sls` can be re-expressed in
+Jerboa once Jerboa exists (Stage 3). The fraction that stays R6RS shrinks;
+the stage-0 versions remain for first-build only.
 
-- **Must stay R6RS**: ~4k (bootstrap floor) + ~10k (FFI/syscall shims) = ~14k LOC
-- **Could move to idiomatic Jerboa `.ss`**: ~140k LOC
+Everything else — `runtime.sls`, `prelude*.sls`, `ffi.sls`, all of
+`lib/std/**` including FFI shims and syscall wrappers — gets a `.ss`
+editing surface, even if a private `.sls` companion does the host-talking.
 
 ## Staging plan
 
-The plan is incremental. Each stage produces a shippable artifact and a
-clear gate before the next stage begins.
-
-### Stage 0 — already done
-
-The current state. Chez-hosted, Jerboa as a syntactic frontend. Tests and
-companion repos written in `.ss`.
-
-### Stage 1 — Tier A migration (`lib/jerboa/` non-bootstrap)
-
-Move pkg/lock/registry/cache/build/hot/embed/cross from `.sls` to `.ss`.
-Rewrite *idiomatically* (see "Migration mechanics" below), not as a sed
-pass. The WASM backend (`wasm/*`) is its own substage; defer if too large.
-
-**Why first**: these are the smallest, most-changing modules. They get the
-most benefit from being in Jerboa (`defstruct` for manifests, `match` for
-dispatch, `keyword:` args for builder APIs). They're isolated — no other
-stdlib module imports `(jerboa pkg)` or `(jerboa build)`.
-
-**Acceptance**:
-- `make binary` succeeds.
-- Full test suite passes.
-- No file in this tier imports `(chezscheme)` directly except via the
-  prelude.
-- Each migrated file uses at least three Jerboa-specific features
-  (`def` + `[...]` + `keyword:` is the minimum bar).
-
-**Estimated effort**: ~9k LOC of rewriting. Probably 3–4 weeks of focused
-work, mostly mechanical once the idiom is settled.
-
-### Stage 2 — stdlib pure-logic modules
-
-Move the Tier B stdlib subtrees in priority order:
-
-1. **Top-level utilities first** (`std/csv`, `std/transducer`, `std/transit`,
-   `std/zipper`, `std/variant`) — small, self-contained, no downstream
-   dependents on their `.sls` paths.
-2. **`std/misc/**`** — heavily used internally; migrate once the pattern is
-   proven.
-3. **`std/text/**`** — JSON, YAML, regex. These are widely depended on; do
-   them carefully with golden-output tests.
-4. **`std/srfi/**`** — by SRFI number, in dependency order.
-5. **`std/typed/`, `std/security/`, `std/actor/`, `std/csp/`, `std/concur/`,
-   `std/net/`, `std/os/` (non-syscall portions)** — large, internally
-   coherent subtrees; migrate one at a time.
-
-**Why this order**: minimize coordination cost. Each subtree should be a
-single PR if possible. The migration order is "fewest reverse dependencies
-first."
-
-**Acceptance per subtree**:
-- `make binary` succeeds.
-- `make test` passes.
-- For text/data modules, golden-output equivalence with the pre-migration
-  version (byte-for-byte JSON output, regex match results).
-- No new performance regression beyond a documented budget (say, 10%).
-
-**Estimated effort**: many months. This is the bulk of the work.
-
-### Stage 3 — re-implement the bootstrap floor in Jerboa
-
-The ambitious step. Write `reader-jerboa.ss`, `translator-jerboa.ss`,
-`core-jerboa.ss` that re-implement the same surface in Jerboa. Use them in
-the standard build; keep the `.sls` versions as the stage-0 bootstrap (used
-to build the stage-N versions for the first time).
+The plan is aggressive on purpose. Dogfooding *is* the win — there is no
+ergonomic-payoff gate before Stage 2. If the migration produces files that
+compile and pass tests, it's working.
 
-**Why last**: cannot be done before Stage 2 because the stage-N versions
-should *use* the Jerboa stdlib, and the stage-N reader must be expressible
-in Jerboa idioms. Doing it before the stdlib migrates would force the
-stage-N reader to be written in R6RS-idiomatic-but-`.ss`, which defeats
-the purpose.
+### Stage 0 — done
 
-**Acceptance**:
-- Two-stage build verifies fixed-point: stage-0 builds stage-N1; stage-N1
-  builds stage-N2; stage-N1 and stage-N2 produce byte-identical output.
-- `make binary` with the stage-N reader/translator passes the full test
-  suite.
-- Stage-0 sources stay in-tree, used only for first-build bootstrap.
+Current state.
 
-**Estimated effort**: ~3k LOC of careful new code. Multiple weeks at minimum.
+### Stage 1 — establish the veneer pattern and the migration template
 
-## Migration mechanics
-
-### What "idiomatic Jerboa" means in a migrated file
+Goal: produce the template that every other migration follows.
 
-Mechanical translation (`define` → `def`, `(list ...)` → `[...]`) is theater.
-A migrated file should use Jerboa features that the R6RS version couldn't
-express:
+**Pilot files** (one PR each):
+- A pure-logic file: `lib/jerboa/lock.sls` → `lock.ss`. Tests `defstruct`
+  + `match` + `keyword:`.
+- An FFI veneer: pick one small file in `lib/std/ffi/` or `lib/std/os/`,
+  split into `_chez.sls` (private) + `.ss` (public re-export). Tests the
+  veneer pattern, including silly one-line re-exports.
+- A macro-heavy stdlib helper: a small file with `define-syntax`. Tests
+  the macro-portability boundary early.
 
-- `def` with destructuring instead of `define` + `let-values`
-- `defstruct` for record types instead of `define-record-type`
-- `match` on values instead of `cond` chains
-- `[...]` list literals where lists are constructed inline
-- `{...}` hash literals where alists were used
-- `keyword:` arguments for any function with more than ~3 positional args
-- `try-catch` instead of `guard` / `with-exception-handler`
-- `defmethod` for any informal type dispatch
+**Deliverables**:
+- `docs/jerboa-on-jerboa-idiom.md` — the "what idiomatic Jerboa looks like"
+  reference.
+- `tools/audit-r6rs-in-ss.sh` — greps for banned R6RS forms in `.ss` files
+  under `lib/`. Initially a stub that flags `define-record-type` and
+  `(library`; tightens over time.
+- A pre-commit hook (or CI step) that runs the audit script.
+- `CLAUDE.md` updated: **new code in `lib/` MUST be `.ss`.**
 
-A migration is not "done" until these features are *used*, not just
-*available*.
+**Acceptance**: three migrated files; idiom doc; audit script; CI hook;
+`CLAUDE.md` rule.
 
-### How to verify equivalence
+### Stage 2 — migrate `lib/` aggressively
 
-For pure functions (parsers, serializers, validators), the cheapest verifier
-is golden output: run the old `.sls` and new `.ss` against a corpus and
-diff. For stateful modules, a unit test pass plus the full integration suite
-is what we have.
+Two parallel tracks. They do not gate each other.
 
-A new `tools/compare-migration.sh` script would make this repeatable. Not
-required for stage 1; will be required for stage 2.
+**Track A — `lib/jerboa/` (non-bootstrap)** — ~9k LOC:
+- `pkg`, `lock`, `registry`, `cache`, `build`, `build/musl`, `hot`,
+  `embed`, `cross`, `clojure`, `cloj`, `wasm/*`: all to `.ss`.
+- `runtime`, `prelude*`, `ffi`: to `.ss` with private `_*.sls` companions
+  carrying the Chez-touching parts.
 
-### What stays as `.sls`
+**Track B — `lib/std/**`** — ~140k LOC.
 
-- FFI shims in `lib/std/ffi/**` — these *are* the C ABI bindings, not code.
-- Syscall wrappers in `lib/std/os/**` that call `foreign-procedure` directly.
-- The bootstrap floor (see "The bootstrap floor" above).
-- `lib/thunderchez/` (already removed as of 2026-05-13).
+Priority is "what does a reader of stdlib see first" — high-traffic
+subtrees first, FFI/syscall pattern-stressors last:
 
-## Risks and non-goals
+1. `std/srfi/`, `std/misc/` — high-traffic, high-leverage.
+2. `std/text/` — JSON, YAML, regex. The "look how parsing is written in
+   Jerboa" exemplars.
+3. `std/net/` — most user code touches HTTP/URI.
+4. `std/actor/`, `std/csp/`, `std/concur/` — concurrency primitives.
+5. `std/typed/`, `std/security/`, `std/secure/`.
+6. Top-level (`std/csv`, `std/transducer`, `std/transit`, `std/time`, …).
+7. `std/os/`, `std/ffi/` — last, because the veneer pattern is most
+   exercised here and by then we have practice.
 
-### Risks
-
-**Performance regression.** The `.ss` → `.sls` translator may produce
-worse code than hand-written R6RS in places. Mitigation: keep the golden
-test suite, and benchmark hot paths (JSON parse, regex match, HTTP framing)
-before/after each subtree migration.
+**Acceptance per subtree** (one PR per subtree):
+- `make binary` succeeds.
+- `make test` passes.
+- Golden-output equivalence for parsers/serializers (regex matches, JSON
+  output byte-equal to pre-migration).
+- `tools/audit-r6rs-in-ss.sh` passes on all migrated files.
+- **No public import path changes.** Users still `(import (std text json))`.
 
-**Debug-ability regression.** Stack traces through translated code may be
-harder to read than direct Chez. Mitigation: source maps in the translator,
-or at least line-preserving translation for runtime errors.
+### Stage 3 — shrink the bootstrap floor
 
-**Macro-system mismatches.** Some R6RS macros in the stdlib use
-`syntax-case` features that don't have direct Jerboa equivalents. Triage
-case by case; some macros may need to stay `.sls` as macro-helpers used by
-the `.ss` files.
+Once Stage 2 is far enough along that the Jerboa stdlib offers rich idiom,
+write `reader-jerboa.ss`, `translator-jerboa.ss`, `core-jerboa.ss` and
+default the build to them. Keep stage-0 `.sls` versions for first-bootstrap
+only.
 
-**Maintenance fork during migration.** Two-version state (`.sls` + `.ss`)
-is bad. Migrate a subtree in one PR, do not leave half-migrated trees in the
-tree across releases.
+Acceptance: stage-N builds itself, passes the test suite, and the build
+graph clearly distinguishes "bootstrap-only" from "production" sources.
+**Not** chasing byte-identical fixed-point.
 
-### Non-goals
+## Migration mechanics
 
-- **Replacing Chez Scheme.** This plan is about source-level
-  self-hosting. The runtime stays Chez.
-- **Removing `.sls` entirely.** The bootstrap floor and FFI/syscall shims
-  stay R6RS. ~14k LOC will always be `.sls`, and that's correct.
-- **Self-hosting the WASM backend on WASM.** Out of scope. The
-  `lib/jerboa/wasm/` code is Tier A as a stdlib migration candidate, but its
-  *output* is and remains WASM bytecode for browsers.
-- **Beating Chez at compile time.** The Chez compiler is staying.
+### The veneer pattern, in detail
+
+For a module that needs raw Chez (FFI, host records, primitive macros):
+
+```
+lib/std/foo/_chez.sls    (library (std foo _chez) ...)   ← private
+lib/std/foo/foo.ss        (jerboa prelude) re-exports + Jerboa wrappers
+```
+
+Rules:
+- `_chez.sls` is **not** in the public surface. Users importing
+  `(std foo)` get `foo.ss` exports.
+- The `.ss` veneer adds value when it can — keyword args, `defstruct`
+  return types, match-friendly sentinels. **But a one-line re-export is
+  acceptable.** The point is the editing surface.
+- Veneer files are exempt from the "must use ≥1 Jerboa-distinct feature"
+  guideline if all they do is re-export.
+
+### Idiom rules (enforced by audit script)
+
+In any non-veneer `.ss` file under `lib/`:
+
+**Forbidden** (CI failure):
+- `define-record-type` — use `defstruct`.
+- `(library (...) (export ...) ...)` — use Jerboa module declaration.
+- Bare top-level `define` for functions — use `def`.
+
+**Strongly discouraged** (warning):
+- `cond` chains over `car`/`cdr` of a known shape — use `match`.
+- Alists with atomic keys — use `{...}` hashes.
+- `let-values` for receiving a stable shape — use `def` destructuring.
+
+**Required** (audit checks):
+- At least one of `def` / `defstruct` / `match` / `[...]` / `{...}` /
+  `keyword:` per non-trivial file.
+
+Veneers are exempt from "required" — a `.ss` that just re-exports a
+`_chez.sls` is allowed to contain nothing but the Jerboa module form and
+re-exports.
+
+### Equivalence verification
+
+- Pure functions: golden output diff against pre-migration `.sls`. Build
+  `tools/compare-migration.sh` during Stage 1.
+- Stateful modules: unit tests + integration suite.
+- Don't migrate a file you can't detect a regression in.
+
+### What stays `.sls`
+
+- The bootstrap floor: `reader.sls`, `core.sls`, `translator.sls`
+  (Stage 3 shrinks the latter two).
+- Private `_*.sls` companions paired with `.ss` veneers.
+- That's it.
+
+## Risks (mitigated, not gates)
+
+- **Translator stability under stdlib-shaped code.** Today most `.ss` is
+  tests and companion repos — narrow distributions. Stdlib will stress
+  corners. **Mitigation**: Stage 1's macro-heavy pilot is the canary; if it
+  breaks the translator, we fix the translator before scaling Stage 2.
+- **Performance regression.** Translator output may be slower than
+  hand-written R6RS on hot paths (JSON parse, regex, HTTP framing).
+  **Mitigation**: benchmark hot paths before each subtree merge. Budget:
+  15% regression OK with a note; worse needs fixing or a design pass on
+  the translator.
+- **Debug-ability.** Translated code may produce harder-to-read stack
+  traces. **Mitigation**: source maps or line-preserving translation in
+  the translator. Treat as a first-class translator feature, not an
+  afterthought.
+- **Half-migrated tree across releases.** **Mitigation**: one subtree per
+  PR. No subtree spans a release boundary half-migrated.
+- **Companion-repo breakage.** `jerboa-gitsafe`, `jerboa-websearch`, etc.
+  depend on stdlib import paths. **Mitigation**: hard rule above — no
+  public import path changes. The veneer pattern is what makes this
+  rule keepable.
 
 ## Open questions
 
-These need answers before Stage 2 starts in earnest:
-
-1. **Translator stability.** Is the `.ss` → `.sls` translator stable enough
-   that 140k LOC depends on it without flux? Today most `.ss` code is tests;
-   stdlib code will exercise corners the translator hasn't seen.
-
-2. **Macro hygiene at scale.** Some R6RS modules cross-import macros from
-   each other. Does the Jerboa macro system handle 500-module compositions
-   cleanly? Stage 1 will exercise this on a small scale; Stage 2 is the
-   real test.
-
-3. **Conditional expansion.** R6RS `cond-expand` and Chez-specific build
+1. **Macro hygiene at stdlib scale.** The current Jerboa macro system
+   handles companion-repo composition. 500-module cross-imports is a
+   different scale.
+2. **Conditional expansion.** R6RS `cond-expand` and Chez build
    conditionals exist in `lib/std/os/`. The Jerboa equivalent surface may
    need extension before that subtree migrates.
-
-4. **Versioned stdlib.** Once the stdlib is in `.ss`, version-pinning a
-   stdlib for embedded use becomes easier. Worth a design pass before
-   Stage 2 ends.
-
-5. **Companion-repo coordination.** `jerboa-gitsafe`, `jerboa-websearch`,
-   etc. depend on stdlib import paths. Stable import paths during migration
-   are a hard requirement.
-
-## How to start
-
-If picking this up cold, the smallest useful first step is:
-
-1. Pick one Tier A file (suggested: `lib/jerboa/lock.sls` — small, isolated,
-   high-`defstruct` payoff).
-2. Rewrite it as `lib/jerboa/lock.ss` using the idiom guide above.
-3. Verify `make binary && make test` passes.
-4. Open a PR. This is the template for every subsequent migration.
-
-Stage 1 is the proof-of-concept. If Stage 1 doesn't deliver a tangible
-ergonomics win on the migrated files, Stage 2 should not start.
+3. **Clean-build cost.** 140k LOC of `.ss` → `.sls` translation per clean
+   build is real. The compile cache covers incremental, but CI clean
+   builds and `make docker-build` will feel it. Measure during Stage 1.
+4. **Versioned stdlib for embedded use.** Easier to version-pin a
+   Jerboa-source stdlib than a Chez-source one. Worth a design pass once
+   Stage 2 is well underway.
+
+## How to start (today)
+
+1. Pick `lib/jerboa/lock.sls`. Write `lib/jerboa/lock.ss` using
+   `defstruct`, `match`, keyword args. Verify `make binary && make test`.
+2. Pick the smallest file under `lib/std/ffi/`. Split into `_chez.sls` +
+   `.ss` veneer (even a one-line re-export). Verify the build.
+3. Write `tools/audit-r6rs-in-ss.sh` — stub: grep for `define-record-type`
+   in `*.ss` under `lib/`. Wire into a CI step.
+4. Open three small PRs, one per pilot. Update `CLAUDE.md` with the
+   "new code in `lib/` is `.ss`" rule.
+
+Stage 1 is done when those three PRs land and the idiom doc exists.
+Stage 2 starts the next day.