{"slug": "impactgate-a-merge-gate-that-scores-the-structural-decay-ai-adds", "title": "ImpactGate: A merge gate that scores the structural decay AI adds", "summary": "OfficeFloor released ImpactGate v0.3.0, an open-source merge gate that scores the structural decay a code change introduces using the formula impact = files_changed * Σ max(WMC_other, 1) * CC * Δlines over changed functions. The tool runs as a standalone CLI, a git pre-commit hook, or a plugin for GitHub, GitLab, and Jenkins CI, and can warn or block a build when impact exceeds configurable thresholds such as --warn-at 50000 and --block-at 200000. ImpactGate is installable via pip install impact-gate or the ghcr.io/officefloor/impact-gate Docker image, and it also supports percentile-based grading against a project's own impact distribution written to .impact-gate-baseline.json.", "body_md": "Measure and gate the structural decay a change introduces. Run it as a standalone CLI, a git pre-commit hook, or a plugin in GitHub, GitLab, and Jenkins CI.\n\nWebsite: [https://impactgate.officefloor.net](https://impactgate.officefloor.net)\n\nStructural decay is complexity accreting into existing structures. A god-method grows\nanother branch. A god-class gains another method. The gate scores a change against a\nbase (`main` by default) with the change-impact measure:\n\n```\nimpact = files_changed * Σ max(WMC_other, 1) * CC * Δlines      (over changed functions)\n```\n\n`WMC_other` is the complexity already in the container you are editing. It is measured\non the pre-change state. So importing a brand-new file or class is cheap. Nothing was\nthere before. Piling onto an already-heavy class is expensive. That is the decay signal.\n\nFor the reasoning behind the formula, see [Measuring the Blast Radius of\nChange](https://blog.officefloor.net/2026/08/measuring-blast-radius-of-change.html) on\nthe OfficeFloor blog.\n\nWhen impact is too high, the gate asks you to simplify the change or refactor the code it touches. It can warn (report only) or block (fail the build).\n\n```\npip install impact-gate         # installs the `impact-gate` command\n```\n\nOr run it without installing anything, via the published image (git is bundled;\nmount the repo to score at `/repo`):\n\n```\ndocker run --rm -v \"$PWD:/repo\" ghcr.io/officefloor/impact-gate \\\n  score --mode range --base origin/main\n```\n\nTo hack on it locally, install from a checkout instead:\n\n```\npython -m venv .venv && . .venv/bin/activate\npip install -e '.[dev]'         # editable install plus the test deps\n# The commit you are about to make (pre-commit): staged vs HEAD. This is the default.\nimpact-gate score\n\n# Uncommitted local edits: working tree vs HEAD.\nimpact-gate score --mode worktree\n\n# CI or PR review: the committed branch vs main (merge-base..HEAD).\nimpact-gate score --mode range --base origin/main --format json\n\n# Set thresholds and enforcement. You can also put these in .impact-gate.yml.\nimpact-gate score --warn-at 50000 --block-at 200000 --enforcement block\n```\n\nExit codes. `0` means ok or warn (the change is allowed). `2` means blocked (impact too\nhigh under `--enforcement block`). `1` means a usage or environment error.\n\nEvery report also lists the **files to consider for refactoring**, ranked by their share\nof the impact. The change-level number gates; the per-file ranking points at where the\ndecay is concentrating, so a file quietly growing into a god-class surfaces as a\ncandidate before it blocks anything.\n\nA source file whose diff is larger than `max_diff_lines` (200,000 by default, in the\nmeasure config) is almost always a generated dump or a vendored blob. The gate skips it\nso it neither distorts the number nor slows scoring, and lists it under **skipped** so\nthe result is never silently wrong.\n\nGate every commit locally, before CI:\n\n```\n# Installs .git/hooks/pre-commit. It scores the staged change on each commit.\nimpact-gate install-hook\n```\n\nWith `enforcement: block` in `.impact-gate.yml`, a commit whose impact is too high is\nblocked; on `warn` (or off) the report prints and the commit proceeds. Re-run with\n`--force` to overwrite an existing pre-commit hook.\n\nPrefer the [pre-commit](https://pre-commit.com) framework? This repo ships a hook\ndefinition — add to your `.pre-commit-config.yaml`:\n\n```\nrepos:\n  - repo: https://github.com/officefloor/ImpactGate\n    rev: v0.3.0\n    hooks:\n      - id: impact-gate\n```\n\nA raw threshold is hard to set: a typical change's impact varies by orders of magnitude\nacross languages and projects. Instead of guessing a number, grade a change by its\n**percentile** against a distribution, and gate on the percentile.\n\n```\n# Build (or refresh) the project's own impact distribution from the merged history.\n# Writes .impact-gate-baseline.json. Re-run it as the branch moves.\nimpact-gate baseline --base-ref main\n\n# Gate on the grade instead of an absolute number.\nimpact-gate score --curve --warn-percentile 90 --block-percentile 98\n```\n\nThe grade blends two distributions:\n\n- a **seed prior** shipped with the tool — per-language percentile tables built from a\n20-repo open-source corpus, with a pooled fallback for languages not in the table;\n- the **project baseline** — the repo's own per-change distribution, walked from the\nmerged mainline (only landed work; in-flight branches are never reached).\n\nThe blend weights the project by `w = n / (n + K)`, where `n` is the number of landed\nchanges behind the baseline and `K` (`curve_prior_weight`, default 200) is how much\nhistory it takes to trust the project over the seed. A fresh repo with no baseline file\ngrades on the seed alone; a deep history leans on itself. The grade shows in every\nformat next to the raw number.\n\n```\nwarn_at: 50000          # impact above which to warn\nblock_at: 200000        # impact above which to block\nenforcement: warn       # off, warn, or block. Start on warn. Flip to block when ready.\ntolerance: 1.0          # CI-adjustable multiplier on both thresholds. Above 1 is more lenient.\n# measure_config: .impact-measure.yml   # optional: ignore globs and language overrides\n\n# Grading curve (percentile gate). When enabled, warn_at/block_at are ignored and the\n# gate uses the percentiles below instead.\ncurve_enabled: false           # gate on the percentile grade instead of absolute numbers\nwarn_percentile: 90            # grade at or above this warns\nblock_percentile: 98           # grade at or above this blocks\ncurve_prior_weight: 200        # K in w = n/(n+K): history needed to trust the project over the seed\nbaseline_file: .impact-gate-baseline.json   # where `impact-gate baseline` caches the distribution\n```\n\nCLI flags override the file. A CI job can pass `--tolerance` or `--warn-at`. So a team\ncan dial tolerance without editing the repo. The curve knobs have flags too: `--curve`,\n`--warn-percentile`, `--block-percentile`, `--baseline-file`.\n\nAdd a workflow to your repo. The action scores the PR branch against its base and writes\na summary. `fetch-depth: 0` is required so the base branch and merge-base are present.\n\n```\nname: Change impact\non: pull_request\npermissions:\n  contents: read\n  pull-requests: write         # so the action can post the score as a PR comment\njobs:\n  impact:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v5\n        with:\n          fetch-depth: 0\n      - uses: officefloor/ImpactGate@v0\n        with:\n          enforcement: warn        # switch to block when ready\n          # warn-at: 50000\n          # block-at: 200000\n          # tolerance: 1.0\n```\n\nThe score appears in the job summary and as a sticky comment on the PR (one comment,\nupdated each run). In `block` mode the job fails when impact exceeds the block threshold.\nMake the check required in branch protection to gate merges. The comment needs\n`pull-requests: write`. Without it the run still passes and just skips the comment.\n\nA ready-made job is in [`ci/gitlab-ci.yml`](https://github.com/officefloor/ImpactGate/blob/main/ci/gitlab-ci.yml). Copy it into your\n`.gitlab-ci.yml`, or include it remotely:\n\n```\ninclude:\n  - remote: 'https://raw.githubusercontent.com/officefloor/ImpactGate/v0/ci/gitlab-ci.yml'\n```\n\nIt runs on merge-request pipelines, scores the MR against its base\n(`$CI_MERGE_REQUEST_DIFF_BASE_SHA`) with the published Docker image, and — when a CI/CD\nvariable `GITLAB_TOKEN` with the `api` scope is set — posts a sticky note to the MR (one\nnote, updated each run). Without the token it still scores and gates; it just skips the\nnote. In `block` enforcement the job fails when impact is too high; make it required in\nthe merge request settings to gate merges.\n\nA pipeline snippet is in [`ci/Jenkinsfile`](https://github.com/officefloor/ImpactGate/blob/main/ci/Jenkinsfile). It runs the Docker image on\nan agent with Docker, scoring the change against its target branch\n(`origin/${CHANGE_TARGET:-main}`) and archiving the report. In `block` enforcement the\nstage fails when impact is too high. Posting the score back to the PR/MR is left to your\nSCM integration; to post it with the tool itself, run `impact-gate comment` in the\ncontainer with the provider's token and env set.\n\n- Core CLI. Score staged, worktree, or range. Warn or block. Text, JSON, markdown. Done.\n- GitHub Action. Composite action, job-summary report, and a sticky PR comment. Done.\n- Baseline and grading curve. `impact-gate baseline` profiles the project history; the\ngate blends a seed-corpus prior with the project's own distribution and grades a change\nby its percentile (`score --curve` ). Done.\n- Distribution. `pip install impact-gate` , a`ghcr.io/officefloor/impact-gate` Docker\nimage for any CI, and a version-tagged Action (`@v0` ). Done.\n- More CI plugins. A GitLab CI template and a Jenkins pipeline snippet, both wrapping the\nDocker image (`ci/gitlab-ci.yml` ,`ci/Jenkinsfile` ). GitLab posts a sticky MR note. Done.\n- Hooks. `impact-gate install-hook` installs a git pre-commit hook, and a`.pre-commit-hooks.yaml` supports the pre-commit framework. Done.\n- IDE. Editor integration over LSP, with a live gauge as you edit.", "url": "https://wpnews.pro/news/impactgate-a-merge-gate-that-scores-the-structural-decay-ai-adds", "canonical_source": "https://github.com/officefloor/ImpactGate", "published_at": "2026-09-16 13:01:09+00:00", "updated_at": "2026-09-16 13:12:52.906614+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools"], "entities": ["OfficeFloor", "ImpactGate", "GitHub", "GitLab", "Jenkins", "pre-commit"], "alternates": {"html": "https://wpnews.pro/news/impactgate-a-merge-gate-that-scores-the-structural-decay-ai-adds", "markdown": "https://wpnews.pro/news/impactgate-a-merge-gate-that-scores-the-structural-decay-ai-adds.md", "text": "https://wpnews.pro/news/impactgate-a-merge-gate-that-scores-the-structural-decay-ai-adds.txt", "jsonld": "https://wpnews.pro/news/impactgate-a-merge-gate-that-scores-the-structural-decay-ai-adds.jsonld"}}