Integrations

Every integration runs the same command and reads the same JSON. What differs between them is how a diff arrives and where comments go — a few dozen lines of API calls each, nothing more.

GitHub#

# .github/workflows/groundtruth.yml
on: pull_request
concurrency:
  group: groundtruth-${{ github.event.pull_request.number }}
  cancel-in-progress: true
permissions:
  pull-requests: write
jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with: { fetch-depth: 0 }
      - uses: groundtruth-code-review/groundtruth-code-review/adapters/github@main
        with: { model: anthropic/claude-sonnet-5 }
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

To use a different endpoint, set it in the workflow's env beside the key it goes with:

        env:
          NVIDIA_NIM_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}
          GROUNDTRUTH_BASE_URL: https://integrate.api.nvidia.com/v1

To verify with a different provider, set verify_model in .groundtruth.yml and pass both keys:

        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
  • fetch-depth: 0 is required — the default shallow clone has no base commit to diff against.
  • pull-requests: write is required for the job's own GITHUB_TOKEN to post.
  • concurrency cancels the review still running for a pull request when a new push arrives, instead of letting two runs race to write the same summary comment.
  • Findings post as review comments on the line; the summary is an issue comment, edited in place on later pushes.

GitLab#

Copy adapters/gitlab/gitlab-ci.example.yml into your .gitlab-ci.yml.

The job token will not work. CI_JOB_TOKEN cannot post notes on a merge request. You need a project access token with the api scope, exposed to the job as GROUNDTRUTH_GITLAB_TOKEN. This is the single most common reason a GitLab setup runs clean and posts nothing.
  • Set GIT_DEPTH: 0 in the job, for the same reason GitHub needs fetch-depth: 0.
  • Findings post as positioned discussions, which need both the base and head SHAs — the adapter reads them from CI_MERGE_REQUEST_DIFF_BASE_SHA and CI_COMMIT_SHA.
  • The summary is a note, edited in place.
  • interruptible: true opts the job into GitLab's auto-cancel-redundant-pipelines behaviour, the same race protection GitHub's concurrency gives — enable it once per project under Settings → CI/CD → General pipelines.

Bitbucket Cloud#

Copy adapters/bitbucket_cloud/bitbucket-pipelines.example.yml into your bitbucket-pipelines.yml.

  • Set clone: depth: full.
  • Pipelines gives you the merge target as a branch name, not a SHA, so the step fetches origin/$BITBUCKET_PR_DESTINATION_BRANCH before reviewing.
  • Authentication is an app password or repository access token over Basic auth, as GROUNDTRUTH_BITBUCKET_USER and GROUNDTRUTH_BITBUCKET_APP_PASSWORD.
  • Findings post as inline-anchored comments; the summary is a plain comment on the pull request.
  • Pipelines has no concurrency-group equivalent to cancel a superseded run on a fast second push — the fingerprint marker is the only protection against a duplicate comment there.

Kubernetes, Tekton, Argo, Jenkins#

There is no Helm chart, because there is no workload to install — a review starts, reads a diff and exits. What in-cluster CI needs is the image:

image: ghcr.io/groundtruth-code-review/groundtruth-code-review:0.1.0
args: ["review", "--repo", "/workspace", "--base", "origin/main", "--format", "json"]
env:
  - name: ANTHROPIC_API_KEY
    valueFrom:
      secretKeyRef: { name: groundtruth, key: api-key }

The image carries git and ripgrep, runs as an unprivileged user, and ships for both amd64 and arm64. Its Dockerfile is multi-stage: the runtime stage installs a wheel that only exists if lint, both test suites and the eval thresholds passed first, so a failing suite cannot produce a shippable image.

What every adapter shares#

  • One JSON contract. Adapters read what groundtruth review --format json prints and nothing else. None of them import the package.
  • One memory. The fingerprints of everything already posted are carried in a hidden HTML comment inside the summary comment, so the next push does not repeat itself. No database, and it works anywhere comments can be edited.
  • One failure mode. If a single finding cannot be placed — a force-push race, a line the platform does not consider part of the diff — that comment is skipped with a warning and the rest of the review still posts.