CLAUDE.md
February 18, 2026 · View on GitHub
Project Overview
Lambda Compiler Kit (LCK) — Lean 4 libraries for verified compiler infrastructure. Lean v4.26.0, Lake, Mathlib v4.26.0. See README.md for usage, examples, and project structure.
Project Structure
Lck.lean -- imports all library modules
Lck/
Basic.lean -- shared utilities
Regex/ -- regex engine (Thompson NFA)
Regex.lean -- regex module imports
cli/ -- CLI tools (lck-grep)
test/ -- LSpec test suites
lakefile.toml -- Lake config (deps: mathlib, LSpec)
New libraries: create Lck/<LibName>/, add Lck/<LibName>.lean, import in Lck.lean.
Development Notes
- New modules go in
Lck/and must be imported inLck.lean - First build fetches Mathlib (~5-10 min).
lean-toolchainpins the exact Lean version. - Build:
lake build| Test:lake build test && lake exe test| Lint:lake lint - Lint single file:
lake lint Lck.Regex.Parser - Build with warnings-as-errors:
lake build --wfail - Update dependencies:
lake update| Clean build artifacts:lake clean - Simple tests:
example(see Assertions below). Complex/property-based: LSpec intest/TestLib/.
Verification Work
Proof Discipline
A theorem is either proven or NOT proven. No middle ground.
Completion criteria: zero sorries in proof body AND all dependencies, no non-standard axioms.
Language rules:
- Say "draft structure" or "proof in progress at line X" for incomplete work
- Never say "proven (modulo sorries)" or "structure is in place"
Assertions: example > #check > #eval
example ... := by decide— preferred. Kernel-verified, proves the value is correct at compile time.example ... := by native_decide— fallback whendecidetimes out (e.g., NFA compilation). Only inexamplestatements, never in theorems. Requiresset_option linter.style.nativeDecide false.#check (expr : Bool)— only type-checks, does NOT verify the value. Avoid in library/spec files.#eval— runtime evaluation, not a proof. Use only for debugging/exploration.
Proof Workflow
To see goals, the sorry must be removed first (Lean only warns on sorry, doesn't show goals). Loop: remove sorry → check goal via MCP → apply tactic → repeat until no sorries remain.
Work depth-first (one proof at a time), bottom-up (leaf lemmas first). Strategies: direct proof, contradiction, induction, case analysis, restructure statement. Escalation: 3+ approaches → restructure → search mathlib → ASK user with options.
Termination — Total Functions Only
Never use partial, fuel parameters, or bound counters. Design code so Lean's termination checker accepts it automatically — the code should speak for itself.
Structural recursion is plan A. Techniques:
- Structural recursion on inductives (lists, trees) — always accepted automatically
- CPS / continuation-passing — thread a structurally smaller input through each recursive call
- Subslice patterns — recursive calls on a strict subslice of the input (e.g.,
input[i+1:])
-- Good: structural recursion on remaining input, zero tactics needed
def parseDigits (input : List Char) : List Char × List Char :=
match input with
| c :: rest =>
if c.isDigit then
let (digits, remaining) := parseDigits rest
(c :: digits, remaining)
else ([], c :: rest)
| [] => ([], [])
Separate tokenization from parsing. Tokenizers consume one character at a time unconditionally (structurally decreasing input). Parsers are typically LL(1) — consume one token, peek at most one ahead — so token lists decrease structurally. This separation makes termination obvious and correctness proofs modular.
decreasing_by is plan B — a code smell. It means the code doesn't make its termination obvious. If unavoidable, the proof should be short (a lemma + omega) and accompanied by a comment explaining why structural recursion isn't possible. For parsers especially — a well-understood discipline and a security boundary — there's strong motive to be strict and good reason to believe structural termination is achievable.
Smells that signal architecture problems:
decreasing_bywith multi-step advancing lemmas → restructure to make progress obvious- Auxiliary lemmas about "what variables mean" → the types don't carry enough information
partialas a "temporary" measure → it never gets removed