ravenclaw
Deterministic code navigation and refactoring for coding agents: find a symbol's definition and every real use of it, rename it, move it, or rename the file that holds it, and have every import follow. It resolves bindings (which name refers to which definition), not text.
This README is written for an agent that has never seen the repository. Each section stands on its own.
- When to use it
- Install
- Install the skills
- The operations, one call each
- Settings
- Which files a scan reads
- Catalog and hygiene checks
- Languages, and TypeScript today
- Known limitations
- Errors you can see
- For contributors
When to use it
Use ravenclaw instead of grep, sed or a hand edit when the job is "every
place this particular thing is used". Text search cannot tell two unrelated
functions that share a name apart, matches the name inside comments and
strings, and misses uses written differently (from .area import compute_area as area_of, a relative import, a re-export). ravenclaw parses the code with
tree-sitter, builds the scopes,
and follows the binding, so it touches exactly the occurrences that are the
same thing, and nothing else. It follows an aliased import to the definition
(a rename edits the import and the alias keeps working), but find_references
lists the import itself, not the calls made through the alias
(see find_references).
| You want to | Use |
|---|---|
| Go from a use to where it is defined | find_definition |
| Know who uses a symbol, or size a change before making it | find_references |
| Rename a function, class, method, variable or constant everywhere | rename_symbol |
| Rename or move a source file and fix every import of it | rename_file |
| Move a function or class to another file and fix every import | move_symbol |
| Check nothing is left dangling after one of the above | find_unresolved_references |
Do not use it for text that is not code (a README, a JSON file: use git mv), for a one-off edit inside one function, or for a language it does not
resolve yet (see Languages).
Every operation takes a position: a file, a 1-indexed line and a
0-indexed column that points at the identifier itself, at any of its
occurrences (its definition or any use: the same set comes back either way).
Install
Needs Python 3.10 or newer. Create a clean environment first if your default Python is older:
uv venv --python 3.12 .venv && uv pip install --python .venv/bin/python ravenclaw
# or, without uv:
python3.12 -m venv .venv && .venv/bin/pip install ravenclaw
then pip install ravenclaw as below, inside it. On Python 3.9 the failure
does not name the Python version, and one form is worse than a failure:
pip install ravenclaw there silently installs 0.1.0, the older unrelated
project (it pulls scikit-learn and has none of the operations below), and
installing the 0.2.0 wheel fails with ERROR: No matching distribution found for tree-sitter-javascript>=0.25.0. After installing, check
python -c "import importlib.metadata as m; print(m.version('ravenclaw'))"
prints 0.2.0 or newer.
pip install ravenclaw
or, as a command-line tool in its own environment:
uv tool install ravenclaw
uvx --from ravenclaw ravenclaw --help # one-off run, nothing installed
The PyPI project ravenclaw is this one from version 0.2.0 (published
2026-10-17 from github.com/idin/ravenclaw).
An older, unrelated project released earlier under the same name (0.1.0, and a
yanked 2020 release) is still listed there; the newest version is the one you
get. Check which you have with
python -c "import importlib.metadata as m; print(m.version('ravenclaw'))".
The operations are a Python library; ravenclaw run also exposes the three
read-only ones as a command line (below). The package depends on pandas,
tree-sitter (pinned below 0.26.0, see Known limitations),
tree-sitter-python, tree-sitter-javascript and tree-sitter-typescript.
Upgrade and uninstall
# Upgrade the library, then refresh the installed skills to match it.
pip install -U ravenclaw
ravenclaw install-skills --overwrite
# Remove the library.
pip uninstall ravenclaw
The skills install-skills copied stay where they are; delete the
ravenclaw-* folders in the skills folder (see
Install the skills) to remove them.
Installing without PyPI
ravenclaw makes no network calls when it runs; only installing needs a package
index. On a machine that has one, collect the files, then install from the
folder on the target machine. Installing a ravenclaw wheel by itself is not
enough: it still downloads its dependencies from an index (offline, uv pip install ravenclaw-0.2.0-py3-none-any.whl stops with "Packages were unavailable
because the network was disabled") unless they are in the same folder and you
pass --no-index --find-links (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 download ravenclaw -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. To build from a source tree,
pip wheel --no-deps git+https://github.com/idin/ravenclaw -w wheels/ (the
build needs hatchling, which pip wheel fetches itself).
Install 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 that tell it to call ravenclaw instead. Copy them into your skills folder:
ravenclaw install-skills
- Where:
$CLAUDE_CONFIG_DIR/skillswhen that variable is set to a non-empty value, otherwise~/.claude/skills.--destination <folder>installs into another skills folder. The folder is created when missing. - What: one folder per bundled skill, each holding a
SKILL.md. The command printsinstalled <skill> into <folder>per skill. - Safe by default: if any skill folder already exists, the command
installs nothing, prints which folder is in the way, and exits 1.
--overwritereplaces the existing copies wholesale (edits you made to them are lost); folders that are not ravenclaw's skills are never touched. - Exit codes: 0 installed; 1 refused (a skill folder exists and
--overwritewas not given, or the destination exists and is not a folder); 2 a usage error (an unknown option).
Inside Claude Code, the same skills can instead be installed 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.
The seven bundled skills
| Skill | Use it when |
|---|---|
ravenclaw-find-definition |
You have a use of a name and want the place it is defined ("go to definition", "which X is this"). |
ravenclaw-find-references |
You want every use of a symbol, to answer "who uses this" or to size a change before making it. |
ravenclaw-find-unresolved-references |
You finished a rename, move or deletion and want to check no import or name was left dangling. |
ravenclaw-rename-symbol |
You rename a function, class, method, variable or constant, however small the rename looks. |
ravenclaw-rename-file |
You rename or move a source file (a module) that other files import. |
ravenclaw-move-symbol |
You move a function or class to another file. |
ravenclaw-settings |
The user wants to configure ravenclaw: which folders it searches, which interpreter it checks imports against, preview before writing, or how much to report. |
Each skill's Requirements section says pip install ravenclaw and that it
needs Python 3.10 or newer. A skill is the instruction an agent reads; the code
it calls is in the operations below. The skills use the library's functions, so
a skill for an operation the ravenclaw run command does not offer (rename,
move) calls the Python function.
A skill named ravenclaw-install is not installed: it is a placeholder, marked
"NOT READY", for a future skill that installs ravenclaw into an environment of
its own (uv, conda, pipx or a virtual environment). Its prototype, the script
that picks the manager, lives with its plan in
playbook/plans/environment_install/prototype/ravenclaw_install/, and a
finished skill will replace it in a later release.
The operations, one call each
Every example below was run against a throwaway project with the installed
wheel (python -I from another directory). The project:
shapes/
pyproject.toml
src/shapes/__init__.py (empty)
src/shapes/area.py
src/shapes/report.py
tests/test_area.py
# src/shapes/area.py
def compute_area(width, height):
return width * height
def describe(width, height):
return f"area is {compute_area(width, height)}"
# src/shapes/report.py
from .area import compute_area, describe
def report():
total = compute_area(2, 3)
return describe(2, 3), total
# tests/test_area.py
from shapes.area import compute_area
def test_area():
assert compute_area(2, 3) == 6
Rules that hold for every operation below:
- Relative paths are read from the working directory, in the Python API as
well as in
ravenclaw run:file="src/shop/pricing.py"from another directory raisesFileNotFoundError. Run from the project root, or pass absolute paths. - Positions:
lineis 1-based andcolumnis 0-based, pointing at the first character of the identifier. For a top-leveldef compute_total, the line comes fromgrep -n "def compute_total" src/shop/pricing.pyand the column is the index of the name in that line, 4 afterdef:python3 -c "print(open('src/shop/pricing.py').read().splitlines()[4 - 1].index('compute_total'))". A wrong column (on thedefkeyword, on whitespace, past the name) raisesUnresolvedBindingError: None at <file>:4:0 - no enclosing scope binds this name(:4:0is the line and column you gave). - An empty scan is not an all-clear unless the scope covers the code. With
no
scope, an operation uses the project of the current working directory (see Settings);find_unresolved_referencesreturns an empty result both for a clean project and for a scope that scanned nothing (a mistyped folder, a working directory with no Python code). GitHub issue #357 tracks making that loud; until then this README documents the behaviour.
In the outputs, <project> stands for the project's absolute path: positions
carry absolute paths.
The command line: ravenclaw run <operation>
ravenclaw run takes the operation's keyword arguments as one JSON object on
standard input and prints one JSON document on standard output. Only the
read-only operations are offered: find-definition, find-references and
find-unresolved-references. The editing operations (rename, move) are Python
functions only (ravenclaw run rename-symbol is refused as an unknown
operation).
# find_definition from a use (report.py line 5, column 12 is the call to compute_area)
echo '{"file": "src/shapes/report.py", "line": 5, "column": 12}' | ravenclaw run find-definition
{"result": "definition", "value": {"type": "Reference", "position": {"type": "Position", "file": "<project>/src/shapes/area.py", "line": 1, "column": 4}, "name": "compute_area", "is_definition": true, "kind": "function", "module_segments": null, "relative_level": null, "imported_name_position": null, "is_from_import": false, "is_aliased_module_import": false, "is_order_dependent": false, "is_type_only": false}}
find-references returns {"result": "references", "value": [...]} with one
object of that same shape per occurrence; find-unresolved-references returns
{"result": "unresolved_references", "value": [...]}. A relative file is
read from the working directory. Failures print one error document on
standard error and exit 1 (see Errors).
find_definition
from ravenclaw.operations import find_definition
definition = find_definition(file="src/shapes/report.py", line=5, column=12)
# definition.name == "compute_area", definition.kind == BindingKind.FUNCTION
# definition.position -> <project>/src/shapes/area.py line 1, column 4
# definition.is_definition is True
Returns one Reference. Raises UnresolvedBindingError when the position has
no resolvable binding (whitespace, a keyword, the wrong column, or a file the
scope leaves out).
find_references
from ravenclaw.operations import find_references
for reference in find_references(file="src/shapes/area.py", line=1, column=4):
print(reference.position.file, reference.position.line, reference.position.column,
reference.is_definition, reference.kind)
Real output (paths shortened):
src/shapes/area.py 1 4 True BindingKind.FUNCTION the definition
src/shapes/area.py 6 22 False BindingKind.FUNCTION the call inside describe
src/shapes/report.py 5 12 False BindingKind.FUNCTION the call in report
src/shapes/report.py 1 18 True BindingKind.MODULE the name in `from .area import ...`
tests/test_area.py 5 11 False BindingKind.FUNCTION the call in the test
tests/test_area.py 1 24 True BindingKind.MODULE the name in `from shapes.area import ...`
An import that names the symbol is listed too (kind module, with its
module_segments), so the list is exactly what a rename would edit. A call
made through an alias is not listed: with from .pricing import compute_total as total_of in a file, find_references on compute_total lists that import
(kind module) but not the total_of(...) calls (checked against the built
wheel). A use through a module alias (pr.tax(5) after from . import pricing as pr) is listed. Leaving the calls out is right for a rename, where the alias stays and keeps
working, but it undercounts "who uses this": search the alias names too.
find_unresolved_references
from ravenclaw.operations import find_unresolved_references
unresolved = find_unresolved_references(scope=("src", "tests"))
# () on the project above. With `from .area import missing_thing` and a use of
# `undefined_name` added, it holds a Reference for each (name, file, line, column).
A scope-wide scan. It never raises for an unresolved name: listing them is its
purpose. It is also how you confirm a rename, a move or a deletion left
nothing dangling. An empty result means "nothing found", and is only an
all-clear if the scope covers the code: a scope naming a folder that does not
exist returns an empty result too. With no scope it scans the project of the
current working directory (its src and tests, or the folder itself when
it is in no project): from a folder with no Python code, or with a mistyped
scope=("srcc",), it returns () exactly as a clean project does, so an empty
result from outside the project root means nothing was scanned. Run it from the
project root or pass absolute paths. (#357 tracks making this loud.)
rename_symbol
from ravenclaw.operations import apply_edit_set, rename_symbol
# Preview: nothing is written, the EditSet comes back.
edit_set = rename_symbol(file="src/shapes/area.py", line=1, column=4,
new_name="calculate_area", auto_apply=False)
# EditSet with 6 edits, no file renames, no created files; edit_set.edits[0] is
# TextEdit(position=Position(file='<project>/src/shapes/area.py', line=1, column=4),
# old_text='compute_area', new_text='calculate_area')
print(apply_edit_set(edit_set=edit_set))
# {'num_files_changed': 3, 'num_edits_applied': 6, 'num_files_renamed': 0, 'num_files_created': 0}
# Direct: with auto_apply left at its default (true) the same call writes and reports.
rename_symbol(file="src/shapes/area.py", line=1, column=4, new_name="compute_area")
# {'num_files_changed': 3, 'num_edits_applied': 6, 'num_files_renamed': 0, 'num_files_created': 0}
It renames the definition, every use, and every import that names the symbol,
from any of those positions. A module that lists the name as a string in its
__all__ has that string renamed too. A report whose num_edits_applied is 1
for a symbol with obvious callers is a red flag: the scope probably leaves
the callers out. As with every operation below, an empty or too-small result
can mean the working directory or scope is not the project (see the rules
above).
Strings, comments and docstrings that mention the old name are not changed
("compute_total" in a string and # compute_total sums in a comment stayed as
they were in a checked run). After a rename, re-read the docstrings and
comments, and grep for the old name as text.
verbosity="files" adds edits_by_file (an edit count per absolute path);
verbosity="edits" adds edits (every {file, line, column, old_text, new_text}).
rename_file
from ravenclaw.operations import rename_file
rename_file(file="src/shapes/area.py", new_path="src/shapes/geometry.py")
# {'num_files_changed': 2, 'num_edits_applied': 2, 'num_files_renamed': 1, 'num_files_created': 0}
Moves the file on disk and rewrites every import of it (absolute, relative,
dotted, and from-imports of its package) and every relative import inside it.
A new path in a folder that does not exist creates the folder and its
__init__.py.
move_symbol
from ravenclaw.operations import move_symbol
move_symbol(file="src/shapes/geometry.py", line=1, column=4,
destination_file="src/shapes/maths.py")
# {'num_files_changed': 4, 'num_edits_applied': 5, 'num_files_renamed': 0, 'num_files_created': 1}
Takes a module-level function or class (decorators included) out of its file,
puts it in the destination (created when missing) with the imports it needs,
rewrites every consumer's import, and keeps an import in the old file when code
left there still uses the name. Afterwards report.py read
from .geometry import describe and from .maths import compute_area, and the
test read from shapes.maths import compute_area.
An attribute use through a module alias is rewritten to an absolute import,
even in a file that otherwise uses relative imports: with from . import pricing as pr and pr.tax(5), moving tax to src/shop/taxes.py added
import shop.taxes to that file and changed the call to shop.taxes.tax(5)
(checked against the built wheel). Expect that in the diff. Like the other
editing operations, it resolves relative paths from the working directory.
apply_edit_set
Shown above: writes an EditSet returned by a preview. The three editing
operations call it themselves unless auto_apply is false.
The ravenclaw.settings functions
See Settings for the examples.
Settings
An agent standing in a project should not have to restate where the code is on
every call, so each operation reads the arguments you leave out from settings.
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.
| Setting | Applies to | Meaning | Fallback |
|---|---|---|---|
scope |
every operation | The paths to search and edit. A relative path in a settings file is read from the folder that holds its .ravenclaw folder (the project root, or the home folder for the user's file). |
The project the target file belongs to (root found by pyproject.toml or .git): its src folder and, when it exists, tests; the whole root for a flat layout or a target outside src and tests. |
python_executable |
every operation | The interpreter whose environment decides which imports are standard-library or third-party. | The running interpreter. |
auto_apply |
rename_symbol, rename_file, move_symbol |
Whether to write the edits in the same call. | true: mechanical edits are the point. Set false to get the EditSet back for review. |
verbosity |
the same three and apply_edit_set |
What to report: totals, files or edits. |
totals, the cheapest. |
A target file, or move_symbol's destination, or rename_file's new path,
outside the given or stored scope raises ScopeError and writes nothing.
Write and read them with the functions of ravenclaw.settings. By convention
scope and python_executable describe the codebase (location project);
auto_apply and verbosity are one person's preferences (location user).
from ravenclaw.settings import (
read_all_settings, read_shared_setting, remove_shared_setting,
write_function_setting, write_shared_setting,
)
path = write_shared_setting(name="scope", value=("src", "tests"),
location="project", start_directory="/path/to/shapes")
# '/path/to/shapes/.ravenclaw/settings.json', containing {"scope": ["src", "tests"]}
write_function_setting(name="auto_apply", value=False, function_name="rename_symbol",
location="user", start_directory="/path/to/shapes")
write_function_setting(name="verbosity", value="files", function_name="rename_symbol",
location="user", start_directory="/path/to/shapes")
# ~/.ravenclaw/settings.json now holds {"rename_symbol": {"auto_apply": false, "verbosity": "files"}}
read_all_settings(start_directory="/path/to/shapes")
# {'shared': {'scope': ('/path/to/shapes/src', '/path/to/shapes/tests'),
# 'python_executable': '/path/to/venv/bin/python'},
# 'functions': {'apply_edit_set': {'verbosity': 'totals', 'auto_apply': True},
# 'move_symbol': {'verbosity': 'totals', 'auto_apply': True},
# 'rename_file': {'verbosity': 'totals', 'auto_apply': True},
# 'rename_symbol': {'verbosity': 'files', 'auto_apply': False}}}
read_shared_setting(name="scope", start_directory="/path/to/shapes") # the stored scope: a tuple of absolute paths
remove_shared_setting(name="scope", location="project", start_directory="/path/to/shapes")
# back to the fallback; returns the path of the settings file
read_all_settings shows the effective value of each setting, fallbacks
applied, not whether it is stored. read_function_setting reads one function's
setting. A write checks the shape of a value, not that it works: a scope
naming a folder that does not exist is stored, and then the position-based
operations raise ScopeError. A write for an operation that has no settings
(find_references, say) raises KeyError; an invalid value (verbosity="loud")
raises ValueError naming what is allowed. Always pass start_directory: it
decides which project file a project write goes to. Do not edit the settings
files by hand.
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.
Catalog and hygiene checks
Separate from navigation and rename, for exploring a codebase and judging its
conventions. Both are Python-only library calls (no ravenclaw run command).
Catalog parses a codebase once and exposes pandas.DataFrame summaries:
.classes, .functions (columns such as name, file, start_line,
end_line, kind, num_parameters, parameter_names, return_type,
has_docstring, is_recursive), .variables, .files (per-file counts),
.symbol_dependencies (source_name, source_file, source_line,
target_name, target_file, target_line, target_kind) and
.file_dependencies. Missing values are None, never NaN. .symbol_graph
and .file_graph each have __graph__(), returning a plain
{"nodes": {...}, "edges": {...}} dict for any graph-drawing tool (rendering
is out of scope).
import sys
from ravenclaw.python.catalog import Catalog
catalog = Catalog(scope=("/path/to/shapes/src",), python_executable=sys.executable)
catalog.functions[["name", "start_line", "end_line", "num_parameters"]]
# name start_line end_line num_parameters
# 0 compute_area 1 2 2
# 1 describe 5 6 2
# 2 report 4 6 0
len(catalog.symbol_dependencies) # 3
list(catalog.file_graph.__graph__()["nodes"]) # ['shapes', 'shapes.area', 'shapes.report']
find_docstring_issues(scope=...) scans for docstrings that are missing,
whose Args: section does not match the real parameters, or that are not
Google style; check_docstring checks one definition. The checks are
deliberately strict, with no built-in exceptions.
from ravenclaw.python.hygiene import find_docstring_issues
for issue in find_docstring_issues(scope=("/path/to/shapes/src",)):
print(issue.name, issue.kind, issue.position.line)
# DocstringIssue(position=Position(file='<project>/src/shapes/area.py', line=1, column=0),
# name='compute_area', kind=<DocstringIssueKind.MISSING: 'missing'>,
# missing_parameter_names=(), stale_parameter_names=())
Languages, and TypeScript today
Python files only. What happens with anything else:
| You do | What happens |
|---|---|
Give a .ts .tsx .mts .cts .js .jsx .mjs .cjs file to a position-based operation (find_definition, find_references, rename_symbol, rename_file, move_symbol) |
UnsupportedLanguageError: TypeScript and JavaScript files are not supported yet: <file>. ravenclaw resolves Python only. Nothing is written. |
Run a scope-wide scan (find_unresolved_references) over a folder holding such files |
The files are skipped, silently. |
ravenclaw run ... on such a file |
An error document on standard error, {"error": "UnsupportedLanguageError", "message": ..., "version": ...}, and exit 1. |
So a TypeScript project is not refactored by ravenclaw yet: use the editor's
or tsc's rename there, and do not fall back to grep-and-replace for a
Python name that also appears in a TypeScript file (the scan will not look at
it).
TypeScript and JavaScript are in progress on main, not usable. The
package ravenclaw.typescript already holds the grammar and parsing, the scope
tree (bindings and scopes), module records (imports and exports) and module
resolution (specifier to file, from tsconfig.json, jsconfig.json and
package.json). None of it is wired into the operations, so the behaviour in
the table above holds. The first release that includes TypeScript support is
planned for the next minor release. One resolver will serve all eight file kinds. The plan and
its milestones are in
playbook/plans/typescript_javascript/;
the order of future languages is
issue #43.
A language's parsing and scope resolution lives in its own subpackage behind
the shared Resolver protocol, since each language needs its own grammar and
scoping rules.
Known limitations
Honest and current as of this README; each item is an open issue or a stated design limit.
Python resolution
- PEP 695/696 type parameters (
Tindef f[T],class A[T]) are never bindings: find_definition, find_references and rename_symbol raiseUnresolvedBindingErroron them (#345). - A PEP 695
type X = ...statement never binds the alias nameX(#353). - Type parameter defaults (PEP 696) are not parsed by the pinned
tree-sitter-python0.25.0, so a comprehension in one binds the outer name (#346). - A lambda or comprehension in a decorator, type alias value, type parameter bound or class attribute annotation binds the outer name (#344).
- Scope spans are tracked by line, so on one line a use after a comprehension or lambda that binds the same name resolves to the inner variable (#155).
find_definitionon a method name defined in a class returns no resolved definition position in some cases (#355).- Undecided whether the module-path segments of an import statement belong in
find_referencesof a module (#172). - Documented, deliberately deferred, in the module docstring of
ravenclaw.python.scope_tree: a binding written after a star import under anifortry, or changed bydelorglobal, is judged by where it is written; dynamicgetattr/setattrattribute access; decorators that rewrite the decorated function's effective scope; PEP 420 namespace packages (a directory without__init__.pyis not a package).
Edits
move_symboldoes not refuse a destination that already binds the dependency's alias to a different definition; it can silently rebind the name (#178).__all__edits: a removal silently leaves the name listed when the statement is a bare tuple the grammar reports as an error (#200); cosmetic leftovers remain after some edits (#199). An__all__built by concatenation, comprehension or with an escape is not edited: search the old name as a string and change each export list by hand.- Import root of a package with no
__init__.pyfiles is a guess aboutsys.path(#112).
TypeScript (the in-progress work, not reachable through any operation yet)
- Two block scopes on one line are not told apart
(#289); a redeclared name
reports the first declaration (#247);
for-in/for-of heads, function names and parameters are skipped until their
milestones (#250); escaped
string names are kept raw in some imports and exports
(#332); a raw CR or CRLF in a
template literal name keeps the CR (#354);
the parse time limit is not yet a setting
(#349);
.jsx,.mjsand.cjsdeclaration mapping is unconfirmed (#234).
Environment
tree-sittermust stay pinned below 0.26.0: that release has a confirmed upstream use-after-free bug affecting node positions past line 256 of a file (playbook/bugs/resolved/2026-08-21_tree_sitter_026_start_point_use_after_free.md).- There is no skill yet that installs ravenclaw itself; the prototype is not shipped (see the skills).
The full list is gh issue list --repo idin/ravenclaw --state open.
Errors you can see
Every error the library raises is a RavenclawError (importable from
ravenclaw.core.errors), except the plain ValueError, KeyError,
TypeError, FileNotFoundError and FileExistsError that argument checks
raise. No operation writes anything when it raises.
| Error | Meaning and what to do |
|---|---|
UnresolvedBindingError |
The identifier at that position has no binding in the scope: wrong position, a keyword, or the defining file is outside scope. Fix the position or widen the scope. |
ScopeError |
The scope (given or stored) is invalid, or the target file, destination or new path lies outside it. Pass a scope that includes it, or fix the stored one. |
UnsupportedLanguageError (also a ValueError) |
The file is TypeScript or JavaScript (or another language with no resolver). See Languages. |
ParseTimeoutError |
A file's parse did not finish within the time limit, so the file is refused. |
StarImportOrderError |
A rename is refused because a use inside a function body may run before the from x import * that would carry the name. Move the star import above the function, or rename the earlier binding too. |
UnmovableDefinitionError |
move_symbol was asked to move something that is not a module-level function or class. |
DestinationDefinesNameError |
The destination already defines a module-level function or class of that name. |
DestinationBindsDependencyNameError |
The destination binds a name the moved definition's import would bind, to something else; writing the import would silently rebind it. |
DestinationIsSourceError (also a ValueError) |
The destination is the file the definition is moving out of. |
QualifiedUseNotRewritableError |
A dotted use of a moved module or symbol (pkg.mod.name) cannot follow the move; the message names the use and the advice. |
CircularImportError |
The operation would leave two files importing each other where that cannot work. |
UnsupportedDunderAllError |
A file's __all__ is built in a form that moving a name cannot keep true; the message names the statement. |
Through ravenclaw run, any failure (these, a bad argument, malformed JSON, a
missing file, an unknown operation) prints one JSON document to standard error,
{"error": "<class name>", "message": "...", "version": "<ravenclaw version>"},
and exits 1. Standard input that is empty or not a JSON object is a
ValueError.
For contributors
Needs uv. The suite needs no node, npm or network once the dependencies
are installed (the TypeScript and JavaScript tests ship their fixtures):
uv sync
uv run pytest
Tests mirror the source under tests/, and everything they write goes under
tests/_output/ (git-ignored). Plans, designs and the bug logs are in
playbook/:
plans/ for what is being built and in what order, bugs/ for the logs of
bugs found and fixed.
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 do not need to rely on an agent's judgement or guesswork.
Metadata
Release files for ravenclaw 0.3.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.3.0.tar.gz | 1.4 MB | Without provenance | Details for ravenclaw-0.3.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| ravenclaw-0.3.0-py3-none-any.whl | Python 3 | none | any | Details for ravenclaw-0.3.0-py3-none-any.whl |
Total release size: 1.8 MB
Release files / ravenclaw-0.3.0.tar.gz
| Download URL | ravenclaw-0.3.0.tar.gz |
|---|---|
| Size | 1.4 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2651cfeb18caf7fa5c185499a200fb7ac97b93b9e2d602b0d38c57a24af87b07
|
|
BLAKE2b-256 checksum How to use checksums |
61c4ae9aab993b056a23e91a4ddf8ac8d7664e93e5112b654d7baf2ea230449b
|
| 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.3.0-py3-none-any.whl
| Download URL | ravenclaw-0.3.0-py3-none-any.whl |
|---|---|
| Size | 464.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b02b6552a16c3aab1fd44910ff768c791cb4e294693ed8600a05de11c6890fd7
|
|
BLAKE2b-256 checksum How to use checksums |
db1c1498453b9923718d111870ba9704a0bd8c79a719dbffbb77cbf37db76b19
|
| 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.3.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.