11name : PR Review
22
3- # Trusted CHATOPS-ONLY deployment of the consolidated OpenRouter review action
4- # (lean4repo-utils/review@main). Invoked by a maintainer commenting `/review` on a PR.
5- # The auto pull_request trigger is intentionally dropped for now.
3+ # Review runs ONLY on demand, via a `/review` comment from a repo member. It is
4+ # deliberately NOT run on PR open: the review path builds and elaborates the
5+ # PR's Lean code with the OpenRouter secret and a write token in scope, so until
6+ # the two-stage secret-free split (S2) lands, running it is safe only for code a
7+ # trusted maintainer has chosen to review. See the review action README.
68on :
79 issue_comment :
810 types : [created]
911
1012concurrency :
1113 group : ${{ github.workflow }}-${{ github.event.issue.number }}
12- cancel-in-progress : false
13-
14- # Least privilege: the reviewer's lean_tools execute model-directed Lean IO and the
15- # action builds the PR-head code, so a token exfiltrated there must not be able to push
16- # or merge. Only what posting a review needs.
17- permissions :
18- contents : read
19- pull-requests : write
20- issues : write
14+ cancel-in-progress : true
2115
2216jobs :
2317 review :
24- # Chatops only, gated to trusted members. NOTE: `/review` here is a coarse prefix
25- # match — a member typing `/reviewer` would also match; harmless under the member
26- # gate, tighten to a word boundary if this ever opens to untrusted commenters.
18+ # The COMMENT author (not the PR author) is the trust boundary.
2719 if : >-
20+ github.event_name == 'issue_comment' &&
2821 github.event.issue.pull_request &&
2922 startsWith(github.event.comment.body, '/review') &&
3023 (
@@ -33,99 +26,35 @@ jobs:
3326 github.event.comment.author_association == 'COLLABORATOR'
3427 )
3528 runs-on : ubuntu-latest
36- timeout-minutes : 240
29+ timeout-minutes : 120
30+ permissions :
31+ contents : read
32+ pull-requests : write
3733 steps :
38- # Parse the /review comment: `Internal:` lines → manual spec/KB refs, everything
39- # under `External:`/`Comments:` → free-text instructions (the action auto-extracts
40- # any URLs it mentions). Comment body is passed via env, never interpolated into
41- # the run: body (GHA expression-injection guard).
42- - name : Extract arguments from comment
34+ # Everything after `/review` is freeform focus text; URLs and existing
35+ # repo paths it mentions become review context automatically.
36+ - name : Extract instructions from /review comment
4337 id : get_args
4438 env :
4539 COMMENT_BODY : ${{ github.event.comment.body }}
46- shell : bash
4740 run : |
4841 EOF=$(openssl rand -hex 8)
49- awk -v eof="$EOF" -v gh_out="$GITHUB_OUTPUT" '
50- BEGIN { spec=""; com=""; section="" }
51- /^External:/ { section="com"; next }
52- /^Internal:/ { section="spec"; next }
53- /^Comments:/ { section="com"; next }
54- {
55- gsub(/\r/, "", $0); gsub(/^[ \t]+|[ \t]+$/, "", $0);
56- if ($0 == "" || $0 == "/review") { next }
57- if (section == "spec") { sub(/^- +/, ""); spec = (spec=="" ? $0 : spec "," $0) }
58- else if (section == "com") { com = (com=="" ? $0 : com "\n" $0) }
59- }
60- END {
61- printf "manual_spec_refs=%s\n", spec >> gh_out
62- printf "additional_comments<<%s\n%s\n%s\n", eof, com, eof >> gh_out
63- }
64- ' <<< "$COMMENT_BODY"
65-
66- # Unguided KB grounding: map the PR''s changed .lean files to the papers they cite
67- # (docs/kb/_generated/lean-citations.json) and pass those KB pages as spec_refs, so
68- # a plain /review is grounded in the right paper summaries with no manual guidance.
69- # Read the citation JOIN from the BASE ref (data-only, fork-safe); do NOT execute
70- # any checked-out repo script. (The KB page CONTENT is read by the action from the
71- # PR head — acceptable for this trusted, manually-inspected pilot; base-ref content
72- # reading is part of the S2/S7 hardening.)
73- - name: Resolve KB context (base-ref citation join)
74- id: kb
75- env:
76- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
77- PR_NUMBER: ${{ github.event.issue.number }}
78- MANUAL_SPEC_REFS: ${{ steps.get_args.outputs.manual_spec_refs }}
42+ {
43+ echo "instructions<<$EOF"
44+ printf '%s\n' "$COMMENT_BODY" | sed -E '1s|^/review[[:space:]]*||'
45+ echo "$EOF"
46+ } >> "$GITHUB_OUTPUT"
7947 shell : bash
80- run: |
81- set -euo pipefail
82- BASE_SHA="$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" --jq '.base.sha')"
83- gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/files" --paginate --jq '.[].filename' > /tmp/changed.txt
84- if gh api "repos/$GITHUB_REPOSITORY/contents/docs/kb/_generated/lean-citations.json?ref=$BASE_SHA" \
85- --jq '.content' 2>/dev/null | base64 -d > /tmp/citations.json; then :; else
86- echo "No lean-citations.json on base ref; proceeding without KB spec_refs."
87- echo '{}' > /tmp/citations.json
88- fi
89- # Join is pure data: filenames + JSON read from files, never interpolated.
90- spec_refs="$(python3 - <<'PY'
91- import json, os
92- try:
93- cites = json.load(open('/tmp/citations.json')).get('files', {})
94- except Exception:
95- cites = {}
96- changed = [l.strip() for l in open('/tmp/changed.txt') if l.strip().endswith('.lean')]
97- keys = []
98- for f in changed:
99- for k in cites.get(f, []):
100- if k not in keys:
101- keys.append(k)
102- refs = [f"docs/kb/papers/{k}.md" for k in keys]
103- # Merge any manual `Internal:` refs from the /review comment (dedup, keep order).
104- for r in (os.environ.get("MANUAL_SPEC_REFS", "") or "").split(","):
105- r = r.strip()
106- if r and r not in refs:
107- refs.append(r)
108- print(",".join(refs))
109- PY
110- )"
111- echo "spec_refs=$spec_refs" >> "$GITHUB_OUTPUT"
112- echo "Resolved KB spec_refs: ${spec_refs:-(none)}"
11348
114- - name: AI review
115- uses: alexanderlhicks/lean4repo-utils/review@main
49+ - uses : alexanderlhicks/lean4repo-utils/review@0.3
11650 with :
11751 github_token : ${{ secrets.GITHUB_TOKEN }}
11852 api_key : ${{ secrets.OPENROUTER_KEY }}
11953 pr_number : ${{ github.event.issue.number }}
120- # Auto-pulled KB paper pages for the changed files + any manual Internal: refs.
121- spec_refs: ${{ steps.kb.outputs.spec_refs }}
122- additional_comments: ${{ steps.get_args.outputs.additional_comments }}
123- # Coverage-first budget control: this sizes/trims prompts for large PRs,
124- # but advisory mode does not stop later files from being reviewed.
125- # Source these from workflow config, never PR content.
126- # llm_max_run_tokens: "400000"
127- # llm_budget_mode: advisory
128- # model: omitted → the action's default OpenRouter slug. Set an explicit slug
129- # here if you want a specific model for the pilot.
130- # lean_tools defaults true (full-quality, compiler-grounded review — trusted
131- # pilot). enable_web_search defaults false (web-search fees are not token-bounded).
54+ # URLs and repo paths mentioned in the /review comment are extracted
55+ # into external/spec/repo context automatically.
56+ additional_comments : " ${{ steps.get_args.outputs.instructions }}"
57+ # Everything else uses the action defaults (deep agents on z-ai/glm-5.2,
58+ # verification on deepseek/deepseek-v4-pro, lean_tools + verify_findings
59+ # on). See the review action README to override models or tune
60+ # spec_refs / escape_hatch_allowlist / dependent_impact_max.
0 commit comments