review¶
A reviewer that did not write the code writes review.md. One
adversarial pass, in an isolated session, bound to the tree.
| Role, driver side | The author handing over. Does not argue inside the reviewer's context, never edits review.md. Fixes, verifies, asks again. |
| Role, reviewer side | Adversarial. Judges against the spec and the contract, one verdict per criterion, with a pointer to the proof. |
| Who decides | atipspec check. |
| Produces | review.md |
How to run it¶
What happens¶
- The model runs
atipspec review password-reset. The CLI refuses while a task is not complete, evidence is stale or the contract is violated, and writes no packet. Otherwise it counts the review (see review rounds) and writes a packet to.atipspec/tmp/password-reset-review-packet.mdwith: the rubric, the delivery's risk and what it asks, the architecture contract, the living specs the delivery declares in its impact list, the accepted decisions that affect them, the spec, the plan, deferred items, an evidence summary, the files changed outside the plan'sscope, and the diff against the delivery's base commit plus untracked files. A diff over 200 KB is replaced by its--statand a truncated excerpt, with a note telling the reviewer to inspect the repository. Then the command printsreview N of Mand the workflow. - The model gives the packet to a reviewer in an isolated session: a
session that has not seen the conversation, given only the packet path. In
Claude Code this is a subagent; see clients for the
others. When the client cannot provide one, the reviewer still writes the
review and declares
session: shared. Withrisk: highorrequire_isolated_review, the model asks you to open a new session for the reviewer instead. - The reviewer reads the packet, inspects the repository, runs commands if reading is not enough, and writes:
---
tree: cd03ae507b3605b12a497fcbcf590894a0073076
reviewer: claude-code subagent
session: isolated
---
# Review: Password reset
## Criteria
- AC-001: PASS. tests/auth/test_reset.py::test_registered_email_sends_link
- AC-002: PASS. tests.auth.test_reset.test_unknown_email_same_response
- AC-003: FAIL. consume() checks the age but the boundary of exactly 30 minutes is accepted.
## Findings
- F1 [blocker]: shop/domain/reset.py imports shop.infrastructure.mail, forbidden by the contract.
- F2 [minor]: request() lacks a docstring.
## Notes
The spec does not say whether a used link may be reused; assumed no.
- The model runs
atipspec check password-reset:- A
FAILor ablocker: fix the code and verify again. Any change to the tree makes the review stale, so it runsreviewand the reviewer again. - A minor finding you decide not to fix now goes to
deferred.mdwith a reason:- F2: cosmetic, handled in the docs cleanup delivery. A blocker or major finding is fixed: only a risk exception under a trust policy defers it. Criteria can never be deferred.
- A
- When
atipspec reviewrefuses because the review rounds are used up, the model stops and presentsatipspec check password-resetwith the findings that remain. You decide.
What the gate checks in review.md¶
tree:is the packet's: the candidate the lastatipspec reviewcounted. A review of another candidate is stale, unless only task titles changed, and one written without runningreviewwaits for it (see review rounds).reviewer:names the client or model that wrote the review; without it,checkreports an error.session:isisolated(the reviewer never saw the author's conversation) orshared. A review that leaves it out counts asshared. A shared review is a warning, and an error when the spec saysrisk: highorrequire_isolated_reviewis on inconfig.yamlor in the trust policy. Any other value is an error. In the guided flow the reviewer declares the session; the declaration is recorded, shown in the dossier and acted on by the gate, but nothing measures it. The process runner holds it to the adapter's declaredisolated_review.- Every criterion has a verdict, except, with
risk: low, one whose test named inProof:passed in the current evidence:checknotes it with an info. AFAILblocks at every level. See risk. -
Every
PASSneeds a pointer the gate resolves, one of:- a test that passed in the current evidence's JUnit report, named as
the report names it (
tests.auth.test_reset.test_link), or as in a Proof entry; a name with spaces goes in backticks; path:lineorpath:line-line, a line that exists in a file of the candidate: outside.atipspec/and.gitand not ignored by git. The delivery's evidence logs count, even where git ignores*.log, asevidence/logs/FINAL-<run>-1.log:12from the delivery folder;path::test, such a file that holds that name as a word (a[param]suffix is ignored); the dossier shows it as a name found in the file, since the gate does not run it.
For a criterion marked
[manual], a description of what was done and observed, three words or more, also passes. AFAILneeds its reason.PASS. okis an error. While the review is stale its pointers are not judged, and while the evidence is not current an unresolved pointer is a todo: the test it names may be in the next run. A pointer that resolves shows that it names something real, not that it proves the criterion: the dossier shows what each one resolved to for you to judge, and Proof entries, required withrequire_proof, bind a criterion to its test: aPASSfor such a criterion that does not name that test is a warning. - a test that passed in the current evidence's JUnit report, named as
the report names it (
Review rounds¶
review_rounds in config.yaml (default 3) is how many reviews a delivery
gets per approved agreement, the first included: a first review and two of
corrected candidates. atipspec review counts a review each time it writes a packet for
another candidate than the last packet's, whatever review.md says: a review
that passed counts too, because the reviewer writes that file and the CLI
cannot tell its verdicts from ones edited afterwards. Writing the packet again
for the same candidate counts nothing, and neither does a packet that cannot
be built. Once the reviews are used and the candidate changes again,
review refuses, counting nothing, and names the decision: change the
proposal, raise review_rounds, or stop. Until then check reports the
used-up reviews as an error, whatever review.md says, and the dossier shows
the count. check takes only the review of the last packet atipspec review
counted: a review.md written for a candidate without running the command is
a todo that asks for it, so skipping it saves no review. The count lives in
review-rounds.json in the delivery folder, which the CLI writes, and starts
again when the approved agreement changes: the proposal of a guided delivery,
the spec of a legacy one. atipspec accept <slug> proposal starts the file,
so in a guided delivery created since 0.3.0 a missing file was deleted:
review refuses and check fails until you decide, by restoring it from git
or approving the proposal again, which starts a new count. The Claude Code
rules keep the client's edit tools off that file, not a shell command. When
the file is missing in a delivery approved under a trust policy, atipspec
review counts again from the review.md in the folder, as the first review;
in one created before 0.3.0 it counts from the next packet, since a
review.md written before names a tree of the earlier fingerprint. Raising review_rounds in a guided delivery changes config.yaml,
which the proposal's approval hashes, so the proposal is presented again. In a
legacy delivery (a fix created before 0.3.0 included), whose approval is the
spec's, no approval covers
config.yaml: a raised limit applies at once, so the model raises it only
when you say so, and the dossier shows Review N of M. atipspec status
password-reset shows review N of M. atipspec run counts the reviews it
asks for the same way and stops at needs_decision once they are used up. It
also keeps its own count: --max-rounds, which defaults to review_rounds
less one, as each correction is reviewed again, and cannot exceed it, limits
every automatic correction, whether a review or a check asked for it.
Why an isolated session¶
The context that wrote the code will confirm it. The reviewer's verdicts are the only ones the gate accepts, and the gate rejects them as soon as the code changes, unless only task titles in a plan changed. A contract violation is always a blocker.
Blocker and major findings require resolution or a trusted risk exception.
Local green is checked: the assistant then presents atipspec report <slug>
for your acceptance of the result, and deliver requires it. Under
trusted acceptance, green is verified, and QA's signed
approval binds the review, exceptions and evidence.