# Retrofit Use this surface when the target repo already has Rust linting, but you want to tighten the ratchet without clobbering valid local structure. ## Goal Migrate the repo toward the `rust-starter` posture while staying diff-aware: - preserve stricter existing rules - preserve justified local carve-outs - delete duplicated policy only after it has been re-homed into the manifest - never paste the fresh template wholesale over a living repo - add a hard source-file line cap without erasing stricter local limits or justified generated-code exemptions ## First Pass: Inventory The Existing Surfaces Before editing, inspect all of the places the repo may already encode lint policy: - root and member `Cargo.toml` - `check.py`, `xtask`, shell wrappers, `justfile`, or other runners - CI workflow files - any existing auto-fix or canonicalization commands - `clippy.toml` - `rust-toolchain.toml` - editor settings - existing oversized source files, checked-in generated code, and any current file-length checks You are trying to answer two questions: 1. What policy already exists? 2. Where is that policy duplicated? ## Comparison Baseline Use these as the baseline, not as paste targets: - [template/fresh](/home/main/programming/projects/rust_starter/template/fresh) - [docs/rust-linting-proposal.md](/home/main/programming/projects/rust_starter/docs/rust-linting-proposal.md) The fresh template tells you the shape to converge toward. The proposal doc tells you why. ## Rules Of Engagement ### Preserve stricter local policy If the repo already does something stricter than the template, keep it unless there is a clear repo-specific reason to relax it. Examples: - stricter rustdoc policy - additional Clippy bans - a more demanding deep gate - an intentionally tighter unsafe policy - a lower source-file line cap ### Preserve justified local exceptions If the repo has exceptions with clear local justification, do not erase them just because the template lacks them. Instead: - move them into root `Cargo.toml` if they are repo-wide - keep them local if they are truly local - add or preserve comments / `reason = "..."` - use `workspace.metadata.rust-starter.source_files.exclude` for checked-in generated Rust or other deliberate file-cap carve-outs ### Remove duplicate policy, not local intent When a repo encodes the same allowlist in both `Cargo.toml` and a runner script, the script copy should die. But only remove the script copy after the equivalent manifest-owned policy exists and the runner still executes the same effective gate. ### Treat `clippy.toml` as configuration-only If the repo uses `clippy.toml` to hold the main allow/deny architecture, migrate that policy into `[workspace.lints.clippy]`. Keep `clippy.toml` only for structured knobs such as test allowances. ## Ratchet Order Apply tightenings in this order: ### 1. Pin or refresh the toolchain Add `rust-toolchain.toml` if missing. If present but floating, pin it. ### 2. Install workspace lint tables Move repo-wide policy into root `[workspace.lints.*]`. ### 3. Make member crates inherit explicitly Add `[lints] workspace = true` to every member crate. ### 4. Re-home script flags into the manifest Take inline `cargo clippy -- -A/-D ...` tails from scripts and CI, and migrate them into the root manifest in grouped, commented form. ### 5. Tighten local suppression discipline Prefer: - `#[expect(..., reason = "...")]` for temporary or evidence-backed suppressions - `#[allow(..., reason = "...")]` only for stable local policy ### 6. Simplify the runner Once the manifest is authoritative, collapse the runner to orchestration-only plus generic manifest-backed checks such as the source-file cap. If the repo already has an auto-fix pass, re-home it into root `workspace.metadata.rust-starter.canonicalize_commands` and make the default local `check` path invoke it before the verification gate instead of relying on engineers or agents to remember a separate pre-pass. ### 7. Install the source-file cap Add `[workspace.metadata.rust-starter.source_files]` to the root manifest and set `max_lines` deliberately. Default to `2500` if the repo has no existing stance. If the repo already enforces a stricter cap, keep the stricter value. If the repo has checked-in generated Rust that would make the rule meaningless, exclude those paths explicitly instead of disabling the whole mechanism. ### 8. Add deep-gate posture if the repo is ready Add `cargo hack`, docs, and dependency-hygiene checks only when the repo can support them without turning the whole effort into churn theater. ## Special Cases ### Existing `expect_used = "allow"` Do not keep this globally just because the repo had it historically. Check whether the real need is test-only ergonomics. If so: - set `expect_used = "deny"` in the root manifest - move the relaxation into `clippy.toml` via `allow-expect-in-tests = true` ### Existing repo-specific carve-outs If the repo carries exceptions for domain-heavy code such as geometry, parsing, or numerics, expect some of them to stay. The goal is not zero exceptions. The goal is explicit, centralized, justified exceptions. ### Checked-in giant files If the repo already contains Rust files over the default cap, do not blindly raise the limit to fit them. Decide whether each file should be split, exempted, or accepted behind a tighter, evidence-backed local exception pattern. ### Existing CI Do not rewrite CI into a second policy source. Make it call the canonical commands or the thin runner. If the repo wants CI to detect canonicalization drift rather than rewriting files in place, keep a non-mutating verification entrypoint such as `check.py verify` and let local `check` remain the mutating convenience path. ## Acceptance Checklist - repo-wide policy lives in root `[workspace.lints.*]` - every member crate inherits that policy explicitly - scripts and CI no longer restate Clippy allowlists - manifest-owned canonicalization exists where the repo wants auto-fixes, instead of living in shell aliases or tribal memory - valid repo-specific exceptions remain intact and justified - stricter pre-existing rules remain stricter - the root manifest carries an intentional source-file cap and any justified exclusions - fast gate commands still match the repo’s effective behavior Use [docs/rust-linting-proposal.md](/home/main/programming/projects/rust_starter/docs/rust-linting-proposal.md) when you need to justify a ratchet choice, not as a blind replacement spec.