skate/CLAUDE.md
2026-02-23 23:16:03 -08:00

3.9 KiB

CLAUDE.md

Rust TUI coding agent. Ratatui + Crossterm + Tokio. See DESIGN.md for architecture decisions and PLAN.md for implementation phases.

Commands

  • cargo build: Build the project
  • cargo test: Run all unit and integration tests
  • cargo test --lib: Unit tests only
  • cargo test --test '*': Integration tests only
  • cargo clippy -- -D warnings: Lint (must pass with zero warnings)
  • cargo fmt --check: Format check
  • cargo run -- --project-dir <path>: Run against a project directory

Architecture

Six modules with strict boundaries:

  • src/app/ — Wiring, lifecycle, tokio runtime setup
  • src/tui/ — Ratatui rendering, input handling, vim modes. Communicates with core ONLY via channels (UserAction → core, UIEvent ← core). Never touches conversation state directly.
  • src/core/ — Conversation tree, orchestrator loop, sub-agent lifecycle
  • src/provider/ModelProvider trait + Claude implementation. Leaf module, no internal dependencies.
  • src/tools/Tool trait, registry, built-in tools. Depends only on sandbox.
  • src/sandbox/ — Landlock policy, path validation, command execution. Leaf module.
  • src/session/ — JSONL logging, session read/write. Leaf module.

The channel boundary between tui and core is critical — never bypass it. The TUI is a frontend; core is the engine. This separation enables headless mode for benchmarking.

Code Style

  • Use thiserror for error types, not anyhow in library code (anyhow only in main.rs/app)
  • Prefer impl Trait return types over boxing when possible
  • All public types need doc comments
  • No unwrap() in non-test code — use ? or explicit error handling
  • Async functions should be cancel-safe where possible
  • Use tracing for structured logging, not println! or log

Documentation

Prefer a literate style: doc comments should explain why and how, not just restate the signature.

When a function or type implements an external protocol or spec:

  • Document the relevant portion of the protocol inline (packet shapes, event sequences, state machines)
  • Link to the authoritative external source — API reference, RFC, WHATWG spec, etc.
  • Include a mapping table or lifecycle diagram when there are multiple cases to distinguish

For example, run_stream in src/provider/claude.rs documents the full SSE event sequence in a text diagram and links to both the Anthropic streaming reference and the WHATWG SSE spec. Aim for that level of context in any code that speaks a wire format or external API.

Conversation Data Model

Events use parent IDs forming a tree (not a flat list). This enables future branching. Every event has: id, parent_id, timestamp, event_type, token_usage. A "turn" is all events between two user messages — this is the unit for token tracking.

Testing

  • Unit tests go in the same file as the code (#[cfg(test)] mod tests)
  • Integration tests go in tests/
  • TUI widget tests use ratatui::backend::TestBackend + insta snapshots
  • Provider tests replay recorded SSE fixtures from tests/fixtures/
  • Sandbox tests use tempdir and skip Landlock-specific assertions if kernel < 5.13
  • Run cargo test before every commit

Key Constraints

  • All file I/O and process spawning in tools MUST go through Sandbox — never use std::fs or std::process::Command directly in tool implementations
  • The ModelProvider trait must remain provider-agnostic — no Claude-specific types in the trait interface
  • Session JSONL is append-only. Never rewrite history. Branching works by writing new events with different parent IDs.
  • Token usage must be tracked per-event and aggregatable per-turn

Do Not

  • Add MCP support (deferred, but keep tool trait compatible)
  • Use unsafe without discussion
  • Add dependencies without checking if an existing dep covers the use case
  • Modify test fixtures without re-recording from a real API session