leanSpec
CLAUDE.md143 starsChanged 2 months agoMITView on GitHub
This is a Python repository for the Lean Ethereum Python specifications. It is set up as a single uv project containing the main specifications and various cryptographic subspecifications that the Lean Ethereum protocol relies on. - Python 3.12+ required - Use Pydantic models for validation - Keep specs simple, readable, and clear - Repository is leanSpec not lean-spec - Always run linter checks before finishing: Run just check at the end of any code changes to ensure all linting, formatting,…
- Commits and pushes
The file
Show its rules and text
We split this CLAUDE.md into the rules it gives an agent
34 rules. About 0 are common, 23 are distinctive, and 11 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈23
Distinctive · estimate
This author's own choices. Worth a look.
11
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Distinctive: line 18
Use Pydantic models for validation
- Distinctive: line 19
Keep specs simple, readable, and clear
- Very specific: line 21
Always run linter checks before finishing: Run just check at the end of any code changes to ensure all linting, formatting, type checking, …
- Very specific: line 24
NO legacy constants (like KEY_TYPE_ED25519 = KeyType.ED25519)
- Distinctive: line 25
NO wrapper functions that delegate to new classes
- Distinctive: line 26
NO re-exports of deprecated APIs
- Distinctive: line 27
NO deprecation shims or aliases
- Distinctive: line 28
When refactoring from functions to classes, DELETE the old functions entirely
- Distinctive: line 29
Update ALL call sites to use the new API directly
- Very specific: line 45
Use the correct domain term, not just any expansion: a validator is referenced by its INDEX, so validator_id → validator_index (never …
- Very specific: line 47
KEEP canonical protocol identifiers that genuinely use "ID": peer_id, node_id, protocol_id, subnet_id, stream_id.
- Very specific: line 50
KEEP canonical short field/format names and accepted prefixes: attnets and seq (ENR fields), pem, num/num_* (eth2 number-of style, e.g.
- Very specific: line 53
KEEP universal Python idioms and library/stdlib API names verbatim: args, kwargs, config, model_config, tmp_path, dest (argparse), …
- Very specific: line 55
NEVER rename external/wire identifiers: third-party library symbols (e.g.
- Distinctive: line 58
When a fully-expanded name becomes unwieldy, prefer a shorter but still complete phrasing (drop redundant words) rather than re-introducing …
- Very specific: line 75
Never shadow-alias a well-named value into a vague one (data = attestation_data is banned).
- Very specific: line 81
NEVER reuse one vague name for two different things in the same scope.
- Distinctive: line 85
If they would have to scroll up to find out, the name is wrong.
- Distinctive: line 91
When you MOVE a class or function to a different module, MOVE its tests to the matching test module in the SAME change.
- Distinctive: line 93
When you CREATE a new source module, its tests go in the mirrored test path, not appended to an unrelated test file.
- Distinctive: line 95
When you DELETE or RENAME a source module, delete or rename its test module to match.
- Distinctive: line 105
When you add or change any fork behavior — fork choice, state transition, block production, validator duties, aggregation, the containers, …
- Very specific: line 108
Write these with the state_transition, fork_choice, ssz, slot_clock, verify_signatures, and related fixtures in …
- Very specific: line 115
Do NOT use pytest.raises(SomeError, match="fragment") to assert a fragment.
- Distinctive: line 125
When you add, remove, or change a step, assertion, or expected value, reconcile the docstring so it still describes exactly what the test …
- Distinctive: line 129
Do not weaken a docstring into vagueness to avoid updating it.
- Distinctive: line 138
If the tie is incidental, assert the head at a later step where it is unambiguous.
- Distinctive: line 146
No contrastive negation or antithesis: Never use patterns like "It's not about X, it's about Y" or "X, not Y, not Z." State the positive …
- Distinctive: line 150
No em-dashes for subphrases: Never use em-dashes (—) or hyphens (-, --) to set off parenthetical thoughts, interruptions, or subphrases.
- Distinctive: line 154
Keep sentences clean: If a subphrase needs more than two commas to execute, split it into two distinct, clean sentences instead of one …
- Distinctive: line 159
No statement-explanation loops: Do not state a fact or opinion and then spend the next sentence explaining, justifying, or restating it in …
- Distinctive: line 160
Active voice: Write in the active voice.
- Distinctive: line 165
No summary closers: Do not write a concluding summary paragraph or use phrases like "In conclusion," "Ultimately," "In essence," or "At the …
- Distinctive: line 169
Never use the following. Replace them with simple, plain English alternatives.
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Working with leanSpec
Repository Overview
This is a Python repository for the Lean Ethereum Python specifications. It is set up as
a single uv project containing the main specifications and various cryptographic
subspecifications that the Lean Ethereum protocol relies on.
Key Directories
src/lean_spec/- Main specifications for the Lean Ethereum protocoltests/- Specification testsdocs/- MkDocs documentation source
Important Notes
- Python 3.12+ required
- Use Pydantic models for validation
- Keep specs simple, readable, and clear
- Repository is
leanSpecnotlean-spec - Always run linter checks before finishing: Run
just checkat the end of any code changes to ensure all linting, formatting, type checking, and spell checking passes. - Pull requests target the main repo by default: Unless the user explicitly says otherwise, open pull requests against the upstream main repository (
leanEthereum/leanSpec, theupstreamremote, basemain) — NOT against a personal fork. Push the branch to the fork and open a cross-fork PR withgh pr create --repo leanEthereum/leanSpec --base main --head <fork-owner>:<branch>. - CRITICAL - NO BACKWARD COMPATIBILITY: This is a STRICT requirement. NEVER add backward compatibility code under any circumstances. This means:
- NO legacy constants (like
KEY_TYPE_ED25519 = KeyType.ED25519) - NO wrapper functions that delegate to new classes
- NO re-exports of deprecated APIs
- NO deprecation shims or aliases
- When refactoring from functions to classes, DELETE the old functions entirely
- Update ALL call sites to use the new API directly
- Old patterns must be REMOVED, not preserved alongside new ones
- NO legacy constants (like
- CRITICAL - NO ABBREVIATIONS IN IDENTIFIERS: This is a STRICT requirement. Every
identifier — variables, parameters, function and method names, class names, attributes,
and constants — must spell words out in full. A reference spec must be as explicit as
possible; abbreviations make it ambiguous. This applies to source, tests, and the
packages/testing framework.- Expand truncated words. Examples:
att/att_data→attestation/attestation_datamsg→message,sig→signature,sk→secret_key,pk/pubkey→public_keyidx→index,prev→previous,curr→current,agg→aggregateprop→proposal,conn→connection,addr→address,cert→certificateprivkey→private_key,elem→element,buf→buffer,dir→directorylen→length(inside a name, never thelen()builtin),fe→field_elementsexc/e→exception(inexcept X as exc:clauses, useexception; the stdlibexc_infoname is still kept verbatim)
- Use the correct domain term, not just any expansion: a validator is referenced by its
INDEX, so
validator_id→validator_index(nevervalidator_id). - KEEP canonical protocol identifiers that genuinely use "ID":
peer_id,node_id,protocol_id,subnet_id,stream_id. The trailing_idis fine on these; only expand the word part (msg_id→message_id, butpeer_idstays). - KEEP canonical short field/format names and accepted prefixes:
attnetsandseq(ENR fields),pem,num/num_*(eth2 number-of style, e.g.num_validators), and thereqresplibp2p protocol name. - KEEP universal Python idioms and library/stdlib API names verbatim:
args,kwargs,config,model_config,tmp_path,dest(argparse),exc_info,__init__,__repr__. - NEVER rename external/wire identifiers: third-party library symbols (e.g. functions
imported from
lean_multisig_py), protobuf field names, JSON/YAML keys, pydantic aliases, or any on-the-wire string. Rename the Python identifier, never the serialized contract. - When a fully-expanded name becomes unwieldy, prefer a shorter but still complete phrasing (drop redundant words) rather than re-introducing an abbreviation.
- Expand truncated words. Examples:
- CRITICAL - DESCRIPTIVE, SELF-DOCUMENTING NAMES: This is a STRICT requirement, separate from
the no-abbreviations rule above. Every identifier must let the reader understand what it holds by
reading it alone, without scanning the surrounding code. A name that is fully spelled out but
still vague is NOT acceptable. This applies to source, tests, and
packages/.- BAN vague placeholder names that describe nothing:
selected,result,data,value,item,temp,obj,info,payload(when unqualified),current,entry,thing,out,ret,expected,actual,part/parts, single-letter names (except a conventional math indexi/jin a tight numeric loop, or notation mirroring a cited formula). Name the THING, not its role:selected→selected_proofs,result→post_state/merged_signature,current→current_justified_checkpoint. expectedandactualmust name WHAT is expected:expected→expected_public_key_count/expected_state_root/expected_encoding;actual→actual_field_value. This applies everywhere, including next to a test assert.indexalone is banned when what it indexes is not obvious; say what it walks:validator_index,aggregate_index,byte_index,chunk_index.- Never shadow-alias a well-named value into a vague one (
data = attestation_datais banned); use the descriptive name directly, even if lines must wrap. - Encode the type or domain meaning when a bare word is ambiguous. A variable holding a
Checkpointisjustified_checkpoint, notjustified; a bitfield of justified slots isjustified_slots, notslots; a boolean is a predicate phrase (found_new_entries,is_genesis_self_vote), never a noun. - NEVER reuse one vague name for two different things in the same scope. If an inner loop holds
something different from an outer variable of the same name, rename it (e.g. payload proofs vs.
grouped signatures →
proofsandgrouped_signatures). - The bar: a reviewer reading any single line in isolation should know what each name refers to. If they would have to scroll up to find out, the name is wrong.
- BAN vague placeholder names that describe nothing:
- CRITICAL - TEST STRUCTURE MIRRORS SOURCE STRUCTURE: This is a STRICT requirement. The
test tree under
tests/mirrors the source tree undersrc/lean_spec/one-to-one. A source modulesrc/lean_spec/<path>/<name>.pyhas its unit tests intests/<path>/test_<name>.py, and every test file tests the single source module it mirrors.- When you MOVE a class or function to a different module, MOVE its tests to the matching test module in the SAME change. Never leave tests behind in the old location.
- When you CREATE a new source module, its tests go in the mirrored test path, not appended to an unrelated test file.
- When you DELETE or RENAME a source module, delete or rename its test module to match.
- A test file must never test a type that lives in a different source module. For example, tests
for
SlotClock(innode/chain/clock.py) belong intests/node/chain/test_clock.py, never in an unrelated test module. - This mirroring covers non-fork modules only (
node/,spec/crypto/, etc.). The fork specs undersrc/lean_spec/spec/forks/are exempt — see the forks-are-vectors rule below.
- CRITICAL - FORKS ARE TESTED BY VECTORS, NOT PYTESTS: This is a STRICT requirement. The fork
specs under
src/lean_spec/spec/forks/are tested exclusively through consensus test vectors undertests/consensus/(generated withuv run fill), never through pytest unit tests.- There is NO
tests/spec/forks/tree, and you must never create one. - When you add or change any fork behavior — fork choice, state transition, block production, validator duties, aggregation, the containers, slot/interval math, the fork registry or protocol — add or update a consensus test-vector fixture, not a pytest.
- Write these with the
state_transition,fork_choice,ssz,slot_clock,verify_signatures, and related fixtures inpackages/testing/src/consensus_testing/.
- There is NO
- CRITICAL - ASSERT THE COMPLETE ERROR MESSAGE: This is a STRICT requirement. When a test
checks a raised exception, assert the FULL message with string equality, never a substring or
partial regex. A partial match lets the rest of the message drift or regress unnoticed.
- Use
with pytest.raises(SomeError) as exception_info:thenassert str(exception_info.value) == "the entire expected message". - Do NOT use
pytest.raises(SomeError, match="fragment")to assert a fragment. If you usematch=, it must anchor the whole message (match=r"^...full message...$"with regex metacharacters escaped); prefer the explicit full-equality assertion above. - This mirrors the full-equality rule for ordinary assertions: assert the whole object, never a piece of it.
- Use
- CRITICAL - KEEP TEST DOCUMENTATION IN SYNC WITH THE TEST: This is a STRICT requirement. Every
time you change a test, update the documentation that describes it in the SAME change, following
the documentation rules (
.claude/rules/documentation.md, and fortests/consensus/the Given/When/Then standard in that file). A test's docstring is part of the test; a change that leaves the docstring describing the old behavior is incomplete.- When you add, remove, or change a step, assertion, or expected value, reconcile the docstring so it still describes exactly what the test does.
- For
tests/consensus/vectors, the step assertions and the Given/When/Then docstring must stay one-to-one: if an assertion changes, the matching docstring line changes with it. - Do not weaken a docstring into vagueness to avoid updating it; describe the new behavior precisely, as the doc-writer rules require.
- CRITICAL - FORK-CHOICE HEAD ASSERTIONS MUST BE SCHEME-INDEPENDENT: A head assertion that
pins the winner of a root-based tiebreak (
head_slot,head_root_label) is scheme-fragile — roots embed validator keys, so the winner can flip betweentestandprod(shipped in #916, #1181). Assert the invariant, not the winner.- Equal-weight tie →
lexicographic_head_among(highest block root, from the store). - Equal-slot equivocation tie →
canonical_equivocation_head_among(largest attestation-data root, from the store). - If the tie is incidental, assert the head at a later step where it is unambiguous.
- Equal-weight tie →
Writing Style & Structural Constraints for Documentation
Applies to comments in code and in other documentation files. Also, when writing in github issues and PRs.
Sentence Construction (Hard Enforcement)
- No contrastive negation or antithesis. Never use patterns like "It's not about X, it's about Y" or "X, not Y, not Z." State the positive reality directly and cleanly, without defensive framing.
Punctuation & Sentence Structure
- No em-dashes for subphrases. Never use em-dashes (—) or hyphens (-, --) to set off parenthetical thoughts, interruptions, or subphrases.
- Use commas instead. Set off descriptive tangents or subphrases with commas (apposition).
- Bad: "The strategy—though risky—yielded massive results."
- Good: "The strategy, though risky, yielded massive results."
- Keep sentences clean. If a subphrase needs more than two commas to execute, split it into two distinct, clean sentences instead of one complex sentence.
Structural Rhythm & Voice
- Vary sentence length. Alternate between short, punchy sentences (under 5 words) and longer, flowing ones. Never write three sentences of similar length in a row.
- No statement-explanation loops. Do not state a fact or opinion and then spend the next sentence explaining, justifying, or restating it in other words. Every sentence must introduce new information or advance the point.
- Active voice. Write in the active voice. Avoid clinical, detached, or overly academic prose.
Banned Formatting & Bookends
- No filler openers. Skip introductions like "Sure, here is..." or "Let's dive in." Start directly with the first relevant sentence.
- No summary closers. Do not write a concluding summary paragraph or use phrases like "In conclusion," "Ultimately," "In essence," or "At the end of the day." Stop writing when the content ends.
Banned AI Vocabulary
Never use the following. Replace them with simple, plain English alternatives.
- Verbs / transitions: delve, utilize, leverage, facilitate, maximize, embrace, foster, emphasize, furthermore, moreover, additionally.
- Nouns / metaphors: tapestry, landscape, realm, arena, symphony, testament, beacon, journey, roadmap, game-changer, paradigm shift.
- Adjectives: robust, seamless, cutting-edge, innovative, multifaceted, crucial, pivotal, deep dive.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

