Skip to content

Commit 665bad0

Browse files
committed
Add Claude AI workflows for automated code review and PR management
Adopted from singularity-language-registry and adapted for Elixir/Nix workflow. Added three workflows: 1. **claude-review.yml** - Automated PR review - Auto-reviews PRs when opened/updated - Scope checks for stale/irrelevant files - Adapted file patterns for Elixir (lib/, test/, mix.exs, etc.) - Auto-approves + enables auto-merge if checks pass - Reviews focus on Elixir quality, security, tests, docs 2. **claude.yml** - Interactive Claude assistant - Triggers on @claude mentions in issues/PRs/comments - Provides code assistance on-demand - Full repository access for deep analysis 3. **auto-pr.yml** - Auto-create PRs from feature branches - Triggers on push to feat/*, fix/*, chore/*, docs/*, claude/* - Generates comprehensive PR description with: * Commit history * File changes * Line stats * Elixir-specific checklist (mix test, format, credo) - Only creates PR if one doesn't exist Key adaptations for Elixir: - File patterns: lib/, test/, mix.exs, .formatter.exs, .credo.exs - Exclude Elixir build artifacts: _build/, deps/, *.beam - Testing checklist: mix test, mix format, mix credo, mix dialyzer - Focus on library package development These workflows enable: ✅ Automated code review with AI ✅ Auto-merge of approved PRs ✅ On-demand Claude assistance ✅ Automatic PR creation from feature branches
1 parent 9593ed3 commit 665bad0

3 files changed

Lines changed: 395 additions & 0 deletions

File tree

.github/workflows/auto-pr.yml

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
name: Auto PR Creation
2+
3+
on:
4+
push:
5+
branches:
6+
# Only create PRs from feature branches
7+
- 'feat/**'
8+
- 'feature/**'
9+
- 'fix/**'
10+
- 'chore/**'
11+
- 'refactor/**'
12+
- 'docs/**'
13+
- 'test/**'
14+
- 'claude/**'
15+
16+
permissions:
17+
contents: read
18+
pull-requests: write
19+
20+
jobs:
21+
auto-create-pr:
22+
name: Auto Create PR with AI Description
23+
runs-on: ubuntu-latest
24+
# Only run if not already on main
25+
if: github.ref != 'refs/heads/main'
26+
27+
steps:
28+
- name: Checkout code
29+
uses: actions/checkout@v4
30+
with:
31+
fetch-depth: 0 # Get full history for better PR description
32+
33+
- name: Check if PR already exists
34+
id: check-pr
35+
env:
36+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
37+
run: |
38+
# Get current branch name
39+
BRANCH_NAME="${GITHUB_REF#refs/heads/}"
40+
echo "branch=$BRANCH_NAME" >> $GITHUB_OUTPUT
41+
42+
# Check if PR exists for this branch
43+
PR_EXISTS=$(gh pr list --head "$BRANCH_NAME" --base main --json number --jq 'length')
44+
echo "exists=$PR_EXISTS" >> $GITHUB_OUTPUT
45+
46+
if [ "$PR_EXISTS" -gt 0 ]; then
47+
PR_NUMBER=$(gh pr list --head "$BRANCH_NAME" --base main --json number --jq '.[0].number')
48+
echo "number=$PR_NUMBER" >> $GITHUB_OUTPUT
49+
echo "PR #$PR_NUMBER already exists for branch $BRANCH_NAME"
50+
else
51+
echo "No PR exists for branch $BRANCH_NAME"
52+
fi
53+
54+
- name: Generate AI PR description
55+
id: ai-description
56+
if: steps.check-pr.outputs.exists == '0'
57+
env:
58+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
59+
run: |
60+
BRANCH_NAME="${{ steps.check-pr.outputs.branch }}"
61+
62+
# Create PR description
63+
echo "## Summary" > pr_description.md
64+
echo "Changes from branch: \`$BRANCH_NAME\`" >> pr_description.md
65+
echo "" >> pr_description.md
66+
67+
echo "## Commits" >> pr_description.md
68+
git log origin/main..HEAD --pretty=format:"- %s (%h)" >> pr_description.md
69+
echo "" >> pr_description.md
70+
echo "" >> pr_description.md
71+
72+
echo "## Modified Files" >> pr_description.md
73+
git diff origin/main...HEAD --name-status | head -30 >> pr_description.md
74+
echo "" >> pr_description.md
75+
echo "" >> pr_description.md
76+
77+
echo "## Changed Lines" >> pr_description.md
78+
git diff origin/main...HEAD --stat >> pr_description.md
79+
echo "" >> pr_description.md
80+
echo "" >> pr_description.md
81+
82+
echo "## Testing Checklist" >> pr_description.md
83+
echo "- [ ] Tests pass locally (\`mix test\`)" >> pr_description.md
84+
echo "- [ ] CI checks pass (test, quality, coverage)" >> pr_description.md
85+
echo "- [ ] Code formatted (\`mix format\`)" >> pr_description.md
86+
echo "- [ ] Credo passes (\`mix credo --strict\`)" >> pr_description.md
87+
echo "- [ ] Documentation updated if needed" >> pr_description.md
88+
echo "- [ ] No security vulnerabilities introduced" >> pr_description.md
89+
echo "" >> pr_description.md
90+
91+
echo "---" >> pr_description.md
92+
echo "*Auto-generated PR - will be reviewed by Claude AI*" >> pr_description.md
93+
94+
# Save description for next step
95+
echo "description_file=pr_description.md" >> $GITHUB_OUTPUT
96+
97+
- name: Create Pull Request
98+
if: steps.check-pr.outputs.exists == '0'
99+
env:
100+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
101+
run: |
102+
BRANCH_NAME="${{ steps.check-pr.outputs.branch }}"
103+
104+
# Extract title from first commit or branch name
105+
TITLE=$(git log origin/main..HEAD --pretty=format:"%s" | head -1)
106+
if [ -z "$TITLE" ]; then
107+
TITLE="Changes from $BRANCH_NAME"
108+
fi
109+
110+
# Create PR with AI-generated description
111+
gh pr create \
112+
--title "$TITLE" \
113+
--body-file pr_description.md \
114+
--base main \
115+
--head "$BRANCH_NAME"
116+
117+
echo "✅ Pull Request created successfully!"
118+
119+
- name: Update existing PR
120+
if: steps.check-pr.outputs.exists != '0'
121+
env:
122+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
123+
run: |
124+
PR_NUMBER="${{ steps.check-pr.outputs.number }}"
125+
echo "✅ PR #$PR_NUMBER already exists - no action needed"
126+
echo "New commits will automatically update the existing PR"
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
name: Claude AI Review
2+
3+
on:
4+
issue_comment:
5+
types: [created]
6+
pull_request:
7+
types: [opened, synchronize]
8+
branches: [main]
9+
pull_request_review_comment:
10+
types: [created]
11+
12+
permissions:
13+
contents: write
14+
pull-requests: write
15+
issues: write
16+
17+
jobs:
18+
claude-review:
19+
name: Claude Auto Review
20+
runs-on: ubuntu-latest
21+
if: github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'main'
22+
23+
steps:
24+
- name: Checkout PR
25+
uses: actions/checkout@v4
26+
with:
27+
fetch-depth: 0
28+
29+
- name: Get changed files
30+
id: changed-files
31+
uses: tj-actions/changed-files@v44
32+
with:
33+
separator: ","
34+
35+
- name: Check for stale/out-of-scope files
36+
id: scope-check
37+
run: |
38+
echo "🔍 Checking for stale files and out-of-scope changes..." > scope_check.md
39+
echo "" >> scope_check.md
40+
41+
SCOPE_ISSUES=0
42+
CHANGED_FILES="${{ steps.changed-files.outputs.all_changed_files }}"
43+
44+
echo "### Stale File Check" >> scope_check.md
45+
for file in $CHANGED_FILES; do
46+
if [ -f "$file" ]; then
47+
FILE_AGE=$(git log -1 --format="%cr" origin/main -- "$file" 2>/dev/null || echo "new file")
48+
FILE_DATE=$(git log -1 --format="%ci" origin/main -- "$file" 2>/dev/null || echo "")
49+
50+
if [ -n "$FILE_DATE" ]; then
51+
YEARS_OLD=$(( ($(date +%s) - $(date -d "$FILE_DATE" +%s)) / 31536000 ))
52+
if [ $YEARS_OLD -gt 2 ]; then
53+
echo "⚠️ **WARNING**: \`$file\` is $YEARS_OLD years old (last modified: $FILE_AGE)" >> scope_check.md
54+
SCOPE_ISSUES=$((SCOPE_ISSUES + 1))
55+
fi
56+
fi
57+
fi
58+
done
59+
60+
if [ $SCOPE_ISSUES -eq 0 ]; then
61+
echo "✅ No stale files detected" >> scope_check.md
62+
fi
63+
echo "" >> scope_check.md
64+
65+
echo "### Scope Check" >> scope_check.md
66+
echo "Checking file relevance (blocks binaries, temp files, etc.)..." >> scope_check.md
67+
echo "" >> scope_check.md
68+
69+
RELEVANT_PATTERNS=(
70+
"lib/"
71+
"test/"
72+
"config/"
73+
"priv/"
74+
"mix.exs"
75+
"mix.lock"
76+
".github/"
77+
".githooks/"
78+
"flake.nix"
79+
"flake.lock"
80+
".formatter.exs"
81+
".credo.exs"
82+
"README.md"
83+
"CHANGELOG.md"
84+
"LICENSE"
85+
"CONTRIBUTING.md"
86+
"*.md"
87+
"scripts/"
88+
".envrc"
89+
"renovate.json5"
90+
)
91+
92+
OUT_OF_SCOPE_FILES=()
93+
for file in $CHANGED_FILES; do
94+
MATCHES_PATTERN=false
95+
for pattern in "${RELEVANT_PATTERNS[@]}"; do
96+
if [[ "$file" == $pattern ]] || [[ "$file" == *"$pattern"* ]]; then
97+
MATCHES_PATTERN=true
98+
break
99+
fi
100+
done
101+
102+
if [ "$MATCHES_PATTERN" = false ]; then
103+
case "$file" in
104+
*.exe|*.dll|*.so|*.dylib|*.bin|*.dat|*.tmp|*.log|*.beam|node_modules/*|.DS_Store|thumbs.db|_build/*|deps/*)
105+
echo "❌ **OUT OF SCOPE**: \`$file\` - Binary/temp/build file not relevant to library" >> scope_check.md
106+
OUT_OF_SCOPE_FILES+=("$file")
107+
SCOPE_ISSUES=$((SCOPE_ISSUES + 1))
108+
;;
109+
*.jpg|*.png|*.gif|*.mp4|*.avi|*.mov)
110+
if [[ ! "$file" =~ ^docs/ ]] && [[ ! "$file" =~ ^assets/ ]]; then
111+
echo "⚠️ **REVIEW NEEDED**: \`$file\` - Media file, ensure it's for documentation" >> scope_check.md
112+
fi
113+
;;
114+
esac
115+
fi
116+
done
117+
118+
if [ ${#OUT_OF_SCOPE_FILES[@]} -eq 0 ]; then
119+
echo "✅ All changes appear relevant (includes .github/ workflows, lib/, test/, config)" >> scope_check.md
120+
fi
121+
echo "" >> scope_check.md
122+
123+
GITHUB_FILES=$(echo "$CHANGED_FILES" | tr ' ' '\n' | grep -c "^.github/" || true)
124+
if [ "$GITHUB_FILES" -gt 0 ]; then
125+
echo "ℹ️ **Note**: $GITHUB_FILES .github/ file(s) changed - workflows/actions are critical infrastructure" >> scope_check.md
126+
echo "" >> scope_check.md
127+
fi
128+
129+
echo "SCOPE_ISSUES=$SCOPE_ISSUES" >> $GITHUB_OUTPUT
130+
cat scope_check.md
131+
132+
- name: Claude Code Review
133+
id: claude-review
134+
uses: anthropics/claude-code-action@v1
135+
with:
136+
github_token: ${{ secrets.GITHUB_TOKEN }}
137+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
138+
show_full_output: true
139+
prompt: |
140+
Review this PR for an Elixir workflow orchestration library (Singularity.Workflow).
141+
142+
Focus on:
143+
1. Elixir code quality (mix format, credo checks)
144+
2. Security vulnerabilities (SQL injection, XSS, etc.)
145+
3. Test coverage (ExUnit tests)
146+
4. Documentation completeness (@doc, @moduledoc)
147+
5. Whether changes are appropriate for a library package
148+
149+
Scope check results:
150+
$(cat scope_check.md 2>/dev/null || echo "No scope issues")
151+
152+
Provide a concise review. If the code looks good, say "LGTM - auto-approving".
153+
If there are issues, list them clearly.
154+
claude_args: "--max-turns 3 --model sonnet"
155+
156+
- name: Determine auto-approve
157+
id: claude-analysis
158+
run: |
159+
SCOPE_ISSUES=${{ steps.scope-check.outputs.SCOPE_ISSUES }}
160+
161+
if [ $SCOPE_ISSUES -eq 0 ]; then
162+
echo "AUTO_APPROVE=true" >> $GITHUB_OUTPUT
163+
echo "✅ No scope issues detected - eligible for auto-approve"
164+
else
165+
echo "AUTO_APPROVE=false" >> $GITHUB_OUTPUT
166+
echo "⚠️ $SCOPE_ISSUES scope issue(s) found - human review required"
167+
fi
168+
169+
- name: Post scope check results
170+
uses: actions/github-script@v7
171+
with:
172+
script: |
173+
const fs = require('fs');
174+
let comment_body = '## 🔍 Automated Checks\n\n';
175+
176+
try {
177+
const scope_check = fs.readFileSync('scope_check.md', 'utf8');
178+
comment_body += scope_check;
179+
} catch (error) {
180+
comment_body += '✅ Scope check passed\n';
181+
}
182+
183+
comment_body += '\n---\n';
184+
comment_body += '*Claude is reviewing the code... Check the "Claude Code Review" step for detailed feedback.*\n';
185+
186+
const pr_number = context.payload.pull_request.number;
187+
188+
await github.rest.issues.createComment({
189+
owner: context.repo.owner,
190+
repo: context.repo.repo,
191+
issue_number: pr_number,
192+
body: comment_body
193+
});
194+
195+
- name: Set review status
196+
id: review-status
197+
uses: actions/github-script@v7
198+
env:
199+
AUTO_APPROVE: ${{ steps.claude-analysis.outputs.AUTO_APPROVE }}
200+
with:
201+
github-token: ${{ secrets.ORG_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
202+
script: |
203+
const pr_number = context.payload.pull_request.number;
204+
const auto_approve = process.env.AUTO_APPROVE === 'true';
205+
206+
await github.rest.pulls.createReview({
207+
owner: context.repo.owner,
208+
repo: context.repo.repo,
209+
pull_number: pr_number,
210+
body: auto_approve
211+
? '✅ Claude AI approved this PR! All checks passed. Will auto-merge when CI is green.'
212+
: '⚠️ Claude AI review found issues. Human review required.',
213+
event: auto_approve ? 'APPROVE' : 'COMMENT'
214+
});
215+
216+
return { auto_approve };
217+
218+
- name: Enable auto-merge
219+
if: steps.claude-analysis.outputs.AUTO_APPROVE == 'true'
220+
env:
221+
GH_TOKEN: ${{ secrets.ORG_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
222+
run: |
223+
PR_NUMBER="${{ github.event.pull_request.number }}"
224+
225+
gh pr merge $PR_NUMBER --auto --squash || echo "Auto-merge may already be enabled or not allowed"
226+
227+
echo "✅ Auto-merge enabled! PR will merge automatically when:"
228+
echo " - All required checks pass (test, quality, coverage)"
229+
echo " - All conversations are resolved"
230+
echo " - Branch is up to date with main"

.github/workflows/claude.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Claude Code
2+
3+
on:
4+
issue_comment:
5+
types: [created]
6+
pull_request_review_comment:
7+
types: [created]
8+
issues:
9+
types: [opened, assigned]
10+
pull_request_review:
11+
types: [submitted]
12+
13+
jobs:
14+
claude:
15+
if: |
16+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
17+
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
18+
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
19+
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
20+
runs-on: ubuntu-latest
21+
permissions:
22+
contents: read
23+
pull-requests: read
24+
issues: read
25+
id-token: write
26+
actions: read
27+
steps:
28+
- name: Checkout repository
29+
uses: actions/checkout@v4
30+
with:
31+
fetch-depth: 1
32+
33+
- name: Run Claude Code
34+
id: claude
35+
uses: anthropics/claude-code-action@v1
36+
with:
37+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
38+
additional_permissions: |
39+
actions: read

0 commit comments

Comments
 (0)