/
/
/
1name: Automated PR Review (manual)
2
3# Reviews a PR against the project's coding standards and posts the result as an automated review.
4# Manual-only for now (workflow_dispatch) so it can be run against existing PRs to evaluate the
5# output before enabling it automatically. To make it automatic later, add a `pull_request`
6# trigger and disable Copilot's auto-review ruleset.
7
8on: # yamllint disable-line rule:truthy
9 workflow_dispatch:
10 inputs:
11 pr:
12 description: "PR number to review"
13 required: true
14
15permissions:
16 contents: read
17 pull-requests: write
18 copilot-requests: write
19
20jobs:
21 pr-review:
22 runs-on: ubuntu-latest
23 steps:
24 - name: Check out the repo
25 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
26 with:
27 persist-credentials: false
28
29 - name: Install GitHub Copilot CLI
30 run: npm install -g @github/copilot
31
32 # Mint a token that posts the review as musicassistant-bot[bot]. Model access is separate
33 # (the built-in token + copilot-requests below). Pinned to the SHA the repo already uses.
34 - name: Create Music Assistant Bot token
35 id: bot
36 uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
37 with:
38 client-id: ${{ vars.MUSIC_ASSISTANT_BOT_CLIENT_ID }}
39 private-key: ${{ secrets.MUSIC_ASSISTANT_BOT_PRIVATE_KEY }}
40
41 # The standards doc is read in place at .github/pr-review/standards.md. The corpus of past
42 # review discussions is committed gzipped (public PR/issue comments); decompress it for grep.
43 - name: Decompress the review corpus (bundled in-repo)
44 run: gunzip -c .github/pr-review/review-corpus.jsonl.gz > review-corpus.jsonl
45
46 - name: Get PR diff
47 env:
48 GH_TOKEN: ${{ github.token }}
49 # Passed via env and validated, never inlined into the shell command (untrusted input).
50 PR: ${{ inputs.pr }}
51 run: |
52 case "$PR" in
53 ''|*[!0-9]*)
54 echo "::error::pr input must be a positive integer"
55 exit 1
56 ;;
57 esac
58 gh pr diff "$PR" --repo "$GITHUB_REPOSITORY" > pr.diff
59
60 - name: Review via Copilot CLI
61 env:
62 # Model access: with `copilot-requests: write` (permissions above), the built-in token
63 # authenticates Copilot inference and billing flows to the ORG's Copilot plan â no seat
64 # PAT. Requires an org-owned repo with the org's Copilot policy enabled. On a personal
65 # fork this won't bill the org; set this to a Copilot-seated PAT instead.
66 COPILOT_GITHUB_TOKEN: ${{ github.token }}
67 # Set repo/org variable REVIEW_MODEL to your exact Claude id (`copilot` -> /model to list).
68 REVIEW_MODEL: ${{ vars.REVIEW_MODEL || 'claude-opus-4.8' }}
69 run: |
70 cat > prompt.txt <<'EOF'
71 You are an automated PR reviewer for this project. Read .github/pr-review/standards.md for
72 the project's coding standards and the review protocol, then review the diff in ./pr.diff.
73 Walk the WHOLE diff. When a standard does not cover something you see, grep
74 ./review-corpus.jsonl (case-insensitive keyword alternation) for a prior project decision
75 and cite its link. Present every finding as a project standard â do NOT name individual
76 maintainers. When a finding's fix is small and self-contained â a correct edit to line(s)
77 already in the diff (a log call where a logger is in scope, wrapping a return,
78 `raise X from err`, a guard clause) â set "suggestion" to the exact replacement for the
79 line at "line" (keep indentation, no diff +/- prefix; may span multiple lines), and read
80 surrounding code so it is idiomatic. Leave "suggestion" null when unsure it is exactly
81 right, or when the fix needs a new file, a new import elsewhere, or edits in more than one
82 place. For a missing-tests finding you MAY set "scaffold" to a short pytest starter
83 (imports + 1-2 test functions with realistic names and assertions) â a copy-paste
84 starting point, NOT applied automatically â otherwise null.
85 Output ONLY a JSON array of findings; each element:
86 {"severity":"CRITICAL|PROBLEM|SUGGESTION","path":"file/path","line":<int or null>,"issue":"1-2 sentences","principle":"the project standard","citation_url":"source link","suggestion":"<replacement, or null>","scaffold":"<pytest starter, or null>"}
87 No prose outside the JSON array. Never invent a standard or citation.
88 EOF
89 # Minimal tools: read (diff + standards) and grep (history search) only â no write, no network.
90 copilot -p "$(cat prompt.txt)" -s --model "$REVIEW_MODEL" \
91 --allow-tool 'shell(grep:*)' --allow-tool 'read' \
92 --no-ask-user | tee findings.json
93
94 - name: Post the review as the bot
95 env:
96 GH_TOKEN: ${{ steps.bot.outputs.token }}
97 REPO: ${{ github.repository }}
98 PR_NUMBER: ${{ inputs.pr }}
99 run: python3 .github/scripts/post_pr_review.py findings.json
100