메뉴
HN
Hacker News • 9일 전

임팩트게이트, AI가 만드는 구조적 부패를 측정해 차단하는 머지 게이트

IMP
6/10
핵심 요약

ImpactGate는 코드 변경이 기존 구조에 추가하는 복잡도(구조적 부패)를 점수화하여, 임계값을 초과하면 경고하거나 빌드를 차단하는 오픈소스 도구입니다. CLI, git pre-commit 훅, GitHub/GitLab/Jenkins CI 플러그인으로 사용할 수 있으며, 이미 복잡한 클래스에 변경이 누적될수록 높은 점수를 매겨 '갓 클래스'로 조용히 비대해지는 파일을 조기에 발견할 수 있게 합니다. AI 생성 코드가 대량으로 유입되는 상황에서 코드베이스 품질을 지키는 실용적인 방어선이라는 점에서 주목받습니다.

번역된 본문

impact-gate: 변경이 유발하는 구조적 부패를 측정하고 게이트로 차단하세요. 독립형 CLI, git pre-commit 훅, 또는 GitHub·GitLab·Jenkins CI의 플러그인으로 실행할 수 있습니다. 웹사이트: https://impactgate.officefloor.net

구조적 부패(structural decay)란 기존 구조에 복잡도가 축적되는 현상입니다. '갓 메서드(god-method)'에 분기가 하나 더 늘어나거나, '갓 클래스(god-class)'에 메서드가 하나 더 추가되는 식입니다.

이 게이트는 변경을 기준 브랜치(기본값은 main)와 비교하여 '변경 임팩트(change-impact)' 지표로 점수를 매깁니다:

impact = 변경된 파일 수 * Σ max(WMC_other, 1) * CC * Δ라인 수 (변경된 함수 기준)

WMC_other는 편집 대상 컨테이너(클래스 등)에 이미 존재하던 복잡도로, 변경 전 상태에서 측정됩니다. 따라서 완전히 새로운 파일이나 클래스를 추가하는 것은 저렴합니다. 원래 아무것도 없었으니까요. 반면 이미 무거운 클래스에 계속 쌓는 것은 비쌉니다. 그것이 바로 부패 신호입니다. 공식의 설계 배경은 OfficeFloor 블로그의 'Measuring the Blast Radius of Change' 글을 참고하세요.

임팩트가 너무 높으면 게이트는 변경을 단순화하거나 관련 코드를 리팩터링하라고 요구합니다. 경고만 하는 모드(리포트만 출력)와 차단 모드(빌드 실패)를 선택할 수 있습니다.

설치

pip install impact-gate # impact-gate 명령 설치

설치 없이 게시된 도커 이미지로도 실행할 수 있습니다(git 포함, 점수를 매길 저장소를 /repo에 마운트):

docker run --rm -v "$PWD:/repo" ghcr.io/officefloor/impact-gate score --mode range --base origin/main

로컬에서 개발하려면 체크아웃에서 설치하세요:

python -m venv .venv && . .venv/bin/activate pip install -e '.[dev]' # 편집 가능 설치 + 테스트 의존성

사용법

방금 커밋하려는 내용 (pre-commit): staged vs HEAD. 기본값.

impact-gate score

커밋 전 로컬 수정: working tree vs HEAD.

impact-gate score --mode worktree

CI 또는 PR 리뷰: 커밋된 브랜치 vs main (merge-base..HEAD).

impact-gate score --mode range --base origin/main --format json

임계값과 강제 방식 설정. .impact-gate.yml에 넣을 수도 있습니다.

impact-gate score --warn-at 50000 --block-at 200000 --enforcement block

종료 코드: 0은 통과 또는 경고(변경 허용), 2는 차단(--enforcement block에서 임팩트 과다), 1은 사용법 또는 환경 오류입니다.

모든 리포트에는 리팩터링을 고려할 파일 목록도 임팩트 비중 순으로 표시됩니다. 변경 단위의 숫자가 게이트 역할을 하고, 파일별 순위는 부패가 어디에 집중되는지 보여주므로, 어떤 파일이 조용히 갓 클래스로 자라나고 있다면 차단되기 전에 후보로 먼저 드러납니다.

diff 크기가 max_diff_lines(측정 설정 기본값 200,000줄)를 초과하는 소스 파일은 거의 항상 자동 생성 덤프나 벤더링된 blob이므로 게이트가 건너뜁니다. 숫자를 왜곡하거나 채점을 늦추지 않도록 하기 위해서이며, skipped 목록에 표시해 결과가 조용히 틀리는 일이 없게 합니다.

git pre-commit 훅으로 사용

CI 전에 로컬에서 모든 커밋을 게이트하세요:

.git/hooks/pre-commit을 설치. 매 커밋마다 staged 변경의 점수를 매깁니다.

impact-gate install-hook

.impact-gate.yml에 enforcement: block을 설정하면 임팩트가 너무 큰 커밋이 차단되고, warn(또는 off)이면 리포트만 출력되고 커밋이 진행됩니다. 기존 pre-commit 훅을 덮어쓰려면 --force로 재실행하세요.

pre-commit 프레임워크를 선호한다면 이 저장소에 훅 정의가 포함되어 있습니다. .pre-commit-config.yaml에 추가하세요:

repos:

분포(곡선) 기준으로 등급 매기기

절대 임계값은 설정하기 어렵습니다. 일반적인 변경의 임팩트는 언어와 프로젝트에 따라 몇 자릿수씩 차이가 나기 때문입니다. 숫자를 추측하는 대신, 변경을 프로젝트 분포 대비 백분위로 등급 매기고 그 백분위로 게이트하세요.

병합 히스토리에서 프로젝트 자체의 임팩트 분포를 생성(또는 갱신).

.impact-gate-baseline.json을 작성하며, 브랜치가 이동하면 다시 실행합니다.

impact-gate baseline --base-ref main

절대 숫자 대신 등급으로 게이트.

impact-gate score --curve --warn-percentile 90 --block-percentile 98

등급은 두 분포를 혼합합니다. 하나는 도구에 함께 제공되는 사전 분포로, (본문 중략) 언어별 백분위 테이블입니다.

원문 보기
원문 보기 (영어)
impact-gate 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. Website: https://impactgate.officefloor.net Structural decay is complexity accreting into existing structures. A god-method grows another branch. A god-class gains another method. The gate scores a change against a base ( main by default) with the change-impact measure: impact = files_changed * Σ max(WMC_other, 1) * CC * Δlines (over changed functions) WMC_other is the complexity already in the container you are editing. It is measured on the pre-change state. So importing a brand-new file or class is cheap. Nothing was there before. Piling onto an already-heavy class is expensive. That is the decay signal. For the reasoning behind the formula, see Measuring the Blast Radius of Change on the OfficeFloor blog. When 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). Install pip install impact-gate # installs the `impact-gate` command Or run it without installing anything, via the published image (git is bundled; mount the repo to score at /repo ): docker run --rm -v " $PWD :/repo " ghcr.io/officefloor/impact-gate \ score --mode range --base origin/main To hack on it locally, install from a checkout instead: python -m venv .venv && . .venv/bin/activate pip install -e ' .[dev] ' # editable install plus the test deps Use # The commit you are about to make (pre-commit): staged vs HEAD. This is the default. impact-gate score # Uncommitted local edits: working tree vs HEAD. impact-gate score --mode worktree # CI or PR review: the committed branch vs main (merge-base..HEAD). impact-gate score --mode range --base origin/main --format json # Set thresholds and enforcement. You can also put these in .impact-gate.yml. impact-gate score --warn-at 50000 --block-at 200000 --enforcement block Exit codes. 0 means ok or warn (the change is allowed). 2 means blocked (impact too high under --enforcement block ). 1 means a usage or environment error. Every report also lists the files to consider for refactoring , ranked by their share of the impact. The change-level number gates; the per-file ranking points at where the decay is concentrating, so a file quietly growing into a god-class surfaces as a candidate before it blocks anything. A source file whose diff is larger than max_diff_lines (200,000 by default, in the measure config) is almost always a generated dump or a vendored blob. The gate skips it so it neither distorts the number nor slows scoring, and lists it under skipped so the result is never silently wrong. Use as a git pre-commit hook Gate every commit locally, before CI: # Installs .git/hooks/pre-commit. It scores the staged change on each commit. impact-gate install-hook With enforcement: block in .impact-gate.yml , a commit whose impact is too high is blocked; on warn (or off) the report prints and the commit proceeds. Re-run with --force to overwrite an existing pre-commit hook. Prefer the pre-commit framework? This repo ships a hook definition — add 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 raw threshold is hard to set: a typical change's impact varies by orders of magnitude across languages and projects. Instead of guessing a number, grade a change by its percentile against a distribution, and gate on the percentile. # Build (or refresh) the project's own impact distribution from the merged history. # Writes .impact-gate-baseline.json. Re-run it as the branch moves. impact-gate baseline --base-ref main # Gate on the grade instead of an absolute number. impact-gate score --curve --warn-percentile 90 --block-percentile 98 The grade blends two distributions: a seed prior shipped with the tool — per-language percentile tables built from a 20-repo open-source corpus, with a pooled fallback for languages not in the table; the project baseline — the repo's own per-change distribution, walked from the merged mainline (only landed work; in-flight branches are never reached). The blend weights the project by w = n / (n + K) , where n is the number of landed changes behind the baseline and K ( curve_prior_weight , default 200) is how much history it takes to trust the project over the seed. A fresh repo with no baseline file grades on the seed alone; a deep history leans on itself. The grade shows in every format next to the raw number. Configure with .impact-gate.yml (repo root) warn_at : 50000 # impact above which to warn block_at : 200000 # impact above which to block enforcement : warn # off, warn, or block. Start on warn. Flip to block when ready. tolerance : 1.0 # CI-adjustable multiplier on both thresholds. Above 1 is more lenient. # measure_config: .impact-measure.yml # optional: ignore globs and language overrides # Grading curve (percentile gate). When enabled, warn_at/block_at are ignored and the # gate uses the percentiles below instead. curve_enabled : false # gate on the percentile grade instead of absolute numbers warn_percentile : 90 # grade at or above this warns block_percentile : 98 # grade at or above this blocks curve_prior_weight : 200 # K in w = n/(n+K): history needed to trust the project over the seed baseline_file : .impact-gate-baseline.json # where `impact-gate baseline` caches the distribution CLI flags override the file. A CI job can pass --tolerance or --warn-at . So a team can dial tolerance without editing the repo. The curve knobs have flags too: --curve , --warn-percentile , --block-percentile , --baseline-file . Use in GitHub Actions Add a workflow to your repo. The action scores the PR branch against its base and writes a summary. fetch-depth: 0 is required so the base branch and merge-base are present. name : Change impact on : pull_request permissions : contents : read pull-requests : write # so the action can post the score as 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 # switch to block when ready # warn-at: 50000 # block-at: 200000 # tolerance: 1.0 The score appears in the job summary and as a sticky comment on the PR (one comment, updated each run). In block mode the job fails when impact exceeds the block threshold. Make the check required in branch protection to gate merges. The comment needs pull-requests: write . Without it the run still passes and just skips the comment. Use in GitLab CI A ready-made job is in ci/gitlab-ci.yml . Copy it into your .gitlab-ci.yml , or include it remotely: include : - remote : ' https://raw.githubusercontent.com/officefloor/ImpactGate/v0/ci/gitlab-ci.yml ' It runs on merge-request pipelines, scores the MR against its base ( $CI_MERGE_REQUEST_DIFF_BASE_SHA ) with the published Docker image, and — when a CI/CD variable GITLAB_TOKEN with the api scope is set — posts a sticky note to the MR (one note, updated each run). Without the token it still scores and gates; it just skips the note. In block enforcement the job fails when impact is too high; make it required in the merge request settings to gate merges. Use in Jenkins A pipeline snippet is in ci/Jenkinsfile . It runs the Docker image on an agent with Docker, scoring the change against its target branch ( origin/${CHANGE_TARGET:-main} ) and archiving the report. In block enforcement the stage fails when impact is too high. Posting the score back to the PR/MR is left to your SCM integration; to post it with the tool itself, run impact-gate comment in the container with the provider's token and env set. Roadmap Core CLI. Score staged, worktree, or range. Warn or block. Text, JSON, markdown. Done. GitHub Action. Composite action, job-summary report, and a sticky PR comment. Done. Baseline and grading curve. impact-gat