tbdflow 0.27.0

A CLI to streamline your Git workflow for Trunk-Based Development.
Documentation
# Non-Blocking Review (NBR) Workflow for Trunk-Based Development
#
# Inspired by Martin's "Trunktopus" pattern for server-side review management.
#
# This workflow provides:
# - Automatic issue creation for every push to main (audit trail)
# - Commit status gates (pending → success) for deploy blocking
# - Inline diffs for small changes (< 60 lines)
# - Multi-reviewer support via checklist
#
# To use this workflow:
# 1. Rename this file to `nbr-review.yml`
# 2. Configure your .tbdflow.yml:
#    review:
#      enabled: true
#      strategy: github-workflow
#      workflow: nbr-review.yml
#      default_reviewers:
#        - reviewer-username

name: Non-Blocking Review

on:
  # Triggered by tbdflow CLI
  workflow_dispatch:
    inputs:
      commit_sha:
        description: 'The full commit SHA to review'
        required: true
        type: string
      commit_message:
        description: 'The commit message'
        required: true
        type: string
      author:
        description: 'The commit author'
        required: true
        type: string
      reviewers:
        description: 'Comma-separated list of reviewers'
        required: false
        type: string
        default: ''

  # Also trigger on every push to main for full audit trail
  push:
    branches:
      - main

# Prevent duplicate issues from rapid pushes
concurrency:
  group: nbr-review-${{ github.sha }}
  cancel-in-progress: false

jobs:
  create-review:
    runs-on: ubuntu-latest
    permissions:
      issues: write
      statuses: write
      contents: read

    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 2  # Need parent commit for diff

      - name: Get commit info
        id: commit-info
        run: |
          if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
            echo "sha=${{ inputs.commit_sha }}" >> $GITHUB_OUTPUT
            echo "message=${{ inputs.commit_message }}" >> $GITHUB_OUTPUT
            echo "author=${{ inputs.author }}" >> $GITHUB_OUTPUT
            echo "reviewers=${{ inputs.reviewers }}" >> $GITHUB_OUTPUT
          else
            echo "sha=${{ github.sha }}" >> $GITHUB_OUTPUT
            echo "message=$(git log -1 --format=%s)" >> $GITHUB_OUTPUT
            echo "author=$(git log -1 --format=%an)" >> $GITHUB_OUTPUT
            echo "reviewers=" >> $GITHUB_OUTPUT
          fi

      - name: Check for existing review issue
        id: existing
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          SHORT_SHA="${{ steps.commit-info.outputs.sha }}"
          SHORT_SHA="${SHORT_SHA:0:7}"

          EXISTING=$(gh issue list --state open --label "author-issue" --search "$SHORT_SHA in:title" --json number --jq '.[0].number // empty')

          if [ -n "$EXISTING" ]; then
            echo "exists=true" >> $GITHUB_OUTPUT
            echo "issue_number=$EXISTING" >> $GITHUB_OUTPUT
            echo "Review issue #$EXISTING already exists for $SHORT_SHA"
          else
            echo "exists=false" >> $GITHUB_OUTPUT
          fi

      - name: Set pending commit status
        if: steps.existing.outputs.exists != 'true'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh api repos/${{ github.repository }}/statuses/${{ steps.commit-info.outputs.sha }} \
            -f state='pending' \
            -f context='peer-review' \
            -f description='Awaiting non-blocking review'
          echo "✅ Commit status set to pending"

      - name: Generate inline diff (for small changes)
        if: steps.existing.outputs.exists != 'true'
        id: diff
        run: |
          DIFF=$(git show --no-color --stat --patch --no-prefix ${{ steps.commit-info.outputs.sha }} | tail -n +2)
          DIFF_LINES=$(echo "$DIFF" | wc -l)

          if [ "$DIFF_LINES" -le 60 ]; then
            echo "include_diff=true" >> $GITHUB_OUTPUT
            echo "diff_lines=$DIFF_LINES" >> $GITHUB_OUTPUT
            # Escape for GitHub Actions
            DIFF_ESCAPED=$(echo "$DIFF" | sed 's/`/\\`/g')
            EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
            echo "diff<<$EOF" >> $GITHUB_OUTPUT
            echo "$DIFF" >> $GITHUB_OUTPUT
            echo "$EOF" >> $GITHUB_OUTPUT
          else
            echo "include_diff=false" >> $GITHUB_OUTPUT
            echo "diff_lines=$DIFF_LINES" >> $GITHUB_OUTPUT
          fi

      - name: Ensure labels exist
        if: steps.existing.outputs.exists != 'true'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh label create "peer-review" --color "1D76DB" --description "Peer review of commit" 2>/dev/null || true
          gh label create "author-issue" --color "5319E7" --description "Author issue for commit review" 2>/dev/null || true

      - name: Create review issue
        if: steps.existing.outputs.exists != 'true'
        id: create-issue
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          SHA="${{ steps.commit-info.outputs.sha }}"
          SHORT_SHA="${SHA:0:7}"
          MESSAGE="${{ steps.commit-info.outputs.message }}"
          AUTHOR="${{ steps.commit-info.outputs.author }}"
          REVIEWERS="${{ steps.commit-info.outputs.reviewers }}"
          REPO_URL="https://github.com/${{ github.repository }}"

          # Build diff section
          if [ "${{ steps.diff.outputs.include_diff }}" == "true" ]; then
            DIFF_SECTION="

          <details>
          <summary>📝 Inline diff (${{ steps.diff.outputs.diff_lines }} lines)</summary>

          \`\`\`diff
          ${{ steps.diff.outputs.diff }}
          \`\`\`

          </details>"
          else
            DIFF_SECTION="

          > 📄 Large change (${{ steps.diff.outputs.diff_lines }} lines) - [view full diff]($REPO_URL/commit/$SHA)"
          fi

          # Build reviewer checklist
          REVIEWER_CHECKLIST=""
          if [ -n "$REVIEWERS" ]; then
            IFS=',' read -ra REVIEWER_ARRAY <<< "$REVIEWERS"
            for reviewer in "${REVIEWER_ARRAY[@]}"; do
              reviewer=$(echo "$reviewer" | xargs)  # trim whitespace
              if [ -n "$reviewer" ]; then
                REVIEWER_CHECKLIST="$REVIEWER_CHECKLIST
          - [ ] @$reviewer"
              fi
            done
          fi

          BODY="## Non-Blocking Review Request

          **Commit:** [\`$SHORT_SHA\`]($REPO_URL/commit/$SHA)
          **Author:** $AUTHOR
          **Message:** $MESSAGE
          $DIFF_SECTION

          ---

          > In Trunk-Based Development, this code is already in the trunk.
          > Your goal is **Course Correction** and **Knowledge Sharing**, not gatekeeping.

          ### 🔍 What to Look For

          | Focus | Question |
          |-------|----------|
          | **Design & Intent** | Does the implementation align with our architectural patterns? |
          | **Logic & Edge Cases** | Are there logical flaws or unhappy paths that tests might miss? |
          | **Readability** | Are names descriptive? (Code as Documentation) |
          | **Simplification** | Can this be done with less code or lower complexity? |

          ### 💬 How to Comment

          - **Questions > Commands**: _\"Could we use the existing helper here?\"_ instead of _\"Change this.\"_
          - **Praise**: If you see something clever or clean, say so! NBR boosts team morale.
          - **Nitpicking**: Label minor style issues as \`(nit)\` so the author knows they're optional.

          ### ✅ Reviewer Checklist
          $REVIEWER_CHECKLIST

          ### When to Close

          - **Approve & Close**: Code is safe and understandable.
          - **Comment & Close**: Non-critical improvements noted.
          - **Keep Open**: Only for critical bugs or security flaws requiring immediate fix-forward.

          ---

          To approve via CLI: \`tbdflow review --approve $SHORT_SHA\`"

          # Create the issue
          ISSUE_URL=$(gh issue create \
            --title "[Review] $MESSAGE ($SHORT_SHA)" \
            --body "$BODY" \
            --label "peer-review,author-issue")

          ISSUE_NUM=$(echo "$ISSUE_URL" | grep -oE '[0-9]+$')
          echo "issue_number=$ISSUE_NUM" >> $GITHUB_OUTPUT
          echo "✅ Created review issue #$ISSUE_NUM: $ISSUE_URL"

  # Update commit status when review issue is closed
  on-review-closed:
    runs-on: ubuntu-latest
    if: github.event_name == 'issues' && github.event.action == 'closed' && contains(github.event.issue.labels.*.name, 'author-issue')
    permissions:
      statuses: write
      issues: read

    steps:
      - name: Extract commit SHA and mark as reviewed
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          BODY="${{ github.event.issue.body }}"

          # Extract commit SHA from issue body (looks for /commit/SHA pattern)
          FULL_SHA=$(echo "$BODY" | grep -oE 'github\.com/.*/commit/[a-f0-9]+' | grep -oE '[a-f0-9]{40}' | head -n1)

          if [ -z "$FULL_SHA" ]; then
            # Try short SHA from title
            SHORT_SHA=$(echo "${{ github.event.issue.title }}" | grep -oE '\([a-f0-9]{7}\)' | tr -d '()')
            if [ -n "$SHORT_SHA" ]; then
              # Get full SHA from short
              FULL_SHA=$(gh api repos/${{ github.repository }}/commits/$SHORT_SHA --jq '.sha' 2>/dev/null || echo "")
            fi
          fi

          if [ -n "$FULL_SHA" ]; then
            gh api repos/${{ github.repository }}/statuses/$FULL_SHA \
              -f state='success' \
              -f context='peer-review' \
              -f description='Non-blocking review completed ✅'
            echo "✅ Commit $FULL_SHA marked as reviewed"
          else
            echo "⚠️ Could not extract commit SHA from issue"
          fi