diff --git a/.changeset/cozy-doodles-lick.md b/.changeset/cozy-doodles-lick.md new file mode 100644 index 0000000..660f14d --- /dev/null +++ b/.changeset/cozy-doodles-lick.md @@ -0,0 +1,4 @@ +--- +--- + +Add maintainer-triggered pollystellabot review automation. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ef069d7..8ef8a29 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,8 +40,8 @@ jobs: - name: Create release PR or publish uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d # v1 with: - version: pnpm changeset version && pnpm install --no-frozen-lockfile - publish: pnpm changeset publish + version: pnpm run release:version + publish: pnpm run release:publish commit: "ci: release" title: "ci: release" env: diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml new file mode 100644 index 0000000..bfd56f6 --- /dev/null +++ b/.github/workflows/review.yml @@ -0,0 +1,132 @@ +name: Review PR + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + +permissions: + contents: read + issues: read + pull-requests: read + +jobs: + review: + name: Run /review + if: >- + github.event.sender.type != 'Bot' + && contains(github.event.comment.body, '/review') + && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) + && (github.event_name != 'issue_comment' || github.event.issue.pull_request != null) + concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number || github.event.pull_request.number || github.ref }} + cancel-in-progress: false + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Resolve PR number + id: pr-number + env: + PR_NUMBER: ${{ github.event.issue.number || github.event.pull_request.number }} + run: echo "number=${PR_NUMBER}" >> "$GITHUB_OUTPUT" + + - name: Verify PR exists + id: verify-pr + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + PR_NUMBER: ${{ steps.pr-number.outputs.number }} + run: | + if gh api "/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" > /dev/null 2>&1; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + echo "::warning::PR #${PR_NUMBER} not found; skipping review." + fi + + - name: Checkout repository + if: steps.verify-pr.outputs.exists == 'true' + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 1 + persist-credentials: false + + - name: Setup Node.js + if: steps.verify-pr.outputs.exists == 'true' + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 24.x + + - name: Check pollystellabot secrets + if: steps.verify-pr.outputs.exists == 'true' + env: + POLYSTELLABOT_APP_ID: ${{ secrets.POLYSTELLABOT_APP_ID }} + POLYSTELLABOT_PRIVATE_KEY: ${{ secrets.POLYSTELLABOT_PRIVATE_KEY }} + run: | + if [[ -z "$POLYSTELLABOT_APP_ID" || -z "$POLYSTELLABOT_PRIVATE_KEY" ]]; then + echo "::error::Set POLYSTELLABOT_APP_ID and POLYSTELLABOT_PRIVATE_KEY as Actions secrets before using /review." + exit 1 + fi + + - name: Generate pollystellabot token + if: steps.verify-pr.outputs.exists == 'true' + id: pollystellabot-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + app-id: ${{ secrets.POLYSTELLABOT_APP_ID }} + private-key: ${{ secrets.POLYSTELLABOT_PRIVATE_KEY }} + permission-contents: read + permission-issues: write + permission-pull-requests: write + + - name: Install opencode + if: steps.verify-pr.outputs.exists == 'true' + run: npm install --global "opencode-ai@1.15.7" + + - name: Get PR details + if: steps.verify-pr.outputs.exists == 'true' + id: pr-details + env: + GH_TOKEN: ${{ steps.pollystellabot-token.outputs.token }} + GITHUB_REPOSITORY: ${{ github.repository }} + PR_NUMBER: ${{ steps.pr-number.outputs.number }} + run: | + gh api "/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" > /tmp/pr.json + { + echo 'title<> "$GITHUB_OUTPUT" + + - name: Run Review + if: steps.verify-pr.outputs.exists == 'true' + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_GATEWAY_ID: ${{ secrets.CLOUDFLARE_GATEWAY_ID }} + GH_TOKEN: ${{ steps.pollystellabot-token.outputs.token }} + GITHUB_TOKEN: ${{ steps.pollystellabot-token.outputs.token }} + MODEL: ${{ vars.POLYSTELLABOT_MODEL || 'opencode/claude-opus-4-5' }} + AGENT: auto-reviewer + SHARE: "false" + USE_GITHUB_TOKEN: "true" + MENTIONS: "/review" + OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} + OPENCODE_CONFIG_CONTENT: '{"permission":{"external_directory":{"*":"deny","/tmp/**":"allow","~/.local/share/opencode/tool-output/**":"allow"},"doom_loop":"deny"}}' + PROMPT: | + Review pull request #${{ steps.pr-number.outputs.number }} ("${{ steps.pr-details.outputs.title }}"). + + Follow your auto-reviewer instructions for investigation, severity calibration, and posting. Post a single GitHub PR review with line-anchored comments when you find actionable issues. If there are no actionable issues, respond with only "LGTM!" and do not post a PR review. + + ${{ steps.pr-number.outputs.number }} + ${{ steps.pr-details.outputs.head_sha }} + + ${{ steps.pr-details.outputs.body }} + + run: opencode github run diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml new file mode 100644 index 0000000..b1ceba1 --- /dev/null +++ b/.github/workflows/semgrep.yml @@ -0,0 +1,36 @@ +name: Semgrep OSS scan + +on: + pull_request: {} + push: + branches: [main, master] + workflow_dispatch: {} + schedule: + - cron: "0 0 20 * *" + +permissions: + contents: read + +concurrency: + group: semgrep-${{ github.event_name }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + semgrep: + name: semgrep-oss + runs-on: ubuntu-slim + timeout-minutes: 10 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 1 + persist-credentials: false + - id: cache-semgrep + uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5 + with: + path: ~/.local + key: semgrep-1.160.0-${{ runner.os }} + - if: steps.cache-semgrep.outputs.cache-hit != 'true' + run: pip install --user semgrep==1.160.0 + - run: echo "$HOME/.local/bin" >> "$GITHUB_PATH" + - run: semgrep scan --config=auto diff --git a/.opencode/agents/auto-reviewer.md b/.opencode/agents/auto-reviewer.md new file mode 100644 index 0000000..beb606c --- /dev/null +++ b/.opencode/agents/auto-reviewer.md @@ -0,0 +1,75 @@ +--- +description: Automated PR reviewer for PolyStella's /review workflow. Used in CI to leave structured pull request feedback, not for interactive local development. +mode: primary +temperature: 0.1 +permission: + read: allow + glob: allow + grep: allow + list: allow + edit: deny + task: deny + webfetch: deny + websearch: deny + question: deny + doom_loop: deny + external_directory: + "*": deny + "/tmp/**": allow + "~/.local/share/opencode/tool-output/**": allow + bash: + "*": deny + "gh api *": allow + "gh pr diff *": allow + "gh pr view *": allow + "git diff*": allow + "git log*": allow + "git show*": allow + "git status*": allow +--- + +You are reviewing a pull request on the **cloudflare/polystella** repository. Your job is to find real bugs, behavioral regressions, security issues, violated repository invariants, and missing tests. Do not make code changes. + +The repo's AGENTS.md is loaded separately. Read it carefully and treat violations as first-class findings. Pay special attention to strict TypeScript rules, cache-key stability, apply-before-PUT, local cache index isolation, runtime bridge timing, URL rewrite idempotence, path separator semantics, provider error classification, and UI-string token preservation. + +## Investigation + +1. Start with the PR description and changed files. Verify the description matches the diff. +2. Read the full diff with `gh pr diff --repo cloudflare/polystella`. +3. For every changed source or test file, inspect enough surrounding code to understand behavior, not just the diff hunk. +4. Trace call sites and sibling implementations when a public type, runtime API, parser adapter, cache path, route shim, CLI command, or translation flow changes. +5. Check tests. Production behavior changes should have meaningful coverage, especially translation pipeline, cache/storage, runtime, routing, parser, and UI-string changes. +6. Do not run package scripts or execute PR-authored code. This workflow reviews code only. + +## Findings + +Use calibrated severity: + +- **Needs fixing** for logic bugs, regressions, security issues, broken contracts, missing required tests, or AGENTS.md invariant violations. +- **Suggestion** for low-risk maintainability, clarity, or style issues. + +Each finding should be concrete and anchored to a changed line. Explain what currently happens, why it is wrong, and what would fix it. Use GitHub suggestion blocks only when the replacement is obvious and safe. + +Be willing to find nothing. If the PR is sound, respond with exactly `LGTM!`. + +## Posting + +When you find issues, post one GitHub PR review via `gh api`: + +```bash +gh api repos/cloudflare/polystella/pulls//reviews \ + -X POST \ + --input - </dev/null || true)", - "prepublishOnly": "pnpm build && pnpm build:llms" + "prepublishOnly": "pnpm build && pnpm build:llms", + "release:version": "changeset version && pnpm install --no-frozen-lockfile", + "release:publish": "changeset publish" }, "dependencies": { "acorn": "^8.17.0",