rq — Reference Query
rq finds the code you're looking for. Name a class, method, function, struct or constant, and rq ranks its definition first.
In a Rails checkout:
rq finds names, not behaviour. If you know what the code does but not what
it's called, use semantic search (contour) or
text search (rg) instead.
Why not grep / ctags / an LSP?
- grep / rg give every textual mention; rq gives the one place a symbol is defined, ranked.
- ctags is static and relevance-blind; rq ranks by match quality, your current repo, recency, and the branch you're on.
- an LSP is heavy — per-language, per-project, slow to warm. rq is one binary across all your repos: sub-millisecond search, warms itself on first use, and self-heals on edits.
Definitions come from Tree-sitter for Ruby, Rust, Go, Python, TypeScript, and JavaScript.
Install
Or build it yourself — rq needs Rust only at build time:
Usage
# (prefix-matched; r=ruby+rust; aliases rb/rs/ts/js)
Opening results
rq -o <query> jumps to the best match in your editor. On a terminal with
several matches it asks you to choose; otherwise it takes the top hit. The
launcher is, in order:
RQ_OPEN, a command template:{file}is the absolute path,{line}the line,{}both aspath:line. A template with none of them getspath:lineappended as its last argument.- VS Code (
code). $VISUAL, then$EDITOR.- Nothing found: rq prints
path:line.
RQ_OPEN='vim +{line} {file}' RQ_OPEN=subl
rq -w <query> does the same in the browser. It opens the match on the repo's
git host (https://<remote>/blob/<sha>/<file>#L<line>), pinned to a commit so
the link survives the branch moving: HEAD, or if HEAD isn't pushed yet, the
newest pushed commit in its history. A result from another repo (-a) links to
that host's default branch, since rq doesn't know what's checked out there. The
launcher is $BROWSER, then open/xdg-open, else rq prints the URL.
For an interactive fzf picker, script/rq-open is a small wrapper around rq.
docs/EDITORS.md covers it, the VS Code extension
(Cmd-click Go to Definition) and Neovim.
Ranking from where you are
--anchor FILE:LINE[:COL] tells rq where the question comes from — an editor's
cursor, or the file an agent is reading. Definitions in the classes and modules
enclosing that line rank first, then the same file and nearby directories.
It reorders and never filters, so it's safe whenever you know the file. FILE
is relative to the current directory; COL is accepted and ignored. rq records
no inheritance, so an inherited method gets no credit from the enclosing class.
The VS Code extension passes --anchor on every click.
For agents / scripts
-j/--json (array) and -J/--ndjson (one object per line) are the structured
surface for editors, scripts, and AI agents. Every command honors them, not just
search.
Result fields
A search result, a --show result and a --symbols row share one shape. A field
that doesn't apply is omitted, never null.
| Field | Present | Meaning |
|---|---|---|
name |
always | The symbol's name. |
kind |
always | class, module, method, function, struct, enum, trait or constant. |
language |
always | ruby, rust, go, python, typescript or javascript. |
file |
always | Path relative to root. |
root |
when rq knows a checkout for the repo (always for --symbols) |
Absolute checkout root. Per result, because -a spans repos: join root and file to read it. |
line |
always | 1-based first line of the definition. |
end_line |
when known | Last line: line..=end_line is the whole definition. |
parent |
when nested | The enclosing scope, e.g. ActiveRecord::Migration. |
visibility |
when the language expresses one | public, crate, private or protected. |
repo |
always | Repo identity: github.com/org/repo, or local:/abs/path. |
source |
search | index, or live when the result came from a live scan of a directory rq doesn't track (see Staying current). |
confidence |
search | 0–1: match quality × how far it leads the runner-up. Near 1 means take it. |
features |
search | The scoring signals that fired, strongest first. |
signature |
when the line is non-empty | The definition's first source line, trimmed. |
body |
--show, confident match |
The full line..=end_line source. |
declarations |
when more than one | How many places declare this name (a reopened module, impl blocks across files), folded into one result. |
also_in |
with declarations |
file:line of the other declarations. |
total |
search | Matches the window was drawn from, before --limit. |
explain |
--explain |
Feature name → score contribution, in whole points. |
query |
batch mode | The stdin line this row answers. |
Misses and exit codes
A miss is one {"status": …, "query": …} object instead of results:
status |
Exit | Meaning |
|---|---|---|
no_match |
1 | Definitive: nothing by that name. |
scope_not_found |
1 | Nothing in the scope you named; found_in says where the name does live. |
warming |
2 | The index is incomplete; retry. Rare, since a cold repo blocks until it can answer. |
interrupted |
2 | Indexing was stopped (Ctrl-C) before it could answer; run again. |
A match exits 0. Every miss is non-zero, so rq … && … reads as "found
something".
Errors
When a run fails under --json/--ndjson — a bad flag or value, an empty
query, an index that can't be opened, a --symbols file that doesn't exist —
stdout carries one object instead of results:
kind is stable, and code is the exit code. The message also goes to stderr.
Errors take codes from sysexits(3), so none is ever mistaken for a miss or a
retry:
kind |
Exit | Meaning |
|---|---|---|
usage |
64 | The command line is wrong: an unknown or conflicting flag, a bad value, an empty query — including flags before --json. Fix the command; it won't succeed on retry. |
not_found |
66 | A file the command names doesn't exist (--symbols). |
no_remote, launch |
69 | Nothing to hand off to: -w on a repo with no git host, or an editor or browser that won't start. |
internal |
70 | rq couldn't render its own output — a bug. |
database, index |
74 | The index can't be opened, read or written. |
Every code means one thing, so a script can branch on the number: 1 is
absent, 2 is ask again, and anything else is an error, which kind names.
rq --help lists the same table.
Batch mode
Pipe queries on stdin, one per line, with -J. rq resolves the repo and opens
the index once instead of per query, which on a large repo is most of what a
lookup costs:
|
Each row carries its query; a miss row carries its own status. The run exits
0 if any query matched, non-zero only if every one missed. --json can't frame
several result sets, so batch needs -J, and --show/--open/--web don't
apply. A cold repo is indexed up front, within the --wait budget, before the
first answer.
Reading the source
--show locates and reads in one call. When the top match's confidence is at
least 0.85 it prints the full line..=end_line span (body in JSON); otherwise
it prints the ranked list, so it never dumps a definition it isn't sure about.
Other commands
rq --status --json emits coverage rows (repo, status, files, symbols).
rq --index --json emits this run's counts (files_added, symbols_added)
plus the repo's totals. rq --drop --json reports what it removed (repo,
files, symbols, dropped). Single-result commands emit one object.
Waiting on the index
--no-wait answers from whatever's already indexed instead of waiting on a
warming repo — say, right after a branch switch on a huge repo. A miss reports
warming (exit 2) so you can retry; warming continues in a detached background
process.
--wait <dur> caps how long a query may wait: 50ms, 2s, 1m, or bare
seconds (--wait 0 is --no-wait). It overrides RQ_WAIT_BUDGET_MS (default 1
minute) for that call.
File outline
rq --symbols <file> lists every definition in a file, in line order — a
structural outline, not a ranked search. Honors -k/--kind and -x/--lang, and
emits the same fields as a search result, minus the scoring ones.
Ranking
A query is matched and scored by an additive sum of named signals, and
--explain shows the sum for each result:
)
- match quality — exact > prefix > camel/underscore abbreviation > subsequence
- visibility — public API edges out private/protected helpers (Rust
pub, Rubyprivatesections, Python_underscore, Go capitalization, TypeScript member modifiers and ESMexport) - qualifier — a scoped query (
Foo::Bar,Foo#bar,Foo.bar) keeps only the definitions inside that scope;Foo.newfinds the constructor, or the class itself when it inherits one - path — the query also matches the file's name
- current repo — results are scoped to the repo you're in by default
(
-a/--all-reposto search every indexed repo) - recency — symbols in recently edited or committed files
- branch — on a feature branch, files you're changing vs the trunk (and their directory neighbors) — where you're most likely working
- anchor — with
--anchor, definitions enclosing that line (enclosing), then those in the same file and nearby directories (proximity)
Fewer, better, ranked results are the goal — not completeness.
Staying current
You rarely run rq --index by hand. The first query in a git repo warms the
index, files you're changing on this branch first, and once your answer prints
a detached, low-priority process finishes the sweep in the background. A
cold repo is the exception: the first query indexes until it can answer
rather than report a false miss. On a terminal, a progress line appears if that
takes longer than half a second, and Ctrl-C stops it. It's a one-time cost — the
index persists and self-heals as you search, re-reading edited files and
reconciling added and removed ones.
A non-git directory isn't warmed on a stray query, but rq --index <dir> tracks
it like any repo under a local:<path> identity; otherwise rq live-scans it, so
it still answers at zero coverage. A live answer is marked "source": "live" in
JSON, -v notes the files it scanned and how long it took against its budget
(RQ_FALLBACK_BUDGET_MS, default 250 ms), and --usage counts these answers
apart. The index is a SQLite file at $RQ_DB
(default ~/.local/share/rq/rq.db).
Shell completions
Homebrew installs bash/zsh completions automatically.
Using with Claude Code
rq ships with a Claude Code skill (claude/rq-skill.md) so Claude reaches for it when locating a definition instead of grepping the tree. Install the marketplace plugin, which updates itself and brings the sibling skills:
/plugin marketplace add dpep/claude
/plugin install code@dpep
Or copy the one file:
The plugin is the better default; claude/INSTALL.md covers when it isn't.
Performance
The in-process search pipeline measures p50 ~160 µs, max < 0.25 ms on a mid-size
library (a few hundred symbols) — microseconds against a 50 ms first-answer
budget. Benchmark your own tree: make bench REPO=/path/to/repo.
Scope
rq indexes definitions — classes, modules, methods, functions. It does
not do call graphs, type inference, reference tracking, inheritance, or LSP
features. It's built for many repositories and millions of symbols, and never
assumes everything belongs to one project. Repository identity is normalized
from the git remote (github.com/org/repo), falling back to
local:/absolute/path.
See docs/ARCHITECTURE.md for the full design.
License
MIT © Daniel Pepper.