ajj 0.7.1

Simple, modern, ergonomic JSON-RPC 2.0 router built with tower and axum
Documentation
# ajj Development Guide

AI agents: read `.claude/skills/ajj-guide.md` for a comprehensive crate
overview before making changes.

## Crate Summary

`ajj` is a JSON-RPC 2.0 router library. Single crate (not a workspace). Core
pipeline: Handler -> Method -> Router -> Server. Feature-gated for axum, ws,
and ipc transports. Built on tower and serde_json.

## Commands

- `cargo +nightly fmt`
- `cargo clippy -p ajj --all-features --all-targets`
- `cargo clippy -p ajj --no-default-features --all-targets`
- `cargo t -p ajj`

Always lint with both `--all-features` and `--no-default-features` before
committing. Never use `cargo check` or `cargo build`.

### Pre-push Checks (enforced by Claude hook)

A Claude hook in `.claude/settings.json` runs `.claude/hooks/pre-push.sh`
before every `git push`. The push is blocked if any check fails. The checks:

- `cargo +nightly fmt -- --check`
- `cargo clippy -p ajj --all-targets --all-features -- -D warnings`
- `cargo clippy -p ajj --all-targets --no-default-features -- -D warnings`
- `RUSTDOCFLAGS="-D warnings" cargo doc -p ajj --no-deps`

Clippy and doc warnings are hard failures.

## Repo Conventions

- Feature gates: `axum`, `pubsub`, `ws`, `ipc`. Default: `axum`, `ws`, `ipc`.
  `ws` and `ipc` both imply `pubsub`.
- `Router<S>` wraps `Arc<RouterInner<S>>`. `S` is "missing" state, not
  current state. After `with_state()`, `S` becomes the next missing type.
- Handlers are type-erased via `ErasedIntoRoute` and stored in
  `BTreeMap<MethodId, Method<S>>`.
- Internal macros `tap_inner!`, `map_inner!`, `panic_on_err!`,
  `impl_handler_call!` are used extensively in router and handler code.
- OpenTelemetry spans follow OTEL semantic conventions for JSON-RPC.
- Code provenance: some code adapted from axum and alloy. Files containing
  adapted code have license notices at the bottom.

## init4 Organization Style (summary of ../CLAUDE.md)

### Research

- Prefer building crate docs (`cargo doc`) and reading them over grepping.

### Code Style

- Functional combinators over imperative control flow. No unnecessary nesting.
- Terse Option/Result handling: `option.map(Thing::do_something)` or
  `let Some(a) = option else { return; };`.
- Small, focused functions and types.
- Never add incomplete code. No `TODO`s for core logic.
- Never use glob imports. Group imports from the same crate. No blank lines
  between imports.
- Visibility: private by default, `pub(crate)` for internal, `pub` for API.
  Never use `pub(super)`.

### Error Handling

- `thiserror` for library errors. Never `anyhow`. Never `eyre` in libraries.
- Propagate with `?` and `map_err`.

### Tracing

- Use `tracing` crate. Instrument work items, not long-lived tasks.
- `skip(self)` when instrumenting methods. Add only needed fields.
- Levels: TRACE (rare, verbose), DEBUG (sparingly), INFO (default),
  WARN (potential issues), ERROR (prevents operation).
- Propagate spans through task boundaries with `Instrument`.

### Async

- Tokio runtime conventions. No blocking in async functions.
- Long-lived tasks: return a spawnable future, don't run directly.
- Short-lived spawned tasks: consider span propagation.

### Testing

- Tests panic, never return `Result`. Use `unwrap()` directly.
- Unit tests in `mod tests` at file bottom. Integration tests in `tests/`.

### Rustdoc

- Doc all public items. Include usage examples in rustdoc.
- Hide scaffolding with `#`. Keep examples concise.
- Traits must include an implementation guide.

### GitHub

- Fresh branches off `main` for PRs. Descriptive branch names.
- AI-authored GitHub comments must include `**[Claude Code]**` header.