diff --git a/README.md b/README.md
index ff328b4..aa1c983 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,226 @@
# spectral
-A monolithic kernel work wrapper to make it easy.
\ No newline at end of file
+A monolithic kernel work wrapper, to make it easy.
+
+> **Status: scaffold.** The command surface parses, every verb is wired
+> through `main` to its module, and the paths resolve — but the bodies are
+> `todo!()`. Twenty of them. Nothing builds a kernel, boots qemu, scrapes
+> bugzilla, or sends mail yet. What exists is the shape of the thing and the
+> seams to fill in, which is exactly what the *Where to pitch in* section
+> below is for.
+
+## Why
+
+The kernel patch lifecycle is a dozen commands you retype every time. Find
+something to fix, write it, run checkpatch, run it again after `--fix`,
+commit with the right trailer, build, boot it under qemu, diff against
+master, work out who the maintainers are for those files, send. Then a week
+later do it all again as v2, remembering to thread it off the original.
+
+None of those steps are hard. They are just easy to get subtly wrong, and
+the subtle mistakes — a missing `Signed-off-by`, a v2 that doesn't thread,
+a missing CC — are the ones that get a patch ignored rather than reviewed.
+
+spectral is a thin wrapper over that loop. It is deliberately *thin*: every
+verb shells out to a tool you already have and already trust.
+
+## What it wraps
+
+Nothing here reimplements kernel tooling, and nothing vendors it:
+
+| spectral | actually runs |
+|---|---|
+| `patch check` / `patch format` | `scripts/checkpatch.pl` **from your tree** |
+| `patch commit` | `git commit` |
+| `patch create` | `git diff ` |
+| `patch submit` | `scripts/get_maintainer.pl` **from your tree**, then `git send-email` |
+| `kernel test` | `make`, then `qemu-system-x86_64` |
+| `kernel quest` | HTTP against `bugzilla.kernel.org` |
+
+The two scripts are called out because it matters: they come from the tree
+you are working in, so spectral cannot have a stale copy of the kernel's own
+style rules or maintainer map. Upgrade your tree, get the new rules.
+
+And because it is thin, you can always drop the wrapper. `patch submit
+--dry-run` prints the recipients and the exact `git send-email` command line
+instead of sending, so you can check the plumbing or just run it yourself.
+
+## The loop
+
+What the finished tool should feel like — none of this runs yet:
+
+```console
+$ spectral kernel quest
+12345 usb: xhci: device does not enumerate after resume
+https://bugzilla.kernel.org/show_bug.cgi?id=12345
+
+# ... go and fix it, in the tree ...
+
+$ spectral patch check
+$ spectral patch format # checkpatch --fix, then check again
+$ spectral patch commit "usb: xhci: re-arm the port after resume" --signoff
+$ spectral kernel test # build, then boot it under qemu
+$ spectral patch create 000-xhci-port-rearm
+~/.spectral/patches/000-xhci-port-rearm.patch
+
+$ spectral patch submit 000-xhci-port-rearm.patch --dry-run
+# happy? drop --dry-run and it goes out
+
+# ... wait for review ...
+
+$ spectral patch update 000-xhci-port-rearm.patch -v 2
+~/.spectral/patches/v2-000-xhci-port-rearm.patch
+$ spectral patch submit v2-000-xhci-port-rearm.patch --in-reply-to ''
+```
+
+One deviation from the obvious design: `patch update` is a single subcommand
+taking `-v N` rather than a generated `update-v2` / `update-v3`. clap
+subcommands are static, and a flag beats a subcommand you cannot tab-complete.
+Leave `-v` off and it infers the next revision from the file's current `vN-`
+prefix.
+
+## Install
+
+Needs a recent stable Rust — edition 2024, so **1.85 or newer**; developed
+against 1.98.1.
+
+```console
+$ git clone https://git.spectoria.dev/huntedbytheirs/spectral.git
+$ cd spectral
+$ cargo build --release
+$ install -Dm755 target/release/spectral ~/.local/bin/spectral
+```
+
+Not on crates.io, so `cargo install spectral` will get you something else.
+`cargo install --path .` from a clone works too.
+
+Beyond Rust you want:
+
+- **a kernel tree** — for `scripts/checkpatch.pl` and
+ `scripts/get_maintainer.pl`. Both ship with the kernel; spectral does not
+ carry its own.
+- **git with `send-email` configured** — `git send-email` must work from a
+ plain shell first. If it does not, `patch submit` cannot make it work, and
+ is not meant to.
+- **qemu** — `qemu-system-x86_64` on `$PATH`, for `kernel test`.
+
+## Configuration
+
+There is no config file yet. Two paths matter, and both come from the
+environment with `$HOME`-relative defaults:
+
+| what | resolution |
+|---|---|
+| kernel tree | `$SPECTRAL_KERNEL`, else `~/.spectral/linux` |
+| patch dir | `~/.spectral/patches` |
+
+```console
+$ export SPECTRAL_KERNEL=$HOME/src/linux
+```
+
+A tree is accepted only if it has `scripts/checkpatch.pl`; otherwise you get
+`not a kernel source tree` rather than a confusing failure three steps later.
+A missing tree reports the path and the variable that would have set it. The
+tree is not required for `kernel quest`, which only needs the network.
+
+`~/.config/spectral/config.toml` and a `spectral init` to clone the tree are
+planned, not built — `src/config.rs` is the only file that will have to change
+for either.
+
+## Commands
+
+```console
+$ spectral --help
+A monolithic kernel work wrapper to make it easy.
+
+Usage: spectral
+
+Commands:
+ kernel Find work, build it, boot it
+ patch Carry a change from working tree to mailing list
+ help Print this message or the help of the given subcommand(s)
+```
+
+`spectral kernel quest` · `kernel test`
+
+`spectral patch check` · `format` · `commit` · `create` · `submit` · `update`
+
+Every one of them has `--help` that says more than this README does — and,
+for now, a body that panics with a description of what it is supposed to do.
+
+## Where to pitch in
+
+Every stub is a small, self-contained function with its signature and doc
+comment already written, its caller already wired, and a `todo!()` naming the
+command it should run. Pick one and the blast radius is that file. No stub
+needs you to have read the rest of the crate.
+
+Roughly in order of how much they unblock:
+
+**`patch submit` — the most self-contained win.** Needs a kernel tree but no
+network and no scraper.
+
+- `patch/maintainers.rs` · `lookup` — run `get_maintainer.pl --git` over the
+ patch, split the output into `To:` and `Cc:`, and collect the files the
+ patch touches
+- `patch/maintainers.rs` · `add_cc` — fold `--cc` flags in without duplicates
+- `patch/mod.rs` · `submit` — the above plus `git send-email`, with
+ `--dry-run` stopping one step short
+
+**`patch check` / `format` / `commit` / `create` — the everyday verbs.**
+`patch/checkpatch.rs` · `run` already defines the `Target` enum (working
+tree, a revision, or a patch file) and `Report` with an `is_clean`, so what
+is missing is the process call and parsing the error/warning counts out of
+its output.
+
+**`patch update` — small, and worth doing with a test.** `patch/mod.rs` ·
+`reroll_path` is the naming rule in one pure function: strip one leading
+`vN-`, prepend `v-`. Re-running it at the same revision should be a
+no-op, which is easier to assert than to describe.
+
+**`kernel quest` — the fun one, if you like HTML.** `kernel/quest.rs` ·
+`Bugzilla::fetch_open` is the only place `reqwest` and `scraper` earn their
+place in `Cargo.toml`; `run` then applies `--filter` and picks one. The
+`QuestSource` trait is the seam for swapping bugzilla for syzbot or a lore
+thread later.
+
+**`kernel test` — needs a machine you are willing to boot kernels on.**
+`kernel/qemu.rs` · `build` then `boot`. Streaming serial output as it arrives
+beats buffering it until qemu exits, which is what makes a boot hang
+diagnosable.
+
+**`git.rs` — the four plumbing calls.** `current_branch`, `diff_against`,
+`commit`, `rev_parse`, each a few lines over the `run` that is already
+written. Take these if you want to warm up on something tiny.
+
+There are no tests yet. The naming rule in `reroll_path` and the recipient
+split in `lookup` are the two that most want one.
+
+## Development
+
+```console
+$ cargo fmt --all
+$ cargo clippy --all-targets -- -D warnings
+$ cargo test
+```
+
+All three are clean on `main` and are the bar for a change.
+
+A few conventions, in the spirit of keeping the crate reviewable:
+
+- `src/cli.rs` holds the whole command surface. It is one file on purpose —
+ the CLI is the specification, and reading it top to bottom should tell you
+ what the tool does.
+- Command modules return what they produced; `main` prints it. Output policy,
+ formatting and all, lives in one place.
+- Errors go through the one `Error` enum and `?`. The command paths have no
+ `unwrap`.
+- A new stub gets a `todo!()` that names the command it will end up running.
+ If a whole module is unreachable until its caller exists, it carries a
+ single `#[allow(dead_code)]` with the reason attached — there are no
+ crate-wide allows, so the warnings come back as the stubs get filled in.
+- No `unsafe`, no FFI.
+
+## License
+
+MIT — see `LICENSE`, © 2026 huntedbytheirs.