Metadata-Version: 2.5
Name: ravenclaw
Version: 0.2.0
Summary: Deterministic code navigation and refactoring tools for coding agents (find-references, rename, more) via tree-sitter
Project-URL: Repository, https://github.com/idin/ravenclaw
Author-email: Idin <idin@idin.ca>
License-Expression: MIT
License-File: LICENSE
Requires-Python: >=3.10
Requires-Dist: pandas>=2.0.0
Requires-Dist: tree-sitter-javascript>=0.25.0
Requires-Dist: tree-sitter-python>=0.25.0
Requires-Dist: tree-sitter-typescript>=0.23.2
Requires-Dist: tree-sitter<0.26.0,>=0.25.0
Description-Content-Type: text/markdown

# 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](https://tree-sitter.github.io/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

```bash
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`):

```bash
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):

```bash
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 it
- `find_definition` — the definition site of an identifier's binding
- `rename_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 it
- `move_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 there
- `find_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 purpose
- `apply_edit_set` — write an `EditSet` to disk; the three editing
  operations call it themselves unless `auto_apply` is 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 `.ravenclaw`
  folder: 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 by
  `pyproject.toml` or `.git`): its `src` folder and, when it exists, its
  `tests` folder; the whole root for a flat layout with no `src`, or when
  the target file lies outside `src` and `tests`. A target file, or
  `move_symbol`'s destination or `rename_file`'s new path, outside a
  given or stored scope raises `ScopeError` and 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 the `EditSet` back unapplied,
  for review or a dry run, and write it later with `apply_edit_set`.
- `verbosity` (the same three, and `apply_edit_set`): how much of what was
  written to report: `totals`, `files` or `edits`. Falls back to `totals`,
  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:

```python
{
    "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:

```bash
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.
