Rules for agents contributing to aur-pkgbuilder (GTK4 + libadwaita desktop app that drives an AUR maintainer's release flow).
- Keep cognitive complexity below 25.
clippy::cognitive_complexityis enabled at warn (seeCargo.toml/clippy.toml); treat-D warningsruns as enforcing the bar — match the shape of existing modules such asworkflow::ssh_setuporworkflow::validate. - Keep functions under 150 lines. Split at natural seams —
fn build(nav, state)insrc/ui/*.rsshould delegate to smallfn *_group(…) -> PreferencesGrouphelpers, not inline everything. - Prefer straightforward data flow (few threaded parameters, clear
ownership boundaries). GTK widgets live on the main thread; long work
is routed through
runtime::spawn/runtime::spawn_streaming. - Add
///rustdoc to all new public items. Private items benefit from docs too — add them when the behavior is non-obvious. - Use the What / Inputs / Output / Details rustdoc layout for non-trivial APIs (template below).
- Add focused unit tests for pure logic. Keep tests deterministic — no network, no files outside the test harness.
- Identify the root cause before writing code.
- Write or adjust a test that fails on the bug.
- Run the test — it must fail. If it passes, the test does not reproduce the issue; adjust it.
- Fix the bug.
- Run the test again — it must pass. If not, iterate on the fix.
- Add edge-case tests when they reduce future regressions.
Run from the repository root, in this order:
cargo fmt --all(usesrustfmt.toml)cargo clippy --all-targets --all-features -- -D warnings(usesclippy.toml)cargo checkcargo test --bin aur-pkgbuildercargo deny check(usesdeny.toml; optional locally ifcargo-denyis not installed)
cargo test --bin aur-pkgbuilder is required because the crate has no
lib target — the plain cargo test command will error with "no
library targets found".
- Clippy:
clippy.tomlsetscognitive-complexity-thresholdandtoo-many-lines-threshold.[lints.clippy]inCargo.tomlenablescognitive_complexity = "warn". CI/agents runcargo clippy --all-targets --all-features -- -D warnings. - rustfmt:
rustfmt.tomlat the repo root. - Dependencies / licenses:
deny.tomlforcargo deny check. - Secrets:
.gitleaks.tomlforgitleaks detect. - Shortcuts: root
Makefiledelegates todev/Makefile(make fmt,make clippy,make test,make pre-commit, …).
When changing any of the above, update this section and keep CLAUDE.md
in sync.
Before completing any task, ensure all of the following pass:
- Format:
cargo fmt --allproduces no diff. - Clippy:
cargo clippy --all-targets --all-features -- -D warningsis clean. - Compile:
cargo checksucceeds. - Tests:
cargo test --bin aur-pkgbuilder— all tests pass. - Complexity: new functions stay under ~25 cognitive complexity.
- Length: new functions stay under ~150 lines.
- Exceptions: if a threshold cannot reasonably be met, add a
documented
#[allow(...)]with a justification comment. Use sparingly. - cargo-deny:
cargo deny checkpasses when usingmake pre-commit(installcargo-denyif needed).
-
For non-trivial APIs, use the structured rustdoc layout with What, Inputs, Output, and Details sections:
/// What: Brief description of what the function does. /// /// Inputs: /// - `param1`: Description of parameter 1 /// - `param2`: Description of parameter 2 /// /// Output: /// - Description of return value or side effects /// /// Details: /// - Additional context, edge cases, or important notes. pub fn example_function(param1: Type1, param2: Type2) -> Result<Type3> { // implementation }
-
Do not write comments that narrate what the code does (
// import the module,// increment the counter). Comments should explain non-obvious intent, trade-offs, or constraints.
For bug fixes:
- Create a failing test that reproduces the issue.
- Fix the bug.
- Verify the test passes.
- Add additional edge-case tests if applicable.
For new features:
- Add unit tests for the core logic.
- Prefer pure functions that are test-friendly. See
workflow::ssh_setup::upsert_host_blockand its three unit tests as a reference — separating parsing/formatting from I/O makes the asserts trivial. - Test error cases and edge conditions.
Test guidelines:
- Tests must be deterministic — no network, no dependence on the developer's home directory, no assumptions about other test order.
- Use the default test threading. There is no parallelism hazard today.
- Edition: Rust 2024 (see
Cargo.toml). - Naming: Clear and descriptive; clarity over brevity.
- Errors: Use
Result. Typed errors inworkflow::*(admin::AdminError,ssh_setup::SshSetupError,aur_account::AurAccountError). UI code matches on the specific error variant to render appropriate toasts. - Never use
unwrap()/expect()in non-test code. The exceptions already in the tree (child.stdout.take().expect("stdout piped")immediately after settingStdio::piped()) are local to the call and documented by the piping call above them — match that pattern if you absolutely need it. - Control flow: Prefer early returns over deep nesting. Reach for
let … else { … return; }when a guard is sharper than anif let. - Logging: there is no tracing infrastructure yet; user-facing
diagnostics go through toasts and the log view. Don't add println!s
to production code paths — route them through the
LogLinestream instead so they appear where the user is already looking.
- GTK/libadwaita widgets must live on the main thread.
- For one-shot Tokio work + single callback, use
runtime::spawn. - For streaming subprocess output to the UI, use
runtime::spawn_streamingwith anasync_channel::Sender<LogLine>. - Shared state (
state::AppStateRef=Rc<RefCell<AppState>>) is single-threaded. Never hand it to a Tokio task — clone the fields you need instead.
makepkg,git,ssh,ssh-keygen,ssh-keyscan,updpkgsums,xdg-open,fakeroot,shellcheck,namcap,which— any of these may be missing.- Required tools are surfaced on the connection screen
(
src/workflow/preflight.rs—makepkg,git,ssh,updpkgsums). - Optional tools (
shellcheck,namcap,fakeroot) are probed at use time and reported as skipped with an install hint — see theis_available(program)helper insrc/workflow/validate.rs. Never crash because an optional tool is missing.
makepkgmust not run as root. Thenix_is_root()guard insrc/ui/build.rsstays in place.xdg-openfailing (no display, no handler) returns an error — handle theErrand toast it.
- User-facing errors must say what failed and what the user can
do next. Missing tool? Quote the exact
pacman -S --needed …command. Bad config? Point at the path of the file.
If config keys or schema change:
- Update
CONFIG_HEADERinsrc/config.rs(forconfig.jsonc) orREGISTRY_HEADERinsrc/workflow/registry.rs(forpackages.jsonc) so the fixed schema comment in saved files stays accurate. - Add new fields with
#[serde(default)]so existing JSONC files upgrade in place without erroring. - The legacy
.jsonfallback branches inConfig::loadandRegistry::loadremain in place — do not remove them.
- Follow libadwaita patterns:
PreferencesGroupfor grouped rows,ActionRow+ suffix buttons for per-row actions,Toastfor async results,AdwNavigationViewfor multi-step flows. - Destructive actions wear the
destructivepill label and, for primary buttons, thedestructive-actionCSS class. previewbadge marks intentional stubs that returnNotImplemented(&'static str)fromAdminError/SshSetupError. The UI surfaces "Coming soon: …" rather than silently failing.- Long subprocess output streams line-by-line into a shared
LogView.
- Do not create or edit
*.mdfiles (includingREADME.md,CONTRIBUTING.md,SECURITY.md,AGENTS.md,CLAUDE.md) unless explicitly requested. - Prefer rustdoc for code documentation.
- When the user asks for README updates, keep it user-facing — pipeline
details belong in rustdoc or
CONTRIBUTING.md, not in the README.
These rules are mandatory, not suggestions. Most of them prevent failure modes specific to a maintainer's environment: SSH keys, local shell, and a push-access remote.
- Never interpolate package names, file paths, or user input
directly into shell command strings. Always pass them through
Command::new().arg()—Command's arguments are not interpreted by a shell. - No
sh -c "…"with user-controlled data. If a multi-step shell invocation is required, build the pipeline inside a Rust function that spawns each step separately. - When logging the command for the user's benefit (e.g.
$ makepkg -f --noconfirmin the log view), that string is display only — the actual invocation still uses discrete.arg()calls.
- Do not overwrite existing key files.
ensure_aur_keyonly runsssh-keygen -t ed25519 -f ~/.ssh/aur …when~/.ssh/aurdoes not exist. Preserve this invariant. - Assert permissions after writes.
~/.sshstays0700, the private key and~/.ssh/configstay0600,~/.ssh/known_hostsstays0644. Re-callfs::set_permissionseven if the tool already set them correctly — some editors / umasks mess with them. - Never auto-trust host keys silently. When appending to
known_hosts, capture the SHA256 fingerprint viassh-keygen -lf -and surface it as a toast so the user can verify against the AUR wiki. - Never log private key contents, session output that contains passphrases, or anything else that would embarrass a maintainer on screenshot day. Fingerprints (SHA256 prefixes) are fine.
- The PKGBUILD fetcher (
workflow::sync::download_pkgbuild) usesreqwestwithrustls-tls— do not disable TLS verification. - The AUR RPC (
workflow::aur_account::fetch_my_packages) validates the response viaerror_for_status()before parsing. Don't swallow HTTP errors silently. reqwestwithdefault-features = falseis deliberate — do not addnative-tlsor the default feature set. Thejsonfeature is on for RPC decoding.
- Validate paths before writing.
aur_git::aur_clone_dirandsync::package_dirresolve paths relative to a trusted work directory. When adding new file writers, join against the configured work dir rather than dropping into/tmpor$HOME. - Create parent directories explicitly via
tokio::fs::create_dir_allbefore writing a child path. - JSONC round-trip: never hand-parse the config or registry files —
route through
config::read_jsonc. That strips comments viajson_comments::StripCommentsbeforeserde_jsonsees the bytes.
nix_is_root()insrc/ui/build.rsis the single source of truth for "am I running as root?" before spawningmakepkg. Don't bypass it and don't duplicate the check — extend the helper if you need more logic.
- Prefer direct dependencies over transitive ones for security-sensitive functionality.
- Run
cargo updatecarefully — pinned major versions inCargo.toml(e.g.gtk4 = "0.11",adw = "0.9") match the GTK/libadwaita versions available on stable Arch. Bumping them may require bumping the feature flags (v4_14,v1_5). - Do not add new dependencies that require
unsafefor their core functionality unless there is no safe alternative and the crate is well-maintained.
| Concern | Enforcement | Threshold |
|---|---|---|
| Cognitive complexity | clippy::cognitive_complexity + clippy.toml |
25 |
| Function length | manual review (too-many-lines-threshold in clippy.toml) |
150 lines |
| Clippy warnings | -D warnings on the command line |
N/A |
| Licenses / advisories | cargo deny check + deny.toml |
policy |
| Data flow / coupling | manual review | N/A |
- No unsolicited
*.md/ wiki / README edits — theCONTRIBUTING.md,SECURITY.md,AGENTS.md, andCLAUDE.mdfiles only change when the user explicitly asks. - Preserve root-refusal, write-once-key, and fingerprint-surface invariants when touching the SSH / build / publish flows.
- Keep typed errors typed — don't collapse
AdminErrororSshSetupErrorintoanyhow::Errorat the boundary; the UI code matches on the variants to decide whether to show "Coming soon: …" vs a concrete failure toast.