Manual — field-tested

From install to tuned configuration

ReproGuard works with no configuration at all. This page covers the parts you reach for once it does: configuration, the CLI surface, custom rules, and how the score is actually computed.

Install

pip install reproguard

# verify
reproguard --version
reproguard scan .
uv tool install reproguard

# or run it without installing
uvx reproguard scan .
git clone https://github.com/vipulgote1999/ReproGuard.git
cd ReproGuard
pip install -e .

# with the dev toolchain
pip install -e ".[dev]"
# Presidio-backed deep PII detection (--presidio)
pip install "reproguard[privacy]"

Requires Python 3.10 or newer. Works on Linux, macOS, and Windows.

Scaffold a project in one command

reproguard init writes a config file and a ready-to-run CI workflow so you start from something working rather than a blank file.

# config + GitHub Actions workflow
reproguard init --ci github

# config tuned for an LLM/agent project
reproguard init --profile genai

# GitLab instead
reproguard init --ci gitlab

CLI reference

Everything below is a flag on reproguard scan <target>.

Flag What it does
--format text|json|html|sarif|gitlab|all Which reports to write. Defaults to all. Text goes to the terminal, the rest into the output directory.
--output-dir <dir>, -o Where reports land, default .reproguard. Use -o - to stream a single machine format to stdout for jq.
--fail-under <score> Exit non-zero when the score is below the threshold. The main CI gate.
--baseline <report.json> Diff against a previous report. Combine with --fail-new N or --fail-new-critical to block only regressions.
--fail-trend N Fail when the score dropped N or more points since the previous recorded scan.
--since <git-ref> Scan only files changed since a ref — fast incremental scans on large repos. Repo-level checks still run.
--execute / --no-execute Run notebooks on a clean kernel to prove they actually execute. --no-execute is the default.
--parallel, --max-workers N Execute notebooks in parallel subprocesses, kernelspec-aware.
--profile genai GenAI-tuned category weights: GenAI findings penalise harder, handoff findings less.
--privacy / --no-privacy, --presidio Control PII and secret scanning. --presidio adds named-entity detection and needs the privacy extra.
--fix gitignore|clear-counts|seed|strip-outputs|all Idempotent, non-destructive remediations applied before the exit gates run.
--git-history Scan git history diffs for secrets committed in the past (gitleaks-lite).
--sbom <file> Write a CycloneDX 1.5 bill of materials from the declared dependencies.
--model-card [--model-card-format md|hf] Emit a Markdown handoff artifact, optionally in Hugging Face Hub front-matter form.
--manifest-online Validate llm.yaml model ids against the OpenRouter catalog.
--execution-timeout <seconds> Per-notebook timeout for clean execution runs.

Other commands

reproguard init      # scaffold config + CI workflow
reproguard list      # every registered check
reproguard explain LEAK001   # full record for one check
reproguard trend     # score history for this project

Configuration

Create a .reproguard.yml at your project root. Like git, ReproGuard walks up from the scan path to find it, so a subdirectory scan picks up the root config.

# .reproguard.yml
exclude_paths:
  - "archive/**"
  - "tests/**"
exclude_dirs:
  - scratch

# CI gate defaults
fail_under: 50

# GenAI-tuned weights (same as --profile genai)
profile: genai

thresholds:
  severity_penalties:
    critical: 18
    high: 10
  category_weights:
    genai: 30
    handoff: 5

checks:
  disabled:
    - LEAK005       # large tabular output
    - PII004        # base64 images in output
  ignore:
    - code: SEC002
      path: "tests/**"          # fixtures hold fake keys
    - code: DATA001
      path: "notebooks/explore.ipynb"
      evidence: "/mnt/shared/.*"   # optional regex on the match

execution:
  enabled: false
  timeout: 300
  parallel: false
  max_workers: 4

Suppression, done properly

checks.ignore is path-scoped, so you silence a finding where it is wrong rather than muting the check everywhere. An optional evidence regex narrows it further. Suppressed counts are recorded in report metadata so nothing disappears silently.

Partial overrides merge

Setting only critical under severity_penalties keeps the defaults for every other severity. Unknown keys and malformed values produce warnings, never tracebacks.

Custom rules

Organisation-specific patterns live in the same file and behave like built-ins: scored, reported, and suppressible.

# .reproguard.yml
rules:
  - code: NOSAMPLE          # flag sampling without a fixed seed
    title: Sample without seed
    pattern: 'sample\('
    category: reproducibility
    severity: medium
    confidence: 0.8
    file_patterns:
      - 'src/**/*.py'
      - '*.ipynb'

  - code: TODO001
    title: TODO left in code
    pattern: '# TODO'
    severity: low

Rules are capped at 20 findings per file, matching the flood-control philosophy of the built-in checks. category defaults to custom when it isn't a known value.

How the score is computed

No machine learning, no hidden model. The score is a transparent penalty calculation you can reproduce on a napkin.

penalty  =  Σ  severity_penalty × (category_weight / 20) × confidence
score    =  max(0, min(100, 100 − penalty))

severity penalties:  critical 18 · high 10 · medium 5 · low 2 · info 0
category weights:    reproducibility 30 · execution 30 · genai 30
                     data_leakage 25 · privacy 20 · data_dependency 15
                     handoff 10 · custom 10

Status bands

75–100 · ready_with_caution — review the minor findings.
50–74 · needs_review — significant issues to address.
0–49 · not_ready — blocking.
Any CRITICAL · not_ready, regardless of score.

Per-category breakdown

Every report carries penalties_by_category, and those values sum exactly to 100 − score. A 40/100 project tells you it lost 25 points to leakage and 20 to privacy, not just that it is "bad".

Output formats

# one document on stdout, ready for jq
reproguard scan . -f json -o - | jq '{score, status, n: (.issues|length)}'

# {
#   "score": 42,
#   "status": "not_ready",
#   "n": 8
# }

Streaming guarantees exactly one parseable document: notices go to stderr, GitHub annotations are suppressed, and --format all with -o - is rejected up front.

# SARIF 2.1.0 for GitHub Code Scanning and VS Code
reproguard scan . --format sarif

Upload with github/codeql-action/upload-sarif@v3. See the CI guide.

# gl-code-quality-report.json for merge-request annotations
reproguard scan . --format gitlab
# standalone interactive report with severity filters
reproguard scan . --format html

Self-contained — no external assets, so it survives being emailed or attached to a ticket.

What ReproGuard deliberately does not do

It is not a linter

Use Ruff and Black for style. ReproGuard looks for risks that only exist in data-science artefacts: execution order, leakage, environment drift, hidden credentials.

It does not version your data

DVC and LakeFS own that. ReproGuard flags that a DVC-tracked output is missing, then stops.

It does not track experiments

MLflow and Weights & Biases own that. ReproGuard runs before and around them.

It does not monitor production

Evidently, Arize, and WhyLabs own monitoring. ReproGuard is a pre-production gate.

Ready to run it?

Start with a single scan. Add configuration only when a finding turns out to be wrong.