carve-server 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- carve_server-0.1.0/.carveignore +37 -0
- carve_server-0.1.0/.gitignore +50 -0
- carve_server-0.1.0/CLAUDE.md +235 -0
- carve_server-0.1.0/CLAUDE.md.crv +26 -0
- carve_server-0.1.0/LICENSE +21 -0
- carve_server-0.1.0/PKG-INFO +271 -0
- carve_server-0.1.0/README.md +210 -0
- carve_server-0.1.0/carve/__init__.py +29 -0
- carve_server-0.1.0/carve/cli.py +135 -0
- carve_server-0.1.0/carve/cli.py.crv +26 -0
- carve_server-0.1.0/carve/models.py +288 -0
- carve_server-0.1.0/carve/node_id.py +144 -0
- carve_server-0.1.0/carve/parser.py +602 -0
- carve_server-0.1.0/carve/parser.py.crv +26 -0
- carve_server-0.1.0/carve/server.py +1961 -0
- carve_server-0.1.0/carve/server.py.crv +10 -0
- carve_server-0.1.0/carve/settings.py +55 -0
- carve_server-0.1.0/carve/tools.py +3012 -0
- carve_server-0.1.0/carve/tools.py.crv +26 -0
- carve_server-0.1.0/carvesettings.toml +12 -0
- carve_server-0.1.0/carvesettings.toml.crv +10 -0
- carve_server-0.1.0/crv.schema.json +40 -0
- carve_server-0.1.0/parsers.json +14 -0
- carve_server-0.1.0/parsers.json.crv +26 -0
- carve_server-0.1.0/pyproject.toml +73 -0
- carve_server-0.1.0/tests/__init__.py +0 -0
- carve_server-0.1.0/tests/test_batch.py +442 -0
- carve_server-0.1.0/tests/test_batch.py.crv +18 -0
- carve_server-0.1.0/tests/test_batch_qa.py +593 -0
- carve_server-0.1.0/tests/test_bugs.py +370 -0
- carve_server-0.1.0/tests/test_bugs.py.crv +18 -0
- carve_server-0.1.0/tests/test_configurable_parser.py +57 -0
- carve_server-0.1.0/tests/test_draft_promote.py +577 -0
- carve_server-0.1.0/tests/test_multifile.py +732 -0
- carve_server-0.1.0/tests/test_multifile.py.crv +10 -0
- carve_server-0.1.0/tests/test_parser.py +1043 -0
- carve_server-0.1.0/tests/test_parser.py.crv +18 -0
- carve_server-0.1.0/tests/test_server.py +620 -0
- carve_server-0.1.0/tests/test_settings.py +43 -0
- carve_server-0.1.0/tests/test_sidecar.py +471 -0
- carve_server-0.1.0/tests/test_tools.py +1690 -0
- carve_server-0.1.0/tests/test_tools.py.crv +10 -0
- carve_server-0.1.0/tests/test_ts_query.py +364 -0
- carve_server-0.1.0/tests/test_undo.py +765 -0
- carve_server-0.1.0/tests/test_undo_qa.py +890 -0
- carve_server-0.1.0/uv.lock +1202 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Carve metadata
|
|
2
|
+
*.crv
|
|
3
|
+
|
|
4
|
+
.claude/
|
|
5
|
+
test.py
|
|
6
|
+
test2.py
|
|
7
|
+
test3.py
|
|
8
|
+
test4.py
|
|
9
|
+
|
|
10
|
+
# VCS
|
|
11
|
+
.git/
|
|
12
|
+
.hg/
|
|
13
|
+
.svn/
|
|
14
|
+
|
|
15
|
+
# Python
|
|
16
|
+
__pycache__/
|
|
17
|
+
*.egg-info/
|
|
18
|
+
.mypy_cache/
|
|
19
|
+
.ruff_cache/
|
|
20
|
+
.pytest_cache/
|
|
21
|
+
.tox/
|
|
22
|
+
.eggs/
|
|
23
|
+
.venv/
|
|
24
|
+
venv/
|
|
25
|
+
|
|
26
|
+
# JS
|
|
27
|
+
node_modules/
|
|
28
|
+
|
|
29
|
+
# Build output
|
|
30
|
+
dist/
|
|
31
|
+
build/
|
|
32
|
+
*.db
|
|
33
|
+
*.png
|
|
34
|
+
*.jpg
|
|
35
|
+
*.gif
|
|
36
|
+
*.ico
|
|
37
|
+
.playwright-mcp/
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
|
|
23
|
+
# Virtual environments
|
|
24
|
+
.venv/
|
|
25
|
+
venv/
|
|
26
|
+
ENV/
|
|
27
|
+
|
|
28
|
+
# IDE
|
|
29
|
+
.idea/
|
|
30
|
+
.vscode/
|
|
31
|
+
*.swp
|
|
32
|
+
*.swo
|
|
33
|
+
*~
|
|
34
|
+
|
|
35
|
+
# Testing
|
|
36
|
+
.pytest_cache/
|
|
37
|
+
.coverage
|
|
38
|
+
htmlcov/
|
|
39
|
+
.mypy_cache/
|
|
40
|
+
.ruff_cache/
|
|
41
|
+
|
|
42
|
+
# OS
|
|
43
|
+
.DS_Store
|
|
44
|
+
Thumbs.db
|
|
45
|
+
|
|
46
|
+
scratch*
|
|
47
|
+
.flask.log
|
|
48
|
+
.flask.pid
|
|
49
|
+
.playwright-mcp/
|
|
50
|
+
.claude/
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
Carve is a Text Editor for Agents. It uses tree-sitter ASTs as the source of truth, treating source files as generated artifacts. The server exposes navigation and mutation tools via MCP (stdio) and HTTP (REST) transports.
|
|
8
|
+
|
|
9
|
+
**Phase 1 and Phase 2 are complete.** See `TODO.md` for Phase 3 roadmap.
|
|
10
|
+
|
|
11
|
+
## Commands
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Install (editable with dev deps)
|
|
15
|
+
uv pip install -e ".[dev]"
|
|
16
|
+
|
|
17
|
+
# Run all tests (149 tests, pytest-asyncio with asyncio_mode=auto)
|
|
18
|
+
uv run pytest
|
|
19
|
+
|
|
20
|
+
# Run a single test file
|
|
21
|
+
uv run pytest tests/test_tools.py
|
|
22
|
+
|
|
23
|
+
# Run a single test by name
|
|
24
|
+
uv run pytest -k "test_detect_python"
|
|
25
|
+
|
|
26
|
+
# Type check (strict mode enabled)
|
|
27
|
+
uv run mypy carve/
|
|
28
|
+
|
|
29
|
+
# Lint
|
|
30
|
+
uv run ruff check carve/ tests/
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Architecture
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
carve/
|
|
37
|
+
models.py - Pydantic models: TreeNode, NodeMetadata, TreeStore (in-memory single-file store)
|
|
38
|
+
node_id.py - Hierarchical ID generation (e.g. "module::ClassName::method_name")
|
|
39
|
+
parser.py - Tree-sitter parser with auto language detection from file extensions
|
|
40
|
+
tools.py - All tool implementations: navigation (subtree, ancestors, inspect, search),
|
|
41
|
+
mutation (insert, update, delete, validate_snippet),
|
|
42
|
+
project (project_import, project_serialize)
|
|
43
|
+
server.py - CarveServer wrapper + MCP transport (create_mcp_server) + HTTP transport (create_http_app via FastAPI)
|
|
44
|
+
cli.py - CLI entry point (`carve serve --mcp` or `carve serve --http`)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Key data flow:** Source file → `TreeSitterParser.to_tree_store()` → `TreeStore` (dict of `TreeNode` keyed by node ID) → tools operate on the store → `project_serialize()` regenerates source code.
|
|
48
|
+
|
|
49
|
+
**Node IDs** are deterministic hierarchical paths built from the AST: `module::ClassName::method_name`. Each language defines its own set of named types that form hierarchy components (see `PYTHON_NAMED_TYPES`, `JAVASCRIPT_NAMED_TYPES`, etc. in `node_id.py`).
|
|
50
|
+
|
|
51
|
+
**Dual transport:** `CarveServer` wraps all tool logic. Both MCP and HTTP transports delegate to it, so tool behavior is transport-agnostic.
|
|
52
|
+
|
|
53
|
+
**Supported languages:** Python, JavaScript, HTML, CSS, JSON (via tree-sitter grammars). Unsupported extensions are stored as `text` (no AST, raw content only).
|
|
54
|
+
|
|
55
|
+
## Using Carve Efficiently
|
|
56
|
+
|
|
57
|
+
When using carve MCP tools to edit this codebase, prefer carve over file-based tools (Read, Edit, Write, Bash). Also use carve's custom tools (`run_test`, `run_typecheck`, `run_lint`, `run_install`, `run_flask`) instead of Bash for running commands.
|
|
58
|
+
|
|
59
|
+
For the full reference on Carve workflows, read the `carve://guide` MCP resource.
|
|
60
|
+
|
|
61
|
+
**Target the smallest node that covers your change.** Carve nodes go all the way down to individual statements. Replacing a single `assert_statement` or `expression_statement` is far more efficient than replacing an entire function.
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
# GOOD: update just the one statement that changed
|
|
65
|
+
update(node_id="...::test_foo::assert_statement[1]", snippet='assert result == "new_value"')
|
|
66
|
+
|
|
67
|
+
# WASTEFUL: replace the entire function to change one line
|
|
68
|
+
update(node_id="...::test_foo", snippet="def test_foo(): ...")
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Use navigation tools for understanding, mutation tools for editing:**
|
|
72
|
+
- `subtree` / `search` — structural overview without reading full files
|
|
73
|
+
- `find_references` — cross-file dependency tracing
|
|
74
|
+
- `inspect` — read a specific node's source
|
|
75
|
+
- `update` at the statement level — surgical edits
|
|
76
|
+
|
|
77
|
+
**Always pass sidecar metadata on every mutation.** The `insert`, `update`, `delete`, `move`, and `batch` tools accept `prompt`, `model`, and `notes` parameters that are logged to `.crv` sidecar files. You MUST pass these on every mutation call — without them the audit trail is empty.
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
# GOOD: includes sidecar metadata
|
|
81
|
+
update(
|
|
82
|
+
node_id="module::Calculator::add",
|
|
83
|
+
snippet="def add(self, a, b): return a + b",
|
|
84
|
+
prompt="Simplify the add method",
|
|
85
|
+
model="claude-opus-4-6",
|
|
86
|
+
notes="Removed type annotations per user request"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# BAD: sidecar log entry will be full of nulls
|
|
90
|
+
update(node_id="module::Calculator::add", snippet="def add(self, a, b): return a + b")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Call `project_serialize` before testing.** Carve holds mutations in-memory. Files on disk are only updated when you serialize. Always serialize before running the server, tests, or any tool that reads from disk.
|
|
94
|
+
|
|
95
|
+
**Use `insert` on a directory node to create new files.** Pass `file_name` along with `parent_id` pointing to the directory. New file creation via `insert` skips tree-sitter validation, which is important for languages with incomplete grammars (see Jinja note below).
|
|
96
|
+
|
|
97
|
+
**For complete rewrites, use file-level `update`.** When every line changes (e.g., converting a todo app to a kanban board), update the file node directly rather than trying to surgically edit dozens of nodes. Reserve surgical edits for incremental changes.
|
|
98
|
+
|
|
99
|
+
**Markdown works great for targeted edits.** The tree-sitter markdown grammar is complete — sections, headings, lists, code blocks are all distinct nodes. You can update a single `### Subsection` without touching the rest of the file.
|
|
100
|
+
|
|
101
|
+
### Language-specific tips
|
|
102
|
+
|
|
103
|
+
**Python:** Best Carve experience. Functions, classes, decorators, individual statements are all targetable. Use `subtree` with `depth=2` to see the structure, then `update` specific nodes.
|
|
104
|
+
|
|
105
|
+
**Jinja/HTML templates:** Limited by incomplete `tree-sitter-jinja` grammar (see `BUGS.md` BUG-001). Key limitations:
|
|
106
|
+
- `{% include %}`, `{% block %}`, `{% extends %}`, `{% set %}`, `{% macro %}` all cause syntax errors during validation
|
|
107
|
+
- `insert` (new file creation) works because it skips validation
|
|
108
|
+
- `update` fails on any file containing unsupported tags — even if your edit is on a different node
|
|
109
|
+
- **Workaround:** Delete the file and recreate it with `insert` (whole-file replace)
|
|
110
|
+
- Templates that only use `{% if %}`, `{% for %}`, and `{{ }}` can be updated normally
|
|
111
|
+
- `template_data` nodes represent raw HTML fragments between Jinja tags — they slice across HTML element boundaries, so they're awkward to target
|
|
112
|
+
|
|
113
|
+
**CSS-in-templates:** Styles embedded in `<style>` tags within Jinja templates land inside `template_data` nodes. If the template uses `{% include %}`, you can't update them surgically. Consider putting shared styles in a partial that only uses supported Jinja tags.
|
|
114
|
+
|
|
115
|
+
### The example Flask app
|
|
116
|
+
|
|
117
|
+
The `examples/` directory contains a Kanban board app used for demoing Carve. Useful commands:
|
|
118
|
+
- `run_flask start` — start the server on port 5000
|
|
119
|
+
- `run_flask stop` — stop it
|
|
120
|
+
- `run_flask status` — check if running
|
|
121
|
+
- The database is `examples/kanban.db` (SQLite, auto-created on first run)
|
|
122
|
+
- Templates are in `examples/templates/` using Jinja2 with Pico CSS + htmx + SortableJS via CDN## Code Conventions
|
|
123
|
+
|
|
124
|
+
- Python 3.11+, strict mypy, Pydantic models for all data structures
|
|
125
|
+
- Ruff with rules: E, F, I, N, W, UP, B, C4, SIM; line length 100
|
|
126
|
+
- Tests are class-based (`TestClassName`), use fixtures, and live in `tests/`
|
|
127
|
+
- Commit messages follow conventional commits (`feat:`, `docs:`, `fix:`)
|
|
128
|
+
|
|
129
|
+
## Testing Guidelines
|
|
130
|
+
|
|
131
|
+
Tests must be **black-box behavioral tests** that survive internal refactoring. The public API surface is:
|
|
132
|
+
|
|
133
|
+
1. **Tool functions** in `carve.tools` (subtree, ancestors, inspect, search, insert, update, delete, move, create_file, find_references, error_locate, ts_query, validate_snippet, project_import, project_serialize)
|
|
134
|
+
2. **CarveServer** methods in `carve.server`
|
|
135
|
+
3. **HTTP endpoints** via FastAPI
|
|
136
|
+
|
|
137
|
+
### Rules for New Tests
|
|
138
|
+
|
|
139
|
+
**Assert on tool return values, not model internals.**
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
# GOOD: verify through the tool API
|
|
143
|
+
result = insert(store, parent_id, "end", snippet)
|
|
144
|
+
assert "error" not in result
|
|
145
|
+
inspected = inspect(store, result["node_id"])
|
|
146
|
+
assert "def greet" in inspected["source"]
|
|
147
|
+
|
|
148
|
+
# BAD: reaching into model internals
|
|
149
|
+
result = insert(store, parent_id, "end", snippet)
|
|
150
|
+
node = store.get_node(result["node_id"]) # internal model access
|
|
151
|
+
assert node.source == expected # Pydantic field
|
|
152
|
+
assert node.metadata.version == 2 # internal metadata
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Discover node IDs dynamically, don't hardcode them.**
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
# GOOD: discover via search
|
|
159
|
+
results = search(store, pattern="Calculator")
|
|
160
|
+
calc_id = results[0]["node_id"]
|
|
161
|
+
|
|
162
|
+
# BAD: hardcoded ID format
|
|
163
|
+
calc_id = "module::Calculator"
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Set up stores via `project_import`, not parser internals.**
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
# GOOD: use the tool API
|
|
170
|
+
py_file = tmp_path / "test.py"
|
|
171
|
+
py_file.write_text(source)
|
|
172
|
+
store = project_import(str(py_file))
|
|
173
|
+
|
|
174
|
+
# BAD: coupling to parser internals
|
|
175
|
+
store = parser.to_tree_store(source, "python", "test.py")
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Verify side effects through tools or disk, not internal dicts.**
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
# GOOD: verify deletion via search
|
|
182
|
+
delete(store, node_id)
|
|
183
|
+
results = search(store, pattern="greet")
|
|
184
|
+
assert not any(r["node_id"] == node_id for r in results)
|
|
185
|
+
|
|
186
|
+
# GOOD: verify disk write by reading the file
|
|
187
|
+
result = update(store, node_id, new_snippet)
|
|
188
|
+
content = Path(store.file_path).read_text()
|
|
189
|
+
assert "new_function" in content
|
|
190
|
+
|
|
191
|
+
# BAD: poking internal dicts
|
|
192
|
+
delete(store, node_id)
|
|
193
|
+
assert node_id not in store.file_sources # internal dict
|
|
194
|
+
assert store.get_node(node_id) is None # internal method
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Verify sidecar writes by reading the .crv JSON file, not private functions.**
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
# GOOD: read the file directly
|
|
201
|
+
crv = tmp_path / "app.py.crv"
|
|
202
|
+
entries = json.loads(crv.read_text())
|
|
203
|
+
assert entries[-1]["operation"] == "update"
|
|
204
|
+
|
|
205
|
+
# BAD: importing private helpers
|
|
206
|
+
from carve.tools import _read_sidecar # underscore = private
|
|
207
|
+
entries = _read_sidecar(crv)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Never import underscore-prefixed functions** (`_read_sidecar`, `_reindent_snippet`, `_validate_in_context`, etc.). These are implementation details. If behavior needs testing, test it through the public tool that calls it.
|
|
211
|
+
|
|
212
|
+
### Acceptable Internal Tests
|
|
213
|
+
|
|
214
|
+
`test_parser.py` tests parser and model internals directly — this is fine and expected for unit-testing the parser layer. These tests are understood to break when parser internals change. Keep them isolated in `test_parser.py` and label the file clearly.
|
|
215
|
+
|
|
216
|
+
### Test File Organization
|
|
217
|
+
|
|
218
|
+
| File | Purpose | API Layer |
|
|
219
|
+
|------|---------|-----------|
|
|
220
|
+
| `test_parser.py` | Parser/model unit tests (internal, expected to break on refactor) | `TreeSitterParser`, `TreeNode`, `TreeStore` |
|
|
221
|
+
| `test_server.py` | Server + HTTP endpoint tests (gold standard for black-box) | `CarveServer`, HTTP |
|
|
222
|
+
| `test_tools.py` | Tool behavior tests | Tool functions |
|
|
223
|
+
| `test_multifile.py` | Multi-file/ProjectStore behavior | Tool functions + `project_import` |
|
|
224
|
+
| `test_bugs.py` | Regression tests for fixed bugs | Tool functions |
|
|
225
|
+
| `test_sidecar.py` | Sidecar persistence behavior | Tool functions + disk |
|
|
226
|
+
| `test_ts_query.py` | Tree-sitter query behavior | `ts_query` tool |
|
|
227
|
+
| `test_draft_promote.py` | Validation/indentation behavior | `insert`, `update` tools |
|
|
228
|
+
|
|
229
|
+
## Bug Reporting
|
|
230
|
+
|
|
231
|
+
When you encounter bugs during development or testing, write detailed bug reports to `BUGS.md`. Each report should include:
|
|
232
|
+
- Summary of the issue
|
|
233
|
+
- Exact steps to reproduce (code snippets or MCP tool call sequences)
|
|
234
|
+
- Observed vs expected behavior
|
|
235
|
+
- Likely cause if apparent
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"operation": "update",
|
|
4
|
+
"node_id": "",
|
|
5
|
+
"timestamp": "2026-02-13T06:04:29.301004+00:00",
|
|
6
|
+
"model": null,
|
|
7
|
+
"prompt": null,
|
|
8
|
+
"notes": null
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"operation": "insert",
|
|
12
|
+
"node_id": "section[0]::section[5]::section[0]",
|
|
13
|
+
"timestamp": "2026-02-13T17:05:55.304839+00:00",
|
|
14
|
+
"model": "claude-opus-4-6",
|
|
15
|
+
"prompt": "Add how-to-rename-a-file guidance to CLAUDE.md",
|
|
16
|
+
"notes": "No dedicated rename tool exists; compose create_file + move + delete instead"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"operation": "update",
|
|
20
|
+
"node_id": "section[0]::section[5]",
|
|
21
|
+
"timestamp": "2026-02-13T17:53:50.079649+00:00",
|
|
22
|
+
"model": "claude-opus-4-6",
|
|
23
|
+
"prompt": "Add practical tips learned from building the kanban board app",
|
|
24
|
+
"notes": "Added: serialize before testing, insert for new files, file-level update for rewrites, markdown works well, language-specific tips for Python/Jinja/CSS, Jinja limitations and workarounds, flask app docs"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Scott Russell
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: carve-server
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Text Editor for AI Agents
|
|
5
|
+
Author-email: Scott Russell <me@scottrussell.net>
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: fastapi>=0.115.0
|
|
10
|
+
Requires-Dist: mcp>=1.0.0
|
|
11
|
+
Requires-Dist: pydantic>=2.0.0
|
|
12
|
+
Requires-Dist: tree-sitter>=0.24.0
|
|
13
|
+
Requires-Dist: uvicorn>=0.34.0
|
|
14
|
+
Provides-Extra: all
|
|
15
|
+
Requires-Dist: tree-sitter-bash>=0.24.0; extra == 'all'
|
|
16
|
+
Requires-Dist: tree-sitter-css>=0.24.0; extra == 'all'
|
|
17
|
+
Requires-Dist: tree-sitter-html>=0.23.0; extra == 'all'
|
|
18
|
+
Requires-Dist: tree-sitter-javascript>=0.24.0; extra == 'all'
|
|
19
|
+
Requires-Dist: tree-sitter-jinja>=0.3.3; extra == 'all'
|
|
20
|
+
Requires-Dist: tree-sitter-json>=0.24.0; extra == 'all'
|
|
21
|
+
Requires-Dist: tree-sitter-markdown>=0.5.1; extra == 'all'
|
|
22
|
+
Requires-Dist: tree-sitter-python>=0.24.0; extra == 'all'
|
|
23
|
+
Requires-Dist: tree-sitter-toml>=0.7.0; extra == 'all'
|
|
24
|
+
Requires-Dist: tree-sitter-yaml>=0.7.2; extra == 'all'
|
|
25
|
+
Provides-Extra: bash
|
|
26
|
+
Requires-Dist: tree-sitter-bash>=0.24.0; extra == 'bash'
|
|
27
|
+
Provides-Extra: css
|
|
28
|
+
Requires-Dist: tree-sitter-css>=0.24.0; extra == 'css'
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: mypy>=1.14.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.25.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: ruff>=0.9.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: tree-sitter-bash>=0.24.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: tree-sitter-css>=0.24.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: tree-sitter-html>=0.23.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: tree-sitter-javascript>=0.24.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: tree-sitter-jinja>=0.3.3; extra == 'dev'
|
|
39
|
+
Requires-Dist: tree-sitter-json>=0.24.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: tree-sitter-markdown>=0.5.1; extra == 'dev'
|
|
41
|
+
Requires-Dist: tree-sitter-python>=0.24.0; extra == 'dev'
|
|
42
|
+
Requires-Dist: tree-sitter-toml>=0.7.0; extra == 'dev'
|
|
43
|
+
Requires-Dist: tree-sitter-yaml>=0.7.2; extra == 'dev'
|
|
44
|
+
Provides-Extra: html
|
|
45
|
+
Requires-Dist: tree-sitter-html>=0.23.0; extra == 'html'
|
|
46
|
+
Provides-Extra: javascript
|
|
47
|
+
Requires-Dist: tree-sitter-javascript>=0.24.0; extra == 'javascript'
|
|
48
|
+
Provides-Extra: jinja
|
|
49
|
+
Requires-Dist: tree-sitter-jinja>=0.3.3; extra == 'jinja'
|
|
50
|
+
Provides-Extra: json
|
|
51
|
+
Requires-Dist: tree-sitter-json>=0.24.0; extra == 'json'
|
|
52
|
+
Provides-Extra: markdown
|
|
53
|
+
Requires-Dist: tree-sitter-markdown>=0.5.1; extra == 'markdown'
|
|
54
|
+
Provides-Extra: python
|
|
55
|
+
Requires-Dist: tree-sitter-python>=0.24.0; extra == 'python'
|
|
56
|
+
Provides-Extra: toml
|
|
57
|
+
Requires-Dist: tree-sitter-toml>=0.7.0; extra == 'toml'
|
|
58
|
+
Provides-Extra: yaml
|
|
59
|
+
Requires-Dist: tree-sitter-yaml>=0.7.2; extra == 'yaml'
|
|
60
|
+
Description-Content-Type: text/markdown
|
|
61
|
+
|
|
62
|
+
# Carve
|
|
63
|
+
|
|
64
|
+
**Text Editor for AI Agents**
|
|
65
|
+
|
|
66
|
+
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.
|
|
67
|
+
|
|
68
|
+
## Install
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install carve-server
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Install with language grammars:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Individual languages
|
|
78
|
+
pip install "carve-server[python]"
|
|
79
|
+
pip install "carve-server[javascript]"
|
|
80
|
+
|
|
81
|
+
# All supported languages
|
|
82
|
+
pip install "carve-server[all]"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Supported: Python, JavaScript, HTML, CSS, JSON, Markdown, TOML, YAML, Bash, Jinja.
|
|
86
|
+
|
|
87
|
+
## Quick Start (MCP)
|
|
88
|
+
|
|
89
|
+
Add to your MCP client config (e.g. Claude Code `settings.json`):
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"mcpServers": {
|
|
94
|
+
"carve": {
|
|
95
|
+
"command": "carve",
|
|
96
|
+
"args": ["serve", "--mcp", "--path", "/path/to/your/project"]
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Or run directly:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
carve serve --mcp --path ./my-project
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The agent can then navigate and edit code through Carve's tool interface.
|
|
109
|
+
|
|
110
|
+
## Quick Start (HTTP)
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
carve serve --http --path ./my-project --port 8080
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Import a file, explore its structure, make a surgical edit:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# See the tree structure
|
|
120
|
+
curl -X POST http://127.0.0.1:8080/tree/subtree \
|
|
121
|
+
-H "Content-Type: application/json" \
|
|
122
|
+
-d '{"node_id": "module", "depth": 2}'
|
|
123
|
+
|
|
124
|
+
# Inspect a specific function
|
|
125
|
+
curl -X POST http://127.0.0.1:8080/tree/inspect \
|
|
126
|
+
-H "Content-Type: application/json" \
|
|
127
|
+
-d '{"node_id": "module::MyClass::my_method"}'
|
|
128
|
+
|
|
129
|
+
# Update just that function
|
|
130
|
+
curl -X POST http://127.0.0.1:8080/tree/update \
|
|
131
|
+
-H "Content-Type: application/json" \
|
|
132
|
+
-d '{
|
|
133
|
+
"node_id": "module::MyClass::my_method",
|
|
134
|
+
"snippet": "def my_method(self, x):\n return x * 2"
|
|
135
|
+
}'
|
|
136
|
+
|
|
137
|
+
# Write changes to disk
|
|
138
|
+
curl -X POST http://127.0.0.1:8080/project/serialize \
|
|
139
|
+
-H "Content-Type: application/json" \
|
|
140
|
+
-d '{}'
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## How It Works
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
Source file → Tree-sitter AST → In-memory tree store → Tools → Regenerated source
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
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.
|
|
150
|
+
|
|
151
|
+
This means an agent can change one line inside a method without seeing or rewriting the rest of the file.
|
|
152
|
+
|
|
153
|
+
## Tools
|
|
154
|
+
|
|
155
|
+
| Tool | Description |
|
|
156
|
+
|------|-------------|
|
|
157
|
+
| `subtree` | Navigate tree structure from any node |
|
|
158
|
+
| `ancestors` | Get the path from a node up to root |
|
|
159
|
+
| `inspect` | Read a node's full source and metadata |
|
|
160
|
+
| `search` | Find nodes by name pattern and/or type |
|
|
161
|
+
| `find_references` | Cross-file dependency tracing |
|
|
162
|
+
| `insert` | Add new code at a specific position |
|
|
163
|
+
| `update` | Replace a node's implementation |
|
|
164
|
+
| `delete` | Remove a node |
|
|
165
|
+
| `move` | Relocate a node in the tree |
|
|
166
|
+
| `batch` | Apply multiple mutations in one pass |
|
|
167
|
+
| `ts_query` | Run tree-sitter S-expression queries |
|
|
168
|
+
| `project_import` | Load a file or directory |
|
|
169
|
+
| `project_serialize` | Write the tree back to source files |
|
|
170
|
+
| `error_locate` | Map stack traces to node IDs |
|
|
171
|
+
| `checkpoint` / `undo` / `redo` | Version control for mutations |
|
|
172
|
+
|
|
173
|
+
## Configuration
|
|
174
|
+
|
|
175
|
+
### carvesettings.toml
|
|
176
|
+
|
|
177
|
+
Drop a `carvesettings.toml` in your project root to configure formatters, custom tools, and language mappings.
|
|
178
|
+
|
|
179
|
+
```toml
|
|
180
|
+
workspace = "."
|
|
181
|
+
sidecars = true
|
|
182
|
+
|
|
183
|
+
[formatters]
|
|
184
|
+
python = "ruff format --quiet {file}"
|
|
185
|
+
javascript = "prettier --write {file}"
|
|
186
|
+
|
|
187
|
+
[tools]
|
|
188
|
+
test = "uv run pytest {args}"
|
|
189
|
+
lint = "uv run ruff check carve/ {args}"
|
|
190
|
+
typecheck = "uv run mypy carve/"
|
|
191
|
+
|
|
192
|
+
[language_map]
|
|
193
|
+
".djhtml" = "jinja"
|
|
194
|
+
".j2" = "jinja"
|
|
195
|
+
|
|
196
|
+
[import_map]
|
|
197
|
+
jinja = "tree_sitter_jinja"
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**`formatters`** — Run after every disk write. `{file}` is replaced with the file path. Keyed by language name.
|
|
201
|
+
|
|
202
|
+
**`tools`** — Custom shell commands exposed as MCP tools (prefixed with `run_`). Placeholders like `{args}` become tool parameters. Requires `--allow-custom-tools` to enable.
|
|
203
|
+
|
|
204
|
+
**`sidecars`** — When `true` (default), Carve writes `.crv` sidecar files (see below). Set to `false` to disable.
|
|
205
|
+
|
|
206
|
+
**`workspace`** — Default project path. Also settable via `CARVE_WORKSPACE` env var.
|
|
207
|
+
|
|
208
|
+
**`language_map`** — Map file extensions to language names, extending the built-in defaults.
|
|
209
|
+
|
|
210
|
+
**`import_map`** — Map language names to tree-sitter grammar module names.
|
|
211
|
+
|
|
212
|
+
### .carveignore
|
|
213
|
+
|
|
214
|
+
Controls which files are included when importing a directory. Uses gitignore-style patterns. Without this file, all supported files are included.
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
# Common defaults
|
|
218
|
+
.git/
|
|
219
|
+
__pycache__/
|
|
220
|
+
*.egg-info/
|
|
221
|
+
.venv/
|
|
222
|
+
node_modules/
|
|
223
|
+
dist/
|
|
224
|
+
build/
|
|
225
|
+
|
|
226
|
+
# Carve metadata
|
|
227
|
+
*.crv
|
|
228
|
+
|
|
229
|
+
# Project-specific
|
|
230
|
+
*.db
|
|
231
|
+
*.png
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### .crv Sidecar Files
|
|
235
|
+
|
|
236
|
+
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:
|
|
237
|
+
|
|
238
|
+
```json
|
|
239
|
+
[
|
|
240
|
+
{
|
|
241
|
+
"operation": "update",
|
|
242
|
+
"node_id": "Calculator::add",
|
|
243
|
+
"timestamp": "2026-02-13T18:30:00+00:00",
|
|
244
|
+
"model": "claude-opus-4-6",
|
|
245
|
+
"prompt": "Simplify the add method",
|
|
246
|
+
"notes": "Removed type annotations per user request"
|
|
247
|
+
}
|
|
248
|
+
]
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
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.
|
|
252
|
+
|
|
253
|
+
To disable: set `sidecars = false` in `carvesettings.toml`. To keep them out of version control, add `*.crv` to your `.gitignore`.
|
|
254
|
+
|
|
255
|
+
## CLI Options
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
carve serve --mcp # MCP server on stdio
|
|
259
|
+
carve serve --http # HTTP server (default port 8080)
|
|
260
|
+
--path DIR # Project directory or file to load
|
|
261
|
+
--port 8080 # HTTP port
|
|
262
|
+
--host 127.0.0.1 # HTTP host
|
|
263
|
+
--read-only # Block all mutations
|
|
264
|
+
--no-create # Block new file creation
|
|
265
|
+
--no-delete # Block file/directory deletion
|
|
266
|
+
--allow-custom-tools # Enable tools from carvesettings.toml
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## License
|
|
270
|
+
|
|
271
|
+
MIT
|