Skip to main content

Argus

License: MIT Python 3.10+

Model-agnostic AI code review for Bitbucket pull requests, wired into the Jenkins pipeline you already run.

Give it a PR URL and a Bitbucket token; it fetches the diff, reviews it file by file with an LLM — any provider LiteLLM supports, or your own self-hosted LiteLLM proxy/gateway — and prints, or posts, the findings as PR comments. Runs standalone from the CLI, or as a stage in an existing Jenkinsfile via a Jenkins shared library.

  • Bitbucket Cloud and Server/Data Center, auto-detected from the PR URL
  • Any LLM provider — OpenAI, Anthropic, Bedrock, self-hosted OSS models via Ollama/vLLM, or a self-hosted LiteLLM proxy — via one abstraction, LiteLLM
  • Jenkins-native — a shared-library step (argusReview()) wraps the CLI in a Docker image, so application repos need nothing but Docker and three credentials
  • Global standards + per-repo, per-module rules — an org-wide baseline merges with each repo's own .argus/rules.yaml, with path-scoped overrides for monorepos where one module's standards differ from another's
  • No vendor lock-in, no SaaS dependency — self-hostable end to end, including the model if you use a local one

Status

Early and evolving. Today: one default review prompt (no per-language packs yet), plus a working global/repo rule hierarchy — see What's next. Contributions welcome; the codebase is small on purpose so it's easy to read before you extend it.

Quickstart

Built and run with uv.

git clone https://github.com/<your-username>/argus.git
cd argus
uv sync --extra dev   # creates .venv, installs the package + test deps

Copy .env.example to .env and fill in:

  • ARGUS_BITBUCKET_TOKEN — for Bitbucket Cloud, an API token or repository/workspace access token; for Bitbucket Server/Data Center, a personal access token. Either way: Repository: read + Pull requests: read+write scope (write only needed for --post-comments).
  • ARGUS_BITBUCKET_EMAILBitbucket Cloud only, and only if the token above is an Atlassian API token rather than a repository/workspace access token. The two token types authenticate differently (Basic vs. Bearer); set this to your Atlassian account email to switch Argus onto Basic auth. Leave unset for access tokens, OAuth tokens, or Bitbucket Server.
  • Model access — pick one:
    • Direct to a provider: set ANTHROPIC_API_KEY / OPENAI_API_KEY / etc. (whichever matches --model), leave LITELLM_API_BASE unset.
    • Through a self-hosted LiteLLM proxy: set LITELLM_API_BASE (the proxy's URL) and LITELLM_API_KEY (the proxy's virtual key) instead — no provider keys needed locally, the proxy holds those. --model then becomes exactly whatever model name is registered on the proxy, byte for byte — check with curl -s "$LITELLM_API_BASE/v1/models" -H "Authorization: Bearer $LITELLM_API_KEY" if you're not sure. Some proxies register aliases that themselves look like openai/gpt-4.1-mini (i.e. the alias contains a slash); Argus posts straight to the proxy's /v1/chat/completions in this mode rather than going through LiteLLM's own SDK, specifically so a prefix like openai/ in your alias doesn't get parsed as a LiteLLM provider marker and stripped off before the proxy ever sees it.

Then source .env (or use direnv/your shell's env loading of choice) before running.

Usage

# Bitbucket Cloud
uv run argus review --pr https://bitbucket.org/<workspace>/<repo>/pull-requests/10

# Bitbucket Server / Data Center
uv run argus review --pr https://bitbucket.example.com/projects/PROJ/repos/my-repo/pull-requests/123

Options:

  • --model — any LiteLLM model string, e.g. anthropic/claude-sonnet-5, openai/gpt-5-mini, bedrock/anthropic.claude-sonnet-5, ollama/qwen2.5-coder:32b. Defaults to anthropic/claude-sonnet-5.
  • --api-base / --api-key (or LITELLM_API_BASE / LITELLM_API_KEY) — point at a self-hosted LiteLLM proxy instead of calling the provider in --model directly.
  • --standards (or ARGUS_STANDARDS) — a local path or URL for your org's global standards, replacing the bundled default. See Rule hierarchy.
  • --post-comments — also post inline + summary comments back to the PR (default: off, console-only report).
  • --fail-on high|medium|low|never — force a severity gate for this run, overriding whatever the merged rules would otherwise use. Omit to let the rules decide (nothing fails by default, including on the bundled standards alone).
  • --insecure — skip TLS verification for self-signed Bitbucket Server certs.

Run uv run pytest -q to run the test suite.

Trying out a .argus/rules.yaml idea without a real PR or LLM calls

tests/test_cli_integration.py mocks the two network boundaries (load_pr, review_file) and drives the real argus review command through Click's CliRunner — no Bitbucket access, no model calls, but it exercises the actual rule-merging, secret-scanning, and policy logic. Useful as a template for trying out a new rules idea locally: build a LoadedPR with synthetic FileDiffs and a repo_rules_yaml string, invoke the command, and check the output/exit code.

What it does today

  1. Auto-detects Bitbucket Cloud vs. Server/Data Center from the PR URL.
  2. Fetches the PR's diff (Cloud: unified text; Server: structured JSON — both normalized to the same shape internally).
  3. Fetches .argus/rules.yaml from the PR's target branch, if present, and merges it with the global standards baseline — see below.
  4. Reviews each changed file independently, filtered to the categories the merged rules say apply to that file, against a single default prompt (src/argus/prompts/default.jinja2).
  5. Prints a findings table; optionally posts comments back to the PR; exits non-zero if any finding crosses its file's effective severity gate.

Rule hierarchy: global standards + repo & module rules

What Argus checks for is a merge, not a fallback. An org-wide global standards baseline (bundled default, or your own via --standards) can mark categories locked: true — those apply everywhere and can't be turned off by a repo. Each repo's own .argus/rules.yaml, read from the PR's target branch, can add categories, adjust the severity gate, and layer path-scoped overrides for modules that need different treatment — a monorepo's frontend/ and legacy/ directories don't have to be reviewed the same way.

# .argus/rules.yaml, at the root of the repo being reviewed
severity_gate: high              # fail the build at/above this, repo-wide
categories:
  add: [accessibility]           # on top of whatever the global baseline includes
  remove: [style]                # ignored for any category the global baseline locked

overrides:
  - paths: ["frontend/*"]        # glob; `*` matches across `/` too
    categories:
      add: [accessibility]
    severity_gate: medium        # stricter than the repo default, for this module only

  - paths: ["legacy/*"]
    severity_gate: low           # more lenient here — findings still shown, just don't fail the build

No .argus/rules.yaml? The repo just inherits the global baseline untouched. The bundled default ships with security and correctness locked and no severity gate set (report-only) — see src/argus/standards/global.yaml.

Two checks that aren't LLM judgment calls

Some things shouldn't depend on a model noticing them. Two mechanisms sit alongside the per-file LLM review for exactly that:

  • Secret/.env detection is always on. Because security is locked in the bundled global standards, every repo already gets a deterministic, regex-based scan (AWS keys, GitHub/Slack tokens, private key blocks, hardcoded api_key = "..."-shaped assignments, and any .env-shaped file being added) on top of whatever the LLM finds — no config needed. A module can't opt out of this without a global standards change, since it rides on a locked category.

  • policies: are structural, PR-level checks — not per-file. "If this module changed, some other file must have changed too" — evaluated once against every file touched in the PR, independent of the LLM.

Putting both together for a monorepo where module-a just needs the default secret scanning (already covered, no config) and module-b should never ship a feature without a README update:

# .argus/rules.yaml
overrides:
  # module-a needs nothing here — .env/API-key detection already applies
  # everywhere via the locked `security` category above.

  - paths: ["module-b/**"]
    policies:
      - require: "module-b/README.md"
        message: "module-b changed but its README wasn't updated."
        severity: medium

If a PR touches anything under module-b/ but doesn't also touch module-b/README.md, Argus adds a PR-level finding for it — posted as a summary comment (there's no single line it's "about"), and it can fail the build the same way a file-level finding does, using this override's severity_gate (falling back to the repo's, then the global one, if unset).

Jenkins integration

The CLI is wrapped in a Jenkins shared-library step (argusReview()), packaged as a Docker image so application repos don't need Python/uv on the agent — just Docker and three credentials. See jenkins/README.md for one-time platform setup and jenkins/examples/Jenkinsfile for how an application repo adds one stage to its existing pipeline:

@Library('argus-shared-library') _

pipeline {
    agent any
    stages {
        stage('Checkout') { steps { checkout scm } }

        stage('AI Code Review') {
            when { changeRequest() }   // only run against pull requests
            steps {
                argusReview(
                    image: 'registry.internal/argus-review:0.1.0',
                    model: 'openai/gpt-4.1-mini',
                )
            }
        }
        // ... your existing build/test stages, unchanged ...
    }
}

Contributing

Issues and PRs welcome. The codebase is deliberately small and split by concern — an SCM connector (bitbucket_cloud.py / bitbucket_server.py) is about 80 lines and a reasonable template for adding GitHub/GitLab; a review pack is just a prompt template and a rules file. Run uv run pytest -q before opening a PR.

What's next

  • Per-language review packs (Python, Java, Angular, React) instead of one generic prompt.
  • Roll out to a handful of pilot repos to gauge signal vs. noise on the global baseline before asking teams to write their own .argus/rules.yaml.
  • A versioned, centrally-owned global standards repo instead of a single bundled/--standards-pointed file, so a platform team can update the baseline without touching Argus itself.

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

argus_reviewer-0.1.0.tar.gz (200.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

argus_reviewer-0.1.0-py3-none-any.whl (24.8 kB view details)

Uploaded Python 3

File details

Details for the file argus_reviewer-0.1.0.tar.gz.

File metadata

  • Download URL: argus_reviewer-0.1.0.tar.gz
  • Upload date:
  • Size: 200.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.20

File hashes

Hashes for argus_reviewer-0.1.0.tar.gz
Algorithm Hash digest
SHA256 3cdc310c201dea7d0aa05d111976bfd8c5d9634adee79c723c1b2e84c71bc579
MD5 acc0d77d147fa6ccb541bab6d043701e
BLAKE2b-256 8ab87dae708eba1e0d803a082d6a683833fdac90395fc9dfb6a2fd6829cabff1

See more details on using hashes here.

File details

Details for the file argus_reviewer-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for argus_reviewer-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 72f010d7b618654c269b58ba0f4242ce51581a75ea037d0357fd155e9e4da744
MD5 b8b52f40aa5ed4f8c63b09b16ee4e93c
BLAKE2b-256 ce5415e2d3b06c1c4fd6114fd6a59642c978f1d6c7663b083a8ea375c90db7cc

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page