Skip to content

doc rewrite 3

doc rewrite 3 #34

name: Documentation Fixer
on:
issue_comment:
types: [created]
jobs:
claude-response:
runs-on: ubuntu-latest
if: |
github.event.issue.pull_request &&
contains(github.event.comment.body, '@claude') &&
github.event.comment.user.login != 'github-actions[bot]'
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
actions: read
steps:
- name: Get PR info
id: pr-info
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER="${{ github.event.issue.number }}"
PR_DATA=$(gh pr view $PR_NUMBER --repo ${{ github.repository }} --json headRefName,isCrossRepository)
echo "number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
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 ${{ steps.pr-info.outputs.number }} --repo ${{ github.repository }} \
--body "This PR is from a fork. Automated fixes cannot be pushed directly. Apply the suggested changes from the inline comments manually."
- name: Checkout repository
if: steps.pr-info.outputs.is_fork == 'false'
uses: actions/checkout@v4
with:
ref: ${{ steps.pr-info.outputs.branch }}
fetch-depth: 0
- name: Checkout system prompt repository
if: steps.pr-info.outputs.is_fork == 'false'
uses: actions/checkout@v4
with:
repository: netwrix-eng/internal-agents
token: ${{ secrets.PRIVATE_AGENTS_REPO }}
path: system-prompt-repo
ref: builds
sparse-checkout: |
engineering/technical_writing/system-prompt.md
sparse-checkout-cone-mode: false
- name: Read system prompt
id: read-prompt
if: steps.pr-info.outputs.is_fork == 'false'
run: |
{
echo "prompt<<EOF"
cat system-prompt-repo/engineering/technical_writing/system-prompt.md
echo "" # Forces a newline to prevent EOF delimiter errors
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Detect command type
id: cmd-type
if: steps.pr-info.outputs.is_fork == 'false'
run: |
COMMENT="${{ github.event.comment.body }}"
if echo "$COMMENT" | grep -qi 'preexisting'; then
echo "is_preexisting=true" >> "$GITHUB_OUTPUT"
else
echo "is_preexisting=false" >> "$GITHUB_OUTPUT"
fi
- name: Install Vale
if: steps.pr-info.outputs.is_fork == 'false' && steps.cmd-type.outputs.is_preexisting == 'false'
run: |
VERSION=$(curl -s "https://api.github.com/repos/errata-ai/vale/releases/latest" | jq -r '.tag_name')
curl -sfL "https://github.com/errata-ai/vale/releases/download/${VERSION}/vale_${VERSION#v}_Linux_64-bit.tar.gz" \
| sudo tar -xz -C /usr/local/bin vale
- name: Apply fixes
if: steps.pr-info.outputs.is_fork == 'false' && steps.cmd-type.outputs.is_preexisting == 'false'
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
show_full_output: true
claude_args: |
--model claude-sonnet-4-5-20250929
--allowedTools "Read,Write,Edit,Bash(vale:*),Bash(gh pr view:*),Bash(gh pr diff:*),Bash(git config:*),Bash(git add:*),Bash(git commit:*),Bash(git push:*),Bash(git status:*),Bash(git diff:*)"
--append-system-prompt "${{ steps.read-prompt.outputs.prompt }}"
- name: Record last comment ID
id: pre-claude
if: steps.pr-info.outputs.is_fork == 'false' && steps.cmd-type.outputs.is_preexisting == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
LAST_ID=$(gh api repos/${{ github.repository }}/issues/${{ steps.pr-info.outputs.number }}/comments \
--jq 'if length > 0 then .[-1].id else 0 end' 2>/dev/null || echo "0")
echo "last_comment_id=$LAST_ID" >> "$GITHUB_OUTPUT"
- name: Detect preexisting issues
if: steps.pr-info.outputs.is_fork == 'false' && steps.cmd-type.outputs.is_preexisting == 'true'
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
show_full_output: false
prompt: |
Detect preexisting issues in PR ${{ steps.pr-info.outputs.number }}.
Follow these steps in order:
1. Use `gh pr diff ${{ steps.pr-info.outputs.number }}` to get the diff. In the diff output, lines starting with `+` are lines added or changed by this PR. Lines starting with `-` were removed. Lines starting with a space are unchanged context lines.
2. Use `gh pr view ${{ steps.pr-info.outputs.number }}` to get the list of changed files, then read the full content of each changed markdown file.
3. Run all three review passes on the full content of each file. Report ONLY issues on lines that do NOT start with `+` in the diff — that is, lines that were not added or changed by this PR. This includes unchanged context lines (space prefix in the diff) and lines not shown in the diff at all.
4. You MUST write your results ONLY to `/tmp/preexisting-issues.md` — always, even if there are no issues. Do not post a comment. Do not write anything else. Use this exact format:
## Preexisting issues
### path/to/file.md
- Line N: issue. Suggested change: '...'
Or if there are no issues:
## Preexisting issues
None.
claude_args: |
--model claude-sonnet-4-5-20250929
--allowedTools "Read,Write,Edit,Bash(gh pr view:*),Bash(gh pr diff:*)"
--append-system-prompt "${{ steps.read-prompt.outputs.prompt }}"
- name: Post preexisting issues comment
if: steps.pr-info.outputs.is_fork == 'false' && steps.cmd-type.outputs.is_preexisting == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.pr-info.outputs.number }}
REPO: ${{ github.repository }}
LAST_COMMENT_ID: ${{ steps.pre-claude.outputs.last_comment_id }}
run: |
python3 << 'PYTHON_EOF'
import os
import json
import re
import subprocess
pr_number = os.environ['PR_NUMBER']
repo = os.environ['REPO']
last_comment_id = int(os.environ.get('LAST_COMMENT_ID', '0'))
FOOTER = (
"\n\n---\n\n"
"To apply the suggested fixes to preexisting issues, comment `@claude` on this PR followed by your instructions\n"
"(`@claude fix all issues` or `@claude fix only the first issue`).\n"
"Note: Automated fixes are only available for branches in this repository, not forks."
)
def normalize_issues(body):
"""Normalize issue lines to single bullet points, same as the reviewer."""
src = body.split('\n')
result = []
i = 0
while i < len(src):
line = src[i]
# Convert heading format: ### Line N: ... → - Line N: ...
m = re.match(r'^#{1,6}\s+(Line \d+:.+)$', line)
if m:
result.append(f'- {m.group(1)}')
i += 1
continue
# Convert bold format: **Line N: title** + sub-bullets → - Line N: single line
m = re.match(r'^\*\*(Line \d+:.*?)\*\*\s*$', line)
if m:
title = m.group(1).rstrip('.')
i += 1
parts = []
while i < len(src) and re.match(r'^\s*[-*]\s+', src[i]):
sub = re.sub(r'^\s*[-*]\s+', '', src[i])
sub = re.sub(r'^(Issue|Fix|Description|Suggested change):\s*', '', sub, flags=re.IGNORECASE)
if sub.strip():
parts.append(sub.strip().rstrip('.'))
i += 1
combined = f'- {title}. {". ".join(parts)}.' if parts else f'- {title}.'
result.append(combined)
continue
result.append(line)
i += 1
return '\n'.join(result)
def normalize_body(body):
"""Extract the ## Preexisting issues section without fluff, normalized to match reviewer format."""
idx = body.find('## Preexisting issues')
if idx == -1:
return '## Preexisting issues\nNone.'
body = body[idx:]
# Strip any footer Claude may have appended after a --- divider
footer_idx = body.find('\n---')
if footer_idx != -1:
body = body[:footer_idx]
# Strip any prose intro between the header and first subheading/content
lines = body.split('\n')
result = []
past_intro = False
for line in lines:
s = line.strip()
if s == '## Preexisting issues':
result.append(line)
elif not past_intro:
if s.startswith('### ') or s == 'None.' or s == '':
if s:
past_intro = True
result.append(line)
# else: prose intro line — skip it
else:
result.append(line)
while result and not result[-1].strip():
result.pop()
return normalize_issues('\n'.join(result))
summary_path = '/tmp/preexisting-issues.md'
if os.path.exists(summary_path):
with open(summary_path) as f:
clean_body = normalize_body(f.read()) + FOOTER
else:
clean_body = '## Preexisting issues\nNone.' + FOOTER
# Find the action's auto-posted comment (ID > last recorded, posted by a bot)
result = subprocess.run(
['gh', 'api', f'repos/{repo}/issues/{pr_number}/comments'],
capture_output=True, text=True, check=True,
)
comments = json.loads(result.stdout)
new_bot_comments = [c for c in comments
if c['id'] > last_comment_id
and c['user']['login'].endswith('[bot]')]
if new_bot_comments:
# Replace the action's auto-comment in-place with our formatted output
target_id = new_bot_comments[-1]['id']
subprocess.run(
['gh', 'api', f'repos/{repo}/issues/comments/{target_id}',
'-X', 'PATCH', '--input', '-'],
input=json.dumps({'body': clean_body}),
capture_output=True, text=True, check=True,
)
else:
subprocess.run(
['gh', 'pr', 'comment', pr_number, '--repo', repo, '--body', clean_body],
check=True,
)
PYTHON_EOF