1717# workflow: nbr-review.yml
1818# default_reviewers:
1919# - reviewer-username
20+ #
21+ # For teams NOT using tbdflow, you can trigger reviews in three ways:
22+ #
23+ # A) Tag-based trigger — add to the `on:` section:
24+ # push:
25+ # tags:
26+ # - 'review/*'
27+ #
28+ # B) Conventional commit filter — add to the `create-review` job:
29+ # if: >-
30+ # github.event_name == 'workflow_dispatch' ||
31+ # contains(github.event.head_commit.message, 'feat:') ||
32+ # contains(github.event.head_commit.message, 'fix:') ||
33+ # contains(github.event.head_commit.message, 'refactor:') ||
34+ # contains(github.event.head_commit.message, 'perf:')
35+ #
36+ # C) Commit message marker — see the "Check if review needed" step below
37+ #
38+ # Option B (conventional commit job filter) is recommended (if you really don't want to use tbdflow :)).
39+ # Most teams already use conventional commits, so it's zero friction,
40+ # just commit as usual and reviews happen automatically for meaningful changes while skipping chore: and ci: commits.
41+ #
42+ # Option C is essentially a more flexible version of B (regex instead of contains()), so teams that want finer control will graduate to that.
43+ #
2044
2145name: Non-Blocking Review
2246
4670 push:
4771 branches:
4872 - main
73+ # --- (A) Uncomment to also trigger on review tags ---
74+ # tags:
75+ # - 'review/*'
76+
77+ # Trigger when review issues are closed (updates commit status)
78+ issues:
79+ types: [closed]
4980
5081# Prevent duplicate issues from rapid pushes
5182concurrency:
@@ -55,6 +86,14 @@ concurrency:
5586jobs:
5687 create-review:
5788 runs-on: ubuntu-latest
89+ if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
90+ # --- (B) Uncomment to only review conventional commit types ---
91+ # if: >-
92+ # github.event_name == 'workflow_dispatch' ||
93+ # contains(github.event.head_commit.message, 'feat:') ||
94+ # contains(github.event.head_commit.message, 'fix:') ||
95+ # contains(github.event.head_commit.message, 'refactor:') ||
96+ # contains(github.event.head_commit.message, 'perf:')
5897 permissions:
5998 issues: write
6099 statuses: write
@@ -66,17 +105,47 @@ jobs:
66105 with:
67106 fetch-depth: 2 # Need parent commit for diff
68107
108+ # --- (C) Uncomment to filter by commit message marker [review] or
109+ # conventional commit prefixes. Gate subsequent steps with:
110+ # if: steps.check-review.outputs.needs_review == 'true'
111+ #
112+ # - name: Check if review needed
113+ # id: check-review
114+ # run: |
115+ # MSG=$(git log -1 --format=%s)
116+ # if [[ "$MSG" =~ ^(feat|fix|refactor|perf|docs)\(?.*\)?:.* ]]; then
117+ # echo "needs_review=true" >> $GITHUB_OUTPUT
118+ # elif [[ "$MSG" =~ \[review\] ]]; then
119+ # echo "needs_review=true" >> $GITHUB_OUTPUT
120+ # else
121+ # echo "needs_review=false" >> $GITHUB_OUTPUT
122+ # fi
123+
69124 - name: Get commit info
70125 id: commit-info
126+ env:
127+ EVENT_NAME: ${{ github.event_name }}
128+ INPUT_SHA: ${{ inputs.commit_sha }}
129+ INPUT_MESSAGE: ${{ inputs.commit_message }}
130+ INPUT_AUTHOR: ${{ inputs.author }}
131+ INPUT_REVIEWERS: ${{ inputs.reviewers }}
132+ PUSH_SHA: ${{ github.sha }}
71133 run: |
72- if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
73- echo "sha=${{ inputs.commit_sha }}" >> $GITHUB_OUTPUT
74- echo "message=${{ inputs.commit_message }}" >> $GITHUB_OUTPUT
75- echo "author=${{ inputs.author }}" >> $GITHUB_OUTPUT
76- echo "reviewers=${{ inputs.reviewers }}" >> $GITHUB_OUTPUT
134+ if [ "$EVENT_NAME" == "workflow_dispatch" ]; then
135+ echo "sha=$INPUT_SHA" >> $GITHUB_OUTPUT
136+ # Use heredoc for message to safely handle special characters
137+ EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
138+ echo "message<<$EOF" >> $GITHUB_OUTPUT
139+ echo "$INPUT_MESSAGE" >> $GITHUB_OUTPUT
140+ echo "$EOF" >> $GITHUB_OUTPUT
141+ echo "author=$INPUT_AUTHOR" >> $GITHUB_OUTPUT
142+ echo "reviewers=$INPUT_REVIEWERS" >> $GITHUB_OUTPUT
77143 else
78- echo "sha=${{ github.sha }}" >> $GITHUB_OUTPUT
79- echo "message=$(git log -1 --format=%s)" >> $GITHUB_OUTPUT
144+ echo "sha=$PUSH_SHA" >> $GITHUB_OUTPUT
145+ EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
146+ echo "message<<$EOF" >> $GITHUB_OUTPUT
147+ git log -1 --format=%s >> $GITHUB_OUTPUT
148+ echo "$EOF" >> $GITHUB_OUTPUT
80149 echo "author=$(git log -1 --format=%an)" >> $GITHUB_OUTPUT
81150 echo "reviewers=" >> $GITHUB_OUTPUT
82151 fi
@@ -85,11 +154,11 @@ jobs:
85154 id: existing
86155 env:
87156 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
157+ COMMIT_SHA: ${{ steps.commit-info.outputs.sha }}
88158 run: |
89- SHORT_SHA="${{ steps.commit-info.outputs.sha }}"
90- SHORT_SHA="${SHORT_SHA:0:7}"
159+ SHORT_SHA="${COMMIT_SHA:0:7}"
91160
92- EXISTING=$(gh issue list --state open --label "author-issue " --search "$SHORT_SHA in:title" --json number --jq '.[0].number // empty')
161+ EXISTING=$(gh issue list --state open --label "review-pending " --search "$SHORT_SHA in:title" --json number --jq '.[0].number // empty')
93162
94163 if [ -n "$EXISTING" ]; then
95164 echo "exists=true" >> $GITHUB_OUTPUT
@@ -99,22 +168,13 @@ jobs:
99168 echo "exists=false" >> $GITHUB_OUTPUT
100169 fi
101170
102- - name: Set pending commit status
103- if: steps.existing.outputs.exists != 'true'
104- env:
105- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
106- run: |
107- gh api repos/${{ github.repository }}/statuses/${{ steps.commit-info.outputs.sha }} \
108- -f state='pending' \
109- -f context='peer-review' \
110- -f description='Awaiting non-blocking review'
111- echo "✅ Commit status set to pending"
112-
113171 - name: Generate inline diff (for small changes)
114172 if: steps.existing.outputs.exists != 'true'
115173 id: diff
174+ env:
175+ COMMIT_SHA: ${{ steps.commit-info.outputs.sha }}
116176 run: |
117- DIFF=$(git show --no-color --stat --patch --no-prefix ${{ steps.commit-info.outputs.sha }} | tail -n +2)
177+ DIFF=$(git show --no-color --stat --patch --no-prefix "$COMMIT_SHA" | tail -n +2)
118178 DIFF_LINES=$(echo "$DIFF" | wc -l)
119179
120180 if [ "$DIFF_LINES" -le 60 ]; then
@@ -136,44 +196,50 @@ jobs:
136196 env:
137197 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
138198 run: |
139- gh label create "peer-review" --color "1D76DB" --description "Peer review of commit" 2>/dev/null || true
140- gh label create "author-issue" --color "5319E7" --description "Author issue for commit review" 2>/dev/null || true
199+ gh label create "review-pending" --color "1D76DB" --description "Review awaiting attention" 2>/dev/null || true
200+ gh label create "review-accepted" --color "0E8A16" --description "Review approved" 2>/dev/null || true
201+ gh label create "review-concern" --color "E4E669" --description "Concern raised - needs attention" 2>/dev/null || true
202+ gh label create "review-dismissed" --color "6E7681" --description "Review dismissed" 2>/dev/null || true
141203
142204 - name: Create review issue
143205 if: steps.existing.outputs.exists != 'true'
144206 id: create-issue
145207 env:
146208 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
209+ COMMIT_SHA: ${{ steps.commit-info.outputs.sha }}
210+ COMMIT_MESSAGE: ${{ steps.commit-info.outputs.message }}
211+ COMMIT_AUTHOR: ${{ steps.commit-info.outputs.author }}
212+ COMMIT_REVIEWERS: ${{ steps.commit-info.outputs.reviewers }}
213+ INCLUDE_DIFF: ${{ steps.diff.outputs.include_diff }}
214+ DIFF_LINES: ${{ steps.diff.outputs.diff_lines }}
215+ DIFF_CONTENT: ${{ steps.diff.outputs.diff }}
216+ REPO_FULL_NAME: ${{ github.repository }}
147217 run: |
148- SHA="${{ steps.commit-info.outputs.sha }}"
149- SHORT_SHA="${SHA:0:7}"
150- MESSAGE="${{ steps.commit-info.outputs.message }}"
151- AUTHOR="${{ steps.commit-info.outputs.author }}"
152- REVIEWERS="${{ steps.commit-info.outputs.reviewers }}"
153- REPO_URL="https://github.com/${{ github.repository }}"
218+ SHORT_SHA="${COMMIT_SHA:0:7}"
219+ REPO_URL="https://github.com/$REPO_FULL_NAME"
154220
155221 # Build diff section
156- if [ "${{ steps.diff.outputs.include_diff }} " == "true" ]; then
222+ if [ "$INCLUDE_DIFF " == "true" ]; then
157223 DIFF_SECTION="
158224
159225 <details>
160- <summary>📝 Inline diff (${{ steps.diff.outputs.diff_lines } } lines)</summary>
226+ <summary>📝 Inline diff (${DIFF_LINES } lines)</summary>
161227
162228 \`\`\`diff
163- ${{ steps.diff.outputs.diff } }
229+ ${DIFF_CONTENT }
164230 \`\`\`
165231
166232 </details>"
167233 else
168234 DIFF_SECTION="
169235
170- > 📄 Large change (${{ steps.diff.outputs.diff_lines }} lines) - [view full diff]($REPO_URL/commit/$SHA )"
236+ > 📄 Large change (${DIFF_LINES} lines) - [view full diff]($REPO_URL/commit/$COMMIT_SHA )"
171237 fi
172238
173239 # Build reviewer checklist
174240 REVIEWER_CHECKLIST=""
175- if [ -n "$REVIEWERS " ]; then
176- IFS=',' read -ra REVIEWER_ARRAY <<< "$REVIEWERS "
241+ if [ -n "$COMMIT_REVIEWERS " ]; then
242+ IFS=',' read -ra REVIEWER_ARRAY <<< "$COMMIT_REVIEWERS "
177243 for reviewer in "${REVIEWER_ARRAY[@]}"; do
178244 reviewer=$(echo "$reviewer" | xargs) # trim whitespace
179245 if [ -n "$reviewer" ]; then
@@ -185,9 +251,9 @@ jobs:
185251
186252 BODY="## Non-Blocking Review Request
187253
188- **Commit:** [\`$SHORT_SHA\`]($REPO_URL/commit/$SHA )
189- **Author:** $AUTHOR
190- **Message:** $MESSAGE
254+ **Commit:** [\`$SHORT_SHA\`]($REPO_URL/commit/$COMMIT_SHA )
255+ **Author:** $COMMIT_AUTHOR
256+ **Message:** $COMMIT_MESSAGE
191257 $DIFF_SECTION
192258
193259 ---
@@ -225,48 +291,69 @@ jobs:
225291
226292 # Create the issue
227293 ISSUE_URL=$(gh issue create \
228- --title "[Review] $MESSAGE ($SHORT_SHA)" \
294+ --title "[Review] $COMMIT_MESSAGE ($SHORT_SHA)" \
229295 --body "$BODY" \
230- --label "peer- review,author-issue ")
296+ --label "review-pending ")
231297
232298 ISSUE_NUM=$(echo "$ISSUE_URL" | grep -oE '[0-9]+$')
233299 echo "issue_number=$ISSUE_NUM" >> $GITHUB_OUTPUT
234300 echo "✅ Created review issue #$ISSUE_NUM: $ISSUE_URL"
235301
236- # Update commit status when review issue is closed
302+ # Set pending commit status with link to the review issue
303+ gh api "repos/$REPO_FULL_NAME/statuses/$COMMIT_SHA" \
304+ -f state='pending' \
305+ -f context='peer-review' \
306+ -f description='Awaiting non-blocking review' \
307+ -f target_url="$ISSUE_URL"
308+ echo "✅ Commit status set to pending"
309+
310+ # Update commit status when review issue is closed.
311+ # Matches review-pending (manual close) OR review-accepted (tbdflow CLI approve).
237312 on-review-closed:
238313 runs-on: ubuntu-latest
239- if: github.event_name == 'issues' && github.event.action == 'closed' && contains(github.event.issue.labels.*.name, 'author-issue')
314+ if: >-
315+ github.event_name == 'issues' &&
316+ github.event.action == 'closed' &&
317+ (contains(github.event.issue.labels.*.name, 'review-pending') ||
318+ contains(github.event.issue.labels.*.name, 'review-accepted'))
240319 permissions:
241320 statuses: write
242- issues: read
321+ issues: write # needs write to swap labels on close
243322
244323 steps:
245324 - name: Extract commit SHA and mark as reviewed
246325 env:
247326 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
327+ ISSUE_BODY: ${{ github.event.issue.body }}
328+ ISSUE_TITLE: ${{ github.event.issue.title }}
329+ ISSUE_URL: ${{ github.event.issue.html_url }}
330+ REPO_FULL_NAME: ${{ github.repository }}
248331 run: |
249- BODY="${{ github.event.issue.body }}"
250-
251332 # Extract commit SHA from issue body (looks for /commit/SHA pattern)
252- FULL_SHA=$(echo "$BODY " | grep -oE 'github\.com/.*/commit/[a-f0-9]+' | grep -oE '[a-f0-9]{40}' | head -n1)
333+ FULL_SHA=$(echo "$ISSUE_BODY " | grep -oE 'github\.com/.*/commit/[a-f0-9]+' | grep -oE '[a-f0-9]{40}' | head -n1)
253334
254335 if [ -z "$FULL_SHA" ]; then
255336 # Try short SHA from title
256- SHORT_SHA=$(echo "${{ github.event.issue.title }} " | grep -oE '\([a-f0-9]{7}\)' | tr -d '()')
337+ SHORT_SHA=$(echo "$ISSUE_TITLE " | grep -oE '\([a-f0-9]{7}\)' | tr -d '()')
257338 if [ -n "$SHORT_SHA" ]; then
258339 # Get full SHA from short
259- FULL_SHA=$(gh api repos/${{ github.repository }} /commits/$SHORT_SHA --jq '.sha' 2>/dev/null || echo "")
340+ FULL_SHA=$(gh api " repos/$REPO_FULL_NAME /commits/$SHORT_SHA" --jq '.sha' 2>/dev/null || echo "")
260341 fi
261342 fi
262343
263344 if [ -n "$FULL_SHA" ]; then
264- gh api repos/${{ github.repository }}/statuses/$FULL_SHA \
345+ ISSUE_NUM=$(echo "$ISSUE_URL" | grep -oE '[0-9]+$')
346+
347+ # Swap labels idempotently (safe if tbdflow CLI already did this)
348+ gh issue edit "$ISSUE_NUM" --repo "$REPO_FULL_NAME" --remove-label "review-pending" 2>/dev/null || true
349+ gh issue edit "$ISSUE_NUM" --repo "$REPO_FULL_NAME" --add-label "review-accepted" 2>/dev/null || true
350+
351+ gh api "repos/$REPO_FULL_NAME/statuses/$FULL_SHA" \
265352 -f state='success' \
266353 -f context='peer-review' \
267- -f description='Non-blocking review completed ✅'
354+ -f description='Non-blocking review completed ✅' \
355+ -f target_url="$ISSUE_URL"
268356 echo "✅ Commit $FULL_SHA marked as reviewed"
269357 else
270358 echo "⚠️ Could not extract commit SHA from issue"
271- fi
272-
359+ fi
0 commit comments