1010 required : true
1111 type : string
1212
13+ # Same trust model as opencode-review.yml:
14+ # - triggered by a trusted maintainer/collaborator comment
15+ # - comments directly from this workflow
16+ # - does not rely on pull_request -> artifact -> workflow_run
1317permissions :
1418 contents : read
1519 issues : write
16- pull-requests : read
20+ pull-requests : write
1721
1822jobs :
1923 docs-ai-review :
@@ -32,33 +36,66 @@ jobs:
3236 )
3337
3438 steps :
39+ - name : Validate trigger command
40+ if : github.event_name == 'issue_comment'
41+ run : |
42+ body=$(jq -r '.comment.body' "$GITHUB_EVENT_PATH")
43+
44+ # Require /review-docs as a standalone command on its own line.
45+ # This avoids accidental triggers from prose and keeps it distinct from /review.
46+ if ! printf '%s\n' "$body" | grep -Eq '^/review-docs([[:space:]]|$)'; then
47+ echo "No standalone /review-docs command found; skipping."
48+ exit 78
49+ fi
50+
3551 - name : Get PR info
3652 id : pr
3753 env :
38- GH_TOKEN : ${{ github.token }}
54+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
3955 EVENT_NAME : ${{ github.event_name }}
4056 DISPATCH_PR_NUMBER : ${{ inputs.pr_number }}
4157 run : |
58+ set -euo pipefail
59+
4260 if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
4361 PR_NUMBER="$DISPATCH_PR_NUMBER"
4462 else
4563 PR_NUMBER=$(jq -r '.issue.number' "$GITHUB_EVENT_PATH")
4664 fi
4765
4866 PR_JSON=$(gh api repos/${{ github.repository }}/pulls/${PR_NUMBER})
67+
4968 HEAD_SHA=$(echo "$PR_JSON" | jq -r '.head.sha')
5069 BASE_SHA=$(echo "$PR_JSON" | jq -r '.base.sha')
70+ HEAD_REF=$(echo "$PR_JSON" | jq -r '.head.ref')
71+ BASE_REF=$(echo "$PR_JSON" | jq -r '.base.ref')
72+ HEAD_REPO=$(echo "$PR_JSON" | jq -r '.head.repo.full_name')
73+ BASE_REPO=$(echo "$PR_JSON" | jq -r '.base.repo.full_name')
5174
5275 echo "number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
5376 echo "head_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT"
5477 echo "base_sha=$BASE_SHA" >> "$GITHUB_OUTPUT"
78+ echo "head_ref=$HEAD_REF" >> "$GITHUB_OUTPUT"
79+ echo "base_ref=$BASE_REF" >> "$GITHUB_OUTPUT"
80+ echo "head_repo=$HEAD_REPO" >> "$GITHUB_OUTPUT"
81+ echo "base_repo=$BASE_REPO" >> "$GITHUB_OUTPUT"
5582
5683 - name : Checkout PR head
5784 uses : actions/checkout@v4
5885 with :
86+ repository : ${{ steps.pr.outputs.head_repo }}
5987 ref : ${{ steps.pr.outputs.head_sha }}
6088 fetch-depth : 0
6189
90+ - name : Fetch base commit
91+ env :
92+ BASE_REPO : ${{ steps.pr.outputs.base_repo }}
93+ BASE_SHA : ${{ steps.pr.outputs.base_sha }}
94+ run : |
95+ set -euo pipefail
96+ git remote add base "https://github.com/${BASE_REPO}.git" || true
97+ git fetch --no-tags --depth=1 base "$BASE_SHA"
98+
6299 - name : Setup Node
63100 uses : actions/setup-node@v4
64101 with :
@@ -73,7 +110,10 @@ jobs:
73110 BASE_SHA : ${{ steps.pr.outputs.base_sha }}
74111 HEAD_SHA : ${{ steps.pr.outputs.head_sha }}
75112 run : |
113+ set -euo pipefail
114+
76115 mkdir -p website-quality-governance/generated
116+
77117 changed_files=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" | paste -sd, -)
78118
79119 yarn docs:ai-review:prepare \
@@ -86,31 +126,37 @@ jobs:
86126
87127 - name : Upsert advisory PR comment
88128 env :
89- GH_TOKEN : ${{ github.token }}
129+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
90130 PR_NUMBER : ${{ steps.pr.outputs.number }}
91131 HEAD_SHA : ${{ steps.pr.outputs.head_sha }}
92132 BASE_SHA : ${{ steps.pr.outputs.base_sha }}
93133 run : |
94- packet=website-quality-governance/generated/ai-review-packet.json
134+ set -euo pipefail
95135
136+ packet=website-quality-governance/generated/ai-review-packet.json
96137 marker='<!-- docs-ai-review:packet -->'
138+
97139 changed_count=$(jq '.changed_files | length' "$packet")
98- high_risk=$(jq -r '.high_risk.required' "$packet")
140+ high_risk=$(jq -r '.high_risk.required // false ' "$packet")
99141 agent_count=$(jq '.review_agents | length' "$packet")
142+ sync_group_count=$(jq '.sync_groups | length' "$packet")
100143
101- cat > /tmp/docs-ai-review-comment.md <<EOF
144+ cat > /tmp/docs-ai-review-comment.md <<EOF_COMMENT
102145 $marker
146+ ## Docs AI Review
147+
103148 Docs AI review packet prepared for this PR.
104149
105150 - Trigger: \`/review-docs\`
106151 - Changed files in packet: \`$changed_count\`
107152 - High-risk docs path matched: \`$high_risk\`
108153 - Review agents included: \`$agent_count\`
154+ - Sync groups included: \`$sync_group_count\`
109155 - Base SHA: \`$BASE_SHA\`
110156 - Head SHA: \`$HEAD_SHA\`
111157
112158 This MVP prepares the packet for advisory AI review. AI findings, when produced, are suggestions only; deterministic governance CI remains the blocking signal.
113- EOF
159+ EOF_COMMENT
114160
115161 existing_comment_id=$(
116162 gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
@@ -125,6 +171,8 @@ jobs:
125171 "repos/${{ github.repository }}/issues/comments/${existing_comment_id}" \
126172 -f body="$(cat /tmp/docs-ai-review-comment.md)"
127173 else
174+ # Same effective behavior as: gh pr comment "$PR_NUMBER" --body-file /tmp/docs-ai-review-comment.md
175+ # Use REST API so we can preserve the upsert-by-marker behavior above.
128176 gh api \
129177 --method POST \
130178 "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
0 commit comments