Skip to content

Commit c30bec8

Browse files
committed
docs: updated nbr-review workflow example
Intent Log: - Pre-sync safety snapshot
1 parent aa17025 commit c30bec8

1 file changed

Lines changed: 140 additions & 53 deletions

File tree

docs/nbr-review.yml.example

Lines changed: 140 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,30 @@
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

2145
name: Non-Blocking Review
2246

@@ -46,6 +70,13 @@ on:
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
5182
concurrency:
@@ -55,6 +86,14 @@ concurrency:
5586
jobs:
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

Comments
 (0)