Skip to content

CI/CD Integration

Integrate Hotspots into your CI/CD pipeline to catch complexity regressions before they reach production.

Overview

Hotspots provides first-class CI/CD support:

  • Zero-config GitHub Action for seamless integration
  • Policy enforcement to block risky changes
  • Delta analysis comparing changes vs. baseline
  • PR comments with actionable insights
  • HTML reports as workflow artifacts
  • Exit codes for build failure control

Supported CI Systems: GitHub Actions, GitLab CI, CircleCI, Travis CI, Jenkins, Bitbucket Pipelines


Quick Start

Create .github/workflows/hotspots.yml:

yaml
name: Hotspots

on:
  pull_request:
  push:
    branches: [main]

jobs:
  analyze:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write  # For PR comments

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Required for delta analysis

      - uses: Stephen-Collins-tech/hotspots-action@v1
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}

That's it! The action will:

  • Detect PRs automatically and run delta analysis
  • Post results as PR comments
  • Generate HTML reports as artifacts
  • Fail the build on policy violations

See GitHub Action Guide for complete documentation.

Action Inputs

InputDescriptionDefault
pathPath to analyze. (repo root)
policyPolicy modecritical-introduction
min-lrsMin LRS threshold-
configPath to config fileAuto-discover
fail-onWhen to fail: error, warn, nevererror
versionHotspots versionlatest
github-tokenToken for PR commentsgithub.token (auto)
post-commentPost PR commenttrue

Action Outputs

OutputDescription
violationsJSON array of violations
passedtrue/false
summaryMarkdown summary
report-pathHTML report path
json-outputJSON output path

Example Configurations

Strict Policy (No Regressions)

yaml
- uses: Stephen-Collins-tech/hotspots-action@v1
  with:
    policy: strict
    fail-on: error
    github-token: ${{ secrets.GITHUB_TOKEN }}

Custom Threshold

yaml
- uses: Stephen-Collins-tech/hotspots-action@v1
  with:
    min-lrs: 8.0  # Only flag functions with LRS ≥ 8
    fail-on: warn
    github-token: ${{ secrets.GITHUB_TOKEN }}

With Configuration File

yaml
- uses: Stephen-Collins-tech/hotspots-action@v1
  with:
    config: .hotspots.ci.json
    github-token: ${{ secrets.GITHUB_TOKEN }}

Create .hotspots.ci.json:

json
{
  "exclude": ["**/*.test.ts", "**/__tests__/**"],
  "min_lrs": 5.0,
  "thresholds": {
    "moderate": 5.0,
    "high": 8.0,
    "critical": 10.0
  }
}

Multi-Path Analysis

yaml
- uses: Stephen-Collins-tech/hotspots-action@v1
  with:
    path: src/
    github-token: ${{ secrets.GITHUB_TOKEN }}

- uses: Stephen-Collins-tech/hotspots-action@v1
  with:
    path: lib/
    github-token: ${{ secrets.GITHUB_TOKEN }}

Upload HTML Report

yaml
- uses: Stephen-Collins-tech/hotspots-action@v1
  id: hotspots
  with:
    github-token: ${{ secrets.GITHUB_TOKEN }}

- uses: actions/upload-artifact@v4
  if: always()
  with:
    name: hotspots-report
    path: ${{ steps.hotspots.outputs.report-path }}
    retention-days: 30

Use Outputs in Subsequent Steps

yaml
- uses: Stephen-Collins-tech/hotspots-action@v1
  id: hotspots
  with:
    fail-on: never  # Don't fail, handle manually
    github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Check Results
  run: |
    if [ "${{ steps.hotspots.outputs.passed }}" == "false" ]; then
      echo "::warning::Hotspots found violations"
      echo "${{ steps.hotspots.outputs.summary }}"
    fi

GitLab CI

Installation

Add to .gitlab-ci.yml:

yaml
stages:
  - analyze

hotspots:
  stage: analyze
  image: rust:latest
  before_script:
    - cargo install hotspots-cli --version 1.0.0
  script:
    - hotspots analyze src/ --mode delta --policy --format json
  artifacts:
    reports:
      junit: hotspots-report.xml
    paths:
      - .hotspots/report.html
    expire_in: 1 week
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

With Configuration File

yaml
hotspots:
  stage: analyze
  image: rust:latest
  before_script:
    - cargo install hotspots-cli
  script:
    - hotspots analyze src/ --config .hotspots.ci.json --mode delta --policy
  artifacts:
    paths:
      - .hotspots/report.html
  only:
    - merge_requests

Docker Image (Faster)

yaml
hotspots:
  stage: analyze
  image: ghcr.io/stephen-collins-tech/hotspots:latest
  script:
    - hotspots analyze src/ --mode delta --policy --format json
  artifacts:
    paths:
      - .hotspots/report.html

CircleCI

Configuration

Add to .circleci/config.yml:

yaml
version: 2.1

jobs:
  hotspots:
    docker:
      - image: rust:latest
    steps:
      - checkout
      - run:
          name: Install Hotspots
          command: cargo install hotspots-cli
      - run:
          name: Run Analysis
          command: hotspots analyze src/ --mode delta --policy --format json
      - store_artifacts:
          path: .hotspots/report.html
          destination: hotspots-report

workflows:
  analyze:
    jobs:
      - hotspots:
          filters:
            branches:
              only: main

With Caching

yaml
jobs:
  hotspots:
    docker:
      - image: rust:latest
    steps:
      - checkout
      - restore_cache:
          keys:
            - cargo-v1-{{ checksum "Cargo.lock" }}
            - cargo-v1-
      - run:
          name: Install Hotspots
          command: |
            if ! command -v hotspots &> /dev/null; then
              cargo install hotspots-cli
            fi
      - save_cache:
          key: cargo-v1-{{ checksum "Cargo.lock" }}
          paths:
            - ~/.cargo
      - run:
          name: Run Analysis
          command: hotspots analyze src/ --mode delta --policy

Travis CI

Configuration

Add to .travis.yml:

yaml
language: rust
rust:
  - stable

install:
  - cargo install hotspots-cli

script:
  - hotspots analyze src/ --mode delta --policy --format json

after_success:
  - echo "Hotspots analysis passed"

PR-Only Analysis

yaml
jobs:
  include:
    - stage: analyze
      if: type = pull_request
      script:
        - hotspots analyze src/ --mode delta --policy

Jenkins

Pipeline Script

groovy
pipeline {
    agent any

    stages {
        stage('Setup') {
            steps {
                sh 'cargo install hotspots-cli || true'
            }
        }

        stage('Analyze') {
            steps {
                sh 'hotspots analyze src/ --mode delta --policy --format json > hotspots-output.json'
            }
        }

        stage('Report') {
            steps {
                archiveArtifacts artifacts: '.hotspots/report.html', fingerprint: true
                publishHTML([
                    reportDir: '.hotspots',
                    reportFiles: 'report.html',
                    reportName: 'Hotspots Report'
                ])
            }
        }
    }

    post {
        failure {
            echo 'Hotspots found blocking violations'
        }
    }
}

Freestyle Project

Build Steps:

bash
# Install Hotspots (first time)
cargo install hotspots-cli

# Run analysis
hotspots analyze src/ --mode delta --policy --format json

Post-build Actions:

  • Archive artifacts: .hotspots/report.html
  • Publish HTML reports: .hotspots/report.html

Bitbucket Pipelines

Configuration

Add to bitbucket-pipelines.yml:

yaml
pipelines:
  default:
    - step:
        name: Hotspots Analysis
        image: rust:latest
        caches:
          - cargo
        script:
          - cargo install hotspots-cli
          - hotspots analyze src/ --mode delta --policy --format json
        artifacts:
          - .hotspots/report.html

  pull-requests:
    '**':
      - step:
          name: Hotspots PR Check
          image: rust:latest
          script:
            - cargo install hotspots-cli
            - hotspots analyze src/ --mode delta --policy

Docker Integration

Using Pre-built Image

bash
docker run --rm -v $(pwd):/workspace ghcr.io/stephen-collins-tech/hotspots:latest \
  analyze /workspace/src --mode delta --policy --format json

Custom Dockerfile

dockerfile
FROM rust:latest as builder
RUN cargo install hotspots-cli

FROM debian:bookworm-slim
COPY --from=builder /usr/local/cargo/bin/hotspots /usr/local/bin/
WORKDIR /workspace
ENTRYPOINT ["hotspots"]

Build and use:

bash
docker build -t hotspots:custom .
docker run --rm -v $(pwd):/workspace hotspots:custom analyze /workspace/src

Environment Variables

Hotspots respects standard git and CI environment variables:

Git Context

  • GIT_DIR - Override .git directory
  • GIT_WORK_TREE - Override working directory

PR Detection (for delta mode)

GitHub Actions:

  • GITHUB_EVENT_NAME=pull_request

GitLab CI:

  • CI_MERGE_REQUEST_IID

CircleCI:

  • CIRCLE_PULL_REQUEST

Travis CI:

  • TRAVIS_PULL_REQUEST

When detected, delta mode compares vs. merge-base instead of direct parent.


Exit Codes

CodeMeaningTriggered By
0SuccessNo violations, or only warnings
1FailureBlocking policy violations

Control exit behavior:

bash
# Fail on errors only (default)
hotspots analyze src/ --mode delta --policy

# Fail on warnings too
hotspots analyze src/ --mode delta --policy --fail-on warnings

# Never fail (for reporting only)
hotspots analyze src/ --mode delta --policy --fail-on never

In CI scripts:

bash
# Ignore exit code (always pass)
hotspots analyze src/ --mode delta --policy || true

# Custom handling
if ! hotspots analyze src/ --mode delta --policy; then
  echo "Hotspots found violations, but continuing..."
fi

Caching Strategies

GitHub Actions

yaml
- name: Cache Hotspots Binary
  uses: actions/cache@v4
  with:
    path: ~/.cargo/bin/hotspots
    key: hotspots-${{ runner.os }}-v1.0.0

- name: Install or Use Cached Hotspots
  run: |
    if ! command -v hotspots &> /dev/null; then
      cargo install hotspots-cli --version 1.0.0
    fi

GitLab CI

yaml
cache:
  key: hotspots-${CI_COMMIT_REF_SLUG}
  paths:
    - ~/.cargo/bin/hotspots
    - .hotspots/snapshots/

CircleCI

yaml
- restore_cache:
    keys:
      - hotspots-v1-{{ .Branch }}
      - hotspots-v1-

- save_cache:
    key: hotspots-v1-{{ .Branch }}
    paths:
      - ~/.cargo/bin/hotspots

Multi-Language Projects

Analyze Multiple Directories

yaml
- name: Analyze TypeScript
  run: hotspots analyze src/ --mode delta --policy

- name: Analyze Go
  run: hotspots analyze backend/ --mode delta --policy

- name: Analyze Python
  run: hotspots analyze scripts/ --mode delta --policy

Combined Report

bash
# Analyze all at once (recommended)
hotspots analyze . --config .hotspotsrc.json --mode delta --policy

.hotspotsrc.json:

json
{
  "include": [
    "src/**/*.ts",
    "backend/**/*.go",
    "scripts/**/*.py"
  ],
  "exclude": [
    "**/*.test.*",
    "**/node_modules/**"
  ]
}

Troubleshooting

"not in a git repository"

Cause: Missing .git directory or shallow clone.

Fix: Use fetch-depth: 0 in checkout:

yaml
- uses: actions/checkout@v4
  with:
    fetch-depth: 0

"failed to extract git context"

Cause: Missing git metadata (commit SHA, parents).

Fix: Ensure full git history:

bash
git fetch --unshallow  # If shallow
git fetch origin main:main  # Fetch base branch

"merge-base not found"

Cause: PR base branch not available.

Fix (GitHub Actions):

yaml
- uses: actions/checkout@v4
  with:
    fetch-depth: 0
    ref: ${{ github.event.pull_request.head.ref }}

Fix (GitLab CI):

yaml
before_script:
  - git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME

Exit Code Always 0

Cause: Using || true or fail-on: never.

Fix: Remove exit code overrides:

bash
# Bad
hotspots analyze src/ --mode delta --policy || true

# Good
hotspots analyze src/ --mode delta --policy

Pre-commit Hooks

Block pushes with complexity regressions before they reach CI using hotspots init --hooks.

Setup

bash
# 1. Seed a baseline snapshot (required — delta mode compares against this)
hotspots analyze . --mode snapshot

# 2. Print hook templates
hotspots init --hooks

Option 1: pre-commit Framework

Add the printed YAML block to .pre-commit-config.yaml:

yaml
repos:
  - repo: local
    hooks:
      - id: hotspots
        name: hotspots risk check
        language: system
        entry: hotspots analyze . --mode delta --policy --format text
        pass_filenames: false
        stages: [pre-push]

Then install:

bash
pre-commit install --hook-type pre-push

Option 2: Raw Shell Hook

Copy the printed shell block (starting with #!/usr/bin/env sh) to .git/hooks/pre-push:

bash
hotspots init --hooks 2>/dev/null | sed -n '/^#!/,$ p' > .git/hooks/pre-push
chmod +x .git/hooks/pre-push

Or copy manually — the hook content is:

sh
#!/usr/bin/env sh
set -e
hotspots analyze . --mode delta --policy --format text

How It Works

  • Runs on pre-push, not pre-commit, to avoid slowing down every commit
  • Delta mode compares the working tree against the last snapshot
  • Exits non-zero if blocking policy violations are found, preventing the push
  • If no baseline snapshot exists yet, policy evaluation is silently skipped

GitHub Code Scanning (SARIF)

Upload SARIF output to GitHub to get inline annotations on pull requests and alerts in the Security tab.

Workflow

yaml
name: Hotspots SARIF

on:
  push:
    branches: [main]
  pull_request:

jobs:
  scan:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write   # Required for upload-sarif

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Run Hotspots (snapshot)
        run: hotspots analyze . --mode snapshot --format sarif --output .hotspots/results.sarif

      - name: Upload SARIF to GitHub
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: .hotspots/results.sarif

Risk Band → SARIF Level Mapping

Hotspots bandSARIF levelGitHub annotation
criticalerrorRed
highwarningYellow
moderatenoteBlue
lowNot emitted

Custom Output Path

bash
hotspots analyze . --mode snapshot --format sarif --output sarif-results/hotspots.sarif
yaml
- name: Upload SARIF
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: sarif-results/hotspots.sarif

See SARIF Format for complete output format documentation.


Best Practices

1. Use Delta Mode in CI

yaml
# ✅ Good - Delta mode for PRs
- run: hotspots analyze src/ --mode delta --policy

# ❌ Bad - Snapshot mode without comparison
- run: hotspots analyze src/ --format json

2. Separate Dev and CI Configs

Development: .hotspotsrc.json

json
{
  "min_lrs": 0.0,
  "top": 20
}

CI: .hotspots.ci.json

json
{
  "min_lrs": 5.0,
  "thresholds": {
    "moderate": 5.0,
    "high": 8.0,
    "critical": 10.0
  }
}

Use in CI:

yaml
- run: hotspots analyze src/ --config .hotspots.ci.json --mode delta --policy

3. Archive Reports

yaml
- uses: actions/upload-artifact@v4
  if: always()
  with:
    name: hotspots-report-${{ github.sha }}
    path: .hotspots/report.html
    retention-days: 30

4. Post PR Comments

yaml
- uses: Stephen-Collins-tech/hotspots-action@v1
  with:
    post-comment: true  # Default for PRs
    github-token: ${{ secrets.GITHUB_TOKEN }}

5. Gradual Rollout

Start with warnings only:

yaml
- run: hotspots analyze src/ --mode delta --policy --fail-on never

Then tighten:

yaml
- run: hotspots analyze src/ --mode delta --policy --fail-on error


Examples Repository

See examples/ci-cd/ for complete working examples:

  • GitHub Actions
  • GitLab CI
  • CircleCI
  • Jenkins
  • Docker

Need help? Open an issue on GitHub.

Released under the MIT License. · hotspots.dev