Skip to content

Commit 7c99ed1

Browse files
authored
Merge pull request #111 from igerber/feature/push-pr-update-skill
Add /push-pr-update skill for pushing PR revisions
2 parents 525ac13 + d5b7890 commit 7c99ed1

2 files changed

Lines changed: 302 additions & 3 deletions

File tree

.claude/commands/push-pr-update.md

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
---
2+
description: Push code revisions to an existing PR and trigger AI code review
3+
argument-hint: "[--message <commit-msg>] [--no-review]"
4+
---
5+
6+
# Push PR Update
7+
8+
Push local changes to an existing pull request branch and optionally trigger AI code review.
9+
10+
## Arguments
11+
12+
`$ARGUMENTS` may contain:
13+
- `--message <msg>` (optional): Custom commit message. If omitted, auto-generate from changes.
14+
- `--no-review` (optional): Skip triggering AI review after push.
15+
16+
## Instructions
17+
18+
### 1. Parse Arguments
19+
20+
Parse `$ARGUMENTS` to extract:
21+
- **--message**: Custom commit message (everything after `--message` until next flag or end)
22+
- **--no-review**: Boolean flag
23+
24+
### 2. Validate Current State
25+
26+
1. **Get repository default branch**:
27+
```bash
28+
gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name'
29+
```
30+
Store as `<default-branch>`.
31+
32+
2. **Check current branch**:
33+
```bash
34+
git branch --show-current
35+
```
36+
- If current branch equals `<default-branch>`, abort:
37+
```
38+
Error: Cannot push PR update from <default-branch> branch.
39+
Switch to a feature branch or use /submit-pr to create a new PR.
40+
```
41+
42+
3. **Get PR information**:
43+
```bash
44+
gh pr view --json number,url,headRefName,baseRefName
45+
```
46+
- If no PR exists for current branch, abort:
47+
```
48+
Error: No open PR found for branch '<branch-name>'.
49+
Use /submit-pr to create a new pull request.
50+
```
51+
- Store PR number and URL for later use.
52+
53+
4. **Check for changes to commit or push**:
54+
```bash
55+
git status --porcelain
56+
```
57+
- If output is empty (working directory clean):
58+
- Check if branch has an upstream tracking branch:
59+
```bash
60+
git rev-parse --abbrev-ref @{u} 2>/dev/null
61+
```
62+
- If NO upstream exists:
63+
- Determine comparison ref (handles shallow/single-branch clones):
64+
- If `<default-branch>` exists locally (`git rev-parse --verify <default-branch> 2>/dev/null`): use `<default-branch>`
65+
- Else if `origin/<default-branch>` exists (`git rev-parse --verify origin/<default-branch> 2>/dev/null`): use `origin/<default-branch>`
66+
- Else: fetch it first (`git fetch origin <default-branch> --depth=1 2>/dev/null || true`), then use `origin/<default-branch>`
67+
- Store as `<comparison-ref>`
68+
- Check if branch has commits ahead: `git rev-list --count <comparison-ref>..HEAD 2>/dev/null || echo "0"`
69+
- If ahead count > 0:
70+
- **Scan for secrets in commits to push** (see Section 3a below)
71+
- Compute `<files-changed-count>`: `git diff --name-only <comparison-ref>..HEAD | wc -l`
72+
- Skip to Section 4 (Push to Remote) — will push with `-u` to set upstream
73+
- If ahead count = 0: Abort (new branch with nothing to push):
74+
```
75+
No changes detected. Working directory is clean and branch has no commits ahead of <default-branch>.
76+
Nothing to push.
77+
```
78+
- If upstream EXISTS:
79+
- Check if branch is ahead: `git rev-list --count @{u}..HEAD`
80+
- If ahead count > 0:
81+
- **Scan for secrets in commits to push** (see Section 3a below)
82+
- Compute `<files-changed-count>`: `git diff --name-only @{u}..HEAD | wc -l`
83+
- Skip to Section 4 (Push to Remote) — there are committed changes to push
84+
- If ahead count = 0: Abort:
85+
```
86+
No changes detected. Working directory is clean and branch is up to date.
87+
Nothing to push.
88+
```
89+
90+
### 3a. Secret Scan for Already-Committed Changes (when skipping Section 3)
91+
92+
When the working tree is clean but commits are ahead, scan for secrets in the commits to be pushed before proceeding to Section 4:
93+
94+
1. **Get diff range**: Use `<comparison-ref>..HEAD` (from Section 2.4 — either `@{u}`, `<default-branch>`, or `origin/<default-branch>`)
95+
96+
2. **Run pattern check** (file names only, no content leaked):
97+
```bash
98+
secret_files=$(git diff <comparison-ref>..HEAD -G "(AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|gho_[a-zA-Z0-9]{36}|[Aa][Pp][Ii][_-]?[Kk][Ee][Yy][[:space:]]*[=:]|[Ss][Ee][Cc][Rr][Ee][Tt][_-]?[Kk][Ee][Yy][[:space:]]*[=:]|[Pp][Aa][Ss][Ss][Ww][Oo][Rr][Dd][[:space:]]*[=:]|[Pp][Rr][Ii][Vv][Aa][Tt][Ee][_-]?[Kk][Ee][Yy]|[Bb][Ee][Aa][Rr][Ee][Rr][[:space:]]+[a-zA-Z0-9_-]+|[Tt][Oo][Kk][Ee][Nn][[:space:]]*[=:])" --name-only 2>/dev/null || true)
99+
```
100+
101+
3. **Check for sensitive file names**:
102+
```bash
103+
sensitive_files=$(git diff --name-only <comparison-ref>..HEAD | grep -iE "(\.env|credentials|secret|\.pem|\.key|\.p12|\.pfx|id_rsa|id_ed25519)$" || true)
104+
```
105+
106+
4. **If patterns detected**, warn with AskUserQuestion:
107+
```
108+
Warning: Potential secrets detected in committed changes:
109+
- <list of files/patterns>
110+
111+
These changes are already committed. Options:
112+
1. Abort - use 'git reset --soft HEAD~N' to uncommit and remove secrets before retrying
113+
2. Continue anyway - I confirm these are not real secrets
114+
```
115+
Note: Unlike Section 3, we cannot simply unstage these changes since they are already committed.
116+
117+
### 3. Stage and Commit Changes
118+
119+
1. **Stage all changes**:
120+
```bash
121+
git add -A
122+
```
123+
124+
2. **Capture file count for reporting**:
125+
```bash
126+
git diff --cached --name-only | wc -l
127+
```
128+
Store as `<files-changed-count>` for use in final report.
129+
130+
3. **Secret scanning check** (same as submit-pr):
131+
- **Run deterministic pattern check** (file names only, no content leaked):
132+
```bash
133+
secret_files=$(git diff --cached -G "(AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|gho_[a-zA-Z0-9]{36}|[Aa][Pp][Ii][_-]?[Kk][Ee][Yy][[:space:]]*[=:]|[Ss][Ee][Cc][Rr][Ee][Tt][_-]?[Kk][Ee][Yy][[:space:]]*[=:]|[Pp][Aa][Ss][Ss][Ww][Oo][Rr][Dd][[:space:]]*[=:]|[Pp][Rr][Ii][Vv][Aa][Tt][Ee][_-]?[Kk][Ee][Yy]|[Bb][Ee][Aa][Rr][Ee][Rr][[:space:]]+[a-zA-Z0-9_-]+|[Tt][Oo][Kk][Ee][Nn][[:space:]]*[=:])" --name-only 2>/dev/null || true)
134+
```
135+
Note: Uses `-G` to search diff content but `--name-only` to output only file names, preventing secret values from appearing in logs. The `|| true` prevents exit status 1 when patterns match from aborting strict runners.
136+
- **Check for sensitive file names**:
137+
```bash
138+
sensitive_files=$(git diff --cached --name-only | grep -iE "(\.env|credentials|secret|\.pem|\.key|\.p12|\.pfx|id_rsa|id_ed25519)$" || true)
139+
```
140+
- **If patterns detected** (i.e., `secret_files` or `sensitive_files` is non-empty), **unstage and warn**:
141+
```bash
142+
git reset HEAD
143+
```
144+
Then use AskUserQuestion:
145+
```
146+
Warning: Potential secrets detected in files:
147+
- <list of files/patterns>
148+
149+
Files have been unstaged for safety.
150+
151+
Options:
152+
1. Abort - review and remove secrets before retrying
153+
2. Continue anyway - I confirm these are not real secrets (will re-stage)
154+
```
155+
- If user chooses to continue, re-stage with `git add -A`
156+
157+
4. **Generate or use commit message**:
158+
- If `--message` provided, use that message
159+
- Otherwise, generate from changes:
160+
- Run `git diff --cached --stat` to see what's being committed
161+
- Analyze the changes and generate a descriptive commit message
162+
- Use imperative mood ("Add", "Fix", "Update", "Refactor")
163+
- Format with HEREDOC and Co-Authored-By:
164+
```bash
165+
git commit -m "$(cat <<'EOF'
166+
<commit message>
167+
168+
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
169+
EOF
170+
)"
171+
```
172+
173+
### 4. Push to Remote
174+
175+
1. **Check for upstream tracking branch**:
176+
```bash
177+
git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null
178+
```
179+
180+
2. **Push to remote**:
181+
- If upstream exists: `git push`
182+
- If no upstream: `git push -u origin HEAD`
183+
184+
If push fails, report error and suggest:
185+
```
186+
Push failed: <error message>
187+
188+
If the remote has new commits, try:
189+
git pull --rebase && /push-pr-update
190+
```
191+
192+
3. **Get pushed commit info**:
193+
```bash
194+
git log -1 --oneline
195+
```
196+
197+
### 5. Trigger AI Review (unless `--no-review`)
198+
199+
If `--no-review` flag was NOT provided:
200+
201+
1. **Get base repository from PR**:
202+
```bash
203+
gh pr view --json baseRepository --jq '.baseRepository.owner.login + "/" + .baseRepository.name'
204+
```
205+
Store as `<owner>/<repo>` (this is the upstream repo, correct for fork workflows).
206+
Parse to extract `<owner>` and `<repo>`.
207+
208+
2. **Add review comment using MCP tool**:
209+
```
210+
mcp__github__add_issue_comment with parameters:
211+
- owner: <owner>
212+
- repo: <repo>
213+
- issue_number: <PR number from step 2>
214+
- body: "/ai-review"
215+
```
216+
217+
### 6. Report Results
218+
219+
**If AI review triggered:**
220+
```
221+
Changes pushed to PR #<number>
222+
223+
Commit: <hash> - <message>
224+
Files changed: <files-changed-count>
225+
226+
AI code review triggered. Results will appear shortly.
227+
228+
PR URL: <url>
229+
```
230+
231+
**If `--no-review` was used:**
232+
```
233+
Changes pushed to PR #<number>
234+
235+
Commit: <hash> - <message>
236+
Files changed: <files-changed-count>
237+
238+
PR URL: <url>
239+
240+
Tip: Run /ai-review to request AI code review.
241+
```
242+
243+
## Error Handling
244+
245+
### Not on a Feature Branch
246+
```
247+
Error: Cannot push PR update from <default-branch> branch.
248+
Switch to a feature branch or use /submit-pr to create a new PR.
249+
```
250+
251+
### No Changes to Commit or Push (with upstream)
252+
```
253+
No changes detected. Working directory is clean and branch is up to date.
254+
Nothing to push.
255+
```
256+
257+
### No Changes to Commit or Push (no upstream, no commits ahead)
258+
```
259+
No changes detected. Working directory is clean and branch has no commits ahead of <default-branch>.
260+
Nothing to push.
261+
```
262+
263+
### No Open PR for Branch
264+
```
265+
Error: No open PR found for branch '<branch-name>'.
266+
Use /submit-pr to create a new pull request.
267+
```
268+
269+
### Push Failed
270+
```
271+
Push failed: <error message>
272+
273+
If the remote has new commits, try:
274+
git pull --rebase && /push-pr-update
275+
```
276+
277+
## Examples
278+
279+
```bash
280+
# Push changes with auto-generated commit message and trigger AI review
281+
/push-pr-update
282+
283+
# Push with custom commit message
284+
/push-pr-update --message "Address PR feedback: fix edge case handling"
285+
286+
# Push without triggering AI review
287+
/push-pr-update --no-review
288+
289+
# Both options together
290+
/push-pr-update --message "Fix typo in docstring" --no-review
291+
```
292+
293+
## Notes
294+
295+
- This skill is for updating existing PRs. Use `/submit-pr` to create new PRs.
296+
- Always stages ALL changes (`git add -A`). Stage manually first for partial commits.
297+
- The `/ai-review` comment triggers the repository's AI review workflow (if configured).
298+
- Uses the same secret scanning as `/submit-pr` to prevent accidental credential commits.

.claude/commands/submit-pr.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,11 @@ Determine if this is a fork-based workflow:
134134
```
135135

136136
2. **Secret scanning check** (AFTER staging to catch all files):
137-
- **Run deterministic pattern check** (case-insensitive with expanded patterns):
137+
- **Run deterministic pattern check** (file names only, no content leaked):
138138
```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
139+
secret_files=$(git diff --cached -G "(AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|gho_[a-zA-Z0-9]{36}|[Aa][Pp][Ii][_-]?[Kk][Ee][Yy][[:space:]]*[=:]|[Ss][Ee][Cc][Rr][Ee][Tt][_-]?[Kk][Ee][Yy][[:space:]]*[=:]|[Pp][Aa][Ss][Ss][Ww][Oo][Rr][Dd][[:space:]]*[=:]|[Pp][Rr][Ii][Vv][Aa][Tt][Ee][_-]?[Kk][Ee][Yy]|[Bb][Ee][Aa][Rr][Ee][Rr][[:space:]]+[a-zA-Z0-9_-]+|[Tt][Oo][Kk][Ee][Nn][[:space:]]*[=:])" --name-only 2>/dev/null || true)
140140
```
141+
Note: Uses `-G` to search diff content but `--name-only` to output only file names, preventing secret values from appearing in logs. The `|| true` prevents exit status 1 when patterns match from aborting strict runners.
141142
- **Check for sensitive file names** (case-insensitive):
142143
```bash
143144
git diff --cached --name-only | grep -iE "(\.env|credentials|secret|\.pem|\.key|\.p12|\.pfx|id_rsa|id_ed25519)$" || true
@@ -151,7 +152,7 @@ Determine if this is a fork-based workflow:
151152
```bash
152153
git diff --cached --name-only --diff-filter=A
153154
```
154-
- If pattern check returns matches or sensitive files detected, **unstage and warn**:
155+
- **If patterns detected** (i.e., `secret_files` or sensitive file names non-empty), **unstage and warn**:
155156
```bash
156157
git reset HEAD # Unstage all files
157158
```

0 commit comments

Comments
 (0)