Skip to content

Password Secure: Release 26.3.100 #119

Password Secure: Release 26.3.100

Password Secure: Release 26.3.100 #119

Workflow file for this run

name: Doc PR Review
on:
pull_request:
types: [opened, synchronize]
branches:
- dev
paths:
- 'docs/**/*.md'
- '!docs/**/CLAUDE.md'
- '!docs/**/SKILL.md'
- '!docs/kb/**'
issue_comment:
types: [created]
jobs:
doc-review:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 1
- name: Get changed markdown files
id: changed-files
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER=${{ github.event.pull_request.number }}
CHANGED_MD_FILES=$(gh pr diff "$PR_NUMBER" --name-only | grep -E '^docs/.*\.md$' | grep -v '/CLAUDE\.md$' | grep -v '/SKILL\.md$' | grep -v '^docs/kb/' || true)
if [ -z "$CHANGED_MD_FILES" ]; then
echo "No docs markdown files changed"
echo "files=" >> "$GITHUB_OUTPUT"
echo "count=0" >> "$GITHUB_OUTPUT"
else
echo "Changed markdown files:"
echo "$CHANGED_MD_FILES"
FILES_LIST=$(echo "$CHANGED_MD_FILES" | tr '\n' ',' | sed 's/,$//')
echo "files=$FILES_LIST" >> "$GITHUB_OUTPUT"
echo "count=$(echo "$CHANGED_MD_FILES" | wc -l | tr -d ' ')" >> "$GITHUB_OUTPUT"
fi
- name: Delete previous bot comments
if: steps.changed-files.outputs.count > 0
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER=${{ github.event.pull_request.number }}
# Delete previous review comments
COMMENT_IDS=$(gh api repos/${{ github.repository }}/issues/${PR_NUMBER}/comments \
--jq '[.[] | select(.user.login == "github-actions[bot]" and (.body | contains("Documentation PR Review"))) | .id] | .[]' 2>/dev/null || true)
for ID in $COMMENT_IDS; do
gh api repos/${{ github.repository }}/issues/comments/${ID} -X DELETE 2>/dev/null || true
done
# Dismiss previous bot reviews (Dale) so they don't pile up
REVIEW_IDS=$(gh api repos/${{ github.repository }}/pulls/${PR_NUMBER}/reviews \
--jq '[.[] | select(.user.login == "github-actions[bot]" and .state == "COMMENTED" and (.body | contains("Dale found"))) | .id] | .[]' 2>/dev/null || true)
for ID in $REVIEW_IDS; do
gh api repos/${{ github.repository }}/pulls/${PR_NUMBER}/reviews/${ID}/dismissals \
-f message="Superseded by new review" -f event="DISMISS" 2>/dev/null || true
done
- name: Run Dale linting
id: dale
if: steps.changed-files.outputs.count > 0
uses: anthropics/claude-code-action@v1
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
show_full_output: true
prompt: |
You are Dale, a documentation linter. Your ONLY job is to check files against Dale rules and write results to a JSON file.
CHANGED FILES: ${{ steps.changed-files.outputs.files }}
INSTRUCTIONS:
Step 1: Read each Dale rule file:
- .claude/skills/dale/rules/minimizing-difficulty.yml
- .claude/skills/dale/rules/negative-assumptions.yml
- .claude/skills/dale/rules/xy-slop.yml
Step 2: Read each changed file listed above (split on commas).
Step 3: For each file, check every line against each rule's "reason" field. When a line triggers a rule, record it.
Step 4: Write results to /tmp/dale-results.json as a JSON array. Each entry must have:
- "path": the file path exactly as given above
- "line": the line number (integer)
- "rule": the rule filename without extension (e.g. "minimizing-difficulty")
- "message": the rule's "message" field value
If no issues found, write an empty array: []
Example output:
[{"path":"docs/foo/bar.md","line":7,"rule":"minimizing-difficulty","message":"Do not minimize the difficulty of tasks users are performing."}]
IMPORTANT: Write ONLY the JSON file. Do not post comments, do not run any other tools. Your task is done when /tmp/dale-results.json exists.
claude_args: '--allowedTools "Read,Write"'
- name: Post Dale inline comments
id: dale-post
if: steps.changed-files.outputs.count > 0
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
DALE_COUNT=0
if [ -f /tmp/dale-results.json ]; then
DALE_COUNT=$(jq 'length' /tmp/dale-results.json 2>/dev/null || echo "0")
fi
echo "dale_count=$DALE_COUNT" >> "$GITHUB_OUTPUT"
if [ "$DALE_COUNT" -gt 0 ]; then
echo "Posting $DALE_COUNT Dale inline comments"
# Transform Dale results into PR review comment format
COMMENTS_JSON=$(jq '[.[] | {"path": .path, "line": .line, "body": ("**Dale** (`" + .rule + "`): " + .message)}]' /tmp/dale-results.json)
jq -n \
--arg body "**Dale found ${DALE_COUNT} issue(s).** See inline comments below." \
--argjson comments "$COMMENTS_JSON" \
'{"body": $body, "event": "COMMENT", "comments": $comments}' \
| gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews \
--input - 2>&1
else
echo "No Dale issues found"
fi
- name: Get PR diff
id: diff
if: steps.changed-files.outputs.count > 0
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr diff ${{ github.event.pull_request.number }} > /tmp/pr-diff.txt 2>&1 || true
echo "Diff saved to /tmp/pr-diff.txt"
wc -l /tmp/pr-diff.txt
- name: Run editorial review
if: steps.changed-files.outputs.count > 0
uses: anthropics/claude-code-action@v1
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
show_full_output: true
prompt: |
You are a documentation reviewer. Your ONLY job is to:
1. Read the PR diff and changed files
2. Do a brief editorial review
3. Post a formatted review comment to the PR
CONTEXT:
- Repository: ${{ github.repository }}
- PR number: ${{ github.event.pull_request.number }}
- Changed files: ${{ steps.changed-files.outputs.files }}
- Dale issues: ${{ steps.dale-post.outputs.dale_count }} (already posted as inline comments)
- PR diff is at: /tmp/pr-diff.txt
NOTE: Vale linting runs separately (vale-linter workflow) and posts inline review comments plus a summary PR comment. Do not run Vale or include Vale issues in this review.
INSTRUCTIONS:
Step 1: Read /tmp/pr-diff.txt to see what changed. Then read each changed file to understand context.
Step 2: Do a brief editorial review of ONLY added/modified lines, focusing on:
- Voice: passive voice, first person, impersonal phrases
- Clarity: hard-to-parse sentences, ambiguous references
- Surface: wordiness, redundancy
Do NOT duplicate issues already caught by Dale — focus on what linters miss.
Step 3: Write the review to /tmp/doc-pr-review.md with this EXACT structure:
## Documentation PR Review
### Editorial Review
(list editorial issues as bullet points: **Category** — Line N: description. Suggested fix: "...")
(if no issues found, write "No editorial issues found.")
### Summary
N Dale issues (see inline comments), N editorial suggestions across N files.
---
**What to do next:**
Comment `@claude` on this PR followed by your instructions. For example:
- `@claude fix all issues`
- `@claude fix only the Dale issues`
- `@claude reorganize the prerequisites section`
- `@claude help improve the flow of this document`
> Automated fixes are only available for branches in this repository, not forks.
Step 4: Post the comment by running this exact command:
gh pr comment ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --body-file /tmp/doc-pr-review.md
IMPORTANT: You MUST complete all 4 steps. Your task is NOT done until step 4 succeeds.
claude_args: '--allowedTools "Bash,Read,Write"'
- name: Verify review was posted
if: steps.changed-files.outputs.count > 0
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
COMMENTS=$(gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments \
--jq '[.[] | select(.body | contains("Documentation PR Review"))] | length' 2>/dev/null || echo "0")
echo "Review comments found: $COMMENTS"
if [ "$COMMENTS" = "0" ]; then
echo "::warning::No review comment was posted by Claude"
fi
doc-followup:
if: >-
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '@claude') &&
!startsWith(github.event.comment.user.login, 'github-actions')
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
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,baseRefName,isCrossRepository)
BASE_BRANCH=$(echo "$PR_DATA" | jq -r '.baseRefName')
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"
# Check target branch using the shell variable to avoid
# re-interpolating the output via expressions (code injection risk).
if [ "$BASE_BRANCH" = "dev" ]; then
echo "targets_dev=true" >> "$GITHUB_OUTPUT"
else
echo "targets_dev=false" >> "$GITHUB_OUTPUT"
fi
- name: Post fork notice
if: steps.pr-info.outputs.is_fork == 'true' && steps.pr-info.outputs.targets_dev == '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. I can still review and suggest changes — apply them manually from the comments."
- name: Checkout repository
if: steps.pr-info.outputs.is_fork == 'false' && steps.pr-info.outputs.targets_dev == 'true'
uses: actions/checkout@v4
with:
ref: ${{ steps.pr-info.outputs.branch }}
fetch-depth: 0
- name: Handle @claude request
if: steps.pr-info.outputs.is_fork == 'false' && steps.pr-info.outputs.targets_dev == 'true'
uses: anthropics/claude-code-action@v1
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMENT_BODY: ${{ github.event.comment.body }}
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
show_full_output: true
prompt: |
/doc-pr-fix ${{ steps.pr-info.outputs.number }} $COMMENT_BODY
claude_args: '--allowedTools "Bash(gh:*),Bash(git:*),Read,Write,Edit,Glob,Grep,Skill(doc-pr-fix),Skill(dale),Skill(doc-help)"'
- name: Resolve inline comments and dismiss old reviews
if: steps.pr-info.outputs.is_fork == 'false' && steps.pr-info.outputs.targets_dev == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER=${{ steps.pr-info.outputs.number }}
REPO=${{ github.repository }}
OWNER="${REPO%%/*}"
NAME="${REPO##*/}"
# Resolve all Dale and Vale inline comment threads
THREAD_IDS=$(gh api graphql -f query='
query($owner:String!,$name:String!,$pr:Int!) {
repository(owner:$owner,name:$name) {
pullRequest(number:$pr) {
reviewThreads(first:100) {
nodes { id isResolved comments(first:1) { nodes { body } } }
}
}
}
}' -f owner="$OWNER" -f name="$NAME" -F pr="$PR_NUMBER" \
--jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false and ((.comments.nodes[0].body | contains("**Dale**")) or (.comments.nodes[0].body | contains("**Vale**")))) | .id' 2>/dev/null || true)
for TID in $THREAD_IDS; do
gh api graphql -f query='
mutation($tid:ID!) {
resolveReviewThread(input:{threadId:$tid}) { thread { isResolved } }
}' -f tid="$TID" 2>/dev/null || true
done
# Dismiss all previous Dale and Vale reviews
REVIEW_IDS=$(gh api repos/${REPO}/pulls/${PR_NUMBER}/reviews \
--jq '[.[] | select(.user.login == "github-actions[bot]" and ((.body | contains("Dale found")) or (.body | contains("Vale found")))) | .id] | .[]' 2>/dev/null || true)
for ID in $REVIEW_IDS; do
gh api repos/${REPO}/pulls/${PR_NUMBER}/reviews/${ID}/dismissals \
-f message="Superseded after fixes applied" -f event="DISMISS" 2>/dev/null || true
done