Write architecture decisions as lint
In the same AGENTS.md, a rule that has a command will light red on three operating systems. The one that is only a path — after a rename, nobody notices.
- Is the call-site arg an anonymous literallib.rs L261
- Does the comment name equal the parameter namelib.rs L222
- Is the callee a workspace cratelib.rs L177
- Does CI run on three platforms at oncerust-ci.yml L174
- Does the Markdown path still existAGENTS.md L35
- Is the Feature registered in the exhaustive tablelib.rs L379
- In-development features must default offtests.rs L18
A new hire gets the task: change an MCP tool call. They open AGENTS.md and copy the path on line 35. The file does not exist. The real file is connection_manager.rs, in the same directory. The name with an mcp_ prefix in the docs is leftover from a rename that was not cleaned up.
Source: AGENTS.md lines 32–36; codex-rs/codex-mcp/src/connection_manager.rs lines 1–15
In the same file, a positional argument missing /*base_url*/ goes red on the local command. Change Cargo.toml and forget to refresh the Bazel lock, and CI goes red. The path on line 35 has no checker. Markdown will not verify the file is still there.
Change the API first so the call site can be read. A reader of foo(false) has to jump to the definition to know what that false governs. Only if you cannot change the API is /*param_name*/ allowed. Lint is the fallback.
Source: AGENTS.md lines 14–20
The implementation lives in a standalone Dylint crate and runs as rustc. Only after type resolution can you get the callee’s parameter names. The entry only looks at function and method calls; anything from macro expansion is skipped.
Checks walk in this order.
1. Only this-repo crates; std and tokio are skipped.
2. Comments are sought in three places: the gap before the argument, the previous 64 bytes, and the argument text itself.
3. A wrong name reports mismatch. A wrong comment will not fall through to the “no comment” rule.
4. When none is written, a method name equal to the only parameter name is exempt, e.g. .enabled(false).
5. The rest only blocks anonymous literals. None, bools, and numbers need a comment; strings and chars are skipped.
Source: tools/argument-comment-lint/src/lib.rs lines 165–180; tools/argument-comment-lint/src/lib.rs lines 261–274
The repo entry raises the default-Allow rule to deny. CI runs once each on Linux, macOS, and Windows; if one fails the other two still finish. A person went green on macOS; if the Windows target’s macro expansion adds one more None, the third machine still blocks.
Source: .github/workflows/rust-ci.yml lines 164–187
The call site is local, the name is resolvable, and false positives can be held by an exemption. Change the language and the shape is the same: rename first; if you cannot, require an in-line name. TypeScript uses ESLint, Python uses ruff — both fit.
Line 35 and line 265 are the same rot. The app-server guide still writes v2.rs; it is now a v2/ directory, split into thirty-plus files. Near 800 lines, a file should split. After the split, nobody changed the single-file path in the guide.
Source: AGENTS.md lines 260–266
The module-size rule names five hot files; four already crossed 800, one sits near 900. chat_composer.rs is 12859 lines. The repo has no command that counts lines. Lines can be counted; CI does not count them. Whether a change is mechanical is a poor machine job, so the 800-line cap stops at review.
Source: AGENTS.md lines 49–61; AGENTS.md lines 125–131
Read the rules as two sets. One has a command or a compiler and goes red before merge. One can only be read by people and review — miss it and it passes. Path existence is the easiest check: extract backtick paths and test them against the repo root. The repo did not. Budget went to call-site readability, not path existence.
Docs will not re-check themselves. A locally checkable rule written only in Markdown — on the day of a rename or a split, the words stay and the object has moved. The smallest form is a twenty-line script that checks paths. You do not need a rustc plugin.
If a feature flag is only a bool and a paragraph, a missed registration, an in-development default-on, or a Deprecated that just sits there will not go red at once.
Beside the Feature enum sits a FEATURES table. FeatureSpec welds id, config key, stage, and default-on onto one row. A missing row is unreachable!. One extra enum variant and one missing table row — run to key() and it panics.
Source: codex-rs/features/src/lib.rs lines 41–58; codex-rs/features/src/lib.rs lines 819–826; codex-rs/features/src/lib.rs lines 379–384
Two nearby tests lock the defaults. In-development features must default off. A default-on feature can only be Stable or Removed. There are five stages; the extra Experimental carries a menu name and an announcement. Deprecated has no expiry; three Deprecated items can still be turned on. A stage can say “do not use”; it cannot say “delete next version.”
Source: codex-rs/features/src/tests.rs lines 17–28; codex-rs/features/src/tests.rs lines 82–94
An exhaustive table plus two tests still holds in another language. Miss a row and it panics; defaults stay locked. You do not get auto-delete — you get these two invariants.
DSH: every package must show up; empty still needs an explanation
DeepSeek Harness writes “every package must own ./invariant” as both prose and a gate. The prose is in packages/AGENTS.md. The gate is the 21-line verify-package-invariants; failure is process.exit(1). An empty installer must carry the fixed prefix No runtime invariant:. Empty is an explicit architecture conclusion — later mutable state must become a real check.
The note answers why empty is allowed; the checker guarantees empty must be explained. Miss either and you are back at Codex line 35: the words stay, the object has moved. DSH has no rustc plugin for foo(false). Codex has no exhaustive package gate for path existence.
Source: packages/AGENTS.md line 18; scripts/verify-package-invariants.ts lines 1–21
Grok: a localizable decision goes straight into clippy
The Grok Build repo root has no AGENTS.md. It still writes one architecture decision as lint: clippy.toml bans canonicalize, because Windows gets a verbatim prefix that breaks git and leaks into model context. The exec boundary is in the same file: this ban is run by each crate’s cargo clippy presubmit; Bazel-only crates rely on a person.
Same class of judgment as Codex’s argument comments: the call site is local, the false-positive surface is controllable. Grok admits Bazel coverage is incomplete. Codex admits local only runs the current OS. A small team should copy path existence and the 21-line verify script first — cheaper than copying Dylint.
Source: clippy.toml lines 9–28
Which automatic check do you build first?
Line 35 and line 265 of AGENTS.md are both stale paths. If you can only build one automatic check first, do you check paths prefixed codex-rs/, or every backtick string that contains /?
The first misses a relative form like app-server-protocol/src/protocol/v2.rs. The second false-hits command names, crate names, and URL fragments. Write your filter rule, and use these two stale paths as positive examples.