Skip to main content

Module preview

Module preview 

Source
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

  1. 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 to index.html beside 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).
  2. 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 exactly Approve / Request changes / Hold.
  3. The person answers. Only their selection, read from tool_response (on_post_ask), or a typed approve|ship|lgtm|looks good in 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 register validated 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: Evidence when the gate passes on the plan’s evidence, Approved when it passes on the person’s approval, NotRequired for no interface change (or a repository without one). None when 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 skips docs/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 register that ran as the last command of the line, in the foreground, to this session and prompt.
comment_only
Whether a git diff -U0 of 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)..commit changes and that hold a *.dc.html artboard at commit.
needs_preview
The rule’s question: would this push publish an interface change that no approved preview covers? Ok fires the rule and says why; Err names 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 AskUserQuestion runs. 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 ## Preview section declares: the section’s first non-empty line, when it starts with evidence: 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), through amont_agent_guard::push_record. push-published reads it back after the push.
register
Validate a preview for registration and render its guide to index.html beside it. Touches no store: the hook binds.
stamp_register
Before a Bash call that runs preview register: record, per tool_use_id, when the call started. bind reads 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.