ローカルしきい値ゲート
CI は最後の防衛線であり、最初の防衛線ではありません。bca check(リポジトリルートの bca.toml マニフェストとその .bca-baseline.toml を読み取る)がプルリクエストで赤く点灯する頃には、問題のある変更はすでにプッシュされ、作者はコンテキストを切り替えており、誰かが diff を見直してメトリクスを制限内に押し戻さなければなりません。ローカルのしきい値ゲートは、そのフィードバックを git commit の瞬間 — cargo fmt --check と cargo clippy -- -D warnings がすでに発火するのと同じ瞬間 — に移すため、リグレッションが開発者のキーボードを越えることはありません。
このレシピは、big-code-analysis が自身のソースに対して使っているパターン(Makefile の self-scan* ターゲット。統合された bca.toml マニフェストに支えられています)を捉え、あなたのリポジトリの Makefile、justfile、package.json スクリプト、pre-commit 設定にそのまま入れられる形に蒸留したものです。根底にある考え方はプロバイダ中立です。どのしきい値チェッカー(bca、ESLint、clippy、SonarLint、Qodana)でも同じ方法で組み込めます。
原則
設計を駆動するのは 3 つの原則です。これらは bca に固有のものではありません。Sonar がデフォルトの品質ゲートを新しいコードに焦点を当てる方向へ転換したときに到達したのと同じ結論であり、より広いラチェットパターンが定式化しているものです。
- ローカルでゲートし、CI を正確にミラーする。 ローカルゲートは、CI と同じバイナリを、同じ引数、同じしきい値 / ベースライン / 除外ファイルで実行しなければなりません。ローカルゲートが「CI の実行内容とほぼ同じ」では、両者が乖離した瞬間にリグレッションを捕捉できなくなります。プッシュ前にゲートを一度実行するコストは安く、PR ボットの赤い通知のコストは安くありません。
- ラチェットせよ、リセットするな。 既存のコードベースにしきい値を導入すると、「どんな」妥当な制限でも数十の既存関数で発火します。現実的な導入経路は「今日の違反をベースラインファイルに吸収し、新規または悪化したものだけを失敗させ、ベースラインを時間をかけて縮小する」です。これは、長年運用されてきたコードベースが数か月がかりの全面改修なしに strict TypeScript や厳格な clippy リントを導入できるのと同じ戦略です。ブートストラップ → CI → 更新 → 退役の流れはベースラインのレシピを参照してください。
- 失敗させる前に警告する。 100% のハードゲートは制限「ちょうど」で失敗し、関数がしきい値の 80% から 95%、99% へと忍び寄る間は何の信号も出しません。たとえば各制限の 95% で発火する、より緩い第 2 のティアがあれば、1〜2 コミット分の早期警告になります。作者はまだファイルを開いており、テストケースが頭に入っており、違反が「まあ、もう main に入ってるし」として固定化する前にリファクタリングする自由があります。Sonar の「新しいコード」品質ゲート、GCC の
-Wall/-Werrorの分離、clippy のwarnとdenyのリントレベルは、いずれも同じ洞察を体現しています。「クリーン」と「壊れている」の間のティアこそ、チームが実際にドリフトを捕捉する場所です。
2 つのティア
このパターンは、同じチェッカーをラップする 2 つのレシピと、各ティアでベースラインを更新するための 2 つのレシピから成ります。
| ターゲット | ティア | しきい値 | ベースラインでフィルタ | ユースケース |
|---|---|---|---|---|
self-scan | ハード | 設定の 100% | はい | CI のミラー。すべてのコミットでグリーンを維持しなければなりません。 |
self-scan-headroom | ソフト | tightened by HEADROOM | はい | 早期警告バンド。ハードティアより先に発火します。 |
self-scan-write-baseline | ハード | 設定の 100% | (書き込み) | 今日のハードティア違反を吸収します。 |
self-scan-write-baseline-headroom | ソフト | tightened by HEADROOM | (書き込み) | バンドの導入時や拡大時にソフトティアの違反を吸収します。 |
The hard tier and the soft tier consume the same [thresholds] table and the same .bca-baseline.toml. The only difference between them is the HEADROOM ratio applied to every threshold value before bca check sees it — tightening each limit, which for the lower-is-worse mi.* family means raising it rather than lowering it.
共有ベースラインはソフトティア(self-scan-write-baseline-headroom)で書き込みます。v5 のベースラインは、書き込み時のティアとヘッドルームを [provenance] テーブルに記録し、bca check は現在の実行がベースライン書き込み時よりも「厳しい」場合に警告します。ソフト 0.95 のベースラインはハードゲートの違反の上位集合なので、ハードの self-scan はそれを黙って読みます。逆にハードティアでベースラインを書くと、ソフトの self-scan-headroom が自分のほうが厳しいゲートだと警告するようになります。ティア/ヘッドルームの来歴を参照してください。
これは重要です。ソフトティアをより厳しくしたい(侵食をより早い段階で捕捉したい)貢献者は、環境変数を 1 つ変えるだけで済み、誰かが両方のファイルの更新を忘れた瞬間にハード設定から乖離していく並行のソフトしきい値ファイルを維持する必要がない、ということです。
2 ティアのしきい値
bca check --tier <hard|soft|soft=RATIO> は、どのティアに対してゲートするかを選択します。hard(デフォルト)は [thresholds] をそのまま比較します。soft は早期警告ティアで、次の順序で解決されます:
[thresholds](マニフェスト。--configとマージ済み)から始めます。[thresholds.soft]テーブルが存在する場合、そのオーバーライドを上に重ねます。ソフトテーブルにないメトリクスはハード制限をそのまま継承します(ソフトバンドなし)。ソフトテーブルがある場合、一括のRATIOは適用されません — 明示的なメトリクスごとの制限がスカラーに勝ちます。- Otherwise tighten every limit by the soft
RATIO(default0.95for a baresoft, so--tier=softis never a silent no-op;soft=1.0disables scaling). - 繰り返し指定された
--threshold name=valueフラグは最後に、絶対値として適用されます。
引数なしの --tier=soft(比率 0.95)が設定なしの入り口です。[thresholds.soft] テーブルは、成熟したプロジェクトが育っていく先の設定面です。メトリクスごとに異なるソフトバンドを表現でき、そのバンドを実行時の乗数に埋もれさせず、ハード制限のすぐ隣に記録できるからです:
[thresholds]
cognitive = 25
cyclomatic = 15
nargs = 7
[thresholds.soft]
cognitive = 22 # 絶対値のソフト制限
cyclomatic = "0.9x" # ハード制限の 90% → 13.5
# nargs は未指定 → ソフト層はハード制限を継承(ソフト帯なし)
整数型のソフト制限は、浮動小数でスケールした値よりも絶対値として書くほうが明快に読めます。スカラーが生む 0.95 × 7 = 6.65 よりも、(ハードの nargs = 7 に対して)nargs = 6 を優先してください。正確な整数のソフト制限を選びにくい大きな値のメトリクス(halstead.*、loc.*)には "<ratio>x" 形式を使います。スケール係数は (0, 1] の範囲でなければなりません — ソフトティアはハードゲートの「前に」発火する早期警告バンドであり、ハードゲートより緩くなることは決してありません。
That "tighter, never looser" rule is what fixes the direction of the scaling, and it is not always a multiplication. A mi.* limit is a floor rather than a ceiling — a value below it is the violation — so RATIO divides instead: "mi.original" = 20 under --tier=soft=0.9 resolves to 22.2223, not 18. The intuition that a soft band is "90% of the limit" is right for every other metric and exactly backwards here.
両ティアは同じ .bca-baseline.toml を通じてラチェットします(別個のソフトベースラインファイルはありません)。bca check --print-effective-config --tier=soft は解決済みの制限を出力します。その [thresholds] 出力を [thresholds.soft] に貼り付ければ、一括比率のバンドから明示的なメトリクスごとの制限へ移行できます。
ゼロコンフィグ: bca.toml マニフェスト
すべてのレシピに --paths、--exclude-from、--jobs、--config、--baseline、--tier=soft=<ratio> を渡して回る代わりに、リポジトリルートに bca.toml を置き、bca check に発見させます:
# bca.toml — 作業ディレクトリ(またはその上位)で自動的に発見されます。
paths = ["."]
exclude_from = ".bcaignore"
jobs = "auto" # または整数(旧 `num_jobs`)
[check]
baseline = ".bca-baseline.toml"
[thresholds]
cognitive = 25
cyclomatic = 15
"halstead.effort" = 50000
nom = 30
nargs = 7
nexits = 5
abc = 50
wmc = 60
headroomキーはソフトティアのスケール比率です。--tier=softの下でのみ効果を持つため、引数なしのbca check(ハードティア)はheadroomキーの有無にかかわらず正確な CI ミラーのままです。メトリクスごとのソフト制限には、スカラーよりも[thresholds.soft]テーブル(後述)を優先してください。選んだバンドを実行時の乗数に委ねるのではなく、ハード制限の隣に記録できます。
このファイルを置けば、4 つのレシピはそれぞれフラグ 1 つに集約されます:
.PHONY: self-scan self-scan-headroom \
self-scan-write-baseline self-scan-write-baseline-headroom
self-scan: # hard tier (CI mirror)
bca check
self-scan-headroom: # soft tier (early warning)
bca check --tier=soft=0.95
self-scan-write-baseline: # absorb hard-tier offenders
bca check --write-baseline
self-scan-write-baseline-headroom: # absorb soft-tier offenders
bca check --tier=soft=0.95 --write-baseline
発見と優先順位
bcaは作業ディレクトリからリポジトリルート(.gitを含むディレクトリ)まで遡ってbca.tomlを探し、最初に見つかったものが使われます。マニフェスト内の相対パスはマニフェスト自身のディレクトリを基準に解決されるため、現在のディレクトリより上にあるbca.tomlでも正しいファイルを指します。- スカラーと肯定的スコープキー: CLI が勝ちます。 明示的な
--baseline、--tier、--jobsなどは対応するマニフェストキーを上書きし、「肯定的スコープ」のリストキー(paths、include)は明示的な CLI 値によって*「置き換え」*られます(マニフェストにpaths = ["src"]があってもbca check one.rsはone.rsだけをチェックします)。--config <file>はマニフェストの[thresholds]テーブルの上に「マージ」され(衝突時は config のキーが勝ちます)、繰り返し指定した--threshold name=valueフラグは絶対制限として最後に適用されます。完全な解決順序 —[thresholds]→--config→ ティア解決([thresholds.soft]またはソフトRATIOスケーリング。--tier=softの下でのみ) →--thresholdオーバーライド — は--config/--tier/ マニフェストのすべてに共通です。 - 否定的フィルタキー: CLI はマニフェストと合併します。 「除外」のリストキー(トップレベルの
exclude、[check] exclude)は置き換えではなく*「マージ」*されます。CLI の--exclude/--check-excludeはマニフェストの拒否セットに「追加」されます。こうすることで、プロジェクト設定が意図的にスキップしたディレクトリ(例:vendor/)を、コマンドラインのフィルタが黙って除外解除することは決してありません。2 つのソースにまたがる重複は畳み込まれ、CLI のパターンが先にソートされます。これは ruff/ESLint のexclude(置換)とextend-exclude(追加)の一般化です。ターゲットは置き換え、フィルタは追加。 従来どおり、--xと--x-fromは常に互いに合併します。マニフェストの除外を完全に消したい場合は--no-configを使ってください。 --no-configは発見を完全にスキップします。リポジトリレベルの設定を拾ってはならない、再現可能で完全に明示的な呼び出しのためのものです。bca initも既存のマニフェストを無視します — 設定を消費するのではなく、スキャフォールドするためです。- トップレベルの
include/excludeキーは、どのファイルを「そもそも解析するか」を決めるグローバルなファイルフィルタのグロブ(--include/--excludeフラグ)です。これらは[check] excludeテーブル(解析され報告されるがゲートされないパス。ファイルカテゴリ全体の除外を参照)とは別物です。 [check]テーブルはゲート専用のオプションを設定します。excludeはグロブのリストで、一致したファイルは解析・報告されますが、しきい値ゲート(および--write-baseline)からは除外されます。exclude_fromは同じグロブを並べた.gitignore形式のファイルを指します(どちらも--check-exclude/--check-exclude-fromフラグに対応します)。exit_codes = "tiered"はより細かい終了コードにオプトインします(--exit-codes=tieredに対応。終了コードを参照)。"default"(暗黙の値)は安定した0/1/2の契約を維持します。ベースラインとヘッドルームのキーもゲート専用なのでここに置かれます:baseline(bca checkが読み、引数なしの--write-baselineが書き込むファイル)、baseline_line_tolerance、baseline_fuzzy_match、そしてheadroom(ソフトティアのスケール比率。--tier=soft=<R>に対応)。いずれのキーも、どちらの方向でも CLI の値がテーブルの値を上書きします。- これら 4 つのキーは以前トップレベルにありました。その綴りは非推奨(#599)で、一度だけ警告が出ます。1 リリースサイクルの間は尊重され、その後の次のメジャーバージョンで削除されます。
baseline、baseline_line_tolerance、baseline_fuzzy_match、headroomを[check]の下へ移してください。キーが両方に設定されている場合は[check]の値が勝ちます。 [vcs]テーブルはbca vcsの変更履歴ランキングのオプションを設定します。そのfile_typesキー(デフォルトの"metrics"/"all"/"rs,py"形式の拡張子リスト)は、どのファイルをランク付けするかを絞り込みます。肯定的スコープキーなので、明示的な--file-typesCLI フラグはそれを*「置き換え」*ます(bca vcsのファイルタイプスコープを参照)。cyclomatic_count_tryとexclude_testsは、--cyclomatic-count-try/--exclude-testsフラグに対応するウォーカー調整用のブール値です。exclude_tests = trueは、メトリクス計算の前に Rust のインラインテストのサブツリー(#[test]、#[cfg(test)]など)を刈り取ります。どちらも Rust 専用で、他の文法では効果がありません。--exclude-testsは存在のみのフラグ(=false形式なし)なので、マニフェストキーは刈り取りをオンにすることしかできません。CLI の--exclude-testsは勝ちますが、CLI が設定していないキーをマニフェストがオフにすることはできません。[thresholds.soft]テーブルは、メトリクスごとのソフトティア制限を設定します(--tier=softによって消費されます。2 ティアのしきい値を参照)。認識されないキーは 1 行の警告とともに無視されるため、古いbcaビルドを壊すことなく、今後のスキーマ追加を先行採用できます。bca check --print-effective-configは、manifestの来歴行を含む解決済みのビューを出力するので、マージが何を生成したかを正確に確認できます。
以下の明示フラグのスケルトンは引き続き完全にサポートされます — マニフェストは同じフラグの糖衣であって、置き換えではありません。リポジトリルートにファイルを置けない場合や、ある CI ジョブがコミット済みマニフェストと異なるレイアウトを必要とする場合(フラグを
--no-configと組み合わせます)に使ってください。
スケルトン: GNU Make(明示フラグ)
以下の 4 つのレシピは、すべてのフラグを明示的に渡す自己完結のドロップインで、上記のマニフェストレシピのロングフォームです。BCA 変数は、チェッカーを提供する呼び出し(ピン留めしたリリースバイナリ、cargo run --release、npm / pip のラッパー)を指すように調整してください。PATHS と EXCLUDE_FROM はあなたのレイアウトに合わせて調整します。
# --- bca ローカルしきい値ゲート ------------------------------------------
# ハード(HARD)ティアは CI を正確にミラーします。両ティアは同じ
# thresholds.toml + .bca-baseline.toml を消費し、ソフトティアはすべての
# しきい値を $(BCA_HEADROOM)(デフォルト 0.95)でスケールします。
#
# Knobs are namespaced with `BCA_` so they don't collide with anything
# else in your environment. The big-code-analysis repo itself uses the
# manifest form above (a single `bca.toml`) rather than these explicit
# flags; reach for this skeleton when you can't drop a manifest at the
# repo root and must point `--config` at a standalone threshold file.
BCA := bca
BCA_PATHS := .
BCA_EXCLUDE_FROM := .bcaignore
BCA_THRESHOLDS := thresholds.toml
BCA_BASELINE := .bca-baseline.toml
BCA_HEADROOM ?= 0.95
# 共通引数。4 つのレシピが揃った状態を保てるよう括り出しています。
# `--jobs` のデフォルトは OS が報告する実効 CPU 数
# (Linux では cgroup / cpuset を考慮)なので、`$(nproc)` の引き回しは
# needed. Override with `--jobs N` (or `--jobs 1` to force
# serial mode for debugging).
BCA_BASE_ARGS := --paths $(BCA_PATHS) --exclude-from $(BCA_EXCLUDE_FROM)
.PHONY: self-scan self-scan-headroom \
self-scan-write-baseline self-scan-write-baseline-headroom
self-scan:
@echo "bca self-scan (hard gate)..."
@$(BCA) check $(BCA_BASE_ARGS) \
--config $(BCA_THRESHOLDS) \
--baseline $(BCA_BASELINE)
# `self-scan-headroom: self-scan` is intentional: under `make -j` Make
# would otherwise run both gates in parallel and the soft tier's scaled
# error message could land before the true regression on the hard tier.
# `--tier=soft=$(BCA_HEADROOM)` scales every config limit before the
# offender comparison — no helper script, no second TOML file.
self-scan-headroom: self-scan
@echo "bca self-scan (soft gate, BCA_HEADROOM=$(BCA_HEADROOM))..."
@$(BCA) check $(BCA_BASE_ARGS) \
--config $(BCA_THRESHOLDS) \
--tier=soft=$(BCA_HEADROOM) \
--baseline $(BCA_BASELINE)
self-scan-write-baseline:
@echo "Refreshing $(BCA_BASELINE) at hard thresholds..."
@$(BCA) check $(BCA_BASE_ARGS) \
--config $(BCA_THRESHOLDS) \
--write-baseline $(BCA_BASELINE)
# Soft-tier baseline write. NOTE: this and `self-scan-write-baseline`
# どちらも `$(BCA_BASELINE)` に書き込みます。これらを並列に
# prerequisites of one umbrella target or invoke them with `make -j2`,
# or the two `bca` processes will race on the same file and the
# losing tier's offenders will silently vanish from the baseline.
# Run them sequentially (hard first, then soft) and commit the diff.
self-scan-write-baseline-headroom:
@echo "Refreshing $(BCA_BASELINE) at soft thresholds (BCA_HEADROOM=$(BCA_HEADROOM))..."
@$(BCA) check $(BCA_BASE_ARGS) \
--config $(BCA_THRESHOLDS) \
--tier=soft=$(BCA_HEADROOM) \
--write-baseline $(BCA_BASELINE)
bca check --tier=soft=<ratio> は、違反判定の前に --config のすべての限度値を指定した比率(引数なしの --tier=soft ではデフォルトの 0.95)でスケーリングし、その後、ハードティアが書き込むものと同じ .bca-baseline.toml に対してフィルタリングします。明示的な --threshold name=value による上書きは絶対値で、再スケーリングされません。維持が必要な補助スクリプトや 2 つ目の TOML ファイルはありません — ソフトティアは、ハードティアの呼び出しにフラグを 1 つ追加しただけのものです。
終了コード
ゲートの終了コードは bca check からそのまま伝播します。0 はクリーン、2 はしきい値違反(ハード・ソフトを問わず)、1 はツールエラーです。ソフトティアは本物のゲートです — 助言的なものだと考えて make self-scan-headroom を || true でラップしてはいけません。非ゼロの終了コードこそが、この接近警告バンドの眼目です。
Pass --exit-codes=tiered (or set [check] exit_codes = "tiered") to split the single violation code 2 by severity: 2 new offenders only, 3 regressions only, 4 both, 5 a --tier=soft violation that also breaches the hard limit. The tiered codes are opt-in; the default stays 0/1/2, and every fail-state remains non-zero. Use them when CI needs to route "a new offender appeared" differently from "a baselined offender got worse" without parsing the [new] / [regr +N%] row tags.
pre-commit と CI への組み込み
開発者がプッシュ前にすでに実行している統括ターゲットに、ソフトゲートを追加してください。ハードゲートはその前提条件として実行される(上記の self-scan-headroom: self-scan のエッジを参照)ため、ソフトターゲットだけを列挙すれば十分です — そして重要なことに、これは make -j にも耐えます。前提条件のエッジがなければ、両方のリーフが並列にスケジュールされ、出力が交互に混ざってしまうところです:
.PHONY: pre-commit
pre-commit: fmt-check clippy test self-scan-headroom
順序が重要です。ハードティアは、スケーリング後ではなく 100% の限度に対する真のリグレッションを指摘します。前提条件のエッジは、並列 Make の下でもその順序を強制します。
CI では、ハードティアのみを実行します:
- name: Threshold gate
run: make self-scan
ソフトティアは開発者向けのフィードバック用ノブであり、リリースゲートではありません。CI で実行すると、(何も接近していなければ)ハードティアと重複するか、100% を超えないままじわじわ悪化しているベースライン吸収済みの違反に対して騒がしく発火するかのどちらかで、いずれも CI がすでにカバーしている以上のものは得られません。
ヘッドルームノブ
BCA_HEADROOM は (0, 1] の範囲の単一のスカラー値です。意味のあるバンドは狭い範囲に限られます:
BCA_HEADROOM | 関数が次に達すると発火… | ユースケース |
|---|---|---|
0.99 | いずれかの限度の 99% | 可能な限り厳しい警告。ハードゲートが発火する直前の最後のコミットで発火します。 |
0.95 | いずれかの限度の 95%(デフォルト) | 1〜2 コミット分のリードタイム。良いデフォルトです。 |
0.90 | いずれかの限度の 90% | より広いバンド。限度を引き上げた直後、新しい上限が落ち着くまでの間に有用です。 |
1.00 | 100%(ハードゲートと同等) | 2 つのティアが一致していることを確認するサニティチェックです。 |
およそ 0.80 を下回る値では、ソフトティアは恣意的な数値による第 2 のハードティアと化し、有用でなくなります。現実のコードベースでは、どのしきい値にもその 80% 付近にある関数が 何かしら 存在するため、ソフトティアは早期警告シグナルではなく、恒常的なベースライン管理の雑務になってしまいます。
ソフトティアが発火したとき
ソフトゲートの失敗はバグ報告ではなく、判断のタイミングです。正当な解決策は、ちょうど次の 3 つです:
- リファクタリングする。 他の複雑度リグレッションと同じワークフローです — ヘルパーを抽出する、ディスパッチアームを畳み込む、関数を分割する。これが最も一般的なケースであり、ソフトティアは同じブランチ上でそれを行う時間を確保するために存在します。
- 限度を引き上げる。
[thresholds]テーブル(このリポジトリではbca.toml、それ以外では各自のしきい値ファイル)を編集し、何が変わったのか(新しい言語モジュール、真にアルゴリズム上の下限、再分類されたマクロなど)を説明する why コメントを残してください。make self-scan-headroomを再実行し、新しい値が余裕をもって違反箇所をカバーしていることを確認します。 - ベースラインに吸収する。 その値が今後もずっと正当である場合 — カバーする文法に見合った幅を持つパーサーのディスパッチアーム、安定した状態機械、生成コードなど — は、
make self-scan-write-baseline(ハードティア)またはmake self-scan-write-baseline-headroom(ソフトティア)を実行してください。.bca-baseline.tomlの差分は、それを生んだコードと同じプルリクエストでコミットします。
ゲートを黙らせるためだけに「限度を引き上げる」を無言で選んではいけません。コミットされた why コメントは、次の読者にとって唯一の監査証跡です。それがなければ、引き上げられた限度は怠慢と見分けがつきません。
スケルトン:justfile
just を好むプロジェクトの場合:
# bca のローカルしきい値ゲート。ハードティアは CI を反映します。ソフト
# ティア(ヘッドルーム)はローカル限定の早期警告です。
bca := "bca"
paths := "."
exclude := ".bcaignore"
thresholds := "thresholds.toml"
baseline := ".bca-baseline.toml"
headroom := env_var_or_default("BCA_HEADROOM", "0.95")
# `--jobs` のデフォルトは実効 CPU 数なので、このスケルトンでは
# `$(nproc)` を `just` に通しません。必要ならインラインで上書き
# します: `just self-scan --jobs 1`。
base_args := "--paths " + paths + " --exclude-from " + exclude
self-scan:
{{bca}} check {{base_args}} \
--config {{thresholds}} --baseline {{baseline}}
self-scan-headroom: self-scan
{{bca}} check {{base_args}} \
--config {{thresholds}} --tier=soft={{headroom}} --baseline {{baseline}}
self-scan-write-baseline:
{{bca}} check {{base_args}} \
--config {{thresholds}} --write-baseline {{baseline}}
# Make のスケルトンと同様、これを `self-scan-write-baseline` と並列に
# 組み合わせないでください — 同じ {{baseline}} ファイルを奪い合います。
self-scan-write-baseline-headroom:
{{bca}} check {{base_args}} \
--config {{thresholds}} --tier=soft={{headroom}} --write-baseline {{baseline}}
スケルトン:package.json スクリプト
npx またはピン留めしたバイナリで bca を取り込む JavaScript プロジェクト向けです。--jobs のデフォルトは実効 CPU 数(Linux では cgroup / cpuset を考慮)なので、npm 側でも Make / just とバイト単位で同一の bca check 呼び出しを生成するために BCA_NUM_JOBS 環境変数はもう必要ありません。--jobs 1 を明示的に渡すのはデバッグ時だけにしてください:
{
"scripts": {
"self-scan": "bca check --paths . --exclude-from .bcaignore --config thresholds.toml --baseline .bca-baseline.toml",
"self-scan-headroom": "bca check --paths . --exclude-from .bcaignore --config thresholds.toml --tier=soft=0.95 --baseline .bca-baseline.toml",
"self-scan-write-baseline": "bca check --paths . --exclude-from .bcaignore --config thresholds.toml --write-baseline .bca-baseline.toml",
"self-scan-write-baseline-headroom": "bca check --paths . --exclude-from .bcaignore --config thresholds.toml --tier=soft=0.95 --write-baseline .bca-baseline.toml"
}
}
ソフトティアはいまや素の bca check 呼び出しなので、npm スクリプトはどのシェルでもバイト単位で同一です — 補助スクリプトも、取り繕うべき python3-vs-py エイリアスも、環境変数とシェル展開の移植性の罠もありません。バンドを広げるには、スクリプト内のリテラル 0.95 を編集する(またはお好みのタスクランナー経由で配線する)だけです。このフラグはどのプラットフォームでも同じようにパースされます。
husky や pre-commit と組み合わせて、同じスクリプトが git commit 時に実行されるようにしてください。
スケルトン:pre-commit フック
pre-commit フレームワーク(バージョン 3.2.0 以降 — 下記のバージョン注記を参照)を使っている場合、両ティアとも make を呼び出すローカルフックになります:
- repo: local
hooks:
- id: bca-self-scan
name: bca self-scan (hard gate)
entry: make self-scan
language: system
pass_filenames: false
stages: [pre-commit]
- id: bca-self-scan-headroom
name: bca self-scan-headroom (soft gate)
entry: make self-scan-headroom
language: system
pass_filenames: false
stages: [pre-commit]
pass_filenames: false は意図的です — bca は --paths とベースラインから自分で入力を発見します。pre-commit に変更ファイルを渡させると、スキャンがそれらのファイルだけに縮小され、ベースライン更新のファイル横断的な影響を見逃してしまいます。
pre-commitの最低バージョンは 3.2.0 です。stages:の語彙は pre-commit 3.2.0(2024 年 3 月)で改名されました —commit→pre-commit、push→pre-pushなど。古いインストール(特に RHEL 8 EPEL、Ubuntu 20.04 のデフォルトパッケージ、レガシー語彙にピン留めされた.pre-commit-config.yaml)はstages: [pre-commit]を未知のステージ名として拒否し、フックが登録されません。古いインストールをサポートする必要がある場合はstages: [commit]に置き換えてください。混在環境では、この矛盾が黙って表面化しないよう、開発ツールのドキュメントでフレームワークをpre-commit --version≥ 3.2.0 にピン留めしてください。
より広いベースラインワークフローとの組み合わせ
上記の 4 つの self-scan* ターゲットは、ドキュメント化されたベースラインのレシピの代替ではありません — それらはそのレシピ 「そのもの」 を、開発者マシンのコマンドとして機械化したものです。同じ順序がそのまま適用されます:
- 最初に一度ブートストラップする。 初期のしきい値を書き、初期のベースラインを書き、両方をコミットします。
- コミットごとにゲートする。 ハードティアはリグレッションで失敗し、ソフトティアは限度への接近で失敗します。
- 集中的なリファクタリング中に更新する。 関数の値が正当に動いた(誰かが 実際に 負債を返済した)場合は、ベースラインを再生成して差分をレビューします。
- Retire when empty. When
.bca-baseline.tomlshrinks to justversion = 6(the bare schema stamp with no offender entries), drop the--baselineflag and delete the file. The thresholds now stand on their own.
ローカルのティアは、ステップ 2 と 3 のフィードバックループを「プルリクエストで CI が赤くなる」から「git commit が返る前に Make レシピが赤くなる」へと短縮します。それがこの仕組みの売りのすべてです。
関連する業界パターン
ハード / ソフトのティア分割は、より広いパターンの一例です。以下のいずれかを使ったことがあれば、そのメンタルモデルがそのまま通用します:
- Sonar の新規コードに焦点を当てた品質ゲート。 既存コードは現状のまま維持され、変更 が事態を悪化させてはならない、という考え方です。ベースラインファイルは、「新規コード」/「リーク期間」という発想の
bcaネイティブな形です。 - clippy の
warn-vs-denyリントレベル。warnリントはローカルビルドで表面化し、同じリントを-D warningsで deny すると CI が失敗します。2 段構えの設定は、実験的により厳しいルールを導入する場所を与えてくれます。 - 一般的な移行ツーリングにおける ラチェットパターン。今日のカウントを記録し、増加で失敗させ、カウントが減るにつれ上限を下げていきます。
bca checkはパターン単位ではなく関数単位でラチェットしますが、単調性の保証は同じです。 - C/C++ における
-Wall+-Werror。 まず-Wallでノイズを洗い出し、ベースラインがゼロに達してから-Werrorに昇格させるのは、空になった.bca-baseline.tomlを削除するのと同じ退役ステップです。