Expand description
Preview approval (ADR-0028, which replaced ADR-0023): a UI-changing
commit is published only after the person approves that exact commit,
seen on localhost — unless the plan the person approved declares, in a
## Preview section, that the evidence is enough (on_plan_answered,
evidence_of).
§The flow this module keeps honest
- The agent verifies the final commit itself, then runs
amont-agent preview register --url … --guide …as the last command of its line, in the foreground. That command VALIDATES (clean tree, real commit, a complete guide outside the worktree), renders the guide toindex.htmlbeside it (crate::guide) and prints JSON on one line; the PostToolUse hook binds that output to the session and the person’s prompt (bind), once it has shown the line is this call’s own (stamp_register). - In the same turn the agent asks a marked question, starting with the
register’s
question_prefix—[preview <id>] <label>, each id beginning with its commit’s sha7 — options exactlyApprove/Request changes/Hold. - The person answers. Only their selection, read from
tool_response(on_post_ask), or a typedapprove|ship|lgtm|looks goodin the very next prompt (on_prompt), approves — and only the commits the question listed.
What this is NOT: proof. A registration is the agent’s attestation of worktree, commit and URL; nothing here can show the browser served that commit or that the person looked. It is a workflow aid over Claude Code’s hooks, not a Git enforcement boundary.
§Why the answer has to be checked twice
AskUserQuestion’s input schema accepts answers. A model could
pre-answer its own question, and the PostToolUse payload would then carry
an “Approve” nobody chose. on_pre_ask records, per tool_use_id,
whether the call arrived pre-answered; a pre-answered marked question never
approves, and under deny it is refused before it runs.
Structs§
- Registered
- What
preview registervalidated and printed.
Enums§
- Needs
- Why a push needs a preview it does not have.
- Outcome
- What the preview rule says of a pushed branch target, for the provenance
record:
Evidencewhen the gate passes on the plan’s evidence,Approvedwhen it passes on the person’s approval,NotRequiredfor no interface change (or a repository without one).Nonewhen nothing has stood yet: a push the gate would hold, or whose files cannot be listed. The record is written once per commit, so it waits for the push that passes. What the preview rule knew about the push.
Constants§
- APPROVE
- The labels of a marked question. Exact, with no “(Recommended)” suffix.
- HOLD
- REQUEST_
CHANGES - RULE_ID
Functions§
- approved
- Whether the person approved this commit of this repository: exactly, or
by content — a stored content id (
crate::content_id::content_id, which skipsdocs/plans/) equal to this commit’s. A reword, an amend or a plan-only change keeps the tree, so the approval still stands. - bind
- After a Bash call: bind a
preview registerthat ran as the last command of the line, in the foreground, to this session and prompt. - comment_
only - Whether a
git diff -U0of one file changes comments and nothing else: at least one hunk, and every added or removed line that is not blank is a comment line (is_comment_line). No hunk at all (a binary file, a mode change) is not “comments only”. - gated
- Whether a repository has a user interface a person can preview.
- is_
comment_ line - A line that, on its own, is nothing but a comment. Conservative: a line
that also carries code —
/* x */ foo(),*/ bar— is not, and neither is a*line that reads like code (a CSS* {rule, a continued multiplication ending in;). - is_
typed_ approval - A typed approval: the whole prompt, trimmed, is one of these words.
- is_
ui_ path - A path whose change a person could see. Test files and test-tooling configs are never UI.
- marker
- The ids in a
[preview a,b]marker, if the question carries one. - mockup_
screens - The screens a commit’s branch carries picked mockups for: folders under
the mockups directory that
merge-base(<default branch>, commit)..commitchanges and that hold a*.dc.htmlartboard atcommit. - needs_
preview - The rule’s question: would this push publish an interface change that no
approved preview covers?
Okfires the rule and says why;Errnames why it did not. - on_
plan_ answered - After an
ExitPlanMode: when the person approved the plan, record its canonical body sha (crate::plan_text::body_sha), hashed from the file as it is now. - on_
post_ ask - After an
AskUserQuestion: an answer to a marked question approves, drops, or — unanswered — leaves its registrations pending. Approve takes every pending registration whose id the marker lists; no label is needed. - on_
pre_ ask - Before an
AskUserQuestionruns. Returns the refusal text when a marked question arrives pre-answered and the rule is denying. - on_
prompt - A new prompt: previews shown in an earlier turn either get the typed approval right now, or lapse.
- preview_
evidence - The reason a plan’s
## Previewsection declares: the section’s first non-empty line, when it starts withevidence:and gives one that is not the template’s<…>placeholder, left unedited. Read from the canonical body, the part the person approved; a heading inside a code fence is text. - question_
prefix - The text a marked question starts with: the marker, then the label the person knows the commit by.
- record_
before - Before a push runs: remember, per
tool_use_id, where each branch destination stood, and whether the commit was an approved UI change (this module’s policy), throughamont_agent_guard::push_record.push-publishedreads it back after the push. - register
- Validate a preview for registration and render its guide to
index.htmlbeside it. Touches no store: the hook binds. - stamp_
register - Before a Bash call that runs
preview register: record, pertool_use_id, when the call started.bindreads it back, so a line an earlier clause printed — or a page an earlier call rendered — cannot be passed off as this call’s. - sweep
- Delete per-call scratch nobody came back for.