diff --git a/.github/codex/prompts/review.md b/.github/codex/prompts/review.md new file mode 100644 index 0000000..47519ba --- /dev/null +++ b/.github/codex/prompts/review.md @@ -0,0 +1,19 @@ +You are an expert reviewer for this repository. + +Review the current pull request changes and provide actionable feedback focused on: +- Correctness and behavioral regressions +- Security risks and unsafe patterns +- Performance issues +- Missing or weak test coverage +- Maintainability concerns likely to cause production defects + +Rules: +- Review only the PR changes (not unrelated code). +- Prioritize high-signal findings over style nits. +- For each finding, include: + - Severity: `critical`, `high`, `medium`, or `low` + - File path(s) + - Why it matters + - Concrete fix recommendation +- If no meaningful issues are found, say: `No blocking issues found.` +- Keep the response concise and in Markdown. diff --git a/.github/workflows/codex-code-review.yml b/.github/workflows/codex-code-review.yml new file mode 100644 index 0000000..b404a1e --- /dev/null +++ b/.github/workflows/codex-code-review.yml @@ -0,0 +1,140 @@ +name: Codex Code Review + +on: + pull_request: + types: [opened, synchronize] + +concurrency: + group: codex-code-review-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + codex-review: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + issues: write + env: + CODEX_MODEL: gpt-5.3-codex + CODEX_FALLBACK_MODEL: gpt-5.2-codex + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: refs/pull/${{ github.event.pull_request.number }}/merge + fetch-depth: 1 + + - name: Pre-fetch base and head refs + run: | + git fetch --no-tags origin \ + ${{ github.event.pull_request.base.ref }} \ + +refs/pull/${{ github.event.pull_request.number }}/head + + - name: Resolve Codex model + id: resolve_model + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + run: | + set -euo pipefail + + if [ -z "${OPENAI_API_KEY}" ]; then + echo "::error::Missing OPENAI_API_KEY secret." + exit 1 + fi + + if curl -fsS "https://api.openai.com/v1/models/${CODEX_MODEL}" \ + -H "Authorization: Bearer ${OPENAI_API_KEY}" \ + -H "Content-Type: application/json" >/dev/null; then + selected_model="${CODEX_MODEL}" + else + echo "::warning::${CODEX_MODEL} is unavailable for this API key; using ${CODEX_FALLBACK_MODEL}." + selected_model="${CODEX_FALLBACK_MODEL}" + fi + + echo "selected_model=${selected_model}" >> "${GITHUB_OUTPUT}" + + - name: Run Codex Code Review + id: codex_review + uses: openai/codex-action@v1 + with: + openai-api-key: ${{ secrets.OPENAI_API_KEY }} + model: ${{ steps.resolve_model.outputs.selected_model }} + prompt-file: .github/codex/prompts/review.md + output-file: codex-output.md + safety-strategy: drop-sudo + sandbox: workspace-write + + - name: Post or update Codex sticky comment + if: steps.codex_review.outputs['final-message'] != '' + uses: actions/github-script@v7 + env: + CODEX_REVIEW_BODY: ${{ steps.codex_review.outputs['final-message'] }} + CODEX_MODEL: ${{ steps.resolve_model.outputs.selected_model }} + with: + github-token: ${{ github.token }} + script: | + const marker = ''; + const owner = context.repo.owner; + const repo = context.repo.repo; + const issue_number = context.payload.pull_request.number; + const reviewBody = (process.env.CODEX_REVIEW_BODY || '').trim(); + const model = process.env.CODEX_MODEL || 'codex'; + + if (!reviewBody) { + throw new Error('Codex produced an empty review.'); + } + + const body = [ + marker, + `### Codex Code Review (\`${model}\`)`, + '', + reviewBody, + ].join('\n'); + + const comments = await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number, + per_page: 100, + }); + + const existing = comments.find((comment) => + comment.user?.login === 'github-actions[bot]' && + comment.body?.includes(marker) + ); + + if (existing) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existing.id, + body, + }); + return; + } + + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body, + }); + + - name: Verify Codex sticky comment exists + if: always() + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + repo='${{ github.repository }}' + pr='${{ github.event.pull_request.number }}' + + count="$(gh api "repos/${repo}/issues/${pr}/comments" --paginate --jq \ + '[.[] | select((.user.login == "github-actions[bot]") and (.body | contains("")))] | length')" + + if [ "${count}" -lt 1 ]; then + echo "::error::No Codex sticky review comment found (github-actions[bot] + marker)." + exit 1 + fi