The most useful contribution here is a TOML file, not Rust. See
docs/adding-an-agent.md for the walkthrough and
docs/support.md for where the blanks are — the two behavioural columns are answered
for one agent out of forty-six, and each answer is a line.
cargo test --workspace --tests
cargo clippy --workspace --all-targets
node crates/aviary-tauri/ui/check.mjs # every reference in the UI resolves
node crates/aviary-tauri/ui/render.mjs # renders every view and reads the text
python3 scripts/build-corpus.py --check
CI runs all five: the library crates on Linux, the desktop crate on macOS, because it links Tauri and macOS is where the app actually runs.
The two Node checks are complements. check.mjs checks what a compiler would — every import names a
real export, no name declared twice, every literal translation key exists — and cannot see what the
words say. render.mjs renders all seven views against the fixture below and reads the result: it fails
on a view that draws nothing, a translation key rendered as itself, a parameter never substituted, and
on any command that writes being reached while drawing. It also counts every plural that follows the
number one, which is a wording decision nobody has taken rather than a defect — see the note in the
file.
If you touched the profiles, regenerate the support table as well — a test compares it against what the profiles produce, so the coverage claim cannot drift from the code:
cargo run -p aviary-cli -- support > docs/support.md
Please do not reformat existing files. If your editor formats Rust on save, turn it off for this repository.
This is a decision rather than an omission, and it was measured before being made. With rustfmt's
defaults, cargo fmt --check reports 571 diff hunks. With a config matching this code's own habits —
max_width = 100, use_small_heuristics = "Max", single_line_let_else_max_width = 100 — it still
reports 501, so the disagreement is not a settings gap that a rustfmt.toml could close. It is mostly
rustfmt preferring to be more verbose: exploding a one-line let … else { continue } into three lines, a
fitting if a { X } else { Y } into five, and method chains that already fit inside 100 columns.
Adopting it would mean one commit touching five hundred places, which on this project costs more than it
saves: git blame on a line is how you find out when a decision was made, and a reformat puts one commit
in front of every one of those answers.
There is no rustfmt.toml, on purpose: a config file that does not make cargo fmt clean would suggest
the project uses rustfmt, which is the opposite of true. New code should look like the code beside it.
If you disagree, the argument to make is not "fmt is standard" — it is that the cost above is worth paying. That is a real argument; make it in an issue before writing the diff.
Both are load-bearing and both are checked by check.mjs only indirectly, so they are worth knowing:
- Every string that came out of a config file is inserted with
textContent, never as HTML. These files are written by twenty different tools and contain arbitrary shell commands. The one placeinnerHTMLis assigned is Markdown that the Rust side rendered with every raw-HTML event dropped. - Nothing is written without the user asking twice, and every write shows the bytes it will change
before it asks. See
Store::previewfor why the preview has to come from the same code as the write.
check.mjs also enforces that the four spine modules — state, dom, i18n, format — cannot reach
boot.js through the import graph. If they can, module evaluation order becomes load-bearing and the
failure looks like an unrelated ReferenceError. It has happened once; the check exists because of it.
crates/aviary-tauri/ui/fixtures/snapshot.json is one complete snapshot — 21 agents, 470 entities, 73
findings — produced by scanning a synthetic home rather than anybody's machine. The synthetic home is
the test corpus, copied to the paths the profiles declare for each file's role, plus a handful of
invented extras the corpus has no file for (a skill, a plugin, two instruction files, the usage
roll-up).
It exists so that something can be rendered without a laptop attached: nothing inspects this JavaScript
before a webview does, and check.mjs catches only what a compiler would. It is also the only way this
project can ever have a screenshot, since a screenshot of real data is exactly what the
identifying-names check cannot read.
AVIARY_BLESS=1 cargo test -p aviary-tauri --test interface_fixture # regenerate, then read the diff
It is also what the screenshots are made from. scripts/screenshots.sh writes each view out as a
standalone page — the document as the views left it, the real stylesheets, no scripts — and turns it
into a PNG with Chrome. Not a gate and not in CI, because Chrome is not a dependency of anything else
here; run it when a view has changed enough that the pictures in the README are wrong. --all renders
every view in both themes into target/shots, which is the only way to look at anything that needs a
layout: render.mjs reads text and knows nothing about CSS.
A test compares the committed file against what the code now produces, the same arrangement as
docs/support.md. If you change a model that reaches the interface, that test fails and regenerating
is the fix — the diff is the review. The file is deliberately pretty-printed so the diff is readable.
Two things to know before touching it. The scan takes its home, profile set and scan time from the
caller (scan_everything_in) precisely so this is possible; ProfilePaths is pinned to the built-in
profiles, because reading ~/.aviary/profiles would make the fixture depend on who ran the test last.
And the Changes view is the one view the fixture leaves empty — the file's own header explains why, and
what fixing it would take.
tests/corpus/ holds 28 real configuration files snapshotted from one machine by
scripts/build-corpus.py. Real files rather than hand-written fixtures, because the interesting cases
are the ones nobody would think to invent: eight leading // lines before a JSON object, foreign
marker keys sitting beside hook definitions, quoted TOML keys containing /, : and @,
comment-delimited managed regions.
Because this repository is public, the corpus is anonymised:
- Credential files are never read (deny-list above); secret values are replaced with a placeholder.
- Every identifying name — account, internal tool, internal host, internal project — is substituted
per
scripts/anonymize.local.toml, which is gitignored on purpose: a mapping table has to name what it replaces, so it is the one file that cannot be published.scripts/anonymize.example.tomldocuments the format. python3 scripts/build-corpus.py --checkfails if anything identifying survives a snapshot, and--reanonymiseapplies the current table to the corpus already committed — which is how a name that was missing from the table gets removed without re-snapshotting a machine that has moved on.cargo testwalks every text file in the repository, and every commit message, and fails if any mapped name appears, or if an absolute home path names somebody real. The home-path half runs without the mapping table, which is the case a fresh clone is in.
That last check exists because the corpus was clean and the code talking about the corpus was not: the leaks it found on the day it was written were all in prose — a doc comment quoting a real home path, a module comment naming an internal tool. Commit messages were added for the same reason, one round later, and found two more.
Tests discover their fixtures' features rather than hard-coding them — comment needles and marker keys are found by pattern — so re-anonymising the corpus cannot silently make a test vacuous.
If you add a fixture, run --check before committing. If you have no mapping table (most
contributors), the corpus is already anonymised and the check still runs its home-path half.
Three fields exist to record that nobody has checked something: Profile.reload,
FileSpec.precedence, and verified. Leaving them at their default is correct and the interface says so.
Filling one in with a plausible value is worse than leaving it blank — it makes Aviary assert something
about somebody's machine that nobody established, which is the one thing this program is built not to do.
Same applies to numbers. If a figure cannot be measured, the convention is an em dash and a sentence saying why, not an estimate that reads like a measurement.