vigiles 2.0.0

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 (156) hide show
  1. package/.claude/settings.json +46 -0
  2. package/.claude/settings.local.json +8 -0
  3. package/.claude-plugin/hooks/post-edit.sh +34 -0
  4. package/.claude-plugin/hooks/pre-edit.sh +40 -0
  5. package/.claude-plugin/hooks/session-start.sh +38 -0
  6. package/.claude-plugin/marketplace.json +14 -0
  7. package/.claude-plugin/plugin.json +47 -0
  8. package/.github/workflows/ci.yml +81 -0
  9. package/.prettierignore +1 -0
  10. package/.vigiles/generated.d.ts +205 -0
  11. package/CLAUDE.md +95 -0
  12. package/CLAUDE.md.spec.ts +142 -0
  13. package/CONTRIBUTING.md +121 -0
  14. package/LICENSE +21 -0
  15. package/README.md +377 -0
  16. package/action.yml +25 -0
  17. package/dist/action.d.ts +7 -0
  18. package/dist/action.d.ts.map +1 -0
  19. package/dist/action.js +180 -0
  20. package/dist/action.js.map +1 -0
  21. package/dist/cli.d.ts +12 -0
  22. package/dist/cli.d.ts.map +1 -0
  23. package/dist/cli.js +1267 -0
  24. package/dist/cli.js.map +1 -0
  25. package/dist/cli.test.d.ts +2 -0
  26. package/dist/cli.test.d.ts.map +1 -0
  27. package/dist/cli.test.js +650 -0
  28. package/dist/cli.test.js.map +1 -0
  29. package/dist/compile.d.ts +101 -0
  30. package/dist/compile.d.ts.map +1 -0
  31. package/dist/compile.js +503 -0
  32. package/dist/compile.js.map +1 -0
  33. package/dist/evolve.d.ts +132 -0
  34. package/dist/evolve.d.ts.map +1 -0
  35. package/dist/evolve.js +599 -0
  36. package/dist/evolve.js.map +1 -0
  37. package/dist/freshness.d.ts +67 -0
  38. package/dist/freshness.d.ts.map +1 -0
  39. package/dist/freshness.js +244 -0
  40. package/dist/freshness.js.map +1 -0
  41. package/dist/freshness.test.d.ts +2 -0
  42. package/dist/freshness.test.d.ts.map +1 -0
  43. package/dist/freshness.test.js +356 -0
  44. package/dist/freshness.test.js.map +1 -0
  45. package/dist/generate-types.d.ts +34 -0
  46. package/dist/generate-types.d.ts.map +1 -0
  47. package/dist/generate-types.js +381 -0
  48. package/dist/generate-types.js.map +1 -0
  49. package/dist/inline.d.ts +58 -0
  50. package/dist/inline.d.ts.map +1 -0
  51. package/dist/inline.js +142 -0
  52. package/dist/inline.js.map +1 -0
  53. package/dist/inline.test.d.ts +5 -0
  54. package/dist/inline.test.d.ts.map +1 -0
  55. package/dist/inline.test.js +152 -0
  56. package/dist/inline.test.js.map +1 -0
  57. package/dist/linters.d.ts +38 -0
  58. package/dist/linters.d.ts.map +1 -0
  59. package/dist/linters.js +588 -0
  60. package/dist/linters.js.map +1 -0
  61. package/dist/proofs.d.ts +272 -0
  62. package/dist/proofs.d.ts.map +1 -0
  63. package/dist/proofs.js +622 -0
  64. package/dist/proofs.js.map +1 -0
  65. package/dist/proofs.test.d.ts +9 -0
  66. package/dist/proofs.test.d.ts.map +1 -0
  67. package/dist/proofs.test.js +952 -0
  68. package/dist/proofs.test.js.map +1 -0
  69. package/dist/spec.d.ts +258 -0
  70. package/dist/spec.d.ts.map +1 -0
  71. package/dist/spec.js +113 -0
  72. package/dist/spec.js.map +1 -0
  73. package/dist/spec.test.d.ts +2 -0
  74. package/dist/spec.test.d.ts.map +1 -0
  75. package/dist/spec.test.js +1222 -0
  76. package/dist/spec.test.js.map +1 -0
  77. package/dist/types.d.ts +101 -0
  78. package/dist/types.d.ts.map +1 -0
  79. package/dist/types.js +3 -0
  80. package/dist/types.js.map +1 -0
  81. package/dist/validate.d.ts +10 -0
  82. package/dist/validate.d.ts.map +1 -0
  83. package/dist/validate.js +286 -0
  84. package/dist/validate.js.map +1 -0
  85. package/dist/validate.test.d.ts +2 -0
  86. package/dist/validate.test.d.ts.map +1 -0
  87. package/dist/validate.test.js +531 -0
  88. package/dist/validate.test.js.map +1 -0
  89. package/docs/agent-setup.md +85 -0
  90. package/docs/agent-workflows.md +103 -0
  91. package/docs/comparison.md +71 -0
  92. package/docs/freshness.md +124 -0
  93. package/docs/inline-mode.md +119 -0
  94. package/docs/linter-support.md +166 -0
  95. package/docs/spec-format.md +194 -0
  96. package/eslint.config.mjs +79 -0
  97. package/examples/CLAUDE.md +54 -0
  98. package/examples/CLAUDE.md.spec.ts +65 -0
  99. package/examples/SKILL.md +50 -0
  100. package/examples/SKILL.md.spec.ts +57 -0
  101. package/fixtures/example-project/CLAUDE.md +11 -0
  102. package/fixtures/example-project/package.json +9 -0
  103. package/fixtures/example-project/src/index.ts +3 -0
  104. package/fixtures/example-project/src/utils.test.ts +2 -0
  105. package/fixtures/example-project/src/utils.ts +3 -0
  106. package/logo.png +0 -0
  107. package/package.json +42 -0
  108. package/research/adoption-strategy.md +111 -0
  109. package/research/agent-integration.md +145 -0
  110. package/research/ai-code-quality.md +197 -0
  111. package/research/code-search-for-agents.md +313 -0
  112. package/research/competitive-landscape.md +163 -0
  113. package/research/doc-freshness.md +516 -0
  114. package/research/executable-specs.md +368 -0
  115. package/research/feature-ideas.md +464 -0
  116. package/research/formal-proofs-for-agents.md +338 -0
  117. package/research/fp-for-agent-harness.md +150 -0
  118. package/research/fp-for-deterministic-ai.md +131 -0
  119. package/research/self-evolving-specs.md +298 -0
  120. package/schemas/claude-md-strict.yml +18 -0
  121. package/schemas/claude-md.yml +6 -0
  122. package/schemas/skill-strict.yml +12 -0
  123. package/schemas/skill.yml +5 -0
  124. package/skills/audit-feedback-loop/SKILL.md +76 -0
  125. package/skills/edit-spec/SKILL.md +131 -0
  126. package/skills/enforce-rules-format/SKILL.md +71 -0
  127. package/skills/generate-logo/SKILL.md +102 -0
  128. package/skills/generate-rule/SKILL.md +90 -0
  129. package/skills/linter-docs/clippy.md +241 -0
  130. package/skills/linter-docs/eslint.md +384 -0
  131. package/skills/linter-docs/pylint.md +288 -0
  132. package/skills/linter-docs/rubocop.md +277 -0
  133. package/skills/linter-docs/ruff.md +187 -0
  134. package/skills/linter-docs/stylelint.md +247 -0
  135. package/skills/migrate-to-spec/SKILL.md +124 -0
  136. package/skills/pr-to-lint-rule/SKILL.md +97 -0
  137. package/skills/strengthen/SKILL.md +168 -0
  138. package/src/action.ts +214 -0
  139. package/src/cli.test.ts +914 -0
  140. package/src/cli.ts +1631 -0
  141. package/src/compile.ts +691 -0
  142. package/src/evolve.ts +781 -0
  143. package/src/freshness.test.ts +449 -0
  144. package/src/freshness.ts +299 -0
  145. package/src/generate-types.ts +448 -0
  146. package/src/inline.test.ts +206 -0
  147. package/src/inline.ts +164 -0
  148. package/src/linters.ts +739 -0
  149. package/src/proofs.test.ts +1314 -0
  150. package/src/proofs.ts +849 -0
  151. package/src/spec.test.ts +1471 -0
  152. package/src/spec.ts +427 -0
  153. package/src/types.ts +117 -0
  154. package/src/validate.test.ts +701 -0
  155. package/src/validate.ts +381 -0
  156. package/tsconfig.json +23 -0
@@ -0,0 +1,102 @@
1
+ ---
2
+ description: Generate or iterate on the vigiles logo using ImageRouter API
3
+ ---
4
+
5
+ # Generate Logo
6
+
7
+ Generate logo variations for vigiles using the ImageRouter API (imagerouter.io).
8
+
9
+ ## Setup
10
+
11
+ Get an API key from https://imagerouter.io/api-keys. Pass it as an argument or set `IMAGEROUTER_API_KEY` env var. Do NOT commit the key.
12
+
13
+ ## API
14
+
15
+ ```
16
+ Endpoint: https://api.imagerouter.io/v1/openai/images/generations
17
+ Auth: Bearer token in Authorization header
18
+ Method: POST, Content-Type: application/json
19
+ ```
20
+
21
+ ### Request body
22
+
23
+ ```json
24
+ {
25
+ "prompt": "...",
26
+ "model": "google/nano-banana-2",
27
+ "quality": "high",
28
+ "size": "1024x1024",
29
+ "response_format": "url",
30
+ "output_format": "png"
31
+ }
32
+ ```
33
+
34
+ ### Available models (image generation)
35
+
36
+ List models: `GET https://api.imagerouter.io/v1/models`
37
+
38
+ Known good models:
39
+
40
+ - `google/nano-banana-2` — best quality, $0.07/image
41
+ - `google/nano-banana-2:free` — free tier
42
+ - `openai/gpt-image-1` — OpenAI's image model
43
+ - `black-forest-labs/FLUX-1.1-pro` — FLUX pro
44
+
45
+ ### Response
46
+
47
+ ```json
48
+ {
49
+ "created": 1775430873,
50
+ "data": [{ "url": "https://storage.imagerouter.io/..." }],
51
+ "cost": 0.069,
52
+ "latency": 27627
53
+ }
54
+ ```
55
+
56
+ Download the image from the URL in `data[0].url`.
57
+
58
+ ## Current logo
59
+
60
+ The current logo (`logo.png`) is v6: overlapping translucent flame petals on dark background, amber-orange palette. Generated with `google/nano-banana-2`.
61
+
62
+ ### Prompt that produced it
63
+
64
+ ```
65
+ A premium, refined logo icon for a developer tool called vigiles that compiles
66
+ typed TypeScript specs to AI instruction files. Inspired by OpenAI geometric aesthetic and Apple
67
+ minimalism. A single abstract geometric shape: an upward-pointing flame composed
68
+ of 3 overlapping translucent rounded shapes, creating depth through overlap —
69
+ similar to how the OpenAI logo uses overlapping curves. Warm amber to deep orange
70
+ color palette. Black background. No text. No letters. Pure abstract mark. Clean
71
+ enough to be an app icon. Luxurious, premium, modern tech company feel.
72
+ ```
73
+
74
+ ## Design principles
75
+
76
+ - **Flame/torch motif** — vigiles were Rome's night watchmen who carried torches
77
+ - **Amber/orange palette** — matches GitHub Action branding color
78
+ - **No text in the icon** — must work at 16px favicon size
79
+ - **Dark background variant** for README, light/transparent variant for npm
80
+
81
+ ## Example curl
82
+
83
+ ```bash
84
+ curl 'https://api.imagerouter.io/v1/openai/images/generations' \
85
+ -H "Authorization: Bearer $IMAGEROUTER_API_KEY" \
86
+ -H 'Content-Type: application/json' \
87
+ -d '{
88
+ "prompt": "YOUR PROMPT HERE",
89
+ "model": "google/nano-banana-2",
90
+ "quality": "high",
91
+ "size": "1024x1024",
92
+ "response_format": "url",
93
+ "output_format": "png"
94
+ }'
95
+ ```
96
+
97
+ ## Workflow
98
+
99
+ 1. Generate variations with different prompts
100
+ 2. Save as `logo-v*.png` (gitignored)
101
+ 3. Pick the best, copy to `logo.png`
102
+ 4. Commit `logo.png` only
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: generate-rule
3
+ description: Add a new enforce(), check(), or guidance() rule to an existing spec file
4
+ disable-model-invocation: true
5
+ argument-hint: <description of the rule to add>
6
+ ---
7
+
8
+ Add a new rule to an existing `CLAUDE.md.spec.ts` (or create one if it doesn't exist).
9
+
10
+ ## Arguments
11
+
12
+ $ARGUMENTS — A natural language description of what the rule should enforce. Examples:
13
+
14
+ - "no console.log in production code"
15
+ - "every controller needs a test file"
16
+ - "always use the custom logger instead of console"
17
+ - "imports should go through barrel files"
18
+ - "don't use moment.js, we're migrating to dayjs"
19
+
20
+ ## Instructions
21
+
22
+ ### Step 1: Find the Spec File
23
+
24
+ Look for `CLAUDE.md.spec.ts` in the repo root. If it doesn't exist:
25
+
26
+ 1. Check if there's a hand-written `CLAUDE.md` — if so, suggest running the `migrate-to-spec` skill first
27
+ 2. If no CLAUDE.md either, suggest running `npx vigiles init` to scaffold one
28
+
29
+ ### Step 2: Classify the Rule
30
+
31
+ Based on the user's description, determine the rule type:
32
+
33
+ **enforce()** — if a linter rule can back it:
34
+
35
+ 1. Check the project's linter configs (ESLint, Ruff, Clippy, Pylint, RuboCop, Stylelint)
36
+ 2. Search for an existing rule that matches the convention
37
+ 3. Verify the rule is enabled: `npx vigiles check` will confirm during compilation
38
+ 4. Also check if an architectural tool (ast-grep, Dependency Cruiser, Steiger) has a relevant rule
39
+
40
+ **check()** — if it's a filesystem structural convention:
41
+
42
+ - "every X needs a Y" → `check(every("glob").has("pattern"), "why")`
43
+ - Only use for file pairing patterns. Don't try to check code content — that's a linter's job.
44
+
45
+ **guidance()** — if it can't be mechanically enforced:
46
+
47
+ - Subjective conventions ("prefer composition over inheritance")
48
+ - Process rules ("ask before deleting files")
49
+ - Context ("we're migrating from X to Y")
50
+
51
+ If uncertain whether a linter rule exists, **ask the user** rather than guessing.
52
+
53
+ ### Step 3: Generate the Rule
54
+
55
+ Create a rule entry in the spec. Use kebab-case for the rule ID derived from the description.
56
+
57
+ Example outputs:
58
+
59
+ ```typescript
60
+ // enforce — backed by ESLint
61
+ "no-console": enforce("eslint/no-console", "Use structured logger for observability."),
62
+
63
+ // check — filesystem assertion
64
+ "controller-tests": check(
65
+ every("src/**/*.controller.ts").has("{name}.controller.test.ts"),
66
+ "Every controller must have a co-located test file.",
67
+ ),
68
+
69
+ // guidance — cannot be mechanically enforced
70
+ "research-before-implementing": guidance(
71
+ "Google unfamiliar APIs before implementing. Check if a well-maintained library exists.",
72
+ ),
73
+ ```
74
+
75
+ ### Step 4: Add to Spec
76
+
77
+ Read the existing spec file and add the new rule to the `rules` object. Maintain alphabetical ordering if the existing rules are alphabetical, otherwise append at the end.
78
+
79
+ Import any new builders needed (e.g., `check` and `every` if this is the first `check()` rule).
80
+
81
+ ### Step 5: Compile and Verify
82
+
83
+ ```bash
84
+ npm run build
85
+ npx vigiles compile
86
+ ```
87
+
88
+ If compilation fails (e.g., linter rule doesn't exist), report the error and suggest alternatives.
89
+
90
+ Show the user the updated spec and the compiled CLAUDE.md diff.
@@ -0,0 +1,241 @@
1
+ # Clippy — Reference
2
+
3
+ Shared linter reference for vigiles skills. Used by `strengthen` (find existing rules) and `pr-to-lint-rule` (write custom lints).
4
+
5
+ ## Check Existing Lints First
6
+
7
+ Before writing a custom lint, search these lint groups — the pattern may already be covered:
8
+
9
+ | Lint group | Scope | Key lints to know |
10
+ | ------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
11
+ | `correctness` | Code that is outright wrong or will panic | `uninit_assumed_init`, `wrong_self_convention`, `nonsensical_open_options`, `mistyped_literal_suffixes`, `transmuting_null`, `undropped_manually_drops` |
12
+ | `suspicious` | Code that is very likely a bug | `suspicious_else_formatting`, `suspicious_unary_op_formatting`, `suspicious_op_assign_impl`, `blanket_clippy_restriction_lints`, `almost_complete_range` |
13
+ | `style` | Idiomatic Rust style violations | `needless_return`, `redundant_closure`, `match_bool`, `single_match`, `collapsible_if`, `len_zero`, `manual_map`, `if_let_mutex` |
14
+ | `complexity` | Code that can be simplified | `unnecessary_cast`, `needless_borrow`, `redundant_clone`, `type_complexity`, `manual_strip`, `option_map_unit_fn`, `useless_conversion` |
15
+ | `perf` | Code that can be made faster | `large_enum_variant`, `box_collection`, `redundant_allocation`, `manual_memcpy`, `iter_nth`, `unnecessary_to_owned`, `needless_collect` |
16
+ | `pedantic` | Stricter lints, off by default | `unwrap_used`, `expect_used`, `cast_possible_truncation`, `cast_sign_loss`, `missing_errors_doc`, `missing_panics_doc`, `doc_markdown`, `must_use_candidate` |
17
+ | `restriction` | Lints for specific project policies, off by default | `print_stdout`, `print_stderr`, `dbg_macro`, `unimplemented`, `todo`, `unwrap_used`, `expect_used`, `mem_forget`, `shadow_reuse`, `float_arithmetic` |
18
+ | `nursery` | Experimental lints, may have false positives | `missing_const_for_fn`, `cognitive_complexity`, `use_self`, `option_if_let_else`, `redundant_pub_crate`, `significant_drop_tightening` |
19
+ | `cargo` | Cargo manifest issues | `multiple_crate_versions`, `wildcard_dependencies`, `negative_feature_names`, `redundant_feature_names` |
20
+
21
+ **Tip:** Run `cargo clippy --warn clippy::pedantic` on your codebase to see what pedantic catches before manually hunting for rules. The full lint list is at <https://rust-lang.github.io/rust-clippy/stable/>.
22
+
23
+ ## Lint Configuration
24
+
25
+ ### Cargo.toml `[lints.clippy]` section (recommended)
26
+
27
+ The modern way to configure Clippy project-wide. Checked into version control, applies to every `cargo clippy` invocation:
28
+
29
+ ```toml
30
+ # Cargo.toml
31
+ [lints.clippy]
32
+ # Enable entire groups
33
+ pedantic = "warn"
34
+ nursery = "warn"
35
+
36
+ # Override individual lints
37
+ unwrap_used = "deny"
38
+ expect_used = "warn"
39
+ cast_possible_truncation = "allow"
40
+
41
+ # Restriction lints — opt-in individually
42
+ print_stdout = "warn"
43
+ dbg_macro = "deny"
44
+ ```
45
+
46
+ This replaces the older `#![warn(clippy::pedantic)]` crate-level attribute approach. vigiles reads `Cargo.toml` for `enforce()` verification.
47
+
48
+ ### Inline attributes
49
+
50
+ Use `#[allow]`, `#[warn]`, and `#[deny]` for per-item overrides:
51
+
52
+ ```rust
53
+ // Suppress a lint on a single function
54
+ #[allow(clippy::too_many_arguments)]
55
+ fn create_widget(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32, g: u32) { /* ... */ }
56
+
57
+ // Escalate a lint to a hard error for a module
58
+ #[deny(clippy::unwrap_used)]
59
+ mod payment_processing {
60
+ // Any .unwrap() in this module fails compilation
61
+ }
62
+
63
+ // Warn on a lint for a specific impl block
64
+ #[warn(clippy::cast_possible_truncation)]
65
+ impl Converter {
66
+ fn to_u32(&self, val: u64) -> u32 { val as u32 }
67
+ }
68
+ ```
69
+
70
+ Attribute precedence (highest to lowest): `#[forbid]` > `#[deny]` > `#[warn]` > `#[allow]`. `#[forbid]` cannot be overridden by inner attributes — use it for security-critical lints.
71
+
72
+ ### `clippy.toml` / `.clippy.toml`
73
+
74
+ Project-level configuration for lint thresholds and behavior:
75
+
76
+ ```toml
77
+ # clippy.toml
78
+ too-many-arguments-threshold = 10
79
+ type-complexity-threshold = 500
80
+ cognitive-complexity-threshold = 30
81
+ single-char-binding-names-threshold = 4
82
+ msrv = "1.70.0"
83
+
84
+ # Disallow certain types
85
+ disallowed-types = [
86
+ { path = "std::collections::HashMap", reason = "Use indexmap::IndexMap for deterministic iteration" },
87
+ ]
88
+
89
+ # Disallow certain methods
90
+ disallowed-methods = [
91
+ { path = "std::env::var", reason = "Use config::get() for environment access" },
92
+ ]
93
+
94
+ # Disallow certain macros
95
+ disallowed-macros = [
96
+ { path = "std::println", reason = "Use tracing::info! instead" },
97
+ ]
98
+ ```
99
+
100
+ `disallowed-types`, `disallowed-methods`, and `disallowed-macros` are particularly powerful — they let you ban specific APIs project-wide with custom error messages, no custom lint needed.
101
+
102
+ ## Custom Lints
103
+
104
+ ### dylint — custom Clippy-style lints
105
+
106
+ [dylint](https://github.com/trailofbits/dylint) loads lint libraries as dynamic libraries, giving you the same compiler internals that Clippy uses:
107
+
108
+ ```rust
109
+ // my_lint/src/lib.rs
110
+ use clippy_utils::diagnostics::span_lint_and_help;
111
+ use rustc_lint::{LateContext, LateLintPass, LintArray, LintPass};
112
+ use rustc_session::{declare_lint, declare_lint_pass};
113
+
114
+ declare_lint! {
115
+ /// Disallow direct database calls outside the `db` module.
116
+ pub NO_DIRECT_DB_CALL,
117
+ Warn,
118
+ "direct database calls should go through the db module"
119
+ }
120
+
121
+ declare_lint_pass!(NoDirectDbCall => [NO_DIRECT_DB_CALL]);
122
+
123
+ impl<'tcx> LateLintPass<'tcx> for NoDirectDbCall {
124
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'_>) {
125
+ if is_direct_db_call(cx, expr) {
126
+ span_lint_and_help(
127
+ cx,
128
+ NO_DIRECT_DB_CALL,
129
+ expr.span,
130
+ "direct database calls are not allowed here",
131
+ None,
132
+ "use the repository pattern — import from `crate::db` instead",
133
+ );
134
+ }
135
+ }
136
+ }
137
+ ```
138
+
139
+ **When to use dylint vs existing Clippy lints:**
140
+
141
+ | Situation | Use |
142
+ | ----------------------------------------- | ------------------------------------------- |
143
+ | Ban a specific function/type/macro | `clippy.toml` `disallowed-*` — zero code |
144
+ | Enforce naming or style convention | Existing Clippy pedantic/style lints |
145
+ | Detect a pattern needing type information | dylint — access to rustc type checker |
146
+ | Enforce architectural boundaries | dylint or `cargo-deny` for dependency rules |
147
+ | One-off "don't use X" in a single crate | `#[deny(clippy::...)]` attribute |
148
+
149
+ **dylint tradeoffs:**
150
+
151
+ - Tied to a specific Rust nightly version (compiler internals are unstable)
152
+ - Requires maintaining a separate crate with `rustc_private` dependencies
153
+ - `clippy_utils` provides helper functions but its API changes between releases
154
+ - Build times increase since the lint library compiles against `rustc` internals
155
+
156
+ For vigiles, reference dylint lints via: `enforce("clippy/no_direct_db_call", "Use the repository pattern.")` — the lint name maps to the `declare_lint!` identifier (snake_case).
157
+
158
+ ## Edge Cases and Gotchas
159
+
160
+ ### False positives from macros
161
+
162
+ Clippy lints operate on the expanded AST. Macros can trigger false positives because the expanded code looks different from what the author wrote:
163
+
164
+ ```rust
165
+ // This macro expansion may trigger `clippy::redundant_clone`
166
+ // even though the clone is structurally necessary in the macro output
167
+ my_derive_macro! {
168
+ struct Foo { bar: String }
169
+ }
170
+ ```
171
+
172
+ Workarounds:
173
+
174
+ - `#[allow(clippy::...)]` on the macro invocation site
175
+ - Add `#[automatically_derived]` in proc-macro output — Clippy skips many lints for derived code
176
+ - File an issue upstream if the false positive is in a common macro pattern
177
+
178
+ ### Nightly-only lints
179
+
180
+ Some Clippy lints require nightly Rust because they depend on unstable compiler features. The `nursery` group is the most common source. If you enable `nursery` lints in CI:
181
+
182
+ - Pin a specific nightly version: `rust-toolchain.toml` with `channel = "nightly-2025-01-15"`
183
+ - Expect breakage on nightly updates — nursery lints can be renamed, removed, or change behavior
184
+ - Never `#[deny]` nursery lints — use `#[warn]` so they don't block builds when behavior changes
185
+
186
+ ### `allow` vs `warn` on groups
187
+
188
+ Enabling a group and then allowing individual lints works top-down:
189
+
190
+ ```toml
191
+ # Cargo.toml
192
+ [lints.clippy]
193
+ pedantic = "warn" # Enable all pedantic lints
194
+ module_name_repetitions = "allow" # But suppress this one
195
+ ```
196
+
197
+ The reverse does NOT work — you cannot `allow` a group and then `warn` an individual lint from it. The group-level `allow` takes precedence. Always enable groups first, then suppress specific lints.
198
+
199
+ ### Interaction with `#[must_use]`
200
+
201
+ Clippy's `must_use_candidate` lint (pedantic) suggests adding `#[must_use]` to functions that return values. This interacts with other lints:
202
+
203
+ - Once `#[must_use]` is added, callers who ignore the return value get `unused_must_use` (a rustc warning, not Clippy)
204
+ - `let _ = foo()` silences `unused_must_use` but triggers `let_underscore_must_use` (Clippy restriction)
205
+ - `drop(foo())` silences both but may trigger `drop_non_drop` if the type doesn't implement `Drop`
206
+
207
+ Recommendation: Enable `must_use_candidate` globally, but only `#[deny(unused_must_use)]` in modules where ignoring results is dangerous (I/O, error handling).
208
+
209
+ ### Unsafe code linting
210
+
211
+ Clippy provides several lints for `unsafe` code, but they have limitations:
212
+
213
+ - `undocumented_unsafe_blocks` (restriction) — requires a `// SAFETY:` comment above every `unsafe` block. Enable this project-wide.
214
+ - `unsafe_derive_deserialize` (pedantic) — warns when `Deserialize` is derived on types with unsafe invariants
215
+ - `multiple_unsafe_ops_per_block` (restriction) — each `unsafe` block should contain exactly one unsafe operation for precise `// SAFETY:` documentation
216
+
217
+ These lints do NOT verify that safety invariants are actually upheld — they only enforce documentation conventions. Use `cargo miri test` and `#[cfg(miri)]` for runtime verification of unsafe code.
218
+
219
+ ### MSRV-aware lints
220
+
221
+ Clippy respects the `msrv` field in `clippy.toml`. Some lints suggest replacements that require newer Rust versions. If you set `msrv = "1.65.0"`, Clippy won't suggest `let-else` (stabilized in 1.65) but will suppress suggestions for features from 1.66+. Always set this to match your `rust-version` in `Cargo.toml`.
222
+
223
+ ## Mapping PR Feedback to Lint Strategy
224
+
225
+ | PR comment pattern | Best approach |
226
+ | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
227
+ | "Don't use `.unwrap()`" | `enforce("clippy/unwrap_used", "Use expect() with context or propagate with ?.")` — pedantic, enable project-wide |
228
+ | "Don't call this function" | `clippy.toml` `disallowed-methods` — zero custom code, includes custom error message |
229
+ | "Don't use this type" | `clippy.toml` `disallowed-types` — same approach, works for `HashMap`, `Mutex`, etc. |
230
+ | "Don't use `println!`" | `enforce("clippy/print_stdout", "Use tracing macros.")` — restriction lint, opt-in |
231
+ | "Add error context" | Not Clippy — use `#[deny(clippy::unwrap_used)]` + guidance for `anyhow`/`thiserror` patterns |
232
+ | "This clone is unnecessary" | `enforce("clippy/redundant_clone", "Remove the unnecessary clone.")` — already in complexity group |
233
+ | "Use `Self` in impl blocks" | `enforce("clippy/use_self", "Use Self instead of repeating the type name.")` — nursery lint |
234
+ | "Document safety invariants" | `enforce("clippy/undocumented_unsafe_blocks", "Add a // SAFETY: comment.")` — restriction lint |
235
+ | "Too many function parameters" | `enforce("clippy/too_many_arguments", "Refactor into a config struct.")` — already in complexity group |
236
+ | "Wildcard deps in Cargo.toml" | `enforce("clippy/wildcard_dependencies", "Pin dependency versions.")` — cargo group |
237
+ | "Every public fn needs error docs" | `enforce("clippy/missing_errors_doc", "Document the errors this function can return.")` — pedantic |
238
+ | "Avoid `as` casts" | `enforce("clippy/cast_possible_truncation", "Use try_from() or explicit checked conversion.")` — pedantic |
239
+ | "Don't leave `todo!()` in code" | `enforce("clippy/todo", "Replace todo!() with an implementation or file an issue.")` — restriction lint |
240
+ | "Use the builder pattern" | Not Clippy — use vigiles `guidance()` for architectural patterns |
241
+ | "Enforce import boundaries between modules" | Not Clippy — use `cargo-deny` for crate-level boundaries or dylint for module-level enforcement |