turbo-editors/turbo-rustpublic Fork 0
713ea5c03c3513c6f6bc4e94969e556ff736e038
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-rust.git
git clone ssh://git@rickub.com/turbo-editors/turbo-rust.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

📦 Turbo Rust 713ea5c · on 713ea5c03c3513c6f6bc4e94969e556ff736e038 · k33g · 12h ago
summary.md · 101 lines · 13.8 KBmarkdown
Blame HistoryOpen raw

turbo-rust — project summary

A snapshot of the present. No history here — that is history.md.

What this is

A Turbo C-style editor for Rust, written in Go: a full-screen terminal IDE with a menu bar, movable overlapping windows, modal dialogs, mouse support, Rust syntax colouring, loadable TOML themes, completion from rust-analyzer, terminal windows running a real shell, per-project settings, a project tree, snippets, and the cargo toolchain a menu away.

It is a thin editor on top of turbo-core, the library every Turbo editor shares. What is in this repository is main.go and internal/rustlang — about seven hundred lines, six hundred of which are the scanner.

Module path rickub.com/turbo-editors/turbo-rust. Go 1.26.5. Remote: ssh://git@rickub.com/turbo-editors/turbo-rust.git.

Created 2026-09-01, the same day turbo-core was extracted, and the reason it was extracted.

Architecture

Two packages here; everything else is the library.

main → {turbo-core/app, turbo-core/profile, turbo-core/settings, turbo-core/theme,
        turbo-core/version, internal/rustlang, tcell}
internal/rustlang → {turbo-core/profile, turbo-core/syntax}
File What it holds
main.go Flags, the terminal, and the wiring: register Rust, build the profile, read the project's settings, hand them to app.New, start rust-analyzer in the crate root, run the loop
internal/rustlang/rustlang.go The profile: name, slug, Rus~t~ menu, Cargo.toml root marker, rust-analyzer with the two directories it is looked for in
internal/rustlang/scan.go The scanner's dispatcher, comments and attributes
internal/rustlang/literals.go Strings, raw strings, byte literals, characters, lifetimes
internal/rustlang/words.go Numbers, keywords, types, macros
internal/rustlang/templates.go The three starter files a project gets

docs/diagrams/packages.drawio is generated from go list and verified against it edge for edge.

Decisions in force

  • The toolchain menu is Rus~t~, on Alt-T, not ~C~argo. R is Run's and S is Search's, so the hot key lands on the last letter of the word, which reads as an afterthought — and C was free. Naming it Cargo was still rejected: the menu holds whatever the project put in its tools file, and the first tools file anybody writes outgrows the language's own toolchain. A menu called Cargo holding docker compose up is a lie about what the menu is.
  • Rust is scanned by hand, in six hundred lines. Go has a lexer in its standard library and Turbo Go uses it; rustc is not a Go library and rust-analyzer's parser is a Rust crate, so the choice was a hand-written scanner or shelling out on every keystroke. The scanner is written against turbo-core's LineScanner, in the same style as the five the library ships.
  • Three constructs cross a line break and are carried exactly. A block comment carries a depth, not a flag, because Rust nests them and a flag ends /* a /* b */ c */ at the first */. A raw string carries its hash count, because it ends at a quote followed by exactly that many. An ordinary string can carry too, because Rust allows a real newline inside "…".
  • An attribute is not carried. #[…] that runs past its line is coloured to the end and dropped, because an unclosed one is nearly always half-typed, and carrying it would paint the rest of the file.
  • A lifetime is told from a character literal by looking for the closing quote where a character would have to put it — one rune along, or further for an escape. 'a is a lifetime, 'a' a character, 'static a lifetime, '\u{1F600}' a character. Getting it wrong strings the rest of the line, so it has tests of its own. A lifetime is coloured as a type, because it is a generic parameter declared and used where one is.
  • A leading capital means a type, leaning on Rust's naming convention: type, trait and enum variant are all UpperCamelCase and nothing else is. It is visibly a heuristic in one place — a SCREAMING_SNAKE_CASE constant is coloured as a type — and the reference says so rather than leaving somebody to find out. A second rule for it was rejected: it would mis-colour a type whose name is an acronym, and trading one wrong answer for another is not progress.
  • A macro takes its !, and a != b is told from it by the =. A number takes its suffix: 42u8 is one literal, and colouring the u8 as a type would split a thing that is not two things. :: and : are punctuation and ../..= are operators, both against the rune classes turbo-core's helpers would otherwise give them.
  • None, Some, Ok and Err are coloured as constants although they are Option's and Result's rather than the language's. A reader meets them before any other variant and reads them as they read true.
  • The Code menu is turbo-core's, and so are its eight questions. Describe symbol and Go to definition moved into it from Run and Search; their keys did not change. This repository documents the menu and owns none of it — as with everything else the two editors share, a change to it is a /methodical-dev cycle in turbo-core.
  • The settings file a project creates turns autosave on. A project that has gone to the trouble of having one has said what it wants, and the file is the visible, editable place to say otherwise. settings.Default() — what applies with no settings file at all — stays off: the editor must not write to disk in a directory somebody merely started it in. Two different statements, set in two different places on purpose.
  • A workspace, not a replace, is how to build against an unreleased turbo-core. go work init . ../turbo-core changes no tracked file, so there is nothing to forget before committing; go.work is gitignored in all three repositories. The commented-out replace at the bottom of go.mod still works and is documented as the older way, with its hazard named.
  • The build runs the binary it just built and checks it names the right version. scripts/check-version.sh is called by make build, by scripts/install.sh before the install, and by 03-build-releases.sh. A linker stamp is a string and a wrong one is not an error — -X naming a symbol that does not exist links happily and stamps nothing — so nothing but running the binary catches it. The comparison is an equality: 0.2.0 is a substring of 10.2.0.
  • rust-analyzer is looked for in CARGO_HOME/bin and RUSTUP_HOME/bin after PATH, and the installer runs it rather than stat-ing it. rustup installs a shim called rust-analyzer whether or not the component is there, and the shim fails only when run — so "the file is there" is not the question worth asking.
  • Snippets indent with four spaces, not tabs, because that is what rustfmt does; a tab in a Rust snippet lands in somebody's file and disappears on the next cargo fmt. There is a test for it, and another that no template still says turbo-go.
  • The tests drive the real editor. internal/rustlang/editor_test.go builds a whole Turbo Rust on a simulated terminal through turbo-core's public API. The library's suite proves the library works; these prove this editor is assembled correctly — that Register was called, that the profile reached the menu bar, that a .rs file comes out coloured and a .go file does not.

Everything else about the editor's behaviour — the event loop, the menus, the dialogs, the terminal emulator, the theme rules — is turbo-core's, and its .memory/summary.md is where those decisions are recorded.

  • A tool's command can ask for values. A {{label}} in it opens a box before the command runs; the value is shell-quoted unless the label ends in .... The feature is turbo-core's — see its summary — and what belongs to this editor is the starter file's comments, which teach the syntax without adding a sixth tool.
  • Agent windows are turbo-core's, and what belongs here is the starter file. acp.toml.tmpl is the fourth embedded template, and profile.Templates.Agents is the whole of Turbo Rust's contribution to the feature. The example agent is docker agent serve acp .turbo-rust/agent.yaml; the only other thing about this editor in it is the sentence saying a ```rust fence is coloured by the scanner this editor colours its own files with. Every other editor got agent windows the same way — one file, one line. The reasoning, and why it could not have been built here, is in docs/*/explanation/agent-windows.md.
  • The starter agents file teaches the window's keyboard as well as the format. Enter, Alt-Enter, Tab, Esc, Ctrl-W and the copying keys are all in its comments, because a file the editor hands you is the one document a user is guaranteed to see.

Build, test, run

make install              # build + install onto PATH (scripts/install.sh)
make build                # → bin/turbo-rust
make test                 # the whole suite; the single documented command
make check                # fmt + vet + test — what a commit should pass
make run FILE=src/main.rs
go test -short ./...      # skips the test that starts a real rust-analyzer

Quality gate, separate from the tests:

python3 ~/.claude/skills/quality/scripts/quality_report.py --workspace .

State as of 2026-09-01

  • Complete and green. Whole suite passing; quality gate PASS at 0 errors, 0 warnings, 0 smells, complexity 98.
  • Verified against a real rust-analyzer: the editor writes a crate, opens a file, starts the server, types text that exists only in the buffer, and gets String::len back. Text already on disk would prove nothing.
  • Verified in a real pty: the menu bar reads File Edit Search Run Options Window Snippets Rust Help; a nested block comment, a raw string with quotes inside it, println! with its !, and 3u8 with its suffix each come out as one correctly-coloured run on the wire.
  • docs/ is 33 pages × EN + FR. reference/languages.md documents the scanner's boundaries, and internal/rustlang/reference_test.go holds the code to every row of its Rust table.
  • Released as v0.1.0 at 2d5dbec, which is exactly HEAD — its first release ever, with a release page on Codeberg.
  • Depends on turbo-core v0.2.0, with no active replace. On main that is v0.1.0 and builds from the module proxy. On feature/more-syntaxes the require names v0.2.0, which is not published yet: that branch does not build until turbo-core is tagged and released, and go.sum has no entry for it. The old replace block is still there, commented out, as the documented way to develop across the three repositories.

Not yet established

  • Agent windows have never been opened in this editor. The feature is turbo-core's and was driven end to end from turbo-go against a real docker agent and a real llama.cpp; what is here is the starter file, covered by tests that create it, load it back and check it names this editor's own language. Nobody has run turbo-rust, pressed Alt-A and talked to an agent from it.

  • Never used by a person for a working session. Everything is verified by tests and by scripted pty runs; nobody has spent an hour editing Rust in it.

  • No release has been cut. The numbered scripts 0104 came from turbo-go and are adapted, but 02 and 04 have never been run here — they publish, and publishing is not this session's to do.

  • The scanner has met one file of Rust in anger. It is covered by 30-odd tests and a broad sweep, but it has not been pointed at a large real crate.

  • Windows and macOS are untested, inherited from turbo-core.

  • macro_rules! bodies are coloured as ordinary Rust, which is usually right and sometimes not. Documented, not fixed.

State as of 2026-09-19 — moved to Rickub, released by a workflow

  • Module path rickub.com/turbo-editors/turbo-rust, depending on rickub.com/turbo-editors/turbo-core v1.0.0 — the first turbo-core version published under that path (v0.9.0 on the proxy still declares the Codeberg path and cannot be required as rickub.com/…). Every import, the Makefile's VERSION_PKG, scripts/install.sh, the README and the docs say rickub.com. GOWORK=off make check green. The repository on this side is a fresh git init with origin at ssh://git@rickub.com/turbo-editors/turbo-rust.git and no commit yet; 01-release.tag.sh makes the first one.
  • Releases are one script and one workflow, modelled on turbo-core's and identical to turbo-go's. 01-release.tag.sh runs make check under TURBO_RUST_RELEASING=1, refuses a tag taken locally or on origin (bump, never move), refuses a replace in go.mod, commits, pushes the current branch, then tags and pushes the tag. That push starts .github/workflows/release.yml: go test with TURBO_RUST_RELEASING=1, ./02-build-releases.sh "${GITHUB_REF_NAME}", release notes from the tag message, a run artifact, then softprops/action-gh-release@v2 attaching turbo-rust-*, SHA256SUMS and README.md with the job's own GITHUB_TOKEN — the only credential Rickub's release API accepts. 02-release.publish.sh and 04-release.upload-binaries.sh are gone; the build script is now 02-build-releases.sh, takes the tag as $1 (CI has no release.env), validates it, refuses a replace, and starts from an empty release/${TAG}/. release.env holds only TAG and ABOUT; turbo-rust.token.env is read by nothing.
  • release_test.go runs 01 for real against a throwaway bare remote (publishes, then refuses the same tag), runs 02 alone to see it refuse v0.o.0, and asserts the workflow's trigger, contents: write, ./02-build-releases.sh, fail_on_unmatched_files, docs linked at the tag, no secrets., and TURBO_RUST_RELEASING. The copy of the module for the clone leaves out .git, bin, release, kits, demo, demos, *.env and go.work*; children run with GOWORK=off.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
# turbo-rust — project summary

*A snapshot of the present. No history here — that is `history.md`.*

## What this is

A Turbo C-style editor for Rust, written in Go: a full-screen terminal IDE with a menu bar, movable overlapping windows, modal dialogs, mouse support, Rust syntax colouring, loadable TOML themes, completion from `rust-analyzer`, terminal windows running a real shell, per-project settings, a project tree, snippets, and the cargo toolchain a menu away.

It is a thin editor on top of [turbo-core](https://rickub.com/turbo-editors/turbo-core), the library every Turbo editor shares. What is in this repository is `main.go` and `internal/rustlang` — about seven hundred lines, six hundred of which are the scanner.

Module path `rickub.com/turbo-editors/turbo-rust`. Go 1.26.5. Remote: `ssh://git@rickub.com/turbo-editors/turbo-rust.git`.

Created 2026-09-01, the same day turbo-core was extracted, and the reason it was extracted.

## Architecture

Two packages here; everything else is the library.

```
main → {turbo-core/app, turbo-core/profile, turbo-core/settings, turbo-core/theme,
        turbo-core/version, internal/rustlang, tcell}
internal/rustlang → {turbo-core/profile, turbo-core/syntax}
```

| File | What it holds |
| --- | --- |
| `main.go` | Flags, the terminal, and the wiring: register Rust, build the profile, read the project's settings, hand them to `app.New`, start rust-analyzer in the crate root, run the loop |
| `internal/rustlang/rustlang.go` | The profile: name, slug, `Rus~t~` menu, `Cargo.toml` root marker, rust-analyzer with the two directories it is looked for in |
| `internal/rustlang/scan.go` | The scanner's dispatcher, comments and attributes |
| `internal/rustlang/literals.go` | Strings, raw strings, byte literals, characters, lifetimes |
| `internal/rustlang/words.go` | Numbers, keywords, types, macros |
| `internal/rustlang/templates.go` | The three starter files a project gets |

`docs/diagrams/packages.drawio` is generated from `go list` and verified against it edge for edge.

## Decisions in force

- **The toolchain menu is `Rus~t~`, on Alt-T, not `~C~argo`.** `R` is Run's and `S` is Search's, so the hot key lands on the last letter of the word, which reads as an afterthought — and `C` was free. Naming it Cargo was still rejected: the menu holds whatever the project put in its tools file, and the first tools file anybody writes outgrows the language's own toolchain. A menu called Cargo holding `docker compose up` is a lie about what the menu is.
- **Rust is scanned by hand, in six hundred lines.** Go has a lexer in its standard library and Turbo Go uses it; `rustc` is not a Go library and rust-analyzer's parser is a Rust crate, so the choice was a hand-written scanner or shelling out on every keystroke. The scanner is written against turbo-core's `LineScanner`, in the same style as the five the library ships.
- **Three constructs cross a line break and are carried exactly.** A block comment carries a **depth**, not a flag, because Rust nests them and a flag ends `/* a /* b */ c */` at the first `*/`. A raw string carries its **hash count**, because it ends at a quote followed by exactly that many. An ordinary string can carry too, because Rust allows a real newline inside `"…"`.
- **An attribute is not carried.** `#[…]` that runs past its line is coloured to the end and dropped, because an unclosed one is nearly always half-typed, and carrying it would paint the rest of the file.
- **A lifetime is told from a character literal by looking for the closing quote** where a character would have to put it — one rune along, or further for an escape. `'a` is a lifetime, `'a'` a character, `'static` a lifetime, `'\u{1F600}'` a character. Getting it wrong strings the rest of the line, so it has tests of its own. A lifetime is coloured as a **type**, because it is a generic parameter declared and used where one is.
- **A leading capital means a type**, leaning on Rust's naming convention: type, trait and enum variant are all `UpperCamelCase` and nothing else is. It is visibly a heuristic in one place — a `SCREAMING_SNAKE_CASE` constant is coloured as a type — and the reference says so rather than leaving somebody to find out. A second rule for it was rejected: it would mis-colour a type whose name is an acronym, and trading one wrong answer for another is not progress.
- **A macro takes its `!`**, and `a != b` is told from it by the `=`. **A number takes its suffix**: `42u8` is one literal, and colouring the `u8` as a type would split a thing that is not two things. **`::` and `:` are punctuation** and **`..`/`..=` are operators**, both against the rune classes turbo-core's helpers would otherwise give them.
- **`None`, `Some`, `Ok` and `Err` are coloured as constants** although they are Option's and Result's rather than the language's. A reader meets them before any other variant and reads them as they read `true`.
- **The Code menu is turbo-core's, and so are its eight questions.** Describe symbol and Go to definition moved into it from Run and Search; their keys did not change. This repository documents the menu and owns none of it — as with everything else the two editors share, a change to it is a `/methodical-dev` cycle in turbo-core.
- **The settings file a project creates turns autosave on.** A project that has gone to the trouble of having one has said what it wants, and the file is the visible, editable place to say otherwise. `settings.Default()` — what applies with no settings file at all — stays **off**: the editor must not write to disk in a directory somebody merely started it in. Two different statements, set in two different places on purpose.
- **A workspace, not a `replace`, is how to build against an unreleased turbo-core.** `go work init . ../turbo-core` changes no tracked file, so there is nothing to forget before committing; `go.work` is gitignored in all three repositories. The commented-out `replace` at the bottom of `go.mod` still works and is documented as the older way, with its hazard named.
- **The build runs the binary it just built and checks it names the right version.** `scripts/check-version.sh` is called by `make build`, by `scripts/install.sh` before the install, and by `03-build-releases.sh`. A linker stamp is a string and a wrong one is not an error — `-X` naming a symbol that does not exist links happily and stamps nothing — so nothing but running the binary catches it. The comparison is an **equality**: `0.2.0` is a substring of `10.2.0`.
- **rust-analyzer is looked for in `CARGO_HOME/bin` and `RUSTUP_HOME/bin` after PATH**, and the installer *runs* it rather than stat-ing it. rustup installs a shim called `rust-analyzer` whether or not the component is there, and the shim fails only when run — so "the file is there" is not the question worth asking.
- **Snippets indent with four spaces, not tabs**, because that is what rustfmt does; a tab in a Rust snippet lands in somebody's file and disappears on the next `cargo fmt`. There is a test for it, and another that no template still says `turbo-go`.
- **The tests drive the real editor.** `internal/rustlang/editor_test.go` builds a whole Turbo Rust on a simulated terminal through turbo-core's public API. The library's suite proves the library works; these prove *this editor is assembled correctly* — that Register was called, that the profile reached the menu bar, that a `.rs` file comes out coloured and a `.go` file does not.

Everything else about the editor's behaviour — the event loop, the menus, the dialogs, the terminal emulator, the theme rules — is turbo-core's, and its `.memory/summary.md` is where those decisions are recorded.

- **A tool's command can ask for values.** A `{{label}}` in it opens a box before the command runs; the value is shell-quoted unless the label ends in `...`. The feature is turbo-core's — see its summary — and what belongs to this editor is the starter file's comments, which teach the syntax without adding a sixth tool.
- **Agent windows are turbo-core's, and what belongs here is the starter file.** `acp.toml.tmpl` is the fourth embedded template, and `profile.Templates.Agents` is the whole of Turbo Rust's contribution to the feature. The example agent is `docker agent serve acp .turbo-rust/agent.yaml`; the only other thing about this editor in it is the sentence saying a ```rust fence is coloured by the scanner this editor colours its own files with. Every other editor got agent windows the same way — one file, one line. The reasoning, and why it could not have been built here, is in `docs/*/explanation/agent-windows.md`.
- **The starter agents file teaches the window's keyboard as well as the format.** `Enter`, `Alt-Enter`, `Tab`, `Esc`, `Ctrl-W` and the copying keys are all in its comments, because a file the editor hands you is the one document a user is guaranteed to see.

## Build, test, run

```bash
make install              # build + install onto PATH (scripts/install.sh)
make build                # → bin/turbo-rust
make test                 # the whole suite; the single documented command
make check                # fmt + vet + test — what a commit should pass
make run FILE=src/main.rs
go test -short ./...      # skips the test that starts a real rust-analyzer
```

Quality gate, separate from the tests:

```bash
python3 ~/.claude/skills/quality/scripts/quality_report.py --workspace .
```

## State as of 2026-09-01

- **Complete and green.** Whole suite passing; quality gate PASS at 0 errors, 0 warnings, 0 smells, complexity 98.
- **Verified against a real rust-analyzer**: the editor writes a crate, opens a file, starts the server, types text that exists only in the buffer, and gets `String::len` back. Text already on disk would prove nothing.
- **Verified in a real pty**: the menu bar reads `File Edit Search Run Options Window Snippets Rust Help`; a nested block comment, a raw string with quotes inside it, `println!` with its `!`, and `3u8` with its suffix each come out as one correctly-coloured run on the wire.
- **`docs/` is 33 pages × EN + FR.** `reference/languages.md` documents the scanner's boundaries, and `internal/rustlang/reference_test.go` holds the code to every row of its Rust table.
- **Released as v0.1.0** at `2d5dbec`, which is exactly HEAD — its first release ever, with a release page on Codeberg.
- **Depends on `turbo-core v0.2.0`**, with no active `replace`. On `main` that is v0.1.0 and builds from the module proxy. On `feature/more-syntaxes` the `require` names **v0.2.0, which is not published yet**: that branch does not build until turbo-core is tagged and released, and `go.sum` has no entry for it. The old replace block is still there, commented out, as the documented way to develop across the three repositories.

## Not yet established

- **Agent windows have never been opened in this editor.** The feature is turbo-core's and was driven end to end from turbo-go against a real `docker agent` and a real llama.cpp; what is here is the starter file, covered by tests that create it, load it back and check it names this editor's own language. Nobody has run `turbo-rust`, pressed `Alt-A` and talked to an agent from it.


- **Never used by a person for a working session.** Everything is verified by tests and by scripted pty runs; nobody has spent an hour editing Rust in it.
- **No release has been cut.** The numbered scripts `01``04` came from turbo-go and are adapted, but `02` and `04` have never been run here — they publish, and publishing is not this session's to do.
- **The scanner has met one file of Rust in anger.** It is covered by 30-odd tests and a broad sweep, but it has not been pointed at a large real crate.
- **Windows and macOS are untested**, inherited from turbo-core.
- **`macro_rules!` bodies are coloured as ordinary Rust**, which is usually right and sometimes not. Documented, not fixed.

## State as of 2026-09-19 — moved to Rickub, released by a workflow

- **Module path `rickub.com/turbo-editors/turbo-rust`**, depending on `rickub.com/turbo-editors/turbo-core v1.0.0` — the first turbo-core version published under that path (`v0.9.0` on the proxy still declares the Codeberg path and cannot be required as `rickub.com/…`). Every import, the Makefile's `VERSION_PKG`, `scripts/install.sh`, the README and the docs say `rickub.com`. `GOWORK=off make check` green. The repository on this side is a fresh `git init` with `origin` at `ssh://git@rickub.com/turbo-editors/turbo-rust.git` and **no commit yet**; `01-release.tag.sh` makes the first one.
- **Releases are one script and one workflow**, modelled on turbo-core's and identical to turbo-go's. `01-release.tag.sh` runs `make check` under `TURBO_RUST_RELEASING=1`, refuses a tag taken locally or on origin (bump, never move), refuses a `replace` in `go.mod`, commits, pushes the current branch, then tags and pushes the tag. That push starts `.github/workflows/release.yml`: `go test` with `TURBO_RUST_RELEASING=1`, `./02-build-releases.sh "${GITHUB_REF_NAME}"`, release notes from the tag message, a run artifact, then `softprops/action-gh-release@v2` attaching `turbo-rust-*`, `SHA256SUMS` and `README.md` with the job's own `GITHUB_TOKEN` — the only credential Rickub's release API accepts. **`02-release.publish.sh` and `04-release.upload-binaries.sh` are gone**; the build script is now `02-build-releases.sh`, takes the tag as `$1` (CI has no `release.env`), validates it, refuses a `replace`, and starts from an empty `release/${TAG}/`. `release.env` holds only `TAG` and `ABOUT`; `turbo-rust.token.env` is read by nothing.
- **`release_test.go`** runs `01` for real against a throwaway bare remote (publishes, then refuses the same tag), runs `02` alone to see it refuse `v0.o.0`, and asserts the workflow's trigger, `contents: write`, `./02-build-releases.sh`, `fail_on_unmatched_files`, docs linked at the tag, no `secrets.`, and `TURBO_RUST_RELEASING`. The copy of the module for the clone leaves out `.git`, `bin`, `release`, `kits`, `demo`, `demos`, `*.env` and `go.work*`; children run with `GOWORK=off`.