Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# AI Instructions

Read and follow the CONTRIBUTING.md file in this repository for all code style conventions, commit message format, and development guidelines.

## Quick Reference

- Commit format: Conventional Commits — `type(scope): lowercase description`
- Version releases are the only exception: just the version number (e.g. `0.21.1`)
- Import order: internal (`crate::`/`super::`) → external crates → `std::`, prefer merged imports
- Use descriptive generic names (`Size`, `Report`), not single letters
- Prefer `where` clauses for multiple trait bounds
- Derive order: std traits → comparison traits → `Hash` → derive_more → feature-gated
- Custom errors: `#[derive(Debug, Display, Error)]` + `#[non_exhaustive]`
- Minimize `unwrap()` in non-test code — use proper error handling
- `#![deny(warnings)]` is active — code must be warning-free
- Install toolchain before running tests: `rustup toolchain install "$(< rust-toolchain)" && rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy`
- If the AI agent is Claude Code, `gh` (GitHub CLI) is not installed — do not attempt to use it
- Run `FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh` to validate changes
18 changes: 18 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# AI Instructions

Read and follow the CONTRIBUTING.md file in this repository for all code style conventions, commit message format, and development guidelines.

## Quick Reference

- Commit format: Conventional Commits — `type(scope): lowercase description`
- Version releases are the only exception: just the version number (e.g. `0.21.1`)
- Import order: internal (`crate::`/`super::`) → external crates → `std::`, prefer merged imports
- Use descriptive generic names (`Size`, `Report`), not single letters
- Prefer `where` clauses for multiple trait bounds
- Derive order: std traits → comparison traits → `Hash` → derive_more → feature-gated
- Custom errors: `#[derive(Debug, Display, Error)]` + `#[non_exhaustive]`
- Minimize `unwrap()` in non-test code — use proper error handling
- `#![deny(warnings)]` is active — code must be warning-free
- Install toolchain before running tests: `rustup toolchain install "$(< rust-toolchain)" && rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy`
- If the AI agent is Claude Code, `gh` (GitHub CLI) is not installed — do not attempt to use it
- Run `FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh` to validate changes
18 changes: 18 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# AI Instructions

Read and follow the CONTRIBUTING.md file in this repository for all code style conventions, commit message format, and development guidelines.

## Quick Reference

- Commit format: Conventional Commits — `type(scope): lowercase description`
- Version releases are the only exception: just the version number (e.g. `0.21.1`)
- Import order: internal (`crate::`/`super::`) → external crates → `std::`, prefer merged imports
- Use descriptive generic names (`Size`, `Report`), not single letters
- Prefer `where` clauses for multiple trait bounds
- Derive order: std traits → comparison traits → `Hash` → derive_more → feature-gated
- Custom errors: `#[derive(Debug, Display, Error)]` + `#[non_exhaustive]`
- Minimize `unwrap()` in non-test code — use proper error handling
- `#![deny(warnings)]` is active — code must be warning-free
- Install toolchain before running tests: `rustup toolchain install "$(< rust-toolchain)" && rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy`
- If the AI agent is Claude Code, `gh` (GitHub CLI) is not installed — do not attempt to use it
- Run `FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh` to validate changes
200 changes: 200 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# Contributing to parallel-disk-usage

## Commit Message Convention

This project uses [Conventional Commits](https://www.conventionalcommits.org/).

### Format

```
type(scope): lowercase description
```

### Rules

- **Types:** `feat`, `fix`, `refactor`, `perf`, `docs`, `style`, `chore`, `ci`, `test`, `lint`
- **Scopes** (optional): `cli`, `api`, `deps`, `readme`, `benchmark`, `toolchain`, `test`, or other relevant area
- **Description:** always lowercase after the colon, no trailing period, brief (3-7 words preferred)
- **Breaking changes:** append `!` before the colon (e.g. `feat(cli)!: remove deprecated flag`)
- **Code identifiers** in descriptions should be wrapped in backticks (e.g. `` chore(deps): update `rand` ``)

### Exception: Version Releases

Version release commits use **only** the version number as the message — no type prefix:

```
0.21.1
```

## Code Style

Automated tools enforce formatting (`cargo fmt`) and linting (`cargo clippy`). The following conventions are **not** enforced by those tools and must be followed manually.

### Import Organization

Prefer **merged imports** — combine multiple items from the same crate or module into a single `use` statement with braces rather than separate `use` lines.

Imports are grouped in this order, separated by blank lines:

1. `use super::...` or `use crate::...` (internal)
2. External crate imports (alphabetical)
3. `use std::...` (standard library)

Within each group, items are ordered alphabetically. Platform-specific imports (`#[cfg(unix)]`) go in a separate block after the main imports.

```rust
use crate::{
args::{Args, Quantity, Threads},
bytes_format::BytesFormat,
size,
};
use clap::Parser;
use pipe_trait::Pipe;
use std::{io::stdin, time::Duration};

#[cfg(unix)]
use crate::get_size::{GetBlockCount, GetBlockSize};
```

### Module Organization

- Use the flat file pattern (`module.rs`) rather than `module/mod.rs` for submodules.
- List `pub mod` declarations first, then `pub use` re-exports, then private imports and items.
- Use `pub use` to re-export key types at the module level for convenience.

```rust
pub mod error_only_reporter;
pub mod error_report;
pub mod event;

pub use error_only_reporter::ErrorOnlyReporter;
pub use error_report::ErrorReport;
pub use event::Event;
```

- Type aliases using `pub use ... as ...` are used to provide semantic alternative names:

```rust
pub use Reflection as DataTreeReflection;
```

### Derive Macro Ordering

When deriving multiple traits, use this order and split across multiple `#[derive(...)]` lines for readability:

1. **Standard traits:** `Debug`, `Default`, `Clone`, `Copy`
2. **Comparison traits:** `PartialEq`, `Eq`, `PartialOrd`, `Ord`
3. **Hash**
4. **`derive_more` traits:** `Display`, `From`, `Into`, `Add`, `AddAssign`, etc.
5. **Feature-gated derives** on a separate `#[cfg_attr(...)]` line

```rust
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[derive(From, Into, Add, AddAssign, Sub, SubAssign, Sum)]
#[cfg_attr(feature = "json", derive(Deserialize, Serialize))]
pub struct Bytes(u64);
```

### Generic Parameter Naming

Use **descriptive names** for type parameters, not single letters:

- `Size`, `Name`, `SizeGetter`, `HardlinksRecorder`, `Report`

Single-letter generics are acceptable only in very short, self-contained trait impls.

### Trait Bounds

Prefer `where` clauses over inline bounds when there are multiple constraints:

```rust
impl<Size, SizeGetter, HardlinksRecorder, Report>
From<FsTreeBuilder<'a, Size, SizeGetter, HardlinksRecorder, Report>>
for DataTree<OsStringDisplay, Size>
where
Report: Reporter<Size> + Sync + ?Sized,
Size: size::Size + Send + Sync,
SizeGetter: GetSize<Size = Size> + Sync,
HardlinksRecorder: RecordHardlinks<Size, Report> + Sync + ?Sized,
```

### Visibility

- Use `pub` for the public API surface.
- Use `pub(crate)` for items shared within the crate but not exposed externally.
- Default to private for everything else.

### Error Handling

- Define custom error enums with `#[derive(Debug, Display, Error)]` from `derive_more`.
- Mark error enums as `#[non_exhaustive]`.
- Minimize `unwrap()` in non-test code — use proper error propagation. `unwrap()` is acceptable in tests and for provably infallible operations (with a comment explaining why). When deliberately ignoring an error, use `.ok()` with a comment explaining why.

```rust
#[derive(Debug, Display, Error)]
#[non_exhaustive]
pub enum RuntimeError {
#[display("SerializationFailure: {_0}")]
SerializationFailure(serde_json::Error),
}
```

### Documentation Comments

- Use `///` doc comments for all public types, traits, functions, and fields.
- Use `//!` module-level doc comments at the top of `lib.rs` and significant modules.
- Include usage examples with `/// ```no_run` blocks for key public APIs.
- Reference related types with `[`backtick links`](crate::path)` syntax.

### Feature Gating

- Use `#[cfg(feature = "...")]` for optional functionality (e.g., `json`, `cli`).
- Use `#[cfg(unix)]` for POSIX-specific code.
- Use `#[cfg_attr(feature = "json", derive(Deserialize, Serialize))]` for conditional derives.

### Pattern Matching

Use exhaustive matching. When mapping enum variants to values, prefer the concise wrapping style:

```rust
ExitCode::from(match self {
RuntimeError::SerializationFailure(_) => 2,
RuntimeError::DeserializationFailure(_) => 3,
})
```

### Struct Field Ordering

Order fields logically by purpose, not alphabetically. Group related fields together. Document every public field with `///` comments.

### Macros

Use macros to reduce boilerplate for repetitive patterns (e.g. newtype wrappers, trait impls for multiple numeric types). Keep macros well-scoped and documented.

### Warnings Policy

The crate uses `#![deny(warnings)]` — all warnings are treated as errors. Code must compile warning-free.

## Setup

Install the required Rust toolchain and components before running any checks:

```sh
rustup toolchain install "$(< rust-toolchain)"
rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy
```

## Automated Checks

Before submitting, ensure:

- `cargo fmt -- --check` passes
- `cargo clippy` passes (on all feature combinations)
- `cargo test` passes
- The project builds with no default features, default features, and all features

The CI script `test.sh` runs all of these across 5 feature combinations. You can run it locally with:

```sh
FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh
```
25 changes: 25 additions & 0 deletions tests/sync_ai_instructions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//! The following tests check whether the AI instruction files are in sync.
//!
//! All three files (CLAUDE.md, AGENTS.md, .github/copilot-instructions.md) should be identical.

macro_rules! check {
($name:ident: $a_path:literal == $b_path:literal) => {
#[test]
fn $name() {
let a = include_str!($a_path);
let b = include_str!($b_path);
Comment thread
KSXGitHub marked this conversation as resolved.
assert!(
a == b,
concat!(
"AI instruction files are out of sync: ",
$a_path,
" != ",
$b_path,
),
Comment thread
KSXGitHub marked this conversation as resolved.
);
Comment thread
KSXGitHub marked this conversation as resolved.
}
};
}

check!(claude_md_vs_agents_md: "../CLAUDE.md" == "../AGENTS.md");
check!(claude_md_vs_copilot_instructions: "../CLAUDE.md" == "../.github/copilot-instructions.md");
Loading