11# AGENTS.md
22
3+ ## Critical rules
4+
5+ - ** Target branch:** ` unstable ` (never ` stable ` )
6+ - ** Pre-push:** run ` pnpm lint ` , ` pnpm check-types ` , ` pnpm test:unit ` before every push
7+ - ** Relative imports:** use ` .js ` extension in TypeScript ESM imports
8+ - ** No ` any ` :** avoid ` any ` / ` as any ` ; use proper types or justified ` biome-ignore `
9+ - ** No ` lib/ ` edits:** never edit ` packages/*/lib/ ` — these are build outputs
10+ - ** Follow existing patterns** before introducing new abstractions
11+ - ** Structured logging** with specific error codes (not generic ` Error ` )
12+ - ** Incremental commits** after review starts — do not force push unless maintainer requests it
13+
314## Project overview
415
516Lodestar is a TypeScript implementation of the Ethereum consensus client
@@ -103,8 +114,12 @@ Lodestar uses [Biome](https://biomejs.dev/) for linting and formatting.
103114- ** Naming** : ` camelCase ` for functions/variables, ` PascalCase ` for classes,
104115 ` UPPER_SNAKE_CASE ` for constants
105116- ** Quotes** : Use double quotes (` " ` ) not single quotes
106- - ** Types** : All functions must have explicit parameter and return types
107- - ** No ` any ` ** : Avoid TypeScript ` any ` type
117+ - ** Types** : Prefer explicit types on public APIs and complex functions
118+ - ** No ` any ` or ` as any ` ** : Do not use ` any ` type or ` as any ` assertions to bypass
119+ the type system. In production code, find the proper type or interface. In test code,
120+ use public APIs rather than accessing private fields via ` as any ` . If genuinely
121+ unavoidable, add a suppression with the full rule ID and justification:
122+ ` // biome-ignore lint/suspicious/noExplicitAny: <reason> `
108123- ** Private fields** : No underscore prefix (use ` private dirty ` , not ` private _dirty ` )
109124- ** Named exports only** : No default exports
110125
@@ -117,10 +132,17 @@ Imports are auto-sorted by Biome in this order:
1171323 . ` @chainsafe/* ` and ` @lodestar/* ` packages
1181334 . Relative paths
119134
120- Always use ` .js ` extension for relative imports (even for ` .ts ` files):
135+ In TypeScript source and test files, use ` .js ` extension for relative ESM imports
136+ (even though source files are ` .ts ` ). This is required for Node.js ESM resolution.
137+ This rule does ** not** apply to non-TS files (e.g., ` package.json ` , ` .mjs ` config).
121138
122139``` typescript
140+ // ✅ Correct
123141import {something } from " ./utils.js" ;
142+ import {IBeaconStateView } from " ../stateView/interface.js" ;
143+
144+ // ❌ Wrong — will break at runtime
145+ import {something } from " ./utils.ts" ;
124146```
125147
126148### Comments
@@ -258,25 +280,8 @@ for (const block of blocks) {
258280
259281### Running specific tests
260282
261- Use vitest project filters for targeted test runs:
262-
263- ``` bash
264- # Unit tests only (from repo root)
265- pnpm vitest run --project unit test/unit/chain/validation/block.test.ts
266-
267- # With pattern matching
268- pnpm vitest run --project unit -t " should reject"
269-
270- # From package directory (no project filter needed)
271- cd packages/beacon-node
272- pnpm vitest run test/unit/chain/validation/block.test.ts
273- ```
274-
275- For spec tests with minimal preset (faster):
276-
277- ``` bash
278- LODESTAR_PRESET=minimal pnpm vitest run --config vitest.spec.config.ts
279- ```
283+ See ** Build commands** above for all test invocations. Use ` --project unit `
284+ for targeted runs and ` LODESTAR_PRESET=minimal ` for faster spec tests.
280285
281286## Pull request guidelines
282287
@@ -321,11 +326,20 @@ refactor(reqresp)!: support byte based handlers
321326### PR etiquette
322327
323328- Keep PRs as drafts until ready for review
324- - Don't force push after review starts (use incremental commits)
325- - Close stale PRs rather than letting them sit
329+ - Avoid force push after review starts unless a maintainer requests it (use incremental commits)
330+ - Flag stale PRs to maintainers rather than letting them sit indefinitely
326331- Respond to review feedback promptly — reply to every comment, including bot reviewers
327332- When updating based on feedback, respond in-thread to acknowledge
328333
334+ ## Pre-push checklist
335+
336+ Before pushing any commit, verify:
337+
338+ 1 . ` pnpm lint ` — Biome enforces formatting; CI catches failures but wastes a round-trip
339+ 2 . ` pnpm check-types ` — catch type errors before CI
340+ 3 . ` pnpm docs:lint ` — if you edited any ` .md ` files, check Prettier formatting
341+ 4 . No edits in ` packages/*/lib/ ` — these are build outputs; edit ` src/ ` instead
342+
329343## Common tasks
330344
331345### Adding a new feature
@@ -341,7 +355,7 @@ refactor(reqresp)!: support byte based handlers
3413551 . Write a failing test that reproduces the bug
3423562 . Fix the bug
3433573 . Verify the test passes
344- 4 . Run full test suite: ` pnpm test:unit `
358+ 4 . Run checks: ` pnpm lint ` , ` pnpm check-types ` , ` pnpm test:unit `
345359
346360### Adding a new SSZ type
347361
@@ -360,47 +374,10 @@ refactor(reqresp)!: support byte based handlers
360374
361375## Style learnings from reviews
362376
363- ### Prefer inline logic over helper functions
364-
365- For simple validation logic, inline the check rather than creating a helper:
366-
367- ``` typescript
368- // Preferred
369- if (error .code === RegenErrorCode .BLOCK_NOT_IN_FORKCHOICE ) {
370- return GossipAction .REJECT ;
371- }
372-
373- // Avoid (unless logic is complex and reused)
374- function shouldReject(error : Error ): boolean {
375- return error .code === RegenErrorCode .BLOCK_NOT_IN_FORKCHOICE ;
376- }
377- ```
378-
379- ### Match existing comment style
380-
381- When adding comments to containers or functions modified across forks,
382- follow the existing style in that file. Don't add unnecessary markers.
383-
384- ### Error handling patterns
385-
386- Use specific error codes when available:
387-
388- ``` typescript
389- // Preferred
390- throw new BlockError (block , {code: BlockErrorCode .PARENT_UNKNOWN });
391-
392- // Avoid generic errors when specific ones exist
393- throw new Error (" Parent not found" );
394- ```
395-
396- ### Config value coercion
397-
398- When reading optional config values, handle undefined explicitly:
399-
400- ``` typescript
401- const peers = config .directPeers ?? [];
402- const trimmed = value ?.trim () ?? " " ;
403- ```
377+ - ** Prefer inline logic** over single-use helper functions for simple checks
378+ - ** Match existing patterns** in the file you're modifying (comments, structure)
379+ - ** Use specific error codes** (` BlockErrorCode.PARENT_UNKNOWN ` ) over generic ` Error `
380+ - ** Handle undefined** explicitly: ` config.directPeers ?? [] ` , ` value?.trim() ?? "" `
404381
405382## Implementing consensus specs
406383
@@ -422,64 +399,13 @@ When implementing changes from the consensus specs, the mapping is typically:
422399
423400### Fork organization
424401
425- Specs and code are organized by fork: ` phase0 ` , ` altair ` , ` bellatrix ` ,
426- ` capella ` , ` deneb ` , ` electra ` , ` fulu ` , ` gloas ` .
402+ Forks follow the progression defined in ** Architecture patterns > Fork-aware code** above.
427403
428404- ** @lodestar/types /src/** - Each fork has its own directory with SSZ type definitions
429405- ** @lodestar/state-transition /src/block/** - Block processing functions
430406 (e.g., ` processAttestations ` , ` processDeposit ` , ` processWithdrawals ` )
431407- ** @lodestar/state-transition /src/epoch/** - Epoch processing functions
432408- ** @lodestar/state-transition /src/slot/** - Slot processing functions
433409
434- ## Important notes
435-
436- ### Default branch is ` unstable `
437-
438- All PRs should target ` unstable ` . The ` stable ` branch is for releases only
439- (see RELEASE.md for details).
440-
441- ### Spec tests require download
442-
443- Before running ` pnpm test:spec ` , download test vectors:
444-
445- ``` bash
446- pnpm download-spec-tests
447- ```
448-
449- ### E2E tests require Docker
450-
451- Start the e2e environment before running e2e tests:
452-
453- ``` bash
454- ./scripts/run_e2e_env.sh start
455- pnpm test:e2e
456- ./scripts/run_e2e_env.sh stop
457- ```
458-
459- ### Generated files
460-
461- Do not edit files in ` packages/*/lib/ ` - these are build outputs.
462- Edit source files in ` packages/*/src/ ` instead.
463-
464- ### Consensus spec references
465-
466410The ` specrefs/ ` directory contains pinned consensus spec versions.
467411When implementing spec changes, reference the exact spec version.
468-
469- ## Common pitfalls
470-
471- - ** Forgetting ` pnpm lint ` before pushing** : Biome enforces formatting. Always
472- run it before committing. CI will catch it, but it wastes a round-trip.
473- - ** Forgetting ` pnpm docs:lint ` after editing docs** : Markdown files are
474- formatted by Prettier. Run ` pnpm docs:lint ` (or ` pnpm docs:lint:fix ` to
475- auto-fix) before pushing changes to ` .md ` files.
476- - ** Editing ` lib/ ` instead of ` src/ ` ** : Files in ` packages/*/lib/ ` are build
477- outputs. Always edit in ` packages/*/src/ ` .
478- - ** Stale fork choice head** : After modifying proto-array execution status,
479- the cached head from ` getHead() ` is stale. Call ` recomputeForkChoiceHead() ` .
480- - ** Holding state references** : Beacon state objects are large. Don't store
481- references beyond their immediate use — let them be garbage collected.
482- - ** Missing ` .js ` extension** : Relative imports must use ` .js ` even though
483- source files are ` .ts ` . This is required for Node.js ESM resolution.
484- - ** Force pushing after review** : Never force push once a reviewer has started.
485- Use incremental commits — reviewers track changes between reviews.
0 commit comments