docs

Review and sign

An agent can write a lot of code in an afternoon. Somebody still has to say "yes, this is what the API should do". calm review is that moment: it shows what changed since the last time a person signed, in behavior terms, and calm review --sign records that a person read it and accepted it. The signature is a small file you commit with the change, and CI can fail when access widens beyond it.

Terminal
npx calm review              # read what changed since the last signature
npx calm review --sign       # accept it: writes .calm/reviews/ (commit it)
npx calm review --list       # only the files agents changed that nobody signed
npx calm review --ci         # exit 1 when access widened since the last signature

What a review shows#

Text
  CalmAPI review · since Ada Lovelace signed 2026-10-13 08:41

  Behavior (7)
    ~  GET    /api/products      policy: authenticated -> public          looser  app/resources/product/product.resource.js
    ~  GET    /api/products/:id  policy: authenticated -> public          looser  app/resources/product/product.resource.js
    ~  product.name              visible to: signed-in users -> everyone  looser  app/resources/product/product.resource.js
    ~  product.price             visible to: signed-in users -> everyone  looser  app/resources/product/product.resource.js
    ~  product.status            visible to: signed-in users -> everyone  looser  app/resources/product/product.resource.js
    ~  product.tags              visible to: signed-in users -> everyone  looser  app/resources/product/product.resource.js
    ~  product.owner             visible to: signed-in users -> everyone  looser  app/resources/product/product.resource.js
    7 widen access.

  Written by agents, not signed (2)
    app/resources/product/product.resource.js  claude-code  2026-10-13 09:24                      access rules
    test/products.test.js                      claude-code  2026-10-13 09:22, then edited by you

  Declared by agents (1 declaration, 3 changes)
    claude-code Show products to visitors of the storefront
    ! declared, not made  route GET /api/products/featured
    ✖ different  route GET /api/products/:id: declared policy authenticated, shipped policy public

  Tests ✔ 6 passed (claude-code, 09:26)

  Sign it with calm review --sign, or in devtools: Review.

Four parts, each only when it has something to say:

  • Behavior: the behavior diff between the last signed baseline and the code as it is now: routes added or removed, who may call them, fields more people can see, environment variables, hosts, events, blocks and invariants. Changes that widen access are marked looser and counted.
  • Written by agents, not signed: every file an AI agent changed since the last signature, from the Activity log, with the agent's name and when. Edits to protected paths or access rules carry their reason. An agent's edit stays unsigned until a person signs, even if you edited the file afterwards (shown as then edited by you): touching a file is not reviewing it. Files that were deleted again are left out.
  • Declared by agents: what agents said they would change with calm_declare, next to what they did.
  • Tests: the last test run an agent made with calm_test since the signature.

The first time, nothing is signed yet, so there is nothing to compare with:

Text
  CalmAPI review · nothing signed yet

  There is no signed baseline. Signing accepts the current behavior; later reviews show what changed from it.

  Sign it with calm review --sign, or in devtools: Review.

Signing#

Terminal
npx calm review --sign --note "Products are public for the storefront launch"

It prints the review, then asks before it writes anything: Sign 7 changes, 7 widening access as Ada Lovelace <ada@example.com>? (the default answer is no). The first signature accepts the current behavior as the baseline. When nothing changed since the last signature it says Nothing to sign: nothing changed since the last signature. and writes nothing.

Text
✔ Signed 2026-10-13-4f9c2a as Ada Lovelace at 7d630d3+changes
  Commit .calm/reviews/ with your change, so the next review and CI start from here.
OptionMeaning
--signAccept what changed and write the signature
--note <text>Why these changes are fine, stored with the signature
--as "Name <email>"Sign as this person instead of your git user
-y, --yesDo not ask. Required without an interactive terminal (a script, CI set).
--listPrint only the files agents changed that nobody signed
--ciExit with code 1 when access widened since the last signature
--jsonThe whole review as JSON: baseline, changes, summary, unreviewed, declarations, declared, tests, needsSignature

Who signs#

The signer is whoever is at the keyboard, the way git would put it in a commit: GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL when they are set, otherwise git config user.name and user.email. --as "Ada Lovelace <ada@example.com>" names someone else (the email is optional). Without a name the command stops and asks you to set git config user.name or pass --as.

The commit is recorded too: the short HEAD, plus +changes when the working tree has uncommitted changes, as in 7d630d3+changes.

Signing is for a person#

calm review --sign refuses to run in an AI agent's shell. Claude Code sets CLAUDECODE=1 for the commands it runs, and the CLI checks it:

Text
✖ Signing is for a person, and this shell belongs to an AI agent (claude-code). Sign in your own terminal, or in devtools: Review.

Agents can read the review (calm_review) but there is no tool to sign it.

What is written#

Signing writes two files in .calm/reviews/:

FileHolds
baseline.jsonThe behavior snapshot that was accepted, with reviewId, signedAt, signer and commit. The next review compares against it.
<date>-<id>.jsonThe signature: id, signedAt, signer (name, email), via (cli or devtools), commit, note, previous (the review before), accepted (the changes, the agent files, the declaration check and the tests that were accepted) and snapshotHash

Commit .calm/reviews/ with the change it accepts. The rest of .calm/ (activity, checkpoints, traffic) stays local: new projects have this in .gitignore:

Text
.calm/*
!.calm/reviews/

Projects created before reviews ignore all of .calm/. The review says so (.gitignore hides .calm/reviews/), and the first signature rewrites the line to the two above.

In devtools#

The Review panel shows the same review: the behavior changes with links to their files, the agent files nobody signed, the declarations and the last test run. At the bottom, a Sign form with your name and email (filled in from GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL or your git config, and editable) and an optional note. Signing there writes the same files, with via: "devtools". Once something has been signed, the panel's badge counts the changes and unsigned agent files, in red when one widens access or an agent's changes do not match what it declared.

Two other panels point at it: in Routes and Resources, a route or resource whose file an agent changed and nobody signed has an unsigned badge that opens Review.

For agents: calm_declare and calm_review#

Before an agent changes behavior, it can declare what it is about to do with the calm_declare MCP tool, in the words the review uses:

JSON
{
  "intent": "Show products to visitors of the storefront",
  "changes": [
    { "kind": "route", "key": "GET /api/products", "policy": "public" },
    { "kind": "route", "key": "GET /api/products/:id", "policy": "authenticated" },
    "route GET /api/products/featured"
  ]
}

A change is a string that starts with its kind (route, field, resource, env, host, event, block or invariant) followed by its key: "route POST /api/refunds", "field order.total", "env STRIPE_SECRET_KEY", "host api.stripe.com". An object { kind, key, policy } also promises the policy of a route, and change (added, removed or changed) what happens to it. At most 100 changes per declaration. The declaration is saved in .calm/declarations/<id>.json and the agent is told to call calm_review when it is done.

The review then compares the declarations made since the last signature with the behavior diff:

MarkMeans
✖ not declaredThe code changed something no declaration mentioned
! declared, not madeA declared change is not in the code
✖ differentThe change was made, but not as declared: a different policy, or added where removed was promised
✔ What changed matches what was declared.Everything lines up

Fields whose visibility changed only because a declared route of their resource changed policy count as part of that route change, which is why the example above lists no field as undeclared.

calm_review gives the agent the same review in compact JSON (without the full snapshots), with a note that a person signs it. Its description asks the agent to call it before it says it is done and to show the developer anything that widens access. Both tools are available in every scope, read included.

In CI#

Terminal
npx calm review --ci

It compares the code in the pipeline with the committed .calm/reviews/baseline.json and exits with code 1 when at least one change widens access:

Text
  ✖ Access widened in 7 places since the last signature. A person signs it (calm review --sign) and commits .calm/reviews/.

Without a baseline it passes, with No signed baseline yet: --ci passes. Changes that do not widen access and unsigned agent files are shown but do not fail the build. To make a wider API pass on purpose, a person signs the review and commits .calm/reviews/ with the change.

calm review --ci and calm diff --ci look at the same changes: the review compares with what a person last signed, the diff with a git ref. The exposure budget holds the API to a committed list of who may do what.

Last updated