skate/CLAUDE.md
Drew Galbraith 3fd448d431 Add modal editing to the agent TUI. (#2)
Adds a status line indicating which mode the user is in.
Adds a "normal" mode with keyboard shortcuts (including a chorded shortcut 'gg').
Adds a command mode with several basic commands that can be entered into an overlay.

Chores:
- Cleans up design/claude/plan.md to avoid confusing claude.
- Adds some TODOs based on claude feedback.`

Reviewed-on: #2
Co-authored-by: Drew Galbraith <drew@tiramisu.one>
Co-committed-by: Drew Galbraith <drew@tiramisu.one>
2026-02-25 01:16:16 +00:00

4.2 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.

Use only characters available on a standard US QWERTY keyboard in all doc comments and inline comments. Specifically:

  • Use -> and <- instead of Unicode arrow glyphs
  • Use -- instead of em dashes or en dashes
  • Use +, -, | for ASCII box diagrams instead of Unicode box-drawing characters
  • Use ... instead of the ellipsis character
  • Spell out "Section N.N" instead of the section-sign glyph

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
  • 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