AI Diff Reviewer — Open PR (sub-skill)
Companion to the ai-diff-reviewer skill. Where the parent
reviews the current branch's diff locally, this sub-skill takes the
next logical step: authors a well-documented pull request from the
same diff — title, body, and (on request) a draft opener.
The design philosophy is unopinionated where it can be, opinionated where
it needs to be:
- Unopinionated: the section shape adapts to your
.github/pull_request_template.md when one exists. If your team already
writes PRs with ## Rollout plan and a ## Security review checkbox,
those survive intact.
- Opinionated: every PR gets a Summary that leads with the outcome,
a Test plan a reviewer can actually run, and — when the diff
signals it — a Screenshots prompt for UI changes and a Breaking
changes callout for API changes. These are the sections whose absence
reviewers complain about; the skill won't let them slip.
Nothing gets pushed. Nothing gets merged. The skill only ever writes the
PR title + body on the remote via gh, always after a single-yes
preview.
When it fires
Create a fresh PR — triggers:
- "Open the PR", "open a pull request for this branch"
- "Create the PR", "create a pull request"
- "Draft the PR title and description"
- "I'm ready to push this for review — write the PR"
- "Make a draft PR" (adds
--draft)
Refresh / fix an existing PR — triggers:
- "Update the PR description"
- "The PR body is a one-liner — rewrite it in the proper format"
- "Fix the PR title, it doesn't match the diff anymore"
- "Refresh the PR body — I pushed new commits"
Fall through to a sibling skill when the developer:
- Wants a local review before pushing → parent
ai-diff-reviewer skill (default review flow). This
sub-skill is the natural next step after that review.
- Wants to install the CI action →
setup.
- Wants to customize the review →
generate-extension.
- Wants a git commit message (not a PR body) → defer; this skill
writes PRs, not individual commits.
If the intent is ambiguous ("write the PR" on a branch that's not
pushed yet, or on a repo with no origin set), ask ONE clarifying
question before acting.
Step 0 — Trust boundary
This skill writes two things on the remote, both only after an
explicit yes in the Step 6 preview:
- The PR title (via
gh pr create --title or gh pr edit --title).
- The PR body (via
--body-file so multi-line Markdown round-trips
cleanly).
It does not:
- Push commits, force-push, rebase, cherry-pick, or rewrite git history.
- Auto-add reviewers, assignees, labels, milestones, or projects unless
the developer explicitly asks and confirms. (
--assignee @me is the
one exception in create mode — always applied.)
- Auto-merge, auto-approve, or convert draft ↔ ready-for-review without
explicit intent in the trigger.
- Touch any file in the working tree.
- Call the LLM provider directly, or send data anywhere besides the
gh API calls it announces in the preview.
If the branch is not pushed, the skill surfaces the exact command
(git push -u origin <branch>) and stops. It never runs git push
itself.
Step 1 — Detect context
Establish the mode (create vs. edit vs. refuse) before drafting anything.
# Current branch + head SHA
HEAD_BRANCH="$(git branch --show-current)"
HEAD_SHA="$(git rev-parse --short HEAD)"
# Base: prefer the tracked upstream's short name, fall back to the repo's
# default branch (asks gh; falls back to `main` if gh unavailable).
BASE="$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null | sed 's|.*/||')"
if [ -z "$BASE" ]; then
BASE="$(gh repo view --json defaultBranchRef --jq .defaultBranchRef.name 2>/dev/null || echo main)"
fi
# Repo slug (owner/repo)
REPO="$(gh repo view --json nameWithOwner --jq .nameWithOwner 2>/dev/null || echo '')"
# Does a PR already exist for the current branch?
PR_JSON="$(gh pr view --json number,url,title,body,isDraft,baseRefName,headRefName,state,author 2>/dev/null || true)"
Decide the mode from the state:
| State |
Mode |
Skill behavior |
On main / master / develop / trunk with no PR |
refuse |
Ask the developer to switch to a feature branch first. |
| Detached HEAD |
refuse |
Ask the developer to check out a branch first. |
PR_JSON non-empty and state is OPEN |
edit |
Load the existing title + body; go to Step 2 (edit path). |
PR_JSON non-empty and state is CLOSED or MERGED |
refuse-soft |
Do not edit a closed/merged PR. Offer: (a) open a new PR from the same branch (create mode), or (b) reopen the closed one manually then re-run this skill. Never gh pr reopen automatically. |
PR_JSON empty |
create |
Plan gh pr create against <base>; go to Step 2 (create path). |
| Working tree has uncommitted changes that look scope-relevant to the diff |
warn |
Show git status --short; ask "commit these first? (y/n)". Never git add on the developer's behalf. |
On gh missing or unauthenticated: surface the exact remediation
(brew install gh or gh auth login) and stop. Do not attempt to draft
the body — writing a PR body the developer has to manually paste is a
downgrade; use the non-blocking rule below instead.
Step 2 — Read the diff and gather signals
Read enough diff to draft the sections; do not stuff the whole patch
into your working context.
Create mode
git fetch origin "$BASE" --quiet 2>/dev/null || true
# Summaries first (cheap):
git diff --stat "origin/${BASE}...HEAD"
git log --oneline "origin/${BASE}..HEAD"
# Full patch second (may be large; read it, but do not paste it back):
git diff "origin/${BASE}...HEAD"
Edit mode
gh pr diff "$PR_NUMBER" --patch
gh pr view "$PR_NUMBER" --json title,body,commits,files,isDraft,url
Signal collection (both modes)
While reading the diff and commit trail, extract these signals —
they drive which optional sections appear in the body:
Issue references. Grep the commit messages for
(Closes|Fixes|Resolves|Refs) #\d+ (case-insensitive). Collect every
unique reference; these go into the ## Related issues section
verbatim. If the diff title has a (#123)-style trailer, capture
that too.
Breaking-change markers. Look for BREAKING CHANGE: in commit
bodies (Conventional Commits marker), any commit subject with
!: (e.g. feat!: or refactor!:), or diff removals in public API
surface (exported functions, action.yml inputs, CLI flags,
published HTTP routes). Any hit → ## Breaking changes section is
mandatory.
UI signals. Look at the file extensions in git diff --name-only "origin/${BASE}...HEAD". If any of .tsx, .jsx, .vue, .svelte,
.astro, .css, .scss, .less, .html are touched (excluding
docs/**, README*, CHANGELOG*) → prompt for screenshots in the
preview (opt-in — never fabricate).
Migration signals. File names matching
migrations?/, alembic/versions/, db/migrate/, prisma/migrations/,
atlas.hcl, or *.sql → ## Migrations section is mandatory.
Dependency signals. Any of package.json, package-lock.json,
pnpm-lock.yaml, yarn.lock, requirements*.txt, pyproject.toml,
Pipfile, Pipfile.lock, poetry.lock, Gemfile, Gemfile.lock,
go.mod, go.sum, Cargo.toml, Cargo.lock, composer.json,
composer.lock touched → ## Dependencies section auto-populated
with what changed.
Convention detection. Read the last 20 merged PR titles to detect
the repo's title convention empirically rather than assuming
Conventional Commits:
gh pr list --state merged --limit 20 --json title --jq '.[].title'
If ≥ 60% of them match ^(feat|fix|docs|chore|refactor|test|ci|perf|style|build|revert)(\(.+\))?!?: ,
use Conventional Commits (Step 4). Otherwise, use plain-sentence titles.
Cache the observation for the preview — surface "detected convention:
Conventional Commits (14/20 recent PRs match)" so the developer can
override.
Emit these signals to your working context; do not print them to the
developer unless the preview needs them.
Step 3 — Merge with the repo's PR template (when present)
GitHub renders .github/pull_request_template.md (or
.github/PULL_REQUEST_TEMPLATE.md — same file, different casing) into
the PR body input by default when a developer opens a PR in the UI. This
skill respects that convention:
TEMPLATE=""
for path in \
.github/pull_request_template.md \
.github/PULL_REQUEST_TEMPLATE.md \
docs/pull_request_template.md \
pull_request_template.md
do
if [ -r "$path" ]; then TEMPLATE="$(cat "$path")"; break; fi
done
Merge rules (never overwrite):
| Template has |
Skill's body has |
Result in final body |
## Summary |
## Summary |
Skill's Summary wins (it's diff-derived); heading kept. |
## Test plan |
## Test plan |
Skill's Test plan wins; heading kept. |
## Checklist (repo-specific tick boxes) |
(no equivalent) |
Preserved intact — appended at the end. |
## Rollout plan (or any custom section) |
(no equivalent) |
Preserved intact — appended at the end. |
<!-- HTML comment instructions --> |
(no equivalent) |
Stripped from the final body. Instructions are for the author, not the reviewer. |
Surface the merge outcome in the Step 6 preview: "Merged with
.github/pull_request_template.md: preserved ## Checklist and ## Rollout plan; the template's <!-- comment --> guidance was
stripped." The developer can override any auto-merged section in the
edit branch of the confirmation.
If no template exists, use the default body template from Step 5.
Step 4 — Draft the title
Title budget: ≤ 72 chars total (GitHub truncates in list views
around 80). The body carries the rest.
4a. Convention detection (see Step 2)
If Step 2 detected Conventional Commits, use the format below. If not,
use a plain sentence — capitalized first word, no trailing period, ≤ 72
chars.
4b. Conventional Commits type inference
Infer <type> from the diff signals, in this precedence order (first
match wins):
| Diff signal |
<type> |
A commit subject already starts with feat!: / fix!: / perf!: (breaking change marker) |
Preserve that type; add ! marker on the PR title too. |
Bug-fix language in commit messages (fix:, fixes #, resolves #, "bug", "hotfix") |
fix |
Only docs/**, README*, CHANGELOG*, or *.md files touched |
docs |
Only .github/workflows/**, .github/actions/**, CI configs (.circleci/, .gitlab-ci.yml, azure-pipelines.yml) touched |
ci |
Only test files touched (tests/**, test/**, **/*.test.*, **/*_test.go, **/*_spec.rb, __tests__/**) |
test |
| Only dependency manifests + lockfiles touched (no source changes) |
chore(deps) |
Performance markers in commits (perf:, "optimize", "cache", "batch", "lazy", "debounce", "virtualization") |
perf |
| Renames / moves / extractions without behavior change (large diff, small semantic change) |
refactor |
Only style-layer files touched (.css, .scss, Tailwind class-only changes, formatting-only commits) |
style |
| Adds a new module / route / feature flag / schema field / CLI subcommand |
feat |
| Everything else with user-visible behavior |
feat |
| Everything else without user-visible behavior |
chore |
4c. Scope
<scope> is the shortest identifier that names the touched surface:
- If the diff is concentrated (>70% of touched lines) in a single
top-level directory, use that directory's basename:
auth, chat,
billing, docs, reviewer.
- If the change touches a single well-known concept across many
directories (e.g. logging refactor), use that concept:
logging,
i18n.
- If no clear single scope dominates, omit the scope:
feat: …
rather than feat(mixed): ….
4d. Description
- Imperative mood, present tense ("add", "fix", "rename" — not "added"
or "adds").
- No trailing period.
- Concrete over vague:
fix(auth): reject empty callback state beats
fix(auth): handle edge case.
- No
[WIP], no ticket numbers in the title (issue links go in the
body). Draft state is expressed via --draft, not [WIP].
4e. Examples
feat(reviewer): add per-repo severity overrides via extension file
fix(auth): reject OAuth callback when state param contains slash
docs(prompts): document local↔CI parity for extension files
chore(deps): bump actions/checkout from v4 to v5
feat!: rename api-key input to provider-api-key (breaking change)
Step 5 — Draft the body
The default body template — used when no PR template exists, or as the
starting point when one does. Every section explains why it's here
inline so the writing rules are self-enforcing.
## Summary
<1–3 sentences. Lead with the OUTCOME (what ships and why), not the
mechanism. Plain English. No diff stats. No file paths. ≤ 280 chars.>
## Changes
<3–8 bullets naming the concrete edits. Each ≤ 15 words. Group by area
if there are multiple concerns. Example:
- Add `PromptExtensionLoader` that reads `.review/extension.md`
- Concatenate extension content to base prompt at boot
- Log the composed prompt's byte size at DEBUG for verifying parity
>
## Related issues
<Only present when Step 2 found issue references. Use GitHub's linking
keywords so the issues auto-close on merge:
- Closes #123
- Fixes #456
- Refs #789 (context only — not closed by this PR)
Omit the section entirely when there are none — never write "N/A".>
## Test plan
<3–8 imperative bullets grouped into named categories. Minimum 3 bullets.
Each bullet ≤ 15 words. A reviewer must be able to run each bullet
mechanically. Example:
**Happy path**
- Run `python3 -m unittest discover -s tests`
- Open a PR against `main` on a fork; confirm review comment appears
**Edge cases**
- Empty diff (no changes) → skill prints "nothing to review" and exits 0
- Base branch does not exist locally → falls back to `git merge-base`
**Regression**
- Existing `strictness: block-on-warning` behavior unchanged on PR #212
>
## Screenshots
<UI CHANGES ONLY. Only present when Step 2 detected touched UI files
AND the developer confirmed screenshots exist. Format:
- Before: [drag image or paste URL]
- After: [drag image or paste URL]
Omit the section entirely when there are no UI changes — never write
"N/A".>
## Breaking changes
<MANDATORY when Step 2 detected a breaking-change signal
(`feat!:`, `BREAKING CHANGE:`, removed public API). 2-4 sentences with:
1. What breaks — the exact symbol / input / URL / behavior.
2. Who is affected — first-party consumers, third-party, both.
3. Migration path — the concrete steps a consumer takes to adapt.
Example:
- **What breaks:** the `api-key` input has been renamed to
`provider-api-key` in `action.yml`.
- **Who is affected:** every consumer of `DailybotHQ/ai-diff-reviewer@v2`
/ `@v2.x` — YAML workflows will fail with "invalid input" at boot.
- **Migration:** rename the key in your workflow YAML:
`with: { api-key: ... }` → `with: { provider-api-key: ... }`.
No code changes required.
Omit the section entirely when there are no breaking changes.>
## Risks
<2-4 sentences with a 3-part structure: [failure mode] — [affected scope]
— [rollback plan]. Keep at the product / rollout level, not
implementation. ≤ 400 chars. For low-risk additive changes, use:
"Low risk — change is additive (no existing behavior modified). Affected:
[area]. Rollback: revert the PR; no data migration involved."
Always include a rollback plan — never write bare "None" or "Low risk"
without the mitigation.>
## Migrations
<Only present when Step 2 detected migration files. Format:
- `migrations/0042_add_notification_prefs` — adds 3 NULLABLE columns,
online, reversible via `db migrate down`
- `migrations/0043_backfill_prefs` — batched data backfill, idempotent
Omit the section entirely when no migrations touched.>
## Dependencies
<Only present when Step 2 detected dependency changes. Format:
- Added: `zod@3.22.0` (schema validation for extension file parsing)
- Removed: `joi` (replaced by zod)
- Bumped: `actions/checkout@v4 → v5` (upstream security fix)
Omit the section entirely when no deps touched.>
Writing rules (self-enforcing — apply them as you draft)
- Outcome first — the Summary's first sentence is what the change
does for the user, not how it does it. "Ship per-team notification
preferences" not "Refactor the NotificationSettings class."
- Plain English over jargon — translate to user impact. "Reduce
p95 checkout latency from 4.2s to 1.1s" not "Migrate to
Prisma-native transactions."
- Concrete beats vague — quantify when possible. "Low risk" alone
is noise; always include the mitigation.
- No file paths, diff stats, line counts, commit hashes in the
body. The GitHub PR diff has all of that one click away.
- No agent / tool attribution ("Claude built…", "Cursor
generated…") — the team shipped it.
- Optional sections vanish when empty — never
N/A, never
TBD, never a section with a single "None" bullet. Absence is
information.
- Imperative mood in bullets — "Add", "Fix", "Rename", not
"Added", "Adds", "This adds".
- Two to four sentences per prose section — one is too vague, five
is a brain dump. Two to three is the sweet spot.
- No
@-handles inside section bodies — reviewer / assignee
handles belong in GitHub's reviewer/assignee UI, not in the free-form
body. The one exception: mentioning a domain owner for context
("Coordinated with @security-team on the token handling") is fine
and encouraged when the change crosses ownership boundaries.
- Preserve the repo's brand spelling — grep for the product name in
README.md and use that exact capitalization throughout the body.
Step 6 — Preview + single confirmation
Show a tight, readable preview in this exact order — this is the
"look-once-yes-once" contract:
About to <create | update> a pull request on <owner/repo>:
Branch: <head> → <base>
Draft: <yes | no>
Convention: <Conventional Commits | plain sentence>
(detected: <n>/20 recent merged PRs match Conventional Commits)
Title: <the final title>
Body (Markdown, sections in final order):
─────────────────────────────────────────────
<full body — every section header + content, verbatim as it will be
written; no truncation, no ellipsis>
─────────────────────────────────────────────
PR template: <detected at .github/pull_request_template.md | none>
Merge decisions: <"Preserved: ## Checklist, ## Rollout plan. Overrode:
## Summary, ## Test plan (skill-anchored versions
used so they stay diff-derived)." | "N/A — no template">
Sections included:
✓ Summary (mandatory)
✓ Changes (mandatory)
✓ Test plan (mandatory)
✓ Related issues (detected #123, #456)
✓ Breaking changes (detected `feat!:` in commit 3f2c9a1)
✗ Screenshots (UI files touched: 4 — SKIPPED per your input)
✓ Risks (mandatory)
✗ Migrations (no migration files detected)
✓ Dependencies (detected package.json changes)
Command: <gh pr create --base <base> --head <branch> [--draft]
--assignee @me --title "<title>" --body-file <tmpfile>
| gh pr edit <n> --title "<title>" --body-file <tmpfile>>
Confirm? (yes / edit / cancel)
Response handling:
- yes → Step 7.
- edit → ask which section(s) or field(s) to change (title, or a
named body section). After the developer's edit, RE-RENDER the whole
preview and ask again. No silent partial application.
- cancel → stop. Print nothing else.
Edit mode addendum: when refreshing an existing PR, also print a
compact diff of the body — the developer needs to see what's
changing, not just what will be written:
Body diff (current PR → proposed):
─────────────────────────────────────────────
- fix bug in oauth callback
+ ## Summary
+ Fix OAuth callback failing when the `state` query param contains a
+ slash. Users hitting Microsoft / GitHub Enterprise were redirected
+ to /404 instead of completing sign-in.
+ …
─────────────────────────────────────────────
Never skip confirmation. A PR title + body is reviewer-visible,
consumer-visible, and often lifted verbatim into release notes.
Step 7 — Execute via gh
Write the body to a temp file so multi-line content, code fences, and
HTML comments round-trip cleanly. Allow ≥ 30 s for either call
(gh can be slow on repos with many reviewers or protection rules).
Create mode
BODY_FILE="$(mktemp -t ai-diff-reviewer-body.XXXXXX.md)"
printf '%s\n' "$BODY" > "$BODY_FILE"
CMD=(gh pr create
--title "$TITLE"
--body-file "$BODY_FILE"
--base "$BASE"
--head "$HEAD_BRANCH"
--assignee @me)
# Add --draft only when the developer's trigger explicitly asked for it
# ("draft PR", "WIP PR", "--draft").
[ "$DRAFT" = "true" ] && CMD+=(--draft)
"${CMD[@]}"
rm -f "$BODY_FILE"
After success:
- Capture the PR URL from
gh pr create stdout.
- Print the URL + a one-line "next step" hint (see Step 8).
Edit mode
BODY_FILE="$(mktemp -t ai-diff-reviewer-body.XXXXXX.md)"
printf '%s\n' "$BODY" > "$BODY_FILE"
gh pr edit "$PR_NUMBER" \
--title "$TITLE" \
--body-file "$BODY_FILE"
rm -f "$BODY_FILE"
Never pass --reviewer, --assignee (other than @me in create mode),
--label, --milestone, or --project unless the developer's trigger
explicitly asked for them AND those choices appeared in the Step 6
preview.
Step 8 — Surface the result + hand-off
On success:
✓ <Created | Updated> PR #<n> — <title>
<URL>
Next steps:
- Review it in the browser: gh pr view <n> --web
- Watch CI as it runs: gh pr checks <n> --watch
- Read the CI review once it lands: (see the `apply-review` sub-skill,
or the trigger "read the CI review
on my PR")
- When CI is green: gh pr merge <n> --squash --delete-branch
(or use the merge button on the PR page)
If the repo has the AI Diff Reviewer CI Action installed (detectable by
grepping .github/workflows/ for DailybotHQ/ai-diff-reviewer), the
apply-review sub-skill is the natural next step after this PR is open:
CI will post its review within a few minutes, and apply-review will
read it back, present the findings in the same format as the local
review, and — with per-finding consent — help apply the fixes.
If the parent ai-diff-reviewer skill's local review flow ran earlier
in the same session and produced any 🚨 critical or ⚠️ warning findings,
append a soft nudge:
Note: the local review in this session found <n> critical / <m>
warning findings. Consider addressing them before requesting review,
or leave them noted in the PR description under a "Known issues"
bullet in `## Risks`.
On failure: quote the gh error verbatim. Do NOT retry in a loop
(non-blocking rule). Common ones:
gh error |
Meaning |
Skill's action |
not logged in / authentication required |
gh unauthenticated |
Print gh auth login; stop. |
no commits between <base> and <head> |
Branch not pushed |
Print git push -u origin <branch>; stop. |
pull request already exists for branch |
Race condition |
Re-run Step 1 to load the just-created PR; switch to edit mode; re-preview. |
base <name> not found |
Typo'd base branch |
Print gh repo view --json defaultBranchRef; ask developer for the correct base; re-preview. |
Resource not accessible by integration |
Fork PR without write access |
Explain the limitation (fork PRs need gh pr create --repo <upstream> and the developer's own token); suggest the developer runs the command manually with the printed title + body. |
| Any 5xx from GitHub |
Server-side |
Stop. Print body draft to terminal so the developer can paste in the UI. Do not loop. |
Guardrails — what this skill MUST NOT do
- Never push commits, force-push, rebase, or rewrite history. If the
branch is not pushed, print
git push -u origin <branch> and stop.
- Never auto-merge, auto-approve, auto-request-review, or convert
draft ↔ ready-for-review without the developer saying so in the
trigger AND confirming in the preview.
- Never
gh pr reopen a closed PR. Offer to create a new PR from
the current branch instead.
- Never fabricate issue references, CI URLs, screenshots, migration
claims, or dependency changes. If a signal is unclear, ask ONCE or
omit the section — never invent.
- Never write
N/A, TBD, None, or - as a placeholder for an
optional section. Omit the whole section (heading + body) together
instead. Absence is information.
- Never overwrite
.github/pull_request_template.md sections
silently — Step 3 merge rules apply, and the merge decision is
surfaced in the preview.
- Never paste raw
git diff output, git diff --stat output, file
paths, line counts, or commit hashes into the body. The GitHub diff
has all of it one click away.
- Never embed reviewer / assignee
@-handles inside body sections.
Handles belong in GitHub's reviewer UI, not in prose.
- Never open a PR from a protected branch (
main, master,
develop, trunk). Refuse and ask the developer to switch first.
- Never open a PR while the working tree has uncommitted scope-relevant
changes. Warn, show
git status --short, and ask. Do not git add.
- Never skip the Step 6 confirmation — even on trivial edits, even
in edit mode.
Coordinating with other skills
../SKILL.md (parent — local review flow) —
natural predecessor. The recommended flow is: (1) run the local
review, (2) act on the findings, (3) open the PR. The parent's Step
4 output ("Verdict + Findings table + Recommendation") is exactly the
input a developer needs to draft a good Summary + Test plan here.
../apply-review/SKILL.md (sibling —
read + apply the CI review on the PR) — natural successor. Once
this sub-skill has opened the PR and CI has posted its review, the
apply-review sub-skill reads that review back locally and walks
the developer through the findings. After they apply fixes and
push, come back here in edit mode if the PR scope changed
enough to warrant a body refresh.
../setup/SKILL.md (sibling — install the CI
action) — different phase of the lifecycle. Runs once per repo when
the reviewer is being introduced; this sub-skill runs once per PR.
If the developer asks to open a PR on a repo that has no
.github/workflows/pr-review.yml, this skill still authors the PR
correctly — CI review is set up separately.
../generate-extension/SKILL.md
(sibling — tailor the reviewer prompt) — orthogonal. Extensions
affect what the reviewer flags; this skill only reads the diff.
Non-blocking rule
Authoring a PR title + body must never block the developer's primary
work. If gh is missing, unauthenticated, network is down, or the
API call errors:
- Print the drafted title + body to the terminal in a copy-pasteable
block. The developer can paste it into the GitHub UI themselves.
- Print the exact remediation (
gh auth login, git push -u origin,
etc.) once.
- Do not retry automatically.
- Do not enter a diagnostic loop.
- End the turn.
This matches the parent skill's non-blocking discipline.
Sample dialogues
Dialogue A — Create a fresh PR after a local review
Developer: [after the parent skill just produced a review with 2 info
findings and no blockers]
"great, open the PR"
Skill:
1. Detect: branch=feat/notif-prefs, base=main, no PR yet, tree clean.
2. Diff: 9 files, +412/-87, mostly under app/notifications/.
Commits reference: Closes #341, Refs #201.
Signals: UI (2 .tsx files), migration (1 file), deps (none).
Convention: Conventional Commits (17/20 recent PRs match).
3. Template: .github/pull_request_template.md present → merge.
Preserved `## Checklist` intact; skill sections override
the template's `## Summary` and `## Test plan`.
4. Title: feat(notifications): per-team notification preferences
5. Body draft:
## Summary
Ship per-team notification preferences so admins can mute Slack
pings without losing email digests. Closes the #2 complaint from
enterprise onboarding.
## Changes
- Add `NotificationPrefs` model + REST endpoints
- Wire the toggle UI into the team-settings page
- Migrate legacy `notification_settings` rows to per-team scope
## Related issues
Closes #341
Refs #201
## Test plan
**Happy path**
- Mute Slack pings for one team; confirm email digest still arrives
- Toggle off all channels; confirm no notifications for 24h
**Edge cases**
- Team with 0 members: toggle is disabled with a tooltip
- Migration rollback on a team mid-toggle: no data loss
**Regression**
- Existing per-user preferences unaffected on PR #212's fixture
## Screenshots
[prompted: does this UI change have before/after screenshots?
developer: "not yet — I'll drop them in after opening"]
(section omitted — will be added post-open)
## Risks
Migration rewrites `notification_settings` (~2M rows).
Low risk — backfill batches behind `notif_prefs_v2` flag;
rollback is a single flag flip with zero data loss.
## Migrations
- `0042_add_notification_prefs` — 3 NULLABLE columns, online, reversible
- `0043_backfill_notification_prefs` — batched, idempotent
(dependencies section omitted — none)
(breaking changes section omitted — none)
## Checklist [from repo template — preserved intact]
- [ ] Migrations are reversible
- [ ] Feature is behind a flag
- [ ] Runbook is updated
6. Preview → developer says "yes".
7. gh pr create --title "..." --body-file <tmp> --base main
--assignee @me
8. ✓ Created PR #384 — feat(notifications): per-team notification preferences
https://github.com/acme/backend/pull/384
Next steps:
- Review it in the browser: gh pr view 384 --web
- Watch CI as it runs: gh pr checks 384 --watch
Dialogue B — Refresh a one-liner PR body
Developer: "the PR description just says 'fix bug'. Rewrite it in the
proper format."
Skill:
1. Detect: branch=fix/oauth-callback, PR #214 exists, state OPEN.
Current title: "fix bug"; current body: "fix bug".
2. Diff: 1 file, +6/-3 in src/auth/oauth.ts.
Signals: no UI, no migration, no deps.
Commit trail: "fix(auth): handle OAuth callback with slash
in state param" — bug-fix language + Conventional Commits.
3. Title: fix(auth): reject OAuth callback when state param contains slash
4. Body draft:
## Summary
Fix OAuth callback failing when the `state` query param contains
a slash. Users on Microsoft and GitHub Enterprise were being
redirected to /404 instead of completing sign-in.
## Changes
- Percent-decode `state` before splitting on `/` in the callback handler
- Add 4 regression tests covering Microsoft, GitHub Enterprise,
Google, and Slack callback URLs
## Test plan
**Happy path**
- Sign in via Microsoft; confirm dashboard loads
- Sign in via GitHub Enterprise; confirm dashboard loads
**Regression**
- Confirm Google + Slack sign-in flows unchanged
## Risks
Decoder change touches every OAuth login. Low risk — covered by
4 new test cases; rollback is a one-line revert with no flag.
5. Preview (with body diff, current → proposed).
6. Developer says "yes".
7. gh pr edit 214 --title "fix(auth): reject OAuth callback when
state param contains slash" --body-file <tmp>
8. ✓ Updated PR #214 — https://github.com/acme/backend/pull/214
Dialogue C — Docs-only PR, most sections omitted
Developer: "open a draft PR for this docs change"
Skill:
1. Detect: branch=docs/i18n-onboarding, base=main, no PR, tree clean.
Draft mode: yes (developer said "draft PR").
2. Diff: 4 .md files under docs/, no code.
Signals: no UI, no migration, no deps, no breaking.
Commits: "docs(i18n): onboarding checklist for new locales"
3. Template: none present.
4. Title: docs(i18n): onboarding checklist for new locales
5. Body draft:
## Summary
Add a step-by-step onboarding checklist for teams adding a new
locale. Consolidates guidance previously scattered across
#278, #305, and Slack threads.
## Changes
- Create `docs/i18n/onboarding-checklist.md` (~120 lines)
- Cross-link from `docs/README.md`
- Update `docs/i18n/README.md` navigation
## Test plan
- Run `docs-site build`; confirm the new page renders without warnings
- Follow the checklist end-to-end for a fake locale (`xx-YY`)
## Risks
None — content-only change to `docs/`. Rollback: revert the PR.
(all other sections omitted — none apply)
6. Preview → yes.
7. gh pr create --title "..." --body-file <tmp> --base main
--draft --assignee @me
8. ✓ Created draft PR #392 — docs(i18n): onboarding checklist for new locales
https://github.com/acme/backend/pull/392
(draft — flip to ready when you're happy: gh pr ready 392)
Dialogue D — Breaking-change PR
Developer: "open the PR — heads up, this renames an action input"
Skill:
1. Detect: branch=feat/rename-api-key-input, base=main, no PR.
2. Diff: action.yml (1 input renamed), scripts/reviewer.py
(env-var read updated), README.md (input table updated).
Signals: breaking change (removed public input surface),
no UI, no migration, no deps.
Commit: "feat!: rename api-key input to provider-api-key"
3. Title: feat!: rename api-key input to provider-api-key
(marker `!` preserved from the commit)
4. Body draft — includes MANDATORY Breaking changes section:
## Summary
Rename `api-key` → `provider-api-key` in `action.yml` for clarity
now that the reviewer supports multiple providers. The old name
was ambiguous about which provider's key it referred to.
## Changes
- Rename input in `action.yml`
- Update env-var read in `scripts/reviewer.py`
- Update the input table in `README.md`
- Update every example workflow in `examples/`
## Breaking changes
- **What breaks:** the `api-key` input is removed. Workflows
pinning `DailybotHQ/ai-diff-reviewer@v2` will fail at boot with
"invalid input `api-key`".
- **Who is affected:** every consumer of `@v2.x` who upgrades —
the change is major-bump-worthy (ships as the next major).
Consumers who stay on their current major pin are unaffected
until they upgrade.
- **Migration:** rename the key in your workflow YAML:
`api-key: ${{ secrets.X }}` → `provider-api-key: ${{ secrets.X }}`.
No other changes required.
## Test plan
...
## Risks
Any consumer on `@v2` who updates without reading the release
notes will see a workflow failure on their next PR. Mitigation:
the release notes lead with this breaking change, and the error
message includes the migration hint.
5. Preview → the skill highlights the mandatory Breaking changes
section in the "Sections included" summary.
6. yes → gh pr create ...
Notes
- This is a title + body skill, not a git skill. It writes what
goes on the PR page. Commits, pushes, and merges are the developer's
(or the merge-queue's) responsibility.
- The body is the source of truth reviewers see first. Time invested
here pays back on every reviewer, every CI comment thread, and every
release note derived from PR history.
- The skill's opinion is intentional and small. Three mandatory
sections (Summary, Test plan, Risks); everything else is conditional
on the diff. If your team wants more (rollout plans, security review
checkboxes), put those in your
pull_request_template.md and the
skill preserves them.
- Bugs, feature requests, and template patterns to add to the default:
github.com/DailybotHQ/ai-diff-reviewer/issues.
1---2name: ai-diff-reviewer-open-pr3description: Author a well-documented GitHub pull request — title and body — for the current branch. Reads the diff and commit trail, infers a Conventional Commits (or repo-native) title, drafts a structured body with the sections a good PR review actually needs (Summary, Changes, Test plan, Related issues, Screenshots when UI files changed, Breaking changes when applicable, Risks), merges with `.github/pull_request_template.md` when present (never overwrites), previews everything to the developer, and executes via `gh pr create` (new PR) or `gh pr edit` (refresh existing PR). Supports draft PRs, stacked PRs against non-default bases, and forks. Use when the developer says "open the PR", "create a pull request", "draft the PR title and description", "write the PR body", "update the PR description", "the PR body is a one-liner — rewrite it properly", or "make a draft PR for this branch".4---56# AI Diff Reviewer — Open PR (sub-skill)78Companion to the [`ai-diff-reviewer`](../SKILL.md) skill. Where the parent9**reviews** the current branch's diff locally, this sub-skill takes the10next logical step: **authors a well-documented pull request** from the11same diff — title, body, and (on request) a draft opener.1213The design philosophy is unopinionated where it can be, opinionated where14it needs to be:1516- **Unopinionated:** the section shape adapts to your17 `.github/pull_request_template.md` when one exists. If your team already18 writes PRs with `## Rollout plan` and a `## Security review` checkbox,19 those survive intact.20- **Opinionated:** every PR gets a **Summary that leads with the outcome**,21 a **Test plan a reviewer can actually run**, and — when the diff22 signals it — a **Screenshots** prompt for UI changes and a **Breaking23 changes** callout for API changes. These are the sections whose absence24 reviewers complain about; the skill won't let them slip.2526Nothing gets pushed. Nothing gets merged. The skill only ever writes the27PR **title + body** on the remote via `gh`, always after a single-yes28preview.2930---3132## When it fires3334**Create a fresh PR** — triggers:3536- "Open the PR", "open a pull request for this branch"37- "Create the PR", "create a pull request"38- "Draft the PR title and description"39- "I'm ready to push this for review — write the PR"40- "Make a draft PR" (adds `--draft`)4142**Refresh / fix an existing PR** — triggers:4344- "Update the PR description"45- "The PR body is a one-liner — rewrite it in the proper format"46- "Fix the PR title, it doesn't match the diff anymore"47- "Refresh the PR body — I pushed new commits"4849**Fall through** to a sibling skill when the developer:5051- Wants a **local review before pushing** → parent52 [`ai-diff-reviewer`](../SKILL.md) skill (default review flow). This53 sub-skill is the natural *next* step after that review.54- Wants to **install the CI action** → [`setup`](../setup/SKILL.md).55- Wants to **customize the review** → [`generate-extension`](../generate-extension/SKILL.md).56- Wants a **git commit message** (not a PR body) → defer; this skill57 writes PRs, not individual commits.5859If the intent is ambiguous ("write the PR" on a branch that's not60pushed yet, or on a repo with no `origin` set), ask ONE clarifying61question before acting.6263---6465## Step 0 — Trust boundary6667This skill writes **two things** on the remote, both only after an68explicit **yes** in the Step 6 preview:6970- **The PR title** (via `gh pr create --title` or `gh pr edit --title`).71- **The PR body** (via `--body-file` so multi-line Markdown round-trips72 cleanly).7374It does **not**:7576- Push commits, force-push, rebase, cherry-pick, or rewrite git history.77- Auto-add reviewers, assignees, labels, milestones, or projects unless78 the developer explicitly asks and confirms. (`--assignee @me` is the79 one exception in create mode — always applied.)80- Auto-merge, auto-approve, or convert draft ↔ ready-for-review without81 explicit intent in the trigger.82- Touch any file in the working tree.83- Call the LLM provider directly, or send data anywhere besides the84 `gh` API calls it announces in the preview.8586If the branch is not pushed, the skill surfaces the exact command87(`git push -u origin <branch>`) and stops. It **never** runs `git push`88itself.8990---9192## Step 1 — Detect context9394Establish the mode (create vs. edit vs. refuse) before drafting anything.9596```bash97# Current branch + head SHA98HEAD_BRANCH="$(git branch --show-current)"99HEAD_SHA="$(git rev-parse --short HEAD)"100101# Base: prefer the tracked upstream's short name, fall back to the repo's102# default branch (asks gh; falls back to `main` if gh unavailable).103BASE="$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null | sed 's|.*/||')"104if [ -z "$BASE" ]; then105 BASE="$(gh repo view --json defaultBranchRef --jq .defaultBranchRef.name 2>/dev/null || echo main)"106fi107108# Repo slug (owner/repo)109REPO="$(gh repo view --json nameWithOwner --jq .nameWithOwner 2>/dev/null || echo '')"110111# Does a PR already exist for the current branch?112PR_JSON="$(gh pr view --json number,url,title,body,isDraft,baseRefName,headRefName,state,author 2>/dev/null || true)"113```114115Decide the mode from the state:116117| State | Mode | Skill behavior |118|---|---|---|119| On `main` / `master` / `develop` / `trunk` with no PR | **refuse** | Ask the developer to switch to a feature branch first. |120| Detached HEAD | **refuse** | Ask the developer to check out a branch first. |121| `PR_JSON` non-empty and state is OPEN | **edit** | Load the existing title + body; go to Step 2 (edit path). |122| `PR_JSON` non-empty and state is CLOSED or MERGED | **refuse-soft** | Do not edit a closed/merged PR. Offer: (a) open a new PR from the same branch (create mode), or (b) reopen the closed one manually then re-run this skill. Never `gh pr reopen` automatically. |123| `PR_JSON` empty | **create** | Plan `gh pr create` against `<base>`; go to Step 2 (create path). |124| Working tree has uncommitted changes that look scope-relevant to the diff | **warn** | Show `git status --short`; ask "commit these first? (y/n)". Never `git add` on the developer's behalf. |125126**On `gh` missing or unauthenticated:** surface the exact remediation127(`brew install gh` or `gh auth login`) and stop. Do not attempt to draft128the body — writing a PR body the developer has to manually paste is a129downgrade; use the non-blocking rule below instead.130131---132133## Step 2 — Read the diff and gather signals134135Read enough diff to draft the sections; do not stuff the whole patch136into your working context.137138### Create mode139140```bash141git fetch origin "$BASE" --quiet 2>/dev/null || true142143# Summaries first (cheap):144git diff --stat "origin/${BASE}...HEAD"145git log --oneline "origin/${BASE}..HEAD"146147# Full patch second (may be large; read it, but do not paste it back):148git diff "origin/${BASE}...HEAD"149```150151### Edit mode152153```bash154gh pr diff "$PR_NUMBER" --patch155gh pr view "$PR_NUMBER" --json title,body,commits,files,isDraft,url156```157158### Signal collection (both modes)159160While reading the diff and commit trail, extract these **signals** —161they drive which optional sections appear in the body:1621631. **Issue references.** Grep the commit messages for164 `(Closes|Fixes|Resolves|Refs) #\d+` (case-insensitive). Collect every165 unique reference; these go into the `## Related issues` section166 verbatim. If the diff title has a `(#123)`-style trailer, capture167 that too.1682. **Breaking-change markers.** Look for `BREAKING CHANGE:` in commit169 bodies (Conventional Commits marker), any commit subject with170 `!:` (e.g. `feat!:` or `refactor!:`), or diff removals in public API171 surface (exported functions, `action.yml` inputs, CLI flags,172 published HTTP routes). Any hit → `## Breaking changes` section is173 mandatory.1743. **UI signals.** Look at the file extensions in `git diff --name-only175 "origin/${BASE}...HEAD"`. If any of `.tsx`, `.jsx`, `.vue`, `.svelte`,176 `.astro`, `.css`, `.scss`, `.less`, `.html` are touched (excluding177 `docs/**`, `README*`, `CHANGELOG*`) → prompt for screenshots in the178 preview (opt-in — never fabricate).1794. **Migration signals.** File names matching180 `migrations?/`, `alembic/versions/`, `db/migrate/`, `prisma/migrations/`,181 `atlas.hcl`, or `*.sql` → `## Migrations` section is mandatory.1825. **Dependency signals.** Any of `package.json`, `package-lock.json`,183 `pnpm-lock.yaml`, `yarn.lock`, `requirements*.txt`, `pyproject.toml`,184 `Pipfile`, `Pipfile.lock`, `poetry.lock`, `Gemfile`, `Gemfile.lock`,185 `go.mod`, `go.sum`, `Cargo.toml`, `Cargo.lock`, `composer.json`,186 `composer.lock` touched → `## Dependencies` section auto-populated187 with what changed.1886. **Convention detection.** Read the last 20 merged PR titles to detect189 the repo's title convention *empirically* rather than assuming190 Conventional Commits:191192 ```bash193 gh pr list --state merged --limit 20 --json title --jq '.[].title'194 ```195196 If ≥ 60% of them match `^(feat|fix|docs|chore|refactor|test|ci|perf|style|build|revert)(\(.+\))?!?: `,197 use Conventional Commits (Step 4). Otherwise, use plain-sentence titles.198 Cache the observation for the preview — surface *"detected convention:199 Conventional Commits (14/20 recent PRs match)"* so the developer can200 override.201202Emit these signals to your working context; do not print them to the203developer unless the preview needs them.204205---206207## Step 3 — Merge with the repo's PR template (when present)208209GitHub renders `.github/pull_request_template.md` (or210`.github/PULL_REQUEST_TEMPLATE.md` — same file, different casing) into211the PR body input by default when a developer opens a PR in the UI. This212skill respects that convention:213214```bash215TEMPLATE=""216for path in \217 .github/pull_request_template.md \218 .github/PULL_REQUEST_TEMPLATE.md \219 docs/pull_request_template.md \220 pull_request_template.md221do222 if [ -r "$path" ]; then TEMPLATE="$(cat "$path")"; break; fi223done224```225226**Merge rules (never overwrite):**227228| Template has | Skill's body has | Result in final body |229|---|---|---|230| `## Summary` | `## Summary` | Skill's Summary wins (it's diff-derived); heading kept. |231| `## Test plan` | `## Test plan` | Skill's Test plan wins; heading kept. |232| `## Checklist` (repo-specific tick boxes) | *(no equivalent)* | Preserved intact — appended at the end. |233| `## Rollout plan` (or any custom section) | *(no equivalent)* | Preserved intact — appended at the end. |234| `<!-- HTML comment instructions -->` | *(no equivalent)* | Stripped from the final body. Instructions are for the author, not the reviewer. |235236Surface the merge outcome in the Step 6 preview: *"Merged with237`.github/pull_request_template.md`: preserved `## Checklist` and `##238Rollout plan`; the template's `<!-- comment -->` guidance was239stripped."* The developer can override any auto-merged section in the240`edit` branch of the confirmation.241242If **no template exists**, use the default body template from Step 5.243244---245246## Step 4 — Draft the title247248Title budget: **≤ 72 chars total** (GitHub truncates in list views249around 80). The body carries the rest.250251### 4a. Convention detection (see Step 2)252253If Step 2 detected Conventional Commits, use the format below. If not,254use a plain sentence — capitalized first word, no trailing period, ≤ 72255chars.256257### 4b. Conventional Commits type inference258259Infer `<type>` from the diff signals, in this precedence order (first260match wins):261262| Diff signal | `<type>` |263|---|---|264| A commit subject already starts with `feat!:` / `fix!:` / `perf!:` (breaking change marker) | Preserve that type; add `!` marker on the PR title too. |265| Bug-fix language in commit messages (`fix:`, `fixes #`, `resolves #`, "bug", "hotfix") | `fix` |266| Only `docs/**`, `README*`, `CHANGELOG*`, or `*.md` files touched | `docs` |267| Only `.github/workflows/**`, `.github/actions/**`, CI configs (`.circleci/`, `.gitlab-ci.yml`, `azure-pipelines.yml`) touched | `ci` |268| Only test files touched (`tests/**`, `test/**`, `**/*.test.*`, `**/*_test.go`, `**/*_spec.rb`, `__tests__/**`) | `test` |269| Only dependency manifests + lockfiles touched (no source changes) | `chore(deps)` |270| Performance markers in commits (`perf:`, "optimize", "cache", "batch", "lazy", "debounce", "virtualization") | `perf` |271| Renames / moves / extractions without behavior change (large diff, small semantic change) | `refactor` |272| Only style-layer files touched (`.css`, `.scss`, Tailwind class-only changes, formatting-only commits) | `style` |273| Adds a new module / route / feature flag / schema field / CLI subcommand | `feat` |274| Everything else with user-visible behavior | `feat` |275| Everything else without user-visible behavior | `chore` |276277### 4c. Scope278279`<scope>` is the shortest identifier that names the touched surface:280281- If the diff is concentrated (>70% of touched lines) in a single282 top-level directory, use that directory's basename: `auth`, `chat`,283 `billing`, `docs`, `reviewer`.284- If the change touches a single well-known concept across many285 directories (e.g. logging refactor), use that concept: `logging`,286 `i18n`.287- If no clear single scope dominates, **omit** the scope: `feat: …`288 rather than `feat(mixed): …`.289290### 4d. Description291292- Imperative mood, present tense (*"add", "fix", "rename"* — not "added"293 or "adds").294- No trailing period.295- Concrete over vague: `fix(auth): reject empty callback state` beats296 `fix(auth): handle edge case`.297- No `[WIP]`, no ticket numbers in the title (issue links go in the298 body). Draft state is expressed via `--draft`, not `[WIP]`.299300### 4e. Examples301302- `feat(reviewer): add per-repo severity overrides via extension file`303- `fix(auth): reject OAuth callback when state param contains slash`304- `docs(prompts): document local↔CI parity for extension files`305- `chore(deps): bump actions/checkout from v4 to v5`306- `feat!: rename api-key input to provider-api-key` *(breaking change)*307308---309310## Step 5 — Draft the body311312The default body template — used when no PR template exists, or as the313starting point when one does. Every section explains **why it's here**314inline so the writing rules are self-enforcing.315316```markdown317## Summary318319<1–3 sentences. Lead with the OUTCOME (what ships and why), not the320mechanism. Plain English. No diff stats. No file paths. ≤ 280 chars.>321322## Changes323324<3–8 bullets naming the concrete edits. Each ≤ 15 words. Group by area325if there are multiple concerns. Example:326- Add `PromptExtensionLoader` that reads `.review/extension.md`327- Concatenate extension content to base prompt at boot328- Log the composed prompt's byte size at DEBUG for verifying parity329>330331## Related issues332333<Only present when Step 2 found issue references. Use GitHub's linking334keywords so the issues auto-close on merge:335- Closes #123336- Fixes #456337- Refs #789 (context only — not closed by this PR)338Omit the section entirely when there are none — never write "N/A".>339340## Test plan341342<3–8 imperative bullets grouped into named categories. Minimum 3 bullets.343Each bullet ≤ 15 words. A reviewer must be able to run each bullet344mechanically. Example:345346**Happy path**347- Run `python3 -m unittest discover -s tests`348- Open a PR against `main` on a fork; confirm review comment appears349350**Edge cases**351- Empty diff (no changes) → skill prints "nothing to review" and exits 0352- Base branch does not exist locally → falls back to `git merge-base`353354**Regression**355- Existing `strictness: block-on-warning` behavior unchanged on PR #212356>357358## Screenshots359360<UI CHANGES ONLY. Only present when Step 2 detected touched UI files361AND the developer confirmed screenshots exist. Format:362- Before: [drag image or paste URL]363- After: [drag image or paste URL]364Omit the section entirely when there are no UI changes — never write365"N/A".>366367## Breaking changes368369<MANDATORY when Step 2 detected a breaking-change signal370(`feat!:`, `BREAKING CHANGE:`, removed public API). 2-4 sentences with:3711. What breaks — the exact symbol / input / URL / behavior.3722. Who is affected — first-party consumers, third-party, both.3733. Migration path — the concrete steps a consumer takes to adapt.374Example:375- **What breaks:** the `api-key` input has been renamed to376 `provider-api-key` in `action.yml`.377- **Who is affected:** every consumer of `DailybotHQ/ai-diff-reviewer@v2`378 / `@v2.x` — YAML workflows will fail with "invalid input" at boot.379- **Migration:** rename the key in your workflow YAML:380 `with: { api-key: ... }` → `with: { provider-api-key: ... }`.381 No code changes required.382Omit the section entirely when there are no breaking changes.>383384## Risks385386<2-4 sentences with a 3-part structure: [failure mode] — [affected scope]387— [rollback plan]. Keep at the product / rollout level, not388implementation. ≤ 400 chars. For low-risk additive changes, use:389"Low risk — change is additive (no existing behavior modified). Affected:390[area]. Rollback: revert the PR; no data migration involved."391Always include a rollback plan — never write bare "None" or "Low risk"392without the mitigation.>393394## Migrations395396<Only present when Step 2 detected migration files. Format:397- `migrations/0042_add_notification_prefs` — adds 3 NULLABLE columns,398 online, reversible via `db migrate down`399- `migrations/0043_backfill_prefs` — batched data backfill, idempotent400Omit the section entirely when no migrations touched.>401402## Dependencies403404<Only present when Step 2 detected dependency changes. Format:405- Added: `zod@3.22.0` (schema validation for extension file parsing)406- Removed: `joi` (replaced by zod)407- Bumped: `actions/checkout@v4 → v5` (upstream security fix)408Omit the section entirely when no deps touched.>409```410411### Writing rules (self-enforcing — apply them as you draft)4124131. **Outcome first** — the Summary's first sentence is what the change414 does for the user, not how it does it. *"Ship per-team notification415 preferences"* not *"Refactor the NotificationSettings class."*4162. **Plain English over jargon** — translate to user impact. *"Reduce417 p95 checkout latency from 4.2s to 1.1s"* not *"Migrate to418 Prisma-native transactions."*4193. **Concrete beats vague** — quantify when possible. *"Low risk"* alone420 is noise; always include the mitigation.4214. **No file paths, diff stats, line counts, commit hashes** in the422 body. The GitHub PR diff has all of that one click away.4235. **No agent / tool attribution** (*"Claude built…", "Cursor424 generated…"*) — the team shipped it.4256. **Optional sections vanish when empty** — never `N/A`, never426 `TBD`, never a section with a single "None" bullet. Absence is427 information.4287. **Imperative mood in bullets** — *"Add", "Fix", "Rename"*, not429 *"Added", "Adds", "This adds"*.4308. **Two to four sentences per prose section** — one is too vague, five431 is a brain dump. Two to three is the sweet spot.4329. **No `@`-handles inside section bodies** — reviewer / assignee433 handles belong in GitHub's reviewer/assignee UI, not in the free-form434 body. The one exception: mentioning a *domain owner* for context435 (*"Coordinated with @security-team on the token handling"*) is fine436 and encouraged when the change crosses ownership boundaries.43710. **Preserve the repo's brand spelling** — grep for the product name in438 `README.md` and use that exact capitalization throughout the body.439440---441442## Step 6 — Preview + single confirmation443444Show a tight, readable preview in this exact order — this is the445"look-once-yes-once" contract:446447```text448About to <create | update> a pull request on <owner/repo>:449450 Branch: <head> → <base>451 Draft: <yes | no>452 Convention: <Conventional Commits | plain sentence>453 (detected: <n>/20 recent merged PRs match Conventional Commits)454 Title: <the final title>455456 Body (Markdown, sections in final order):457 ─────────────────────────────────────────────458 <full body — every section header + content, verbatim as it will be459 written; no truncation, no ellipsis>460 ─────────────────────────────────────────────461462 PR template: <detected at .github/pull_request_template.md | none>463 Merge decisions: <"Preserved: ## Checklist, ## Rollout plan. Overrode:464 ## Summary, ## Test plan (skill-anchored versions465 used so they stay diff-derived)." | "N/A — no template">466467 Sections included:468 ✓ Summary (mandatory)469 ✓ Changes (mandatory)470 ✓ Test plan (mandatory)471 ✓ Related issues (detected #123, #456)472 ✓ Breaking changes (detected `feat!:` in commit 3f2c9a1)473 ✗ Screenshots (UI files touched: 4 — SKIPPED per your input)474 ✓ Risks (mandatory)475 ✗ Migrations (no migration files detected)476 ✓ Dependencies (detected package.json changes)477478 Command: <gh pr create --base <base> --head <branch> [--draft]479 --assignee @me --title "<title>" --body-file <tmpfile>480 | gh pr edit <n> --title "<title>" --body-file <tmpfile>>481482 Confirm? (yes / edit / cancel)483```484485Response handling:486487- **yes** → Step 7.488- **edit** → ask which section(s) or field(s) to change (title, or a489 named body section). After the developer's edit, RE-RENDER the whole490 preview and ask again. No silent partial application.491- **cancel** → stop. Print nothing else.492493**Edit mode addendum:** when refreshing an existing PR, also print a494compact **diff of the body** — the developer needs to see what's495changing, not just what will be written:496497```text498 Body diff (current PR → proposed):499 ─────────────────────────────────────────────500 - fix bug in oauth callback501 + ## Summary502 + Fix OAuth callback failing when the `state` query param contains a503 + slash. Users hitting Microsoft / GitHub Enterprise were redirected504 + to /404 instead of completing sign-in.505 + …506 ─────────────────────────────────────────────507```508509**Never skip confirmation.** A PR title + body is reviewer-visible,510consumer-visible, and often lifted verbatim into release notes.511512---513514## Step 7 — Execute via `gh`515516Write the body to a temp file so multi-line content, code fences, and517HTML comments round-trip cleanly. Allow ≥ 30 s for either call518(`gh` can be slow on repos with many reviewers or protection rules).519520### Create mode521522```bash523BODY_FILE="$(mktemp -t ai-diff-reviewer-body.XXXXXX.md)"524printf '%s\n' "$BODY" > "$BODY_FILE"525526CMD=(gh pr create527 --title "$TITLE"528 --body-file "$BODY_FILE"529 --base "$BASE"530 --head "$HEAD_BRANCH"531 --assignee @me)532533# Add --draft only when the developer's trigger explicitly asked for it534# ("draft PR", "WIP PR", "--draft").535[ "$DRAFT" = "true" ] && CMD+=(--draft)536537"${CMD[@]}"538rm -f "$BODY_FILE"539```540541After success:542543- Capture the PR URL from `gh pr create` stdout.544- Print the URL + a one-line "next step" hint (see Step 8).545546### Edit mode547548```bash549BODY_FILE="$(mktemp -t ai-diff-reviewer-body.XXXXXX.md)"550printf '%s\n' "$BODY" > "$BODY_FILE"551552gh pr edit "$PR_NUMBER" \553 --title "$TITLE" \554 --body-file "$BODY_FILE"555556rm -f "$BODY_FILE"557```558559Never pass `--reviewer`, `--assignee` (other than `@me` in create mode),560`--label`, `--milestone`, or `--project` unless the developer's trigger561explicitly asked for them AND those choices appeared in the Step 6562preview.563564---565566## Step 8 — Surface the result + hand-off567568**On success:**569570```text571✓ <Created | Updated> PR #<n> — <title>572 <URL>573574Next steps:575 - Review it in the browser: gh pr view <n> --web576 - Watch CI as it runs: gh pr checks <n> --watch577 - Read the CI review once it lands: (see the `apply-review` sub-skill,578 or the trigger "read the CI review579 on my PR")580 - When CI is green: gh pr merge <n> --squash --delete-branch581 (or use the merge button on the PR page)582```583584If the repo has the AI Diff Reviewer CI Action installed (detectable by585grepping `.github/workflows/` for `DailybotHQ/ai-diff-reviewer`), the586`apply-review` sub-skill is the natural next step after this PR is open:587CI will post its review within a few minutes, and `apply-review` will588read it back, present the findings in the same format as the local589review, and — with per-finding consent — help apply the fixes.590591If the parent `ai-diff-reviewer` skill's local review flow ran earlier592in the same session and produced any 🚨 critical or ⚠️ warning findings,593append a soft nudge:594595```text596 Note: the local review in this session found <n> critical / <m>597 warning findings. Consider addressing them before requesting review,598 or leave them noted in the PR description under a "Known issues"599 bullet in `## Risks`.600```601602**On failure:** quote the `gh` error verbatim. Do NOT retry in a loop603(non-blocking rule). Common ones:604605| `gh` error | Meaning | Skill's action |606|---|---|---|607| `not logged in` / `authentication required` | `gh` unauthenticated | Print `gh auth login`; stop. |608| `no commits between <base> and <head>` | Branch not pushed | Print `git push -u origin <branch>`; stop. |609| `pull request already exists for branch` | Race condition | Re-run Step 1 to load the just-created PR; switch to edit mode; re-preview. |610| `base <name> not found` | Typo'd base branch | Print `gh repo view --json defaultBranchRef`; ask developer for the correct base; re-preview. |611| `Resource not accessible by integration` | Fork PR without write access | Explain the limitation (fork PRs need `gh pr create --repo <upstream>` and the developer's own token); suggest the developer runs the command manually with the printed title + body. |612| Any 5xx from GitHub | Server-side | Stop. Print body draft to terminal so the developer can paste in the UI. Do not loop. |613614---615616## Guardrails — what this skill MUST NOT do617618- **Never push commits, force-push, rebase, or rewrite history.** If the619 branch is not pushed, print `git push -u origin <branch>` and stop.620- **Never auto-merge, auto-approve, auto-request-review, or convert621 draft ↔ ready-for-review** without the developer saying so in the622 trigger AND confirming in the preview.623- **Never `gh pr reopen`** a closed PR. Offer to create a new PR from624 the current branch instead.625- **Never fabricate** issue references, CI URLs, screenshots, migration626 claims, or dependency changes. If a signal is unclear, ask ONCE or627 omit the section — never invent.628- **Never write `N/A`, `TBD`, `None`, or `-` as a placeholder** for an629 optional section. Omit the whole section (heading + body) together630 instead. Absence is information.631- **Never overwrite `.github/pull_request_template.md` sections632 silently** — Step 3 merge rules apply, and the merge decision is633 surfaced in the preview.634- **Never paste raw `git diff` output, `git diff --stat` output, file635 paths, line counts, or commit hashes** into the body. The GitHub diff636 has all of it one click away.637- **Never embed reviewer / assignee `@`-handles inside body sections.**638 Handles belong in GitHub's reviewer UI, not in prose.639- **Never open a PR from a protected branch** (`main`, `master`,640 `develop`, `trunk`). Refuse and ask the developer to switch first.641- **Never open a PR while the working tree has uncommitted scope-relevant642 changes.** Warn, show `git status --short`, and ask. Do not `git add`.643- **Never skip the Step 6 confirmation** — even on trivial edits, even644 in edit mode.645646---647648## Coordinating with other skills649650- **[`../SKILL.md`](../SKILL.md) (parent — local review flow)** —651 natural predecessor. The recommended flow is: *(1) run the local652 review, (2) act on the findings, (3) open the PR.* The parent's Step653 4 output ("Verdict + Findings table + Recommendation") is exactly the654 input a developer needs to draft a good Summary + Test plan here.655- **[`../apply-review/SKILL.md`](../apply-review/SKILL.md) (sibling —656 read + apply the CI review on the PR)** — natural successor. Once657 this sub-skill has opened the PR and CI has posted its review, the658 `apply-review` sub-skill reads that review back locally and walks659 the developer through the findings. After they apply fixes and660 push, come back here in **edit mode** if the PR scope changed661 enough to warrant a body refresh.662- **[`../setup/SKILL.md`](../setup/SKILL.md) (sibling — install the CI663 action)** — different phase of the lifecycle. Runs once per repo when664 the reviewer is being introduced; this sub-skill runs once per PR.665 If the developer asks to open a PR on a repo that has no666 `.github/workflows/pr-review.yml`, this skill still authors the PR667 correctly — CI review is set up separately.668- **[`../generate-extension/SKILL.md`](../generate-extension/SKILL.md)669 (sibling — tailor the reviewer prompt)** — orthogonal. Extensions670 affect what the reviewer flags; this skill only reads the diff.671672---673674## Non-blocking rule675676Authoring a PR title + body must **never block the developer's primary677work**. If `gh` is missing, unauthenticated, network is down, or the678API call errors:6796801. Print the drafted title + body to the terminal in a copy-pasteable681 block. The developer can paste it into the GitHub UI themselves.6822. Print the exact remediation (`gh auth login`, `git push -u origin`,683 etc.) once.6843. Do **not** retry automatically.6854. Do **not** enter a diagnostic loop.6865. End the turn.687688This matches the parent skill's non-blocking discipline.689690---691692## Sample dialogues693694### Dialogue A — Create a fresh PR after a local review695696```text697Developer: [after the parent skill just produced a review with 2 info698 findings and no blockers]699 "great, open the PR"700701Skill:702 1. Detect: branch=feat/notif-prefs, base=main, no PR yet, tree clean.703 2. Diff: 9 files, +412/-87, mostly under app/notifications/.704 Commits reference: Closes #341, Refs #201.705 Signals: UI (2 .tsx files), migration (1 file), deps (none).706 Convention: Conventional Commits (17/20 recent PRs match).707 3. Template: .github/pull_request_template.md present → merge.708 Preserved `## Checklist` intact; skill sections override709 the template's `## Summary` and `## Test plan`.710 4. Title: feat(notifications): per-team notification preferences711 5. Body draft:712 ## Summary713 Ship per-team notification preferences so admins can mute Slack714 pings without losing email digests. Closes the #2 complaint from715 enterprise onboarding.716717 ## Changes718 - Add `NotificationPrefs` model + REST endpoints719 - Wire the toggle UI into the team-settings page720 - Migrate legacy `notification_settings` rows to per-team scope721722 ## Related issues723 Closes #341724 Refs #201725726 ## Test plan727 **Happy path**728 - Mute Slack pings for one team; confirm email digest still arrives729 - Toggle off all channels; confirm no notifications for 24h730731 **Edge cases**732 - Team with 0 members: toggle is disabled with a tooltip733 - Migration rollback on a team mid-toggle: no data loss734735 **Regression**736 - Existing per-user preferences unaffected on PR #212's fixture737738 ## Screenshots739 [prompted: does this UI change have before/after screenshots?740 developer: "not yet — I'll drop them in after opening"]741 (section omitted — will be added post-open)742743 ## Risks744 Migration rewrites `notification_settings` (~2M rows).745 Low risk — backfill batches behind `notif_prefs_v2` flag;746 rollback is a single flag flip with zero data loss.747748 ## Migrations749 - `0042_add_notification_prefs` — 3 NULLABLE columns, online, reversible750 - `0043_backfill_notification_prefs` — batched, idempotent751752 (dependencies section omitted — none)753 (breaking changes section omitted — none)754755 ## Checklist [from repo template — preserved intact]756 - [ ] Migrations are reversible757 - [ ] Feature is behind a flag758 - [ ] Runbook is updated759 6. Preview → developer says "yes".760 7. gh pr create --title "..." --body-file <tmp> --base main761 --assignee @me762 8. ✓ Created PR #384 — feat(notifications): per-team notification preferences763 https://github.com/acme/backend/pull/384764765 Next steps:766 - Review it in the browser: gh pr view 384 --web767 - Watch CI as it runs: gh pr checks 384 --watch768```769770### Dialogue B — Refresh a one-liner PR body771772```text773Developer: "the PR description just says 'fix bug'. Rewrite it in the774 proper format."775776Skill:777 1. Detect: branch=fix/oauth-callback, PR #214 exists, state OPEN.778 Current title: "fix bug"; current body: "fix bug".779 2. Diff: 1 file, +6/-3 in src/auth/oauth.ts.780 Signals: no UI, no migration, no deps.781 Commit trail: "fix(auth): handle OAuth callback with slash782 in state param" — bug-fix language + Conventional Commits.783 3. Title: fix(auth): reject OAuth callback when state param contains slash784 4. Body draft:785 ## Summary786 Fix OAuth callback failing when the `state` query param contains787 a slash. Users on Microsoft and GitHub Enterprise were being788 redirected to /404 instead of completing sign-in.789790 ## Changes791 - Percent-decode `state` before splitting on `/` in the callback handler792 - Add 4 regression tests covering Microsoft, GitHub Enterprise,793 Google, and Slack callback URLs794795 ## Test plan796 **Happy path**797 - Sign in via Microsoft; confirm dashboard loads798 - Sign in via GitHub Enterprise; confirm dashboard loads799800 **Regression**801 - Confirm Google + Slack sign-in flows unchanged802803 ## Risks804 Decoder change touches every OAuth login. Low risk — covered by805 4 new test cases; rollback is a one-line revert with no flag.806 5. Preview (with body diff, current → proposed).807 6. Developer says "yes".808 7. gh pr edit 214 --title "fix(auth): reject OAuth callback when809 state param contains slash" --body-file <tmp>810 8. ✓ Updated PR #214 — https://github.com/acme/backend/pull/214811```812813### Dialogue C — Docs-only PR, most sections omitted814815```text816Developer: "open a draft PR for this docs change"817818Skill:819 1. Detect: branch=docs/i18n-onboarding, base=main, no PR, tree clean.820 Draft mode: yes (developer said "draft PR").821 2. Diff: 4 .md files under docs/, no code.822 Signals: no UI, no migration, no deps, no breaking.823 Commits: "docs(i18n): onboarding checklist for new locales"824 3. Template: none present.825 4. Title: docs(i18n): onboarding checklist for new locales826 5. Body draft:827 ## Summary828 Add a step-by-step onboarding checklist for teams adding a new829 locale. Consolidates guidance previously scattered across830 #278, #305, and Slack threads.831832 ## Changes833 - Create `docs/i18n/onboarding-checklist.md` (~120 lines)834 - Cross-link from `docs/README.md`835 - Update `docs/i18n/README.md` navigation836837 ## Test plan838 - Run `docs-site build`; confirm the new page renders without warnings839 - Follow the checklist end-to-end for a fake locale (`xx-YY`)840841 ## Risks842 None — content-only change to `docs/`. Rollback: revert the PR.843844 (all other sections omitted — none apply)845 6. Preview → yes.846 7. gh pr create --title "..." --body-file <tmp> --base main847 --draft --assignee @me848 8. ✓ Created draft PR #392 — docs(i18n): onboarding checklist for new locales849 https://github.com/acme/backend/pull/392850 (draft — flip to ready when you're happy: gh pr ready 392)851```852853### Dialogue D — Breaking-change PR854855```text856Developer: "open the PR — heads up, this renames an action input"857858Skill:859 1. Detect: branch=feat/rename-api-key-input, base=main, no PR.860 2. Diff: action.yml (1 input renamed), scripts/reviewer.py861 (env-var read updated), README.md (input table updated).862 Signals: breaking change (removed public input surface),863 no UI, no migration, no deps.864 Commit: "feat!: rename api-key input to provider-api-key"865 3. Title: feat!: rename api-key input to provider-api-key866 (marker `!` preserved from the commit)867 4. Body draft — includes MANDATORY Breaking changes section:868 ## Summary869 Rename `api-key` → `provider-api-key` in `action.yml` for clarity870 now that the reviewer supports multiple providers. The old name871 was ambiguous about which provider's key it referred to.872873 ## Changes874 - Rename input in `action.yml`875 - Update env-var read in `scripts/reviewer.py`876 - Update the input table in `README.md`877 - Update every example workflow in `examples/`878879 ## Breaking changes880 - **What breaks:** the `api-key` input is removed. Workflows881 pinning `DailybotHQ/ai-diff-reviewer@v2` will fail at boot with882 "invalid input `api-key`".883 - **Who is affected:** every consumer of `@v2.x` who upgrades —884 the change is major-bump-worthy (ships as the next major).885 Consumers who stay on their current major pin are unaffected886 until they upgrade.887 - **Migration:** rename the key in your workflow YAML:888 `api-key: ${{ secrets.X }}` → `provider-api-key: ${{ secrets.X }}`.889 No other changes required.890891 ## Test plan892 ...893894 ## Risks895 Any consumer on `@v2` who updates without reading the release896 notes will see a workflow failure on their next PR. Mitigation:897 the release notes lead with this breaking change, and the error898 message includes the migration hint.899 5. Preview → the skill highlights the mandatory Breaking changes900 section in the "Sections included" summary.901 6. yes → gh pr create ...902```903904---905906## Notes907908- **This is a title + body skill, not a git skill.** It writes what909 goes on the PR page. Commits, pushes, and merges are the developer's910 (or the merge-queue's) responsibility.911- **The body is the source of truth reviewers see first.** Time invested912 here pays back on every reviewer, every CI comment thread, and every913 release note derived from PR history.914- **The skill's opinion is intentional and small.** Three mandatory915 sections (Summary, Test plan, Risks); everything else is conditional916 on the diff. If your team wants more (rollout plans, security review917 checkboxes), put those in your `pull_request_template.md` and the918 skill preserves them.919- **Bugs, feature requests, and template patterns to add to the default:**920 [`github.com/DailybotHQ/ai-diff-reviewer/issues`](https://github.com/DailybotHQ/ai-diff-reviewer/issues).