READSEEK(1)

NAME
readseek - structural source-code reader with stable line hashing

SYNOPSIS
readseek [-h|--help]
readseek [-V|--version]
readseek [--readseek-dir dir] command ...
readseek detect [--language language]
    (--stdin path | target)
readseek read [--language language]
    [--start line]
    [--end line|--limit n]
    (--stdin path | target)
readseek map [--language language]
    (--stdin path | target)
readseek check [--language language]
    (--stdin path | target)
readseek symbol [--language language] [--line line]
    (--stdin path | target) [--name qualified-name]
readseek identify [--language language]
    [--line line] [--column column]
    (--stdin path | target)
readseek def [--from-identify] [--format format] [-c|--cached] [-o|--others]
    [-i|--ignored] [--language language]
    target [name]
readseek refs [--format format] [--scope --line line [--column column]]
    [-c|--cached] [-o|--others]
    [-i|--ignored] [--language language]
    target name
readseek rename --line line [--column column] --to name
    [--workspace dir] [-c|--cached] [-o|--others]
    [-i|--ignored] [--apply] [--language language] target
readseek search [-c|--cached] [-o|--others]
    [-i|--ignored] [--language language]
    target pattern
readseek init [path]

DESCRIPTION
readseek is a command-line tool that reads source files and outputs pretty-printed JSON. It is designed for scripts, editors, and coding agents that need stable addressable source context. Every output line carries a hashline: a line number, a short 3-character hash, and the original text. These hashes are stable anchors that let subsequent calls refer back to specific lines without depending on shifting line numbers. Diagnostics and errors are written separately from JSON output, making the tool safe to pipe into other programs.

INPUTS AND TARGETS
Most commands take a file or directory target on the command line. The detect, read, map, check, symbol, and identify commands can alternatively read from standard input with --stdin path, where path supplies a filename used for language detection and output metadata. You cannot combine --stdin with a positional target. A target path may carry a suffix that selects a specific line:
    file:line
    file:hash
Line numbers are one-based. A hash suffix is a 3-character string that was previously emitted by read, map, or search output. For symbol, you can also pass --name with a qualified or unqualified symbol name. If the name matches more than one symbol the command reports an error. For def, the --from-identify flag reads a JSON object previously produced by identify. The lookup tries symbol.qualified_name first and falls back to identifier.text.

LANGUAGES
readseek detects the language from the file name, extension, shebang line, and syntax hints. Pass --language when detection is ambiguous, when input comes from a synthetic path, or when a file has no meaningful extension. The following language identifiers are accepted: assembly, bash, c, cpp, csharp, css, dockerfile, gdscript, go, html, java, javascript, json, jsx, just, kconfig, latex, lua, make, markdown, meson, nix, perl, php, puppet, python, riscv, ruby, rust, sql, swift, toml, tsx, typescript, typst, xml, yaml, vimscript, and zig. The special value unknown treats the input as plain text and disables any parser-based features.

COMMANDS

detect
    Print detection metadata for the input: filename, detected language, whether a parser is available, whether the content is binary, MIME type, and syntax name.

read
    Print line numbers, hashes, and text for a range of lines. Without any range options the entire file is returned. Use a line or hash suffix on the target path, or pass --start, to set the first line. Use --end for the last line (inclusive) or --limit for a count; the two cannot be combined.

map Print the structural source map: each top-level symbol's kind, name, qualified name, start and end line, boundary hashes, the symbol's start_byte and end_byte span, and the name_byte offset of its name token. For languages that do not support symbol extraction the output contains an empty symbol list.

check
    Print parse diagnostics: the counts of tree-sitter ERROR and MISSING nodes and a list of each diagnostic's kind and start and end line. For languages without a tree-sitter parser the output contains zero counts and an empty list.

symbol
    Print the source range for one symbol. Pick it by --name (qualified or unqualified) or by --line. When only --line is given, the symbol that contains that line is resolved.

identify
    Print the requested cursor line, its hash, the identifier at the cursor position, and the enclosing mapped symbol. If --column is omitted, column 1 is used. Columns are one-based byte offsets and must be within the selected line. The identifier token is resolved with the language parser when available, falling back to an ASCII identifier scan otherwise. Identifier ranges in the JSON use a one-based inclusive start column and a one-based exclusive end column, plus start_byte and end_byte file offsets that mark the exact token span.

def Find definitions of a mapped symbol in a file or directory. The name argument matches a fully qualified or unqualified symbol name. With --from-identify and no name, the name is read from an identify JSON object on standard input. Directory search follows the same file-selection rules as search. Use --format plain to emit quickfix-friendly location entries instead of full definition objects.

refs
    Find all occurrences of an identifier in a file or directory. Output is a flat list of locations with file, line, column, line hash, line text, and the enclosing symbol when available. Directory search rules are the same as for search. Use --format plain to flatten symbol metadata into each location entry. With --scope and --line/--column, resolution is restricted to the lexical binding under the cursor within a single file. Each reported location then carries an occurrence field of definition or reference, and same-named occurrences that resolve to a different binding are excluded. Scope resolution is currently available for rust, c, and cpp; other languages report no resolvable binding.

rename
    Plan a binding-accurate rename of the lexical binding under --line/--column within a single file. The output is an edit plan: old_name, new_name, any conflicts the new name would introduce, and an edits array with the line, column range, byte range, occurrence kind, and the line hashline for each edit. Without --apply the command does not modify files; it emits the plan for a caller to apply. With --apply the edits are written to the file, but only after refusing on any conflict and re-verifying that every edited line's hash still matches the plan, so a file that changed since planning is rejected rather than corrupted. The reported applied field indicates whether the file was written. Rename uses the same scope resolution as refs --scope and is available for the same languages.

    With --workspace the rename expands across the given directory or repository. The cursor file stays binding-accurate; every other file is matched by name, the same way refs matches, and the additional files appear in an others array of per-file plans. Because readseek has no cross-file symbol resolver, an occurrence in another file is renamed only when it does not resolve to a local declaration there, so a same-named local binding is left untouched; files in languages without binding support are matched by name alone. Directory selection honours the same -c/-o/-i Git flags as refs. With --apply every file is verified first and only written if all pass, so a conflict or hash drift in any file aborts the whole rename without leaving a partial result.

search
    Run an AST pattern against a file or every supported source file under a directory. Files that cannot be parsed or are in unsupported languages are skipped. Pattern variables use $NAME to match a single node and $$$NAME to match zero or more sibling nodes. Output is grouped by file and includes the file hash, matched ranges, captures, boundary hashes, and hashlines for each match.

init
    Initialize a .readseek/ directory under path (defaults to the current directory). This directory holds the source-map cache used by other commands. If the .readseek/ directory already exists, init reinitializes it. readseek also refreshes the cache internally on each invocation.

OPTIONS

-h, --help
    Print usage information and exit.

-V, --version
    Print the program version and exit.

--readseek-dir dir
    Use dir as the .readseek/ directory instead of discovering one by walking up from the target, analogous to git's --git-dir. This global option must precede the command. For init it controls where the directory is created; other commands read and refresh their source-map cache there.

--language language
    Force a specific language instead of auto-detection. See LANGUAGES above.

--stdin path
    Read document contents from standard input instead of a file, using path for language detection and reported in JSON output.

--from-identify
    For def, read an identify JSON object from standard input and use it to choose the symbol name instead of passing name.

--format format
    For def and refs, select the output format: json (default) for the command-specific JSON shape, or plain for flat locations arrays with editor-friendly fields.

--line line
    One-based target line for symbol or identify, or the cursor line for refs --scope and rename.

--column column
    One-based byte column for identify or the cursor column for refs --scope and rename. Identifier ranges in JSON use a one-based inclusive start column and a one-based exclusive end column.

--scope
    For refs, resolve only the lexical binding under --line/--column in a single file instead of matching every occurrence of the name.

--to name
    For rename, the new name for the binding. Must be a plain identifier and differ from the current name.

--workspace dir
    For rename, expand the rename across the directory or repository dir. The cursor file stays binding-accurate; other files are matched by name (local shadows excluded) and returned in an others array. Honours the -c/-o/-i Git flags.

--apply
    For rename, write the planned edits. Refuses on any naming conflict and verifies each edited line's hash before writing. With --workspace every file is verified first and written all-or-nothing.

--start line
    First line to include when reading. If the target path also carries a line suffix, the two must agree.

--end line
    Last line to include when reading. Cannot be combined with --limit.

--limit n
    Maximum number of lines to include when reading. Cannot be combined with --end.

-c, --cached
    When searching a directory inside a Git repository, or expanding rename --workspace, search tracked and indexed files.

-o, --others
    When searching a directory inside a Git repository, or expanding rename --workspace, search untracked files that are not ignored.

-i, --ignored
    With --others, also include ignored untracked files. Inside a Git repository --ignored requires --others.

DIRECTORY SEARCH
The def, refs, and search commands accept either a single file or a directory. A file is searched as-is; a directory is searched recursively. Inside a Git work tree, directory searches are Git-aware. Without any selection flags, readseek searches both tracked/indexed files and untracked, non-ignored files. Pass --cached or --others to restrict to one group. Outside a Git work tree the selection flags are ignored and a normal recursive walk is used.

EXIT STATUS

0   Success.

1   Operational error: file not found, binary input, malformed input, cache failure, or symbol lookup failure.

2   Usage error: incorrect invocation or argument combination.

FILES

.readseek/maps/
    Binary source-map cache files keyed by file hash.

.readseek/def-index/
    Sharded JSON definition index used by def directory lookup.

.readseek/def-index.json
    Legacy definition index removed during cache refresh when present.

EXAMPLES
Detect the language of a file:
    readseek detect src/main.rs
Read lines 10 through 20:
    readseek read src/main.rs --start 10 --end 20
Read 12 lines starting from line 42:
    readseek read src/main.rs:42 --limit 12
Read unsaved buffer text from standard input:
    printf '%s\n' 'fn main() {}' | readseek read --stdin scratch.rs
Build a structural map of a file:
    readseek map src/main.rs
Look up a symbol by name or by the line it's on:
    readseek symbol src/main.rs --name run
    readseek symbol src/main.rs:270
Identify what's under the cursor at line 42, column 8:
    readseek identify src/main.rs:42 --column 8
Find definitions of a symbol in tracked files:
    readseek def -c src run --language rust
Find references to a symbol in tracked files:
    readseek refs -c src main --language rust
Search Rust functions with an AST pattern:
    readseek search -c src 'fn $NAME() { $$$BODY }' --language rust
Plan a rename of the binding under the cursor:
    readseek rename src/main.rs --line 42 --column 8 --to renamed
Apply the rename to the file:
    readseek rename src/main.rs --line 42 --column 8 --to renamed --apply
Rename a symbol across the repository (cursor file binding-accurate, other files matched by name) and apply it all-or-nothing:
    readseek rename src/main.rs --line 42 --column 8 --to renamed --workspace . --apply

BUILDING
Build from source:
    cargo build --release
The included Makefile also provides install and uninstall targets.

LICENSE
The readseek native program is licensed under LGPL-2.1-or-later.

SEE ALSO
cargo(1)