Review code changes by what they do, not line by line.
See the code
Review code changes by what they do, not line by line.
perspica reads a diff the way a careful reviewer would. It parses both sides, works out what actually changed (a rename, a new parameter, a moved function, a real logic change), sets aside the mechanical noise (reformatting, comments, rename-only lines, unchanged moves, generated files), and points at what a plain diff hides: references to names that no longer exist, calls that weren't updated for a new signature, code left unused.
None of that needs a model. perspica parses both versions with tree-sitter and follows the calls between them, so it's one local binary that answers in a few hundred milliseconds on a typical PR, gives the same answer every time, and needs no API key. An LLM is an optional extra: it groups the changes by intent, rates the risk of each group and writes a summary, and nothing leaves your machine unless you ask for it.
It understands TypeScript and JavaScript (including TSX and JSX), Python, Rust, Go, Java and C. Files in other languages still show up, as ordinary line diffs.
It's built for reviewing work done with coding agents. When the change came from your Claude Code or Codex session, perspica shows what you asked for in your own words and, with an LLM, marks which parts you asked for and which the agent decided on its own.
Reviewing colinhacks/zod#6587 (intent, checks, split view), pallets/click#3767 (reading order, analysis, terminal) and charmbracelet/bubbletea#1801 (noise). The agent-session example is illustrative. Full-quality video (MP4).
cargo install perspica
Or download a binary for macOS, Linux or Windows from Releases, or build from source:
git clone https://github.com/sshah03/perspica && cd perspica
cargo build --release # → target/release/perspica
Inside a repository:
perspica # review your current branch: commits, uncommitted and untracked files
perspica --web # …in the browser
perspica --pr 123 --web # a GitHub pull request (uses the `gh` CLI)
Other targets:
perspica --branch develop # against another base branch
perspica --staged # staged changes
perspica --git main...feature # a range (merge-base, like a PR)
perspica --git HEAD~3 # the working tree against a commit
perspica old.ts new.ts # two files
perspica --json # machine-readable output
Everything above works on its own. The optional LLM analysis (-s, or Analyze… in the viewer) runs through Claude Code, an API key or Ollama; see LLM analysis for setup.
Noise set aside. Formatting, comment-only edits, lines that differ only by a rename, code moved unchanged, generated and vendored files are tagged and collapsed, so the lines that carry real changes stand out. Tests and docs are their own tier.
Every change named. Renames (even when the body was also edited), signature changes (new required vs. optional parameters), dependency changes per module and symbol, extractions into new functions, moves within and across files, logic changes per function and method.
What a diff hides.
Flush doesn't raise false alarms);A reading order. Changed code from the entry points down to the changed functions they call (the order you'd want someone to walk you through it), and which of them the changed tests actually reach, with the call path.
What you asked for. If the change was made with Claude Code or Codex, perspica shows your prompts from the sessions that edited these files. With an LLM analysis, each group of changes is marked asked, with a short quote checked word for word against your prompts, or agent's call: the agent decided it on its own. Those are the ones to look at first.
Intent, risk and a summary (optional). With an LLM, changes are grouped by what they're for, each with a risk level and what to verify, plus a summary and the model's notes. Those are kept apart from what perspica found in the code, and labeled as less certain.
Optional. -s in the terminal, or Analyze… in the viewer (with a model picker and a Standard or Thorough depth).
Setup. There's no key to paste anywhere. perspica uses whichever of these you have:
claude auth login.~/.zshrc, ~/.bashrc) and open a new terminal:
export ANTHROPIC_API_KEY=sk-ant-… # or OPENAI_API_KEY
ollama pull gemma4:12b # about 16 GB of RAM; qwen3-coder:30b with 32 GB
perspica picks the best model you've downloaded (or choose one in the viewer, or with --model). Local models are slower and less precise than hosted ones: about a minute for a 10-file PR on an M4 Pro. OLLAMA_HOST points it at another machine.If none is found, Analyze… in the viewer shows these steps, and Check again picks up a Claude Code login or Ollama without restarting. When there are several, the order is --api-key / --provider or PERSPICA_API_KEY (and PERSPICA_PROVIDER), then ANTHROPIC_AUTH_TOKEN (gateways), ANTHROPIC_API_KEY, OPENAI_API_KEY, Claude Code, Ollama; --provider ollama picks the local model over the rest. Prefer the environment to --api-key, which leaves the key in your shell history.
The default Claude model is claude-opus-5-5; --model claude-sonnet-5-5 is faster, claude-haiku-4-5 is a quick first pass. What's sent: the list of classified changes and the changed code, never whole files (Thorough may also read definitions and files under 200 lines from the changed files). Analyses are saved per diff in .git/perspica/, so reloading or running again doesn't pay for the same analysis twice; --fresh reruns.
-s / Analyze…, and with Ollama, not even then.~/.claude/projects, ~/.codex/sessions) are read only for your own changes (uncommitted work, or commits authored with your git identity), never for someone else's PR. perspica says when it uses them; --no-sessions turns this off.127.0.0.1 and only answers its own page: other hosts (DNS rebinding) and other sites' requests are refused.The semantic analysis covers TypeScript and JavaScript, Python, Rust, Go, Java and C. Every other file is shown as a line diff, with syntax highlighting where available, whitespace-only changes collapsed and its role (test, docs, generated…).
File roles come from paths and codegen markers; override them in .gitattributes with linguist-generated, linguist-vendored, linguist-documentation, or perspica-role=source|test|docs|generated|vendored.
Usage: perspica [OPTIONS] [OLD_FILE] [NEW_FILE]
Arguments:
[OLD_FILE] Old file path
[NEW_FILE] New file path
Options:
-l, --language <LANGUAGE> Override language detection
-f, --format <FORMAT> Output format: tty (default), json, web [default: tty]
--json Shorthand for --format json
--web Open results in browser
--port <PORT> Port for web viewer (the next free port is used if taken) [default: 7890]
--no-open Don't open a browser tab (web mode)
--no-color Disable colored output
--show-noise Show mechanical hunks (formatting, renames, moves) in full in the terminal
--staged Diff staged changes
--git [<GIT>] Diff working tree against HEAD, or specify a commit range (a..b, a...b, or a ref)
--pr <PR> Review a GitHub pull request by number (uses the `gh` CLI)
--branch [<BASE>] Review the current branch against its merge-base with <BASE> (default: origin/HEAD, main or master). What `perspica` does with no arguments
-s, --summarize Group changes by intent, with risk and a summary, using an LLM. Sends the change list and changed code, never whole files
-d, --deep Thorough analysis: the LLM may first read definitions and small files from the changed files. Slower. Implies -s
--api-key <API_KEY> LLM API key (better: set it in the environment, see the README)
--provider <PROVIDER> With --api-key: anthropic (default) or openai. `ollama` needs no key and uses a local model
--model <MODEL> Model to use instead of the provider's default
--no-sessions Don't read the Claude Code or Codex sessions behind your change (your prompts are shown, and sent with -s). Never read for other people's changes
--fresh Run the LLM analysis again even if a saved one matches this diff
-h, --help Print help
-V, --version Print version
Viewer shortcuts: j/k next/previous change · n/p next/previous file or group · v mark viewed and go to the next unviewed file · f/i/r by file / by intent / reading order · m show/hide mechanical changes · u/s unified/split · / filter · ? all shortcuts.
perspica-core is a library with no I/O: source strings in, structured results out. It parses both sides with tree-sitter, fingerprints every item (token-level hashes that ignore formatting and comments), matches items across versions (by name, by shape for renames, by similarity for renamed-and-edited ones), classifies the differences, then links display hunks to them and tags the mechanical lines. Cross-file passes find moves between files, names that vanished, affected call sites, and a call graph (by name, gated on the caller importing the callee's module) for the reading order and test reach.
perspica (the CLI) collects the change from git (one git diff and one git cat-file --batch), renders it to the terminal, JSON or the embedded web viewer, and runs the optional LLM analysis, whose output is checked against perspica's own analysis (unknown entries dropped, "asked" quotes checked against the actual prompts).
crates/perspica-core/src parser · diff · classify · cross_file · annotate · roles · flow · languages/
crates/perspica-cli/src main · git · sessions · intel · llm · deep · web · render_tty
crates/perspica-cli/web the viewer (no build step)
tests/fixtures change-type fixtures, and real PRs from other projects (tests/fixtures/real)
Development: cargo test runs everything, including real pull requests from Flask and ky as fixtures.
MIT
Rust
74.4%
JavaScript
17.5%
CSS
6.7%
HTML
1.3%
Review code changes by what they do, not line by line.
See the code
Review code changes by what they do, not line by line.
perspica reads a diff the way a careful reviewer would. It parses both sides, works out what actually changed (a rename, a new parameter, a moved function, a real logic change), sets aside the mechanical noise (reformatting, comments, rename-only lines, unchanged moves, generated files), and points at what a plain diff hides: references to names that no longer exist, calls that weren't updated for a new signature, code left unused.
None of that needs a model. perspica parses both versions with tree-sitter and follows the calls between them, so it's one local binary that answers in a few hundred milliseconds on a typical PR, gives the same answer every time, and needs no API key. An LLM is an optional extra: it groups the changes by intent, rates the risk of each group and writes a summary, and nothing leaves your machine unless you ask for it.
It understands TypeScript and JavaScript (including TSX and JSX), Python, Rust, Go, Java and C. Files in other languages still show up, as ordinary line diffs.
It's built for reviewing work done with coding agents. When the change came from your Claude Code or Codex session, perspica shows what you asked for in your own words and, with an LLM, marks which parts you asked for and which the agent decided on its own.
Reviewing colinhacks/zod#6587 (intent, checks, split view), pallets/click#3767 (reading order, analysis, terminal) and charmbracelet/bubbletea#1801 (noise). The agent-session example is illustrative. Full-quality video (MP4).
cargo install perspica
Or download a binary for macOS, Linux or Windows from Releases, or build from source:
git clone https://github.com/sshah03/perspica && cd perspica
cargo build --release # → target/release/perspica
Inside a repository:
perspica # review your current branch: commits, uncommitted and untracked files
perspica --web # …in the browser
perspica --pr 123 --web # a GitHub pull request (uses the `gh` CLI)
Other targets:
perspica --branch develop # against another base branch
perspica --staged # staged changes
perspica --git main...feature # a range (merge-base, like a PR)
perspica --git HEAD~3 # the working tree against a commit
perspica old.ts new.ts # two files
perspica --json # machine-readable output
Everything above works on its own. The optional LLM analysis (-s, or Analyze… in the viewer) runs through Claude Code, an API key or Ollama; see LLM analysis for setup.
Noise set aside. Formatting, comment-only edits, lines that differ only by a rename, code moved unchanged, generated and vendored files are tagged and collapsed, so the lines that carry real changes stand out. Tests and docs are their own tier.
Every change named. Renames (even when the body was also edited), signature changes (new required vs. optional parameters), dependency changes per module and symbol, extractions into new functions, moves within and across files, logic changes per function and method.
What a diff hides.
Flush doesn't raise false alarms);A reading order. Changed code from the entry points down to the changed functions they call (the order you'd want someone to walk you through it), and which of them the changed tests actually reach, with the call path.
What you asked for. If the change was made with Claude Code or Codex, perspica shows your prompts from the sessions that edited these files. With an LLM analysis, each group of changes is marked asked, with a short quote checked word for word against your prompts, or agent's call: the agent decided it on its own. Those are the ones to look at first.
Intent, risk and a summary (optional). With an LLM, changes are grouped by what they're for, each with a risk level and what to verify, plus a summary and the model's notes. Those are kept apart from what perspica found in the code, and labeled as less certain.
Optional. -s in the terminal, or Analyze… in the viewer (with a model picker and a Standard or Thorough depth).
Setup. There's no key to paste anywhere. perspica uses whichever of these you have:
claude auth login.~/.zshrc, ~/.bashrc) and open a new terminal:
export ANTHROPIC_API_KEY=sk-ant-… # or OPENAI_API_KEY
ollama pull gemma4:12b # about 16 GB of RAM; qwen3-coder:30b with 32 GB
perspica picks the best model you've downloaded (or choose one in the viewer, or with --model). Local models are slower and less precise than hosted ones: about a minute for a 10-file PR on an M4 Pro. OLLAMA_HOST points it at another machine.If none is found, Analyze… in the viewer shows these steps, and Check again picks up a Claude Code login or Ollama without restarting. When there are several, the order is --api-key / --provider or PERSPICA_API_KEY (and PERSPICA_PROVIDER), then ANTHROPIC_AUTH_TOKEN (gateways), ANTHROPIC_API_KEY, OPENAI_API_KEY, Claude Code, Ollama; --provider ollama picks the local model over the rest. Prefer the environment to --api-key, which leaves the key in your shell history.
The default Claude model is claude-opus-5-5; --model claude-sonnet-5-5 is faster, claude-haiku-4-5 is a quick first pass. What's sent: the list of classified changes and the changed code, never whole files (Thorough may also read definitions and files under 200 lines from the changed files). Analyses are saved per diff in .git/perspica/, so reloading or running again doesn't pay for the same analysis twice; --fresh reruns.
-s / Analyze…, and with Ollama, not even then.~/.claude/projects, ~/.codex/sessions) are read only for your own changes (uncommitted work, or commits authored with your git identity), never for someone else's PR. perspica says when it uses them; --no-sessions turns this off.127.0.0.1 and only answers its own page: other hosts (DNS rebinding) and other sites' requests are refused.The semantic analysis covers TypeScript and JavaScript, Python, Rust, Go, Java and C. Every other file is shown as a line diff, with syntax highlighting where available, whitespace-only changes collapsed and its role (test, docs, generated…).
File roles come from paths and codegen markers; override them in .gitattributes with linguist-generated, linguist-vendored, linguist-documentation, or perspica-role=source|test|docs|generated|vendored.
Usage: perspica [OPTIONS] [OLD_FILE] [NEW_FILE]
Arguments:
[OLD_FILE] Old file path
[NEW_FILE] New file path
Options:
-l, --language <LANGUAGE> Override language detection
-f, --format <FORMAT> Output format: tty (default), json, web [default: tty]
--json Shorthand for --format json
--web Open results in browser
--port <PORT> Port for web viewer (the next free port is used if taken) [default: 7890]
--no-open Don't open a browser tab (web mode)
--no-color Disable colored output
--show-noise Show mechanical hunks (formatting, renames, moves) in full in the terminal
--staged Diff staged changes
--git [<GIT>] Diff working tree against HEAD, or specify a commit range (a..b, a...b, or a ref)
--pr <PR> Review a GitHub pull request by number (uses the `gh` CLI)
--branch [<BASE>] Review the current branch against its merge-base with <BASE> (default: origin/HEAD, main or master). What `perspica` does with no arguments
-s, --summarize Group changes by intent, with risk and a summary, using an LLM. Sends the change list and changed code, never whole files
-d, --deep Thorough analysis: the LLM may first read definitions and small files from the changed files. Slower. Implies -s
--api-key <API_KEY> LLM API key (better: set it in the environment, see the README)
--provider <PROVIDER> With --api-key: anthropic (default) or openai. `ollama` needs no key and uses a local model
--model <MODEL> Model to use instead of the provider's default
--no-sessions Don't read the Claude Code or Codex sessions behind your change (your prompts are shown, and sent with -s). Never read for other people's changes
--fresh Run the LLM analysis again even if a saved one matches this diff
-h, --help Print help
-V, --version Print version
Viewer shortcuts: j/k next/previous change · n/p next/previous file or group · v mark viewed and go to the next unviewed file · f/i/r by file / by intent / reading order · m show/hide mechanical changes · u/s unified/split · / filter · ? all shortcuts.
perspica-core is a library with no I/O: source strings in, structured results out. It parses both sides with tree-sitter, fingerprints every item (token-level hashes that ignore formatting and comments), matches items across versions (by name, by shape for renames, by similarity for renamed-and-edited ones), classifies the differences, then links display hunks to them and tags the mechanical lines. Cross-file passes find moves between files, names that vanished, affected call sites, and a call graph (by name, gated on the caller importing the callee's module) for the reading order and test reach.
perspica (the CLI) collects the change from git (one git diff and one git cat-file --batch), renders it to the terminal, JSON or the embedded web viewer, and runs the optional LLM analysis, whose output is checked against perspica's own analysis (unknown entries dropped, "asked" quotes checked against the actual prompts).
crates/perspica-core/src parser · diff · classify · cross_file · annotate · roles · flow · languages/
crates/perspica-cli/src main · git · sessions · intel · llm · deep · web · render_tty
crates/perspica-cli/web the viewer (no build step)
tests/fixtures change-type fixtures, and real PRs from other projects (tests/fixtures/real)
Development: cargo test runs everything, including real pull requests from Flask and ky as fixtures.
MIT
Rust
74.4%
JavaScript
17.5%
CSS
6.7%
HTML
1.3%