Skip to content

Commit e2111ea

Browse files
authored
Merge pull request #89 from igerber/feature/submit-pr-skill
Add /submit-pr skill for automated PR creation
2 parents f84173d + 4a1cca7 commit e2111ea

1 file changed

Lines changed: 349 additions & 0 deletions

File tree

.claude/commands/submit-pr.md

Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
---
2+
description: Commit changes to a new branch, push to GitHub, and open a PR with project template
3+
argument-hint: "[title] [--branch <name>] [--base <branch>] [--draft]"
4+
---
5+
6+
# Submit Pull Request
7+
8+
Commit work, push to a new branch, and open a pull request with the project-specific PR template.
9+
10+
## Arguments
11+
12+
`$ARGUMENTS` may contain:
13+
- **title** (optional): PR title. If omitted, auto-generate from changes/commits.
14+
- `--branch <name>` (optional): Branch name. If omitted, auto-generate from title.
15+
- `--base <branch>` (optional): Base branch for PR. Default: `main`.
16+
- `--draft` (optional): Create as draft PR.
17+
18+
## Instructions
19+
20+
### 1. Parse Arguments
21+
22+
Parse `$ARGUMENTS` to extract:
23+
- **title**: Everything before any `--` flags
24+
- **--branch**: Branch name (if provided)
25+
- **--base**: Base branch (default: `main`)
26+
- **--draft**: Boolean flag
27+
28+
### 2. Detect Remote Configuration
29+
30+
Determine if this is a fork-based workflow:
31+
32+
1. **Check for upstream remote**:
33+
```bash
34+
git remote get-url upstream 2>/dev/null
35+
```
36+
37+
2. **Set remote variables**:
38+
- If `upstream` exists → **fork workflow**:
39+
- `<base-remote>` = `upstream` (for base comparisons, PR target)
40+
- `<push-remote>` = `origin` (for pushing branches)
41+
- Extract `<upstream-owner>/<upstream-repo>` from upstream URL
42+
- Extract `<fork-owner>` from origin URL
43+
- If `upstream` does not exist → **direct workflow**:
44+
- `<base-remote>` = `origin`
45+
- `<push-remote>` = `origin`
46+
- Extract `<owner>/<repo>` from origin URL
47+
48+
3. **Fetch from base remote**:
49+
```bash
50+
git fetch <base-remote>
51+
```
52+
53+
### 3. Sync with Remote
54+
55+
1. **Check if behind base branch**:
56+
```bash
57+
git rev-list --count HEAD..<base-remote>/<base-branch>
58+
```
59+
- If count > 0, we're behind. Warn user and offer options:
60+
```
61+
Your branch is X commits behind <base-remote>/<base-branch>.
62+
63+
Options:
64+
1. Rebase first: git pull --rebase <base-remote> <base-branch>
65+
2. Continue anyway (may have merge conflicts in PR)
66+
```
67+
- Use AskUserQuestion to let user choose whether to continue or abort
68+
69+
### 4. Check for Changes
70+
71+
1. **Check for uncommitted changes**:
72+
```bash
73+
git status --porcelain
74+
```
75+
- If output is non-empty, there are staged or unstaged changes → proceed to step 5
76+
77+
2. **Check for unpushed commits** (if no uncommitted changes):
78+
```bash
79+
git rev-list --count <base-remote>/<base-branch>..HEAD
80+
```
81+
- If count > 0, there are unpushed commits → skip to step 7
82+
- If count == 0, inform user and exit:
83+
```
84+
No changes detected. Your working directory is clean and up-to-date with <base-remote>/<base-branch>.
85+
Nothing to submit.
86+
```
87+
88+
### 5. Resolve Branch Name (BEFORE any commits)
89+
90+
**IMPORTANT**: Always resolve the branch name before staging or committing to avoid commits on the base branch.
91+
92+
1. **Check current branch**:
93+
```bash
94+
git branch --show-current
95+
```
96+
97+
2. **If on base branch (e.g., `main`)**:
98+
- Generate or use provided branch name
99+
- **Generate branch name** (if not provided via `--branch`):
100+
- Analyze changes to understand the change type:
101+
```bash
102+
git diff --stat # Unstaged changes
103+
git diff --cached --stat # Staged changes
104+
git status --porcelain # All changes summary
105+
```
106+
- **Sanitize the branch name** (from title or generated):
107+
1. Lowercase the string
108+
2. Replace spaces with hyphens
109+
3. Remove invalid git ref characters: `:`, `?`, `*`, `[`, `]`, `^`, `~`, `\`, `@{`, `..`
110+
4. Replace consecutive hyphens/underscores with single hyphen
111+
5. Trim leading/trailing hyphens
112+
6. Truncate to reasonable length (50 chars max for branch name portion)
113+
- Prefix based on change type: `feature/`, `fix/`, `refactor/`, `docs/`
114+
- **Validate with git**:
115+
```bash
116+
git check-ref-format --branch "<branch-name>"
117+
```
118+
- If validation fails, prompt user for a valid branch name
119+
- If no diff output and no title provided, prompt user for branch name
120+
- **Create and switch to the new branch BEFORE staging**:
121+
```bash
122+
git checkout -b <branch-name>
123+
```
124+
125+
3. **If already on a feature branch**:
126+
- Use the current branch name
127+
- No need to create a new branch
128+
129+
### 6. Stage and Commit Changes
130+
131+
1. **Stage all changes first**:
132+
```bash
133+
git add -A
134+
```
135+
136+
2. **Secret scanning check** (AFTER staging to catch all files):
137+
- **Run deterministic pattern check** (case-insensitive with expanded patterns):
138+
```bash
139+
git diff --cached | grep -iE "(AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|gho_[a-zA-Z0-9]{36}|api[_-]?key\s*[=:]|secret[_-]?key\s*[=:]|password\s*[=:]|private[_-]?key|bearer\s+[a-zA-Z0-9_-]+|token\s*[=:])" || true
140+
```
141+
- **Check for sensitive file names** (case-insensitive):
142+
```bash
143+
git diff --cached --name-only | grep -iE "(\.env|credentials|secret|\.pem|\.key|\.p12|\.pfx|id_rsa|id_ed25519)$" || true
144+
```
145+
- **Optional**: For more thorough scanning, use dedicated tools if available:
146+
```bash
147+
# gitleaks detect --staged --no-git # If gitleaks installed
148+
# trufflehog git file://. --only-verified --fail # If trufflehog installed
149+
```
150+
- Pay special attention to newly added files:
151+
```bash
152+
git diff --cached --name-only --diff-filter=A
153+
```
154+
- If pattern check returns matches or sensitive files detected, **unstage and warn**:
155+
```bash
156+
git reset HEAD # Unstage all files
157+
```
158+
Then use AskUserQuestion:
159+
```
160+
Warning: Potential secrets detected in files:
161+
- .env.local (contains API_KEY=)
162+
- config.json (contains "password":)
163+
164+
Files have been unstaged for safety.
165+
166+
Options:
167+
1. Abort - review and remove secrets before retrying
168+
2. Continue anyway - I confirm these are not real secrets (will re-stage)
169+
```
170+
- If user chooses to continue, re-stage with `git add -A`
171+
172+
3. **Generate commit message**:
173+
- Run `git diff --cached --stat` to see what's being committed
174+
- Analyze the changes and generate a descriptive commit message
175+
- Use imperative mood ("Add", "Fix", "Update", "Refactor")
176+
- Format with HEREDOC:
177+
```bash
178+
git commit -m "$(cat <<'EOF'
179+
<generated commit message>
180+
181+
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
182+
EOF
183+
)"
184+
```
185+
186+
### 7. Push Branch to Remote
187+
188+
1. **Resolve and validate branch name**:
189+
```bash
190+
git branch --show-current
191+
```
192+
193+
2. **Guard: Prevent pushing from base branch**:
194+
- If current branch equals `<base-branch>` (e.g., `main`):
195+
- This can happen when step 4 skipped to step 7 due to unpushed commits on base
196+
- **Must create a new branch before proceeding**:
197+
- Generate branch name from unpushed commits (analyze `git log <base-remote>/<base-branch>..HEAD`)
198+
- Use provided `--branch` name if available
199+
- Create and switch:
200+
```bash
201+
git checkout -b <branch-name>
202+
```
203+
- If branch creation fails or is declined, abort with error:
204+
```
205+
Error: Cannot create PR from base branch to itself.
206+
Please create a feature branch first or provide --branch <name>.
207+
```
208+
209+
3. **Push to push-remote** (always `origin`, even in fork workflows):
210+
```bash
211+
git push -u <push-remote> <branch-name>
212+
```
213+
214+
### 8. Extract Commit Information for PR Body
215+
216+
1. Get commits on this branch (compare against base-remote to avoid stale data):
217+
```bash
218+
git log <base-remote>/<base-branch>..HEAD --oneline
219+
```
220+
221+
2. Get changed files:
222+
```bash
223+
git diff <base-remote>/<base-branch>..HEAD --stat
224+
```
225+
226+
3. Categorize changes for the template:
227+
- **Estimator/math changes**: files in `diff_diff/`, `rust/src/`, or `docs/methodology/`
228+
- Test changes: files in `tests/`
229+
- Documentation: files in `docs/`, `*.md`, `*.rst`
230+
231+
### 9. Generate PR Body
232+
233+
Fill in the template:
234+
235+
```markdown
236+
## Summary
237+
- <bullet point for each commit>
238+
239+
## Methodology references (required if estimator / math changes)
240+
- Method name(s): <from code analysis or "N/A - no methodology changes">
241+
- Paper / source link(s): <from docstrings or "N/A">
242+
- Any intentional deviations from the source (and why): <if applicable or "None">
243+
244+
## Validation
245+
- Tests added/updated: <list test files or "No test changes">
246+
- Backtest / simulation / notebook evidence (if applicable): <if tutorials updated or "N/A">
247+
248+
## Security / privacy
249+
- Confirm no secrets/PII in this PR: Yes
250+
251+
---
252+
Generated with Claude Code
253+
```
254+
255+
**Template logic:**
256+
- **Methodology**: Mark "N/A" only if NO files changed in `diff_diff/`, `rust/src/`, or `docs/methodology/`. If methodology files changed, consult `docs/methodology/REGISTRY.md` for proper citations.
257+
- **Validation**: List `test_*.py` files changed, note tutorial updates
258+
- **Security**: Default "Yes", but warn if `.env`, credentials, or API key patterns detected
259+
260+
### 10. Create Pull Request
261+
262+
Use the MCP GitHub tool to create the PR:
263+
264+
```
265+
mcp__github__create_pull_request with parameters:
266+
- owner: <target-owner> # upstream-owner (fork) or owner (direct)
267+
- repo: <target-repo> # upstream-repo (fork) or repo (direct)
268+
- title: <PR title>
269+
- head: <head-ref> # See below for fork vs direct
270+
- base: <base-branch>
271+
- body: <generated PR body>
272+
- draft: <true if --draft flag provided>
273+
```
274+
275+
**Head reference format**:
276+
- **Direct workflow**: `head: <branch-name>`
277+
- **Fork workflow**: `head: <fork-owner>:<branch-name>`
278+
279+
**Extract remote info**:
280+
```bash
281+
# For target (where PR is created)
282+
git remote get-url <base-remote>
283+
# Parse: git@github.com:owner/repo.git or https://github.com/owner/repo.git
284+
285+
# For fork owner (if fork workflow)
286+
git remote get-url origin
287+
# Extract owner from URL
288+
```
289+
290+
### 11. Report Results
291+
292+
```
293+
Pull request created successfully!
294+
295+
Branch: <branch-name>
296+
PR: #<number> - <title>
297+
URL: https://github.com/<target-owner>/<target-repo>/pull/<number>
298+
299+
Changes included:
300+
<list of changed files>
301+
302+
Next steps:
303+
- Review the PR at the URL above
304+
- Request reviewers if needed
305+
- Run /review-pr <number> to get AI review
306+
```
307+
308+
## Error Handling
309+
310+
### No Changes to Commit
311+
```
312+
No changes detected. Your working directory is clean.
313+
Nothing to submit.
314+
```
315+
316+
### Branch Already Exists
317+
```
318+
Branch '<name>' already exists.
319+
Options:
320+
1. Provide different name: /submit-pr "title" --branch <new-name>
321+
2. Delete existing: git branch -D <name>
322+
```
323+
324+
### Push/PR Creation Failed
325+
Show the error and provide manual fallback commands.
326+
327+
## Examples
328+
329+
```bash
330+
# Auto-generate everything
331+
/submit-pr
332+
333+
# With custom title
334+
/submit-pr "Add pre-trends power analysis"
335+
336+
# With custom branch
337+
/submit-pr "Fix bootstrap variance" --branch fix/bootstrap-variance
338+
339+
# Draft PR against different base
340+
/submit-pr "Refactor linalg module" --base develop --draft
341+
```
342+
343+
## Notes
344+
345+
- Always stages ALL changes (`git add -A`). Stage manually first for partial commits.
346+
- Branch names auto-prefixed: feature/, fix/, refactor/, docs/
347+
- Uses MCP GitHub server for PR creation (requires PAT with repo access)
348+
- Git push uses SSH or HTTPS based on remote URL configuration
349+
- **Fork workflows supported**: If `upstream` remote exists, PRs target upstream with `<fork-owner>:<branch>` head reference

0 commit comments

Comments
 (0)