-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaction.yml
More file actions
362 lines (313 loc) · 17.1 KB
/
action.yml
File metadata and controls
362 lines (313 loc) · 17.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
name: "Claude Code Automatic PR Documentation Generator"
description: "Automatically generate comprehensive documentation for merged PRs using Claude Code"
author: "TextCortex"
branding:
icon: "file-text"
color: "purple"
inputs:
anthropic_api_key:
description: "Anthropic API key (required for direct API, not needed for Bedrock/Vertex)"
required: false
min_lines_changed:
description: "Minimum number of lines changed to trigger documentation"
required: false
default: "50"
min_files_changed:
description: "Minimum number of files changed to trigger documentation"
required: false
default: "1"
documentation_prompt:
description: "Custom prompt for Claude to generate documentation"
required: false
default: |
You are helping to automatically document a merged PR. Create comprehensive documentation for the merged PR #${{ github.event.pull_request.number }}.
PR Title: ${{ github.event.pull_request.title }}
PR Description: ${{ github.event.pull_request.body }}
PR Author: ${{ github.event.pull_request.user.login }}
Merge Date: ${{ github.event.pull_request.merged_at }}
Instructions:
1. Run: git log --oneline -10 to see recent commits
2. Run: git diff ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} to see what changed in PR #${{ github.event.pull_request.number }}
3. Create directory if needed: mkdir -p docs/prs
4. Create a documentation file with filename format: docs/prs/YYYY-MM-DD-appropriate-title-for-the-pr.md
5. Include this YAML front matter at the top:
---
author: Claude
date: YYYY-MM-DD
title: [descriptive title based on PR]
pr_number: ${{ github.event.pull_request.number }}
pr_author: ${{ github.event.pull_request.user.login }}
---
6. Document:
- Purpose and context of the changes
- Key modifications made
- Technical implementation details
- Impact on the system
- Any breaking changes or migration notes
- Related issues or follow-up tasks
IMPORTANT: Just create and write the documentation file. Do NOT commit or create any branches/PRs - the workflow will handle that.
commit_tags:
description: "Tags to append to the commit message"
required: false
default: "[skip ci]"
pr_title_tags:
description: "Tags to append to the PR title"
required: false
default: "[skip ci]"
documentation_directory:
description: "Directory where documentation will be created"
required: false
default: "docs/prs"
timeout_minutes:
description: "Timeout for Claude to generate documentation (in minutes)"
required: false
default: "10"
github_token:
description: "GitHub token for Claude to operate with. Only include this if you're connecting a custom GitHub app of your own!"
required: false
default: ${{ github.token }}
model:
description: "Model to use (provider-specific format required for Bedrock/Vertex)"
required: false
use_bedrock:
description: "Use Amazon Bedrock with OIDC authentication instead of direct Anthropic API"
required: false
default: "false"
use_vertex:
description: "Use Google Vertex AI with OIDC authentication instead of direct Anthropic API"
required: false
default: "false"
allowed_tools:
description: "Additional tools for Claude to use (the base GitHub tools will always be included)"
required: false
default: ""
disallowed_tools:
description: "Tools that Claude should never use"
required: false
default: ""
custom_instructions:
description: "Additional custom instructions to include in the prompt for Claude"
required: false
default: ""
pr_labels:
description: "Comma-separated list of labels to apply to the PR (labels must already exist in the repository)"
required: false
default: ""
runs:
using: "composite"
steps:
- name: Check if PR should be documented
id: check_pr
shell: bash
run: |
# Skip if PR was created by Claude or any bot
if [[ "${{ github.event.pull_request.user.login }}" == "claude[bot]" ]] || \
[[ "${{ github.event.pull_request.user.login }}" == "claude-code[bot]" ]] || \
[[ "${{ github.event.pull_request.user.type }}" == "Bot" ]]; then
echo "skip_reason=PR created by bot" >> $GITHUB_OUTPUT
echo "should_skip=true" >> $GITHUB_OUTPUT
exit 0
fi
# Skip if PR title indicates it's a documentation PR
if [[ "${{ github.event.pull_request.title }}" == "docs: Add documentation for PR"* ]]; then
echo "skip_reason=PR is already a documentation PR" >> $GITHUB_OUTPUT
echo "should_skip=true" >> $GITHUB_OUTPUT
exit 0
fi
# Skip if PR has 'automated' or 'documentation' label
if [[ "${{ contains(github.event.pull_request.labels.*.name, 'automated') }}" == "true" ]] || \
[[ "${{ contains(github.event.pull_request.labels.*.name, 'documentation') }}" == "true" ]]; then
echo "skip_reason=PR has automated or documentation label" >> $GITHUB_OUTPUT
echo "should_skip=true" >> $GITHUB_OUTPUT
exit 0
fi
echo "should_skip=false" >> $GITHUB_OUTPUT
- name: Log skip reason
if: steps.check_pr.outputs.should_skip == 'true'
shell: bash
run: |
echo "⚠️ Skipping documentation generation: ${{ steps.check_pr.outputs.skip_reason }}"
echo "This prevents infinite loops when documenting PRs created by bots or documentation PRs themselves."
- name: Checkout repository
if: steps.check_pr.outputs.should_skip != 'true'
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ inputs.github_token }}
- name: Fetch PR commits
if: steps.check_pr.outputs.should_skip != 'true'
shell: bash
run: |
# For merged PRs, the PR refs might not exist anymore, so we need to be more careful
echo "Fetching commits for PR #${{ github.event.pull_request.number }}"
# Try to fetch the PR ref if it exists (might not for merged PRs)
git fetch origin +refs/pull/${{ github.event.pull_request.number }}/merge:refs/remotes/origin/pr/${{ github.event.pull_request.number }} 2>/dev/null || true
# Fetch the base and head commits directly
echo "Fetching base commit: ${{ github.event.pull_request.base.sha }}"
git fetch origin ${{ github.event.pull_request.base.sha }} 2>/dev/null || true
echo "Fetching head commit: ${{ github.event.pull_request.head.sha }}"
git fetch origin ${{ github.event.pull_request.head.sha }} 2>/dev/null || true
# For merged PRs, also try to fetch from the merge commit
if [ "${{ github.event.pull_request.merged }}" == "true" ] && [ -n "${{ github.event.pull_request.merge_commit_sha }}" ]; then
echo "Fetching merge commit: ${{ github.event.pull_request.merge_commit_sha }}"
git fetch origin ${{ github.event.pull_request.merge_commit_sha }} 2>/dev/null || true
fi
# Ensure we have the latest from the base branch
echo "Fetching latest from base branch: ${{ github.event.pull_request.base.ref }}"
git fetch origin ${{ github.event.pull_request.base.ref }}:refs/remotes/origin/${{ github.event.pull_request.base.ref }} || true
- name: Check if documentation should be generated
if: steps.check_pr.outputs.should_skip != 'true'
id: check_changes
shell: bash
run: |
# Get PR stats - try different approaches to ensure we can get the diff
BASE_SHA="${{ github.event.pull_request.base.sha }}"
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
MERGE_SHA="${{ github.event.pull_request.merge_commit_sha }}"
# For merged PRs, we can use the merge commit to get accurate stats
if [ "${{ github.event.pull_request.merged }}" == "true" ] && [ -n "$MERGE_SHA" ] && git rev-parse --verify "$MERGE_SHA" >/dev/null 2>&1; then
echo "Using merge commit for diff: $MERGE_SHA"
# Get the parent of the merge commit (should be the base branch state)
MERGE_PARENT=$(git rev-parse ${MERGE_SHA}^1 2>/dev/null || echo "")
if [ -n "$MERGE_PARENT" ]; then
LINES_CHANGED=$(git diff --shortstat ${MERGE_PARENT}...${MERGE_SHA} | awk '{ins=0; del=0; for(i=1;i<=NF;i++){if($i ~ /insertion/) ins=$(i-1); if($i ~ /deletion/) del=$(i-1)} print ins+del}' || echo "0")
FILES_CHANGED=$(git diff --name-only ${MERGE_PARENT}...${MERGE_SHA} | wc -l | tr -d ' ')
else
# Fallback to comparing with base
LINES_CHANGED=$(git diff --shortstat origin/${{ github.event.pull_request.base.ref }}...${MERGE_SHA} | awk '{ins=0; del=0; for(i=1;i<=NF;i++){if($i ~ /insertion/) ins=$(i-1); if($i ~ /deletion/) del=$(i-1)} print ins+del}' || echo "0")
FILES_CHANGED=$(git diff --name-only origin/${{ github.event.pull_request.base.ref }}...${MERGE_SHA} | wc -l | tr -d ' ')
fi
elif git rev-parse --verify "$BASE_SHA" >/dev/null 2>&1 && git rev-parse --verify "$HEAD_SHA" >/dev/null 2>&1; then
# Both commits exist, use them directly
echo "Using base and head commits for diff"
LINES_CHANGED=$(git diff --shortstat ${BASE_SHA}...${HEAD_SHA} | awk '{ins=0; del=0; for(i=1;i<=NF;i++){if($i ~ /insertion/) ins=$(i-1); if($i ~ /deletion/) del=$(i-1)} print ins+del}' || echo "0")
FILES_CHANGED=$(git diff --name-only ${BASE_SHA}...${HEAD_SHA} | wc -l | tr -d ' ')
else
# Fallback: try to use merge base
echo "Warning: Could not find exact commits, using merge-base approach"
MERGE_BASE=$(git merge-base origin/${{ github.event.pull_request.base.ref }} HEAD || echo "")
if [ -n "$MERGE_BASE" ]; then
LINES_CHANGED=$(git diff --shortstat ${MERGE_BASE}...HEAD | awk '{ins=0; del=0; for(i=1;i<=NF;i++){if($i ~ /insertion/) ins=$(i-1); if($i ~ /deletion/) del=$(i-1)} print ins+del}' || echo "0")
FILES_CHANGED=$(git diff --name-only ${MERGE_BASE}...HEAD | wc -l | tr -d ' ')
else
# Last resort: just check current changes
echo "Warning: Using simplified diff check"
LINES_CHANGED=$(git diff --shortstat origin/${{ github.event.pull_request.base.ref }}...HEAD | awk '{ins=0; del=0; for(i=1;i<=NF;i++){if($i ~ /insertion/) ins=$(i-1); if($i ~ /deletion/) del=$(i-1)} print ins+del}' || echo "0")
FILES_CHANGED=$(git diff --name-only origin/${{ github.event.pull_request.base.ref }}...HEAD | wc -l | tr -d ' ')
fi
fi
echo "Lines changed: $LINES_CHANGED"
echo "Files changed: $FILES_CHANGED"
echo "lines_changed=$LINES_CHANGED" >> $GITHUB_OUTPUT
echo "files_changed=$FILES_CHANGED" >> $GITHUB_OUTPUT
if [ "$LINES_CHANGED" -ge "${{ inputs.min_lines_changed }}" ] && [ "$FILES_CHANGED" -ge "${{ inputs.min_files_changed }}" ]; then
echo "should_document=true" >> $GITHUB_OUTPUT
echo "Documentation will be generated (lines: $LINES_CHANGED >= ${{ inputs.min_lines_changed }}, files: $FILES_CHANGED >= ${{ inputs.min_files_changed }})"
else
echo "should_document=false" >> $GITHUB_OUTPUT
echo "Skipping documentation (lines: $LINES_CHANGED < ${{ inputs.min_lines_changed }} or files: $FILES_CHANGED < ${{ inputs.min_files_changed }})"
fi
- name: Set up git configuration
if: steps.check_pr.outputs.should_skip != 'true' && steps.check_changes.outputs.should_document == 'true'
shell: bash
run: |
git config --global user.name "Claude Code[bot]"
git config --global user.email "noreply@anthropic.com"
# Use timestamp to create unique branch names for each run
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BRANCH_NAME="docs/pr-${{ github.event.pull_request.number }}-${TIMESTAMP}"
echo "DOCS_BRANCH=$BRANCH_NAME" >> $GITHUB_ENV
- name: Prepare documentation prompt
if: steps.check_pr.outputs.should_skip != 'true' && steps.check_changes.outputs.should_document == 'true'
id: prepare_prompt
shell: bash
run: |
# Replace placeholder directory paths in the default prompt
PROMPT="${{ inputs.documentation_prompt }}"
PROMPT="${PROMPT//docs\/prs/${{ inputs.documentation_directory }}}"
# Also prepare the git diff command based on available commits
BASE_SHA="${{ github.event.pull_request.base.sha }}"
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
MERGE_SHA="${{ github.event.pull_request.merge_commit_sha }}"
# For merged PRs, prefer using the merge commit
if [ "${{ github.event.pull_request.merged }}" == "true" ] && [ -n "$MERGE_SHA" ] && git rev-parse --verify "$MERGE_SHA" >/dev/null 2>&1; then
MERGE_PARENT=$(git rev-parse ${MERGE_SHA}^1 2>/dev/null || echo "")
if [ -n "$MERGE_PARENT" ]; then
DIFF_CMD="git diff ${MERGE_PARENT}...${MERGE_SHA}"
else
DIFF_CMD="git diff origin/${{ github.event.pull_request.base.ref }}...${MERGE_SHA}"
fi
elif git rev-parse --verify "$BASE_SHA" >/dev/null 2>&1 && git rev-parse --verify "$HEAD_SHA" >/dev/null 2>&1; then
DIFF_CMD="git diff ${BASE_SHA}...${HEAD_SHA}"
else
MERGE_BASE=$(git merge-base origin/${{ github.event.pull_request.base.ref }} HEAD || echo "")
if [ -n "$MERGE_BASE" ]; then
DIFF_CMD="git diff ${MERGE_BASE}...HEAD"
else
DIFF_CMD="git diff origin/${{ github.event.pull_request.base.ref }}...HEAD"
fi
fi
echo "diff_command=$DIFF_CMD" >> $GITHUB_OUTPUT
# Save to output
echo "prompt<<EOF" >> $GITHUB_OUTPUT
echo "$PROMPT" >> $GITHUB_OUTPUT
echo "${{ inputs.custom_instructions }}" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Generate PR Documentation with Claude Code
if: steps.check_pr.outputs.should_skip != 'true' && steps.check_changes.outputs.should_document == 'true'
uses: anthropics/claude-code-action@beta
with:
anthropic_api_key: ${{ inputs.anthropic_api_key }}
direct_prompt: ${{ steps.prepare_prompt.outputs.prompt }}
timeout_minutes: ${{ inputs.timeout_minutes }}
model: ${{ inputs.model }}
use_bedrock: ${{ inputs.use_bedrock }}
use_vertex: ${{ inputs.use_vertex }}
allowed_tools: "Bash(git status),Bash(git log --oneline -10),Bash(${{ steps.prepare_prompt.outputs.diff_command }}),Bash(git branch --show-current),Bash(mkdir -p ${{ inputs.documentation_directory }}),Write,Read,Glob,Grep,Edit,${{ inputs.allowed_tools }}"
disallowed_tools: ${{ inputs.disallowed_tools }}
- name: Clean up unwanted files
if: steps.check_pr.outputs.should_skip != 'true' && steps.check_changes.outputs.should_document == 'true'
shell: bash
run: |
# Remove any output.txt or other temporary files that might have been created
rm -f output.txt
rm -f *.tmp
rm -f *.log
# Only keep files in documentation directory that were intentionally created
git clean -fd --exclude=${{ inputs.documentation_directory }}/
- name: Stage documentation files
if: steps.check_pr.outputs.should_skip != 'true' && steps.check_changes.outputs.should_document == 'true'
shell: bash
run: |
# Check if any documentation files were created
if [ -n "$(git status --porcelain ${{ inputs.documentation_directory }}/)" ]; then
git add ${{ inputs.documentation_directory }}/*.md
echo "Documentation files staged for commit"
else
echo "No documentation files created"
exit 1
fi
- name: Create Pull Request
if: steps.check_pr.outputs.should_skip != 'true' && steps.check_changes.outputs.should_document == 'true'
uses: peter-evans/create-pull-request@v6
with:
token: ${{ inputs.github_token }}
branch: ${{ env.DOCS_BRANCH }}
base: ${{ github.event.pull_request.base.ref }}
commit-message: "docs: Add documentation for PR #${{ github.event.pull_request.number }} ${{ inputs.commit_tags }}"
committer: "Claude Code[bot] <noreply@anthropic.com>"
author: "Claude Code[bot] <noreply@anthropic.com>"
title: "docs: Add documentation for PR #${{ github.event.pull_request.number }} ${{ inputs.pr_title_tags }}"
body: |
This PR adds auto-generated documentation for PR #${{ github.event.pull_request.number }}.
## Original PR Details
- **Title:** ${{ github.event.pull_request.title }}
- **Author:** @${{ github.event.pull_request.user.login }}
- **Merged:** ${{ github.event.pull_request.merged_at }}
- **Lines Changed:** ${{ steps.check_changes.outputs.lines_changed }}
- **Files Changed:** ${{ steps.check_changes.outputs.files_changed }}
---
---
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
labels: ${{ inputs.pr_labels }}