swarm repositories / source
aboutsummaryrefslogtreecommitdiff
path: root/docs/bootstrap-retrofit.md
blob: c07be5634da56669b90c692d9126ad5b1eb4beba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# 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.