Skip to content
Русский

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:

Terminal window
cargo build # the everyday build
cargo run -p standalone --release # the app
cargo nextest run --workspace # the whole suite
cargo clippy --workspace --all-targets -- -D warnings
cargo 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:

Terminal window
sudo pacman -S --needed rustup vulkan-swrast vulkan-tools \
cargo-nextest cargo-release cargo-deny uv \
libpulse pkgconf wayland libxkbcommon ffmpeg nodejs npm python
rustup show # inside the checkout: installs the toolchain rust-toolchain.toml pins
git config core.hooksPath .githooks # the pre-push gate, opt-in per clone
npm --prefix studio ci # the studio's dependencies, which the hook's studio step needs
cargo build

vulkan-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:

RunWall timeResult
.githooks/pre-push, run by hand so every step runs333 sgreen, no step skipped; the test step alone took 297 s, 1695 passed and 86 skipped
cargo nextest run --workspace454 s1774 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_adapter prints 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] layout and 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:

Terminal window
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:

Terminal window
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:

Terminal window
git config core.hooksPath .githooks

An 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:

StepCommand
Doc linksnode scripts/check-doc-links.mjs
Index rowsnode scripts/check-index-rows.mjs
Index rows (self-test)node scripts/check-index-rows.mjs --self-test
Backlog claimsnode scripts/check-backlog-claims.mjs
Filter figuresnode scripts/check-filter-figures.mjs
Comment hygienenode scripts/check-comment-hygiene.mjs
Contents blocksnode scripts/toc.mjs --check
Contents blocks (self-test)node scripts/toc.mjs --self-test
Reader prosenode scripts/check-reader-prose.mjs
Release tagnode 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
Translationsnode scripts/check-translations.mjs
Translations (self-test)node scripts/check-translations.mjs --self-test
System countsnode scripts/check-system-counts.mjs
Settings have filesnode scripts/check-settings-have-files.mjs
Claude declarationsnode scripts/check-claude-declarations.mjs
Claude declarations (self-test)node scripts/check-claude-declarations.mjs --self-test
Gate carriersnode scripts/check-gate-carriers.mjs
Gate carriers (self-test)node scripts/check-gate-carriers.mjs --self-test
Diffusion filterpython3 tools/sd-filter/test_sd_filter.py (skips with no python3)
Studio typechecknpm --prefix studio run typecheck (skips with no studio/node_modules)
Studio lintnpm --prefix studio run lint (same guard)
Studio testsnpm --prefix studio test (same guard)
Formatcargo fmt --all --check (only when the push moved a Rust-relevant path — below)
Lintcargo clippy --workspace --all-targets -- -D warnings (same condition)
Rustdoccargo doc --workspace --no-deps under RUSTDOCFLAGS=-D warnings (same condition)
Testscargo 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): yes

Whatever 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:

PushWall timeWhat ran
A range touching no Rust-relevant path8.2 sthe Node roster and the Python suite; every cargo step skipped
A Rust range whose tree has a full-suite record12.1 sthe 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 record557.2 severything; 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:

Terminal window
node scripts/check-npm-audit.mjs # the three audits, against the registry
node scripts/check-npm-audit.mjs --self-test # the gate's own assertions, offline

The 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:

Terminal window
# Windows only: Spout is a Windows SDK, and the feature compiles nothing elsewhere
powershell -File packaging/spout/fetch-sdk.ps1 # once per checkout
cargo check -p standalone --features spout

Bypass 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 pathWhy the local loop cannot see itCompiled before a tag by
standalone/src/capture_mac/#[cfg(target_os = "macos")], so no Windows or Linux target type-checks itcheck (macos-latest) in ci.yml
cfg(feature = "spout") in standalone/off by default, and code behind a disabled feature is not type-checkedthe spout job in ci.yml
plugin-foobar/C++ against the third-party foobar2000 SDK, which is gitignored; no cargo command builds itthe 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:

Terminal window
node scripts/prune-target.mjs # dry run: what would go, and the bytes
node scripts/prune-target.mjs --apply # delete it
node scripts/prune-target.mjs --verify-fresh # every artifact the loop reports is still fresh

The 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:

Terminal window
rm -rf target/debug/incremental # sh
Remove-Item -Recurse -Force target\debug\incremental # PowerShell

That 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.

Terminal window
node tools/conductor/conductor.mjs check # local.json, the CLI version, the queue
node tools/conductor/conductor.mjs run # both lanes, until the queue is merged or parked
node tools/conductor/conductor.mjs status # what each lane is doing, and every park

Before the first run:

  • Spend caps. Write tools/conductor/local.json with 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: approved is 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 review section.

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:

Terminal window
node --test "tools/conductor/test/*.test.mjs"

What else there is to read

Built from 13c7582 at version 0.158.0. This site tracks main and is not versioned per release.