github

This guide explains how to set up Lintro with GitHub Actions for automated code quality checks, coverage reporting, and CI/CD integration.

GitHub Integration Guide

This guide explains how to set up Lintro with GitHub Actions for automated code quality checks, coverage reporting, and CI/CD integration.

Quick Setup

The repository includes pre-configured GitHub Actions workflows. To activate them:

  1. Enable GitHub Pages in repository settings (for coverage badges)
  2. Push to main branch to trigger workflows
  3. Add badges to your README.md (optional)

Available Workflows

1. Quality Check Workflow

File: .github/workflows/docker-ci.yml (dogfooding-quality job)

Delegates to lgtm-ci reusable-quality.yml, linting with the Docker image built in the same workflow run (lintro-image: ghcr.io/lgtm-hq/py-lintro:ci-<run_id>). Posts results as a PR comment.

Triggers: Pull requests, pushes to main, merge queue, manual dispatch (via docker-ci).

1b. Required Lint Check (lintro-code-quality)

File: .github/workflows/docker-ci.yml (code-quality-gate and lintro-code-quality jobs)

The code-quality-gate job rolls the effective dogfooding lint attempt up into the single required lintro-code-quality check.

The gate fails closed (#2296). When the lint job is killed (SIGTERM, exit 143), cancelled, or times out, it produced no lint verdict — and a check that never ran must not report success. The gate writes status=no-verdict, infra-flake=true and goes red. The job summary then says the following, so the red check is not mistaken for a lint violation:

No lint verdict (runner loss); auto-rerun will retry (run attempt N; up to 3 automatic reruns)

.github/workflows/auto-rerun-on-infra-failure.yml reruns the failed jobs on that signature, up to three reruns — the original run is attempt 1, so the last eligible attempt is 4, and the summary says so once the budget is spent. The check turns green only once a rerun produces a real lint verdict. Runner noise is still classified (scripts/ci/is-infra-flake-failure.sh) — it is just no longer absorbed into a green check.

The one failure still absorbed is the mirror image: lint reported status=passed / exit-code=0 and only a post-lint step of the surrounding job failed. That is a real verdict, so the check stays green with infra-flake=true — and image promotion in the publish job still refuses to promote an image built on an infra-flake=true run.

2. Test Suite & Coverage

File: .github/workflows/test-ci.yml and .github/workflows/docker-ci.yml

Features:

  • 🧪 Unit test coverage via lgtm-ci reusable-test-python.yml (Python 3.11 + 3.14)
  • 🐳 Docker integration tests in docker-ci.yml

4. Lintro Report Workflow

File: .github/workflows/lintro-report-scheduled.yml

Features:

  • 📊 Comprehensive codebase analysis with Lintro
  • 📈 Report generation in multiple formats (Grid, Markdown)
  • 📋 GitHub Actions summary with detailed results
  • 📦 Artifact upload for report retention
  • 🌐 Optional GitHub Pages deployment for report hosting

If you want to publish the weekly report to Pages, prefer using a dedicated deploy-pages job gated on the report workflow.

4b. AI Review (Dogfood) Workflow

File: .github/workflows/ai-review.yml

py-lintro dogfoods its own lintro review command on every eligible pull request. The workflow runs an AI diff review and prints the JSON result to the job log.

Features:

  • 🤖 AI diff review via lintro review --pr <n> --depth 1 --output json
  • 🛡️ Trusted install — lintro is installed from the PR’s base ref (main, via pull_request.base.sha), never the PR head. The code that runs with the credential is always trusted, so a PR cannot substitute its own lintro/** to exfiltrate it. The PR is still reviewed: lintro review --pr fetches the diff through gh (GitHub API), so the PR’s changes are reviewed as data and never executed with the secret. Trade-off: a PR that breaks the review code itself isn’t caught by this job — that is covered by the unit tests.
  • 🔑 Bring-your-own credential — runs the cli transport. Provider and model come from the LINTRO_AI_PROVIDER / LINTRO_AI_MODEL Actions variables. When those variables are unset, ai-review.yml still passes LINTRO_AI_PROVIDER: anthropic and run-ai-review.sh falls back the same way (${LINTRO_AI_PROVIDER:-anthropic}), and that env overlay wins over the committed .lintro-config.yaml, so the workflow — not the config — makes the choice. Those two CI-side literals are the deliberate exceptions to #2143’s no-default rule, which governs the shipped product; #2143’s workflow restructure removes both. Operators can set the variables to cursor and cursor-grok-4.6-high for current dogfood. Cursor uses CURSOR_API_KEY; Anthropic uses the pinned claude CLI authenticated by CLAUDE_CODE_OAUTH_TOKEN (a Claude subscription session). ANTHROPIC_API_KEY is deliberately not in scope, and LINTRO_CLI_BARE: never keeps --bare off the command line so an OAuth session is actually used (#1838). CLI versions are pinned in docker/ai-tools.Dockerfile and installed from npm at those exact versions.
  • 💸 Operator-set spend ceiling (advisory under the CLI transport) — the trusted base config ships no ai.max_cost_usd, so there is no committed default cap. A PR still cannot raise spend: the workflow installs lintro from the base ref and forwards LINTRO_AI_MAX_COST_USD from the repository Actions variable. Operators set that variable to a positive USD cap or to uncapped (overlay 0 is rejected as ambiguous). It prices only the tokens lintro billed itself, so on the cli transport — where the call bills the subscription — it bounds lintro’s own accounting rather than enforcing spend. Setting a cap does not serialize provider calls: the budget checks and charges the ceiling around each call rather than holding a lock across it, so budgeted chunk reviews still run concurrently. The trade-off is that calls already in flight when the ceiling is reached still finish, so the final total can overshoot ai.max_cost_usd by roughly one round of concurrent calls.
  • 🟡 Loud but non-blocking — the check is deliberately not required, but it is not unconditionally green either: it reddens whenever no review was produced (missing or dead credential, depleted balance, unreachable provider, lintro-side failure). See the No-Silent-Skip note in #1826 — a continue-on-error job rewrites its conclusion to success, which is how “AI Review ✓” meant nothing for months.
  • ⏭️ Skipped, not failed, where it cannot run — draft PRs and fork PRs (which cannot read secrets) never start the job at all.

To activate it, add the matching secret (Settings → Secrets and variables → Actions): CURSOR_API_KEY plus LINTRO_AI_PROVIDER=cursor (and optionally LINTRO_AI_MODEL / LINTRO_AI_MAX_COST_USD=uncapped) for Cursor, or CLAUDE_CODE_OAUTH_TOKEN (mint with claude setup-token) for Anthropic. Because reviews run using lintro from the trusted base branch, the credential is safe to enable.

Activation precondition (security audit #1317)

Before enabling the provider credential, confirm the dogfood workflow still satisfies all three controls (also asserted in tests/scripts/test_run_ai_review.py):

  1. Same-repo only — the job if guard requires pull_request.head.repo.full_name == github.repository (fork PRs never run).
  2. Trusted install — the checkout step uses pull_request.base.sha, never the PR head, so code that runs with the credential is always from the trusted base ref. The claude CLI is installed at a version pinned in that same trusted checkout.
  3. Secret ordering — CLAUDE_CODE_OAUTH_TOKEN and CURSOR_API_KEY are injected only into the final review step’s env, after checkout, the CLI install, and dependency install.

These controls landed with #1074; #1317 verified them against current main. A dedicated GitHub Environment with required reviewers is optional once (1–3) hold. Re-run the audit if the checkout ref, job guard, or secret injection site changes.

JSON error contract

Under --output json, a provider failure (invalid key, rate limit, depleted quota/credits, 5xx, or a malformed model response) emits a stable machine-readable error envelope on stdout and exits with code 2, so CI consumers can classify failures without scraping human-readable stderr prose:

{
  "error": {
    "kind": "auth_failed",
    "provider": "anthropic",
    "status": 401,
    "retryable": false,
    "message": "Anthropic authentication failed: Error code: 401 - authentication_error"
  }
}
FieldTypeMeaning
kindstring (enum)Canonical classification (see below). Stable across providers.
providerstringProvider identifier, lowercased (e.g. anthropic, openai, cursor).
statusinteger | nullExtracted HTTP status (e.g. 401, 429, 529), or null when none.
retryablebooleantrue for transient conditions safe to retry unchanged.
messagestringThe most specific underlying cause text.

kind is one of: auth_failed, insufficient_credits, quota_exceeded, rate_limited, context_length, server_error, timeout, invalid_response, unknown. retryable is true only for rate_limited, server_error, and timeout.

Exit codes under --output json:

CodeMeaning
0Review completed, no P1 findings. Success envelope on stdout.
1Review completed with P1 findings. Success envelope on stdout.
2Provider/execution failure. Error envelope (above) on stdout.

Exit 2 disambiguates a provider error from the P1-findings exit 1, so consumers never have to guess whether stdout holds a review or an error — check for the top-level error key.

Requesting a review from a pull-request comment

A user with write access (admin, maintain or write) can ask for a review round by commenting on an open, non-draft pull request from a branch of this repository. The command must be the first line of the comment:

CommentReview
@lintro reviewFull review of the whole diff; carried coverage is discarded (--full)
@lintro review deltaThe change since the last recorded round, like a push
@lintro review <path> [<path> ...]Only files under these path prefixes (--path)

Path arguments are prefixes, not globs: *, ?, .. segments, absolute paths, a leading -, and characters outside A-Z a-z 0-9 . _ / - are refused. At most 20 prefixes per request, each up to 200 characters.

  • An accepted request gets a 👀 reaction and runs the same review job as a push: the same sticky comment, the same review state, the same review slot (see Review slots and the per-PR budget) and 53-minute cap. A request never cancels a push’s review and a push never cancels a request’s review: both run, one after the other, and the later one updates the sticky comment.
  • A malformed request from a writer gets a reaction and the usage text as a reply, on any pull request (the pull-request checks apply only to a well-formed request).
  • A request from anyone without write access or from a bot account, and a well-formed request on a closed, draft or fork pull request, are ignored: the workflow logs why and posts nothing.
  • The review job checks the pull request again when it starts, after any wait in the queue. If it has since been closed, turned into a draft, or is no longer from this repository, the review is skipped and logged. If it cannot be read at that point, the job fails with a visible reason, so the requester can ask again.
  • Other comments never start or cancel a review.

The request is validated in a separate request job that holds no secrets and only a read-only token (scripts/ci/resolve_review_request.py, with the parser in lintro/ai/review/commands.py). The comment text reaches it only through the environment, never a shell line. On issue_comment there is no pull-request base commit, so the review job installs lintro from the default-branch commit that supplied the workflow (github.workflow_sha). issue_comment is not affected by GitHub’s 2026-11-02 default restriction on pull_request_target.

Review slots and the per-PR budget

Two review slots (#2796). At most two review jobs run at once across the repository. Each job waits in one of two queues, ai-review-slot-even or ai-review-slot-odd, chosen by the parity of the pull request’s number. Every round of one pull request, push or on-request, uses the same slot, so two rounds of one pull request never run at the same time and never race on the sticky comment or the review state. A queued review waits (cancel-in-progress: false, queue: max); it is never dropped. The split is static: two busy pull requests of the same parity queue behind each other while the other slot is idle. The per-pull-request cancel group for pushes and the 53-minute cap are unchanged.

Per-PR review budget (#2796). ai.review_pr_budget_usd caps the total review spend across every round of one pull request: push, delta, full and targeted on-request rounds alike. It is unset by default: there is no check and no sticky line.

  • Spend is the pull request’s cumulative review spend (pr_spend_usd in the review state) plus the running round’s spend so far. Every round adds its cost when it finishes: everything it charged, including a call whose result was dropped (a chunk cancelled at a budget stop), so the sticky line shows the figure the next round enforces against (#2814). Every mid-run checkpoint stores the running round’s spend too. So a round that is killed after a checkpoint still counts, and the next round starts from that figure. The total only grows: pruning the run history to the newest 30 rounds never lowers it. A state written before this total existed seeds it from the rounds it kept. The spend a round incurs after its last checkpoint (for example, synthesis or verification) counts only when the round finishes or stops gracefully; a hard kill after that point loses it.
  • Enforcement mirrors ai.max_cost_usd. The LINTRO_AI_REVIEW_PR_BUDGET_USD Actions variable, which the workflow forwards, always enforces. A budget set only in .lintro-config.yaml enforces only when spend is billed or estimated. So under the cli transport, a budget set only in the config is display-only, and the sticky line says so. The variable still enforces there. The variable takes a positive USD figure or uncapped; 0 is rejected as ambiguous.
  • Checks run before the round’s first provider call and at the round cap’s existing check points, before each chunk and each later pass. A budget already spent stops the round before any provider call. A round that crosses the budget finishes the call in flight and stops before the next one. The limit is whichever is tighter: the budget or ai.max_cost_usd.
  • Parallelism is unchanged by a budget, enforced or not. Chunk calls still run concurrently, so a round can overshoot the budget, but only by the calls already in flight when it is reached: each call reserves its estimate against the budget before it starts, and a call that would start past the limit does not. This is the same trade ai.max_cost_usd accepts.
  • On the cli transport the figures are a runtime bound, not a bill: the sticky line and the stop reason both end with “(runtime bound on the cli transport)”.
  • The stop is graceful. Findings so far are posted, coverage is saved, and the stop reason reads PR budget ($40.00) reached. The sticky comment shows PR budget: $X of $Y on every round, where X includes the current round.
  • The check’s outcome at the budget:
    • Coverage of the head is incomplete: the check goes red (exit 1) with its own pr_budget outcome and the headline PR review budget reached — N/M files covered at HEAD. Later rounds stop at the same budget, so raise the variable to review the rest, or leave the pull request as it is.
    • Coverage carried from earlier rounds is complete: every file is already covered at the head, so the check is green (exit 0), like any complete review.
  • Choosing a figure: on #2806, the round on the second head cost $10.03 and the round on the third $7.63, so two rounds cost about $17.66. A budget that stops a runaway pull request without cutting off a normal three-to-four-round one sits around $40–60. Unset keeps today’s behaviour.

5. Docker Image Publishing

File: .github/workflows/docker-build-publish.yml

Features:

  • 🐳 Automated Docker image building and publishing to GHCR
  • 🏷️ Smart tagging - Latest, main branch, and semantic versions
  • 🔄 Release integration - Images published on releases
  • 📦 GHCR integration — Full image at ghcr.io/lgtm-hq/py-lintro
  • 📦 GHCR base image — Minimal image at ghcr.io/lgtm-hq/py-lintro-base

The full image (ghcr.io/lgtm-hq/py-lintro) includes the runtime and optional tooling so you can run Lintro out of the box. The base image (ghcr.io/lgtm-hq/py-lintro-base) is a slimmer layer with core dependencies only—use it when you want a smaller footprint, CI-only steps, or a foundation for a custom image. Add your own packages or layers on top of the base as needed.

FROM ghcr.io/lgtm-hq/py-lintro-base
# Install project-specific tools or copy your app here

7. OpenSSF Allstar (Repository Security Enforcement)

Allstar is an OpenSSF GitHub App that enforces repository security policies org-wide or per-repo. To enable at the repo level:

  • Create .allstar/ with:
    • allstar.yaml → enable opt-in at repo level
    • branch_protection.yaml, binary_artifacts.yaml, outside.yaml, security.yaml each with optConfig: { optIn: true } and action: issue as a safe default.

Install and configure via the Allstar app and docs:

  • App install: https://github.com/apps/allstar-app
  • Policies and schema: https://github.com/ossf/allstar#policies
  • Manual install guide: https://github.com/ossf/allstar/blob/main/manual-install.md

Notes:

  • Org-wide management prefers an org .allstar repository with opt-out strategy.
  • Repo-level configs require org disableRepoOverride to be false to take effect.

Usage in CI/CD:

You can use the published Docker image in your own CI/CD pipelines:

# GitHub Actions example
- name: Run Lintro with Docker
  run: |
    docker run --rm -v ${{ github.workspace }}:/code \
      ghcr.io/lgtm-hq/py-lintro:latest check --output-format grid

# GitLab CI example
lintro:
  image: ghcr.io/lgtm-hq/py-lintro:latest
  script:
    - lintro check --output-format grid

Setting Up in Your Repository

1. Copy Workflow Files

Copy the workflow files from this repository to your project:

mkdir -p .github/workflows
cp .github/workflows/*.yml your-project/.github/workflows/

2. Customize for Your Project

Edit the workflow files to match your project structure:

# .github/workflows/docker-ci.yml — dogfooding-quality calls lgtm-ci reusable-quality
# with lintro-image set to the CI-built ghcr.io/lgtm-hq/py-lintro:ci-<run_id> tag.

3. Configure Repository Settings (optional for Pages)

Enable GitHub Pages:

  1. Go to repository Settings → Pages
  2. Select Source: “GitHub Actions”
  3. Your coverage badge will be available at: https://lgtm-hq.github.io/py-lintro/badges/coverage.svg

Release Automation (Single Release Train)

The repository ships with fully automated releases and PyPI publishing via lgtm-ci reusable workflows.

  • Automated Release PR (.github/workflows/release-version-pr.yml)

    • On push to main, computes the next version from Conventional Commits
    • Updates version files via lgtm-ci ecosystem updaters
    • Opens a Release PR (no direct push to main); it is merged deliberately once its generated changelog has been read, never auto-merged (#2694)
  • Auto Tag on Main (.github/workflows/release-auto-tag.yml)

    • After the Release PR is merged, creates/pushes the version tag
    • GitHub Release is created by publish-pypi-on-tag.yml on tag push
  • Publish to PyPI on Tag (.github/workflows/publish-pypi-on-tag.yml)

    • On tag push (e.g., 1.2.3), verifies tag equals pyproject.toml version
    • Uses Trusted Publishing (OIDC) to upload to PyPI
    • Also creates a GitHub Release and attaches built artifacts

End-to-end: Conventional commits → Release PR (merged by hand) → Tag created → PyPI publish.

Permissions Model (least privilege)

  • Default each workflow to permissions: { contents: read }.
  • Grant elevated permissions only where required:
    • Tag creation job: contents: write.
    • PyPI publish job: id-token: write (for OIDC) and contents: write only if creating a GH Release.
    • PR comment jobs: pull-requests: write.

Why we do not allow astral-sh/setup-uv

Our Actions policy requires that all actions (including transitive actions used by composites) are allowlisted and pinned to a full commit SHA. The astral-sh/setup-uv action invokes actions/setup-python@v5 internally, which is both not on our allowlist and referenced by tag (not a 40-char SHA). This causes policy enforcement to block any job that uses setup-uv.

To comply, we replaced it with an internal composite setup-env that:

  • installs uv via pip (no nested actions),
  • provisions the requested Python version via uv python install, and
  • syncs dependencies, keeping our pipeline policy-compliant.

Deprecated/manual flows (e.g., direct Release creation workflows) are removed to avoid parallel release paths.

Labels & guards

  • Release PRs are labeled release-bump to make them easy to target in policies.
  • Tagging is handled by release-auto-tag.yml, which only tags commits matching chore(release): version … after Release PR merges.

Security & Pinning

  • Third-party actions are pinned to commit SHAs for reproducibility and supply-chain safety.
  • Official GitHub actions can also be pinned; we’ve pinned most for consistency.
  • pypa/gh-action-pypi-publish remains on release/v1 by policy (Trusted Publishing updates). If desired, pinning to a SHA is possible.

Example Workflows

Basic Quality Check

name: Code Quality

on:
  pull_request:
  push:
    branches: [main]

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.13'

      - name: Install UV
        run: pip install uv

      - name: Install dependencies
        run: uv sync

      - name: Run Lintro
        run: |
          # Run core tools, then post-checks (Black) per pyproject config
          uv run lintro check --output-format grid --output lintro-results.txt
          cat lintro-results.txt

      - name: Upload results
        uses: actions/upload-artifact@v3
        if: always()
        with:
          name: lintro-results
          path: lintro-results.txt

Auto-fix Pull Request

name: Auto-fix Code Issues

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  autofix:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.13'

      - name: Install UV and dependencies
        run: |
          pip install uv
          uv sync

      - name: Run Lintro auto-fix
        run: uv run lintro format --output-format grid

      - name: Check for changes
        id: verify-changed-files
        run: |
          if [ -n "$(git status --porcelain)" ]; then
            echo "changed=true" >> $GITHUB_OUTPUT
          else
            echo "changed=false" >> $GITHUB_OUTPUT
          fi

      - name: Commit changes
        if: steps.verify-changed-files.outputs.changed == 'true'
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"
          git add .
          git commit -m "style: auto-fix code issues with Lintro"
          git push

Quality Gate

name: Quality Gate

on:
  pull_request:

jobs:
  quality-gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.13'

      - name: Install UV and dependencies
        run: |
          pip install uv
          uv sync

      - name: Run quality checks
        run: |
          # Try to auto-fix first
          uv run lintro format --output-format grid

          # Then check for remaining issues
          uv run lintro check --output-format grid --output quality-report.txt

          # Fail if critical issues remain
          if grep -q "error" quality-report.txt; then
            echo "❌ Critical quality issues found"
            cat quality-report.txt
            exit 1
          else
            echo "✅ Quality gate passed"
          fi

Badge Integration

Coverage Badge

Add to your README.md:

![Coverage](https://lgtm-hq.github.io/py-lintro/badges/coverage.svg)

Quality Badge

![Code Quality](https://github.com/lgtm-hq/py-lintro/workflows/CI%20-%20Quality/badge.svg)

Custom Lintro Badge

![Lintro](https://img.shields.io/badge/code%20quality-lintro-blue)

OpenSSF Scorecard Badge

Add to your README.md:

[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/lgtm-hq/py-lintro/badge)](https://scorecard.dev/viewer/?uri=github.com/lgtm-hq/py-lintro)

Reference installation docs: https://github.com/ossf/scorecard?tab=readme-ov-file#installation.

Advanced Configuration

Tool-Specific Workflows

# Python-only quality check
- name: Python Quality
  run: uv run lintro check src/ tests/ --tools ruff,pydoclint --output-format grid

# Frontend-only quality check
- name: Frontend Quality
  run: uv run lintro check assets/ --tools prettier --output-format grid

# Infrastructure quality check
- name: Infrastructure Quality
  run: uv run lintro check Dockerfile* --tools hadolint --output-format grid

Matrix Builds

strategy:
  matrix:
    python-version: ['3.11', '3.12', '3.13']
    tool: ['ruff', 'pydoclint', 'oxfmt']

Conditional Execution

- name: Run Lintro on changed files
  run: |
    # Get changed files
    git diff --name-only HEAD^ HEAD > changed-files.txt

    # Run Lintro only on changed files
    if [ -s changed-files.txt ]; then
      uv run lintro check $(cat changed-files.txt) --output-format grid
    else
      echo "No files changed"
    fi

Troubleshooting

Common Issues

1. Workflow not triggering:

  • Check workflow file syntax
  • Ensure proper indentation (YAML)
  • Verify trigger conditions

2. Permission denied:

- uses: actions/checkout@v4
  with:
    token: ${{ secrets.GITHUB_TOKEN }}

3. Dependencies not installed:

- name: Install dependencies
  run: |
    pip install uv
    uv sync --dev

4. Tool not found:

- name: Install system dependencies
  run: |
    sudo apt-get update
    sudo apt-get install -y hadolint

Debug Workflow

- name: Debug Lintro
  run: |
    echo "=== Environment ==="
    python --version
    uv --version

    echo "=== Available tools ==="
    uv run lintro list-tools

    echo "=== File structure ==="
    find . -name "*.py" | head -10

    echo "=== Running Lintro ==="
    uv run lintro check --output-format grid || true

Integration Benefits

Using Lintro in GitHub Actions provides:

  1. Early Issue Detection - Catch problems before they reach production
  2. Consistent Quality - Enforce coding standards across all contributors
  3. Automated Fixes - Reduce manual work with auto-fixing
  4. Comprehensive Reporting - Multi-tool analysis in one place
  5. Quality Gates - Block problematic code from merging
  6. Coverage Tracking - Monitor test coverage over time

Best Practices

  1. Run Lintro early in your CI pipeline (before tests)
  2. Use auto-fix first, then check for remaining issues
  3. Separate workflows for different file types when needed
  4. Cache dependencies to speed up workflows
  5. Use artifacts to preserve reports
  6. Set up quality gates to maintain code standards
  7. Monitor coverage trends over time

This integration transforms your repository into a high-quality, maintainable codebase with automated quality assurance! 🚀