Developing
How to build the project from a checkout and what the local gate runs before a push. This is the contributor’s page; if you are looking for what the application does, start at Running the app.
Building
A recent stable Rust toolchain (the workspace is edition 2024 — Rust 1.85+) and, for the
documentation gates and the two npm projects, Node. CI runs Node 24, the active LTS, whose npm
11 is the one allowScripts below assumes. From the repo root:
cargo build # the everyday buildcargo run -p standalone --release # the appcargo nextest run --workspace # the whole suitecargo clippy --workspace --all-targets -- -D warningscargo fmt --all --check--workspace is load-bearing on the test and lint rows rather than a flourish: the C ABI crate sits
outside the workspace’s default members, so a bare cargo nextest run skips the ABI conformance
suite and a bare cargo clippy stops linting the C ABI, and both come back green while covering
nothing.
The headless capture CLI and the visual-QA harness have their own page, Headless capture and video.
The exe’s size is read by the build that makes it
NFR §4 caps the release exe softly, and the figure is a property
of a build rather than of the tree: it moves with the toolchain, the profile and the feature set.
So nothing in the everyday loop or in ci.yml measures it. The two standalone packaging recipes do —
packaging/windows/stage.ps1 and packaging/macos/bundle.sh — on every run, printing the length
beside the build that produced it and warning past 90 % of the cap, never failing
(ADR-0231).
To read it on a box that runs neither, build the release binary and read its length; the
composition behind the cap, and how it was taken, is written down in NFR §4 so the next reading can
be compared with the last.
A fresh Arch Linux checkout
Everything below uses Arch package names. libpulse, pkgconf, wayland, libxkbcommon,
ffmpeg, nodejs and python are usually present already on a desktop install:
sudo pacman -S --needed rustup vulkan-swrast vulkan-tools \ cargo-nextest cargo-release cargo-deny uv \ libpulse pkgconf wayland libxkbcommon ffmpeg nodejs npm pythonrustup show # inside the checkout: installs the toolchain rust-toolchain.toml pinsgit config core.hooksPath .githooks # the pre-push gate, opt-in per clonenpm --prefix studio ci # the studio's dependencies, which the hook's studio step needscargo buildvulkan-swrast is lavapipe, the software Vulkan rasterizer the adapter-agnostic tests run on
(ADR-0242).
vulkaninfo --summary should list it as llvmpipe beside the hardware GPUs. uv provides the older
CPython that the diffusion sidecar’s pinned torch needs. Desktop audio reaches the standalone through
PulseAudio’s monitor source, which PipeWire serves via pipewire-pulse
(ADR-0131).
The whole gate is green on Arch. Measured on 2026-09-22 on an Arch laptop (RTX 3080 Laptop
dGPU, AMD Vega iGPU, lavapipe from Mesa 26.2.2), with target/ already built:
| Run | Wall time | Result |
|---|---|---|
.githooks/pre-push, run by hand so every step runs | 333 s | green, no step skipped; the test step alone took 297 s, 1695 passed and 86 skipped |
cargo nextest run --workspace | 454 s | 1774 passed, 7 skipped |
Two parts of that green are expected skips, not coverage:
- The pinned-baseline tests skip on lavapipe with a notice. The golden baselines were blessed on
DX12 WARP. On any other adapter,
common::baseline_adapterprints how many comparisons would have failed and skips the assertion, until the baselines are recaptured on lavapipe (ADR-0242). So on Linux a golden mismatch is reported, not failed. Only a Windows run asserts one. - The hardware tests take whichever GPU wgpu’s default picks. On that laptop this is the Vega iGPU, not the dGPU. They pass there, but their timing reports name the iGPU.
npm 11 runs a dependency’s install script only when allowScripts names it. The studio’s
package.json lists each approved package pinned as pkg@version, and a new entry is a reviewed
edit, made with npm --prefix studio install-scripts approve <pkg> and never with --all
(ADR-0244).
A bump strands its pinned entry, and npm ci then succeeds while silently skipping the script, so
a bump of a listed package re-approves it. npm --prefix studio install-scripts ls should print
No packages with unreviewed install scripts. site/ has no field: the one script its graph holds,
esbuild’s, is not needed for its build. Electron 44 has no install script at all; it downloads
its binary the first time it runs, which npx --prefix studio electron --version triggers.
The studio run from source reads "playerPath" from ~/.config/ritmolux-studio/settings.json.
That is settings.json in Electron’s per-user directory, which on Linux is
$XDG_CONFIG_HOME/ritmolux-studio/, falling back to ~/.config/ when XDG_CONFIG_HOME is unset.
Set "playerPath" there to point the studio at your own target/release/ritmolux. The directory is
named after the "name" in studio/package.json, so it is lowercase, unlike the player’s own
~/.local/share/Ritmolux/.
No linker override on Linux. The toolchain’s default rust-lld relinks the 52 MB suite test
binary in about 200 ms. mold 2.42 saves about 15 ms of that, and makes no measurable difference to
a cold rebuild of rlx-core’s tests, so no machine-local config file is suggested.
Editing presets in VS Code
Install Even Better TOML (tamasfe.even-better-toml). That is the whole setup: a committed
.taplo.toml at the repository root tells the extension which schema each preset file gets, with
nothing per-user to configure.
Which schema a file gets depends on its name. A library file — one directly in presets/,
presets/proposed/ or presets/pending/ — named for its system’s family (fragment_*.toml for
fragment_field, collage_*.toml for shape_collage, curve_*.toml for parametric_curve) gets
that system’s own schema from presets/schema/. Inside it you get:
- completion on
[params]keys, offering that system’s parameters and the compositing stages’, and not another system’s; - hover documentation on a parameter — what it does, its default, its typical range and its kind;
- a Problems entry naming a key the system does not accept, which is the mistake the loader
deliberately forgives (an unknown parameter is a warning and the binding is kept, ADR-0020). The
underline sits on the
[params]header rather than on the key itself, so read the Problems panel for the name; - the closed rosters as a dropdown on
[curve] family,[spectrum] layoutand every other key drawn from a fixed set.
Everything else gets validation only, from the generic presets/preset.schema.json: the teaching
files under docs/examples/, and a library file named off its family. Those still get an entry for
an unknown key, but no parameter completion and no parameter hover. So does every [layer], in any
file: a layer names its own system, and a per-system schema does not narrow the layer’s [params].
ritmolux --check warns (file-name) on a library file named off its family, which is exactly the
file that would lose completion.
All sixteen files — the generic schema, the fourteen per-system schemas and .taplo.toml itself — are
generated from the engine’s own ParamSpec, TableDesc and SystemKind declarations, and
core/tests/suite/preset_schema.rs fails if any of them is stale or presets/schema/ holds a file no
system renders. The same test file holds a seventeenth file from the same export to it:
docs/specs/player-schema.json, the document ritmolux --schema prints, which the studio’s schema
walks read when no player is built. One command regenerates all of them, and removes a schema left
behind by a system that no longer exists:
RLX_UPDATE_PRESET_SCHEMA=1 cargo nextest run -p rlx-core --test suite preset_schema::The same shape holds the interface’s look (ADR-0252). studio/renderer/tokens.css, the studio’s
colour, type, spacing and motion custom properties, is generated from the theme table in
core/src/render/theme.rs, which every engine-drawn surface also reads.
core/tests/suite/ui_tokens.rs fails when the committed file differs from what the table renders.
To change a colour anywhere, change it in the table and regenerate:
RLX_UPDATE_UI_TOKENS=1 cargo nextest run -p rlx-core --test suite ui_tokens::Never hand-edit tokens.css: that is exactly the edit the test fails on.
Turn format-on-save off for TOML. The extension ships a formatter, and this project does not
format TOML: the presets carry deliberate local alignment that every formatter measured against them
destroyed (ADR-0190). .taplo.toml carries no [formatting] table, but that cannot stop your
editor’s own format-on-save, so if you have it on globally, exclude TOML in your settings:
"[toml]": { "editor.formatOnSave": false }Without that, one save reflows the file and the diff touches every line. .vscode/ is gitignored, so
this lives in your own settings rather than in the repository.
Optionally, a task that runs the checker on the open file. In .vscode/tasks.json:
{ "version": "2.0.0", "tasks": [ { "label": "check preset", "type": "shell", "command": "cargo run -q -p standalone --bin ritmolux -- --check ${file}", "problemMatcher": { "owner": "ritmolux", "fileLocation": ["absolute"], "pattern": { "regexp": "^(.*):(\\d+):(\\d+): (error|warning)\\[([^\\]]+)\\]: (.*)$", "file": 1, "line": 2, "column": 3, "severity": 4, "code": 5, "message": 6 } } } ]}That gives the checker’s diagnostics in the Problems panel. The schema and the checker cover
different things and both are worth having: the schema knows every name and roster and underlines
live, and only --check compiles an expression. See
the preset authoring guide for what it reports.
The pre-push gate
A checked-in .githooks/pre-push runs the fast subset of CI before a push, so a
broken push costs seconds locally instead of minutes in CI. It is opt-in per
clone — enable it with:
git config core.hooksPath .githooksAn uninstalled clone has no gate. Git will not run a hook from a tracked directory without that config, so until you set it nothing below happens. There is deliberately no auto-install (ADR-0033 Alternative H).
What it runs
It stops at the first failure and names the step that failed:
| Step | Command |
|---|---|
| Doc links | node scripts/check-doc-links.mjs |
| Index rows | node scripts/check-index-rows.mjs |
| Index rows (self-test) | node scripts/check-index-rows.mjs --self-test |
| Backlog claims | node scripts/check-backlog-claims.mjs |
| Filter figures | node scripts/check-filter-figures.mjs |
| Comment hygiene | node scripts/check-comment-hygiene.mjs |
| Contents blocks | node scripts/toc.mjs --check |
| Contents blocks (self-test) | node scripts/toc.mjs --self-test |
| Reader prose | node scripts/check-reader-prose.mjs |
| Release tag | node scripts/check-release-tag.mjs |
| Release tag (self-test) | node scripts/check-release-tag.mjs --self-test |
| Release assets (self-test) | node scripts/check-release-assets.mjs --self-test |
| Translations | node scripts/check-translations.mjs |
| Translations (self-test) | node scripts/check-translations.mjs --self-test |
| System counts | node scripts/check-system-counts.mjs |
| Settings have files | node scripts/check-settings-have-files.mjs |
| Claude declarations | node scripts/check-claude-declarations.mjs |
| Claude declarations (self-test) | node scripts/check-claude-declarations.mjs --self-test |
| Gate carriers | node scripts/check-gate-carriers.mjs |
| Gate carriers (self-test) | node scripts/check-gate-carriers.mjs --self-test |
| Diffusion filter | python3 tools/sd-filter/test_sd_filter.py (skips with no python3) |
| Studio typecheck | npm --prefix studio run typecheck (skips with no studio/node_modules) |
| Studio lint | npm --prefix studio run lint (same guard) |
| Studio tests | npm --prefix studio test (same guard) |
| Format | cargo fmt --all --check (only when the push moved a Rust-relevant path — below) |
| Lint | cargo clippy --workspace --all-targets -- -D warnings (same condition) |
| Rustdoc | cargo doc --workspace --no-deps under RUSTDOCFLAGS=-D warnings (same condition) |
| Tests | cargo nextest run --workspace -P fast (same condition, narrowed, and served from the suite ledger when it can be — below) |
Everything above the cargo steps runs on every push; the four cargo steps do not
(ADR-0237).
Git hands the hook one line per pushed ref, and the hook asks
node scripts/push-scope.mjs <remote sha> <local sha> about each range. The cargo steps run when
any range touches a path listed in scripts/push-scope.manifest.mjs — Rust source, a manifest or
the lockfile, the toolchain and cargo configuration, anything under a workspace crate’s directory,
presets/, and the few files outside the crates that a Rust test opens, such as
docs/configuration.md. When nothing does, one line names the ranges it read and the push is done:
pre-push: skipping cargo fmt, clippy, rustdoc and tests: 7f7ed2c..e3498a5 touches no Rust-relevant path (scripts/push-scope.manifest.mjs)When they run, the line names the first path that matched:
pre-push: running the cargo steps: push-scope: f2bf1dd..60f4c28 touches Cargo.lock (rule Cargo.lock): yesWhatever the hook cannot read runs all four, and the line says why: a ref that is new on the
remote (there is no range to compare against), an empty or unparseable stdin, a push of nothing but
deletions, a shallow clone, a sha this clone does not hold, or the hook run by hand from a terminal.
A new tag is a new ref too, so a push carrying a release tag — every close push made with
git push --follow-tags — runs the cargo steps even when the branch beside it moved no Rust; the
test step can still be served from the ledger.
The test step is served instead of run when the suite ledger already proves the tree. Before it,
node tools/conductor/suite-record.mjs asks the ledger
(ADR-0207)
whether this worktree’s tree, clean, has a green cargo nextest run --workspace. Who wrote the
record does not matter — a conductor gate, a review session, or your own full suite run through
tools/conductor/with-lock.mjs — and the line names it:
pre-push: serving cargo nextest run -P fast from the suite ledger: tree <tree> is green in the suite ledger, run by <writer> at <time>: <nextest summary>A dirty worktree, a tree whose only record is a -P fast run, and a tree whose newest full run was
red all run the step, after a line saying why the ledger did not serve it. So the saving lands on a tip a full suite already
proved — main after a conductor fast-forward — and not on every intermediate commit.
The path list is a judgement, and it will be wrong once: some build input nobody listed will let
a push skip a suite that would have gone red. CI runs every step unconditionally and is what catches
it. The repair is a line in the manifest and a case in scripts/fixtures/push-scope/cases.json,
checked by node scripts/push-scope.mjs --self-test.
The two guarded groups skip rather than fail, and say so. A clone with no
python3 on PATH and one that has never run npm --prefix studio ci are both
ordinary, so those four steps print a notice naming what is missing and the
command that would make them run, and the push continues
(ADR-0016). CI runs all four
unconditionally and is the backstop under both; so does the conductor’s gate,
which since
ADR-0218
reports each skip the same way instead of dropping the step in silence.
The Node steps come first because they are the cheapest (tens of milliseconds
between them): every relative markdown link in the repo must resolve, every row
inside a marked roster region must stay under 320 bytes
(ADR-0116),
every live design-backlog.md entry must carry a probe that still holds
(ADR-0108),
the diffusion filter’s cost figures must live in one file
(ADR-0122), no .rs
comment may carry a relative link or plan-relative narration
(ADR-0127),
every citation in a reader document must sit inside a link
(ADR-0168),
every generated contents block must still match the headings beneath it
(ADR-0163),
the version root Cargo.toml declares must carry an annotated tag on HEAD’s
history, because git push --follow-tags never sends a lightweight one
(ADR-0203),
every .ru.md translation must open with the translated-from: <sha> stamp
naming the commit its source was translated from
(ADR-0185),
and this table’s own Node steps must equal the ordered roster in
scripts/gates.manifest.mjs, as must CI’s links job and the conductor’s gate
(ADR-0217) —
so a gate added to one carrier and not the others is red here, at the push.
The four that could go green on a rule that had quietly stopped working — a roster
detector matching nothing, an anchor rule that is merely plausible, a tag-type check
that no longer looks, a stamp reader that finds no translations at all — carry a
--self-test beside their check. The roster gate carries one for a different
reason: its plain run cannot go vacuously green (a parser that stopped matching
reports an empty list against a roster that is not), so its self-test is there for
the reporting path and the three drift shapes instead.
A translation that has drifted is never a failure. The stamp check reports a
source that has moved as an advisory row and exits 0: nothing mechanical can judge
whether the Russian still says what the English now says, and hard-failing would
make that slice a hostage of every hotkey edit. A missing or malformed stamp is
the exit code, because that much is mechanical.
The release-tag step refuses a push, not only a release: from the moment a close
moves the version, every push fails until that version’s tag is annotated. CI’s
links job cannot read local tags, so it runs the same script with --remote on a
push to main instead, and asks origin.
If node is not on your PATH they all skip with a notice rather than
failing the push; nothing else here needs Node. That skip is about the hook only
— CI’s links job runs the same checks on ubuntu-latest, where they cannot
skip and are not bypassable.
What it costs
What a push costs depends on which of the three shapes above it takes. Measured on 2026-09-22,
one run each, the whole hook driven with a real ref line on stdin, on the Windows reference machine
(AMD Radeon integrated, DX12), in a worktree whose target/ was already built and which had no
studio/node_modules — so the studio’s three steps skipped, and add their 15.2 s wherever they run:
| Push | Wall time | What ran |
|---|---|---|
| A range touching no Rust-relevant path | 8.2 s | the Node roster and the Python suite; every cargo step skipped |
| A Rust range whose tree has a full-suite record | 12.1 s | the same, plus fmt, clippy and rustdoc with nothing to rebuild, and the ledger lookup; the test step served. The record was seeded into a scratch ledger for the reading, since no tree of the measuring lane had a real one |
| A Rust range whose tree has no record | 557.2 s | everything; the test step alone took 544.8 s, 1695 tests passed and 86 skipped |
The test step is the whole difference between the last two rows. On 2026-09-14 an earlier
~410 s reading of it on the same machine was about 165 s idle, and the reason is still true:
every test that asserts on wall-clock time runs alone: nextest waits for the running tests to
drain, runs it, and starts nothing beside it. So you will see the run pause on those tests.
.config/nextest.toml names them, and a guard in core/tests/suite/hygiene.rs holds that list to
the tests that read the clock
(ADR-0193). The hook excludes the nine
GPU-heavy suites that iterate every shipped preset or scene through a real
adapter. Which nine is not written here — since
ADR-0156
the list is the fast profile’s default-filter in .config/nextest.toml, and the hook and CI’s
check job both cite -P fast rather than restating it.
The test runner names the skipped binaries itself on every run, on the profile’s
authority, so the narrowing is never silent. CI runs all of them regardless, but on Windows
only. The check matrix runs -P fast on Windows, macOS and Ubuntu. Since
ADR-0073, the nine run in the coverage job alone,
on windows-latest, so one Windows job underwrites that promise. No CI job runs them on Linux.
Locally, the full cargo nextest run --workspace runs them on any machine, and it is green on
Arch (see A fresh Arch Linux checkout).
The rustdoc step fails a broken or private intra-doc link in any of the five workspace
members before CI’s cargo doc --workspace job does. Scoping it to -p rlx-core left the other
four documented in CI and nowhere else — a rustdoc error in one of them was unreachable before a
push, and it fired twice in sixteen days, both times repaired after a release tag had been written
on top of the red
(ADR-0217).
On the reference machine the widened step measures
7.8 s warm after an edit to rlx-ring (the deepest crate, so every member re-documents), 0.5 s warm
with nothing changed, and 20.1 s after a cargo clean --doc — about two seconds more than the
scoped step it replaces.
cargo deny, doctests, Miri, and the coverage job are deliberately not in the
hook — they push it into minutes, and a gate that hurts gets disabled
(ADR-0033 Alternative F).
They are also the checks least likely to break from a local edit.
The npm audit gate is outside the hook for cargo deny’s reason: it asks the registry, and its
answer moves when an advisory is published, not when you commit. It runs in CI’s npm-audit job
and fails at high in the studio’s shipped graph (npm audit --omit=dev, the packages inside the
studio zip) and at critical in the full graphs of studio/ and site/
(ADR-0244).
An exception is an entry in npm-audit.allow.json at the repository root, a GHSA id with the reason
the studio or the site is not exposed; an entry without a reason fails the gate, and one whose
advisory has gone away is printed for deletion. Run it by hand before a push that bumps an npm
dependency:
node scripts/check-npm-audit.mjs # the three audits, against the registrynode scripts/check-npm-audit.mjs --self-test # the gate's own assertions, offlineThe spout compile job (ADR-0181) is outside the hook too, and for neither of those reasons.
It stages a third-party SDK over the network before it compiles anything, which does not fit the
hook’s budget at all — and it is the one CI gate that is likely to break from an ordinary local
edit, because code behind #[cfg(feature = "spout")] is not type-checked when the feature is off,
so every cargo build, clippy --all-targets and nextest run here is blind to it by
construction. If you are refactoring anything standalone/src/stream.rs reaches, compile it
yourself before pushing:
# Windows only: Spout is a Windows SDK, and the feature compiles nothing elsewherepowershell -File packaging/spout/fetch-sdk.ps1 # once per checkoutcargo check -p standalone --features spoutBypass once with git push --no-verify.
The gated compile paths
Three parts of the tree compile only on a platform or with an SDK that the everyday loop does not
have. Nothing local compiles any of them: not cargo build, not clippy --all-targets, not
nextest, and not the pre-push hook. Each one is compiled before a tag by a CI job that names it
(ADR-0251):
| Gated path | Why the local loop cannot see it | Compiled before a tag by |
|---|---|---|
standalone/src/capture_mac/ | #[cfg(target_os = "macos")], so no Windows or Linux target type-checks it | check (macos-latest) in ci.yml |
cfg(feature = "spout") in standalone/ | off by default, and code behind a disabled feature is not type-checked | the spout job in ci.yml |
plugin-foobar/ | C++ against the third-party foobar2000 SDK, which is gitignored; no cargo command builds it | the foobar job in ci.yml |
release.yml’s jobs build all three again at tag time, and its needs: skips the release when any
fails. So a break in one of them is read by name, on the push that caused it, from the job
above. If nobody reads it, the only sign is an artifact missing from a release. The macOS build was
red for two releases that way.
A red job on main is named at the next close, which still goes ahead. Before a close merges,
it reads the newest CI run for origin/main with node scripts/check-upstream-ci.mjs. A red run
is reported with the failing job named; Pages and Release runs are never read. The reading needs
gh auth login on the machine doing the close. Without it, or without a network, the script prints
upstream CI: skipped: not read (<case>) and exits 0, so the output says nothing was checked.
Releasing says where the reading
appears and why it never blocks.
Disk
Every worktree builds into its own target/, and cargo collects nothing in it on its own.
target/debug/deps/ keeps every generation of every unit. A dependency bump, a feature flip,
or a narrowed cargo nextest run -p <crate> whose feature set differs from the workspace build
writes a second rlx_core, wgpu and windows beside the first, and nothing collects the old one.
scripts/prune-target.mjs deletes what the everyday loop no longer uses:
node scripts/prune-target.mjs # dry run: what would go, and the bytesnode scripts/prune-target.mjs --apply # delete itnode scripts/prune-target.mjs --verify-fresh # every artifact the loop reports is still freshThe live set is what cargo reports: the script runs cargo build --workspace,
cargo clippy --workspace --all-targets and cargo nextest run --workspace -P fast --no-run with
JSON messages, and keeps every file one of them names. So the three have to be green, and no other
cargo process may run in the same checkout while it does. Deleting something the loop needed costs a
rebuild, never a wrong build: cargo sees a missing output as dirty. The target directory comes from
cargo metadata, so a CARGO_TARGET_DIR redirect is followed.
Run it when the disk is short, and after anything that changes many units at once: a toolchain or
dependency bump, or a session of narrowed -p runs.
target/debug/incremental/ holds one directory per compiled unit, and up to two sessions in
each. Measured on the reference machine on 2026-09-15, in a lane built by the three commands
above: 171 unit directories and 1.95 GB. One edit to core/src/render/metrics.rs and the three
commands again took it to 3.62 GB with no new directory, because each rebuilt unit now kept its
previous session beside the new one. Three more edit-and-revert rounds left it at 3.59 GB, still
171 directories and never more than two sessions in one. So the loop alone does not grow it without
bound. What adds directories is a new unit: a different feature set, a narrowed -p build, a
dependency or toolchain change, each of which leaves the old unit’s directory behind.
prune-target.mjs does not touch this directory, because nothing cargo reports names which unit a
directory belongs to. Delete it instead, when it has grown past what the loop above needs:
rm -rf target/debug/incremental # shRemove-Item -Recurse -Force target\debug\incremental # PowerShellThat costs one non-incremental rebuild of the workspace crates on the next build, and nothing else: dependencies are never built incrementally, and no output outside that directory depends on it.
Running approved plans under the conductor
tools/conductor/ runs approved plans to a merged main with nobody at the keyboard. It works in up
to two worktree lanes, and each plan gets separate headless claude -p sessions: its implementer
runs, then a review that also closes the plan, then a fast-forward of main and removal of the lane
(ADR-0205).
It never pushes. Anything it cannot decide — a human phase, a red gate, a review still failing
after two fix rounds, a spend cap — parks the plan and moves on to the next one.
node tools/conductor/conductor.mjs check # local.json, the CLI version, the queuenode tools/conductor/conductor.mjs run # both lanes, until the queue is merged or parkednode tools/conductor/conductor.mjs status # what each lane is doing, and every parkBefore the first run:
- Spend caps. Write
tools/conductor/local.jsonwith your per-step caps. It is gitignored, and the conductor refuses to start without it. - The main checkout. Keep it clean while a run is live: a fast-forward refuses a dirty one.
What changes for a plan it runs:
- The approval is the go. The plan’s
Status: approvedis the only approval it needs. - The review is a separate process. A fresh session, handed the plan and the lane and nothing an implementer wrote, performs the close review.
- The review is committed. It lands in the plan’s
## Close reviewsection.
Every other seam stays exactly as the skills describe it.
Watch it from the terminal that started it. run prints one line per milestone as it happens: a
step starting and ending with its duration, spend and turn count, each commit that lands in the
worktree, each phase whose log row flips to done, each test and gate command with its counts and lock
wait, the 5-hour and 7-day usage readings, and every command a permission rule denied. The same lines
go to tools/conductor/state/live.log, under one header per run, for a run you did not watch.
Next morning, read tools/conductor/digest.md. It is the current state in two sections and
nothing else
(ADR-0214).
Needs you comes first and is the whole worklist: each park with its age, the worktree it holds —
or the branch resume reopens it from, when you have already removed that worktree — the usage
reading its session ended on and its resume command; each lane stopped at the worktree cap; each lane
still on disk after a merge; and every merge’s still-open findings with their file:line. A park the
repository itself shows as finished closes that section under Already settled, clear the record,
counted apart from the live ones, so a plan you closed by hand is never repeated at you as work.
Now follows: per lane, the plan, the step and how long it has been in it, and what it has spent.
A first section that says it found nothing means nothing is waiting on you.
What last night produced is one command away: node tools/conductor/conductor.mjs digest --history writes digest-history.md beside it, newest run first, each run with its own closed
plans, its failures and its Totals — the usage windows at run start and run end, and gate minutes
split into the full workspace suite and everything else, with the count of suite runs skipped because
the conductor had already seen that exact tree pass
(ADR-0207).
Nothing writes that file until you ask for it, and a closed plan’s own committed ## Close review
carries its review either way.
The operator guide — every command, what to do about each kind of park, and how to verify a new CLI
version — is tools/conductor/README.md. Its tests need no network
and spend nothing:
node --test "tools/conductor/test/*.test.mjs"What else there is to read
- Testing and visual QA — the
core/tests/harness that hard-tests every preset for reactivity, animation, shape sanity and beat response. - Headless capture and video — the
shotCLI,--renderand the live video-out. - MilkDrop conversion — reading what
milkconvproduced. - Embedding the core — putting the engine inside another application.
- On-device validation — the manual checklist for what CI cannot run: real GPUs, live loopback, installing the foobar2000 component.
- Releasing — how the version moves and what a
v*tag builds. - Non-functional requirements — the quantified budgets behind every “lightweight” and “real-time” claim.
- The architecture decisions — start with ADR-0001, the founding decision, with the rejected alternatives recorded.
Built from 13c7582 at version 0.158.0. This site tracks main and is not versioned per release.