Chisel is built for LLM agents and solo developers running multiple agent sessions on one repo. Contributions should preserve: stdlib-only runtime, structured MCP responses (status dicts, next_steps where applicable), multi-process safety (locks around storage), and import-graph + test edges as primary signals when git co-change is thin.
- Python 3.11 or later
- git (used at runtime for log, blame, and diff operations)
git clone https://github.com/IronAdamant/Chisel.git
cd Chisel
pip install -e ".[dev]" --break-system-packages # --break-system-packages needed on Arch LinuxThis installs Chisel in editable mode along with dev dependencies (pytest, pytest-cov, ruff).
pytest tests/ -v --tb=short # full suite
pytest tests/test_engine.py # single module
pytest -k "test_risk" # by name patternAll tests must pass before submitting changes.
This project uses ruff for linting.
- Line length: 100 characters
- Run
ruff check chisel/before committing - No type stubs required, but type hints are welcome
Configuration lives in pyproject.toml under [tool.ruff].
See CLAUDE.md for the full module map and dependency graph, ARCHITECTURE.md for the data model, and wiki-local/spec-project.md for MCP tool contracts.
The core modules are:
| Module | Role |
|---|---|
engine.py |
Orchestrator; owns all subsystems; tool_*() for MCP |
storage.py |
SQLite persistence (WAL mode, single persistent connection, batch queries) |
ast_utils.py |
Multi-language AST extraction (12 languages) + pluggable extractor registry |
git_analyzer.py |
Git log/blame parsing via subprocess |
metrics.py |
Pure computation: churn scoring, ownership, co-change detection |
test_mapper.py |
Test discovery, framework detection, dependency extraction, edge building |
impact.py |
Impact analysis (direct + co-change + import-graph tests), risk scoring, stale tests, git-derived ownership/review hints |
project.py |
Project root, path normalization, ProcessLock (multi-process / multi-agent) |
schemas.py |
JSON Schema + dispatch for 26 MCP tools (20 functional + 6 advisory file-lock) |
next_steps.py |
Contextual follow-up hints for agent clients |
cli.py |
argparse CLI (core tools + serve + lock subcommands) |
mcp_server.py |
HTTP MCP server (dispatch_tool, next_steps) |
mcp_stdio.py |
stdio MCP server |
rwlock.py |
In-process read/write lock (pairs with ProcessLock) |
Maximum 500 lines of code per file. If a module grows beyond this, split it along responsibility boundaries.
Chisel's runtime has zero external dependencies. This is a firm design constraint. All functionality uses the Python standard library:
astfor Python parsing- Regular expressions for 11 other languages (JS/TS, Go, Rust, C#, Java, Kotlin, C/C++, Swift, PHP, Ruby, Dart)
subprocess.run(["git", ...])for git operationssqlite3for persistencefcntl(Unix) /ctypes+msvcrt(Windows) for cross-process file locking
The only exceptions are optional extras declared in pyproject.toml:
mcpextra: required only for the stdio MCP serverdevextra: pytest, pytest-cov, ruff (development only)
Do not add runtime dependencies.
To add a new tool, wire it through these layers:
- Engine method: Add
tool_<name>(self, ...)inengine.py. Wrap withself._process_lock.shared()+self.lock.read_lock()for reads, orexclusive()+write_lock()for writes. Prefer explicit statuses (no_data,git_error, etc.) over ambiguous empty lists for agents. - Schema + dispatch: Add the tool schema to
_TOOL_SCHEMASand the dispatch entry to_TOOL_DISPATCHinschemas.py. Use prescriptive descriptions (“Use when…”). Both HTTP and stdio servers import these automatically; HTTP responses includenext_steps— add a handler innext_steps.pyif the new tool should suggest follow-ups. - CLI handler: Add a subcommand in
cli.pywhen the tool should be human-invokable from the terminal (optional for agent-only tools). - Tests: Add tests in
tests/— at minimum an engine integration test; add CLI tests if you added a subcommand.
Keep multi-agent safety in mind: long-running writes (analyze, update) must stay under the process exclusive lock; readers should not block writers longer than necessary.
- Bump
pyproject.toml[project].versionandchisel/__init__.py__version__together (CI runspython scripts/check_version.py). - Add a
CHANGELOG.mdsection for the release. - Tag
vX.Y.Zand push — PyPI publishes via.github/workflows/publish.yml(trusted publishing). - Create a GitHub Release with notes from the changelog.
Users can register custom AST extractors (e.g., tree-sitter-backed) without modifying Chisel:
from chisel.ast_utils import register_extractor, CodeUnit
def my_python_extractor(file_path, content):
# Your tree-sitter/LSP logic here
return [CodeUnit(file_path, "func_name", "function", 1, 10)]
register_extractor("python", my_python_extractor)Custom extractors override the built-in regex ones for the registered language.
See docs/CUSTOM_EXTRACTORS.md for CHISEL_BOOTSTRAP and third-party parsers (tree-sitter, etc.) in user environments — Chisel stays stdlib-only.
Use imperative mood in the subject line. Keep the subject under 72 characters. Include a body when the change is non-trivial, explaining what changed and why.
Examples:
Add unit-level churn via git log -L
Wire get_function_log() into engine.analyze() so each
function gets its own churn score alongside file-level stats.
Fix Go import parsing for aliased imports