agentfiles 0.0.2

Unified agent file installer for Claude Code, OpenCode, Codex, and Cursor
Documentation
# AGENTS.md

Guidance for AI coding agents operating in this repository.

## Project Summary

**agentfiles** is a Rust CLI tool that installs agent files (skills, agents, commands) across multiple agentic coding providers (Claude Code, OpenCode, Codex, Cursor) from a unified `agentfiles.json` manifest. Rust 2024 edition, purely synchronous, no unsafe code.

## Build / Lint / Test Commands

```bash
cargo build                    # Build the project
cargo run                      # Build and run the binary
cargo test                     # Run ALL tests
cargo test <name>              # Run a single test by substring match
cargo test <name> -- --exact   # Run a single test by exact name
cargo clippy -- -D warnings    # Lint (CI treats warnings as errors)
cargo fmt                      # Format code
cargo fmt -- --check           # Check formatting without modifying
```

CI runs on every PR: `fmt --check`, `clippy -D warnings`, `test`, `build` across Linux/macOS/Windows. All four must pass.

## Module Structure

```
src/
  lib.rs         -- pub mod re-exports only
  types.rs       -- Core enums (FileScope, FileKind, FileStrategy, AgentProvider)
  provider.rs    -- Provider directory layout resolution, compatibility matrix
  manifest.rs    -- Manifest/FileMapping structs, JSON load/save (serde)
  scanner.rs     -- Auto-discovery of agent files from directory structures
  installer.rs   -- File installation (copy/symlink) to provider directories
  git.rs         -- Remote git: URL detection, @ref parsing, clone/cache
  cli.rs         -- CLI argument parsing (clap derive)
  main.rs        -- Binary entry point, command handlers (cmd_install, cmd_init, etc.)
```

Dependency flow: `types` <- `provider`, `manifest` <- `scanner`, `installer`. `git` and `cli` are standalone. `main` wires everything together via the lib crate.

## Code Style Guidelines

### Formatting

Default `rustfmt` settings (no `.rustfmt.toml`). No configuration overrides. Just run `cargo fmt`.

### Import Ordering

Three groups separated by blank lines:

```rust
// 1. Standard library
use std::fs;
use std::path::{Path, PathBuf};

// 2. External crates
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};

// 3. Crate-internal modules
use crate::manifest::FileMapping;
use crate::types::{AgentProvider, FileKind};
```

Exception: `types.rs` has no crate-internal imports so groups 1 and 2 may appear together.

### Naming Conventions

- **Functions**: `snake_case`. Public: `scan_agent_files`, `load_manifest`. Private helpers: `scan_kind_dir`, `normalize_url`. Command handlers in main: `cmd_install`, `cmd_init`.
- **Types/Structs/Enums**: `PascalCase`. Examples: `FileMapping`, `InstallResult`, `AgentProvider`.
- **Enum variants**: `PascalCase`. Examples: `FileScope::Project`, `FileKind::Skill`.
- **Constants**: `UPPER_SNAKE_CASE`. Example: `const KIND_DIRS: &[(&str, FileKind)]`.
- **Modules**: `snake_case`, flat structure (all in `src/`), no nested module directories.

### Error Handling

Uses `anyhow` exclusively. No custom error types. All `Result` types are `anyhow::Result<T>`. Three patterns:

```rust
// 1. Static context
let content = std::fs::read_to_string(path).context("failed to read manifest")?;

// 2. Dynamic context with formatting
source.canonicalize()
    .with_context(|| format!("failed to resolve: {}", source.display()))?;

// 3. Early-return errors
anyhow::bail!("source file not found: {}", path.display());
```

Error message style: lowercase, no trailing punctuation, descriptive. All `FromStr` impls use `type Err = anyhow::Error`. Never use `unwrap()` in production code -- only in tests.

### Type Conventions

- Derive set for domain types: `#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]`
- Serde: `#[serde(rename_all = "lowercase")]` on enums, `skip_serializing_if` for optional/default fields
- Builder-like pattern on `Manifest`: `Manifest::default().with_name(n).with_files(f)`
- No trait abstractions -- concrete types throughout. Standard traits (`Display`, `FromStr`, `Default`) implemented as needed.

### Platform-Specific Code

Gate with `#[cfg(unix)]` / `#[cfg(windows)]`. Used in `installer.rs` for symlink creation and in tests.

### Output

Uses `println!()` directly for user-facing output. No logging framework, no structured logging, no tracing.

### Rust 2024 Edition Features

Let-chains are used (e.g., in `scanner.rs`):
```rust
if entry_path.is_file()
    && let Some(ext) = entry_path.extension()
    && ext == "md"
{
```

## Test Conventions

### Location

Inline `#[cfg(test)] mod tests` blocks within each module. No separate `tests/` directory, no test fixture files.

### Organization

Two patterns:

```rust
// Flat (types.rs, provider.rs, scanner.rs):
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn descriptive_name() -> Result<()> { ... }
}

// Nested sub-modules for grouping (manifest.rs, git.rs, installer.rs):
#[cfg(test)]
mod tests {
    mod load_manifest {
        use super::super::*;
        #[test]
        fn from_directory() -> Result<()> { ... }
    }
}
```

### Test Patterns

- **Return type**: Most tests return `Result<()>` and use `?` for propagation. Tests checking error cases use `assert!(result.is_err())`.
- **Filesystem**: Use `tempfile::TempDir` for all filesystem tests. Create test data inline -- no fixture files.
- **Platform gates**: Symlink tests use `#[cfg(unix)] #[test]`.
- **Naming**: Descriptive `snake_case`. No mandatory `test_` prefix. Examples: `save_and_roundtrip`, `install_command_skips_codex`, `shorthand_gets_https`.
- **Helpers**: Define local helper functions within test modules (e.g., `setup_skill`, `make_manifest`).

### Running a Single Test

```bash
cargo test save_and_roundtrip              # Match by substring
cargo test tests::load_manifest::from_dir  # Match nested module path
cargo test -p agentfiles save_and_roundtrip -- --exact  # Exact match
```

## Design Principles

1. **Flat module structure** -- all source in `src/*.rs`, no nested directories.
2. **Minimal dependencies** -- 5 runtime deps, 1 dev dep. Shell out to `git` rather than adding a git library.
3. **Single source of truth** -- `ProviderLayout` in `provider.rs` defines all provider config. Adding a provider means adding one layout entry.
4. **Compatibility matrix drives behavior** -- one manifest file, multiple providers handled automatically.
5. **No async** -- entirely synchronous. No tokio/async-std.
6. **Section separators** in larger files use comment banners:
   ```rust
   // ---------------------------------------------------------------------------
   // Internal helpers
   // ---------------------------------------------------------------------------
   ```