ImpactGate: A merge gate that scores the structural decay AI adds

Sep 16, 2026 08:01 PM - 2 hours ago 4

Measure and gross the structural decay a alteration introduces. Run it arsenic a standalone CLI, a git pre-commit hook, aliases a plugin successful GitHub, GitLab, and Jenkins CI.

Website: https://impactgate.officefloor.net

Structural decay is complexity accreting into existing structures. A god-method grows another branch. A god-class gains different method. The gross scores a alteration against a base (main by default) pinch the change-impact measure:

impact = files_changed * Σ max(WMC_other, 1) * CC * Δlines (over changed functions)

WMC_other is the complexity already successful the instrumentality you are editing. It is measured on the pre-change state. So importing a brand-new record aliases people is cheap. Nothing was there before. Piling onto an already-heavy people is expensive. That is the decay signal.

For the reasoning down the formula, spot Measuring the Blast Radius of Change on the OfficeFloor blog.

When effect is excessively high, the gross asks you to simplify the alteration aliases refactor the code it touches. It tin pass (report only) aliases artifact (fail the build).

pip instal impact-gate # installs the `impact-gate` command

Or tally it without installing anything, via the published image (git is bundled; mount the repo to people astatine /repo):

docker tally --rm -v "$PWD:/repo" ghcr.io/officefloor/impact-gate \ people --mode scope --base origin/main

To hack connected it locally, instal from a checkout instead:

python -m venv .venv && . .venv/bin/activate pip instal -e '.[dev]' # editable instal positive the trial deps
# The perpetrate you are astir to make (pre-commit): staged vs HEAD. This is the default. impact-gate score # Uncommitted section edits: moving character vs HEAD. impact-gate people --mode worktree # CI aliases PR review: the committed branch vs main (merge-base..HEAD). impact-gate people --mode scope --base origin/main --format json # Set thresholds and enforcement. You tin besides put these successful .impact-gate.yml. impact-gate people --warn-at 50000 --block-at 200000 --enforcement block

Exit codes. 0 intends good aliases pass (the alteration is allowed). 2 intends blocked (impact too high nether --enforcement block). 1 intends a usage aliases situation error.

Every study besides lists the files to see for refactoring, classed by their share of the impact. The change-level number gates; the per-file ranking points astatine wherever the decay is concentrating, truthful a record softly increasing into a god-class surfaces arsenic a candidate earlier it blocks anything.

A root record whose diff is larger than max_diff_lines (200,000 by default, successful the measure config) is almost ever a generated dump aliases a vendored blob. The gross skips it so it neither distorts the number nor slows scoring, and lists it nether skipped so the consequence is ne'er silently wrong.

Use arsenic a git pre-commit hook

Gate each perpetrate locally, earlier CI:

# Installs .git/hooks/pre-commit. It scores the staged alteration connected each commit. impact-gate install-hook

With enforcement: artifact successful .impact-gate.yml, a perpetrate whose effect is excessively precocious is blocked; connected pass (or off) the study prints and the perpetrate proceeds. Re-run with --force to overwrite an existing pre-commit hook.

Prefer the pre-commit framework? This repo ships a hook definition — adhd to your .pre-commit-config.yaml:

repos: - repo: https://github.com/officefloor/ImpactGate rev: v0.3.0 hooks: - id: impact-gate

Grade against a distribution (the curve)

A earthy period is difficult to set: a emblematic change's effect varies by orders of magnitude across languages and projects. Instead of guessing a number, people a alteration by its percentile against a distribution, and gross connected the percentile.

# Build (or refresh) the project's ain effect distribution from the merged history. # Writes .impact-gate-baseline.json. Re-run it arsenic the branch moves. impact-gate baseline --base-ref main # Gate connected the people alternatively of an absolute number. impact-gate people --curve --warn-percentile 90 --block-percentile 98

The people blends 2 distributions:

  • a seed prior shipped pinch the instrumentality — per-language percentile tables built from a 20-repo open-source corpus, pinch a pooled fallback for languages not successful the table;
  • the project baseline — the repo's ain per-change distribution, walked from the merged mainline (only landed work; in-flight branches are ne'er reached).

The blend weights the task by w = n / (n + K), wherever n is the number of landed changes down the baseline and K (curve_prior_weight, default 200) is really much history it takes to spot the task complete the seed. A caller repo pinch nary baseline file grades connected the seed alone; a heavy history leans connected itself. The people shows successful every format adjacent to the earthy number.

Configure pinch .impact-gate.yml (repo root)

warn_at: 50000 # effect supra which to warn block_at: 200000 # effect supra which to block enforcement: warn # off, warn, aliases block. Start connected warn. Flip to artifact erstwhile ready. tolerance: 1.0 # CI-adjustable multiplier connected some thresholds. Above 1 is much lenient. # measure_config: .impact-measure.yml # optional: disregard globs and connection overrides # Grading curve (percentile gate). When enabled, warn_at/block_at are ignored and the # gross uses the percentiles beneath instead. curve_enabled: false # gross connected the percentile people alternatively of absolute numbers warn_percentile: 90 # people astatine aliases supra this warns block_percentile: 98 # people astatine aliases supra this blocks curve_prior_weight: 200 # K successful w = n/(n+K): history needed to spot the task complete the seed baseline_file: .impact-gate-baseline.json # wherever `impact-gate baseline` caches the distribution

CLI flags override the file. A CI occupation tin walk --tolerance aliases --warn-at. So a team can dial tolerance without editing the repo. The curve knobs person flags too: --curve, --warn-percentile, --block-percentile, --baseline-file.

Add a workflow to your repo. The action scores the PR branch against its guidelines and writes a summary. fetch-depth: 0 is required truthful the guidelines branch and merge-base are present.

name: Change impact on: pull_request permissions: contents: read pull-requests: write # truthful the action tin station the people arsenic a PR comment jobs: impact: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 with: fetch-depth: 0 - uses: officefloor/ImpactGate@v0 with: enforcement: warn # move to artifact erstwhile ready # warn-at: 50000 # block-at: 200000 # tolerance: 1.0

The people appears successful the occupation summary and arsenic a sticky remark connected the PR (one comment, updated each run). In artifact mode the occupation fails erstwhile effect exceeds the artifact threshold. Make the cheque required successful branch protection to gross merges. The remark needs pull-requests: write. Without it the tally still passes and conscionable skips the comment.

A ready-made occupation is successful ci/gitlab-ci.yml. Copy it into your .gitlab-ci.yml, aliases see it remotely:

include: - remote: 'https://raw.githubusercontent.com/officefloor/ImpactGate/v0/ci/gitlab-ci.yml'

It runs connected merge-request pipelines, scores the MR against its base ($CI_MERGE_REQUEST_DIFF_BASE_SHA) pinch the published Docker image, and — erstwhile a CI/CD variable GITLAB_TOKEN pinch the api scope is group — posts a sticky statement to the MR (one note, updated each run). Without the token it still scores and gates; it conscionable skips the note. In artifact enforcement the occupation fails erstwhile effect is excessively high; make it required in the merge petition settings to gross merges.

A pipeline snippet is successful ci/Jenkinsfile. It runs the Docker image on an supplier pinch Docker, scoring the alteration against its target branch (origin/${CHANGE_TARGET:-main}) and archiving the report. In artifact enforcement the stage fails erstwhile effect is excessively high. Posting the people backmost to the PR/MR is near to your SCM integration; to station it pinch the instrumentality itself, tally impact-gate remark successful the container pinch the provider's token and env set.

  • Core CLI. Score staged, worktree, aliases range. Warn aliases block. Text, JSON, markdown. Done.
  • GitHub Action. Composite action, job-summary report, and a sticky PR comment. Done.
  • Baseline and grading curve. impact-gate baseline profiles the task history; the gate blends a seed-corpus anterior pinch the project's ain distribution and grades a change by its percentile (score --curve). Done.
  • Distribution. pip instal impact-gate, a ghcr.io/officefloor/impact-gate Docker image for immoderate CI, and a version-tagged Action (@v0). Done.
  • More CI plugins. A GitLab CI template and a Jenkins pipeline snippet, some wrapping the Docker image (ci/gitlab-ci.yml, ci/Jenkinsfile). GitLab posts a sticky MR note. Done.
  • Hooks. impact-gate install-hook installs a git pre-commit hook, and a .pre-commit-hooks.yaml supports the pre-commit framework. Done.
  • IDE. Editor integration complete LSP, pinch a unrecorded gauge arsenic you edit.
More