From sd0x-dev-flow
Creates or updates GitHub PRs using gh CLI. Auto-extracts ticket IDs from branch names, generates titles/summaries from commits, detects existing PRs for update mode. Defaults to dry-run.
npx claudepluginhub sd0xdev/sd0x-dev-flow --plugin sd0x-dev-flowThis skill is limited to using the following tools:
`/create-pr [--head <branch>] [--base <branch>] [--title <title>] [--update] [--execute] [--dry-run]`
Creates GitHub Pull Requests using GitHub CLI: detects existing PRs for branches, pushes changes, generates titles/bodies from commits. Handles monorepos/submodules. Use for /create-pr or PR/review requests.
Creates draft pull requests via GitHub CLI: gathers git context with status/logs/diffs, generates conventional commit titles, formats markdown bodies. Triggers on PR creation phrases.
Analyzes git diffs and commit history to fill PR templates and create pull requests via gh CLI. Useful for creating PRs, filling templates, or generating descriptions.
Share bugs, ideas, or general feedback.
/create-pr [--head <branch>] [--base <branch>] [--title <title>] [--update] [--execute] [--dry-run]
--head: Source branch (default: current branch)--base: Target branch (default: {TARGET_BRANCH} or main)--title: Override auto-generated title--update: Force update mode (re-generate title/body for existing PR)--dry-run: Show command without executing (default)--execute: Actually create/update the PR (requires user confirmation)# Current branch
git rev-parse --abbrev-ref HEAD
# Remote repo (owner/repo)
gh repo view --json nameWithOwner --jq '.nameWithOwner'
# Check if head branch is pushed
git ls-remote --heads origin <head-branch>
# Check existing PR
gh pr list --head <head-branch> --base <base-branch> --json number,title,state
# Commits between base..head
git log --oneline <base>..<head>
# Full diff for summary
git diff <base>...<head> --stat
From branch name, extract ticket ID using {TICKET_PATTERN} (default: [A-Z]+-\d+):
| Branch Pattern | Ticket ID |
|---|---|
fix/PROJ-520 | PROJ-520 |
fix/PROJ-520-2 | PROJ-520 |
feat/PROJ-123-some-desc | PROJ-123 |
refactor/PROJ-999 | PROJ-999 |
Regex: first match of {TICKET_PATTERN} — take first match. Strip trailing -N suffixes.
Format: <type>: [<TICKET>] <concise summary>
<type>: from branch prefix (fix/ → fix, feat/ → feat, docs/ → docs, refactor/ → refactor)<TICKET>: extracted ticket ID (omit if none found)<concise summary>: summarize commits in <60 chars, focus on main changes## Summary
<3-5 bullet points summarizing changes from commits>
## Ticket
[<TICKET>]({ISSUE_TRACKER_URL}<TICKET>)
## Test plan
- [ ] <test items based on what changed>
Rules:
{ISSUE_TRACKER_URL} not configuredForbidden patterns (POSIX ERE, case-insensitive — canonical source: scripts/commit-msg-guard.sh:19-23):
| Pattern Category | Regex |
|---|---|
| Co-Authored-By AI | Co-Authored-By:.*(Claude|Anthropic|GPT|OpenAI|Copilot|noreply@anthropic) |
| Generated-by tag | Generated (by|with).*(Claude|Claude Code|AI|GPT|Copilot) |
| Emoji robot tag | 🤖.*(Claude|AI|GPT) |
Note:
\|in the table above is Markdown table escaping. Actual POSIX ERE uses unescaped|.
After generating title and body (Step 3-4), scan for forbidden patterns and sanitize before any output or execution. Applies to all modes: dry-run/execute, create/update, --title override.
Title sanitization (regenerate/fail):
grep -Ei)--title override: same scan-and-fail logic (no regeneration — user-provided text fails immediately if matched)Body sanitization (line-strip + log):
[AI_STRIPPED] <removed line>| Check | Action if fails |
|---|---|
| Head branch not pushed | Warn: "branch not pushed to remote, push first" and STOP |
| PR already exists | → Enter Update Mode (see section below) |
--update flag + no existing PR | Warn: "no PR found for this branch" and STOP |
| No commits between base..head (create mode) | Warn: "no diff between branches" and STOP |
| No commits between base..head (update mode) | Continue — PR may need title/body refresh from --title override |
Mode detection logic:
| Condition | Mode |
|---|---|
--update flag passed | Force update mode (error if no PR exists) |
| Existing PR detected (auto) | Update mode (auto-switch) |
No existing PR, no --update | Create mode (original workflow) |
When an existing PR is detected (or --update is passed):
Step 1: Fetch current PR state (use PR number from pre-flight gh pr list result):
gh pr view <PR-number> --json number,title,body,url,baseRefName
Step 2: Re-generate title and body from latest commits (same logic as Steps 2-4 above, using full commit range base..head). Run Step 4b AI Content Sanitization on the re-generated content before proceeding.
Step 3: Smart diff — compare current vs newly generated:
| Field | Current | New | Action |
|---|---|---|---|
| Title | same | same | Skip (no change needed) |
| Title | differs | differs | Show before/after |
| Body | same | same | Skip |
| Body | differs | differs | Show before/after |
Step 4: Decision — if both title and body are unchanged → report "PR is already up to date" and STOP.
If changes detected, show the diff and decide what to update:
fix: → feat:) or ticket ID changed.<type>: [<TICKET>] differs.--title is passed: override title regardless of diffStep 5: Output (respects --dry-run / --execute):
Dry-run (default) — show the gh pr edit command with only changed fields included:
# Title-only update (use printf for safe escaping):
gh pr edit <number> --title "$(printf '%s' '<new-title>')"
# Body-only update (use --body-file for safe escaping):
gh pr edit <number> --body-file /dev/stdin <<'EOF'
<new-body>
EOF
# Both title + body:
gh pr edit <number> --title "$(printf '%s' '<new-title>')" --body-file /dev/stdin <<'EOF'
<new-body>
EOF
Use --body-file instead of --body to avoid shell escaping issues with quotes and newlines in the body content.
Execute (--execute) — ask user for confirmation via AskUserQuestion, then run gh pr edit. Output:
PR updated: <URL>
Title: <old-title> → <new-title>
Changes: title updated, body updated
Show the full gh pr create command:
gh pr create \
--head <head-branch> \
--base <base-branch> \
--title "<title>" \
--body "$(cat <<'EOF'
<generated body>
EOF
)"
User can copy-paste to execute, or re-run with --execute.
Ask user for confirmation, then run the command. Output:
PR created: <URL>
Title: <title>
Base: <base> ← Head: <head>
After gh pr create or gh pr edit completes in --execute mode, verify the published content for AI attribution leaks.
Step 1: Fetch actual published content:
gh pr view <number> --json title,body --template '{{.title}}{{"\n"}}{{.body}}'
Step 2: Scan for forbidden patterns (same 3 POSIX ERE from Step 4b).
Step 3: If leak detected — auto-remediate (single attempt, using pre-sanitized snapshot from Step 4b):
# Title (safe escaping via printf):
gh pr edit <number> --title "$(printf '%s' "$SANITIZED_TITLE")"
# Body (safe escaping via --body-file + heredoc):
gh pr edit <number> --body-file /dev/stdin <<'EOF'
<pre-sanitized-body-snapshot>
EOF
Step 4: Re-verify via gh pr view. If still leaked → HARD FAIL:
❌ AI attribution leaked in PR #<number> after remediation attempt.
Manual fix: gh pr edit <number> --title "<clean-title>" --body-file <clean-body-file>
Guardrails:
When user specifies multiple branch pairs (e.g. "A → main, B → A"), create them sequentially and output all URLs at the end.
| Case | Behavior |
|---|---|
| No ticket ID in branch name | Omit [TICKET] from title, omit Ticket section from body |
Branch suffix like -2, -3 | Strip suffix when extracting ticket ID |
User provides --title | Use as-is (skip auto-generation), but still run Step 4b scan — fail immediately if forbidden pattern matched |
| Stacked PRs (B → A → main) | Note dependency in body: "Stacked on #" |
--update but no existing PR | Error: "No PR found for branch <head> → <base>" |
| Auto-detect existing PR | Switch to update mode, show "Existing PR #N detected, switching to update mode" |
| PR body has manual edits | Re-generate from commits; user reviews before/after diff |
| Title unchanged after new commits | Skip title update, only update body |
gh pr view)gh pr edit command