CE PR Description
Generate a conventional-commit-style title and a value-first body describing a pull request's work. Returns structured `{title, body_file}` for the caller to apply — this skill never invokes `gh pr edit` or `gh pr create`, and never prompts for interactive confirmation.
Overview
CE PR Description
Generate a conventional-commit-style title and a value-first body describing a pull request's work. Returns structured {title, body_file} for the caller to apply — this skill never invokes gh pr edit or gh pr create, and never prompts for interactive confirmation.
Why a separate skill: several callers need the same writing logic without the single-PR interactive scaffolding that lives in ce-commit-push-pr. ce-pr-stack's splitting workflow runs this once per layer as a batch; ce-commit-push-pr runs it inside its full-flow and refresh-mode paths. Extracting keeps one source of truth for the writing principles.
Naming rationale: ce-pr-description, not git-pr-description. Stacking and PR creation are GitHub features; the "PR" in the name refers to the GitHub artifact. Using the ce- prefix matches the plugin naming convention for all skills.
Inputs
Input is a free-form prompt. Parse it into two parts:
- A PR reference, if present. Any of these patterns counts: a full GitHub PR URL (
https://github.com/owner/repo/pull/NN),pr:<number>orpr:, a bare hashmark form (#NN), or the argument being just a number (561). Extract the PR reference and treat the rest of the argument as steering text. - Everything else is steering text (a "focus" hint like "emphasize the benchmarks" or "do a good job with the perf story"). It may be combined with a PR reference or stand alone.
No specific grammar is required — read the argument as natural language and identify whichever PR reference is present. If no PR reference is present, default to describing the current branch.
Mode selection
| What the caller passes | Mode |
|---|---|
| No PR reference (empty argument or steering text only) | Current-branch mode — describe the commits on HEAD vs the repo's default base |
A PR reference (URL, pr:, #NN, or bare number) | PR mode — describe the specified PR |
Steering text is always optional. If present, incorporate it alongside the diff-derived narrative; do not let it override the value-first principles or fabricate content unsupported by the diff.
Optional base:<ref> override (current-branch mode only). When a caller already knows the intended base branch (e.g., ce-commit-push-pr has detected origin/develop or origin/release/2026-04 as the target), it can pass base:<ref> to pin the base explicitly. The ref must resolve locally. This overrides auto-detection for current-branch mode; PR mode ignores it (PRs already define their own base via baseRefName). Most invocations don't need this — auto-detection (existing PR's baseRefName → origin/HEAD) covers the common case.
Examples:
ce-pr-description→ current-branch, no focus, auto-detect basece-pr-description emphasize the benchmarks→ current-branch, focus = "emphasize the benchmarks"ce-pr-description base:origin/develop→ current-branch, base pinned toorigin/developce-pr-description base:origin/develop emphasize perf→ same + focusce-pr-description pr:561→ PR #561, no focusce-pr-description #561 do a good job with the perf story→ PR #561, focus = "do a good job with the perf story"ce-pr-description https://github.com/foo/bar/pull/561 emphasize safety→ PR #561 in foo/bar, focus = "emphasize safety"
Output
Return a structured result with two fields:
title-- conventional-commit format:type: descriptionortype(scope): description. Under 72 characters. Choosetypebased on intent (feat/fix/refactor/docs/chore/perf/test), not file type. Pick the narrowest usefulscope(skill or agent name, CLI area, or shared label); omit when no single label adds clarity.body_file-- absolute path to an OS temp file (created viamktemp) containing the body markdown that follows the writing principles below. Do not emit the body inline in the return.
The caller decides whether to apply via gh pr edit, gh pr create, or discard, reading the body from body_file (e.g., --body "$(cat "$BODY_FILE")"). This skill does NOT call those commands itself. No cleanup is required — mktemp files live in OS temp storage, which the OS reaps on its own schedule.
What this skill does not do
- No interactive confirmation prompts. If the diff is ambiguous about something important (e.g., the focus hint conflicts with the actual changes), surface the ambiguity in the returned output or raise it to the caller — do not prompt the user directly.
- No branch checkout. Current-branch mode describes the HEAD in the user's current checkout; PR mode describes the specified PR. Neither mode checks out a different branch.
- No compare-and-confirm narrative ("here's what changed since the last version"). The description describes the end state; the caller owns any compare-and-confirm framing.
- No auto-apply via
gh pr editorgh pr create. Return the output and stop.
Interactive scaffolding (confirmation prompts, compare-and-confirm, apply step) is the caller's responsibility.
Step 1: Resolve the diff and commit list
Parse the input (see Inputs above) and branch on which mode it selects.
Current-branch mode (default when no PR reference was given)
Determine the base against which to compare, in this priority order:
- Caller-supplied
base:<ref>— if present, use it verbatim. The caller is asserting the correct base. The ref must resolve locally. - Existing PR's
baseRefName— if the current branch already has an open PR on this repo, use that PR's base. Handles feature branches targeting non-default bases (e.g.,develop) when the PR is already open. - Repo default (
origin/HEAD) — fall back for branches with no PR yet and no caller-supplied base.
# Detect current branch (fail if detached HEAD)
CURRENT_BRANCH=$(git branch --show-current)
if [ -z "$CURRENT_BRANCH" ]; then
echo "Detached HEAD — current-branch mode requires a branch. Pass a PR reference instead."
exit 1
fi
# Priority: caller-supplied base: > existing PR's baseRefName > origin/HEAD > origin/main
if [ -n "$CALLER_BASE" ]; then
BASE_REF="$CALLER_BASE"
elif EXISTING_PR_BASE=$(gh pr view --json baseRefName --jq '.baseRefName'); then
BASE_REF="origin/$EXISTING_PR_BASE"
elif DEFAULT_HEAD=$(git rev-parse --abbrev-ref origin/HEAD); then
BASE_REF="$DEFAULT_HEAD"
else
BASE_REF="origin/main"
fi
Both gh pr view and git rev-parse --abbrev-ref origin/HEAD exit non-zero on the "not configured" paths; the elif chain drives off exit code rather than suppressed stderr. Stderr from a missing PR or unresolved origin/HEAD is informational and acceptable.
If $BASE_REF does not resolve locally (git rev-parse --verify "$BASE_REF" fails), the caller (or the user) needs to fetch it first. Exit gracefully with "Base ref $BASE_REF does not resolve locally. Fetch it before invoking the skill." — do not attempt recovery.
Gather merge base, commit list, and full diff:
MERGE_BASE=$(git merge-base "$BASE_REF" HEAD) && echo "MERGE_BASE=$MERGE_BASE" && echo '=== COMMITS ===' && git log --oneline $MERGE_BASE..HEAD && echo '=== DIFF ===' && git diff $MERGE_BASE...HEAD
If the commit list is empty, report "No commits between $BASE_REF and HEAD" and exit gracefully — there is nothing to describe.
If an existing PR was found in step 1, also capture its body for evidence preservation in Step 3.
PR mode (when the input contained a PR reference)
Normalize the reference into a form gh pr view accepts: a bare number (561), a full URL (https://github.com/owner/repo/pull/561), or the number extracted from pr:561 or #561. gh pr view's positional argument accepts bare numbers, URLs, and branch names — not owner/repo#NN shorthand. For a cross-repo number reference without a URL, the caller would use -R owner/repo; this skill accepts a full URL as the simplest cross-repo path, and that's what most callers use.
gh pr view <pr-ref> --json number,state,title,body,baseRefName,baseRefOid,headRefName,headRefOid,headRepository,headRepositoryOwner,isCrossRepository,commits,url
Key JSON fields: headRefOid (PR head SHA — prefer over indexing into commits), baseRefOid (base-branch SHA), headRepository + headRepositoryOwner (PR source repo), isCrossRepository. There is no baseRepository field — the base repo is the one queried by gh pr view itself.
If the returned state is not OPEN, report "PR <number> is <state> (not open); cannot regenerate description" and exit gracefully without output. Callers expecting {title, body_file} must handle this empty case.
Determine whether the PR lives in the current working directory's repo by parsing the URL's <owner>/<repo> path segments and comparing against git remote get-url origin (strip .git suffix; handle both git@github.com:owner/repo and https://github.com/owner/repo forms). If the URL repo matches origin's repo, route to the local-git path (Case A). Otherwise route to the API-only path (Case B). Bare numbers and #NN forms implicitly target the current repo → Case A.
Case A → Case B fallback: Even when the URL repo matches origin, the local clone may not be usable for this PR's refs — shallow clone, detached state missing the base branch, offline, auth issues, GHES quirks. If Case A's fetch or git merge-base fails, fall back to Case B rather than failing the skill. Note the fallback in the caller-facing output.
Case A — PR is in the current repo:
Read the PR head SHA directly from headRefOid in the JSON response above. Fetch the base ref and the head SHA in one call (the fetch is idempotent when refs are already local):
PR_HEAD_SHA=<headRefOid from JSON>
git fetch --no-tags origin <baseRefName> $PR_HEAD_SHA
Using the explicit $PR_HEAD_SHA in downstream commands avoids FETCH_HEAD's multi-ref ordering problem (git rev-parse FETCH_HEAD returns only the first fetched ref's SHA, which silently breaks a multi-ref fetch).
MERGE_BASE=$(git merge-base origin/<baseRefName> $PR_HEAD_SHA) && echo "MERGE_BASE=$MERGE_BASE" && echo '=== COMMITS ===' && git log --oneline $MERGE_BASE..$PR_HEAD_SHA && echo '=== DIFF ===' && git diff $MERGE_BASE...$PR_HEAD_SHA
If the explicit-SHA fetch is rejected (rare on GitHub, possible on some GHES configurations that disallow fetching non-tip SHAs), fall back to fetching refs/pull/<number>/head and reading the PR head SHA from .git/FETCH_HEAD by pull-ref pattern:
git fetch --no-tags origin "refs/pull/<number>/head"
PR_HEAD_SHA=$(awk '/refs\\/pull\\/[0-9]+\\/head/ {print $1; exit}' "$(git rev-parse --git-dir)/FETCH_HEAD")
Case B — PR is in a different repo:
Skip local git entirely. Read the diff and commit list from the API:
gh pr diff <pr-ref>
gh pr view <pr-ref> --json commits --jq '.commits[] | [.oid[0:7], .messageHeadline] | @tsv'
Same classification/framing/writing pipeline. Note in the caller-facing output that the API fallback was used.
Also capture the existing PR body for evidence preservation in Step 3 (both cases).
Step 2: Classify commits before writing
Scan the commit list and classify each commit:
- Feature commits -- implement the PR's purpose (new functionality, intentional refactors, design changes). These drive the description.
- Fix-up commits -- iteration work (code review fixes, lint fixes, test fixes, rebase resolutions, style cleanups). Invisible to the reader.
When sizing the description, mentally subtract fix-up commits: a branch with 12 commits but 9 fix-ups is a 3-commit PR.
Step 3: Decide on evidence
Decide whether evidence capture is possible from the full branch diff.
Evidence is possible when the diff changes observable behavior demonstrable from the workspace: UI, CLI output, API behavior with runnable code, generated artifacts, or workflow output.
Evidence is not possible for:
- Docs-only, markdown-only, changelog-only, release metadata, CI/config-only, test-only, or pure internal refactors
- Behavior requiring unavailable credentials, paid/cloud services, bot tokens, deploy-only infrastructure, or hardware not provided
This skill does NOT prompt the user to capture evidence. The decision logic is:
- PR mode invocation (any form: bare number,
#NN,pr:, or a full URL — anything that resolves to an existing PR whose body we fetched) and the existing body contains a## Demoor## Screenshotssection with image embeds: preserve it verbatim unless the steering text asks to refresh or remove it. Include the preserved block in the returned body. This applies regardless of which input shape the caller used; what matters is that a PR exists and its body was read. - Current-branch mode or PR mode without an evidence block: omit the evidence section entirely. If the caller wants to capture evidence, the caller is responsible for invoking
ce-demo-reelseparately and splicing the result in, or for asking this skill to regenerate with updated steering text after capture.
Do not label test output as "Demo" or "Screenshots". Place any preserved evidence block before the Compound Engineering badge.
Step 4: Frame the narrative before sizing
Articulate the PR's narrative frame:
- Before: What was broken, limited, or impossible? (One sentence.)
- After: What's now possible or improved? (One sentence.)
- Scope rationale (only if 2+ separable-looking concerns): Why do these ship together? (One sentence.)
This frame becomes the opening. For small+simple PRs, the "after" sentence alone may be the entire description.
Step 5: Size the change
Assess size (files, diff volume) and complexity (design decisions, trade-offs, cross-cutting concerns) to select description depth:
| Change