Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

チェック

bca check compares metrics against thresholds at each metric’s own scope (function, file, or container) and exits non-zero when any value exceeds its limit. It is the CI integration point: wire it into a build step and a regression in code complexity fails the pipeline before the change lands.

This page documents the command. The first half covers what most runs need: declaring thresholds, exit codes, offender output, baselines, and CI wiring. The second half covers the exemption mechanisms, per-language limits, the soft tier, and the edge cases in how they combine. For runnable pipeline examples see the CI integration recipe, which covers the --report-format matrix, GitHub Actions and .gitlab-ci.yml examples, the baseline ratchet, and the GitLab Code Quality path.

しきい値の宣言

Pass --threshold <metric>=<limit> once per metric (repeatable). Metric names match bca list-metrics; sub-metrics use a dotted form. A value violates its limit only when it exceeds it, so 0 is a valid limit that permits exactly zero.

bca check --paths src/ \
    --threshold cyclomatic=15 \
    --threshold cognitive=20 \
    --threshold loc.lloc=200

Or keep thresholds in the bca.toml manifest, so CI thresholds are versioned alongside the code. A manifest at the repo root is discovered automatically: a bare bca check reads it with no --config flag.

# bca.toml
paths = ["src"]

[thresholds]
cyclomatic = 15
cognitive = 20
"loc.lloc" = 200
"halstead.volume" = 1000
bca check

1 回の実行でマニフェストの上に別のしきい値ファイルをマージするには、--config で明示的に渡します。CLI フラグと --config の値は、同じメトリクス名についてマニフェストを上書きするため、プロジェクト全体のデフォルトを保ちながら、特定の実行でひとつのメトリクスだけを厳しくできます。

bca check --paths src/ --config bca.toml

bca init は出発点となるテーブルをスキャフォールドします。それらの数値の出どころ、ゲート対象の言語に合わせて上書きすべき項目、エージェントフィードバック・レガシートリアージ・セーフティクリティカルな作業向けに別のセットを選ぶ方法については、しきい値の選択を参照してください。

使用できるメトリクス名

トップレベルのスカラーメトリクスは list-metrics の名前をそのまま使います:cognitive、cyclomatic、nargs、nexits、nom、tokens、abc、wmc、npm、npa。複数のサブフィールドを持つメトリクススイートはドット区切り形式を使います。

メトリクス使用できるしきい値名
循環的複雑度cyclomatic、cyclomatic.modified
Halsteadhalstead.volume、halstead.difficulty、halstead.effort、halstead.time、halstead.bugs
コード行数loc.sloc, loc.ploc, loc.lloc, loc.cloc, loc.blank, loc.dloc
保守容易性指数mi.original、mi.sei、mi.visual_studio

不明なしきい値名はツールエラー(終了コード 1)となり、黙って無視されることはありません。

しきい値のスコープ

A threshold is checked only against the space kind its metric measures, so a metric’s whole-file or whole-impl aggregate is never mistaken for a per-function limit. Each metric has a fixed scope; there is nothing to configure.

スコープゲート対象のスペースメトリクス
ファイルファイル全体のルートのみloc.sloc, loc.ploc, loc.lloc, loc.cloc, loc.blank, loc.dloc
関数個々の関数、メソッド、クロージャcognitive、cyclomatic、cyclomatic.modified、halstead.*、mi.*、abc、nargs、nexits、tokens
コンテナクラス、構造体、トレイト、impl、名前空間、インターフェイスnom、wmc、npm、npa

The Function-scoped metrics include the subtree sums (nargs, nexits, tokens, halstead.*). These still roll a function’s own nested closures into its figure, but they are not summed across an entire file or impl. The Container-scoped metrics describe a type’s method set (methods per class, weighted methods, public members), so they gate the container rather than every leaf function. A file whose functions are individually fine therefore never trips an additive limit on the file-wide total, which is the false positive bca: suppress-file markers used to mask.

The bare bca diff --metric spelling of a loc sub-metric is accepted as an alias for its dotted form (sloc for loc.sloc, and so on for ploc/lloc/cloc/blank/dloc), so a name copied from a diff run gates correctly. A bare family head with no single threshold scalar (halstead, mi) is ambiguous and rejected with a “did you mean” hint listing the concrete sub-metrics; pick one, such as halstead.volume.

The two spellings name one metric, so they override each other wherever limits merge: a per-language [thresholds.lang.c] ploc = 100 replaces the global "loc.ploc", and --threshold ploc=100 replaces either. Within a single table, writing both is an error rather than a silent winner. Set the metric once, under whichever spelling you prefer.

終了コード

コード意味
0すべての関数がしきい値内(または --no-fail 指定時)。
2少なくとも 1 つのしきい値を超過。
1ツールエラー(不正な引数、読み取れない設定または入力、未知のメトリクス)。

1 は予約されており、CI がリグレッション(2)とツールの設定ミス(1)を区別できるようになっています。

A gate that could not read all of its input has no verdict to report, so three input problems exit 1 rather than 0: nothing matched --paths / --include / --exclude, any input file that failed to read, and any directory the walk could not list. The last two are the workspace-wide unreadable-input rule. check is not special here; it is where the rule matters most, because a gate reporting clean on a tree it could not read looks the same as a gate that passed. All three checks run before the gate is evaluated. --no-fail does not suppress them, since it suppresses threshold failures rather than broken input, so none of them lets --write-baseline record a partial run.

段階的終了コード(--exit-codes=tiered)

--exit-codes=tiered (or [check] exit_codes = "tiered" in bca.toml) splits the single violation code 2 by severity so CI can branch on it without parsing the [new] / [regr …] row tags a baseline run prints:

コード意味(tiered モード)
0すべての関数がしきい値内(または --no-fail 指定時)。
1ツールエラー。
2新規違反のみ(一致する --baseline エントリなし)。
3ベースライン退行のみ(ベースライン登録済みの違反が悪化)。
4新規違反と退行の両方。
5ハード制限も超えている --tier=soft 違反。

The tiered codes are opt-in; the default contract above stays 0/1/2. Every fail-state remains non-zero, so exit != 0 → fail wrappers keep working. Only tooling that tests $? -eq 2 explicitly needs to widen to 2-5. --no-fail still forces exit 0. Code 5 is emitted only at the soft tier; at the hard tier every violation is a hard breach by definition, so the 2/3/4 split applies instead.

--exit-codes <default|tiered> takes a value, and the CLI value overrides the [check] exit_codes manifest key in either direction. An invalid exit_codes value is a tool error (1). --print-effective-config reports the resolved exit_codes style. The deprecated --strict-exit-codes flag is a one-cycle alias for --exit-codes tiered (warns; removed at the next major).

違反の出力

違反した (function, metric) の各ペアは、次の安定したフォーマットで 1 行ずつ stdout に出力されます。

<path>:<start_line>-<end_line>: <function_name>: <metric> = <value> (limit <limit>)

例:

src/parser.rs:42-117: parse_expression: cyclomatic = 22 (limit 15)
src/parser.rs:42-117: parse_expression: cognitive = 31 (limit 20)

行はパス、開始行、メトリクス名の順でソートされるため、同じツリーに対する実行間で出力は決定的です。

どちらのストリームか

違反行はこのコマンドの成果物なので stdout に出力されます。bca check | wc -l、| head、| rg -c、2>/dev/null のいずれでも違反行に届きます。

実行が「自分自身について」語る内容はすべて stderr に出力されます:

  • ファイルごとの --- summary --- フッター、
  • --- next steps --- の修復ブロック、
  • GitHub Actions の ::error アノテーション、
  • bca: skipped N violations via [check.exclude] と bca: filtered N violations via baseline の件数表示、
  • すべての warning: および error: 診断メッセージ。

One combination inverts this. --report-format <dialect> without --output puts the aggregated SARIF / Checkstyle / Code Climate document on stdout, so the human rows move to stderr rather than corrupting it (the first example counts results with jq):

bca check --report-format sarif | jq '.runs[0].results | length'   # ドキュメントは stdout に
bca check --report-format sarif --output report.sarif | wc -l      # 違反行は stdout に戻る

--summary-file のダイジェストはストリームではなくファイルであり、どちらのストリームにも現れません。

Earlier releases sent the rows to stderr along with everything else, which made | wc -l and 2>/dev/null report an empty offender list, indistinguishable from a clean tree. A pipeline that reads the rows through 2>&1 needs no change; one that captured them with 2>file should now use >file.

ベースライン

Adopting thresholds on an existing codebase usually presents a choice between “raise the limit until nothing fires” and “fix every offender before turning the gate on”. A baseline file is the ratchet-down alternative: record today’s offenders, fail only on regressions and new offenders, and shrink the file over time as the team pays down debt.

Baselines complement the suppression markers; they are not a substitute. A suppression says “this function is intentionally exempt forever” and lives in source; a baseline says “this is tech debt we are paying down” and lives in a committed TOML file. bca check applies in-source suppressions first, then [check.exclude] globs, then the baseline filter.

ベースラインの書き出し

bca check --paths src/ \
    --write-baseline .bca-baseline.toml

This walks the tree, captures every threshold violation that would otherwise fail the check, and writes them to the file as sorted TOML. The run exits 0 regardless of offender count, since the point is to capture them.

# bca ベースラインファイル。`bca check --write-baseline` により生成。
# 記載された違反はしきい値チェックから除外されます。記録値より
# 悪化した関数は引き続き失敗します。エントリが古くなったら
# `--write-baseline` で更新してください。
version = 6

[provenance]
tier = "hard"

[[entry]]
path = "src/parser.rs"
qualified = "Parser::parse_expression"
metric = "cyclomatic"
value = 22.0

The qualified field is the function’s qualified symbol: the ::-joined chain of enclosing named containers plus the function name. An entry carries a start_line only when another entry shares its (path, qualified, metric) identity, the one case where matching consults a line number. Recording it elsewhere would rewrite the file every time an edit above the function shifted it. With --baseline-fuzzy-match, each entry also carries a body_hash for rename-tolerant matching.

Functions already covered by an in-source suppression marker are excluded. Pass --no-suppress together with --write-baseline to include violations hidden by source markers (the CI-auditor flow); [check.exclude] exemptions still apply.

--write-baseline cannot be combined with --baseline, --report-format, --output, --since, or --changed-only, because the baseline file is the output.

ベースラインの読み込み

bca check --paths src/ \
    --baseline .bca-baseline.toml

違反は、次の両方の条件が成り立つときに抑制されます。

  • An entry matches by (path, qualified_symbol, metric), independent of line number, or, failing that and with --baseline-fuzzy-match, by body hash. See the Baselines recipe for the full resolution order.
  • 現在の value が記録された値以下である。

A function that gets worse than its baseline value still fails. New offenders not listed in the baseline still fail. Improvements pass silently; the entry keeps its older, higher value until the next --write-baseline refresh.

存在しない、空である、version が欠落もしくは未対応である、またはパースに失敗するベースラインファイルはツールエラー(終了コード 1)であり、黙ってゼロマッチ扱いにはなりません。

Path keys are canonicalised relative to the baseline file’s own directory (the anchor), so --paths ., --paths src/, and --paths "$PWD" produce byte-identical baselines, and a --baseline run matches whichever --paths form generated the file. Switch between them freely without re-running --write-baseline.

制限事項

  • Ambiguous symbols and anonymous functions. Entries key on the qualified symbol, so inserting code above a named function does not re-key it. Two exceptions re-key as “new” on movement: functions sharing a qualified symbol that drift more than --baseline-line-tolerance apart, and anonymous closures or lambdas, whose synthetic symbol embeds the line. Refresh with --write-baseline.
  • OS 間の可搬性。 パスはスラッシュ区切りで保存されるため、ある OS で書かれたベースラインは別の OS 上の同じツリーにマッチします。有効な UTF-8 でないパスは損失のある表示形式(U+FFFD 置換)にフォールバックし、正確にラウンドトリップしない場合があります。

エンドツーエンドの導入フローと CI 統合パターンについては、ベースラインレシピを参照してください。

失敗させずに報告する

--no-fail reports offenders as usual but exits 0. Use it while adopting baselines without turning CI red. Other CI tools call this behavior --report-only or --soft-fail; here the flag is spelled --no-fail.

bca check --paths src/ --no-fail

CI の例(GitHub Actions)

- name: Check code complexity thresholds
  run: |
    bca check
  # Thresholds and paths come from the auto-discovered `bca.toml`
  # manifest at the repo root. The default non-zero exit fails the
  # step, which is the behaviour wanted here. No extra wiring.

To keep the job green and surface offenders as a build annotation while you reduce the count, swap in --no-fail:

- name: Surface complexity hot spots (non-blocking)
  run: |
    bca check --paths src/ --no-fail

対処しやすい失敗出力

When bca check fails, five flags shape the failure stream so a developer skimming a CI log can see what tripped, where in their PR it tripped, and what to do next. Each flag is independent, and all of them auto-detect from GitHub Actions environment variables when present, so the common CI case needs no explicit configuration.

フラグ効果自動検出する環境変数
--since <ref>ファイル別フッターを “Files in this range” と “Other offenders” に分割BCA_DIFF_BASE、GITHUB_BASE_REF、GITHUB_EVENT_BEFORE
--changed-onlydiff スコープ外の違反を完全に除外解決可能なベースが必要(--since または上記のいずれか)
--github-annotations <auto|always|never>インラインのファイル注釈のために ::error file=…::msg ワークフローコマンドを出力(フラグ単独指定 = always)auto は GITHUB_ACTIONS == "true" を検出
--summary-file <path|auto|never>Markdown ダイジェスト(ファイル別集計 + 内訳 + 違反トップ 10)を追記。never で抑止auto は GITHUB_STEP_SUMMARY を検出
--no-remediation末尾の --- next steps --- ブロックを抑止このフラグを渡さない限り、失敗時にブロックを出力

The per-violation rows and the per-file rollup footer keep the same content when none of the above are active, so CI tooling that grep-anchors on the legacy text keeps working. See Which stream for where each now lands.

See the CI integration recipe for worked examples, including a GitHub Actions snippet that composes all five flags into one step, and the Baselines recipe for the --write-baseline refresh flow the remediation block links to.

diff ベース自動検出の優先順位

When --since is omitted, bca consults environment variables in this order:

  1. BCA_DIFF_BASE: an explicit override for local shells or CI runners other than GitHub Actions.
  2. GITHUB_BASE_REF: set by GitHub Actions on pull_request events. Expanded to origin/<value>; the runner is responsible for the corresponding git fetch (fetch-depth: 0 on actions/checkout).
  3. GITHUB_EVENT_BEFORE: set by GitHub Actions on push events to the SHA at HEAD before the push. The all-zeroes sentinel (force push, brand-new branch) is treated as no signal.

Failing to resolve a base is non-fatal unless --changed-only is passed, in which case the gate dies. Silently suppressing every violation under a misconfigured base is the failure mode this feature exists to prevent. --write-baseline also conflicts with --since / --changed-only: a partial baseline would silently mask every offender outside the diff scope on the next full-tree run.

違反レコードのエクスポート

bca check also emits a single CI/IDE document covering every offender in the walk. Pass --report-format <fmt> to pick the shape and --output <file> to write it to disk (stdout if omitted). The --format, -O, and --output-format spellings are accepted as deprecated aliases and will be removed in a future release. These flags do not change the exit-code contract: 0 clean, 2 on any violation (unless --no-fail), 1 on tool error.

When --output is given without --report-format, the format is inferred from the output extension: .sarif selects sarif and .xml selects checkstyle. An extension with no unique format (notably .json, which both sarif and code-climate produce) or no extension at all is a usage error (exit 1) naming --report-format, so an explicit --output is never silently ignored. An explicit --report-format always wins over the extension.

フォーマット対象
checkstyleJenkins、SonarQube、GitLab、“warnings plugin” 系 CI
sarifGitHub Code Scanning、モダンな IDE / セキュリティツール
code-climateGitLab MR の Code Quality ウィジェット
clang-warningエディタの quickfix パーサー、GitHub Actions の problem matcher
msvc-warningVisual Studio、VS Code、Windows CI ランナー

When no offenders exist the writer emits a well-formed but empty document, so CI consumers can ingest clean runs unchanged: an empty runs[].results array for SARIF, an empty JSON array ([]) for Code Climate, no <file> children under the <checkstyle> root for Checkstyle, and zero bytes for the two warning-line formats.

Checkstyle(CI 統合)

bca check --paths src/ \
    --threshold cyclomatic=15 \
    --report-format checkstyle \
    --output report.checkstyle.xml

Checkstyle ライターは、ソースパスごとに 1 つの <file> 要素を含む単一の <checkstyle version="4.3"> ドキュメントを出力し、各 <file> はメトリクスしきい値違反ごとに 1 つの <error> を保持します。スキーマは Checkstyle 4.3 の XSD で、Jenkins および SonarQube の「Warnings Next Generation」/「Generic Issue」インポーターが直接取り込めます。

SARIF(GitHub Code Scanning)

bca check --paths src/ \
    --threshold cyclomatic=15 \
    --report-format sarif \
    --output report.sarif.json

SARIF ライターは、1 つの runs[] 要素を持つ単一の SARIF 2.1.0 JSON ドキュメントを出力します。各メトリクスしきい値違反は runs[0].results[] 配下の result になり、実行結果に現れるメトリクス名は重複排除されて、短い説明とともに runs[0].tool.driver.rules[] に格納されます。

ワークフローから GitHub Code Scanning に SARIF ファイルをアップロードするには:

name: bca-sarif
on: [push, pull_request]
jobs:
  scan:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
    steps:
      - uses: actions/checkout@v4
      - name: Run big-code-analysis
        run: |
          bca check --paths . \
              --report-format sarif \
              --output report.sarif.json \
              --no-fail
      - name: Upload SARIF
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: report.sarif.json

--no-fail はジョブを成功(グリーン)のまま保つため、違反が存在しても SARIF アップロードステップは実行されます。メトリクスの悪化でワークフローを失敗させたくなったら外してください。

GitLab Code Quality(Code Climate JSON)

bca check --paths src/ \
    --threshold cyclomatic=15 \
    --report-format code-climate \
    --output gl-code-quality-report.json

The Code Climate writer emits a single JSON array of issue objects matching GitLab’s strict subset of the upstream Code Climate engine spec: one entry per metric-threshold violation, no byte-order-mark, one trailing newline (empty input renders as []\n).

Each issue carries three fields of note. The check_name is namespaced as big-code-analysis/<metric>. The fingerprint is a stable SHA-256 over path \0 function \0 metric; it ignores line and value, so cosmetic edits still dedup in the MR widget. The severity maps GitLab’s five-level enum from the same severity key bca check ranks its own offenders by: ≤ 1.5 → minor, ≤ 2 → major, ≤ 4 → critical, > 4 → blocker.

For a count the key is value / limit, and a breach of a zero limit is always blocker. For the mi.* family, where lower is worse and the index has no true zero, it is 1 + (limit − value) / unit: one step per maintainability band (17.1 points of mi.original / mi.sei, 10 of mi.visual_studio) below the floor. An mi.original of 0 against a floor of 20 is critical; one of −40 is blocker. The full enum is info/minor/major/critical/blocker; bca never emits info, because a threshold violation always lands at minor or higher.

アーティファクトを GitLab の MR Code Quality ウィジェットに接続するには:

code_quality:
  stage: quality
  script:
    - bca check --paths "$CI_PROJECT_DIR"
          --report-format code-climate
          --output gl-code-quality-report.json
          --no-fail
  artifacts:
    when: always
    reports:
      codequality: gl-code-quality-report.json
    paths:
      - gl-code-quality-report.json

パイプライン全体(Code Climate + Checkstyle + Markdown レポートの組み合わせ)とローカルでの jq によるスモークチェックについては、GitLab Code Quality ウィジェットのレシピを参照してください。

--no-fail はジョブを成功(グリーン)のまま保つため、違反が存在しても Code Quality レポートはアップロードされます。メトリクスの悪化でパイプラインを失敗させたくなったら外してください。

Clang/GCC 警告行(エディタの quickfix と CI アノテーター)

bca check --paths src/ \
    --threshold cyclomatic=15 \
    --report-format clang-warning \
    --output report.txt

Clang フォーマットは、慣例的なコンパイラ警告の形式で違反を 1 行に 1 件ずつ出力します:

path/to/file.rs:42:5: warning: cyclomatic 17 exceeds limit 15 [big-code-analysis-cyclomatic]

This is the format clang -fdiagnostics-format= produces and the shape editor quickfix parsers (VS Code, IntelliJ, Vim) and most CI annotators understand without configuration.

GitHub Actions surfaces the lines as inline annotations on the PR diff through the built-in GCC problem matcher (or any community compiler-problem-matchers action):

name: bca-clang-warnings
on: [push, pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Enable GCC problem matcher
        run: echo "::add-matcher::$RUNNER_TOOL_CACHE/problem-matchers/gcc.json"
      - name: Run big-code-analysis
        run: |
          bca check --paths . \
              --report-format clang-warning \
              --no-fail

ランナーに GCC matcher が同梱されていない場合は、これらの行をストリーム処理して ::warning file=...,line=...:: ワークフローコマンドとして再出力する方法にフォールバックしてください。

MSVC 警告行(Visual Studio と Windows CI)

bca check --paths src/ \
    --threshold cyclomatic=15 \
    --report-format msvc-warning \
    --output report.txt

MSVC フォーマットは、Visual Studio の cl.exe 診断形式で違反を 1 行に 1 件ずつ出力します:

path\to\file.rs(42,5): warning : cyclomatic 17 exceeds limit 15

The space before the colon after warning/error is the MSVC convention. On Windows the path is normalized to \ separators, matching cl.exe output; on other platforms the path is emitted as-is. Visual Studio, VS Code with the C/C++ extension, and Windows CI runners (Azure Pipelines, GitHub Actions on windows-latest) parse these inline without extra configuration.

抑制マーカーによる違反の抑制

ソース内コメントによって、問題のコードを編集したりウォークから除外したりせずに、個々の関数またはファイル全体のしきい値違反を抑制できます。ネイティブの記法は bca: suppress / bca: suppress-file で、Lizard の #lizard forgives は互換シムとして認識されます。完全なリファレンスと --no-suppress CI 監査フラグについては抑制マーカーを参照してください。

ファイルカテゴリ全体の除外([check.exclude])

Some files should be analysed and reported but never gated: test fixtures that intentionally trip cognitive or cyclomatic, generated bindings, macro-dispatch modules whose complexity is structural and will never be “fixed”. Putting these in .bcaignore is too blunt: it removes them from the walk entirely, so bca report loses them too. Baselining them is also wrong, because they are not debt being paid down, and they churn the baseline diff forever.

[check.exclude] is the glob-level middle ground. Matching files are walked, parsed, measured, and shown by bca report, but bca check drops their violations before emitting offenders and before --write-baseline records anything, so the structural exemptions stay out of .bca-baseline.toml.

bca.toml の場合:

[check]
exclude = [
    "tests/**",
    "big-code-analysis-ast/src/languages/language_*.rs",
    "xtask/**",
]

またはコマンドラインで指定します(--check-exclude は繰り返し指定可能で、--check-exclude-from と結合されます)。

bca check --check-exclude "tests/**" --check-exclude "xtask/**"
bca check --check-exclude-from .bcacheckignore

--check-exclude-from reads a .gitignore-style file (blank lines and # comments skipped); the conventional name is .bcacheckignore, mirroring .bcaignore for the walker. Globs match the path exactly as the walker matched it for --exclude. As a negative filter key, an explicit --check-exclude list unions with the manifest [check] exclude list rather than replacing it. A CLI exemption is added to the project’s, so you cannot accidentally re-gate a path the manifest deliberately exempted. Duplicates collapse; CLI patterns sort first. Pass --no-config to drop the manifest’s exemptions entirely. Positive scope keys like paths / include still replace on the CLI; only the exclude filters merge.

相対グロブは何に対して相対か

The source of a glob determines its root:

ソース相対グロブが解決される基準
bca.toml [check] exclude / exclude_frombca.toml を置いているディレクトリ
--check-exclude / --check-exclude-frombca を実行したディレクトリ

So exclude = ["./vendor/**"] in a manifest at the repo root means <repo>/vendor/, whichever directory you invoke bca check from, including from inside vendor/ itself. The same glob passed as --check-exclude './vendor/**' means vendor/ under your shell’s current directory, because that is where you typed it.

This matters most for per-file callers (editor integrations, pre-commit hooks, the agent hooks in Agent feedback), which run bca check <one file> from whatever directory they happen to be in. Their exemptions belong in the manifest, where the anchor is the project’s and not the caller’s.

The walker’s exclude / --exclude pair is split by origin the same way: a --exclude glob is matched against each file’s path relative to the seed you named, and a manifest exclude glob is resolved against the manifest’s own directory, whatever seed you name and wherever you run from. So bca metrics and bca metrics -p sub honour the same manifest exclusions (#1189), and the warning that a named file overrode an exclude resolves each glob at its own root (#1164).

他の抑制メカニズムとの優先順位

bca check は、最も特異的なものから順に、次の順序で除外を解決します。

  1. In-source markers (bca: suppress / bca: suppress-file) always win; they apply during the walk, so the function never becomes a violation.
  2. [check.exclude] globs exempt categories of files (tests, generated code).
  3. .bca-baseline.toml lists known offenders being paid down.

--print-effective-config は、他のゲート入力と併せて解決後の check_exclude グロブを報告します。

check_exclude lists the combined CLI and manifest globs. manifest_check_exclude identifies the manifest subset; resolve those globs relative to the reported manifest directory. The walker uses exclude and manifest_exclude in the same way. The manifest-specific keys are absent when the manifest contributes no globs.

manifest_exclude_from and manifest_check_exclude_from follow the same idea for the exclude_from files, with one difference: a command-line --exclude-from replaces the manifest’s file rather than adding to it, so the manifest key is present only when the manifest’s file is the one in effect. The inline glob lists union; the files do not.

言語別の制限([thresholds.lang.<slug>])

Metric distributions vary by language more than by project: the measured 97.5th-percentile per-function cognitive value runs from 4 in C# to 50 in C. A [thresholds.lang.<slug>] table gives one language its own limits, layered over the project-wide table:

[thresholds]
cognitive = 15
cyclomatic = 15
"loc.ploc" = 600

[thresholds.lang.c]
cognitive = 30
"loc.ploc" = 1200

[thresholds.lang.elixir]
nom = 150
wmc = 300

C is now gated at cognitive = 30 and loc.ploc = 1200 while keeping the project’s cyclomatic = 15; every other language keeps all three. The override is per metric, not a replacement table: a language inherits every limit it does not restate. Which numbers to change, and the two cases where an override is a correction rather than tuning, are in Choosing thresholds.

The key is the canonical language slug, the same vocabulary --language accepts: rust, python, cpp, csharp, objc, tsx, mozcpp, mozjs, and so on. bca check --language nonsense prints the full list. Two rules point in opposite directions here:

  • マニフェスト内の未知のスラッグは、did-you-mean ヒント付きのツールエラー(終了コード 1)です。タイポした [thresholds.lang.rust-lang] が、作者は緩めたつもりでいるのにゲートを黙ってプロジェクトの制限のままにしておく、という事態があってはなりません。
  • An unrecognised file language falls through to the global table, as does any language with no override of its own. Nothing is silently ungated. A file whose extension maps to no grammar at all is skipped by the walk before the gate sees it, with a warning if you named it explicitly.

コマンドラインの --threshold はグローバルのままで、これまでどおり最後に絶対的に適用されます。プロジェクトのテーブル「と」すべての言語別テーブルの両方を上書きするため、あなたが入力した制限が実際に動く制限になります。

--print-effective-config prints one fully resolved table per overridden language, inherited limits included rather than a diff, so the number that will fire is the number you read:

[thresholds]
cognitive = 15.0
cyclomatic = 15.0

[thresholds.lang.c]
cognitive = 30.0
cyclomatic = 15.0

Per-path limits ([[thresholds.path]])

A [[thresholds.path]] entry re-limits the files its glob matches for the metrics it names, and inherits everything else. It is for a population of files whose shape the project limit misreads — a directory of dispatch tables, a generated layer — and it keeps those files gated, at a limit chosen for them, where a suppression marker would remove them:

[[thresholds.path]]
glob = "src/metrics/{abc,cognitive,cyclomatic,loc}/*.rs"
cyclomatic = 40
"halstead.effort" = 150000

The glob is matched relative to the directory of the config that declared it — the bca.toml, or the --config file — with the engine and anchor [check] exclude uses. Where several entries match one file the last wins, per metric. An unknown metric, a missing or empty glob, and tokens are each a tool error naming the entry. The soft tier derives from the overridden limit, and --threshold still outranks it; the full order is stated once in How the layers compose, and when to reach for an entry rather than a marker is in Per-path overrides.

--print-effective-config prints each entry after the per-language tables, with the metrics it declares at their hard-tier limit — --threshold applied last, the tier not applied — so the printed file replays the gate at the hard tier through --config. That is the whole guarantee. The resolved view carries no [thresholds.soft] table and does not record which limits came from --threshold, so a soft-tier run is not replayed by the printout alone: loading it with the same --tier re-derives a band from the printed hard limits, which matches the original only when the original’s soft configuration is still in scope (the manifest is still discovered, not --no-config) and it used no --threshold (a CLI limit is absolute and unscaled on the original run; reloaded, it is a hard limit the band scales). The reason the printed path limits are hard, not banded, is in Per-path overrides:

[thresholds]
cyclomatic = 15.0

[[thresholds.path]]
glob = "src/metrics/{abc,cognitive,cyclomatic,loc}/*.rs"
cyclomatic = 40.0
"halstead.effort" = 150000.0

二層しきい値(--tier)

--tier <hard|soft|soft=RATIO> は、ゲートが比較に使うしきい値の層を選択します。hard(デフォルト)は [thresholds] テーブルをそのまま使います。soft はハードゲートの「前に」発火する早期警告の層で、すべての制限を RATIO 倍に引き締めます。--tier 単独指定は soft を意味し、soft だけならデフォルト比率 0.95 を使い、soft=0.90 は比率を 0.90 に固定し、soft=1.0 は一括スケールを無効にします。

RATIO scales the band, not the number. For most metrics a limit is a ceiling, so tightening it means multiplying: cognitive = 15 with soft=0.9 warns at 13.5.

The lower-is-worse mi.* family is different. Its limit is a floor, a value below it is the violation, and the index has no true zero (mi.original and mi.sei go negative on large functions). So the floor is raised by 1 − RATIO of one maintainability band rather than scaled. One band is 10 points of mi.visual_studio, whose 20/10 cut-offs Visual Studio and radon use, and the same 17.1 points on the 171-point mi.original / mi.sei scale. "mi.original" = 20 with soft=0.9 warns at 20 + 0.1 × 17.1 = 21.71, and a floor of -50 at -48.29. Multiplying a floor would move the warning to 18, under the hard gate, and dividing a negative one would do the same (-50 / 0.9 = -55.6). Either way nothing could reach the soft band first.

Limits must be finite. A count limit must also be non-negative; an mi.original or mi.sei floor may be negative. A limit that cannot fire, or fires on every value, still parses but prints a warning: mi.visual_studio is clamped to [0, 100], so = 0 reads warning: limit mi.visual_studio=0 cannot fire: 0 is the metric's floor; gate mi.original instead, and = 101 warns that it fires on every value.

[thresholds.soft] テーブルはメトリクスごとのソフト制限を設定します。各値は絶対値の数値か、そのメトリクスのハード制限をスケールする "<ratio>x" 文字列のいずれかです。

[thresholds]
cognitive  = 25
cyclomatic = 15
nargs      = 7

[thresholds.soft]
cognitive  = 22       # 絶対値のソフト制限
cyclomatic = "0.9x"   # ハード制限の 90% → 13.5
# nargs は未指定 → ソフト層はハード制限を継承(ソフト帯なし)
bca check --paths src/ --tier=soft

The soft tier is one step of the layering order stated in How the layers compose. Within that step: if a [thresholds.soft] table exists, its overrides merge onto the resolved hard limits and metrics absent from it inherit their hard limit — the blanket RATIO does not apply, since explicit per-metric limits win; otherwise every limit is tightened by the soft RATIO (default 0.95 for a bare soft; soft=1.0 disables scaling). Repeated --threshold name=value flags still apply after it, absolutely.

The step runs once per table — the project’s, each language’s, and each [[thresholds.path]] combination’s — against that table’s own resolved hard limits. There is no [thresholds.lang.<slug>].soft table and none is needed. With [thresholds] cognitive = 15, [thresholds.lang.c] cognitive = 30, and --tier=soft=0.9, C’s soft band is 27, nine tenths of its limit, and the ceiling a C offender is measured against for the exit-5 escalation is 30, not 15. Derive either from the project’s 15 and every C function between 15 and 30 reports “also breaches the hard limit” while sitting inside the limit the project configured for it.

The one combination that can invert the two tiers is an absolute [thresholds.soft] value looser than the hard limit it shadows: [thresholds.soft] cognitive = 12 alongside [thresholds.lang.csharp] cognitive = 4, or an mi.* soft floor below its hard floor. That is a tool error (exit 1) naming the offending table, for the same reason a "<ratio>x" factor above 1 is rejected at parse time: a soft tier that fires after the hard gate is never the intent.

The soft RATIO (and the scale factor in a "<ratio>x" string) must be in (0, 1]. The [check] headroom manifest key supplies the ratio for a bare --tier=soft. The deprecated --headroom <R> flag is a one-cycle alias for --tier=soft=<R> (warns; removed at the next major); it now promotes a hard run to the soft tier. Both tiers ratchet through the same --baseline, and --print-effective-config reports the resolved tier alongside the post-merge limits. See the Local threshold gates recipe for the migration tip and rationale.

候補制限のプレビュー(--explain-threshold)

--explain-threshold <metric>=<limit> reports what a candidate limit would cost at both tiers instead of gating. It is repeatable, takes one candidate per metric, and writes nothing.

$ bca check --explain-threshold nargs=6
nargs: candidate limit 6
  hard tier (limit 6): 60 offenders, 60 already baselined, 0 new
  soft tier (limit 5.7, 0.95x): 135 offenders, 61 already baselined, 74 new
  cluster: 75 of 75 soft-band offenders sit at exactly 6 — the candidate
  limit itself. The soft tier measures distance to the limit, so a limit
  of 6 places them inside the 5.7 band by construction and none of them
  can clear it without real work.

The new column is the figure to weigh: it is how many baseline entries adopting the limit would add. Here the hard tier reads as free and the soft tier costs 74 entries, none of which any amount of tidying can retire. Making that visible is the reason the flag exists.

--threshold nargs=6 はこれを教えてくれません。その制限は最後に、絶対的に、決してスケールされずに適用されるため、この方法で試した候補にはソフト層がまったくありません。同じメトリクスに両方のフラグを渡すと、黙って解決されるのではなく拒否されます。

The soft limit is derived from the candidate exactly as a real run would derive it: a [thresholds.soft] entry for the metric wins, then the --tier=soft=RATIO ratio if one was given, then 0.95. A [thresholds.lang.<slug>] table that overrides the metric keeps its own limit, since a candidate global limit does not reach it, and the report says so on its own line. A ratio-derived soft limit names its arithmetic: 5.7, 0.95x for a count, and for an mi.* floor the additive step, as in mi.original=20 → 20.855, +0.855 (0.05 × 17.1).

それ以外はすべて、予測対象の実行と一致します。exclude_tests、[check] exclude、ソース内の抑制マーカー、--changed-only、ベースラインはいずれも通常どおり適用されます。唯一の違いは、ベースラインでカバーされた違反者が、落とされるのではなくカウントされることです。これによって already baselined / new の分割が可能になっています。

The preview replaces the gate, so it never fails: the exit code is 0 unless a tool error (exit 1) stops the run, and a one-line reminder of that goes to stderr. It conflicts with --write-baseline, --print-effective-config, --report-format, --output, and an explicit --summary-file <path> (the auto / never keywords are still accepted), each of which would produce a second, different artifact.

このフラグが可視化するために存在する規則については、しきい値の選択を参照してください。

信頼できない入力に対するモード(--strict)

ゲートが検査するファイル集合を狭める既定の動作が 2 つあり、どちらもテスト対象のツリーから入力を読み取ります。生成コード検出器はヘッダにマーカー語句を含むファイルをスキップし、ウォーカーは走査対象ツリー内の .gitignore ファイルを解釈します。どちらも信頼できるチェックアウトの分析には適切な既定値です。しかしプルリクエストのゲートでは、レビュー対象の変更が引けるレバーになります。コメント 1 行の追加、または .gitignore への 1 エントリで、ファイルがゲートから外れます。CI レシピに両方の再現手順があります。

--strict は両者を 1 つのフラグで無効にします。--no-skip-generated --no-ignore を渡すのと等価です。

bca check --strict --paths src/

プロジェクトはワークフローごとではなく、bca.toml で一度だけオプトインします。

[check]
strict = true

このキーはトップレベルの exclude_tests と同様に presence-only です。プロファイルを有効にすることはできますが、無効に戻すことはできません(--no-strict はありません)。そのため CI ワークフローがコミット済みのポリシーを黙って弱めることはできません。

プロファイルが無効でも、bca check は検査しなかったものを報告します。いずれかの仕組みがファイルを除外した場合は、1 行のサマリーを stderr に出力して件数を示します。

bca: 2 files not checked (1 generated, 1 ignored) — pass --report-skipped to list them

クリーンな実行では何も出力されず、サマリーが終了コードを変えることもありません。--report-skipped は除外された各エントリを note: skipped (generated): <path>、note: skipped (ignored): <path>、または note: skipped (ignored directory): <path> の行で一覧表示します。--strict の下では何もスキップされないため、要約するものもありません。

The counts come from the walk’s prune points: for every directory the walker entered, the gate checks which immediate children the ignore rules dropped. Only files a parser owns are counted, on both sides. An ignored log file, or a generated Cargo.lock (whose header matches @generated), would have been read and dropped rather than checked, and counting either would make the summary fire on every run. A file named explicitly on the command line bypasses ignore rules and is analyzed, so it is never counted.

Ignored directories are reported only under --report-skipped, as N ignored directories not walked entries, and are never entered. Nearly every checkout has an ignored build tree on disk, and enumerating a target/-sized tree would cost a full second traversal for a million-file count with no signal in it. The ignored tally reflects every source the walker honors (in-tree .gitignore and .ignore files, the global gitignore, and ancestor ignore files), so local and CI counts can differ.

With content sniffing off, generated trees still need an exemption. Give them one the reviewers own, either a committed walker deny-set (--exclude-from .bcaignore) or a [check.exclude] glob, rather than a marker the submitter controls.