rsconstruct 0.9.64

Rust based fast build system
# RSConstruct - Rust Build Tool

A fast, incremental build tool written in Rust with tera support, Python linting, and parallel execution.

Detailed documentation is in `docs/src/`. Key references:
- Commands: `docs/src/commands.md`
- Configuration: `docs/src/configuration.md`
- Architecture (subprocess execution, path handling, caching): `docs/src/internal/architecture.md`
- Processor contract: `docs/src/internal/processor-contract.md`
- Coding standards: `docs/src/internal/coding-standards.md`
- Per-processor docs: `docs/src/processors/`

## Philosophy

- **Simplicity first** — keep the code simple whenever possible. Avoid clever solutions that are hard to understand or maintain. When in doubt, choose the straightforward approach.
- **Convention over configuration** — simple naming conventions, explicit config loading, incremental builds by default.
- **No macros** — the codebase has zero `macro_rules!` and must stay that way. Use regular functions, generics, traits, and structs to eliminate duplication. Do not add new macros. (The former `ctx!` exception is gone: `errors::ctx()` is `#[track_caller]`, so it reports the caller's file:line without a macro. Note `tests/` still uses `test_checker!`; test-only macros are out of scope for this rule.)
- **Unix-only; OS calls live in `src/platform.rs`** — RSConstruct targets Linux and macOS only (see `docs/src/internal/rejected-problems.md`). All OS-specific code (file permissions, signal handling) lives in `src/platform.rs`, which the rest of the codebase calls through named wrappers. Do not add `#[cfg(...)]` blocks anywhere, including `platform.rs`: there is no second platform to switch on, and a `#[cfg(not(unix))]` branch is dead code that cannot be compiled or tested. Unix assumptions elsewhere (`flock`, `/dev/null`, `$HOME`, apt) are deliberate, not bugs.
- **No infrastructure workarounds** — do not add apt retries, download timeouts, or similar knobs to `tools install-deps`/`tools install` to paper over GitHub-runner or mirror flakiness. It is not this tool's job to fix GitHub's infrastructure. The 2026-08-19 incidents showed such knobs mostly misfire: a 5-minute step timeout killed a healthy release build, and against the actual failure mode (a mirror serving at ~60 KB/s — slow but progressing) a timeout kills working runs and a retry does nothing. The remedy for a degraded runner is cancel + re-run on fresh hardware, in the consuming repo's CI. **Narrow exception, added 2026-08-23: connection-level retry on downloads**, owned by `src/download.rs` and documented in `docs/src/internal/download-policy.md`. A connection *reset during handshake* (curl exit 35) is a different failure from a slow mirror — the transfer never started, so retrying costs ~nothing and usually succeeds, as a re-run of the failing job demonstrated. That module bounds `--connect-timeout` only, never total transfer time (`--max-time` is asserted absent by a unit test), so the 2026-08-19 failure mode stays impossible. This exception does not extend to apt retries, step timeouts, or any knob that can kill a slow-but-progressing run.
- **Strict by default** — never silently skip errors or ignore failures. Non-strict systems hide problems and are a disaster. If a tool is missing, fail. If a test fails, fix it before moving on.
- **All tests must pass** — always run `cargo test` with no filters or skips. Do not move forward with any failing test. If a test fails, fix it immediately — the failure is real.
- **No scripts to modify code** — never use Python scripts, sed, awk, or any external tool to modify Rust source code. All code changes must be made manually through the editor. Automated bulk changes produce inconsistent results and hide mistakes.
- **Always add context to errors** — every `?` on an IO operation (`fs::read`, `fs::write`, `Command::spawn`, `fs::create_dir_all`, etc.) must have `.with_context(|| format!("..."))` that says what you were trying to do and which file/command was involved. A bare `?` on an IO operation is a bug — it produces useless error messages like "No such file or directory" with no indication of what went wrong. Use `anyhow::Context` everywhere.
- **Never create dummy instances** — never instantiate a processor (or any object) just to inspect its metadata. Metadata (config fields, defaults, descriptions) must be available without creating an instance. If you need config info, get it from the plugin interface, not from a throwaway instance.
- **CLI subcommands are always alphabetical** — every `#[derive(Subcommand)] enum` in `src/cli.rs` (top-level `Commands` and every `*Action` enum) must list its variants in alphabetical order by display name (clap's kebab-case conversion of the variant — e.g. `EnableDetected` → `enable-detected`). Clap renders subcommands in declaration order, so this list IS the help output. When adding a new variant, insert it at its alphabetical position. No exceptions.
- **All tunable behavioral knobs are config fields** — any value that affects runtime behavior and could reasonably vary per project must be a `pub` field on a config struct (the per-processor `*Config` in the processor's own file, or `BuildConfig` in `src/config/mod.rs` for cross-cutting build-wide settings), with a `#[serde(default = "...")]` defaulting to the historical hardcoded value. This includes timeouts, retry counts, max-attempts, batch sizes, poll intervals, size/length limits, dictionary paths, output caps. It does NOT include regex patterns, internal algorithm constants, enum discriminators, or constants dictated by an external file format. The field must also get a `FieldSpec` entry in the plugin's `fields` list (name, type, `affects_output` if it changes the produced output bytes, doc) — that one entry drives validation, checksum membership, and `defconfig` display. A hardcoded `const FOO_TIMEOUT: Duration = ...` (or similar magic literal in code) inside `src/processors/` is a bug. Use `rsconstruct processors defconfig <name>` to verify the field is exposed.
- **A processor is one file** — everything that defines a processor lives in its file under `src/processors/<category>/`: the `Processor` impl (or `SimpleChecker`/`SimpleGenerator` params), its config struct (for custom fields), and one `inventory::submit!` plugin entry carrying the whole schema (`fields` as `FieldSpec`s, `scan_defaults`, `defaults`, `omit_standard_fields`) plus any `ToolInfo` submission for a tool the central registry lacks. There are no central per-processor tables to update — do not add any. The `mod` declaration in the category's `mod.rs`, a docs page in `docs/src/processors/`, and a test file in `tests/processors/` are the only outside touch-points, and completeness tests enforce them.