# Open Pr

> Create a GitHub PR with smart defaults, detailed body generation, and user confirmation

- Skill: `mosamaasif/open-pr` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add mosamaasif/open-pr`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mosamaasif/open-pr/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: mosamaasif (https://skillmd.com/u/mosamaasif)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mosamaasif/open-pr

---


# /open-pr — GitHub Pull Request Creator

Create a GitHub PR with inferred defaults, a detailed generated body, and mandatory user confirmation before creation. Project-agnostic — works with any repo. Supports PR templates, CODEOWNERS, conventional commits, monorepos, and multiple ticket systems out of the box.

## Argument Parsing

Parse arguments from the skill invocation string. All are optional, `--flag value` format:

| Arg | Alias | Default | Description |
|-----|-------|---------|-------------|
| `--base` | `-b` | Inferred (see Phase 2) | Target branch |
| `--assignee` | `-a` | Current `gh` user | PR assignee |
| `--reviewer` | `-r` | Ask user | Reviewer(s), comma-separated |
| `--label` | `-l` | Inferred from paths | Label(s), comma-separated |
| `--images` | `-i` | None | Image paths for screenshots |
| `--draft` | `-d` | Ask user | Create as draft |
| `--no-draft` | | | Create as ready for review |
| `--title` | `-t` | Auto-generated | Override title |
| `--ticket` | `-j` | Extracted from branch | Override ticket ID |

## Execution: 5 Phases

You MUST execute all 5 phases in order. Do NOT skip Phase 4 (confirmation).

---

### Phase 1 — Preflight Checks

Run these checks in parallel where possible:

1. **Auth check**: Run `gh auth status`. If not authenticated, stop and tell the user to run `gh auth login`.
2. **Uncommitted changes**: Run `git status --porcelain`. If output is non-empty, warn the user about uncommitted changes and ask whether to proceed or abort.
3. **Existing PR**: Run `gh pr view --json url,state 2>/dev/null`. If a PR already exists for this branch:
   - Show the existing PR URL and state
   - Ask user: "A PR already exists. Would you like to update it with `gh pr edit` instead?"
   - If yes, switch to edit flow (re-generate body, run `gh pr edit`)
   - If no, abort
4. **Commits ahead**: Run `git log {base}..HEAD --oneline` (after base is determined in Phase 2). If empty, stop — nothing to open a PR for.

#### Optional Config File

Check for configuration files (strictly optional — skill works perfectly without them):
1. Check `.github/open-pr.yml` (repo-level config)
2. Check `~/.config/open-pr.yml` (user-level config)
3. Repo config overrides user config. CLI flags override both.

Supported config keys:
```yaml
base: develop                    # default base branch
draft: true                      # default draft status
labels:
  path_mappings:                 # custom path→label mappings
    "packages/payments/": "payments"
    "libs/auth/": "auth"
reviewers:
  default: ["alice"]             # default reviewer suggestions
  use_codeowners: true           # enable CODEOWNERS parsing (default: true)
template: ".github/PULL_REQUEST_TEMPLATE/feature.md"  # force a specific template
body_footer: "QA: https://staging.example.com"        # append to PR body
```

If config files are not found, proceed silently with built-in defaults. Never error on missing config.

#### CI Status Warning

After confirming the branch has been pushed (or will be pushed), run:
```bash
gh run list --branch {branch} --limit 3 --json status,conclusion,name 2>/dev/null
```
If any recent runs have `conclusion: "failure"`, warn: "Recent CI runs have failures: {names}. This won't block PR creation, but you may want to investigate."
This is advisory only — never block on CI status.

---

### Phase 2 — Gather & Infer Parameters

#### Ticket ID
- If `--ticket` provided, use it directly.
- Otherwise extract from branch name using priority-ordered patterns:
  1. **Jira/Linear**: `([A-Z][A-Z0-9]+-\d+)` — e.g., `DP-1067`, `ENG-42`
  2. **Shortcut**: `(sc-\d+)` (case-insensitive) — e.g., `sc-12345`
  3. **Azure DevOps**: `(AB#\d+)` — e.g., `AB#7890`
  4. **GitHub issue**: `#(\d+)` in branch name, or bare number prefix like `123-fix-bug` → `#123`
  5. **HOTFIX**: `(HOTFIX)` (case-insensitive)
- Stop at the first match. If no match, ticket ID is empty — that's fine.

Also scan commit messages for issue references (`#\d+`) to collect related issues for the body.

#### Base Branch
- If `--base` provided, use it.
- If config file specifies `base`, use that (unless `--base` was given).
- Otherwise detect:
  1. Get the repo default branch: `gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name'`
  2. Check common candidates: `main`, `master`, `develop`, plus any `release/*` branches
  3. For each candidate that exists, compute `git merge-base` distance
  4. Pick the closest ancestor (fewest commits between merge-base and HEAD)
  5. Fallback: repo default branch
- Verify the base branch exists on the remote. If not, warn and fall back to the repo default branch.

**Stacked PR detection**: If the resolved base branch is not `main`, `master`, `develop`, or `release/*`, note: "This appears to be a stacked PR targeting `{base}`." Include this note in the Phase 4 preview.

#### Assignee
- If `--assignee` provided, use it.
- Otherwise: `gh api user --jq '.login'`

#### Reviewer
- If `--reviewer` provided, use it.
- Otherwise use a layered strategy, combining results from all available sources:

1. **CODEOWNERS** (top priority): Check for `.github/CODEOWNERS`, `CODEOWNERS`, or `docs/CODEOWNERS`. If found, parse it and match changed file paths against patterns. Extract usernames/teams. CODEOWNERS format: each line is `pattern  @owner1 @owner2`. Match using glob patterns (last matching pattern wins per file, same as GitHub). If config has `reviewers.use_codeowners: false`, skip this step.
2. **Recent file authors**: For the top 5 most-changed files (by diff stat), run `git log --format='%aN' -5 -- {file}`, deduplicate, exclude self.
3. **Recent PR reviewers** (fallback): `gh pr list --limit 15 --state merged --json author,reviews --jq '.[].reviews[].author.login'`, deduplicate, exclude self.

- Combine all results. Label each suggestion with its source:
  - "alice (CODEOWNERS for server/)"
  - "bob (recent author of auth.ts)"
  - "carol (recent PR reviewer)"
- If config specifies `reviewers.default`, include those with label "(default from config)".
- Deduplicate across sources (keep the highest-priority label).
- Present the combined list to the user via AskUserQuestion (up to 6 options + "Other" + "Skip"). Let them pick one or more.

#### Labels
- If `--label` provided, use those directly.
- Otherwise:
  1. Fetch available labels: `gh label list --json name --jq '.[].name'`
  2. If no labels exist in the repo, skip label inference entirely.
  3. Get changed file paths: `git diff --name-only {base}..HEAD`
  4. Infer labels using a dynamic matching strategy:

  **Config-based mappings** (if config has `labels.path_mappings`):
  - Match changed paths against configured path prefixes. Use the mapped label name if it exists in the repo.

  **Dynamic directory-to-label matching**:
  - Extract directory names from changed file paths (e.g., `packages/payments/src/foo.ts` → `packages`, `payments`, `src`)
  - For each repo label, check if its name (case-insensitive) is a substring of any directory name in the changed paths, or vice versa
  - E.g., label `payments` matches paths under `packages/payments/`; label `auth` matches paths under `libs/auth/`

  **Universal fallback heuristics** (always applied):
  - `*.md` files or paths under `docs/` → labels containing "doc"
  - Paths under `test/`, `tests/`, `__tests__/`, or files matching `*test*`, `*spec*` → labels containing "test"
  - Paths under `.github/`, `.circleci/`, `ci/`, `.gitlab-ci*`, `Jenkinsfile`, `Dockerfile`, `docker-compose*`, `terraform/`, `infra/` → labels containing "ci", "infra", or "devops"

  **Size labels**: Calculate total lines changed from `git diff --stat`. If the repo has labels matching `size/XS`, `size/S`, `size/M`, `size/L`, `size/XL` (case-insensitive):
  - XS: <10 lines, S: <50, M: <200, L: <500, XL: 500+
  - Auto-suggest the matching size label.

  **Monorepo package matching**: If monorepo detected (see Phase 3), match package/workspace names against available labels.

  5. Present inferred labels to user for confirmation. Let them add/remove before proceeding.
  6. If no labels match, proceed without labels.

#### Draft Status
- If `--draft` provided → draft = true
- If `--no-draft` provided → draft = false
- If config specifies `draft`, use that as default.
- Otherwise: ask the user via AskUserQuestion: "Create as draft?" with options "Yes (draft)" and "No (ready for review)"

---

### Phase 3 — Analyze Changes & Generate PR Content

#### Collect Change Data (run in parallel)
- `git log {base}..HEAD --pretty=format:'%h %s'` — commit list
- `git diff --stat {base}..HEAD` — file change stats
- `git diff {base}..HEAD` — full diff (only if <5000 lines; check with `git diff {base}..HEAD | wc -l` first)
- If diff is >5000 lines, warn the user and rely on commit messages + stats only.
- Count changed files: if >50, warn about large PR.

#### Size Classification
Calculate total lines changed (additions + deletions from `git diff --stat`). Classify:
- **XS**: <10 lines
- **S**: <50 lines
- **M**: <200 lines
- **L**: <500 lines
- **XL**: 500+ lines

Include this in the Phase 4 preview. If XL, add advisory: "This is a very large PR. Consider splitting into smaller, focused PRs for easier review."

#### Detect Monorepo Structure
Check for monorepo indicators (in parallel with diff collection):
- `pnpm-workspace.yaml`, `lerna.json`, `nx.json`, `rush.json`, `turbo.json`
- Presence of `packages/` or `apps/` directories at repo root

If detected, identify which packages/workspaces have changes by mapping changed file paths to package directories. This information is used for:
- Grouping changes in the PR body by package
- Package-based label matching (Phase 2)
- Noting in the summary if changes are scoped to a single package

#### Detect Conventional Commits
Parse the commit list. Check if >50% of commits follow the pattern `type(scope): description` or `type: description` (where type is one of: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`).

If conventional commits are detected:
- Use the predominant type to inform the title prefix (e.g., `feat:` → feature PR, `fix:` → bugfix PR)
- Group changes by type in the body (Features, Bug Fixes, Refactoring, etc.)
- If any commit contains `BREAKING CHANGE:` in its body/footer or uses `!` after the type (e.g., `feat!:`), add a "Breaking Changes" section to the body
- Still include the ticket ID prefix if present: `[TICKET-ID] feat: description`

If conventional commits are NOT detected (<=50% match), fall back to the current diff analysis behavior.

#### Detect PR Template
Before generating the body, check for repository PR templates in this order:
1. `.github/PULL_REQUEST_TEMPLATE.md`
2. `.github/pull_request_template.md`
3. `PULL_REQUEST_TEMPLATE.md` (repo root)
4. `pull_request_template.md` (repo root)
5. Multiple templates in `.github/PULL_REQUEST_TEMPLATE/*.md` — if found, list them and ask the user to pick one via AskUserQuestion

If config specifies `template`, use that path directly instead of searching.

**If a template is found:**
- Read the template file
- Preserve its structure: headings, checkboxes, sections, formatting
- Remove HTML comments (`<!-- ... -->`) and replace them with actual content generated from the diff/commits
- Fill in each section intelligently:
  - Summary/description sections → summarize from diff analysis
  - Changes/what sections → list actual changes
  - Testing sections → infer test steps from changes
  - Checklist items → check off items that are clearly satisfied by the diff (e.g., "Tests added" if test files are in the diff)
  - Leave unknown/inapplicable sections with a brief note or empty
- Append the `*Generated with Claude Code*` footer

**If no template is found**, use the built-in template below.

#### Generate Title
- If `--title` provided, use it.
- Otherwise:
  - Analyze the commits and diff to determine the primary change.
  - If conventional commits detected: use the predominant type as part of the title format.
  - Format: `[TICKET-ID] Imperative description` if ticket exists, or just `Imperative description`.
  - For HOTFIX branches: `[HOTFIX] Imperative description`.
  - Keep under 70 characters.

#### Generate PR Body (built-in template — used only when no repo template is found)

```
## Summary
{2-5 bullets: what changed and why, written from analyzing the actual diff and commits}

## Changes
{If monorepo: group by package with package name as sub-heading}
{If conventional commits: group by type — Features, Bug Fixes, Refactoring, etc.}
{Otherwise: group by area}
### {Area/Package/Type 1}
- {specific change}
### {Area/Package/Type 2}
- {specific change}
(use a flat bullet list instead of grouped headings if changes are simple/few)

{If breaking changes detected:}
## Breaking Changes
- {description of breaking change and migration path}

## Related Issues
{List any GitHub issue references found in commits or branch name}
{Use "Closes #123" for issues this PR fixes}
{Use "Relates to #123" for referenced but not fixed issues}
(OMIT this section if no issue references found)

## Screenshots
{embedded images if URLs were provided}
{TODO: drag-and-drop images in browser — if local file paths were provided}
(OMIT this entire section if no images)

## Testing
- [ ] {test item inferred from the changes}
- [ ] {build/compile verification relevant to the project}
- [ ] {manual verification step if applicable}

---
*Generated with [Claude Code](https://claude.com/claude-code)*
```

If config specifies `body_footer`, append it before the "Generated with Claude Code" line.

Fill in each section by actually reading and understanding the diff/commits — do not use generic placeholders. The summary and changes sections should reflect what actually changed.

---

### Phase 4 — Confirm with User (MANDATORY — never skip)

Present a full preview to the user. Format it clearly:

```
=== PR Preview ===

Title:    [DP-1067] Add safe area support
Base:     main
Assignee: mosamaasif
Reviewer: alice (CODEOWNERS), bob (recent author)
Labels:   client, mobile, size/M
Draft:    Yes
Size:     M (142 lines changed)
Template: .github/PULL_REQUEST_TEMPLATE.md
{If stacked PR: "Note: Stacked PR targeting `feature/base-branch`"}
{If monorepo: "Packages: @app/payments, @app/auth"}

--- Body ---
(full PR body from Phase 3)
--- End Body ---
```

Then ask via AskUserQuestion:
- "Create this PR?" with options:
  - "Yes, create it"
  - "Edit details" (loop back — ask what to change)
  - "Abort"

If user picks "Edit details", ask what they want to change, apply the change, and show the preview again. Loop until they approve or abort.

---

### Phase 5 — Create PR

1. **Push branch** if not already pushed:
   - Check: `git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null`
   - If no upstream: `git push -u origin HEAD`
   - If upstream exists but local is ahead: `git push`

2. **Create the PR** using `gh pr create`:
   ```bash
   gh pr create \
     --base "{base}" \
     --title "{title}" \
     --assignee "{assignee}" \
     --reviewer "{reviewer1},{reviewer2}" \
     --label "{label1},{label2}" \
     {--draft if draft} \
     --body "$(cat <<'PRBODYEOF'
   {generated body}
   PRBODYEOF
   )"
   ```
   - Omit `--reviewer` if none selected
   - Omit `--label` if none selected
   - Omit `--draft` if not draft

3. **Display the result**:
   - Show the PR URL returned by `gh pr create`
   - If local image files were provided via `--images`, remind: "Local images can't be uploaded via CLI. Open the PR in your browser and drag-and-drop the images into the Screenshots section."
   - Post-creation hint: "Monitor CI checks with `gh pr checks`."

---

## Edge Case Handling

- **No ticket ID in branch**: Omit `[TASK-ID]` prefix from title and omit ticket-related content from body.
- **HOTFIX branches**: Use `[HOTFIX]` prefix. No ticket link.
- **PR already exists**: Detected in Phase 1. Offer `gh pr edit` flow.
- **Very large diffs (>5000 lines or 50+ files)**: Warn user. Rely on commit messages and `--stat` for body generation.
- **Local image files**: Remind user to manually upload after PR creation.
- **Base branch missing on remote**: Fall back to repo default branch with a warning.
- **No labels in repo**: Skip label inference silently.
- **No reviewers found**: Skip reviewer suggestion, allow manual entry.
- **gh CLI not installed**: Stop immediately with install instructions.
- **Not a git repo**: Stop immediately.
- **No PR template found**: Use built-in template — never error on missing template.
- **No CODEOWNERS file**: Skip CODEOWNERS step, fall through to other reviewer strategies.
- **No conventional commits**: Fall back to diff-based analysis for title and body grouping.
- **Not a monorepo**: Use flat file grouping — no package-level grouping.
- **No config file**: Proceed with built-in defaults — config is strictly optional.
- **Config file has unknown keys**: Ignore unknown keys silently.
- **Multiple PR templates directory**: Ask user to pick one.
- **Stacked PR (non-standard base)**: Show advisory note, don't change behavior.
- **CI failures on branch**: Show warning, don't block PR creation.

