Autonomous code review and bug-hunting plugin, model-agnostic across the frontier AI stack: Claude Code and Claude Desktop, OpenAI Codex, Gemini CLI, and any other MCP client. Diff-scoped semantic review across five detection vectors β logic, security, concurrency, performance, and API contracts β with confidence scoring, linter deduplication, a false-positive suppression cache, interactive triage, and a pre-commit secret guard.
CodeFerret runs entirely on your machine. There is no CodeFerret service, account, or API key: the review is performed by a coding agent you already have installed, and your code never leaves your computer through anything CodeFerret does. That is the deliberate difference from hosted reviewers like CodeRabbit. The one setting that can make a network call β dependency auditing β is off by default and documented below.
# From a local clone
/plugin marketplace add ~/projects/code-ferret
/plugin install code-ferret@code-ferret-marketplace
# Or from GitHub
/plugin marketplace add musicofthings/code-ferret
/plugin install code-ferretbash mcp-server/build-mcpb.sh # produces dist/code-ferret.mcpbDouble-click the bundle (or drag it into Claude Desktop) to install. See
packaging/claude-desktop/README.md.
Register the MCP server in ~/.codex/config.toml and copy the
/ferret-review, /ferret-precommit, /ferret-triage custom prompts. See
packaging/codex/README.md.
gemini extensions install https://github.com/musicofthings/code-ferret
cd ~/.gemini/extensions/code-ferret/mcp-server && npm installThe repo root is a Gemini CLI extension (gemini-extension.json): it wires up
the MCP server, GEMINI.md context, and /ferret:review, /ferret:precommit,
/ferret:triage commands.
Run npm install in mcp-server/, then point the client at
node mcp-server/server/index.js (stdio). The host's model does the semantic
analysis, so CodeFerret works with whichever frontier model the client runs.
CodeFerret never calls a model API itself. The semantic analysis is done by whatever model your host application already runs β Claude Code, Claude Desktop, Codex, Gemini CLI, or any other MCP client β so it is model-agnostic by construction, with no provider registry, no key to configure, and no model for CodeFerret to keep up to date.
The standalone CLI works the same way: it shells out to an installed claude,
codex, or gemini and reads back the findings file. See CLI.
| Command | What it does |
|---|---|
/code-ferret:review [staged|head|<base>] |
Full semantic review of the diff. Writes findings to .ferret/last-review.json and prints a report with clickable file:line:col locations, severity tiers, confidence ratings, and ready-to-apply patches. |
/code-ferret:triage |
Steps through findings one by one: Accept & apply patch, Ignore pattern (suppresses it in future runs), Discuss, or Skip. |
/code-ferret:precommit |
Fast staged-only check. Reports only CRITICAL + HIGH-confidence blockers and secrets. First line is FERRET: PASS or FERRET: BLOCK. |
CodeFerret also ships a standalone CLI that runs reviews from any terminal by
delegating to a coding agent you already have installed (claude, codex, or
gemini) β no API key and no CodeFerret account.
cd cli && npm link # or, from the repo root: npm install -g ./cli
ferret doctor # verify the setup first
ferret # review committed + staged + unstaged changes| Command | What it does |
|---|---|
ferret [review] |
Review the current diff; plain-text report |
ferret review --agent |
Structured JSONL for coding agents and automation |
ferret review --light |
Fast policy: no analyzers, -U10 context, LOGIC + SECURITY only |
ferret review findings |
Replay the last review without re-analyzing |
ferret review --show-prompts |
Print the prompts from the last review |
ferret doctor |
Verify setup and connectivity; exit 1 on failure |
ferret stats [--rebuild] |
Review statistics from .ferret/history.jsonl |
Scope flags: --committed, --uncommitted, --include-untracked,
--base <branch>, --base-commit <sha>, --dir <path>, -c/--config <file>.
Contradictory combinations are rejected before a review starts, as is a base
ref that does not resolve β a review that cannot run always exits non-zero,
never "no changes found".
One JSON object per line on stdout, and nothing else β anything a human would read goes to stderr.
type |
When |
|---|---|
review_context |
Once, first: target, branch, base ref, file count, agent, light |
status |
Phase change: collecting_context, reviewing, or review_skipped |
heartbeat |
Every 15s while the agent is running |
finding |
One per finding; severity on the wire is critical/major/minor |
prompts |
--show-prompts only: the saved prompts from the last review |
complete |
Once, last, on success β including the empty-diff case |
error |
Once, last, on any failure |
Every failure path emits an error event, including ones that occur before a
review starts (bad flags, unresolvable base ref, no agent, a blocked lock), so
a consumer never has to distinguish "failed" from "produced nothing".
Reviews are serialized per repository through a .ferret/review.lock. A second
ferret review against the same repo fails immediately, naming the process
that holds the lock, rather than queueing or letting two agents overwrite each
other's results. A lock left behind by a killed process is reclaimed
automatically β either its process is gone, or it has aged out.
The MCP review prompt takes the same lock through the ferret_review_lock
tool, so a prompt-driven review and a terminal ferret review cannot run
against one repository at once. That path holds a 30-minute lease rather
than a process-lifetime lock: the MCP server stays alive between tool calls, so
its process being up says nothing about whether a review is still going, and
only an expiry can free one that was abandoned mid-conversation. Use
ferret_review_lock with status to see the holder, or release with force
to clear an abandoned lock without waiting out the lease.
Because the agent writes .ferret/last-review.json with its own file tools
rather than through the server, this lock is cooperative β it is honored by
the shipped review prompt, not enforced against an agent that ignores it.
Every ferret review spawns a real agent session against your existing
subscription. Use --light for the cheap path, and keep the git pre-commit
hook on the pure-bash secret scan rather than a full review.
Only the diff plus its surrounding lexical scope, never the whole repo. Each
hunk is shown with its complete enclosing function (git -W), not a fixed
number of context lines β a small edit deep inside a 600-line function still
arrives with the whole function attached, which a fixed -U window cannot do.
Standard .gitignore rules apply automatically; add a .ferretignore
(gitignore syntax) at the repo root to also skip generated files, schemas,
minified assets, etc.
Lockfile hunks (package-lock.json, Cargo.lock, go.sum, and nine others)
are omitted by default β thousands of machine-generated lines no semantic
vector can find a bug in. Their names still appear in the changed-file and
dependency-manifest lists, so version-bump analysis is unaffected. Set
FERRET_INCLUDE_LOCKFILES=1 to include them.
Detection vectors:
- LOGIC β off-by-one, boundary conditions, null/undefined flow, unhandled promises, resource leaks, infinite loops
- SECURITY β hardcoded secrets, SQL injection, XSS, unsafe deserialization, OWASP Top 10
- CONCURRENCY β races, deadlocks, non-atomic read-modify-write, unsynchronized shared state
- PERFORMANCE β O(NΒ²) on unbounded data, N+1 queries, redundant allocations
- API β breaking public contract changes, type-safety violations, SDK misuse
Style and lint issues are explicitly out of scope β findings matching an existing linter rule are deduplicated away.
Before semantic analysis, full reviews run known analyzers that are already
installed: ESLint, Ruff, ShellCheck, TypeScript, Semgrep with a checked-in
configuration, and the CodeFerret secret scanner. Tools are never installed
automatically or invoked through arbitrary package scripts. Normalized,
secret-scrubbed results are saved to .ferret/tool-results.json. Analyzers run
in the current environment and may load repository-owned configuration or
plugins, so run them only in a checkout you trust.
Dependency auditing (npm audit, pip-audit) is off by default: it is the
only analyzer group that contacts the network, sending your dependency tree to
the npm registry or the advisory API. Your source is never sent, but the call
happens, so enabling it is a deliberate choice β set tools.dependencies: true
in .codeferret.yaml. Everything else runs offline.
Copy .codeferret.example.yaml to .codeferret.yaml to configure review
behavior. Supported controls include:
reviews.profile:chill,balanced, orassertivereviews.minimum_severity:critical,warning, orsuggestionreviews.ignore: repository-relative glob patternsreviews.path_instructions: focused review policy for matching pathsguidelines: automatic discovery ofAGENTS.md,CLAUDE.md,.cursorrules, or additional repository policy filesreports: PR summary, walkthrough, risk assessment, and linked-issue validation controlstools: installed linter, type-checker, security, dependency, and CI-context controls with bounded execution time
/code-ferret:review runs use the working tree configuration and root-level
guideline context from collect-context.sh.
collect-context.sh is tuned through the environment. Defaults are chosen so a
review is cheap without losing the context needed to trace a failure.
| Variable | Default | Effect |
|---|---|---|
FERRET_FUNCTION_CONTEXT |
1 (0 in light mode) |
Show each hunk's complete enclosing function via git -W. 0 falls back to a fixed line count. |
FERRET_CONTEXT_LINES |
12 (6 light) |
Fixed context lines when function context is off. |
FERRET_OUT |
unset | Write the payload to this path and print only a compact index of section line ranges. Keeps a large diff out of the agent's transcript. |
FERRET_FILES |
unset | Colon- or newline-separated file subset. Scopes a batch when working a large diff in sequence. |
FERRET_SKIP_GUIDELINES |
0 |
Omit AGENTS.md/CLAUDE.md/.cursorrules bodies when the host agent already has them. |
FERRET_INCLUDE_LOCKFILES |
0 |
Include lockfile hunks in the diff. |
FERRET_LIGHT |
0 |
Speed over depth: fixed -U6, no per-file history. |
FERRET_DIR_PATHSPEC |
. |
Restrict everything to one subtree. |
FERRET_BASE_REF |
main |
Base ref for all mode. |
FERRET_INCLUDE_UNTRACKED |
mode-dependent | Force untracked files in. |
FERRET_MAX_TOOL_OUTPUT |
8000 |
Per-analyzer output cap, head+tail truncated so summary lines survive. |
scripts/plan-shards.sh <target> <n> prints n colon-joined file lists,
balanced by diff size rather than file count, ready to pass as FERRET_FILES:
bash scripts/plan-shards.sh main 5Collect once with FERRET_OUT, then give each reviewer one shard and the
shared context file. Collecting per-agent instead multiplies a review's cost by
the number of agents.
-
Every finding carries a confidence rating (HIGH/MEDIUM/LOW) based on structural proof, independent of severity (CRITICAL/WARNING/SUGGESTION).
-
Choosing Ignore pattern during triage records a structural hash in
.ferret/review-cache.json; matching findings are silently suppressed in future reviews. Manage the cache with:python3 scripts/fp_cache.py list python3 scripts/fp_cache.py add <file> <vector> "<message>" "<reason>"
Commit
.ferret/review-cache.jsonto share suppressions with your team.
The plugin ships a PreToolUse hook: whenever Claude Code runs git commit,
staged changes are regex-scanned for credentials (GitHub/AWS/Anthropic/Slack
tokens, private keys, generic high-entropy assignments) and the commit is
blocked if any are found. It is fast (pure bash/grep, no LLM call).
For commits made outside Claude Code, install the native git hook:
cp examples/git-pre-commit-hook .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
git config codeferret.root "$(pwd)"If the target repository is not the CodeFerret checkout, set
codeferret.root to CodeFerret's absolute installation path instead.
- Reviews run on your machine, through a coding agent you already installed. There is no CodeFerret service or account, and no telemetry.
- Code payloads are regex-scrubbed for credentials (
[REDACTED_SECRET]) before reaching the model. - Secret locations are reported, never secret values.
- Nothing CodeFerret runs contacts the network, with one opt-in exception:
dependency auditing (
tools.dependencies, off by default) calls the npm registry / advisory API. Your source is never transmitted. .ferret/state stays in your working tree; only the shareable false-positive cache (review-cache.json) is intended to be committed.
# Shell collectors, secret scanner, commit guards, and the analyzer runner
bash tests/run.sh
# Analyzer runner and model checks
python3 -m pytest tests/ -v
# CLI unit tests
cd cli && npm testtests/run.sh covers the context-collection controls that determine what a
reviewer actually sees: FERRET_OUT indexing, FERRET_FILES sharding (both
ends of the list), guideline omission, lockfile suppression and opt-in,
function context on/off/light, and shard partitioning.
code-ferret/
βββ .claude-plugin/plugin.json # Claude Code plugin manifest (+ marketplace.json)
βββ gemini-extension.json # Gemini CLI extension manifest (+ GEMINI.md)
βββ commands/ # /code-ferret:* (Claude Code .md) + ferret/*.toml (Gemini CLI)
βββ skills/code-ferret/ # review methodology + vector checklists + schema
βββ hooks/hooks.json # PreToolUse git-commit secret guard
βββ scripts/ # collectors, shard planner, analyzers, fp cache
βββ cli/ # standalone `ferret` CLI (delegates to your agent)
βββ mcp-server/ # MCP server + MCPB bundle build (Claude Desktop et al.)
βββ packaging/ # Codex prompts/config, Claude Desktop install guide
βββ examples/ # native git pre-commit hook