ai-diff-reviewer-open-pr Β· diff
v3.2.3 to v3.3.0
1 added, 1 removed. Audit A to A.
---
name: ai-diff-reviewer-open-pr
description: Author a well-documented GitHub pull request β title and body β for the current branch, keeping the branch in sync with the remote base first. Fetches the base, merges origin/<base> into the current branch when it is behind, resolves merge conflicts, runs the repo's quick validation and pushes the current branch (never force, never another branch), then reads the diff and commit trail, infers a Conventional Commits (or repo-native) title, drafts a structured body (Summary, Changes, Test plan, Risks, plus conditional sections such as Related issues, Breaking changes and Merge notes), merges with .github/pull_request_template.md when present, previews everything, and executes via gh pr create or gh pr edit. Supports draft PRs, stacked PRs 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", "rewrite the PR body properly", "make a draft PR", or "update my branch with main and open the PR".
- version: "3.2.3"
+ version: "3.3.0"
documentation_url: https://github.com/DailybotHQ/ai-diff-reviewer/blob/main/skills/ai-diff-reviewer/open-pr/SKILL.md
user-invocable: true
metadata: {"openclaw":{"emoji":"π","homepage":"https://github.com/DailybotHQ/ai-diff-reviewer","requires":{"anyBins":["git","gh"]}}}
allowed-tools: Bash, Read, Grep, Glob
---
# AI Diff Reviewer β Open PR (sub-skill)
Companion to the [`ai-diff-reviewer`](../SKILL.md) 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.md) skill (default review flow). This
sub-skill is the natural *next* step after that review.
- Wants to **install the CI action** β [`setup`](../setup/SKILL.md).
- Wants to **customize the review** β [`generate-extension`](../generate-extension/SKILL.md).
- 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 **three things** on the remote:
- **The current branch's commits**, via one non-force `git push` of the
**current branch only**, as the last action of the base sync in
Step 1.5 (announced in one line before it runs; no per-run
confirmation β the maintainer asked for the branch to arrive
up to date without being asked each time).
- **The PR title** (via `gh pr create --title` or `gh pr edit --title`),
only after an explicit **yes** in the Step 6 preview.
- **The PR body** (via `--body-file` so multi-line Markdown round-trips
cleanly), same confirmation.
To keep the branch mergeable it **may**:
- `git fetch origin <base>` and `git merge --no-edit origin/<base>` into
the **current** branch when it is behind.
- Edit files **only to resolve merge conflicts** produced by that merge
(never to "improve" code on the way), stage them, run the repo's
documented quick validation, and create the merge commit.
- `git push -u origin <current-branch>` β never `--force`, never
`--force-with-lease`, never another branch.
It does **not**:
- Rebase, cherry-pick, amend, or rewrite history. The one exception: a
`CONTRIBUTING.md` / `AGENTS.md` that **mandates a linear history**
(no merge commits). Then it asks **once** ("rebase onto origin/<base>
and force-with-lease push?") and proceeds only after an explicit yes.
- Sync when the current branch **is** the base / default branch
(`main`, `master`, `develop`, `trunk`) β it refuses in Step 1 anyway.
- Discard the base's changes to make a conflict go away, or push when
the quick validation fails after a resolution (Step 1.5 stop rules).
- 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 other than conflict resolution.
- Call the LLM provider directly, or send data anywhere besides the
`git push` and `gh` calls it announces.
If the working tree is dirty before the sync, the skill stops and asks
(see the **warn** row in Step 1) β it never stashes or commits the
developer's uncommitted work.
---
## Step 1 β Detect context
Establish the mode (create vs. edit vs. refuse) before drafting anything.
```bash
# 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 1.5 β Sync with the remote base (automatic)
Runs in **create and edit mode alike**, after the mode is decided and
before any diff is read, so the PR you open (or refresh) is never
immediately "behind" and CI reviews the merged state. Skipped only in
the **refuse** modes. Announce one line before running:
`Syncing <head> with origin/<base>β¦`.
```bash
# 1. Freshen the base and measure the gap.
git fetch origin "$BASE" --quiet
MERGE_BASE="$(git merge-base HEAD "origin/${BASE}")"
BEHIND="$(git rev-list --count "HEAD..origin/${BASE}")"
SYNC_SUMMARY="up to date"
if [ "$BEHIND" -gt 0 ]; then
# 2. Pre-flight: never sync a dirty tree (Step 1 "warn" row applies).
if [ -n "$(git status --porcelain)" ]; then
SYNC_SUMMARY="skipped (dirty tree)"; # stop β ask, do not continue
else
# 3. Merge the base into the CURRENT branch (never rebase by default).
if git merge --no-edit "origin/${BASE}"; then
SYNC_SUMMARY="merged origin/${BASE} (${BEHIND} commits behind)"
else
# 4. Conflicts: resolve each file by reading BOTH sides and the
# surrounding intent. Prefer keeping both changes; never drop the
# base's change silently; for generated files/lockfiles regenerate
# with the repo's tool instead of hand-merging; binary conflicts
# β stop and ask.
CONFLICTS="$(git diff --name-only --diff-filter=U)"
# β¦ resolve, then:
git add -- $CONFLICTS
# 5. Quick gate: the repo's documented fast validation (AGENTS.md /
# CONTRIBUTING.md "Quick commands" β e.g. compile + unit tests +
# lint). If it FAILS β fix the fallout you caused; if you cannot
# β `git merge --abort`, stop and report the failing command.
git commit -q -F - <<EOF
chore: merge origin/${BASE} into ${HEAD_BRANCH}
Resolved conflicts in:
$(printf -- '- %s\n' $CONFLICTS)
EOF
SYNC_SUMMARY="merged origin/${BASE} (${BEHIND} commits behind) β conflicts resolved in: $(printf '%s' "$CONFLICTS" | tr '\n' ' ')"
fi
fi
fi
# 6. Push the current branch (also covers the "branch never pushed" case).
# Non-force. A rejection means the REMOTE branch moved (someone else
# pushed): do NOT pull/rebase/force β stop and report.
git push -u origin "$HEAD_BRANCH" || { SYNC_SUMMARY="push rejected β remote branch moved"; }
```
**Stop conditions** (report, do not continue to Step 2):
| Condition | Action |
|---|---|
| Dirty working tree before the merge | Show `git status --short`; ask "commit or stash these first?"; stop. |
| Conflict in a binary file, or a resolution you cannot justify from both sides | `git merge --abort`; list the files; ask the developer to resolve; stop. |
| Quick gate fails after resolution and you cannot fix the fallout | `git merge --abort`; print the failing command and its tail; stop. |
| `git push` rejected (non-fast-forward) | Stop; print `git fetch origin && git log HEAD..origin/<head>` so the developer can see what moved. Never force. |
| Repo mandates a linear history (`CONTRIBUTING.md` / `AGENTS.md`: "no merge commits", "rebase only") | Ask once: *"Rebase onto origin/<base> and push with `--force-with-lease`?"* Proceed only on an explicit yes; otherwise stop and print the commands. |
| Current branch is the base / default branch | Never sync (Step 1 already refused). |
Record `MERGE_BASE`, `BEHIND`, `CONFLICTS` and `SYNC_SUMMARY`: the
preview prints `SYNC_SUMMARY` (Step 6) and, when any conflict was
resolved, the PR body gets a `## Merge notes` section (Step 5) listing
the merge-base SHA and the resolved files.
---
## 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
```bash
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
```bash
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:
1. **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.
2. **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.
3. **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).
4. **Migration signals.** File names matching
`migrations?/`, `alembic/versions/`, `db/migrate/`, `prisma/migrations/`,
`atlas.hcl`, or `*.sql` β `## Migrations` section is mandatory.
5. **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.
6. **Convention detection.** Read the last 20 merged PR titles to detect
the repo's title convention *empirically* rather than assuming
Conventional Commits:
```bash
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:
```bash
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.
```markdown
## 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@v3`
/ `@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.>
```
## Merge notes
*Conditional β only when Step 1.5 resolved at least one conflict.*
One line per resolved file stating what was kept (e.g. "kept both: the
base's retry constant and this branch's timeout"), plus the merge-base
SHA (`git merge-base` recorded in Step 1.5) so a reviewer can replay the
merge. Never list files that merged cleanly.
### Writing rules (self-enforcing β apply them as you draft)
1. **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."*
2. **Plain English over jargon** β translate to user impact. *"Reduce
p95 checkout latency from 4.2s to 1.1s"* not *"Migrate to
Prisma-native transactions."*
3. **Concrete beats vague** β quantify when possible. *"Low risk"* alone
is noise; always include the mitigation.
4. **No file paths, diff stats, line counts, commit hashes** in the
body. The GitHub PR diff has all of that one click away.
5. **No agent / tool attribution** (*"Claude builtβ¦", "Cursor
generatedβ¦"*) β the team shipped it.
6. **Optional sections vanish when empty** β never `N/A`, never
`TBD`, never a section with a single "None" bullet. Absence is
information.
7. **Imperative mood in bullets** β *"Add", "Fix", "Rename"*, not
*"Added", "Adds", "This adds"*.
8. **Two to four sentences per prose section** β one is too vague, five
is a brain dump. Two to three is the sweet spot.
9. **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.
10. **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:
```text
About to <create | update> a pull request on <owner/repo>:
Branch: <head> β <base>
Base sync: <merged origin/<base> (3 commits behind) β conflicts resolved in: a.py, b.md
| merged origin/<base> (3 commits behind) | up to date | skipped (dirty tree)>
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)
β Merge notes (2 conflicts resolved during base sync)
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:
```text
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
```bash
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
```bash
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:**
```text
β <Created | Updated> PR #<n> β <title>
<URL>
Base sync: <SYNC_SUMMARY>
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:
```text
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 on the remote (Step 1.5 push failed or was skipped) | Re-run Step 1.5's push; if it is rejected again, stop and report. |
| `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 force-push, rebase, or rewrite history.** The only push this
skill runs is the non-force `git push -u origin <current-branch>` at
the end of Step 1.5 (linear-history repos: rebase + `--force-with-lease`
only after an explicit yes). Never push any other branch.
- **Never resolve a conflict by dropping the base's change silently**,
never push when the quick validation fails after a resolution, and
never sync while on the base / default branch.
- **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`](../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`](../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`](../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`](../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:
1. Print the drafted title + body to the terminal in a copy-pasteable
block. The developer can paste it into the GitHub UI themselves.
2. Print the exact remediation (`gh auth login`, `git push -u origin`,
etc.) once.
3. Do **not** retry automatically.
4. Do **not** enter a diagnostic loop.
5. End the turn.
This matches the parent skill's non-blocking discipline.
---
## Sample dialogues
### Dialogue A β Create a fresh PR after a local review
```text
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
```text
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
```text
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
```text
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@v3` 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`](https://github.com/DailybotHQ/ai-diff-reviewer/issues).