Install
$ agentstack add skill-furedea-agent-harness-rust-style ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Rust Coding Style Guidelines
Scope
This skill governs code written inside an already-bootstrapped Rust project: package commands, module layout, test authoring, refactoring, code review, naming, ownership, errors, filesystem operations, comments, and docs.
Project bootstrap (flake.nix, direnv, initial Cargo.toml, initial rust-toolchain.toml) belongs to the nix-dev-init skill. If the project is not yet bootstrapped, defer to nix-dev-init first and return here once direnv allow succeeds and Cargo is available.
Why the split
Bootstrap is an environment concern. This skill is for day-to-day Rust implementation inside an existing project. Do not duplicate Nix, direnv, or initial Cargo setup rules here.
Package Management
- Use Cargo for Rust package operations.
- Add dependencies with
cargo addwhencargo addis available. - Add development dependencies with
cargo add --dev. - Use
cargo checkfor a fast compile/type-check pass when tests are not needed yet. - Use
cargo testfor the default verification pass. - Use
cargo clippy --all-targets -- -D warningsfor linting. - Do not edit
Cargo.lockmanually. - Commit
Cargo.lockfor applications, CLI tools, and internal tools. - For library crates, follow the repository's existing
Cargo.lockpolicy. - Do not use
@latest-style version shortcuts in documentation or committed commands. - Do not add a dependency only because it is common. Add it when it removes real boundary complexity or prevents error-prone code.
Directory Structure
- Follow Cargo's standard layout.
- Store production code in
src/. - Keep
src/main.rsthin for binary crates. It should parse CLI arguments and call library code. - Put reusable execution logic in
src/lib.rsand focused modules undersrc/. - Store integration tests in
tests/. - Keep unit tests near the module when they need private access.
- Use
examples/only for runnable examples that should compile. - Avoid
utils.rsandhelpers.rs; name modules by domain or action.
Typical binary crate shape:
src/
├── main.rs
├── lib.rs
├── cli.rs
├── config.rs
├── fs_ops.rs
└── render.rs
tests/
└── cli.rs
File Standards
- Let
rustfmtdefine formatting. Do not hand-format around rustfmt. - Keep files focused and cohesive.
- Prefer modules of roughly 200-500 lines. Split when multiple responsibilities appear.
- Keep public items before private helpers when it improves scanning.
- Use
mod.rsonly when the existing project already uses that style; otherwise prefermodule_name.rsplusmodule_name/child.rs. - Put one top-level concept per file when the concept has real behavior.
- Keep generated code out of hand-written modules unless the project has a clear generated-code convention.
Testing
- Use
cargo testas the default test command. - Write unit tests in the target source file or module with
#[cfg(test)] mod tests. - Use
tests/*.rsfor integration tests that exercise public APIs or CLI behavior. - Integration tests compile as a separate crate and should use only public APIs.
- Use doc tests for public examples that should stay compilable.
- Test edge cases and error paths for new behavior.
- Add regression tests for bug fixes.
- Use temporary directories for filesystem tests. Do not touch the real home directory.
- Snapshot generated artifacts only when exact output is part of the behavior.
- Keep snapshots small and focused.
- Do not test behavior that the type system already guarantees.
- Prefer fake implementations behind traits over broad mocking libraries.
- Use table-driven tests for the same behavior across multiple inputs.
- Use async tests only when the project already has an async runtime requirement.
Test Structure
- Unit test: place next to the implementation in the same file or module.
- Integration test: place under
tests/when testing public API, CLI behavior, or crate-level wiring. - Helper function: prefer local helper functions when setup is lightweight or argument-driven.
- Shared fixture: keep it in the test module first; move to
tests/common/only when multiple integration test files need it. - Fake implementation: prefer explicit fake types over global mocks.
- Parameterized cases: use a table of cases and loop over it when one behavior has many inputs.
Example unit test layout:
pub fn normalize_name(input: &str) -> String {
input.trim().replace('_', "-")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn normalize_name_trims_whitespace_and_replaces_underscores() {
assert_eq!(normalize_name(" rust_style "), "rust-style");
}
}
Syntax Rules
Module Item Order
- Put module-level constants before types when they configure the following code.
- Prefer
typealiases and enums before structs that use them. - Put public structs, enums, and traits before their implementations.
- Put public functions before private helper functions.
- Keep
#[cfg(test)] mod testsat the bottom of the file. - Keep related types and their
implblocks close together.
Structs
- Derive
Debugfor most domain structs. - Derive
Clone,PartialEq,Eq,Ord, orHashonly when the type actually needs that capability. - Keep fields private when the type has invariants.
- Use constructors for types with validation or normalization.
- Use tuple structs for narrow newtypes and named-field structs when field names improve clarity.
- Avoid boolean constructor arguments; prefer an enum or options struct.
Value Object pattern:
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SkillName(String);
impl SkillName {
pub fn parse(value: impl Into) -> anyhow::Result {
let value = value.into();
if value.is_empty() {
anyhow::bail!("skill name must not be empty");
}
Ok(Self(value))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
Enums
- Use enums for fixed states, providers, modes, and policy decisions.
- Convert user input strings into enums at the boundary.
- Avoid passing raw string modes through the codebase.
- Prefer exhaustive
matchstatements for domain control flow. - Implement
Displaywhen the enum has a stable user-facing representation. - Implement
FromStrorTryFromwhen parsing user input.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Provider {
Claude,
Codex,
}
Functions and Methods
- Keep functions focused and small.
- Prefer borrowing in parameters:
&str,&Path,&[T]. - Return owned values when the function creates or transforms ownership.
- Use associated functions for pure construction.
- Keep I/O in module-level functions or dedicated service structs, not inside Value Object constructors.
- Prefer
Resultfor expected failures. - Prefer early returns for guard clauses.
- Avoid
asyncunless the project has real concurrency or nonblocking I/O requirements.
Getter and Conversion Naming
- Borrowed views use
as_*:as_str,as_path,as_slice. - Consuming conversions use
into_*:into_inner,into_path_buf. - Cheap copies can use the value name directly:
len,status,provider. - Avoid Java-style
get_*unless following an existing local convention. - Boolean queries use
is_*,has_*,can_*, orshould_*. - Fallible parsing uses
parse,try_from, orfrom_str.
Ownership and Borrowing
- Accept
impl AsRefonly at outer convenience boundaries. Inside the codebase, pass&Path. - Accept
impl Intofor constructors that store owned strings. - Do not clone to satisfy the borrow checker until the ownership model is understood.
- Use
Cowonly when profiling or API shape shows it is worthwhile. - Prefer slices over
&Vecin function parameters.
Collections
- Use
Vecfor ordered sequences. - Use
BTreeMapor sorted vectors when deterministic output order matters. - Use
HashMapwhen order is irrelevant and lookup dominates. - Do not expose mutable collections directly from domain types.
Strings and Formatting
- Use
&strfor borrowed string input andStringfor owned string storage. - Use
format!when constructing a new owned string from values. - Use captured identifiers in formatting when it improves readability:
format!("{name}"). - Prefer
to_owned()orString::fromwhen converting a string literal toString. - Avoid unnecessary
to_string()in hot or repeated code. - Keep user-facing text separate from machine-readable output.
Numeric Conversions
- Use
usizefor indexing and collection lengths. - Do not use
asfor narrowing integer conversions. - Use
TryFromortry_intowhen a conversion can fail. - Use newtypes when a number has a domain unit or invariant.
- Make lossy conversions explicit in the function or variable name.
Error Handling
- Use
anyhow::Resultat application and CLI orchestration boundaries where errors are reported to humans and callers do not branch on error categories. - Use concrete error types when callers or tests need to distinguish failure categories.
thiserroris appropriate for deriving concrete error types. Do not introduce it just to wrap every possible failure.- Add context at I/O, external command, parse, and config boundaries.
- A low-level error such as "No such file or directory" must include the operation and path.
- Use
?for propagation. - Do not use
unwraporexpectin production code except for impossible states justified by a nearby invariant.
Panic Policy
- Use
Resultfor expected failures. - Reserve
panic!for logic bugs and impossible states. - Avoid direct indexing when missing data is a normal possibility; use
get()or explicit validation. - Public APIs that can panic must document
# Panics. - Do not leave
todo!,unimplemented!, or debuggingpanic!calls in production code. - Use
unreachable!only when the type system or previous validation makes the branch impossible.
Application-boundary error:
use anyhow::{Context, Result};
pub fn read_config(path: &Path) -> Result {
std::fs::read_to_string(path)
.with_context(|| format!("failed to read config file {}", path.display()))
}
Distinguishable domain error:
#[derive(Debug, thiserror::Error)]
pub enum ProviderError {
#[error("unknown provider: {0}")]
UnknownProvider(String),
}
CLI Code
- Use typed structs and enums for CLI input.
- Keep argument parsing separate from execution.
- Do not perform filesystem or network work while parsing CLI arguments.
- Human-readable command results go to stdout.
- Progress, warnings, and diagnostics go to stderr.
- Machine-readable output must not be mixed with human logs.
- Keep exit codes coarse but meaningful enough for CI.
#[derive(Debug, clap::Parser)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, clap::Subcommand)]
pub enum Command {
Render(RenderArgs),
Verify(VerifyArgs),
}
Logging and Diagnostics
- Short-lived CLI tools may use
eprintln!for warnings and progress. - Libraries must not initialize global logging.
- Use
tracingonly when the project needs structured logs, spans, or long-running diagnostics. - Keep human diagnostics on stderr and machine-readable command output on stdout.
- Include paths, command names, and config keys in diagnostics when they explain the failure.
Filesystem Operations
- Use
PathandPathBuffor paths. Do not build paths with string concatenation. - Treat source tree traversal and symlink creation as separate behaviors.
- Do not follow symlinks while traversing source trees unless the behavior is explicitly required and tested.
- Use
DirEntry::file_type()instead ofPath::is_dir()when symlink behavior matters. - Keep file-writing operations behind small functions that are easy to test with temporary directories.
- Distinguish files fully owned by the project from files also modified by users or external tools.
- Prefer writing to a temporary file and replacing the target when partial writes would be harmful.
Symlink-aware traversal without following symlinks:
use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
pub fn collect_regular_files(dir: &Path) -> Result> {
let mut files = Vec::new();
collect_regular_files_into(dir, &mut files)?;
files.sort();
Ok(files)
}
fn collect_regular_files_into(dir: &Path, files: &mut Vec) -> Result {
for entry in std::fs::read_dir(dir)
.with_context(|| format!("failed to read directory {}", dir.display()))?
{
let entry = entry?;
let file_type = entry.file_type()?;
let path = entry.path();
if file_type.is_symlink() {
continue;
}
if file_type.is_dir() {
collect_regular_files_into(&path, files)?;
} else if file_type.is_file() {
files.push(path);
}
}
Ok(())
}
Config and Serialization
- Use typed structs for project-owned config.
- Use
serde(deny_unknown_fields)where unknown input keys should be rejected. - Do not reject unknown keys in external-tool-owned config files.
- Use
#[serde(rename_all = "...")]to keep serialized field naming consistent. - Use
#[serde(default)]for optional input fields that have stable defaults. - Use
#[serde(skip_serializing_if = "Option::is_none")]when absent values should not appear in output. - Use
#[serde(flatten)]sparingly because it makes schemas less explicit. - Separate fully generated files from user-owned files that receive managed updates.
- Do not overwrite user-owned or external-tool-owned config files wholesale.
- For managed config sync, update only the keys owned by the project and preserve unknown keys.
- Use stable ordering for generated output when review diffs or tests depend on order.
- Use a format-preserving edit strategy when comments, ordering, and unknown future keys must be preserved.
Cargo Features
- Keep feature flags additive. Enabling a feature should not remove behavior.
- Avoid growing default features unless the behavior is truly the default user expectation.
- Keep optional dependency features named clearly after the capability they enable.
- Test important feature combinations explicitly.
- Consider
cargo test --all-featureswhen features affect compiled code paths. - Do not use features as a runtime configuration substitute.
Public API Design
- Reserve
pubfor APIs intended to be consumed outside the crate. - Use
pub(crate)for items shared across internal modules, including inside private modules when it clarifies that the item is not an external API. - Keep items private when they are only used inside the current module.
- Keep public APIs smaller than internal APIs.
- Avoid exposing third-party types in public APIs unless that dependency is part of the intended API.
- Use
#[non_exhaustive]for public enums or structs that may need new variants or fields. - Seal traits that downstream crates should not implement.
- Treat new public trait implementations as semver-relevant changes.
- Do not expose mutable internal collections directly.
Macro and Build Script Policy
- Prefer functions, traits, and generics before macros.
- Add a macro only when it removes repetition that ordinary Rust abstractions cannot express clearly.
- Avoid procedural macros unless the project has a strong reason for compile-time code generation.
- Keep test helper macros small and obvious.
- Do not add
build.rsuntil native linking, environment probing, or code generation is actually required. - Generated code should have tests or snapshots that make the generated output reviewable.
Naming Conventions
- Modules and files:
snake_case. - Functions, methods, and variables:
snake_case. - Types, traits, and enum variants:
UpperCamelCase. - Constants and statics:
SCREAMING_SNAKE_CASE. - Generic type parameters: shor
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: furedea
- Source: furedea/agent-harness
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.