iru-rust-code-one-task
Implement a single task from a Rust `implementation_plan.md`-style task list — just the implementation and its tests — in any Cargo package or workspace: an agent-friendly command-line tool scaffolded
SKILL.md.claude/skills/iru-rust-code-one-task/SKILL.md
GitHubImplement one plan task (Rust)
Carry out a single task's implementation, tests, and checkbox update in a Rust crate or workspace — nothing else.
This is the narrowest execution unit for a rust task in the iru-code pipeline: iru-rust-code-one-task-group
calls it once per task in a bucket (potentially many at once, in parallel agents), then handles license headers,
rustdoc auditing, tests, coverage, clippy/rustfmt/cargo-deny, and any container-backed tests itself, once for the
whole bucket. It uses a medium model on purpose — the plan carries the hard reasoning; this skill executes one task
against code agreements that live in reference/.
Two things shape every Rust task:
- Logic lives in the library. In a CLI,
src/main.rsonly gathers process facts and calls onelibentry point with injected writers; commands, policies, the TUI state and rendering are library code with unit tests. That is what makes the 80% per-file coverage bar reachable — where a piece of code goes is the first decision. - A contract CLI's observable behaviour is the interface. When the project follows the catalog's CLI contract,
stdout/stderr separation, the exit-code enum, the JSON envelope,
--format, no prompts without a TTY, andNO_COLORare non-negotiable: a change that breaks one is a bug, whatever the task says about style.
Step 0 — Detect the project shape once
Settle these from the files, never from assumptions (this skill is copied into other repositories):
- Rust at all: a
Cargo.tomlat the repository root or in the directory the task names. None → this isn't a Rust project: report it as a blocker (the task was mis-tagged) and stop. - Layout:
cargo metadata --no-deps --format-version 1 --locked(nojqassumed — read the JSON): workspace members (workspace_members, a virtual manifest with only[workspace],[workspace.package]/[workspace.dependencies]/[workspace.lints]inheritance), each package's targets (kind:lib,bin,test,example,bench,custom-buildforbuild.rs),edition,rust_version, andfeatures. Note anxtaskmember and the.cargo/config.tomlalias that runs it. A bin-only package (nolibtarget) can't be imported bytests/— note it; don't createlib.rsunless the task says so. - Toolchain:
rust-toolchain.toml(channel, components, targets),cargo --version/rustc --version;clippy.toml(msrv,allow-unwrap-in-tests, …),rustfmt.toml(style_edition,max_width),deny.toml. An older toolchain thanrust-version→ "compile-check unavailable", not a reason to edit the toolchain file. - Lints:
[lints]/[workspace.lints](and whether members saylints.workspace = true) — what new code must satisfy (missing_docs,unsafe_code = "forbid", the clippy pedantic subset,print_stdout,unwrap_used). - Frameworks (from
[dependencies]):clapwith thederivefeature and#[derive(Parser)]→ derive API, else builder;clap_complete/clap_mangen;ratatui(and whether code importsratatui::crosstermor a separatecrossterm);tokio(and its features);serde/serde_json(checkcargo tree -e features -i serde_jsonforpreserve_orderonly when a task touches JSON ordering);thiserror/anyhow;anstream/anstyle/owo-colors;dialoguer;signal-hook/ctrlc;indicatif;tracing/log. - Tests (
[dev-dependencies]+tests/):assert_cmd,predicates,tempfile/assert_fs,snapbox/trycmd(tests/cmd/*.trycmd),insta(snapshots/),proptest,rstest,serial_test,testcontainers(container tests marked#[ignore = "…"]); golden files (tests/golden/); a#[cfg(test)] mod test_supporthelper; two or three existing tests for naming and helper style. - CLI contract: the project follows it when it has an exit-code enum (or const block) converted into
std::process::ExitCodewith the contract's numbers (2 usage, 3 validation, 4 not found, 5 confirmation…), a--format/--jsonglobal flag, or an error envelope withschemaVersion. Thenreference/cli-contract.mdrules are binding for every command/output change. A CLI without those signals → follow its own conventions and don't retrofit the contract unless the task asks (report the divergence). A non-CLI crate → the CLI references are never routed. - Host:
uname -s(absent/MINGW*/MSYS*→ Windows).#[cfg(windows)]code can be compile-checked with--target x86_64-pc-windows-msvconly whenrustup target list --installedalready lists it — never install a target here.
Step 1 — Decide where the code goes, then read only the reference material this task needs
A path in the task wins. Otherwise: domain logic, command behaviour, output/terminal policies, config merging, TUI
state/rendering → the library (src/lib.rs modules, src/commands/<name>.rs in the scaffold); clap types →
src/cli.rs; process-fact gathering → src/main.rs (only that); generators (man pages, completions files, CLI
reference) → xtask/; shared code in a workspace → the member that owns the concept. Genuinely ambiguous → the
library, and say so in Step 8. Record the packages the task touches — Step 5 derives the compile-check from them.
This skill ships a reference/ directory next to this file. Read reference/README.md first — it is short and
routes you to the rest — then read only what it points at. The routing, restated:
| Read | When |
|---|---|
reference/code-style.md |
Always. |
reference/testing.md |
The task writes or updates tests — nearly every task. |
reference/error-handling.md |
An error type, a fallible function, a panic/expect, or the failure→exit-code mapping. |
reference/rustdoc.md |
The task adds or changes a pub item (including clap fields/variants — their docs are --help). |
reference/cli-contract.md |
Contract CLI (Step 0) and the task touches a command, flag, output, exit code, prompt, signal, config, schema or completion. |
reference/clap.md |
A subcommand, argument, flag, value enum, env-backed flag, help text, completions/man pages, or config precedence. |
reference/output-and-terminal.md |
Writing to stdout/stderr, JSON/JSONL/the envelope, colour, prompts, progress, SIGINT/SIGTERM, broken pipe. |
reference/ratatui.md |
ratatui is a dependency and the task touches the terminal UI. |
reference/ui-contract.md + reference/ui-implementation.md |
ratatui is a dependency and the task adds or changes a screen, component, list or other UI. |
reference/security.md |
Spawning a process, paths from arguments/config, untrusted input, tokens/secrets, writing files, or anything tempting unsafe/env::set_var. |
reference/async.md |
tokio is already a dependency and the task touches async code, cancellation, timeouts, or async tests. |
If reference/ is missing (the skill directory was copied without it), proceed using the repository's own
CLAUDE.md conventions and the surrounding code's style, and say so in Step 8 — don't guess at what the reference
would have said.
Step 2 — Re-check the current code state
Read the actual current content of every file the task touches before editing — other tasks in the same bucket may
be landing concurrently. Also read what binds to what you change: for a new/renamed subcommand or flag, src/cli.rs,
the dispatch in lib::run, the schema command, and the end-to-end tests and golden files that pin its output; for
a changed error, every From impl and the exit-code mapping; for a changed pub signature, its callers (grep -rn
across the workspace, xtask/ and tests/ included). Note what the surrounding code does about the things
reference/ has opinions on; a crate that consistently does something else wins, and the divergence goes in Step 8.
Step 3 — Implement exactly what the task specifies
Apply reference/code-style.md and whichever optional files Step 1 selected, plus the repository's own CLAUDE.md
where more specific. Work in dependency order: domain types and errors → logic → command wiring (clap types,
dispatch, schema) → main.rs only if process facts change.
Non-negotiable, because a gate, the contract, or another platform enforces them:
main.rsstays thin — no logic, no branching beyond collecting facts and calling the library.No
println!/print!/eprintln!/process::exitin library or command code: write through the injected writers and return anExit; never panic on input (unwrap/expect/indexing on untrusted data).Contract CLIs: results only on stdout, everything else on stderr; failures map to the existing exit-code enum in one place (never a new number, never renumbering); JSON output keeps sorted keys,
schemaVersion, and the error envelope; prompts only behind the TTY/--no-input/CIpolicy; destructive commands keep--dry-run/--yes; a TUI starts only on a TTY and its actions keep a--jsoncommand.Docs on every new
pubitem (missing_docs+-D warningsin CI),# Errors/# Panicssections where they apply.No new dependency, feature, crate, workspace member, edition/MSRV/toolchain change,
unsafe, or lint relaxation (#[allow],[lints]edits) unless the task explicitly calls for it — a blocker to report (Step 6) otherwise. When the task does add a dependency, followreference/code-style.md(workspace dependencies, minimal features);cargo checkupdatesCargo.lock— never hand-edit it.Never hand-edit generated output — completions, man pages, a generated
cli-reference.adoc/partial,instasnapshots you haven't reviewed, anything undertarget/.Terminal UI work decomposes first (
reference/ui-contract.md§2): write theScreen › Region › Componenttree (inputs,Actionevents, visual states, what is reused) before coding, build leaves first, keep rawKeyEvents inside key handling, render collections through the visible-window pattern (reference/ui-implementation.md), and include the tree in the Step 8 report.
Don't add anything the task didn't ask for — no speculative abstractions, no unrelated cleanup, no restyling.
Step 4 — Write or update the tests
Follow reference/testing.md and the existing test style: unit tests in a #[cfg(test)] mod tests at the bottom of
each changed file, commands tested through the crate's in-memory test helper (stdout/stderr buffers, fake confirm,
injected TTY facts); for every new or changed command, error path or flag in a contract CLI, end-to-end tests in
tests/cli.rs (or the crate's equivalent) through the clean-environment helper — exit code from a table, exact
stdout (golden file for --json), stderr("") on success, one envelope on stderr and empty stdout for a JSON
failure, a closed-stdin "never prompts" case, NO_COLOR/no-TTY output without escape bytes; TestBackend tests for
TUI rendering and plain KeyEvent tests for its state — and, for a screen/component the task added or changed, the
UI tests of reference/ui-implementation.md §4–§6: every visual state with minimal/typical/many-items/long-content
and right-to-left fixtures, each captured through src/tui/ui_screenshots.rs as
.ui-screenshots/<kind>/<Id>/<state>--<direction>--<theme>.txt for every .ui-quality.yml direction × theme the
TUI has (plus no-color), and the asserted accessibility checks at the file's level. Tests never depend on the real terminal, the network, the
user's config, or process-global state (no env::set_var; temp dirs for HOME/XDG_CONFIG_HOME). A test that
needs Docker or the network is marked #[ignore = "requires docker (testcontainers)"]. Cover the edge cases implied
by the doc comments' # Errors/# Panics. Update golden files deliberately and name each one in Step 8. Do not run
the tests — iru-rust-code-one-task-group runs them once per bucket.
Step 5 — Format and compile-check
A broken compile in one task blocks every other task in a parallel bucket from validating.
- Format only the files this task wrote or edited:
rustfmt --edition <edition> <file> …(rustfmt picks up the repository'srustfmt.toml). rustfmt also formats the out-of-line child modules of a file it's given (verified) — when a touched file is alib.rs/main.rs/mod.rs, checkgit diff --name-onlyafterwards and list any other file it reformatted in Step 8. Never runcargo fmtover the whole workspace here (sibling tasks are editing it). - Compile-check the touched packages, tests included:
cargo check --all-targets --locked -p <package>(one-pper touched package;--workspaceonly when the task changed something every member depends on), adding--all-featureswhen the package declares features (the CI convention) — and also the default features if the task touched#[cfg(feature = …)]code.--lockedfails ifCargo.lockwould change: for a task that legitimately adds a dependency, run once without--lockedto update the lock file, then re-run with it. Keep the crate's lint configuration as is: a rustc warning (missing_docs,unused_imports,dead_code) in code this task wrote is the task's own error to fix now. Clippy is not run here — the group runs it once — but write code that satisfies the crate's[lints](reference/code-style.md). #[cfg(windows)]/#[cfg(unix)]code: when the other target is already installed, alsocargo check --all-targets --locked --target <triple> -p <package>; otherwise say "other-OS cfg unchecked".
Outcomes:
- Failure in code this task wrote or edited: fix it — it's the task's own code, not a finding to defer.
- Failure outside the task's scope (a sibling task's in-flight edit, a pre-existing error): don't fix unrelated
code; re-run once if it looks like a transient race with a concurrently-writing sibling, then note it in Step 8.
(Concurrent
cargoruns wait on the build-directory lock — "Blocking waiting for file lock" is normal, not a failure.) cargocan't run at all (toolchain older thanrust-version, no network for a first dependency fetch, arust-toolchain.tomlchannel that isn't installed): report "compile-check unavailable" with the reason — the group's validation still catches real errors.
Step 6 — When to interrupt the user
Keep interruptions rare. Stop and use AskUserQuestion (or plain text if no real choice is offered) only when:
- The task is ambiguous in a way that changes correctness or scope and can't be safely inferred.
- The task's approach is infeasible against the real code (it names an item that doesn't exist, needs a binary-only
crate's internals from
tests/, or demands a behaviour the CLI contract forbids — a prompt without a TTY, data on stderr, a renumbered exit code). - The task needs a new dependency/feature/crate/member, a toolchain/edition/MSRV change,
unsafe, or a lint relaxation it doesn't explicitly call for. - The task would put a secret in arguments, output, logs, or source (
reference/security.md) — a design problem to report. - A breaking change to published JSON output (renamed/removed/retyped field) that the task doesn't acknowledge — it
needs a
schemaVersiondecision.
Don't stop merely because the task is nontrivial. Don't report the task done to work around a blocker; report it as
blocked (Step 8). A conflict between reference/ and the surrounding code is not a reason to stop: follow the
surrounding code and report the divergence.
Step 7 — Check off this task's checkbox now
If this task did not stop on a blocker, update implementation_plan.md at the repository root before reporting back.
Re-read it fresh (sibling tasks may be editing it), locate this task's own checkbox line by the exact text passed in,
flip [ ] to [x] for it and every sub-task the work covered, and add a short note naming the packages and files
touched, e.g.:
- [x] Task 3. **Add `list` command with `--limit`/`--cursor`** — src/cli.rs, src/commands/list.rs, schema entry,
tests/cli.rs + tests/golden/list-default.json; group validation pending.
Keep the "group validation pending" wording — iru-rust-code-one-task-group replaces it with the final outcome.
Edit only this task's own line(s). If this task stopped on a blocker, leave its checkbox untouched.
Step 8 — Report the outcome
Hand back a short summary:
- The shape detected (workspace/single crate, packages and targets, edition/MSRV, clap API, ratatui/tokio, contract CLI or not, test crates) and the packages touched, with the reference files actually read.
- The files touched and the tests added/updated (unit, end-to-end, TUI, doctests — not run), and every
golden/snapshot file created or changed (and whether a JSON change is additive or needs a
schemaVersionbump). - The compile-check result per package (and
--all-features/other-target checks): passed, failed on this task's code and fixed, failed outside this task's scope, or unavailable (and why); any file rustfmt reformatted beyond the task's own. - For terminal UI work: the
Screen › Region › Componentdecomposition, and the UI tests and captures added (component/screen/flow, states × directions × themes, accessibility rule ids checked; not run). - Generated artifacts that need regenerating (completions, man pages, CLI reference via
cargo xtask …) because the command tree changed. - Whether this task stopped on a blocker, with enough detail for the caller to surface it.
- Any deliberate deviation from
reference/, and pre-existing violations noticed and left alone (println!in commands, logic inmain.rs,unwrapon input, a missing contract rule, secrets in output).
This skill never runs tests, clippy, coverage, cargo deny, cargo doc, the full workspace cargo fmt, or
license-header checks, and never captures a quality baseline.
Verification notes
Exercised in $TMPDIR/iru-verify/cli/rust-coding/ on macOS (arm64) with Rust 1.99.1 (clippy 0.1.99, rustfmt
style_edition = "2024"), cargo-nextest 0.9.148 and cargo-llvm-cov 0.9.1, crate versions resolved live from
crates.io on 2026-10-16 (clap 4.6.7, clap_complete 4.6.11, clap_mangen 0.3.3, ratatui 0.30.2, anstream 1.0.0,
anstyle 1.0.14, dialoguer 0.12.0, signal-hook 0.4.5, thiserror 2.0.21, serde_json 1.0.154, toml 1.1.8, tokio 1.53.3,
assert_cmd 2.2.2, predicates 3.1.4, tempfile 3.27.0):
- A
democlicrate built to the guidance (edition 2024,rust-version = "1.88", the recommended[lints]set;Exitenum 0–7/130/143,CliError+RunError, sorted-keywrite_json, the envelope, colour/prompt policies,signal-hookinterrupts,greet/reset/completion/schema/browsewith a Ratatui list) passedcargo check --all-targets --locked,cargo test(45 unit + 9 end-to-end + 1 doctest),cargo nextest run(54 passed),cargo clippy --all-targets --all-features -- -D warningsandcargo fmt --check, andRUSTDOCFLAGS="-D warnings" cargo doc --no-deps;cargo check/clippy --target x86_64-pc-windows-msvcalso passed (incl. signal-hook'sregister_conditional_shutdown), and so didcargo check --target x86_64-unknown-linux-gnu.cargo llvm-cov: 96.6% lines overall, every file ≥ 86% (main.rs100% via the end-to-end tests); the lowest,browse.rs86.8% andtui/mod.rs91.5%, miss exactly the terminal-boundratatui::run/event-loop lines. - A second crate compiled and tested every other snippet (path canonicalization incl. a symlink escape, argv-only
Command, a redactingToken+0600file, control-character stripping, config precedence withdeny_unknown_fields,clap_mangen::generate_to, tokioselect!with timeout/ctrl_c/SIGTERM). - Real-process checks: SIGTERM → 143, SIGINT → 130 after cleanup, a second SIGINT during cleanup exits after 50 ms
instead of 300 ms; tokio variant: timeout → 6, SIGTERM → 143, SIGINT → 130;
democli browseunder a pseudo-terminal (80×24) rendered, quit onqwith exit 0, entered and left the alternate screen. - Findings folded into
reference/: a bool flag withenv = "…"rejects1unless it usesBoolishValueParser;assert_buffer_linesalso compares styles (useBuffer::with_lines+set_style+assert_buffer);env_clear()in end-to-end tests dropsLLVM_PROFILE_FILEandmain.rsthen measures 0% (forward it); clippy pedantic'sstruct_excessive_boolsfires on clap flag structs,doc_markdownon bareserde_json,assert_is_emptyonassert!(!s.is_empty());allow-unwrap-in-testsdoesn't cover helper fns intests/*.rs; clap'sStyledStrDisplayis plain text;#[tokio::test(start_paused = true)]needstest-util; rustfmt formats child modules of a file it's given. reference/ui-contract.md(verbatim catalog copy) andreference/ui-implementation.mdwere added later for the UI contract without a Rust toolchain on the authoring machine: the Ratatui APIs they use (Buffer::content/area,Cell::symbol,Span::width,ListState::offset_mut,TestBackend::buffer) were checked against docs.rs, the code sketches were not compiled.- Unverified locally: running the binaries on Windows/Linux (only cross-
checked), the TUI on a Windows console, realdialoguerprompts (they need a TTY; the policy and fake-confirm paths are tested), and container-backed (testcontainers) tests.