Skip to content
246 changes: 246 additions & 0 deletions .github/workflows/continue-pr-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
name: Continue PR Review

# Security: this workflow uses a repository secret to configure opub/Continue.
# It is intentionally limited to non-fork pull_request events. Do not switch this
# workflow to pull_request_target while checking out or executing PR code.
# This job must not check out or execute PR-head code before secret-bearing steps;
# it reviews metadata/diff only from the trusted base checkout plus GitHub API.
# If fork support is needed later, split trusted/internal and untrusted/fork paths.

on:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review

permissions:
contents: read
pull-requests: write
issues: write

jobs:
fork-notice:
if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.fork == true
runs-on: ubuntu-24.04
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
GH_TOKEN: ${{ github.token }}
steps:
- name: Upsert fork notice comment
run: |
set -euo pipefail

COMMENT_FILE="$RUNNER_TEMP/pr-comment.md"
MARKER="<!-- continue-pr-review -->"

{
echo "$MARKER"
echo "## Continue PR Review"
echo
echo "_Continue review is only run automatically for trusted non-fork pull requests because this workflow requires repository secrets._"
echo
echo "- This PR comes from a fork, so the secret-backed review job was skipped by design."
echo "- Maintainers can run an internal review workflow or review locally if needed."
} > "$COMMENT_FILE"

EXISTING_COMMENT_ID="$({
gh api \
"/repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" \
--paginate \
--jq '.[] | select(.user.login == "github-actions[bot]" and (.body | contains("<!-- continue-pr-review -->"))) | .id' \
| tail -n1
} || true)"

if [ -n "$EXISTING_COMMENT_ID" ]; then
gh api \
--method PATCH \
"/repos/$GITHUB_REPOSITORY/issues/comments/$EXISTING_COMMENT_ID" \
--field body="$(cat "$COMMENT_FILE")"
else
gh pr comment "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --body-file "$COMMENT_FILE"
fi

continue-review:
if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.fork == false
runs-on: ubuntu-24.04
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.sha }}
fetch-depth: 1

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: Initialize workflow environment
run: |
echo "OPUB_HOME=$RUNNER_TEMP/opub" >> "$GITHUB_ENV"
echo "REVIEW_FILE=$RUNNER_TEMP/continue-review.md" >> "$GITHUB_ENV"
echo "SANITIZED_REVIEW_FILE=$RUNNER_TEMP/continue-review-sanitized.md" >> "$GITHUB_ENV"

- name: Install GitHub CLI
run: |
type -p gh >/dev/null || {
sudo apt-get update
sudo apt-get install -y gh
}

- name: Install opub
run: curl -fsSL https://opub.dev/install.sh | sh

- name: Add installed tools to PATH
run: |
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
echo "$HOME/go/bin" >> "$GITHUB_PATH"

- name: Install Continue CLI
run: npm install -g @continuedev/cli@1.5.45

- name: Verify installed CLIs
run: |
opub --version
cn --help >/dev/null
gh --version

- name: Configure opub for Continue
env:
OPUB_PROVIDER_KEY: ${{ secrets.CONTINUE_CI_OPUB }}
run: |
test -n "$OPUB_PROVIDER_KEY"
printf '%s\n' "$OPUB_PROVIDER_KEY" | opub setup continue \
--project 'opubdev/opub-cli' \
--project-id 4 \
--compute-key-id 3 \
--api-key-hash '231ed9ad...90f1e7d8' \
--insecure-storage \
--provider-key-stdin


- name: Fetch PR metadata and diff
run: |
set -euo pipefail

DIFF_FILE="$RUNNER_TEMP/pr.diff"
CHANGED_FILE_LIST="$RUNNER_TEMP/changed-files.txt"
gh api \
-H "Accept: application/vnd.github.v3.diff" \
"/repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" > "$DIFF_FILE"
gh api \
"/repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/files" \
--paginate \
--jq '.[].filename' > "$CHANGED_FILE_LIST"

- name: Run Continue review
run: |
set -euo pipefail

DIFF_FILE="$RUNNER_TEMP/pr.diff"
CHANGED_FILE_LIST="$RUNNER_TEMP/changed-files.txt"
REVIEW_INPUT="$RUNNER_TEMP/review-input.txt"
REVIEW_PROMPT_FILE="$RUNNER_TEMP/review-prompt.txt"
MAX_DIFF_BYTES=120000
MAX_CHANGED_FILES=250

DIFF_BYTES=$(wc -c < "$DIFF_FILE" | tr -d '[:space:]')
CHANGED_FILES_COUNT=$(wc -l < "$CHANGED_FILE_LIST" | tr -d '[:space:]')
DIFF_TRUNCATED=false

if [ "$CHANGED_FILES_COUNT" -gt "$MAX_CHANGED_FILES" ]; then
echo "PR changes $CHANGED_FILES_COUNT files; max supported is $MAX_CHANGED_FILES for automatic review." >&2
exit 1
fi

if [ "$DIFF_BYTES" -gt "$MAX_DIFF_BYTES" ]; then
python3 -c 'import pathlib, sys; path = pathlib.Path(sys.argv[1]); limit = int(sys.argv[2]); data = path.read_text(encoding="utf-8", errors="replace"); path.write_text(data[:limit], encoding="utf-8")' "$DIFF_FILE" "$MAX_DIFF_BYTES"
DIFF_TRUNCATED=true
fi

cat > "$REVIEW_PROMPT_FILE" <<'PROMPT'
Review this pull request diff for bugs, regressions, security issues, and missing tests.
Focus especially on opub invariants around secrets, MCP response discipline, session linking, and target completeness.
Keep the review concise and practical.
The review input was collected without checking out PR-head code; reason only from the provided diff and changed file list.
If the input says the diff was truncated, mention that limitation in the summary and avoid overclaiming coverage.
Output markdown with these sections exactly:

## Summary

## Findings
- Use bullets. If there are no issues, write `- No actionable issues found.`

## Suggested follow-ups
- Use bullets. If none, write `- None.`
PROMPT

{
echo "You are reviewing PR #$PR_NUMBER for repository $GITHUB_REPOSITORY."
echo
echo "Base branch: ${{ github.event.pull_request.base.ref }}"
echo "Base SHA checked out in this job: ${{ github.event.pull_request.base.sha }}"
echo "PR head SHA under review: ${{ github.event.pull_request.head.sha }}"
echo
echo "Changed files count: $CHANGED_FILES_COUNT"
echo "Diff bytes before truncation: $DIFF_BYTES"
echo "Diff truncated: $DIFF_TRUNCATED"
echo
echo "Changed files:"
cat "$CHANGED_FILE_LIST"
echo
echo "Diff:"
cat "$DIFF_FILE"
echo
cat "$REVIEW_PROMPT_FILE"
} > "$REVIEW_INPUT"

opub run continue -- \
-p "$(cat "$REVIEW_INPUT")" \
--silent > "$REVIEW_FILE"

test -s "$REVIEW_FILE"

- name: Upsert PR comment
run: |
set -euo pipefail

COMMENT_FILE="$RUNNER_TEMP/pr-comment.md"
MARKER="<!-- continue-pr-review -->"

python3 -c 'import pathlib, re, sys; source = pathlib.Path(sys.argv[1]).read_text(encoding="utf-8", errors="replace"); source = re.sub(r"(^|[^\\w`])@([A-Za-z0-9][A-Za-z0-9_-]*(?:/[A-Za-z0-9][A-Za-z0-9_-]*)?)", lambda m: f"{m.group(1)}@\u200b{m.group(2)}", source); source = re.sub(r"(?<![`\\\\])#(\\d+)", "@\u200b#\\1", source); pathlib.Path(sys.argv[2]).write_text(source, encoding="utf-8")' "$REVIEW_FILE" "$SANITIZED_REVIEW_FILE"

{
echo "$MARKER"
echo "## Continue PR Review"
echo
echo "_Automated review via Continue CLI run through opub._"
echo
cat "$SANITIZED_REVIEW_FILE"
} > "$COMMENT_FILE"

EXISTING_COMMENT_ID="$({
gh api \
"/repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" \
--paginate \
--jq '.[] | select(.user.login == "github-actions[bot]" and (.body | contains("<!-- continue-pr-review -->"))) | .id' \
| tail -n1
} || true)"

if [ -n "$EXISTING_COMMENT_ID" ]; then
gh api \
--method PATCH \
"/repos/$GITHUB_REPOSITORY/issues/comments/$EXISTING_COMMENT_ID" \
--field body="$(cat "$COMMENT_FILE")"
else
gh pr comment "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --body-file "$COMMENT_FILE"
fi
Loading
Loading