Write a PR title and description
Use this template for writing the PR body:
## Summary
<diagram, diff-sketch, or tree>
## Evidence
- **Before:** <screenshot/output/failing test run>
**After:** <screenshot/output/passing test run>
## Merge Danger
**Door:** one-way or two-way
**Blast Radius:** <potential ramifications of merge>
Closes PROJ-XXXX. Part of PROJ-YYYY.
Setup: the tracker steps use an issue-tracker MCP server (
mcp__linear-server__*tool names are placeholders: swap them for your tracker's tools). The ticket prefix (PROJ) comes fromTRACKER_ISSUE_PREFIXin.env. No tracker? Skip step 2 and drop the ticket id and the closing line everywhere.
Sections
Skip all preambles and keep prose brief. Use the user's domain language from docs/glossary.md.
Summary
Pick the smallest view that makes the key point clear.
- Show logic or an algorithm as pseudocode:
on(save)
if content is unchanged
return cached result
write new content
return fresh result
- Show runtime control flow as a call tree:
submitForm
createSession
persistPrompt
launchAgent
navigateToSession
- Show UI structure as a component tree, including state and module boundaries that matter:
<SessionPage> (apps/example/src/routes/session.tsx)
useSessionEvents()
<SessionToolbar>
<RunSkillButton> (packages/ui)
- Show file responsibility or a broad refactor as a shallow file tree:
src/
├── commands/ # parses user actions
├── sessions/ # owns session state
└── transport/ # sends API requests
- Show component interaction, control flow, or data flow with Mermaid:
sequenceDiagram
participant User
participant UI
participant Daemon
User->>UI: choose command
UI->>Daemon: send expanded prompt
Daemon-->>UI: stream result
- Use
diffwhen the point is what changes and the surrounding shape already exists. Match the diff shape to the topic.
For a component change:
<SessionPage>
useSessionEvents()
<SessionToolbar>
+ <RunSkillButton />
<SessionTimeline>
+ <SkillResultCard />
For a file-layout change:
src/
├── commands/
+│ └── show-me.ts # expands the slash command
├── sessions/
-└── transport.ts
+└── transport/
+ ├── client.ts
+ └── stream.ts
For a call-tree or call-stack change:
submitForm
createSession
persistPrompt
+ expandSkillMention
launchAgent
- navigateToSession
+ navigateToSession
+ subscribeToEvents
For a state or control-flow change:
on(save)
- write content
+ if content is unchanged
+ return cached result
+ write new content
+ invalidate cache
- Show the whole block when most of it is new, when omitted context would hide ownership or order, or when the user needs a copyable target shape:
function expandSkill(command: string): string {
const skillName = command.slice(1);
return `use the ${skillName} skill`;
}
Guidance
Place each visual next to the short text it supports. Keep only the calls, files, props, states, and boundaries needed to answer the user's current question or the options to resolve the current discussion point.
You may use one of these, you may use several, it is unlikely you will use all of them. Use your judgement and don't overwhelm the user.
Evidence
Concrete evidence that the change works. Show a before and after.
Screenshots are S-tier, when the environment is set up for it and the change is visual.
Execution-based evidence is A-tier. Test results, console output. Show the exact test that now fails and passes, using pseudocode.
Merge Danger
Describe whether it's a one-way or two-way door. You can walk back through two-way doors, but not one-way doors. A PR that is cheap to roll back is lower risk. Changes that involve destructive actions or hard-to-reverse decisions are one-way doors.
The blast radius is the potential impact or scope of the changes introduced by this PR. Consider all possibilities. Examples are layout shift, breakages for consumers, mobile responsiveness, etc.
Workflow
Gather context (run together):
The trunk is
GIT_TRUNKfrom.claude/project.env(defaultmain); substitute it below.git branch --show-current # MUST NOT be the trunk; abort if it is git log <trunk>..HEAD --pretty=format:'%h %s%n%b' # commits on this branch git diff <trunk>...HEAD --stat # changed files + churn gh pr view --json number,url,state,title,body 2>/dev/null # non-zero exit = no PR yetRead the diff for the key files (
git diff <trunk>...HEAD -- <path>); on large diffs lean on--statplus the important files.Ground in the tracker (read-only). Grep branch + commit subjects for
PROJ-\d+. If found, fetch the issue and its parent withmcp__linear-server__get_issuefor the problem, the intent, and the canonical link. NEVER call anysave_*tool. No id: derive from diff + commits.Draft title + body together. Title per Title format; body to a temp file (
mktemp) per the template and Sections, obeying Body rules. For an existing PR, treat the current title and body as a draft, not a constraint.Create or update the PR. Show the drafted title and body + the exact
ghcommand; quick confirm before running (notifies reviewers + CODEOWNERS) unless told to just do it.git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null || git push -u origin "$(git branch --show-current)" # update existing; pass --title only if it changes: gh pr edit <number> --body-file <tmp> [--title "<drafted title>"] # or create new: gh pr create --base <trunk> --title "<drafted title>" --body-file <tmp>Report the PR URL.
Title format
Communicate what changed and why at a glance, specific enough that a reviewer can predict the diff. GitHub auto-appends (#NNNN) on merge; do not write it yourself.
<type>(<scope>): <summary> (PROJ-XXXX)
<type>: dominant intent of the diff.Type Use for featNew feature or capability fixBug fix refactorCode restructuring with no behaviour change docsDocumentation only (includes ADRs) testAdding or updating tests choreBuild config, dependencies, tooling perfPerformance improvement Mixed diff: pick the user-visible win, mention the rest in the Summary.
<scope>: optional but usually present. Lowercase kebab, naming the component or area touched. Common scopes in this repo: . Multi-scope(api,db)only when both are non-trivial. Drop the scope when the change is repo-wide.<summary>: imperative present tense, lowercase first word, no trailing period. Proper nouns keep their case (PostgreSQL,GitHub,ADR-0014); double quotes around identifiers are fine.(PROJ-XXXX): most specific tracker id (slice over spec); drop entirely if none. Follow-ups with no ticket:(follow-up to #NNNN).
Lint:
- No em-dash (
—/–). Hyphen, colon, or rephrase. - No trailing period; no capital after the colon (proper nouns excepted).
- ≲ 80 chars including
(PROJ-XXXX); if over, trim adjectives, not specificity. - Reject generic verbs (
update,improve,change,various). Strong fix-title names cause, surface, impact:fix(api): race condition in cache invalidation that caused 502s under load, notfix bug.
Worked examples:
| Diff shape | Title |
|---|---|
| FE + API slice tied to a ticket | feat(admin): rename and soft-archive a workspace (PROJ-2509) |
| Targeted backend bug, no ticket | fix(db): resolve ambiguous "id" in admin list/detail reads |
| Docs/ADR landing ahead of impl | docs: add ADR-0014 per-workspace retention policies + Retention Policy glossary term |
| Follow-up to a prior PR | docs: scrub em-dashes from ADR-0014 (follow-up to #1287) |
| Repo-wide change, no scope | feat: collapse the user-role enum to admin and member (PROJ-2504) |
Body rules
- No em-dash (
—/–). Hyphen or colon. - No hardcoded hostnames/URLs. Derive from config; link tracker issues by id/URL only.
- Tracker auto-link + auto-close. The title's
(PROJ-XXXX)links the ticket. End the body withCloses PROJ-XXXX.so the slice ticket auto-transitions to Done on merge. For spec context addPart of PROJ-YYYY(non-closing magic word; links only). Magic words must appear in the PR description, not in a comment. OmitClosesonly when the PR has no tracker id. - No attribution footer. Never add
🤖 Generated with Claude Code(or any agent attribution) to the PR body. TheCo-Authored-Bytrailer on commits is the only attribution.
The Summary views are adapted from Dex Horthy's show-me skill (humanlayer/skills).