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.
Files changed (46) hide show
  1. carve_server-0.1.0/.carveignore +37 -0
  2. carve_server-0.1.0/.gitignore +50 -0
  3. carve_server-0.1.0/CLAUDE.md +235 -0
  4. carve_server-0.1.0/CLAUDE.md.crv +26 -0
  5. carve_server-0.1.0/LICENSE +21 -0
  6. carve_server-0.1.0/PKG-INFO +271 -0
  7. carve_server-0.1.0/README.md +210 -0
  8. carve_server-0.1.0/carve/__init__.py +29 -0
  9. carve_server-0.1.0/carve/cli.py +135 -0
  10. carve_server-0.1.0/carve/cli.py.crv +26 -0
  11. carve_server-0.1.0/carve/models.py +288 -0
  12. carve_server-0.1.0/carve/node_id.py +144 -0
  13. carve_server-0.1.0/carve/parser.py +602 -0
  14. carve_server-0.1.0/carve/parser.py.crv +26 -0
  15. carve_server-0.1.0/carve/server.py +1961 -0
  16. carve_server-0.1.0/carve/server.py.crv +10 -0
  17. carve_server-0.1.0/carve/settings.py +55 -0
  18. carve_server-0.1.0/carve/tools.py +3012 -0
  19. carve_server-0.1.0/carve/tools.py.crv +26 -0
  20. carve_server-0.1.0/carvesettings.toml +12 -0
  21. carve_server-0.1.0/carvesettings.toml.crv +10 -0
  22. carve_server-0.1.0/crv.schema.json +40 -0
  23. carve_server-0.1.0/parsers.json +14 -0
  24. carve_server-0.1.0/parsers.json.crv +26 -0
  25. carve_server-0.1.0/pyproject.toml +73 -0
  26. carve_server-0.1.0/tests/__init__.py +0 -0
  27. carve_server-0.1.0/tests/test_batch.py +442 -0
  28. carve_server-0.1.0/tests/test_batch.py.crv +18 -0
  29. carve_server-0.1.0/tests/test_batch_qa.py +593 -0
  30. carve_server-0.1.0/tests/test_bugs.py +370 -0
  31. carve_server-0.1.0/tests/test_bugs.py.crv +18 -0
  32. carve_server-0.1.0/tests/test_configurable_parser.py +57 -0
  33. carve_server-0.1.0/tests/test_draft_promote.py +577 -0
  34. carve_server-0.1.0/tests/test_multifile.py +732 -0
  35. carve_server-0.1.0/tests/test_multifile.py.crv +10 -0
  36. carve_server-0.1.0/tests/test_parser.py +1043 -0
  37. carve_server-0.1.0/tests/test_parser.py.crv +18 -0
  38. carve_server-0.1.0/tests/test_server.py +620 -0
  39. carve_server-0.1.0/tests/test_settings.py +43 -0
  40. carve_server-0.1.0/tests/test_sidecar.py +471 -0
  41. carve_server-0.1.0/tests/test_tools.py +1690 -0
  42. carve_server-0.1.0/tests/test_tools.py.crv +10 -0
  43. carve_server-0.1.0/tests/test_ts_query.py +364 -0
  44. carve_server-0.1.0/tests/test_undo.py +765 -0
  45. carve_server-0.1.0/tests/test_undo_qa.py +890 -0
  46. 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