# Carve

**Text Editor for AI Agents**

Carve lets AI agents edit code structurally instead of rewriting entire files. It parses source code into a tree-sitter AST, exposes navigation and mutation tools over MCP or HTTP, and regenerates source files from the tree.

## Install

```bash
pip install carve-server
```

Install with language grammars:

```bash
# Individual languages
pip install "carve-server[python]"
pip install "carve-server[javascript]"

# All supported languages
pip install "carve-server[all]"
```

Supported: Python, JavaScript, HTML, CSS, JSON, Markdown, TOML, YAML, Bash, Jinja.

## Quick Start (MCP)

Add to Claude Code with one command:

```bash
claude mcp add carve -- carve serve --mcp
```

Or add to your MCP client config manually:

```json
{
  "mcpServers": {
    "carve": {
      "command": "carve",
      "args": ["serve", "--mcp", "--path", "/path/to/your/project"]
    }
  }
}
```

Or run directly:

```bash
carve serve --mcp --path ./my-project
```

The agent can then navigate and edit code through Carve's tool interface.

## Quick Start (HTTP)

```bash
carve serve --http --path ./my-project --port 8080
```

Import a file, explore its structure, make a surgical edit:

```bash
# See the tree structure
curl -X POST http://127.0.0.1:8080/tree/subtree \
  -H "Content-Type: application/json" \
  -d '{"node_id": "module", "depth": 2}'

# Inspect a specific function
curl -X POST http://127.0.0.1:8080/tree/inspect \
  -H "Content-Type: application/json" \
  -d '{"node_id": "module::MyClass::my_method"}'

# Update just that function
curl -X POST http://127.0.0.1:8080/tree/update \
  -H "Content-Type: application/json" \
  -d '{
    "node_id": "module::MyClass::my_method",
    "snippet": "def my_method(self, x):\n        return x * 2"
  }'

# Write changes to disk
curl -X POST http://127.0.0.1:8080/project/serialize \
  -H "Content-Type: application/json" \
  -d '{}'
```

## How It Works

```
Source file → Tree-sitter AST → In-memory tree store → Tools → Regenerated source
```

Carve parses source files into a tree of named nodes with hierarchical IDs like `module::Calculator::add`. Agents navigate with `subtree`, `search`, and `inspect`, then make targeted edits with `insert`, `update`, and `delete` — down to individual statements. When done, `project_serialize` writes the tree back to disk.

This means an agent can change one line inside a method without seeing or rewriting the rest of the file.

## Tools

| Tool | Description |
|------|-------------|
| `subtree` | Navigate tree structure from any node |
| `ancestors` | Get the path from a node up to root |
| `inspect` | Read a node's full source and metadata |
| `search` | Find nodes by name pattern and/or type |
| `find_references` | Cross-file dependency tracing |
| `insert` | Add new code at a specific position |
| `update` | Replace a node's implementation |
| `delete` | Remove a node |
| `move` | Relocate a node in the tree |
| `batch` | Apply multiple mutations in one pass |
| `ts_query` | Run tree-sitter S-expression queries |
| `project_import` | Load a file or directory |
| `project_serialize` | Write the tree back to source files |
| `error_locate` | Map stack traces to node IDs |
| `checkpoint` / `undo` / `redo` | Version control for mutations |

## Configuration

### carvesettings.toml

Drop a `carvesettings.toml` in your project root to configure formatters, custom tools, and language mappings.

```toml
workspace = "."
sidecars = true

[formatters]
python = "ruff format --quiet {file}"
javascript = "prettier --write {file}"

[tools]
test = "uv run pytest {args}"
lint = "uv run ruff check carve/ {args}"
typecheck = "uv run mypy carve/"

[language_map]
".djhtml" = "jinja"
".j2" = "jinja"

[import_map]
jinja = "tree_sitter_jinja"
```

**`formatters`** — Run after every disk write. `{file}` is replaced with the file path. Keyed by language name.

**`tools`** — Custom shell commands exposed as MCP tools (prefixed with `run_`). Placeholders like `{args}` become tool parameters. Requires `--allow-custom-tools` to enable.

**`sidecars`** — When `true` (default), Carve writes `.crv` sidecar files (see below). Set to `false` to disable.

**`workspace`** — Default project path. Also settable via `CARVE_WORKSPACE` env var.

**`language_map`** — Map file extensions to language names, extending the built-in defaults.

**`import_map`** — Map language names to tree-sitter grammar module names.

### .carveignore

Controls which files are included when importing a directory. Uses gitignore-style patterns. Without this file, all supported files are included.

```
# Common defaults
.git/
__pycache__/
*.egg-info/
.venv/
node_modules/
dist/
build/

# Carve metadata
*.crv

# Project-specific
*.db
*.png
```

### .crv Sidecar Files

Every time Carve mutates a source file, it appends an entry to a `.crv` JSON file next to it (e.g. `app.py.crv`). This is an audit trail of AI-driven changes:

```json
[
  {
    "operation": "update",
    "node_id": "Calculator::add",
    "timestamp": "2026-02-13T18:30:00+00:00",
    "model": "claude-opus-4-6",
    "prompt": "Simplify the add method",
    "notes": "Removed type annotations per user request"
  }
]
```

Each entry records *what* changed, *when*, *which model* did it, and *why*. This metadata is restored when you re-import the file, so `inspect` shows the prompt and model that last touched each node.

To disable: set `sidecars = false` in `carvesettings.toml`. To keep them out of version control, add `*.crv` to your `.gitignore`.

## CLI Options

```
carve serve --mcp              # MCP server on stdio
carve serve --http             # HTTP server (default port 8080)
  --path DIR                   # Project directory or file to load
  --port 8080                  # HTTP port
  --host 127.0.0.1             # HTTP host
  --read-only                  # Block all mutations
  --no-create                  # Block new file creation
  --no-delete                  # Block file/directory deletion
  --allow-custom-tools         # Enable tools from carvesettings.toml
```

## License

MIT
