Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 44 additions & 53 deletions .claude/skills/pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
---
name: pr
description: Write the pull request for the current famstack branch, a title that is the changelog line and a description by the repo's Pull requests rules, both checked with tools/commit-lint. Use when asked to write, draft or open a PR, or for its title or squash message.
description: Write the pull request for the current branch: a title that is the changelog line and a description per the Pull requests rules, both checked with tools/commit-lint. Use when asked to write, draft or open a PR, or to produce its title or squash message.
---

# Write the pull request
# Pull request

A PR is squash merged, so its title becomes the commit subject on main, and
that subject is the line in the release notes. The rules live in
`docs/agent/dev.md`, sections "Commits: the subject is the changelog" and
"Pull requests". Read both before writing: this file is the procedure, dev.md
is the rule, and where they differ dev.md wins.
A PR is squash merged: the title becomes the commit subject on main and the
release-notes line. `docs/agent/dev.md` ("Commits: the subject is the changelog",
"Pull requests") holds the rules; this file is the procedure. dev.md wins.

## 1. Read the branch

Expand All @@ -19,83 +17,76 @@ git log --reverse --format='%h %s%n%b' origin/main..HEAD
git diff --stat origin/main...HEAD
```

Read the diff wherever the subjects don't say what an admin would notice. If
`HEAD..origin/main` is not empty, say the branch is behind; don't rebase unasked.
Read the diff wherever a subject does not state the user-visible change. Report a
non-empty `HEAD..origin/main` as "branch is behind". Do not rebase unasked.

## 2. One changelog entry, or two
## 2. One entry or two

List what a reader of the release notes would see: commits of a shown type
(`feat`, `fix`, `security`, `perf`, `docs`) and any commit with `!`, `Upgrade:`
or `BREAKING CHANGE:`. Tests, refactors and chores that serve one of those
belong to it and add no entry of their own.
Count the entries a release-notes reader sees: commits of a shown type (`feat`,
`fix`, `security`, `perf`, `docs`) plus any commit carrying `!`, `Upgrade:` or
`BREAKING CHANGE:`. Tests, refactors and chores serving one of those add no entry.

If that leaves two changes a reader would see separately, stop there. Don't
write a title with "and": say the PR should be split and which commits go in
which PR. One PR is one changelog entry.
Two visible entries mean two PRs. Never write a title containing "and": report
the split and which commits belong to which PR.

## 3. The title
## 3. Title

- `type(scope)!: subject`, with the type and scope from the tables in dev.md.
The scope is what a reader recognises (a stacklet or a bot), never a file.
- The change as an admin sees it: imperative, lowercase after the colon, no
full stop, no class or function names. At most 72 characters as commit-lint
counts them; dev.md asks under 70 for a PR. The ` (#N)` GitHub appends is
not counted.
- `!` only when the admin has to act, and then the description carries the
`Upgrade:` or `BREAKING CHANGE:` footer.
- For a one-commit branch whose subject already passes, use that subject:
GitHub takes the commit's subject as the squash title of a one-commit PR.
- Format: `type(scope)!: subject`. Types and scopes come from the dev.md tables.
- The scope names what a reader recognises, a stacklet or a bot, never a file.
- Imperative, lowercase after the colon, no full stop, no identifiers.
- Limit 72 characters as commit-lint counts them; dev.md asks for under 70. The
` (#N)` appended at merge does not count.
- Use `!` only when the user must act. The description then carries `Upgrade:`
or `BREAKING CHANGE:`.
- Single-commit branch whose subject passes: reuse that subject.

## 4. The description
## 4. Description

```
## Summary
- <what changes for the admin, and why>
- <user-visible change, and why>

Upgrade: <what the admin must do, as markdown>
Upgrade: <required action, as markdown>
Refs: FAM-12
```

- One to three bullets. The footers only when they apply.
- One to three bullets. Footers only when they apply.
- No "Test plan" section. Test output and measurements go in a PR comment.
- No em dashes: a comma, colon, period or parentheses.
- No Co-Authored-By trailer, no "Generated with" line, no tool link. The repo
is public and commit-lint rejects all three.
- No em dashes. Use a comma, colon, period or parentheses.
- No `Co-Authored-By`, no "Generated with" line, no tool link: this repository is
public and commit-lint rejects all three.

## 5. Check before showing it
## 5. Check

```bash
python3 tools/commit-lint --title "<title>"
python3 tools/commit-lint --body "<description>"
python3 tools/commit-lint --range origin/main..HEAD
```

The last one is what CI runs over every commit in the PR. Fix and re-run until
all three pass. Rewording a commit that is already pushed needs a force push,
which needs Arthur's go.
CI runs the third over every commit in the PR. Fix and re-run until all three
pass. Rewording a pushed commit needs a force push: request approval first.

Then show the title, the description in a code block, and the three results.
Output: the title, the description in a code block, the three results.

## Opening it
## 6. Open

Pushing and opening the PR are Arthur's call. Ask for his go every time, even
if he gave it for the last one. With it:
Pushing and opening a PR need explicit approval, for every push. With approval:

```bash
git push -u origin HEAD
gh pr create --base main --title "<title>" --body-file <file>
```

`--draft` if he asks for a draft. Never merge.
Add `--draft` when asked. Never merge.

## At merge time
## Merge

The repo's squash setting fills the commit body from the branch's commit
messages, not from the PR description. A footer in the description reaches
main only if the merge uses the description as the body, in the merge dialog
or with `gh pr merge --squash --body-file`. Merging is Arthur's: say this when
handing the PR over, and don't run it.
The repository builds the squash body from the branch's commit messages, not
from the PR description. A footer written in the description reaches main only
when the merge uses the description as the body: the merge dialog, or
`gh pr merge --squash --body-file`. State this when handing the PR over.

A PR Arthur marks for a rebase merge is the exception: every commit lands as
its own changelog line, so each one has to pass commit-lint on its own, and
the title is not a changelog line.
Rebase merge, when requested, is the exception: every commit becomes its own
changelog line, each must pass commit-lint alone, and the title is not a
changelog line.
Loading