PR body
The body is for whoever merges the change. It carries facts about the change in
the system's own terms. The diff already carries the files.
The gold standard
This is the target. Copy its shape, not its subject.
The whole thing is what you would say to the team in one sentence, plus the
handful of facts a merger cannot get from the title. The decisions, the
alternatives, and the operator log are in the diff and the spec, and the body
says where they are rather than repeating them.
## Motivation and Context
ghes-config now sets the memory ceilings and rate-limit posture that production already runs: 17 keys per ring for prd and stg, read off the production primary, rate limiting off. Staging had sandbox-sized ceilings on production-sized hardware.
* The 14 rate-limit keys are new to the reconciler's allowlist, on the `ghe-config` transport. dev.yaml is unchanged.
* Staging's primary was brought to those values by hand. Three of the four keys written did not exist there before, so the comparison below shows staging carrying production's values, not the appliance honouring the new ceilings.
* No reconciler instance exists in any ring, so nothing reads these files today.
Decisions and the operator log are in the change folder under `openspec/changes/archive/`.
Fixes PROJ-7351
## How Has This Been Tested?
* Production primary against staging primary after the apply: 17 lines per side, diff exit 0. Replication in sync on both replicas.
* `parity_test.sh` 37 checks pass, `python3 -m unittest discover -s scripts` 36 OK.
* `pulumi preview` against `origin/main`: this branch moves no resource.
## Checklist before requesting a review
- [x] I have performed a self-review of my code.
- [x] I have stepped through the README as though I was a new user to ensure clarity.
- [ ] I have added new or changed keys, tokens, other secrets to the DevOps 1Pass.
- [ ] I have labeled all "TO DO" items with associated Jira ticket number.
- [x] I have removed commented code from PRs to `main`.
- [x] I have followed conventional-commits standards when making this PR.
> Written by an AI agent operating for <operator>. Verify before relying on it.
What that body does
- Sections come from the team, never from you. Fill
.github/pull_request_template.md when the repo ships one, then the
organization default in the organization's .github repo. When neither
exists, read the last two merged pull requests written by someone else and
use their headings; a team convention lives in the pull requests even when it
lives in no file. Only a repo with no template and no history gets a body
with no headings.
- The opening states what the system now does, present tense, then the
state it replaced. One sentence each. It is the line you would say to the
team, and a reader who stops there has the change.
- Three or four bullets, not twelve. A bullet earns its place by carrying a
fact the opening does not imply. Never a bullet per file, and two bullets
that restate each other are one bullet.
- Point at the spec and the diff instead of summarizing them. Why this
value, what else was considered, which command ran in what order: one line
saying where that lives. A reader who wants the decision opens the spec, and
a body that retells it goes stale against it.
- Evidence is the raw number. "17 lines per side, diff exit 0", "37 checks
pass", "moves no resource". Not the method, not the command line, not a
sentence about having been careful.
- The caveat states what the evidence does not show. "The comparison shows
staging carrying production's values, not the appliance honouring the new
ceilings." That clause is the most valuable one in the body, and it is the
first one a weaker author drops. Fold the deviation that caused it into the
same bullet.
- Blast radius as mechanism. "No reconciler instance exists in any ring, so
nothing reads these files today." Never a rating of the risk.
Fixes <KEY> on its own line, and the ticket appears nowhere else.
- Tick only what is true. An unticked box for a check that does not apply
is correct, and it is more credible than a body where every box is ticked.
- Length follows the facts, and most facts are not the body's job. The
gold standard is about 1.5 KB. An earlier draft of the same change ran 2.4
KB by carrying decisions the spec already held, and prose ran 5.8 KB and
said no more.
Never
- Motivation nobody told you. "Ahead of the next release cycle" is a guess
wearing a fact's clothes.
- A clause that rates the change: "low risk", "no impact expected", "improves
maintainability", "lands cleanly". The reader can check a mechanism and cannot
check a rating.
- Anything addressed to the reviewer: "worth checking", "please look at the IAM
change". Telling a reviewer where to look tells them where not to.
- Method narration: "I ran preview against dev and prd", "verified with".
- A retelling of the spec or the diff: the reasoning behind a value, the
alternatives weighed, the order the commands ran in. Name where it lives.
- An empty template section. Cut the heading only if the template does not ship
it; otherwise fill it.
- An em dash (use parens or two sentences), emoji,
- bullets, capitalized host
or account names, and a bare file path outside code formatting.
- Three parentheses in a sentence. One in a body is fine.
Verbs
write
Write the body to a file. The gate only reads --body-file, so the file has
to exist anyway. Pass an absolute path with no ~: the gate reads the
command text, and it cannot resolve a tilde or a path that does not exist
yet. Create the file in one call and run gh in the next, or the gate sees a
file that is not there.
Score it, with the target repo so the scorer can read the repo's template:
python3 <pr-body-skill-directory>/scripts/pr_body_lint.py \
--body-file <path> --repo <repo-root> --json
Fix every blocking finding. Each carries fix text written as an instruction;
apply it rather than arguing with it. A warning is a judgment call you own.
Run the voice judge: references/judge.md. Give it the
body and git diff <base>...HEAD. The judge exists for one thing the scorer
cannot see: a fact the diff makes load-bearing that the body never mentions.
Apply the rewrites you accept, then re-score, because a rewrite can
reintroduce a rule finding.
Record the receipt, then open or edit the pull request with --body-file
pointing at the same file:
python3 <pr-body-skill-directory>/scripts/pr_body_receipt.py write <path>
Editing the file after step 5 changes its hash and voids the receipt. Run the
write verb again.
audit
Score an existing body and report. Never modify it.
gh pr view <pr> --json body -q .body > <path>.
- Score it with
--repo pointed at a checkout of the target repo.
- Run the judge over the same body, with
gh pr diff <pr> as the diff.
- Report the scorer findings by rule with their line numbers, then the judge's
rewrites. No receipt: the audit did not author the body.
The gate
A separately installed PreToolUse hook on Bash matches gh pr create and gh pr edit, hashes the
--body-file it was handed, and denies when no receipt matches that hash. It
also denies the forms it cannot read: inline --body, heredocs, a tilde path, a
file that does not exist yet.
When that hook is installed, there is no bypass and it fails closed. The escape that exists is
uninstalling the hook from ~/.claude/hooks/, which a human can do and an agent
must not.
Rules the scorer implements
scripts/pr_body_lint.py is the only rule engine. A rule named here and absent
there is decorative. Grounds and fix text for each:
references/rubric.md.
| Rule |
Fires on |
Severity |
empty-body |
a body with no non-whitespace content |
blocks |
ai-disclosure-missing |
no line disclosing AI assistance |
blocks |
empty-template-section |
a heading with nothing under it |
blocks |
vacuous-opener |
a first line like "Fix bug", "Phase 1", "Cleanup" |
blocks |
oversized-input |
input past the scorer's robustness cap |
blocks |
em-dash |
an em dash in prose |
blocks |
emoji |
an emoji codepoint in prose |
blocks |
path-in-prose |
a file path outside code formatting |
blocks |
method-narration |
"verified with", "I ran", "tested by running" |
blocks |
verdict-clause |
a clause rating the change instead of describing it |
blocks |
reviewer-instruction |
"please review", "worth checking", "take a look at" |
blocks |
bullet-per-file |
most bullets in a list of 3 or more lead with a filename |
blocks |
symbol-in-prose |
an identifier or function name outside code formatting |
warns |
Lines matching the repo's template, and the AI disclosure blockquote, are
scaffolding and exempt from the prose rules. Below 40 characters of prose the
density rules do not run at all; the structural rules run at every length.
A scorer pass means the countable defects are absent. On the labeled set it
caught 4 of 12 rejected bodies, and all 4 were empty or near-empty. Everything
else was voice, which is what the judge and the gold standard above are for.
1---2name: pr-body3description: Write a pull-request body and score it before the PR is opened, or audit an existing PR's body against the same rubric. Use when about to run gh pr create, when a PR body needs writing or rewriting, or when asked whether a PR description is any good. Invoked as /pr-body write <body-file> or /pr-body audit <pr>.4---56# PR body78The body is for whoever merges the change. It carries facts about the change in9the system's own terms. The diff already carries the files.1011## The gold standard1213This is the target. Copy its shape, not its subject.1415The whole thing is what you would say to the team in one sentence, plus the16handful of facts a merger cannot get from the title. The decisions, the17alternatives, and the operator log are in the diff and the spec, and the body18says where they are rather than repeating them.1920```markdown21## Motivation and Context2223ghes-config now sets the memory ceilings and rate-limit posture that production already runs: 17 keys per ring for prd and stg, read off the production primary, rate limiting off. Staging had sandbox-sized ceilings on production-sized hardware.2425* The 14 rate-limit keys are new to the reconciler's allowlist, on the `ghe-config` transport. dev.yaml is unchanged.26* Staging's primary was brought to those values by hand. Three of the four keys written did not exist there before, so the comparison below shows staging carrying production's values, not the appliance honouring the new ceilings.27* No reconciler instance exists in any ring, so nothing reads these files today.2829Decisions and the operator log are in the change folder under `openspec/changes/archive/`.3031Fixes PROJ-73513233## How Has This Been Tested?3435* Production primary against staging primary after the apply: 17 lines per side, diff exit 0. Replication in sync on both replicas.36* `parity_test.sh` 37 checks pass, `python3 -m unittest discover -s scripts` 36 OK.37* `pulumi preview` against `origin/main`: this branch moves no resource.3839## Checklist before requesting a review4041- [x] I have performed a self-review of my code.42- [x] I have stepped through the README as though I was a new user to ensure clarity.43- [ ] I have added new or changed keys, tokens, other secrets to the DevOps 1Pass.44- [ ] I have labeled all "TO DO" items with associated Jira ticket number.45- [x] I have removed commented code from PRs to `main`.46- [x] I have followed conventional-commits standards when making this PR.4748> Written by an AI agent operating for <operator>. Verify before relying on it.49```5051## What that body does52531. **Sections come from the team, never from you.** Fill54 `.github/pull_request_template.md` when the repo ships one, then the55 organization default in the organization's `.github` repo. When neither56 exists, read the last two merged pull requests written by someone else and57 use their headings; a team convention lives in the pull requests even when it58 lives in no file. Only a repo with no template and no history gets a body59 with no headings.602. **The opening states what the system now does**, present tense, then the61 state it replaced. One sentence each. It is the line you would say to the62 team, and a reader who stops there has the change.633. **Three or four bullets, not twelve.** A bullet earns its place by carrying a64 fact the opening does not imply. Never a bullet per file, and two bullets65 that restate each other are one bullet.664. **Point at the spec and the diff instead of summarizing them.** Why this67 value, what else was considered, which command ran in what order: one line68 saying where that lives. A reader who wants the decision opens the spec, and69 a body that retells it goes stale against it.705. **Evidence is the raw number.** "17 lines per side, diff exit 0", "37 checks71 pass", "moves no resource". Not the method, not the command line, not a72 sentence about having been careful.736. **The caveat states what the evidence does not show.** "The comparison shows74 staging carrying production's values, not the appliance honouring the new75 ceilings." That clause is the most valuable one in the body, and it is the76 first one a weaker author drops. Fold the deviation that caused it into the77 same bullet.787. **Blast radius as mechanism.** "No reconciler instance exists in any ring, so79 nothing reads these files today." Never a rating of the risk.808. **`Fixes <KEY>` on its own line**, and the ticket appears nowhere else.819. **Tick only what is true.** An unticked box for a check that does not apply82 is correct, and it is more credible than a body where every box is ticked.8310. **Length follows the facts, and most facts are not the body's job.** The84 gold standard is about 1.5 KB. An earlier draft of the same change ran 2.485 KB by carrying decisions the spec already held, and prose ran 5.8 KB and86 said no more.8788## Never8990* Motivation nobody told you. "Ahead of the next release cycle" is a guess91 wearing a fact's clothes.92* A clause that rates the change: "low risk", "no impact expected", "improves93 maintainability", "lands cleanly". The reader can check a mechanism and cannot94 check a rating.95* Anything addressed to the reviewer: "worth checking", "please look at the IAM96 change". Telling a reviewer where to look tells them where not to.97* Method narration: "I ran preview against dev and prd", "verified with".98* A retelling of the spec or the diff: the reasoning behind a value, the99 alternatives weighed, the order the commands ran in. Name where it lives.100* An empty template section. Cut the heading only if the template does not ship101 it; otherwise fill it.102* An em dash (use parens or two sentences), emoji, `-` bullets, capitalized host103 or account names, and a bare file path outside code formatting.104* Three parentheses in a sentence. One in a body is fine.105106## Verbs107108### write1091101. Write the body to a file. The gate only reads `--body-file`, so the file has111 to exist anyway. Pass an absolute path with no `~`: the gate reads the112 command text, and it cannot resolve a tilde or a path that does not exist113 yet. Create the file in one call and run `gh` in the next, or the gate sees a114 file that is not there.1152. Score it, with the target repo so the scorer can read the repo's template:116117 ```sh118 python3 <pr-body-skill-directory>/scripts/pr_body_lint.py \119 --body-file <path> --repo <repo-root> --json120 ```1211223. Fix every blocking finding. Each carries fix text written as an instruction;123 apply it rather than arguing with it. A warning is a judgment call you own.1244. Run the voice judge: [references/judge.md](references/judge.md). Give it the125 body and `git diff <base>...HEAD`. The judge exists for one thing the scorer126 cannot see: a fact the diff makes load-bearing that the body never mentions.127 Apply the rewrites you accept, then re-score, because a rewrite can128 reintroduce a rule finding.1295. Record the receipt, then open or edit the pull request with `--body-file`130 pointing at the same file:131132 ```sh133 python3 <pr-body-skill-directory>/scripts/pr_body_receipt.py write <path>134 ```135136Editing the file after step 5 changes its hash and voids the receipt. Run the137write verb again.138139### audit140141Score an existing body and report. Never modify it.1421431. `gh pr view <pr> --json body -q .body > <path>`.1442. Score it with `--repo` pointed at a checkout of the target repo.1453. Run the judge over the same body, with `gh pr diff <pr>` as the diff.1464. Report the scorer findings by rule with their line numbers, then the judge's147 rewrites. No receipt: the audit did not author the body.148149## The gate150151A separately installed PreToolUse hook on `Bash` matches `gh pr create` and `gh pr edit`, hashes the152`--body-file` it was handed, and denies when no receipt matches that hash. It153also denies the forms it cannot read: inline `--body`, heredocs, a tilde path, a154file that does not exist yet.155156When that hook is installed, there is no bypass and it fails closed. The escape that exists is157uninstalling the hook from `~/.claude/hooks/`, which a human can do and an agent158must not.159160## Rules the scorer implements161162`scripts/pr_body_lint.py` is the only rule engine. A rule named here and absent163there is decorative. Grounds and fix text for each:164[references/rubric.md](references/rubric.md).165166| Rule | Fires on | Severity |167| --- | --- | --- |168| `empty-body` | a body with no non-whitespace content | blocks |169| `ai-disclosure-missing` | no line disclosing AI assistance | blocks |170| `empty-template-section` | a heading with nothing under it | blocks |171| `vacuous-opener` | a first line like "Fix bug", "Phase 1", "Cleanup" | blocks |172| `oversized-input` | input past the scorer's robustness cap | blocks |173| `em-dash` | an em dash in prose | blocks |174| `emoji` | an emoji codepoint in prose | blocks |175| `path-in-prose` | a file path outside code formatting | blocks |176| `method-narration` | "verified with", "I ran", "tested by running" | blocks |177| `verdict-clause` | a clause rating the change instead of describing it | blocks |178| `reviewer-instruction` | "please review", "worth checking", "take a look at" | blocks |179| `bullet-per-file` | most bullets in a list of 3 or more lead with a filename | blocks |180| `symbol-in-prose` | an identifier or function name outside code formatting | warns |181182Lines matching the repo's template, and the AI disclosure blockquote, are183scaffolding and exempt from the prose rules. Below 40 characters of prose the184density rules do not run at all; the structural rules run at every length.185186A scorer pass means the countable defects are absent. On the labeled set it187caught 4 of 12 rejected bodies, and all 4 were empty or near-empty. Everything188else was voice, which is what the judge and the gold standard above are for.