Skip to content

Commit 300f2a0

Browse files
authored
Merge pull request #290 from netwrix/dev
updated workflows for inline suggestions
2 parents 5878f96 + 2f81bb4 commit 300f2a0

2 files changed

Lines changed: 73 additions & 115 deletions

File tree

.github/workflows/claude-documentation-fixer.yml

Lines changed: 18 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
github.event.issue.pull_request &&
1313
contains(github.event.comment.body, '@claude')
1414
permissions:
15-
contents: read
15+
contents: write
1616
pull-requests: write
1717
issues: write
1818
id-token: write
@@ -23,124 +23,33 @@ jobs:
2323
env:
2424
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2525
run: |
26-
PR_DATA=$(gh pr view ${{ github.event.issue.number }} --repo ${{ github.repository }} --json headRefOid)
27-
echo "sha=$(echo "$PR_DATA" | jq -r '.headRefOid')" >> "$GITHUB_OUTPUT"
26+
PR_DATA=$(gh pr view ${{ github.event.issue.number }} --repo ${{ github.repository }} --json headRefName,isCrossRepository)
27+
echo "branch=$(echo "$PR_DATA" | jq -r '.headRefName')" >> "$GITHUB_OUTPUT"
28+
echo "is_fork=$(echo "$PR_DATA" | jq -r '.isCrossRepository')" >> "$GITHUB_OUTPUT"
29+
30+
- name: Post fork notice
31+
if: steps.pr-info.outputs.is_fork == 'true'
32+
env:
33+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34+
run: |
35+
gh pr comment ${{ github.event.issue.number }} --repo ${{ github.repository }} \
36+
--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."
2837
2938
- name: Checkout repository
39+
if: steps.pr-info.outputs.is_fork == 'false'
3040
uses: actions/checkout@v4
3141
with:
42+
# Check out the branch by name so git push works.
43+
ref: ${{ steps.pr-info.outputs.branch }}
3244
fetch-depth: 0
3345

34-
- name: Fetch PR head
35-
run: |
36-
git fetch origin pull/${{ github.event.issue.number }}/head:pr-fix-branch
37-
git checkout pr-fix-branch
38-
39-
- name: Generate fixes
46+
- name: Apply fixes
47+
if: steps.pr-info.outputs.is_fork == 'false'
4048
uses: anthropics/claude-code-action@v1
4149
with:
4250
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
4351
github_token: ${{ secrets.GITHUB_TOKEN }}
4452
show_full_output: true
45-
prompt: |
46-
${{ github.event.comment.body }}
47-
48-
Apply the requested fixes to the documentation files. Edit files directly using the Write and Edit tools.
49-
Do NOT run git commit, git push, or git add. Only edit the files.
5053
claude_args: |
5154
--model claude-sonnet-4-5-20250929
52-
--allowedTools "Read,Write,Edit,Bash(gh pr view:*),Bash(gh pr diff:*),Bash(git status:*),Bash(git diff:*)"
53-
54-
- name: Post inline suggestions
55-
env:
56-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
57-
PR_NUMBER: ${{ github.event.issue.number }}
58-
HEAD_SHA: ${{ steps.pr-info.outputs.sha }}
59-
REPO: ${{ github.repository }}
60-
run: |
61-
python3 << 'PYEOF'
62-
import subprocess, json, os, re, sys
63-
64-
def parse_diff(diff_text):
65-
suggestions = []
66-
current_file = None
67-
lines = diff_text.split('\n')
68-
i = 0
69-
while i < len(lines):
70-
line = lines[i]
71-
if line.startswith('diff --git'):
72-
i += 1
73-
continue
74-
elif line.startswith('--- a/'):
75-
current_file = line[6:]
76-
i += 1
77-
continue
78-
elif line.startswith('+++ b/'):
79-
i += 1
80-
continue
81-
elif line.startswith('@@ '):
82-
m = re.match(r'@@ -(\d+)(?:,\d+)? \+\d+(?:,\d+)? @@', line)
83-
if not m:
84-
i += 1
85-
continue
86-
old_line = int(m.group(1))
87-
i += 1
88-
while i < len(lines) and not lines[i].startswith('@@') and not lines[i].startswith('diff '):
89-
if lines[i].startswith('-') or lines[i].startswith('+'):
90-
removed, added = [], []
91-
start = old_line
92-
while i < len(lines) and (lines[i].startswith('-') or lines[i].startswith('+')):
93-
if lines[i].startswith('-'):
94-
removed.append(lines[i][1:])
95-
old_line += 1
96-
else:
97-
added.append(lines[i][1:])
98-
i += 1
99-
if removed:
100-
suggestions.append({'file': current_file, 'start': start, 'end': old_line - 1, 'new': added})
101-
elif lines[i].startswith(' '):
102-
old_line += 1
103-
i += 1
104-
else:
105-
i += 1
106-
else:
107-
i += 1
108-
return suggestions
109-
110-
diff = subprocess.run(['git', 'diff'], capture_output=True, text=True).stdout
111-
if not diff.strip():
112-
print("No changes to suggest")
113-
sys.exit(0)
114-
115-
suggestions = parse_diff(diff)
116-
if not suggestions:
117-
print("No line-replacement suggestions to post")
118-
sys.exit(0)
119-
120-
comments = []
121-
for s in suggestions:
122-
body = '```suggestion\n' + '\n'.join(s['new']) + '\n```'
123-
c = {'path': s['file'], 'line': s['end'], 'side': 'RIGHT', 'body': body}
124-
if s['start'] != s['end']:
125-
c['start_line'] = s['start']
126-
c['start_side'] = 'RIGHT'
127-
comments.append(c)
128-
129-
review_data = {
130-
'commit_id': os.environ['HEAD_SHA'],
131-
'body': f"Here are the suggested fixes ({len(comments)} suggestion(s)). Click 'Apply suggestion' on each one to accept it.",
132-
'event': 'COMMENT',
133-
'comments': comments
134-
}
135-
136-
r = subprocess.run(
137-
['gh', 'api', f"repos/{os.environ['REPO']}/pulls/{os.environ['PR_NUMBER']}/reviews",
138-
'--method', 'POST', '--input', '-'],
139-
input=json.dumps(review_data).encode(),
140-
capture_output=True
141-
)
142-
if r.returncode != 0:
143-
print(f"Error posting suggestions: {r.stderr.decode()}")
144-
sys.exit(1)
145-
print(f"Posted {len(comments)} inline suggestion(s)")
146-
PYEOF
55+
--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:*)"

.github/workflows/claude-documentation-reviewer.yml

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,19 +88,68 @@ jobs:
8888
8989
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.
9090
91-
Write your complete review to /tmp/review.md.
91+
Write your findings to /tmp/suggestions.json following the format in your instructions.
9292
9393
claude_args: |
9494
--model claude-sonnet-4-5-20250929
9595
--allowedTools "Write,Bash(gh pr diff:*),Bash(gh pr view:*)"
9696
--append-system-prompt "${{ steps.read-prompt.outputs.prompt }}"
9797
98-
- name: Append closing lines and post review
98+
- name: Post inline suggestions
9999
if: steps.changed-files.outputs.count > 0
100100
env:
101101
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
102+
PR_NUMBER: ${{ github.event.pull_request.number }}
103+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
104+
REPO: ${{ github.repository }}
102105
run: |
103-
echo "" >> /tmp/review.md
104-
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
105-
echo "Note: fixes are posted as inline suggestions. Click 'Apply suggestion' on each one to accept it." >> /tmp/review.md
106-
gh pr comment ${{ github.event.pull_request.number }} --body-file /tmp/review.md
106+
python3 << 'PYEOF'
107+
import subprocess, json, os, sys
108+
109+
pr_number = os.environ['PR_NUMBER']
110+
head_sha = os.environ['HEAD_SHA']
111+
repo = os.environ['REPO']
112+
113+
try:
114+
with open('/tmp/suggestions.json') as f:
115+
suggestions = json.load(f)
116+
except (FileNotFoundError, json.JSONDecodeError) as e:
117+
print(f"Could not read /tmp/suggestions.json: {e}")
118+
sys.exit(1)
119+
120+
if not suggestions:
121+
subprocess.run(
122+
['gh', 'pr', 'comment', pr_number, '--repo', repo,
123+
'--body', 'No issues found in the changed files.'],
124+
check=True
125+
)
126+
print("No issues found")
127+
sys.exit(0)
128+
129+
comments = []
130+
for s in suggestions:
131+
body = s['body'] + '\n```suggestion\n' + s['suggestion'] + '\n```'
132+
c = {'path': s['path'], 'line': s['line'], 'side': 'RIGHT', 'body': body}
133+
if s.get('start_line') and s['start_line'] != s['line']:
134+
c['start_line'] = s['start_line']
135+
c['start_side'] = 'RIGHT'
136+
comments.append(c)
137+
138+
review_data = {
139+
'commit_id': head_sha,
140+
'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`).',
141+
'event': 'COMMENT',
142+
'comments': comments
143+
}
144+
145+
r = subprocess.run(
146+
['gh', 'api', f'repos/{repo}/pulls/{pr_number}/reviews',
147+
'--method', 'POST', '--input', '-'],
148+
input=json.dumps(review_data).encode(),
149+
capture_output=True
150+
)
151+
if r.returncode != 0:
152+
print(f"Error posting suggestions: {r.stderr.decode()}")
153+
sys.exit(1)
154+
print(f"Posted {len(comments)} inline suggestion(s)")
155+
PYEOF

0 commit comments

Comments
 (0)