ravenclaw
Deterministic code navigation and refactoring tools for coding agents.
When an agent needs to find every reference to a symbol or rename it, text search over-matches and under-matches: it can't tell two identically-named but unrelated bindings apart, and it misses references written differently than the search text. ravenclaw resolves real semantic bindings instead, using tree-sitter to parse source into an AST and a scope-resolution pass on top, so operations touch exactly the occurrences that are actually the same binding.
Installing
pip install git+https://github.com/idin/ravenclaw
The PyPI project named ravenclaw is an older, unrelated project until a new
release replaces it.
Installing without PyPI
ravenclaw makes no network calls when it runs; only installing needs a
package index, for pandas>=2.0.0, tree-sitter>=0.25.0,<0.26.0,
tree-sitter-javascript>=0.25.0, tree-sitter-python>=0.25.0 and
tree-sitter-typescript>=0.23.2 (pandas also pulls numpy, python-dateutil,
pytz, tzdata and six), on Python 3.10 or newer. Building the wheel from a
source tree also needs hatchling, which pip wheel fetches itself. Either
bring the files across, or point at a mirror.
On a machine that has PyPI, collect the files, then install from the folder
on the target machine (the wheels must match the target's Python version and
platform; if it differs, add --only-binary=:all: --python-version <3.x> --platform <tag> to pip download):
pip wheel --no-deps git+https://github.com/idin/ravenclaw -w wheels/
pip download pandas tree-sitter tree-sitter-javascript tree-sitter-python \
tree-sitter-typescript -d wheels/
pip install --no-index --find-links wheels/ ravenclaw
With an internal mirror, pip install --index-url <mirror> ravenclaw, or set
PIP_INDEX_URL (uv: UV_DEFAULT_INDEX) once.
The plugin route (/plugin marketplace add idin/ravenclaw) clones from
GitHub, so it needs the network; ravenclaw install-skills does not, once
the package is installed.
Installing the skills
The library alone does nothing for an agent that does not know to call it:
Claude Code still reaches for grep and hand edits. ravenclaw ships Claude
Code skills (such as ravenclaw-rename-symbol) that tell it to use
ravenclaw instead. Install them one of two ways.
With the package installed, copy them into your skills folder
($CLAUDE_CONFIG_DIR/skills when that is set, else ~/.claude/skills;
--destination picks another, --overwrite replaces an older copy):
ravenclaw install-skills
Or, inside Claude Code, install them as a plugin:
/plugin marketplace add idin/ravenclaw
/plugin install ravenclaw@ravenclaw
Claude Code does not update a plugin from this marketplace on its own: run
claude plugin update ravenclaw@ravenclaw in your shell, or turn on
auto-update for the ravenclaw marketplace in the Marketplaces tab of
/plugin.
Either way the skills call the ravenclaw package, so it must be installed
in the environment that runs them.
Operations
find_references— every real occurrence of an identifier's binding, including the definition, every usage, and every import that names itfind_definition— the definition site of an identifier's bindingrename_symbol— rename a binding from any site (its definition, any usage, or an import statement that names it)rename_file— rename a file and update every import/reference to itmove_symbol— move a function/class definition to a different file, auto-detecting its free-variable dependencies (adding whatever new imports the destination needs, or reusing what it already has), removing it from its old location, and rewriting every consumer that previously imported it from therefind_unresolved_references— a scope-wide diagnostic scan for every identifier usage or import with no resolvable binding; unlike the operations above, this never raises — enumerating every unresolved occurrence is its entire purposeapply_edit_set— write anEditSetto disk; the three editing operations call it themselves unlessauto_applyis false, so it is needed only to write a set held back for review
Which files a scan reads
A scope-wide scan should see the project's own source, not everything
that happens to sit under its folder, or it would resolve against (and
rename inside) installed copies of the same code. So a folder in scope
is walked skipping .git, __pycache__ and node_modules, any virtual
environment (a folder holding pyvenv.cfg, whatever it is called), any
conda environment (holding conda-meta), any folder of installed
packages (holding a *.dist-info folder), and every path git ignores.
A file named in scope directly is read even if git ignores it. Inside a
git work tree the ignore rules come from git itself, so git must be
runnable there: if it is not, the scan raises rather than silently
reading ignored files.
Settings
An agent standing in a project should not have to restate where the code
is on every call, so the operations read their arguments from settings
when the caller leaves them out. An argument passed explicitly always wins.
Otherwise the value comes from the project's .ravenclaw/settings.json
(found by walking up from the folder of the file the operation acts on, or
from the working directory for an operation on no one file), then the
user's ~/.ravenclaw/settings.json, then a fallback:
scope(every operation): the paths to search and edit. A relative path in a settings file is taken from the folder that holds its.ravenclawfolder: the project root, or the home folder for the user's file. Falls back to the project the target file belongs to (its root found bypyproject.tomlor.git): itssrcfolder and, when it exists, itstestsfolder; the whole root for a flat layout with nosrc, or when the target file lies outsidesrcandtests. A target file, ormove_symbol's destination orrename_file's new path, outside a given or stored scope raisesScopeErrorand nothing is written.python_executable(every operation): the interpreter whose environment external (stdlib/third-party) imports are checked against. Falls back to the running interpreter.auto_apply(rename_symbol,rename_file,move_symbol): whether to write the edits in the same call. Falls back to true, since mechanical edits are the point; set it false to get theEditSetback unapplied, for review or a dry run, and write it later withapply_edit_set.verbosity(the same three, andapply_edit_set): how much of what was written to report:totals,filesoredits. Falls back tototals, the cheapest.
ravenclaw.settings reads and writes these files
(write_shared_setting, write_function_setting, read_all_settings).
Catalog
ravenclaw.python.catalog.Catalog(scope=..., python_executable=...) parses
a codebase once and exposes pandas.DataFrame summaries of its structure,
for exploration and dependency visualization rather than
navigation/rename: .classes (inheritance, method-kind counts,
decorators), .functions (parameters, return type, decorators, whether a
function calls itself), .variables (every
variable/constant/attribute/parameter binding, with type annotations and
enclosing class/function), .files (per-file counts, aggregated from the
properties above), and two dependency tables — .symbol_dependencies (one
row per "this symbol references that symbol" edge) and
.file_dependencies (a groupby of .symbol_dependencies by file, so
the two can never disagree). Every property is computed once and cached;
accessing several never re-parses the same files. Every table preserves
None for missing values rather than pandas' usual NaN coercion
(convert_rows_to_dataframe builds each column as an explicit dtype=object
pd.Series).
.symbol_graph and .file_graph (the folder tree plus
.file_dependencies) each have a __graph__() method returning a plain
{"nodes": {...}, "edges": {...}} dict — a data-only interchange contract
with no import in either direction between ravenclaw and any
graph-visualization consumer (e.g. coralreef). Both sides only need to
agree on the dict's shape:
{
"nodes": {node_id: {...attrs}},
"edges": {(source_id, target_id): {edge_id: {...attrs}}},
}
Graph rendering itself is intentionally out of scope for this library — see a separate package for that.
Hygiene checks
Separate from navigation/rename, ravenclaw.python.hygiene judges whether
existing, correctly-resolved code follows conventions — starting with
find_docstring_issues (a scope-wide scan) and check_docstring (a single
definition), which detect missing docstrings, Args: sections that don't
match a function's real parameters, and docstrings that don't follow Google
style (identifying NumPy, reST/Sphinx, or plain style instead). Hygiene
checks are deliberately strict with no built-in exceptions — see
playbook/bugs/ for the project's own dogfooding history.
Languages
Python only, for now. A language's parsing and scope resolution lives in
its own subpackage (ravenclaw.python) behind the shared Resolver
protocol, since each language needs its own grammar and scoping rules; a
new language is added the same way. Files in a language without a
resolver are skipped by scope-wide scans and refused by position-based
operations.
Testing
The test suite needs no node, npm or network: the TypeScript and
JavaScript tests ship their fixtures instead of installing packages to
resolve against. After the dependencies are installed (uv sync needs the
network once), the suite runs offline:
uv sync
uv run pytest
Where ravenclaw cannot guarantee a result without an external tool, its error names the tool to install.
Known limitations
The Python resolver's known, deliberately deferred gaps (documented, not
silently broken) are listed in ravenclaw.python.scope_tree's module
docstring: the star-import limits (#88, #89, #90), dynamic getattr/setattr
attribute access, decorators that rewrite a function's effective scope, and
PEP 420 namespace packages.
tree-sitter must stay pinned below 0.26.0 — that release has a confirmed
upstream use-after-free bug affecting node position data past line 256 of a
file. See playbook/bugs/resolved/2026-08-21_tree_sitter_026_start_point_use_after_free.md.
Scope of the project
Not limited to the operations above — the goal is the full category of code-development-lifecycle operations that have a deterministic correct answer, so they don't need to rely on an agent's judgment or guesswork.
Metadata
Release files for ravenclaw 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| ravenclaw-0.2.0.tar.gz | 1.1 MB | Without provenance | Details for ravenclaw-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| ravenclaw-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for ravenclaw-0.2.0-py3-none-any.whl |
Total release size: 1.5 MB
Release files / ravenclaw-0.2.0.tar.gz
| Download URL | ravenclaw-0.2.0.tar.gz |
|---|---|
| Size | 1.1 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3df7b135d774911b541d164245216e8251dfb2777f21e7eeac946d507f60b1d6
|
|
BLAKE2b-256 checksum How to use checksums |
6e08468526130f9f68abf3141d63cd5bfe6659f550f97933a924bb5136a1bc21
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / ravenclaw-0.2.0-py3-none-any.whl
| Download URL | ravenclaw-0.2.0-py3-none-any.whl |
|---|---|
| Size | 397.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ff04516e505633130b389ca499c45703c821f77565603573c085c40fb3531743
|
|
BLAKE2b-256 checksum How to use checksums |
e3051f59bc0d0fab45074486441dd880ecc5374e0c78137ff8bdc9f45aeb9db8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for ravenclaw 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.