name: Junie review # JetBrains' Junie agent reads each pull request and leaves inline review comments. It runs # alongside CI rather than as part of it: CI answers whether the code works, this answers whether # it reads well, and a review comment should never be able to turn the build red. on: pull_request: # `reopened` because a pull request closed and reopened has had no review since it was # closed, and `ready_for_review` because the draft guard below would otherwise skip a pull # request opened as a draft forever. No `branches:` filter: work here stacks feature onto # feature, so filtering on main would skip every pull request in a chain but the last. types: [opened, synchronize, reopened, ready_for_review] # A review of the previous push is stale the moment a new one lands, so supersede it rather than # letting two reviews comment on the same pull request. Keyed by pull request rather than by ref # so a push to `main` never cancels a review. concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} cancel-in-progress: true # Read the code, write the review. `issues: write` is what posts the summary comment — GitHub # treats a pull request's conversation timeline as an issue. permissions: contents: read pull-requests: write issues: write jobs: review: name: Review the diff runs-on: ubuntu-latest # A ceiling, not a target: a run that goes wrong hangs rather than stops, and the pull # request shows a pending check until it does. Set above the longest review this repository # has actually had — 35 minutes, on the largest diff so far — rather than at the sibling # repositories' 30, which would have cut that one short. timeout-minutes: 45 # Drafts are work in progress and forks cannot see `JUNIE_API_KEY` — GitHub withholds secrets # from `pull_request` runs on forked branches, so the job would fail on a missing key rather # than say anything useful about the code. if: >- github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository env: # Whether the key exists, resolved once here because `secrets` is not one of the contexts a # step's `if` can read, while `env` is. HAS_JUNIE_KEY: ${{ secrets.JUNIE_API_KEY != '' }} steps: # Without the key the action exits on "Missing required input", which would mark every pull # request failed for a reason that has nothing to do with its code. Say so in the run # summary and stop instead — visible to anyone who looks, blocking nobody who doesn't. - name: Explain the absent key if: env.HAS_JUNIE_KEY != 'true' run: | echo "No \`JUNIE_API_KEY\` secret is set, so this pull request was not reviewed." >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" echo "Generate a key at and add it under" >> "$GITHUB_STEP_SUMMARY" echo "Settings → Secrets and variables → Actions." >> "$GITHUB_STEP_SUMMARY" - uses: actions/checkout@v5 if: env.HAS_JUNIE_KEY == 'true' with: # Junie reads the diff through the GitHub API, so the full history it would otherwise # clone is never used. fetch-depth: 1 # Pinned to a commit rather than to `v1`: this is the only third-party action here and it # is handed a repository secret, so its definition should not be able to change under us. # `v1` resolves to this same commit today; the pin is about who gets to move it. The pin # covers this definition only — the composite pulls its own dependencies by tag and fetches # the Junie CLI over the network. Bump by resolving the new release's commit, never by # moving a tag name. - uses: JetBrains/junie-github-action@3f6a906f11c6f67c76efaf3d3264bbb615f9ce29 # v1.7.5 if: env.HAS_JUNIE_KEY == 'true' # An opinion, not a gate. If Junie is down or rate-limited that is worth seeing in the # log, but it is not a reason to hold a pull request whose tests pass. continue-on-error: true with: junie_api_key: ${{ secrets.JUNIE_API_KEY }} # The action's built-in review prompt. Replace with a prompt block to review against # criteria of our own. prompt: code-review # Rewrite one comment on each push instead of stacking a new one per revision, so the # conversation shows the current state of the review rather than its history. use_single_comment: "true"