garden pr explain
A walkthrough is a proof-carrying reading guide you (the author) write for a PR. Instead of a reviewer re-deriving what your change does from the diff, you order the change into blocks, say what each block does, and point at proof it does that. Review becomes adjudicating evidence, not guessing intent.
Blocks form a tree — like a document outline. A block’s claim is established
by its own proof plus the claims of its child blocks. The top-level blocks
are the roots; together they prove the PR as a whole (the summary). So a
high-level claim (“the merge queue is now crash-safe”) sits at the top, and the
pieces that prove it (“state is persisted before the merge”, “restart resumes
from that state”) nest underneath, each with their own proof, recursively.
You have the session, repo, and implementation context no reviewer has — so you are the best source for why this is correct. This is deliberately not a session dump: it’s the curated, minimal artifact you choose to stand behind.
Write one after every garden push (per revision).
garden pr explain <pr> # prints a YAML template from the diff…edit it…<edited> | garden pr explain apply <pr> -f - # write it (upsert)
garden pr explain show <pr> # read it back (block order)There is no editor: fill the printed template and pipe it back with apply -f -.
Blocks are yours to shape
Section titled “Blocks are yours to shape”The scaffold prints one block per changed file, but that is only a starting point. Re-block freely:
- Split a file into several blocks when its hunks do different things.
- Merge trivial blocks.
- Drop boilerplate (imports, renames, generated code) — you don’t have to explain every hunk. What you leave out is itself a signal reviewers can ask about.
- Nest blocks under a
children:list to build the tree: put the pieces that prove a claim under that claim. A parent can have its ownscopeandprooftoo — it’s proven by both. - Order siblings the way a human should read them. Sibling order is the review path — it is never re-sorted.
Reading order (layout)
Section titled “Reading order (layout)”The tree is fixed by your nesting; layout chooses how it flattens into the
main reading pane (the sidebar outline is always top-down):
top-down(default) — each claim before its sub-proofs. Conclusion-first.bottom-up— the building blocks before the claim they support. Foundation-first.sandwich— the summary up top, then bottom-up construction, then a closing synthesis that ties it back to the PR.
Pick the one that best fits this change; there’s no universally right answer.
A big or mixed file almost always wants several blocks. Example — one file that does two unrelated things becomes two blocks:
blocks: - id: b1 title: parse the keep/drop plan scope: - { path: cli/plan.go, lines: 40-90 } # just the parser intent: turns the edited plan text back into a per-hunk keep/drop set. attention: review risk: low proof: - test: plan_test.go::TestParsePlan - id: b2 title: reject a structurally-edited plan scope: - { path: cli/plan.go, lines: 95-120 } # the validation, same file intent: errors if the agent rewrote the diff body instead of flipping verbs, so a corrupt plan can't silently commit the wrong thing. attention: scrutinize risk: high proof: - test: plan_test.go::TestRejectsCorruption - note: no test yet for a plan whose hunk headers were reordered.Document schema
Section titled “Document schema”summary: what this change does + the single most important thing to knowconfidence: medium # low | medium | high — your own certaintylayout: top-down # top-down | bottom-up | sandwichcritical_path: [b2] # block ids — "5 minutes? read these"blocks: # top-level roots — together they prove the PR - id: b1 # unique across the whole tree title: short human title scope: # the code this block covers - { path: pkg/x.go, lines: 40-80 } # whole file if lines omitted - { path: pkg/y.go } intent: what it does and why — the claim being proven attention: review # skim | review | scrutinize risk: low # low | medium | high (blast radius) proof: [] # see below — a list, several allowed unproven: what you did NOT verify — the honest gaps children: # sub-blocks that prove this one (the tree) - id: b1a title: … # same shape, recursivelyattention (how hard to look) and risk (how bad if wrong) are separate axes.
unproven is the highest-value field: an honest gap list beats any prose.
Length — the budgets
Section titled “Length — the budgets”summary, intent, unproven and note proofs are Markdown; bullet lists
are ordinary content.
A block’s prose must be shorter than the code it points at.
A walkthrough that takes longer to read than the diff has no reason to exist. Reviewers abandon walls of text, and the artifact is worthless unread. These are budgets, not suggestions:
| field | budget |
|---|---|
title |
≤ 50 characters. A headline: no trailing punctuation, no leading article. Keep the identifier — ChunkDiskMapper.Truncate, poll_proceed() — and drop the prose around it. |
intent |
one line, ≤ 20 words. If it has 2–3 separable points, use - bullets of ≤ 12 words instead, 3 maximum. |
unproven |
bullets only, 2 maximum, ≤ 15 words each. More gaps than that? Keep the two that most change what a reviewer does. |
summary |
≤ 50 words, ending on the single most important thing to know. |
intent must add what title doesn’t — the consequence, the constraint, the
gotcha. If it only rephrases the title, leave it empty.
Empty beats filler. A blank unproven is honest; a padded one trains
reviewers to skip the field.
Don’t write these
Section titled “Don’t write these”- Process narration — “I grepped”, “I checked”, “I verified”, “not
verified:”, “I could not establish”. The field is called
unproven; saying it again spends the reader’s only resource. - Restating what the code plainly shows. The diff is one panel away.
- Hedging — “arguably”, “as far as I can tell”, “that reads deliberate but I cannot confirm it”. Either it is worth saying flat, or cut it.
- Inline
file:linecitations.scopeandproofalready carry locations.
Before and after
Section titled “Before and after”# 41 words — three sentences to say one thingintent: > Adds a second accumulator alongside `removed_selectors`. `removed_selectors` keeps its existing meaning — "these make config resolution fail" — and the new `removed_ignored_rules` collects the selectors that are now downgraded to a warning. Splitting the sets rather than adding a flag keeps the pre-existing error path byte-identical.
# 16 wordsintent: A second set, so the pre-existing error path stays byte-identical instead of growing a flag.
# 58 words of narrationunproven: > Two things I can establish and neither is tested. (1) The message text changes: this site stores `err.to_string()` raw, whereas `Message::from_parse_error` prefixes `"SyntaxError: "` … (2) The only tests touching `not_json.ipynb` live in `crates/ruff_notebook/…`
# 21 wordsunproven: | - Malformed `.ipynb` now renders without the `SyntaxError: ` prefix. - Nothing exercises `from_source_error`'s rendering, before or after.Quote any value containing : — YAML reads a bare colon-space as a mapping
key, so caption: the gate — ErrInvalidSample: …`` is a parse error.
Proof kinds
Section titled “Proof kinds”Each proof entry is exactly one kind:
proof: - test: path/foo_test.go::TestName # a test that exercises it; CI runs it - command: "go test ./internal/x/" # a command a reviewer can run - attachment: ./demo.gif # a local screenshot/recording/data caption: dark→light, persists on reload # file — uploaded on apply - pointer: { path: pkg/x.go, lines: 40-80 } # any code, in the diff or not caption: reuses the battle-tested path - external: https://ci.example/run/123 # a CI run / deployed preview / issue - note: reasoned only — see unproven # the weakest; pair with unprovenGarden does not verify proofs — CI runs the tests, the CLI handles attachments, and reviewers follow the pointer. Point at proof that already exists. To say a block is proven by another block, make it a child — that’s the tree.
Coverage is selective
Section titled “Coverage is selective”You do not have to cover every file or hunk. Cover what a reviewer needs to understand and trust — the important logic, the risky parts, the load-bearing changes — and leave out routine boilerplate (imports, renames, formatting, generated code). Whatever no block covers is shown to the reviewer as an “unaccounted for” list, so nothing is hidden; an uncovered hunk is a signal that it didn’t need explaining, not a gap you must fill.
- A walkthrough is written per revision and is frozen once the PR merges. A new push means a new revision; write a fresh one (you can reuse the previous revision’s as a starting point).
- Any repo member can write or replace it; the author is recorded from your credentials.