Loading...
Loading...
Writes and reviews structured Lean 4 proofs and designs Lean libraries following Mathlib conventions. Use when proving theorems in Lean, formalizing mathematics or specifications in Lean 4, defining new types or definitions in a Lean library, reviewing Lean proofs for readability and maintainability, refactoring long tactic proofs into lemmas, filling in sorry placeholders in a Lean development, setting up CI or linters for a Lean project, diagnosing slow proofs or maxHeartbeats timeouts, or writing custom tactics, macros, or linters.
npx skill4agent add trailofbits/skills writing-lean-proofssorrymaxHeartbeatsge_or_gtdiscrete_fieldOption(0 : ℝ)⁻¹ = 0FunLikeSetLikeIsHomext@[simp]unfoldshow ... from rfl:= sorrysorryhavesufficescalcsorryexample (a b c d : ℝ) (h : c = d * a + b) (h' : b = a * d) : c = 2 * a * d := by
calc
c = d * a + b := sorry
_ = d * a + a * d := sorry
_ = 2 * a * d := sorry·multiGoalshowshowchangecalchavesufficestrace_statedonelake env lean Path/To/File.leanlake buildsorry#print axioms myTheoremLean.collectAxioms[propext, Classical.choice, Quot.sound]sorrynative_decidelinter.mathlibStandardSetlinter.auxLemmalinter.style.maxHeartbeatslinter.style.multiGoallinter.style.setOptionlinter.style.showlinter.flexiblelinter.style.missingEndlinter.style.openClassicalunused*InTypenativeDecide#lintsimpNFweak.@[env_linter]exampleexact?apply?havetheorem min_comm (a b : ℝ) : min a b = min b a := by
have h : ∀ x y : ℝ, min x y ≤ min y x := by
intro x y
apply le_min
· show min x y ≤ y
exact min_le_right x y
· show min x y ≤ x
exact min_le_left x y
apply le_antisymm
· show min a b ≤ min b a
exact h a b
· show min b a ≤ min a b
exact h b a| Rule | Why | Enforced by |
|---|---|---|
Never unfold definitions downstream; | API lemmas are the abstraction boundary | review ("missing API" smell) |
Terminal | squeezed terminal calls bury the key lemmas and break on renames | style guide |
One focused goal at a time ( | kills goal-ordering fragility | |
| stated goals stay honest | |
No | debugging scaffolding | |
State lemmas in simp-normal form, | simp matches syntactically | |
| Golf only when the result is at least as readable; trivial results exempt | short ≠ better | review |
| global instances degrade all typeclass search | review |
| Name lemmas from their statements (see naming reference) | names become guessable without search | |
| Search a bare goal by shape before writing a helper or claiming an API gap | names are not always guessable from the target | |
| Generally one tactic invocation per line; a one-line closing proof is the exception | preserves readable proof structure without inventing an absolute rule | style guide |
Gate | grep matches comments, misses unproved helpers | axiom audit in CI |
| Prefer simp-lemma LHSs keyed on structure, not numerals; one spelling per constant | | |
Re-derive every | lists do not transfer between look-alike goals | |
Every | copy-pasted budgets carry no information | |
Conditional simp lemma fires shallow but not deep → raise | chained side conditions truncate silently, no diagnostic | diagnosis (proof-style, simp discipline) |
| Every project-specific convention gets a custom linter, in CI from day one | review misses the 29-of-30 failure mode | |
| Excuse | Reality |
|---|---|
| "The proof compiles, ship it" | Compiling is the floor. A monolithic tactic block that only Lean can read will break silently at the next Mathlib bump and no one will be able to repair it. |
| "Unfolding the definition is simpler than writing API lemmas" | Every downstream |
| "Squeezing every simp makes the proof faster and more robust" | Backwards for terminal simp calls: the squeezed list breaks on every rename and drowns the signal. Squeeze non-terminal calls only. |
| "It's shorter, therefore better" | Mathlib review policy: golfing is fine only when it does not sacrifice readability. Length is not the target; legibility is. |
| "I'll restructure it into lemmas after it works" | After it works, the structure is load-bearing and tangled. State the skeleton first; the lemmas fall out for free. |
"Adding | They are redundant to the kernel and essential to every human or model that reads the proof next. |
| "This helper is too specific to be a lemma" | If it has a clean statement, extract it — dropping the hypotheses it doesn't need usually reveals it was general all along. |
| "We'll add linters once the library stabilizes" | Backwards: patterns propagate by copy-paste, so a deferred linter meets a 400-warning backlog instead of one bad line. Enable what is already clean and gate it now. |
| "The check passed, so we're clean" | A check that can't fail proves nothing — sweeps reach zero files, misspelled |
| "The proof is slow, raise maxHeartbeats" | An unmeasured budget is a claim, not a fix — and it masks the regression the next reader needs to see. Measure with |