How it works
How Shipmoor Code Review works
The local-first engine that reads a change the way a careful reviewer would, reports advisory findings, and never gates. Bring your own agent. The orchestration is deterministic, the judgment is not, and that is exactly why review advises while the scan stays the gate.
This is the reading layer. Where the deterministic scan answers structural questions with the same finding ids every time, Code Review asks the soft questions a reviewer asks out loud, and it answers them with an agent you bring. It is advisory by nature: it reports and exits zero. The scan is the only enforcer, and that split is the whole point.
1. The problem
The deterministic scan was built to answer questions that have one right answer: does this package exist, is this body a stub, is this a trust bypass. It is fast, reproducible, and correct because it never has an opinion.
A real reviewer asks a different kind of question, the kind that has no AST node and no registry lookup behind it:
- is this the right SQL, or just SQL that runs
- should that handle be closed before the function returns
- is this the discount, or the last item in the list
- does this
exceptswallow the failure the caller needed to see - does the change actually do what the surrounding code implies it should
These are reading-level judgments. They depend on intent, on the rest of the file, on what the change was for. No deterministic rule reaches them, and a rule that tried would be wrong often enough to be noise. The honest answer is that this layer needs a reader, and a reader is a model.
So Code Review draws a clean line. The scan keeps the questions with one answer and is allowed to block. Code Review takes the questions with a judgment call and is never allowed to block. A reading pass that can be confidently wrong must not hold up a merge, so it advises and the deterministic gate stays in charge.
2. Where it runs
Code Review runs in the same window as the scan, after the agent finishes and before a human spends attention on the change. The difference is what does the reading: your own agent, on your own machine, under your own provider relationship. Shipmoor hosts no model and uploads no source.
The agent produces an edit. The developer runs shipmoor review --agent claude. Shipmoor
resolves the change, builds a review request, drives your agent through a tool-use
conversation, collects the agent’s comments, maps them to findings, and exits zero. The
advisory findings feed the fix loop. They never become a verdict.
This is the deliberate contrast with the scan. The scan is a deterministic gate that can fail the build. Code Review is a second pair of eyes that cannot. Same moment, opposite authority.
3. The pipeline at a glance
A review is a one-shot pipeline, like the scan, but the reasoning step is delegated to a program you control. There is no daemon, no history database, and no Shipmoor-hosted model. Shipmoor owns the change selection, the prompt, and the mapping. Your agent owns the judgment.
shipmoor.scan.v1 findingsThe six stages:
- Acquire the change for the selected mode into a list of per-file diffs, with the introduced line ranges noted for later.
- Build the per-file review context from the bundled knowledge rules and the diff.
- Drive your bring-your-own-agent through a tool-use loop, one turn per subprocess call, so it can inspect the change and then report.
- Collect the agent’s comments from a thread-safe collector.
- Map each comment to a canonical
shipmoor.scan.v1finding stampedcategory: code_reviewandrule_id: shipmoor.review.llm. - Render the result to the terminal, JSON, and SARIF, and exit zero.
The one nuance the scan page does not have lives in stages 3 and 4. The orchestration around the agent is fully deterministic: the acquisition, the prompts, the tool plumbing, the token budget, the mapping. The agent’s judgment inside it is not. That is the reason review advises and the deterministic scan gates.
4. Bring your own agent
Shipmoor ships no model. The reviewing intelligence is a command you pass with --agent,
and Shipmoor talks to it across one small, neutral boundary: a single turn is a single
subprocess call. Shipmoor writes a JSON ChatRequest to the command’s stdin and
reads a JSON ChatResponse from its stdout. That is the entire contract.
ChatRequest model · messages · tools · max_tokens—agent command request JSON on stdinChatResponse read content and tool_calls onlyThe request side carries a model, the messages, the available tools, and a
max_tokens ceiling. The response side is read for exactly two things: the assistant
text and the tool calls. A finish_reason is parsed for fidelity but never consulted.
Completion is signalled by a tool call, not by a stop reason, which keeps the contract
unambiguous across providers.
The built-in presets. A raw coding-agent CLI like claude -p does not speak this
JSON wire. It takes a prompt and prints text. So Shipmoor ships an adapter inside the
binary and exposes it as two bare presets, claude and codex:
shipmoor review --agent clauderesolves the bare tokenclaudeto a re-exec of the very same Shipmoor executable as its own subprocess, through a hidden subcommand. That subprocess is the adapter.- The adapter reads the
ChatRequeston stdin, flattens the review prompt and the diff into a single instruction, drives your localclaudeCLI with it, parses whatever the CLI prints back into comments, and writes a well-formedChatResponseon stdout.
Because the adapter ships in the binary, the presets work for a plain curl | bash
install with no external script. The underlying command is overridable through an
environment variable when you want a different model or a wrapper.
Custom agents. Anything that can read the request on stdin and write the response on
stdout works the same way. Point --agent at your own program or a command with
arguments, and Shipmoor drives it exactly as it drives the presets. Your agent owns the
provider relationship: the model, the key, the rate limits. Shipmoor owns only the bytes
on the boundary.
Fail-safe is absolute. Every failure mode of the agent collapses to one safe outcome: a finding-free completion. A command that is missing, exits non-zero, times out, writes bytes that are not valid UTF-8, or returns output that cannot be parsed into findings ends the file cleanly with no comment. Shipmoor records zero advisory findings for that file and moves on. A broken reviewer can never invent an issue, and it can never fail your build, because review does not have that power in the first place.
5. The agentic loop
For each file in the change, Shipmoor runs a tool-use conversation with your agent. The
agent can call tools to inspect the change, then reports each issue with a code_comment
and ends the file with task_done. The loop keeps the conversation inside a token budget
across turns by compressing its own memory.
How a turn runs:
- Shipmoor sends the conversation with the tool definitions. The budget for a review file is a fixed number of turns, decremented before each call, so a budget of N means exactly N completions.
- The response is read for tool calls. If there are none, Shipmoor appends a short nudge
asking the agent to try again or call
task_done, and continues. An empty nudge round does not count against the budget. - Each tool call is dispatched. An inspection tool returns its result back into the
conversation as a tool result. A
code_commentis parsed into comments and pushed to the collector. Atask_doneshort-circuits the whole file immediately. - If several rounds in a row return no usable data, the loop stops on its own. So does budget exhaustion. Every normal ending is quiet: the file simply finishes.
Memory compression keeps the conversation in budget. A long inspection can grow the transcript past what fits. The loop manages this with three zones. The first two messages (the system prompt and the initial review prompt) are frozen and always kept. The most recent complete rounds that fit the budget form an active zone and are kept verbatim. The middle is summarized.
Two thresholds drive it. Crossing 60% of the budget kicks off a background compression job that does not block the turn. Crossing 80% compresses immediately. The summary is produced by your agent through the same wire, then folded back into the second frozen message as a labeled summary block so the system message stays at the top.
The rule that makes this safe is that it never truncates. If the summary comes back empty, if the compression call errors, or if there is nothing meaningful to compress, the loop keeps the original conversation unchanged. Staying briefly over budget is always preferred to discarding context. Files in the change are dispatched concurrently under a bounded worker pool, and a per-file timeout keeps any single file from stalling the run.
6. The tools the agent gets
Inside the loop the agent has six tools. Four are for looking at the change, two are for reporting. Every one runs locally against the working tree or a git ref. Shipmoor uploads no source: every byte the agent learns about the code is produced here.
file_readread a file window, up to 500 linesfile_read_diffthe diff blocks for given pathscode_searchtext search across the treefile_findlocate files by namecode_commentreport a findingtask_doneend the file| Tool | What it does |
|---|---|
file_read | reads a line window of the new version of a file, capped at 500 lines, with a truncation note when the range is larger |
file_read_diff | returns the parsed diff blocks for the requested paths, including related files that were changed but filtered out of review |
code_search | text search across the repository, backed by git grep with a non-git fallback, so the agent can check how a symbol is used elsewhere |
file_find | finds files by name from the git file list, so the agent can pull in a definition it needs |
code_comment | submits one or more review comments, each with the offending lines and an optional suggested fix |
task_done | signals the file is reviewed and ends the loop |
The reporting tools have a few guardrails worth naming. task_done is recognized by the
loop itself, before any registry lookup, so completion is never confused with a normal
tool. On a code_comment, the loop force-overwrites the comment’s path with the file
currently under review: cross-file comments are impossible, which closes the door on a
model hallucinating a path. Tool arguments arrive as a JSON string and are decoded behind
a guard that rejects non-finite numbers, so a malformed payload turns into an error
result the agent can recover from rather than a crash. The loop survives bad input by
design.
After the main pass, an optional review filter does one more turn that asks the agent to drop any comment the diff plainly contradicts, and those are removed from the collector before mapping. It is a quiet precision step, and if it errors it is simply skipped.
7. Knowledge and rules
The prompts and the per-language review guidance are bundled with the engine, not fetched. A layered resolver picks the rule text for each file deterministically, in declaration order, so the same file always gets the same guidance.
Precedence runs from most specific to least: a custom rule passed on the flag, then a
project rule at .shipmoor/rule.json, then a global rule in your home directory, then the
bundled defaults. First match wins, and the order is the contract. The bundled defaults
are keyed by path patterns, and the pattern that matches first supplies the review text
for that file.
The language scope is wider than the scan, on purpose. The deterministic scan covers four languages, because deterministic rules are expensive to get right and the scan gates. Code Review carries a much broader language set, because a reading pass can advise across many more file types without the risk of a false block. The mapping that turns a comment into a finding is grounded in a 67-extension allowlist and a per-language taxonomy that names Java, Kotlin, Rust, C and C++, C#, Ruby, PHP, Swift, shell, SQL, and many more, each carried with its natural language label.
This is a deliberate per-feature carve-out, not a change to the scan. The four scanner
languages still emit their exact canonical spelling, so a review finding on one of those
files also satisfies the strict scan enum. Every other extension carries its own language
label verbatim through the advisory code_review category, which is exempt from the
four-language enum. The scan’s scope is untouched. The reading pass simply reaches
further.
8. Change acquisition
A review sees the change through the same input modes the scan uses, so a review and a scan look at the same thing. One mode is active per run.
shipmoor review| Mode | What it reviews |
|---|---|
shipmoor review | the working copy: tracked changes plus a synthetic diff per untracked file |
shipmoor review --staged | the git index only, for a pre-commit style review |
shipmoor review --commit <ref> | a single commit against its parent |
shipmoor review --from <base> --to <head> | a branch range, from the merge-base to the head ref |
shipmoor review --scan | whole files rather than a diff, for a broader reading pass |
Acquisition turns the selected change into the per-file diffs the loop consumes, and it
binds the content reader to match the mode: the working tree for a workspace or staged
review, and git show <ref>:<path> for a commit or range, so the agent reads the file as
it exists at the reviewed point. The introduced line ranges are parsed from the diff
hunks and carried forward, so a finding can later be marked as introduced by the change
or pre-existing.
A small, honest detail: path-emitting git calls run with quoting turned off, so a file with non-ASCII bytes in its name is surfaced and reviewed rather than silently dropped. That path is then made safe at the mapping boundary so it cannot break the JSON or SARIF output.
9. From comment to finding
The seam between the engine and Shipmoor’s canonical contract is the mapper. It turns an
internal model comment into an ordinary, advisory shipmoor.scan.v1 finding. No parallel
serializer, no new schema: a review finding is a normal finding with a review stamp.
- +idSHM-…
- +rule_idshipmoor.review.llm
- +categorycode_review
- +languageverbatim label
- +severitymedium, fixed
- +confidencemedium, fixed
- +path
- +start_linemin 1
- +end_linemin 1
- +messagethe comment
- +recommendationsuggested fix
- +evidenceexisting_code, normalized
- +change_status
- +fingerprintSHA-256
The mapping pins a few things on purpose:
- The review stamp. Every review finding carries
category: code_reviewandrule_id: shipmoor.review.llm. The model emits no per-comment rule taxonomy, so all advisory findings share one Shipmoor-branded rule. - Severity and confidence are fixed at medium. The agent reports neither, and the category is excluded from the gate regardless of severity, so a fixed value is honest and deterministic instead of a fabricated per-comment score.
- The positioning sentinel. When the engine cannot resolve a comment to a real line,
it uses an internal
(0,0)sentinel. A finding requires a position of at least 1, so an unresolved comment anchors to line 1 and carriesevidence["position"] == "unresolved". That keeps a genuine line-1 finding distinguishable from a positioning failure. - Stable evidence. The
existing_codethe model copied can be rewritten in place by line relocation, so it is normalized before it enters the fingerprint. Two runs that relocate differently still produce the sameSHM-id for the same issue. - Change status. In a diff review, a finding whose lines intersect an introduced range
is marked
introduced, otherwiseexisting. In whole-file mode there are no change ranges, so the status isunknown. An unresolved finding cannot be attributed to a change, so it isunknowntoo.
The model’s private reasoning never escapes the engine. It is not part of the comment contract, it reaches no emitted field, and it is not in the fingerprint.
SARIF and JSON come from the scanner’s own emitters. The output bridge re-uses
Shipmoor’s existing JSON and SARIF writers verbatim, so review output is byte-shape
identical to scan output. SARIF carries a stable rule object for shipmoor.review.llm on
every result, and the document serializes as a plain shipmoor.scan.v1 file, which keeps
Community and IC parity and never bumps the schema version. Every consumer, the terminal,
JSON, SARIF, the IDE, the harness, and the CI poster, reads one finding shape whether it
came from a scan or a review.
10. It never gates
A review reports and exits zero. Always. The exit code reflects only how the run went, never what it found.
| Exit code | Meaning |
|---|---|
| 0 | the review ran, with or without advisory findings |
| 2 | a usage error: a bad flag, or an --agent command that does not resolve to a runnable program |
| 3 | the agent ran but the run failed: a transport, timeout, or engine failure |
There is no exit 1. The scan reserves exit 1 for a blocked gate, and review never returns
it. The split is structural, not a setting you could flip. The code_review category
is excluded from the --fail-on threshold by a single rule in the engine, and the scan
gate imports that same rule (with a literal fallback when the review engine is not
bundled). A code_review finding at any severity, even one the model calls critical,
contributes zero to the gate. The advisory category used by the claim check is excluded
the same way.
So the clean lane holds. The deterministic scan is the only enforcer. A review is a reading pass that can advise broadly precisely because it can never be coerced into blocking, and the agent-not-found check fails fast with a precise usage error rather than letting a misconfigured command collapse into a generic failure.
11. Local-first and private
A review is private by construction. Shipmoor hosts no model and uploads no source. The review request is piped into a subprocess you chose, and only that subprocess talks to a provider, over exactly the network path your agent already uses.
Shipmoor itself opens no network plane, hosts no model, and needs no API key of its own. The tools the agent uses to inspect the change all run locally against the working tree or a git ref. If you bring an offline or local agent, a review makes no network calls at all.
The trust boundary is the gate-versus-advisory split. The deterministic scan exit code is the only thing that can block a merge, and a review, being structurally gate-excluded, can never weaken it or be talked into blocking. In CI this is enforced by a split-workflow design so an untrusted fork pull request can never combine a source checkout with the credentials your agent uses.
12. Where it shows up
It is one engine. The same review, the same wire contract, and the same advisory findings appear everywhere Shipmoor runs.
- CLI.
shipmoor reviewdrives the review and prints advisory findings. - IDE. The editor renders review findings as information-level advisory diagnostics, kept visually and structurally separate from scan diagnostics.
- CI. A review posts advisory comments on the pull request, under the split-workflow trust model, without ever touching the check status.
- Agent Skills and Agent Harness. Both drive a review then fix loop: run the review,
read the advisory findings, fix them, re-run. Because the findings are ordinary
shipmoor.scan.v1findings, the loop reads them the same way it reads scan findings.
One engine behind all of them. The reason it can sit in every surface without risk is the same reason it advises rather than gates: the orchestration is deterministic, the judgment is the agent you bring, and the deterministic scan stays the only gate.
13. In one sentence
Reading, not enforcement. Code Review reads the agent’s change the way a careful reviewer
would, drives an agent you bring through one small JSON wire, maps its comments to ordinary
shipmoor.scan.v1 findings, and exits zero every time. The orchestration around the agent
is deterministic and local: the same acquisition, prompts, tools, and mapping, no hosted
model and no source upload. The judgment inside it is the agent’s, which is exactly why a
review advises and the deterministic scan stays the gate.
Try it
Code Review is a Shipmoor IC feature. Run it on your next agent-authored change with the agent you already use:
cd path/to/your/repo
shipmoor review --agent claude
It runs locally, hosts no model, and uploads no source. Read the Code Review overview for the product view, or see how the deterministic scan works for the gate it sits beside.