| name | finalize |
| description | Distil a PR's accumulated knowledge into the durable squash commit message — reconcile the /reflect experience notes across its commits with the review feedback into one end-state record, and propose promoting cross-cutting lessons to learning skills/memory. Use just before squash-merging a PR. Prepares the message and the gh merge command; does not merge. |
Finalize — distil a PR into its durable commit
/reflect scatters provisional experience notes across a PR's WIP commits while the work
happens. The PR then accumulates review feedback. This skill compresses both into the one
commit that survives the squash — the durable, location-reachable record a future agent
meets via git log / git blame.
Vocabulary, subject convention, and format are defined once in
../_shared/commit-trailers.md — read it first, then
read this for how to reconcile and where to put the output.
Where this sits
/reflect → WIP commit messages [episodic, provisional, disposable]
PR review + bots → PR comments [external critique]
↓ THIS SKILL, just before squash
/finalize → squash commit message [durable, location-reachable]
↓ fork
promotion → learning skills / memory [cross-cutting, always-loaded]
The one principle: reconcile to the end state, not concatenate
The easy version — collect every trailer and paste them in — is wrong, because the PR's job
is to change its own mind. A WIP note the PR later contradicted must be dropped, not
carried, or it lands frozen and wrong on main:
WIP commit 1: Rejected: server-side sessions | too much infra. Review pushes back; commit
4 adopts server-side sessions. Concatenation carries Rejected: server-side sessions onto
main — now actively misleading. Reconciliation drops it (or flips it to a Constraint).
So read the whole arc in order (commits + comments + check outcomes) and emit only what is
still true at the end. This is judgment work — it's why this is a skill, and why it's a
human-triggered pre-merge step, not an automated merge hook.
Step 1 — Gather
- Author-side notes:
git log --reverse main..HEAD --format='%(trailers:only,unfold)' for
the trailers, plus the commit bodies for the prose. --reverse is required — the arc
walk in Step 2 is oldest-first, and the default git log order (newest-first) would invert
the timeline and let stale, overturned notes win. (Drop the deliberate no-note commits.)
Also fold in any PR comment carrying the <!-- reflect-note --> marker — those are
author-side reflection routed to a comment because the commit was already pushed, not
critique (see ../_shared/commit-trailers.md).
- Reviewer/bot critique: the PR comments without the
<!-- reflect-note --> marker.
Reuse /docs:assess-comments if available (it already collects, role-tags, and splits
open/resolved across tools); otherwise read them directly
(gh api repos/<o>/<r>/pulls/<n>/comments, .../issues/<n>/comments,
.../pulls/<n>/reviews). Note which findings were fixed vs dismissed vs still open.
- Check outcomes: whether CI / security / Bugbot ended clean.
Step 2 — Reconcile
Walk the arc in commit/comment order and produce the end state:
- Drop any WIP note a later commit or review overturned.
- Merge duplicates and near-duplicates into one line; prefer the latest, most specific.
- Resolve contradictions in favour of what the final code actually does — verify against
the diff, don't trust the note.
- Fold in review outcomes as their verdict, not a transcript: a review thread that
settled on a rule becomes one
Constraint:; an approach the review killed becomes
Rejected:. Copy what survived, not the conversation.
- An open unresolved concern is not end-state knowledge — leave it on the PR, don't
bury it in a trailer.
Step 3 — Sort & propose promotion
Same split /reflect Step 3 makes, now over the reconciled set:
- Location-bound (a future agent editing this area needs it) → durable trailer on the
squash commit.
- Cross-cutting (must apply regardless of what's edited — a preference, a process rule, a
standing gotcha, a project-wide constraint) → propose an edit to the relevant learning
skill or a memory file. A commit can't guarantee it'll be seen. Propose, don't apply
silently — show the user the exact addition and target file.
Step 4 — Compose
Write the distilled message per the shared shape: a DOC-XXXX subject, a tight body paragraph
(the durable narrative for humans + the history bot), then the reconciled trailer block. Far
fewer trailers than the inputs held — distillation is compression, not collection.
Keep the subject and the body-file separate: the body-file holds the body paragraph
and the trailer block only — no subject line. The subject is passed via --subject in
Step 5, so repeating it in the file would duplicate the title into the commit body.
Step 5 — Hand off (do not merge)
This repo squashes with squash_merge_commit_message = COMMIT_MESSAGES (verified
2026-06-25), so the default squash body is every WIP commit message concatenated — exactly
the raw dump we're avoiding. Override it explicitly; never rely on the default:
gh pr merge <n> --squash \
--subject "DOC-XXXX <summary>" \
--body-file <distilled-message-file>
Present three things and stop:
- the distilled squash message (subject + body + trailers),
- any promotion proposals (target file + exact addition),
- the ready-to-run
gh pr merge … --body-file command.
The human triggers the merge. Reconciliation is judgment-heavy and a squash-merge is hard to
reverse, so finalize prepares; it does not press the button. (An opt-in auto-merge could be
added later, but it inherits all the risk below.)
Limits (read honestly)
- Under-distilling is recoverable; a wrong frozen trailer on
main is not. When the arc
is ambiguous, drop the trailer and leave the nuance in body prose. Bias to fewer, certain
trailers.
- It reconciles text, it doesn't relive the work — its only knowledge is what
/reflect
and the reviewers serialised. Garbage in stays garbage; verify contradictions against the
actual diff.
- A merge-time automated backstop (GitHub Action) is the weakest possible version of this
— least context, no human, and under
COMMIT_MESSAGES it must also override --body-file
or it concatenates raw. Keep it phase 2, and conservative.
- If Step 3 promotion is skipped, cross-cutting lessons die in a commit nobody re-reads and
the always-loaded tier starves. It is not optional.