Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 18 additions & 109 deletions .github/workflows/claude-documentation-fixer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
github.event.issue.pull_request &&
contains(github.event.comment.body, '@claude')
permissions:
contents: read
contents: write
pull-requests: write
issues: write
id-token: write
Expand All @@ -23,124 +23,33 @@
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_DATA=$(gh pr view ${{ github.event.issue.number }} --repo ${{ github.repository }} --json headRefOid)
echo "sha=$(echo "$PR_DATA" | jq -r '.headRefOid')" >> "$GITHUB_OUTPUT"
PR_DATA=$(gh pr view ${{ github.event.issue.number }} --repo ${{ github.repository }} --json headRefName,isCrossRepository)
echo "branch=$(echo "$PR_DATA" | jq -r '.headRefName')" >> "$GITHUB_OUTPUT"
echo "is_fork=$(echo "$PR_DATA" | jq -r '.isCrossRepository')" >> "$GITHUB_OUTPUT"

- name: Post fork notice
if: steps.pr-info.outputs.is_fork == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr comment ${{ github.event.issue.number }} --repo ${{ github.repository }} \
--body "This PR is from a fork. Automated fixes cannot be pushed directly. Apply the inline suggestions from the review manually, or use GitHub's batch feature to commit them all at once."

- name: Checkout repository
if: steps.pr-info.outputs.is_fork == 'false'
uses: actions/checkout@v4
with:
# Check out the branch by name so git push works.
ref: ${{ steps.pr-info.outputs.branch }}
fetch-depth: 0

- name: Fetch PR head
run: |
git fetch origin pull/${{ github.event.issue.number }}/head:pr-fix-branch
git checkout pr-fix-branch

- name: Generate fixes
- name: Apply fixes
if: steps.pr-info.outputs.is_fork == 'false'
uses: anthropics/claude-code-action@v1

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow

Unpinned 3rd party Action 'Documentation Fixer' step [Uses Step](1) uses 'anthropics/claude-code-action' with ref 'v1', not a pinned commit hash
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
show_full_output: true
prompt: |
${{ github.event.comment.body }}

Apply the requested fixes to the documentation files. Edit files directly using the Write and Edit tools.
Do NOT run git commit, git push, or git add. Only edit the files.
claude_args: |
--model claude-sonnet-4-5-20250929
--allowedTools "Read,Write,Edit,Bash(gh pr view:*),Bash(gh pr diff:*),Bash(git status:*),Bash(git diff:*)"

- name: Post inline suggestions
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.issue.number }}
HEAD_SHA: ${{ steps.pr-info.outputs.sha }}
REPO: ${{ github.repository }}
run: |
python3 << 'PYEOF'
import subprocess, json, os, re, sys

def parse_diff(diff_text):
suggestions = []
current_file = None
lines = diff_text.split('\n')
i = 0
while i < len(lines):
line = lines[i]
if line.startswith('diff --git'):
i += 1
continue
elif line.startswith('--- a/'):
current_file = line[6:]
i += 1
continue
elif line.startswith('+++ b/'):
i += 1
continue
elif line.startswith('@@ '):
m = re.match(r'@@ -(\d+)(?:,\d+)? \+\d+(?:,\d+)? @@', line)
if not m:
i += 1
continue
old_line = int(m.group(1))
i += 1
while i < len(lines) and not lines[i].startswith('@@') and not lines[i].startswith('diff '):
if lines[i].startswith('-') or lines[i].startswith('+'):
removed, added = [], []
start = old_line
while i < len(lines) and (lines[i].startswith('-') or lines[i].startswith('+')):
if lines[i].startswith('-'):
removed.append(lines[i][1:])
old_line += 1
else:
added.append(lines[i][1:])
i += 1
if removed:
suggestions.append({'file': current_file, 'start': start, 'end': old_line - 1, 'new': added})
elif lines[i].startswith(' '):
old_line += 1
i += 1
else:
i += 1
else:
i += 1
return suggestions

diff = subprocess.run(['git', 'diff'], capture_output=True, text=True).stdout
if not diff.strip():
print("No changes to suggest")
sys.exit(0)

suggestions = parse_diff(diff)
if not suggestions:
print("No line-replacement suggestions to post")
sys.exit(0)

comments = []
for s in suggestions:
body = '```suggestion\n' + '\n'.join(s['new']) + '\n```'
c = {'path': s['file'], 'line': s['end'], 'side': 'RIGHT', 'body': body}
if s['start'] != s['end']:
c['start_line'] = s['start']
c['start_side'] = 'RIGHT'
comments.append(c)

review_data = {
'commit_id': os.environ['HEAD_SHA'],
'body': f"Here are the suggested fixes ({len(comments)} suggestion(s)). Click 'Apply suggestion' on each one to accept it.",
'event': 'COMMENT',
'comments': comments
}

r = subprocess.run(
['gh', 'api', f"repos/{os.environ['REPO']}/pulls/{os.environ['PR_NUMBER']}/reviews",
'--method', 'POST', '--input', '-'],
input=json.dumps(review_data).encode(),
capture_output=True
)
if r.returncode != 0:
print(f"Error posting suggestions: {r.stderr.decode()}")
sys.exit(1)
print(f"Posted {len(comments)} inline suggestion(s)")
PYEOF
--allowedTools "Read,Write,Edit,Bash(gh pr view:*),Bash(gh pr diff:*),Bash(gh pr comment:*),Bash(git config:*),Bash(git add:*),Bash(git commit:*),Bash(git push:*),Bash(git status:*),Bash(git diff:*)"
61 changes: 55 additions & 6 deletions .github/workflows/claude-documentation-reviewer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,68 @@ jobs:

Do not review or comment on any other files (e.g., .js, .ts, .json, etc.). Focus exclusively on the documentation changes in the markdown files listed above.

Write your complete review to /tmp/review.md.
Write your findings to /tmp/suggestions.json following the format in your instructions.

claude_args: |
--model claude-sonnet-4-5-20250929
--allowedTools "Write,Bash(gh pr diff:*),Bash(gh pr view:*)"
--append-system-prompt "${{ steps.read-prompt.outputs.prompt }}"

- name: Append closing lines and post review
- name: Post inline suggestions
if: steps.changed-files.outputs.count > 0
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
REPO: ${{ github.repository }}
run: |
echo "" >> /tmp/review.md
echo "To apply fixes, reply with \`@claude\` followed by your instructions (e.g. \`@claude fix all issues\` or \`@claude fix only the spelling errors\`)." >> /tmp/review.md
echo "Note: fixes are posted as inline suggestions. Click 'Apply suggestion' on each one to accept it." >> /tmp/review.md
gh pr comment ${{ github.event.pull_request.number }} --body-file /tmp/review.md
python3 << 'PYEOF'
import subprocess, json, os, sys

pr_number = os.environ['PR_NUMBER']
head_sha = os.environ['HEAD_SHA']
repo = os.environ['REPO']

try:
with open('/tmp/suggestions.json') as f:
suggestions = json.load(f)
except (FileNotFoundError, json.JSONDecodeError) as e:
print(f"Could not read /tmp/suggestions.json: {e}")
sys.exit(1)

if not suggestions:
subprocess.run(
['gh', 'pr', 'comment', pr_number, '--repo', repo,
'--body', 'No issues found in the changed files.'],
check=True
)
print("No issues found")
sys.exit(0)

comments = []
for s in suggestions:
body = s['body'] + '\n```suggestion\n' + s['suggestion'] + '\n```'
c = {'path': s['path'], 'line': s['line'], 'side': 'RIGHT', 'body': body}
if s.get('start_line') and s['start_line'] != s['line']:
c['start_line'] = s['start_line']
c['start_side'] = 'RIGHT'
comments.append(c)

review_data = {
'commit_id': head_sha,
'body': f'Found {len(comments)} issue(s). To apply all fixes at once, reply with `@claude` followed by your instructions (e.g. `@claude fix all issues`).',
'event': 'COMMENT',
'comments': comments
}

r = subprocess.run(
['gh', 'api', f'repos/{repo}/pulls/{pr_number}/reviews',
'--method', 'POST', '--input', '-'],
input=json.dumps(review_data).encode(),
capture_output=True
)
if r.returncode != 0:
print(f"Error posting suggestions: {r.stderr.decode()}")
sys.exit(1)
print(f"Posted {len(comments)} inline suggestion(s)")
PYEOF
Loading