7.3 KiB
PR Description Writing
The core principle
The diff is already visible on GitHub. The description exists to explain what the diff cannot show: what was impossible before and is now possible, what was broken and is now fixed, what shape changed. Cut any sentence a reader could reconstruct from the diff itself.
- Bad: "Adds
evidence-decider.ts, modifiesce-commit-push-pr/SKILL.mdto call it, and updates two test files." - Good: "Evidence capture now decides automatically whether a change has observable behavior. CLI tools and libraries are now eligible alongside web UIs."
If the lead sentence describes what was moved, renamed, or added rather than what's now possible or fixed, rewrite it. This applies to every section, not just the opening — restating the diff is the failure mode this skill exists to prevent.
For user-facing bugs, run an extra before/after pass before writing the mechanism: name what the user would have seen before and what they now see instead. Only then mention the technical cause or fix, and only if it helps the reviewer understand risk. A lead like "Playback hooks now ignore late async responses" is still too mechanical if the visible bug was "old videos, thumbnails, or errors could appear after switching selections."
Step Pre-A: Resolve the range and base
Two modes:
- Current-branch mode (default) — describe HEAD vs the repo's default base.
- PR mode — describe a specific PR. Triggered when the caller passes a PR ref.
For PR mode, fetch metadata first:
gh pr view <ref> --json baseRefName,headRefOid,url,body,state,isCrossRepository,headRepositoryOwner
If state is not OPEN, report and stop — do not invent a description. Use baseRefName as <base> and headRefOid as <head>.
For current-branch mode, resolve <base> in priority order: caller-supplied (base:<ref>) → git rev-parse --abbrev-ref origin/HEAD (strip origin/) → gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name' → try main/master/develop via git rev-parse --verify origin/<candidate>. If none resolve, ask the user. <head> is HEAD.
Base remote: origin for current-branch mode and same-repo PRs. For fork PRs, match the PR's base owner/repo against git remote -v. If no local remote matches, skip to the gh fallback — do not diff against origin (wrong base).
git fetch --no-tags <base-remote> <base>
git fetch --no-tags <base-remote> <head> # PR mode only: <head> is headRefOid and may not be local
git log --oneline "<base-remote>/<base>..<head>"
git diff "<base-remote>/<base>...<head>"
If the commit list is empty, report "No commits to describe" and stop.
Fallback — use gh pr diff <ref> and gh pr view <ref> --json commits when local git can't reach the refs (fork PR with no matching remote, shallow clone, offline, merge-base on unrelated histories). For GHES configurations that reject SHA fetch but allow refs/pull/:
git fetch --no-tags <base-remote> "refs/pull/<number>/head"
PR_HEAD_SHA=$(awk '/refs\/pull\/[0-9]+\/head/ {print $1; exit}' "$(git rev-parse --git-dir)/FETCH_HEAD")
Note in the user-facing summary when the API fallback was used.
Step A: Size the description
Match weight to weight. When in doubt, shorter wins. Subtract fix-up commits (review fixes, lint, rebase resolutions) when sizing — they're invisible to the reader. Large PRs need more selectivity, not more content.
| Change profile | Description approach |
|---|---|
| Small + simple (typo, config, dep bump) | 1-2 sentences, no headers. Under ~300 characters. |
| Small + non-trivial (bugfix, behavioral change) | 3-5 sentences. No headers unless two distinct concerns. |
| Medium feature or refactor | Narrative frame, then what changed and why. Call out design decisions. |
| Large or architecturally significant | Narrative frame + 3-5 design-decision callouts + brief test summary. Target ~100 lines, cap ~150. For PRs with many mechanisms, use a Summary table; do not create an H3 per mechanism. |
| Performance improvement | Include before/after measurements as a markdown table. |
For small + simple PRs, the value-led sentence is the entire description. For small + non-trivial bugfixes, the 3-5 sentence target still needs a user-visible before/after lead when the bug affected UI, CLI output, workflow output, or any other user-observable behavior. Concision is not a reason to skip the visible symptom.
Step B: Compose the title
type: description or type(scope): description.
- Type by intent, not file extension. When
fixandfeatboth seem to fit, default tofix— adding code to remedy missing behavior isfix. Reservefeatfor capabilities the user could not previously accomplish. Userefactor/docs/chore/perf/testwhen more precise. - Scope (optional): narrowest useful label. Omit when no single label adds clarity.
- Description: imperative, lowercase, under 72 chars, no trailing period.
- Match repo conventions visible in recent commits.
- Never use
!orBREAKING CHANGE:without explicit user confirmation — they trigger automated major-version bumps.
Step C: Assemble the body
In order: opening → body sections that earn their keep → test plan if non-obvious → evidence block if one exists → Compound Engineering badge after a --- rule.
The opening goes under ## Summary if the body uses any ## headings; bare paragraph otherwise. No orphaned opening paragraphs above the first heading.
Evidence handling: preserve any existing ## Demo or ## Screenshots block verbatim unless the user's focus asks to refresh it. If the caller passed a freshly captured URL or path, splice as ## Demo. Otherwise omit. Place before the badge. Never label test output as "Demo" or "Screenshots."
Visual aids: reach for a diagram or table when it conveys the change faster than prose — relationships, flows, state transitions, sequences, trade-offs, before/after data, or any structure prose would have to enumerate. Mermaid and markdown tables cover most shapes; don't be limited to a particular type if a different one fits the change better. Place inline at the point of relevance. Skip for simple, prose-clear, or rename/dep-bump changes. Prose is authoritative when it conflicts with a visual.
GitHub gotchas: never prefix list items with # (GitHub auto-links #1 as an issue ref). Use org/repo#123 or full URL for actual references.
Step D: Badge
---
[](https://github.com/EveryInc/compound-engineering-plugin)

| Harness | LOGO |
COLOR |
|---|---|---|
| Claude Code | claude |
D97757 |
| Codex | (omit ?logo= param) |
000000 |
| Gemini CLI | googlegemini |
4285F4 |
Model slug: spaces become underscores; append context window and thinking level in parens if known. URL-encode literal parens as %28 / %29 — unencoded parens inside markdown image URLs break release-please's commit parser, which silently drops the commit from the changelog. Examples: Opus_4.6_%281M,_Extended_Thinking%29, Sonnet_4.6_%28200K%29, Gemini_3.1_Pro.
Skip the badge if regenerating a body that already contains it.