Skip to content

Latest commit

 

History

History
147 lines (113 loc) · 8.81 KB

File metadata and controls

147 lines (113 loc) · 8.81 KB

Contributing

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.

The five gates

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

cargo fmt is deliberately not run, and not enforced

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.

Two rules in the interface code

Both are load-bearing and both are checked by check.mjs only indirectly, so they are worth knowing:

  1. 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 place innerHTML is assigned is Markdown that the Rust side rendered with every raw-HTML event dropped.
  2. Nothing is written without the user asking twice, and every write shows the bytes it will change before it asks. See Store::preview for 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.

The interface fixture

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.

The test corpus

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.toml documents the format.
  • python3 scripts/build-corpus.py --check fails if anything identifying survives a snapshot, and --reanonymise applies 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 test walks 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.

What not to guess

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.