lazycodex-ai 5.0.0-beta.85 → 5.0.0-beta.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/index.js +68 -41
  3. package/dist/cli-node/index.js +68 -41
  4. package/package.json +1 -1
  5. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  6. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +2 -0
  7. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  8. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  9. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  10. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  11. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  12. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  13. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  14. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  15. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  16. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  17. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  18. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-6.md +1 -1
  19. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  20. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  21. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  22. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  23. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  24. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  25. package/packages/omo-codex/plugin/components/ultrawork/README.md +1 -1
  26. package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +2 -2
  27. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +63 -89
  28. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  29. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  30. package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
  31. package/packages/omo-codex/plugin/components/ulw-execute-continuation/directive.md +2 -2
  32. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  33. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  34. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +63 -89
  35. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  36. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  37. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/define-goal.md +2 -3
  38. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +2 -2
  39. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  40. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  41. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  42. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  43. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  44. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  45. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  46. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  47. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  48. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  49. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  50. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  51. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  52. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  53. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  54. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  55. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  56. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  57. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  58. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  59. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  60. package/packages/omo-codex/plugin/package-lock.json +12 -12
  61. package/packages/omo-codex/plugin/package.json +1 -1
  62. package/packages/omo-codex/plugin/scripts/materialize-shared-upstreams.mjs +8 -2
  63. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +2 -2
  64. package/packages/omo-codex/plugin/skills/browser/ATTRIBUTION.md +26 -14
  65. package/packages/omo-codex/plugin/skills/browser/SKILL.md +65 -52
  66. package/packages/omo-codex/plugin/skills/browser/references/commands.md +81 -66
  67. package/packages/omo-codex/plugin/skills/browser/references/install.md +31 -34
  68. package/packages/omo-codex/plugin/skills/browser/references/owned-engine/README.md +41 -21
  69. package/packages/omo-codex/plugin/skills/browser/references/owned-engine/frames-and-humans.md +33 -20
  70. package/packages/omo-codex/plugin/skills/browser/references/owned-engine/ladder.md +24 -22
  71. package/packages/omo-codex/plugin/skills/browser/references/owned-engine/network.md +35 -15
  72. package/packages/omo-codex/plugin/skills/browser/references/recipes/1password.md +13 -11
  73. package/packages/omo-codex/plugin/skills/browser/references/remote.md +5 -4
  74. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/index.js +1534 -0
  75. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +9 -0
  76. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/page-bundle.js +1395 -0
  77. package/packages/omo-codex/plugin/skills/browser/scripts/browser-doctor.mjs +37 -32
  78. package/packages/omo-codex/plugin/skills/browser/scripts/browser-install.mjs +31 -44
  79. package/packages/omo-codex/plugin/skills/browser/scripts/omowright.mjs +25 -0
  80. package/packages/omo-codex/plugin/skills/debugging/SKILL.md +2 -2
  81. package/packages/omo-codex/plugin/skills/debugging/references/methodology/06-fix.md +3 -3
  82. package/packages/omo-codex/plugin/skills/debugging/references/methodology/08-qa.md +1 -1
  83. package/packages/omo-codex/plugin/skills/debugging/references/tools/browser-qa.md +104 -0
  84. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +1 -1
  85. package/packages/omo-codex/plugin/skills/frontend/references/design/clone-from-url.md +1 -1
  86. package/packages/omo-codex/plugin/skills/programming/SKILL.md +12 -18
  87. package/packages/omo-codex/plugin/skills/programming/references/rust/README.md +43 -15
  88. package/packages/omo-codex/plugin/skills/programming/references/rust/api-design.md +81 -0
  89. package/packages/omo-codex/plugin/skills/programming/references/rust/async-tokio.md +60 -28
  90. package/packages/omo-codex/plugin/skills/programming/references/rust/axum-stack.md +1 -13
  91. package/packages/omo-codex/plugin/skills/programming/references/rust/cargo-strict.md +44 -8
  92. package/packages/omo-codex/plugin/skills/programming/references/rust/clap-stack.md +8 -3
  93. package/packages/omo-codex/plugin/skills/programming/references/rust/concurrency.md +66 -52
  94. package/packages/omo-codex/plugin/skills/programming/references/rust/libraries.md +35 -25
  95. package/packages/omo-codex/plugin/skills/programming/references/rust/macros.md +63 -0
  96. package/packages/omo-codex/plugin/skills/programming/references/rust/one-liners.md +5 -3
  97. package/packages/omo-codex/plugin/skills/programming/references/rust/proptest-insta.md +8 -0
  98. package/packages/omo-codex/plugin/skills/programming/references/rust/type-state.md +50 -12
  99. package/packages/omo-codex/plugin/skills/programming/references/rust/unsafe-discipline.md +34 -6
  100. package/packages/omo-codex/plugin/skills/programming/references/rust/zero-cost-safety.md +62 -52
  101. package/packages/omo-codex/plugin/skills/programming/references/rust-ub/miri-sanitizers-loom.md +1 -1
  102. package/packages/omo-codex/plugin/skills/programming/references/rust-ub/ub-taxonomy.md +6 -3
  103. package/packages/omo-codex/plugin/skills/programming/scripts/rust/check-no-excuse-rules.sh +86 -75
  104. package/packages/omo-codex/plugin/skills/programming/scripts/rust/new-project.py +31 -28
  105. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +1 -1
  106. package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +27 -20
  107. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +1 -1
  108. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/chrome-stealth.md +32 -100
  109. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +5 -11
  110. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/playwright.md +20 -37
  111. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +63 -89
  112. package/packages/omo-codex/plugin/skills/ulw-execute/SKILL.md +4 -4
  113. package/packages/omo-codex/plugin/skills/ulw-loop/references/define-goal.md +2 -3
  114. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +2 -2
  115. package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +1 -1
  116. package/packages/omo-codex/plugin/skills/visual-qa/references/browser-setup.md +46 -46
  117. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +2 -2
  118. package/packages/omo-codex/scripts/install-dist/install-local.mjs +4 -2
  119. package/packages/prompts-core/prompts/ultrawork/codex.md +63 -89
  120. package/packages/shared-skills/skills/browser/ATTRIBUTION.md +26 -14
  121. package/packages/shared-skills/skills/browser/SKILL.md +65 -52
  122. package/packages/shared-skills/skills/browser/references/commands.md +81 -66
  123. package/packages/shared-skills/skills/browser/references/install.md +31 -34
  124. package/packages/shared-skills/skills/browser/references/owned-engine/README.md +41 -21
  125. package/packages/shared-skills/skills/browser/references/owned-engine/frames-and-humans.md +33 -20
  126. package/packages/shared-skills/skills/browser/references/owned-engine/ladder.md +24 -22
  127. package/packages/shared-skills/skills/browser/references/owned-engine/network.md +35 -15
  128. package/packages/shared-skills/skills/browser/references/recipes/1password.md +13 -11
  129. package/packages/shared-skills/skills/browser/references/remote.md +5 -4
  130. package/packages/shared-skills/skills/browser/runtime/omowright/index.js +1534 -0
  131. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +9 -0
  132. package/packages/shared-skills/skills/browser/runtime/omowright/page-bundle.js +1395 -0
  133. package/packages/shared-skills/skills/browser/scripts/browser-doctor.mjs +37 -32
  134. package/packages/shared-skills/skills/browser/scripts/browser-install.mjs +31 -44
  135. package/packages/shared-skills/skills/browser/scripts/omowright.mjs +25 -0
  136. package/packages/shared-skills/skills/debugging/SKILL.md +2 -2
  137. package/packages/shared-skills/skills/debugging/references/methodology/06-fix.md +3 -3
  138. package/packages/shared-skills/skills/debugging/references/methodology/08-qa.md +1 -1
  139. package/packages/shared-skills/skills/debugging/references/tools/browser-qa.md +104 -0
  140. package/packages/shared-skills/skills/frontend/SKILL.md +1 -1
  141. package/packages/shared-skills/skills/frontend/references/design/clone-from-url.md +1 -1
  142. package/packages/shared-skills/skills/programming/SKILL.md +12 -18
  143. package/packages/shared-skills/skills/programming/references/rust/README.md +43 -15
  144. package/packages/shared-skills/skills/programming/references/rust/api-design.md +81 -0
  145. package/packages/shared-skills/skills/programming/references/rust/async-tokio.md +60 -28
  146. package/packages/shared-skills/skills/programming/references/rust/axum-stack.md +1 -13
  147. package/packages/shared-skills/skills/programming/references/rust/cargo-strict.md +44 -8
  148. package/packages/shared-skills/skills/programming/references/rust/clap-stack.md +8 -3
  149. package/packages/shared-skills/skills/programming/references/rust/concurrency.md +66 -52
  150. package/packages/shared-skills/skills/programming/references/rust/libraries.md +35 -25
  151. package/packages/shared-skills/skills/programming/references/rust/macros.md +63 -0
  152. package/packages/shared-skills/skills/programming/references/rust/one-liners.md +5 -3
  153. package/packages/shared-skills/skills/programming/references/rust/proptest-insta.md +8 -0
  154. package/packages/shared-skills/skills/programming/references/rust/type-state.md +50 -12
  155. package/packages/shared-skills/skills/programming/references/rust/unsafe-discipline.md +34 -6
  156. package/packages/shared-skills/skills/programming/references/rust/zero-cost-safety.md +62 -52
  157. package/packages/shared-skills/skills/programming/references/rust-ub/miri-sanitizers-loom.md +1 -1
  158. package/packages/shared-skills/skills/programming/references/rust-ub/ub-taxonomy.md +6 -3
  159. package/packages/shared-skills/skills/programming/scripts/rust/check-no-excuse-rules.sh +86 -75
  160. package/packages/shared-skills/skills/programming/scripts/rust/check-no-excuse-rules.test.ts +83 -0
  161. package/packages/shared-skills/skills/programming/scripts/rust/new-project.py +31 -28
  162. package/packages/shared-skills/skills/review-work/SKILL.md +1 -1
  163. package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +27 -20
  164. package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +1 -1
  165. package/packages/shared-skills/skills/ultimate-browsing/references/chrome-stealth.md +32 -100
  166. package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +5 -11
  167. package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/playwright.md +20 -37
  168. package/packages/shared-skills/skills/ulw-execute/SKILL.md +4 -4
  169. package/packages/shared-skills/skills/visual-qa/SKILL.md +1 -1
  170. package/packages/shared-skills/skills/visual-qa/references/browser-setup.md +46 -46
  171. package/packages/omo-codex/plugin/skills/browser/scripts/browser-env.mjs +0 -41
  172. package/packages/omo-codex/plugin/skills/debugging/references/tools/playwright-cli.md +0 -112
  173. package/packages/omo-codex/plugin/skills/programming/scripts/rust/check-no-excuse-rules.py +0 -296
  174. package/packages/shared-skills/skills/browser/scripts/browser-env.mjs +0 -41
  175. package/packages/shared-skills/skills/debugging/references/tools/playwright-cli.md +0 -112
  176. package/packages/shared-skills/skills/programming/scripts/rust/check-no-excuse-rules.py +0 -296
@@ -146,7 +146,7 @@ impl Arena {
146
146
  // bump. `aligned` is aligned to `layout.align()` by `align_up`. No
147
147
  // other writer can have observed this offset because the CAS above
148
148
  // returned `Ok`.
149
- let ptr = unsafe { self.map.as_mut_ptr().add(aligned) as *mut T };
149
+ let ptr = unsafe { self.map.as_mut_ptr().add(aligned) }.cast::<T>();
150
150
  // SAFETY: `ptr` is non-null (mmap base + offset), aligned (above),
151
151
  // exclusively owned (CAS), and we are about to initialize it.
152
152
  unsafe { ptr::write(ptr, value) };
@@ -187,7 +187,35 @@ What miri catches that the borrow checker cannot:
187
187
  - Stacked borrows / Tree borrows aliasing violations
188
188
  - Data races (single-threaded model, but catches concurrent access through `UnsafeCell` misuse)
189
189
  - Atomic ordering bugs in some patterns
190
- - Memory leaks (with `-Zmiri-track-pointer-tag`)
190
+ - Memory leaks (reported by default at exit; `-Zmiri-ignore-leaks` turns it off)
191
+
192
+ ## FFI Declarations (Rust 2024)
193
+
194
+ The 2024 edition makes the unsafety of FFI items visible at the declaration:
195
+
196
+ ```rust
197
+ use core::ffi::{c_char, c_int};
198
+
199
+ // SAFETY: these signatures match <string.h>/<stdlib.h> for every target we build.
200
+ unsafe extern "C" {
201
+ /// No preconditions: callable from safe code.
202
+ pub safe fn abs(x: c_int) -> c_int;
203
+ /// Caller must pass a valid NUL-terminated string.
204
+ pub unsafe fn strlen(s: *const c_char) -> usize;
205
+ }
206
+
207
+ // Exported symbol: the attribute itself is unsafe because a name clash is UB.
208
+ #[unsafe(no_mangle)]
209
+ pub extern "C" fn mylib_version() -> u32 { 3 }
210
+ ```
211
+
212
+ - A bare `extern "C" { ... }` block is a hard error in the 2024 edition; write `unsafe extern` and mark each item `safe fn` (no caller obligations) or `unsafe fn`. Marking an item `safe` is a promise you make for every caller; when in doubt, it is `unsafe`.
213
+ - `#[no_mangle]`, `#[export_name]`, and `#[link_section]` are written `#[unsafe(...)]`.
214
+ - A panic that unwinds out of an `extern "C"` function aborts the process (1.81+). Catch it with `std::panic::catch_unwind` at the boundary and return an error code; use `extern "C-unwind"` only when the foreign side is built to unwind.
215
+
216
+ ## Uninitialized Memory
217
+
218
+ `MaybeUninit<T>` is the only way to hold memory that is not yet a valid `T`. `mem::uninitialized()` is always wrong, and `mem::zeroed()` is UB for any type whose all-zero bit pattern is invalid (references, `NonZero*`, `bool`-bearing enums, most `repr(Rust)` structs). An uninitialized array is `[const { MaybeUninit::<T>::uninit() }; N]`; write every element through `MaybeUninit::write`, then convert once, with a SAFETY comment stating that all `N` elements were written.
191
219
 
192
220
  ## When Miri Cannot Run
193
221
 
@@ -218,8 +246,8 @@ mod tests {
218
246
  fn concurrent_push_pop() {
219
247
  loom::model(|| {
220
248
  let queue = std::sync::Arc::new(MyQueue::new());
221
- let q1 = queue.clone();
222
- let q2 = queue.clone();
249
+ let q1 = std::sync::Arc::clone(&queue);
250
+ let q2 = std::sync::Arc::clone(&queue);
223
251
  let h1 = loom::thread::spawn(move || q1.push(1));
224
252
  let h2 = loom::thread::spawn(move || q2.pop());
225
253
  h1.join().unwrap();
@@ -229,7 +257,7 @@ mod tests {
229
257
  }
230
258
  ```
231
259
 
232
- Run: `RUSTFLAGS="--cfg loom" cargo test --release`. Loom exhaustively explores thread interleavings for the test scope. Combined with miri on the single-thread paths, you have machine-checked soundness over the full state space.
260
+ Run: `RUSTFLAGS="--cfg loom" cargo test --release`. Loom systematically explores the interleavings its bounded model allows for the test scope; combined with miri on the single-thread paths, that is strong machine-checked evidence, not a proof over every execution.
233
261
 
234
262
  ## The Forbidden List
235
263
 
@@ -241,7 +269,7 @@ Reject in code review, automatic CI fail:
241
269
  - `std::mem::transmute` for anything but lifetime extension on the same layout (and that should usually be `core::mem::transmute_copy` or `bytemuck::cast` if the relayout is well-defined).
242
270
  - `std::ptr::read_unaligned` / `write_unaligned` without a comment explaining why aligned access is impossible.
243
271
  - `from_raw_parts` / `from_raw_parts_mut` without proving the source pointer's provenance covers the entire slice.
244
- - `Arc::get_mut_unchecked`, `Box::leak` to bypass ownership, `MaybeUninit::assume_init` on partially-initialized data.
272
+ - `Arc::get_mut_unchecked`, `Box::leak` to bypass ownership, `MaybeUninit::assume_init` on partially-initialized data, `mem::uninitialized`, `mem::zeroed` for a type whose all-zero pattern is not valid.
245
273
  - `unsafe impl Send`, `unsafe impl Sync` on types containing raw pointers, without a comment naming exactly which interior-mutability rule is upheld.
246
274
  - Any `unsafe` block whose justification depends on "in practice this never happens".
247
275
 
@@ -100,18 +100,20 @@ const fn fibonacci(n: usize) -> usize {
100
100
  const FIB_20: usize = fibonacci(20); // computed at compile time: 6765
101
101
 
102
102
  // Use in array sizes
103
+ #[expect(clippy::indexing_slicing, reason = "const evaluation: an out-of-bounds index is a compile error")]
103
104
  const LOOKUP: [u8; 256] = {
104
105
  let mut table = [0u8; 256];
105
- let mut i = 0;
106
- while i < 256 {
107
- table[i] = (i as u8).wrapping_mul(7);
106
+ let mut i: u8 = 0;
107
+ loop {
108
+ table[i as usize] = i.wrapping_mul(7); // u8 -> usize widens: lossless
109
+ if i == u8::MAX { break; }
108
110
  i += 1;
109
111
  }
110
112
  table
111
113
  };
112
114
  ```
113
115
 
114
- **Stable since Rust 1.82:** `const fn` supports `match`, loops, `if`, references, mutable locals — nearly full Rust. Use `const { }` blocks (Rust 1.79+) for inline compile-time assertions.
116
+ `const fn` bodies may use `if`, `match`, `loop`/`while`, and mutable locals; trait methods (`From`, `TryFrom`, `Iterator`) are not callable in `const` on stable. That makes a **lossless widening `as` inside `const`** the one accepted numeric cast: iterate in the narrow type and widen to index, as above, so no value can truncate. Use `const { }` blocks (Rust 1.79+) for inline compile-time assertions.
115
117
 
116
118
  ```rust
117
119
  fn process<const N: usize>(data: &[u8; N]) {
@@ -123,6 +125,7 @@ fn process<const N: usize>(data: &[u8; N]) {
123
125
  ### const generics — Type-Level Values
124
126
 
125
127
  ```rust
128
+ #[derive(Debug)]
126
129
  struct Buffer<const N: usize> {
127
130
  data: [u8; N],
128
131
  len: usize,
@@ -134,13 +137,17 @@ impl<const N: usize> Buffer<N> {
134
137
  }
135
138
 
136
139
  fn push(&mut self, byte: u8) -> Result<(), BufferFullError> {
137
- if self.len >= N { return Err(BufferFullError); }
138
- self.data[self.len] = byte;
139
- self.len += 1;
140
+ let slot = self.data.get_mut(self.len).ok_or(BufferFullError)?;
141
+ *slot = byte;
142
+ self.len = self.len.saturating_add(1); // < N after the get_mut above
140
143
  Ok(())
141
144
  }
142
145
  }
143
146
 
147
+ impl<const N: usize> Default for Buffer<N> {
148
+ fn default() -> Self { Self::new() }
149
+ }
150
+
144
151
  // Compiler enforces: Buffer<16> and Buffer<32> are distinct types.
145
152
  let small: Buffer<16> = Buffer::new();
146
153
  let large: Buffer<1024> = Buffer::new();
@@ -148,26 +155,7 @@ let large: Buffer<1024> = Buffer::new();
148
155
 
149
156
  ### proc macros — Code Generation (Zig comptime type creation)
150
157
 
151
- When `const fn` is not enough (generating struct fields, impl blocks, or derive logic), proc macros fill the gap.
152
-
153
- ```rust
154
- // In a proc-macro crate:
155
- use proc_macro::TokenStream;
156
- use quote::quote;
157
- use syn::{parse_macro_input, DeriveInput};
158
-
159
- #[proc_macro_derive(Builder)]
160
- pub fn derive_builder(input: TokenStream) -> TokenStream {
161
- let input = parse_macro_input!(input as DeriveInput);
162
- let name = &input.ident;
163
- // ... generate builder struct and impl
164
- TokenStream::from(quote! {
165
- impl #name {
166
- pub fn builder() -> #name##Builder { /* ... */ }
167
- }
168
- })
169
- }
170
- ```
158
+ When `const fn` is not enough (generating struct fields, impl blocks, or derive logic), proc macros fill the gap. Crate layout, `syn`/`quote`, identifier construction, and spanned errors → [macros.md](macros.md).
171
159
 
172
160
  **Decision tree:**
173
161
 
@@ -195,15 +183,14 @@ fn process(input: &str) -> String {
195
183
 
196
184
  // GOOD: caller provides output buffer, zero allocation
197
185
  fn process(input: &[u8], output: &mut [u8]) -> usize {
198
- let len = input.len().min(output.len());
199
- for i in 0..len {
200
- output[i] = input[i].to_ascii_uppercase();
186
+ for (dst, src) in output.iter_mut().zip(input) {
187
+ *dst = src.to_ascii_uppercase();
201
188
  }
202
- len // returns bytes written
189
+ input.len().min(output.len()) // bytes written; zip stopped at the shorter slice
203
190
  }
204
191
 
205
192
  // GOOD: return borrowed data when possible
206
- fn find_token<'a>(input: &'a str) -> Option<&'a str> {
193
+ fn find_token(input: &str) -> Option<&str> {
207
194
  input.split_whitespace().next() // no allocation — borrows from input
208
195
  }
209
196
  ```
@@ -231,9 +218,28 @@ tags.push(1); // on stack if <= 8 elements
231
218
 
232
219
  // ArrayVec: purely stack, fixed capacity, no heap ever
233
220
  let mut buf: ArrayVec<u8, 64> = ArrayVec::new();
234
- buf.try_push(42).map_err(|_| "full")?; // returns error instead of panic
221
+ buf.try_push(42).map_err(|_| BufferFullError)?; // typed error instead of panic
222
+ ```
223
+
224
+ ### Move Out of `&mut` Without Cloning
225
+
226
+ ```rust
227
+ // Take the value, leave Default behind: no clone, no Option dance.
228
+ let pending = std::mem::take(&mut self.pending); // Vec/String/HashMap
229
+ let old = std::mem::replace(&mut self.state, State::Idle);
230
+ let conn = self.conn.take(); // Option<T> field
235
231
  ```
236
232
 
233
+ A `.clone()` whose only purpose is to satisfy the borrow checker behind `&mut self` is almost always one of these three.
234
+
235
+ ### Reuse and Pre-Size
236
+
237
+ - Reserve from a known size: `Vec::with_capacity(n)` / `String::with_capacity(n)`, or `reserve(n)` before a batch `extend`.
238
+ - In a loop, hoist a scratch buffer and `clear()` it each iteration instead of allocating a fresh one; `clear` keeps the capacity. Drop or `shrink_to` it when one outlier would pin a huge allocation.
239
+ - Format into an existing buffer with `write!(buf, ...)` (`use std::fmt::Write as _`) instead of building intermediate `format!` strings, and pass `&str` literals where no formatting happens.
240
+
241
+ Apply these on paths a profile or benchmark shows to be hot; elsewhere the clearer allocation wins.
242
+
237
243
  ### Cow — Defer Allocation Until Mutation
238
244
 
239
245
  ```rust
@@ -264,15 +270,7 @@ Even in `std` code, the *mindset* applies: prefer `&[T]` over `Vec<T>` in functi
264
270
 
265
271
  ### Clippy Lints for Hidden Allocations
266
272
 
267
- ```toml
268
- # Cargo.toml — catch accidental allocations
269
- [lints.clippy]
270
- # These warn on patterns that allocate when a borrow would suffice:
271
- unnecessary_to_owned = "warn" # .to_string() / .to_vec() when borrow works
272
- redundant_clone = "warn" # .clone() that's immediately consumed
273
- large_stack_arrays = "warn" # accidental large stack usage
274
- vec_init_then_push = "warn" # Vec::new() + push instead of vec![]
275
- ```
273
+ The groups in [cargo-strict.md](cargo-strict.md) already enable the allocation lints: `unnecessary_to_owned` and `vec_init_then_push` (in `all`, denied), `redundant_clone` (nursery), `large_stack_arrays` (pedantic). Do not re-list them in `[lints.clippy]`: a per-lint `"warn"` entry overrides the group and silently downgrades a deny.
276
274
 
277
275
  ---
278
276
 
@@ -408,22 +406,24 @@ Zig's `errdefer` runs cleanup only on error paths. Rust's `Drop` always runs, bu
408
406
  ### scopeguard — The errdefer Equivalent
409
407
 
410
408
  ```rust
411
- use scopeguard::{defer, guard};
409
+ use scopeguard::{guard, ScopeGuard};
412
410
  use std::fs;
413
411
 
414
412
  fn create_and_process(path: &str) -> std::io::Result<()> {
415
413
  let file = fs::File::create(path)?;
416
414
  // If anything below fails, clean up the file.
417
415
  // This is exactly Zig's errdefer.
418
- let _cleanup = guard((), |_| {
419
- let _ = fs::remove_file(path);
416
+ let cleanup = guard((), |()| {
417
+ if let Err(error) = fs::remove_file(path) {
418
+ tracing::warn!(%error, path, "cleanup after failed write");
419
+ }
420
420
  });
421
421
 
422
422
  write_data(&file)?;
423
423
  validate_data(path)?;
424
424
 
425
425
  // Success: defuse the guard so it does NOT run cleanup.
426
- std::mem::forget(_cleanup);
426
+ ScopeGuard::into_inner(cleanup);
427
427
  Ok(())
428
428
  }
429
429
  ```
@@ -437,7 +437,7 @@ fn with_temp_dir() -> anyhow::Result<()> {
437
437
  let dir = tempfile::tempdir()?;
438
438
  defer! {
439
439
  // Runs when scope exits, success or failure.
440
- println!("Cleaning up {}", dir.path().display());
440
+ tracing::debug!(path = %dir.path().display(), "leaving temp dir scope");
441
441
  // dir's Drop also cleans up, but this shows the pattern.
442
442
  }
443
443
 
@@ -449,6 +449,7 @@ fn with_temp_dir() -> anyhow::Result<()> {
449
449
  ### Drop as RAII Cleanup
450
450
 
451
451
  ```rust
452
+ #[derive(Debug)]
452
453
  struct TempFile { path: std::path::PathBuf }
453
454
 
454
455
  impl TempFile {
@@ -461,7 +462,10 @@ impl TempFile {
461
462
 
462
463
  impl Drop for TempFile {
463
464
  fn drop(&mut self) {
464
- let _ = std::fs::remove_file(&self.path);
465
+ // Drop cannot return an error; report it instead of discarding it.
466
+ if let Err(error) = std::fs::remove_file(&self.path) {
467
+ tracing::warn!(%error, path = %self.path.display(), "temp file not removed");
468
+ }
465
469
  }
466
470
  }
467
471
 
@@ -469,19 +473,25 @@ impl Drop for TempFile {
469
473
  let tmp = TempFile::new("/tmp/scratch.dat")?;
470
474
  ```
471
475
 
476
+ ### Drop Order Is Part of the Contract
477
+
478
+ Locals drop in reverse declaration order; struct fields drop in declaration order (first field first). When one resource must outlive another (a transaction before its connection, a worker handle before the channel it drains), encode it: declare the dependent field first, or call `drop(x)` at the exact point the dependency ends. Never rely on an order the code does not state.
479
+
472
480
  ### The errdefer Pattern — Defuse on Success
473
481
 
474
482
  The key insight from Zig's `errdefer`: you want cleanup on error but NOT on success. In Rust:
475
483
 
476
484
  ```rust
477
- use scopeguard::ScopeGuard;
485
+ use scopeguard::{guard, ScopeGuard};
478
486
 
479
487
  fn deploy(artifact: &Path) -> Result<(), DeployError> {
480
488
  let backup = backup_current()?;
481
489
 
482
490
  // errdefer: restore backup if anything fails
483
- let rollback = guard(backup.clone(), |b| {
484
- let _ = restore_from_backup(&b);
491
+ let rollback = guard(backup, |b| {
492
+ if let Err(error) = restore_from_backup(&b) {
493
+ tracing::error!(%error, "rollback failed; manual restore needed");
494
+ }
485
495
  });
486
496
 
487
497
  upload(artifact)?;
@@ -522,6 +532,6 @@ All achievable within Rust's single toolchain. You get Zig's explicitness **plus
522
532
  - **Zero-alloc APIs** hurt readability when the function naturally produces owned data. Don't force `&mut [u8]` output buffers on a function that logically returns `String`.
523
533
  - **`#[repr(packed)]`** only for wire formats and FFI. Never for regular domain types.
524
534
  - **Scope guards** unnecessary when `Drop` on the value itself handles cleanup (e.g., `tempfile::NamedTempFile` already does this).
525
- - **`const fn`** everything? No — only when the value is genuinely needed at compile time or the function is trivially const-eligible. Don't contort logic just to be const.
535
+ - **`const fn`** everything? No — only when a `const` context needs the function or it is trivially const-eligible. Don't contort logic just to be const.
526
536
 
527
537
  The goal is **visible costs and explicit control**, not asceticism. Use `String` and `Vec` freely when they're the right tool. Reach for these patterns when allocation behavior matters for correctness or performance.
@@ -66,7 +66,7 @@ cargo +nightly miri test
66
66
  MIRIFLAGS="-Zmiri-tree-borrows" cargo +nightly miri test
67
67
  ```
68
68
 
69
- **Protocol:** Run Stacked Borrows first. If it fails, fix it. Then run Tree Borrows to confirm. Code that passes Stacked Borrows is sound under both models.
69
+ **Protocol:** Run Stacked Borrows first. If it fails, fix it. Then run Tree Borrows as a separate pass. The models are not nested: Stacked Borrows is stricter about most reborrow patterns, but Tree Borrows rejects some code Stacked Borrows accepts. Ship only when both are clean.
70
70
 
71
71
  #### Strict Provenance
72
72
 
@@ -140,7 +140,7 @@ MIRIFLAGS="-Zmiri-strict-provenance" cargo +nightly miri test
140
140
  - Passing a Rust `enum` to C without `#[repr(C)]` or `#[repr(i32)]`.
141
141
  - Null pointer passed where C expects non-null (and Rust wraps it in `&T`).
142
142
  - C code writing to Rust-owned memory through a pointer Rust considers immutable.
143
- - Forgetting to mark FFI functions as `unsafe extern "C"`.
143
+ - Declaring a foreign function `safe fn` inside `unsafe extern` when it has caller preconditions (null, length, lifetime), so safe code can violate them.
144
144
  - longjmp/setjmp across Rust frames (unwinding UB).
145
145
 
146
146
  **Miri detection:** LIMITED — Miri cannot execute foreign code. It detects UB in the Rust-side handling of FFI return values.
@@ -236,12 +236,15 @@ MIRIFLAGS="-Zmiri-strict-provenance" cargo +nightly miri test
236
236
  **Root cause:** A Rust panic unwinding through a frame that uses the C calling convention.
237
237
 
238
238
  **Canonical triggers:**
239
- - `panic!()` inside a `#[no_mangle] extern "C" fn` callback passed to C code.
239
+ - `panic!()` inside a `#[unsafe(no_mangle)] extern "C" fn` callback passed to C code.
240
240
  - `unwrap()` inside FFI callbacks.
241
+ - Foreign code unwinding (C++ exceptions, `longjmp`) through a Rust frame declared `extern "C"`.
242
+
243
+ Since Rust 1.81 a Rust panic that reaches an `extern "C"` boundary aborts the process instead of unwinding into foreign frames: no longer UB, but still a crash.
241
244
 
242
245
  **Miri detection:** PARTIAL — Miri does not model foreign unwinding, but it can detect the immediate UB if the panic reaches the FFI boundary.
243
246
 
244
- **Fix pattern:** Use `std::panic::catch_unwind` at every FFI entry point. Mark FFI callbacks as `extern "C-unwind"` when panic propagation is intentional (nightly). Prefer returning `Result`-like error codes from FFI callbacks.
247
+ **Fix pattern:** Use `std::panic::catch_unwind` at every FFI entry point and return an error code. Declare the ABI `extern "C-unwind"` (stable since 1.71) only when both sides are built to let unwinding cross.
245
248
 
246
249
  ---
247
250
 
@@ -1,8 +1,22 @@
1
1
  #!/usr/bin/env bash
2
2
  # No-excuse rule checker for Rust files.
3
- # Mirrors the philosophy of python-programmer / typescript-programmer scripts:
4
- # only rules that can be enforced via pure text matching live here.
3
+ # Only rules that can be enforced via pure text matching live here.
5
4
  # Everything semantic is on clippy + miri + nextest.
5
+ #
6
+ # Rules:
7
+ # unwrap / expect outside tests, unless the previous line carries
8
+ # #[expect(clippy::unwrap_used|expect_used, reason = "...")]
9
+ # placeholder-macro todo!/unimplemented!/unreachable! in committed code
10
+ # box-dyn-error Box<dyn Error> in non-test code
11
+ # lib-panic panic!() in library code
12
+ # discarded-result `let _ = call(...)`, unless the previous line carries
13
+ # #[expect(clippy::let_underscore_must_use, reason = "...")]
14
+ # blocking-in-async std::thread::sleep / std::fs / reqwest::blocking /
15
+ # .blocking_*() inside an async fn or async block
16
+ # unsafe-no-safety unsafe { without // SAFETY: in the preceding 5 lines
17
+ # allow-attribute #[allow(...)] - silence lints with #[expect(lint, reason)]
18
+ # expect-without-reason #[expect(...)] with no `reason = "..."`
19
+ # narrowing-as-cast possible narrowing numeric `as` cast
6
20
 
7
21
  set -euo pipefail
8
22
 
@@ -13,23 +27,22 @@ fi
13
27
 
14
28
  violations=0
15
29
  report() {
16
- local file="$1"
17
- local line="$2"
18
- local rule="$3"
19
- local detail="$4"
20
- echo "::error file=${file},line=${line}::[${rule}] ${detail}" >&2
30
+ echo "::error file=$1,line=$2::[$3] $4" >&2
21
31
  violations=$((violations + 1))
22
32
  }
23
33
 
24
34
  is_test_path() {
25
- local path="$1"
26
- case "$path" in
35
+ case "$1" in
27
36
  */tests/*|*/benches/*|*/examples/*|*/build.rs|*_test.rs|tests/*|benches/*|examples/*) return 0 ;;
28
37
  esac
29
- # In-file #[cfg(test)] modules are handled per-line below.
30
38
  return 1
31
39
  }
32
40
 
41
+ count_char() {
42
+ local only="${1//[^$2]/}"
43
+ echo "${#only}"
44
+ }
45
+
33
46
  for file in "$@"; do
34
47
  [ -f "$file" ] || continue
35
48
  case "$file" in
@@ -37,108 +50,106 @@ for file in "$@"; do
37
50
  *) continue ;;
38
51
  esac
39
52
 
40
- if is_test_path "$file"; then
41
- # Test files are exempt from unwrap/expect/todo rules.
42
- # Still enforce unsafe-comment, allow-comment, panic-in-lib rules below
43
- # by setting a marker - keeping the loop unified.
44
- in_test_file=1
45
- else
46
- in_test_file=0
47
- fi
48
-
49
- # Track #[cfg(test)] regions for per-line exemptions.
50
- in_cfg_test=0
51
- cfg_test_brace_depth=0
52
- line_no=0
53
-
54
- while IFS= read -r raw_line || [ -n "$raw_line" ]; do
55
- line_no=$((line_no + 1))
56
- line="$raw_line"
57
-
58
- # Crude #[cfg(test)] region tracker: when we see #[cfg(test)] on a
59
- # line followed by a mod with `{`, count braces until depth returns
60
- # to zero. This is approximate but matches typical formatting.
53
+ in_test_file=0
54
+ is_test_path "$file" && in_test_file=1
55
+
56
+ lines=()
57
+ while IFS= read -r raw || [ -n "$raw" ]; do lines+=("$raw"); done < "$file"
58
+ total=${#lines[@]}
59
+
60
+ # Brace-tracked regions: a region starts at its marker line and ends when the
61
+ # braces opened after the marker are closed again.
62
+ in_cfg_test=0; cfg_depth=0; cfg_opened=0
63
+ in_async=0; async_depth=0; async_opened=0
64
+
65
+ for ((i = 0; i < total; i++)); do
66
+ line_no=$((i + 1))
67
+ line="${lines[$i]}"
68
+ prev=""
69
+ [ "$i" -gt 0 ] && prev="${lines[$((i - 1))]}"
70
+ code_only="${line%%//*}"
71
+ opens=$(count_char "$code_only" "{")
72
+ closes=$(count_char "$code_only" "}")
73
+
61
74
  if [[ "$line" =~ \#\[cfg\(test\)\] ]]; then
62
- in_cfg_test=1
63
- cfg_test_brace_depth=0
75
+ in_cfg_test=1; cfg_depth=0; cfg_opened=0
64
76
  fi
65
77
  if [ "$in_cfg_test" -eq 1 ]; then
66
- opens=$(printf '%s' "$line" | tr -cd '{' | wc -c)
67
- closes=$(printf '%s' "$line" | tr -cd '}' | wc -c)
68
- cfg_test_brace_depth=$((cfg_test_brace_depth + opens - closes))
69
- if [ "$cfg_test_brace_depth" -le 0 ] && [[ ! "$line" =~ \#\[cfg\(test\)\] ]]; then
70
- in_cfg_test=0
71
- fi
78
+ cfg_depth=$((cfg_depth + opens - closes))
79
+ [ "$opens" -gt 0 ] && cfg_opened=1
80
+ if [ "$cfg_opened" -eq 1 ] && [ "$cfg_depth" -le 0 ]; then in_cfg_test=0; fi
81
+ fi
82
+
83
+ if [ "$in_async" -eq 0 ] && [[ "$code_only" =~ (^|[^[:alnum:]_])async([[:space:]]+(move[[:space:]]*)?\{|[[:space:]]+(unsafe[[:space:]]+)?fn[[:space:]]) ]]; then
84
+ in_async=1; async_depth=0; async_opened=0
85
+ fi
86
+ async_line=$in_async
87
+ if [ "$in_async" -eq 1 ]; then
88
+ async_depth=$((async_depth + opens - closes))
89
+ [ "$opens" -gt 0 ] && async_opened=1
90
+ if [ "$async_opened" -eq 1 ] && [ "$async_depth" -le 0 ]; then in_async=0; fi
72
91
  fi
73
92
 
74
93
  exempt=0
75
94
  [ "$in_test_file" -eq 1 ] && exempt=1
76
95
  [ "$in_cfg_test" -eq 1 ] && exempt=1
77
96
 
78
- # Strip line comments before pattern checks - so doc comments and
79
- # explanatory prose do not trip the regexes.
80
- code_only="${line%%//*}"
81
-
82
97
  if [ "$exempt" -eq 0 ]; then
83
- # .unwrap()
84
- if [[ "$code_only" =~ \.unwrap\(\) ]]; then
85
- # Allow if previous line had // SAFE-UNWRAP: comment
86
- prev_line=$(sed -n "$((line_no - 1))p" "$file" 2>/dev/null || true)
87
- if [[ ! "$prev_line" =~ //[[:space:]]*SAFE-UNWRAP: ]]; then
88
- report "$file" "$line_no" "unwrap" ".unwrap() outside tests - use ? / ok_or / pattern match or annotate previous line with // SAFE-UNWRAP: <reason>"
89
- fi
98
+ if [[ "$code_only" =~ \.unwrap\(\) ]] && [[ ! "$prev" =~ \#\[expect\(clippy::unwrap_used,.*reason ]]; then
99
+ report "$file" "$line_no" "unwrap" ".unwrap() outside tests - use ? / ok_or / let-else, or for a proven invariant use .expect() behind #[expect(clippy::expect_used, reason = \"...\")]"
90
100
  fi
91
101
 
92
- # .expect("...")
93
- if [[ "$code_only" =~ \.expect\( ]]; then
94
- prev_line=$(sed -n "$((line_no - 1))p" "$file" 2>/dev/null || true)
95
- if [[ ! "$prev_line" =~ //[[:space:]]*SAFE-EXPECT: ]]; then
96
- report "$file" "$line_no" "expect" ".expect() outside tests - use ? or annotate previous line with // SAFE-EXPECT: <reason>"
97
- fi
102
+ if [[ "$code_only" =~ \.expect\( ]] && [[ ! "$prev" =~ \#\[expect\(clippy::expect_used,.*reason ]]; then
103
+ report "$file" "$line_no" "expect" ".expect() outside tests - use ?, or for a proven invariant annotate the previous line with #[expect(clippy::expect_used, reason = \"...\")]"
98
104
  fi
99
105
 
100
- # todo!() / unimplemented!() / unreachable!()
101
106
  if [[ "$code_only" =~ (todo!|unimplemented!|unreachable!|unreachable_unchecked!) ]]; then
102
107
  report "$file" "$line_no" "placeholder-macro" "todo!/unimplemented!/unreachable! in committed code"
103
108
  fi
104
109
 
105
- # Box<dyn Error
106
110
  if [[ "$code_only" =~ Box\<dyn[[:space:]]+Error ]]; then
107
111
  report "$file" "$line_no" "box-dyn-error" "Box<dyn Error> in non-test code - use anyhow::Error (apps) or thiserror enum (libs)"
108
112
  fi
109
113
 
110
- # panic!( in lib
111
114
  if [[ "$file" == */src/lib.rs || "$file" == */src/*/mod.rs || ( "$file" == */src/*.rs && "$file" != */src/main.rs && "$file" != */src/bin/* ) ]]; then
112
115
  if [[ "$code_only" =~ panic!\( ]]; then
113
116
  report "$file" "$line_no" "lib-panic" "panic!() in library code - return Result"
114
117
  fi
115
118
  fi
119
+
120
+ if [[ "$code_only" =~ let[[:space:]]+_[[:space:]]*=[[:space:]]*[^\;]*\( ]] && [[ ! "$prev" =~ \#\[expect\(clippy::let_underscore_must_use,.*reason ]]; then
121
+ report "$file" "$line_no" "discarded-result" "let _ = call() drops its result - propagate or handle the error; log it on a best-effort path"
122
+ fi
123
+
124
+ if [ "$async_line" -eq 1 ] && [[ "$code_only" =~ (thread::sleep\(|std::fs::|reqwest::blocking::|\.blocking_(recv|send|lock|read|write)\() ]]; then
125
+ report "$file" "$line_no" "blocking-in-async" "blocking call inside async code - use tokio::time::sleep / tokio::fs / the async client, or move it into spawn_blocking"
126
+ fi
116
127
  fi
117
128
 
118
- # unsafe { without preceding // SAFETY: in the last 5 lines (always enforced)
119
129
  if [[ "$code_only" =~ unsafe[[:space:]]*\{ ]]; then
120
- start=$((line_no > 5 ? line_no - 5 : 1))
121
- window=$(sed -n "${start},${line_no}p" "$file" 2>/dev/null || true)
130
+ start=$((i > 5 ? i - 5 : 0))
131
+ window=$(printf '%s\n' "${lines[@]:$start:$((i - start + 1))}")
122
132
  if [[ ! "$window" =~ //[[:space:]]*SAFETY: ]]; then
123
133
  report "$file" "$line_no" "unsafe-no-safety-comment" "unsafe block without // SAFETY: comment in preceding 5 lines"
124
134
  fi
125
135
  fi
126
136
 
127
- # #[allow(clippy::...)] without preceding // CLIPPY-ALLOW: justification
128
- if [[ "$code_only" =~ \#\[allow\(clippy:: ]]; then
129
- prev_line=$(sed -n "$((line_no - 1))p" "$file" 2>/dev/null || true)
130
- if [[ ! "$prev_line" =~ //[[:space:]]*CLIPPY-ALLOW: ]]; then
131
- report "$file" "$line_no" "unjustified-clippy-allow" "#[allow(clippy::...)] without // CLIPPY-ALLOW: <reason> on previous line"
137
+ if [[ "$code_only" =~ \#!?\[allow\( ]]; then
138
+ report "$file" "$line_no" "allow-attribute" "#[allow(...)] - use #[expect(lint, reason = \"...\")] so the exception fails once the lint stops firing"
139
+ fi
140
+
141
+ if [[ "$code_only" =~ \#!?\[expect\( ]]; then
142
+ attr=$(printf '%s\n' "${lines[@]:$i:4}")
143
+ if [[ ! "$attr" =~ reason[[:space:]]*= ]]; then
144
+ report "$file" "$line_no" "expect-without-reason" "#[expect(...)] needs reason = \"...\" naming why the lint does not apply"
132
145
  fi
133
146
  fi
134
147
 
135
- # Narrowing numeric `as` casts - heuristic flag for human review.
136
- # Catches the common shapes; precise type analysis belongs to clippy::cast_possible_truncation.
137
- if [[ "$code_only" =~ as[[:space:]]+(u8|u16|u32|i8|i16|i32) ]] && \
138
- [[ "$code_only" =~ (u16|u32|u64|u128|usize|i16|i32|i64|i128|isize)[[:space:]]+as[[:space:]]+(u8|u16|u32|i8|i16|i32) ]]; then
148
+ # Heuristic flag for human review; exact analysis belongs to clippy::cast_possible_truncation.
149
+ if [[ "$code_only" =~ (u16|u32|u64|u128|usize|i16|i32|i64|i128|isize)[[:space:]]+as[[:space:]]+(u8|u16|u32|i8|i16|i32)([^[:alnum:]_]|$) ]]; then
139
150
  report "$file" "$line_no" "narrowing-as-cast" "possible narrowing 'as' cast - use TryFrom / try_into() for fallible conversion"
140
151
  fi
141
- done < "$file"
152
+ done
142
153
  done
143
154
 
144
155
  if [ "$violations" -gt 0 ]; then
@@ -146,9 +157,9 @@ if [ "$violations" -gt 0 ]; then
146
157
  echo "rust-programmer: ${violations} violation(s). Fix before declaring work done." >&2
147
158
  echo "" >&2
148
159
  echo "Then run the full toolchain gate:" >&2
149
- echo " cargo +stable fmt --all -- --check" >&2
150
- echo " cargo +stable clippy --all-targets --all-features -- -D warnings" >&2
151
- echo " cargo nextest run --all-targets --all-features" >&2
160
+ echo " cargo fmt --all -- --check" >&2
161
+ echo " cargo clippy --all-targets --all-features -- -D warnings" >&2
162
+ echo " cargo nextest run --all-targets --all-features && cargo test --doc" >&2
152
163
  echo " cargo +nightly miri nextest run --all-features # if unsafe touched" >&2
153
164
  echo " cargo machete" >&2
154
165
  echo " cargo deny check" >&2