# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Swival is a CLI coding agent built in Python 3.13+ that works with any LLM supporting tool calling. It connects to LM Studio, HuggingFace, OpenRouter, ChatGPT Plus/Pro, or any OpenAI-compatible server.

- Single Python package (`swival/`) with CLI entry point at `swival.agent:main`
- Public API: `swival.Session` (programmatic embedding), `swival.run()` (one-call convenience)
- Packaging: uv + hatchling

## Build & Test

```bash
make install    # uv sync
make test       # uv run python -m pytest tests/ -v
make lint       # uv run ruff check swival/ tests/
make format     # uv run ruff format swival/ tests/
make check      # lint + format check (CI gate)
```

Run a single test:
```bash
uv run python -m pytest tests/test_edit.py -v
uv run python -m pytest tests/test_edit.py::TestExactMatch::test_simple_replacement -v
```

Use `uv run` to execute any Python code.

## Architecture

| Module | Purpose |
|--------|---------|
| `agent.py` | Main event loop, provider resolution, system prompt building, CLI argument parsing |
| `tools.py` | Tool definitions (12+) and `dispatch()` routing with path security (`_safe_*_path()`) |
| `session.py` | Public API: `Session` class, `Result` dataclass |
| `config.py` | 3-level TOML config cascade: CLI > project `swival.toml` > global `~/.config/swival/config.toml` |
| `snapshot.py` | Context checkpoints that survive compaction |
| `thinking.py` | Structured thinking with numbered thoughts, revisions, branches |
| `todo.py` | Task tracking in `.swival/todo.md` (reset each run, survives compaction) |
| `skills.py` | SKILL.md discovery and validation (`.swival/skills/` or `--skills-dir`) |
| `mcp_client.py` | Model Context Protocol integration; tools exposed as `mcp__<server>__<tool>` |
| `edit.py` | String replacement engine with fuzzy matching (exact, line-trimmed, Unicode-normalized) |
| `fmt.py` | Rich-based ANSI formatted stderr output |
| `continue_here.py` | Session state persistence for interruption recovery |
| `sandbox_agentfs.py` | AgentFS copy-on-write filesystem sandboxing |
| `report.py` | JSON benchmarking reports; defines `AgentError` base exception |
| `reviewer.py` | External reviewer script support for automated QA |
| `tracker.py` | File access tracking for read-guard enforcement |
| `cache.py` | Optional SQLite LLM response cache (`--cache`); `LLMCache` class, `open_cache()`, `_reconstruct_message()` |

### Context Management

Graduated compaction on context overflow: `compact_messages` → `drop_middle_turns` → `aggressive_drop_turns`. The `snapshot` tool allows explicit checkpoints with summaries that survive compaction. `continue_here` saves state on interruption (Ctrl+C, max turns, overflow) to `.swival/continue.md`.

### Providers

All providers resolve via `resolve_provider()` in `agent.py` to `(model_id, api_base, api_key, context_length, llm_kwargs)`. Supported: `lmstudio` (default, auto-discovery), `huggingface`, `openrouter`, `chatgpt`, `generic`.

## Conventions

From AGENTS.md:

- **Exceptions**: `AgentError` (base), `ConfigError(AgentError)`, `ContextOverflowError`
- **Exit codes**: 0 (success), 1 (failure), 2 (exhausted), 130 (Ctrl+C); reviewer: 0 (ACCEPT), 1 (RETRY), 2 (error)
- **Result dataclass**: `answer` (str|None), `exhausted` (bool), `messages` (list), `report` (dict|None)
- **Tools**: `dispatch()` returns string or `"error: <desc>"`; security via `_safe_*_path()`
- **READ_ONLY_TOOLS**: read_file, read_multiple_files, list_files, grep, fetch_url, think, todo, snapshot
- **Session.run()**: fresh state each call. **Session.ask()**: shares state (REPL mode)
- **Hidden state** in `.swival/`: HISTORY.md, MEMORY.md, todo.md, continue.md, cache.db, trash/
- **Skills**: SKILL.md files with YAML frontmatter; names must be lowercase alphanumeric + hyphens matching directory name; user mention via `$skill-name`
- **MCP servers**: names match `^[a-z][a-z0-9-]*$`; config via `[mcp_servers.<name>]` in swival.toml
- **Test classes**: `Test<Feature>`; methods: `test_<what>` (pytest)
- **Env vars in tests**: Save and restore original values — tests run in parallel in the same process, unrestored vars cause flaky failures
