Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:47:43 +08:00
commit 2e8d89fca3
41 changed files with 14051 additions and 0 deletions

328
commands/create-pr.md Normal file
View File

@@ -0,0 +1,328 @@
---
description: Create PR as draft, wait for CI to pass, then mark ready for review (solves the "I'll check back later" problem)
---
# Creating PR with CI Verification
**IMPORTANT:** This command solves the common problem where Claude creates a PR, says "I'll wait for CI", then gives up. This command ACTUALLY waits for CI completion.
## Workflow
This command will:
1. ✅ Create PR as draft
2. ✅ Wait for CI checks to complete (actually wait, not give up)
3. ✅ Mark as ready for review if CI passes
4. ✅ Report failures with links if CI fails
## Step 1: Determine Current Branch and Changes
```bash
# Get current branch
CURRENT_BRANCH=$(git branch --show-current)
# Get base branch (usually main or master)
BASE_BRANCH=$(git remote show origin | grep 'HEAD branch' | cut -d' ' -f5)
# Verify we're not on main/master
if [[ "$CURRENT_BRANCH" == "main" || "$CURRENT_BRANCH" == "master" ]]; then
echo "ERROR: Cannot create PR from main/master branch"
echo "Create a feature branch first: git checkout -b feature-name"
exit 1
fi
# Check if branch is pushed
git rev-parse --verify origin/$CURRENT_BRANCH > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "Branch not pushed yet. Pushing now..."
git push -u origin $CURRENT_BRANCH
fi
```
## Step 2: Gather PR Context
Run in parallel to gather information:
```bash
# 1. Git status
git status
# 2. Diff since divergence from base
git diff ${BASE_BRANCH}...HEAD
# 3. Commit history for this branch
git log ${BASE_BRANCH}..HEAD --oneline
# 4. Check if PR already exists
gh pr view --json number,state,isDraft 2>/dev/null
```
**If PR already exists:**
```bash
EXISTING_PR=$(gh pr view --json number --jq '.number')
echo "PR #$EXISTING_PR already exists for this branch"
echo "Use /review-pr $EXISTING_PR or /fix-pr $EXISTING_PR instead"
exit 0
```
## Step 3: Create PR Title and Body
Based on the changes (read from git diff and commit history):
**Title:** Concise summary of changes (50 chars max)
**Body format:**
```markdown
## Summary
- Bullet point 1
- Bullet point 2
## Changes
- Specific change 1
- Specific change 2
## Testing
- Test approach
- Verification steps
## Checklist
- [ ] Tests pass locally
- [ ] Code formatted (make format)
- [ ] Changelog entry created (if applicable)
---
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
```
## Step 4: Create PR as Draft
**CRITICAL:** Always create as draft initially.
```bash
# Create PR as draft
gh pr create \
--title "PR Title Here" \
--body "$(cat <<'EOF'
PR body here
EOF
)" \
--draft
# Get PR number
PR_NUMBER=$(gh pr view --json number --jq '.number')
echo "Created draft PR #$PR_NUMBER"
echo "URL: $(gh pr view --json url --jq '.url')"
```
## Step 5: Wait for CI Checks (DO NOT GIVE UP!)
**This is where Claude usually fails. DO NOT say "I'll check back later".**
**Instead, ACTUALLY wait using a polling loop with NO timeout:**
```bash
echo "Waiting for CI checks to complete..."
echo "Typical CI times by repository:"
echo " - policyengine-app: 3-6 minutes"
echo " - policyengine-uk: 9-12 minutes"
echo " - policyengine-api: 30 minutes"
echo " - policyengine-us: 60-75 minutes (longest)"
echo ""
echo "I will poll every 15 seconds until all checks complete. No time limit."
POLL_INTERVAL=15 # Check every 15 seconds
ELAPSED=0
while true; do
# Get check status
CHECKS_JSON=$(gh pr checks $PR_NUMBER --json name,status,conclusion)
# Count checks
TOTAL=$(echo "$CHECKS_JSON" | jq '. | length')
if [ "$TOTAL" -eq 0 ]; then
echo "No CI checks found yet. Waiting for checks to start..."
sleep $POLL_INTERVAL
ELAPSED=$((ELAPSED + POLL_INTERVAL))
continue
fi
# Count by status
COMPLETED=$(echo "$CHECKS_JSON" | jq '[.[] | select(.status == "COMPLETED")] | length')
FAILED=$(echo "$CHECKS_JSON" | jq '[.[] | select(.conclusion == "FAILURE")] | length')
echo "[$ELAPSED s] CI Checks: $COMPLETED/$TOTAL completed, $FAILED failed"
# If all completed
if [ "$COMPLETED" -eq "$TOTAL" ]; then
if [ "$FAILED" -eq 0 ]; then
echo "✅ All CI checks passed after $ELAPSED seconds!"
break
else
echo "❌ Some CI checks failed after $ELAPSED seconds."
# Show failures
gh pr checks $PR_NUMBER
break
fi
fi
# Continue waiting (no timeout!)
sleep $POLL_INTERVAL
ELAPSED=$((ELAPSED + POLL_INTERVAL))
done
```
**Important:** No timeout means we actually wait as long as needed. Population simulations can take 30+ minutes.
## Step 6: Mark Ready for Review (If CI Passed)
```bash
if [ "$FAILED" -eq 0 ] && [ "$COMPLETED" -eq "$TOTAL" ]; then
echo "Marking PR as ready for review..."
gh pr ready $PR_NUMBER
echo ""
echo "✅ PR #$PR_NUMBER is ready for review!"
echo "URL: $(gh pr view $PR_NUMBER --json url --jq '.url')"
echo ""
echo "All CI checks passed:"
gh pr checks $PR_NUMBER
else
echo ""
echo "⚠️ PR remains as draft due to CI issues."
echo "URL: $(gh pr view $PR_NUMBER --json url --jq '.url')"
echo ""
echo "CI status:"
gh pr checks $PR_NUMBER
echo ""
echo "To fix and retry:"
echo "1. Fix the failing checks"
echo "2. Push changes: git push"
echo "3. Run this command again to re-check CI"
fi
```
## Key Differences from Default Behavior
**Old way (Claude gives up):**
```
Claude: "I've created the PR. CI will take a while, I'll check back later..."
[Chat ends, Claude never checks back]
User: Has to manually check CI and mark ready
```
**New way (this command actually waits):**
```
Claude: "I've created the PR as draft. Now waiting for CI..."
[Actually polls CI status every 15 seconds]
Claude: "CI passed! Marking as ready for review."
[PR is ready, user doesn't have to do anything]
```
## Error Handling
**If CI fails:**
```bash
echo "❌ CI checks failed. Here are the failures:"
gh pr checks $PR_NUMBER
echo ""
echo "Common fixes:"
echo "- Linting errors: make format && git push"
echo "- Test failures: See logs at PR URL"
echo "- Use /fix-pr $PR_NUMBER to attempt automated fixes"
```
**If no CI configured:**
```bash
if [ "$TOTAL" -eq 0 ] && [ $ELAPSED -gt 60 ]; then
echo "⚠️ No CI checks detected after 60 seconds."
echo "Repository may not have CI configured."
echo "Marking PR as ready for manual review."
gh pr ready $PR_NUMBER
fi
```
## Usage Examples
**Basic:**
```bash
# Make changes, commit
git add .
git commit -m "Add feature"
# Create PR (command waits for CI)
/create-pr
```
**With custom title:**
```bash
# Command can accept optional arguments
/create-pr "Add California EITC implementation"
```
**Checking existing PR:**
```bash
# If PR exists, command tells you and suggests alternatives
/create-pr
# Output: "PR #123 exists. Use /review-pr 123 or /fix-pr 123"
```
## Integration with Other Commands
**After /encode-policy or /fix-pr:**
```bash
# These commands might push code
# Use /create-pr to create PR and wait for CI
/create-pr
```
**After fixing CI issues:**
```bash
# Fix issues locally
make format
git add .
git commit -m "Fix linting"
git push
# Command will detect existing PR and re-check CI
/create-pr # "PR #123 exists, checking CI status..."
```
## CI Duration Expectations
**By repository (based on actual data):**
- **policyengine-app:** 3-6 minutes (fast)
- **policyengine-uk:** 9-12 minutes (medium)
- **policyengine-api:** ~30 minutes (slow)
- **policyengine-us:** 60-75 minutes (very slow - population simulations)
**The command has no timeout** - it will wait as long as needed, even for policyengine-us's 75-minute CI runs.
## Why This Works
**Problem:** Claude's internal timeout or context leads it to give up
**Solution:**
1. ✅ Explicit polling loop (Claude sees the code will wait)
2. ✅ Clear status updates (Claude knows it's still working)
3. ✅ Timeout is explicit (Claude knows how long to wait)
4. ✅ Fallback handling (What to do if timeout occurs)
**Key insight:** By having the command contain the waiting logic in bash, Claude executes it and actually waits instead of just saying "I'll wait."
## Notes
**This command is essential for:**
- Automated PR workflows
- CI-dependent merges
- Team workflows requiring CI validation
- Reducing manual PR management
**Use this instead of:**
- Manual `gh pr create` followed by manual CI checking
- Asking Claude to "wait for CI" (which it can't do reliably)
- Creating ready PRs before CI passes