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

変更履歴(VCS)メトリクス

bca vcs ranks files by change-history risk: signals derived from git history rather than the source AST. It is the project’s one language-agnostic, non-AST metric family. The goal is to surface the files most likely to harbour bugs or vulnerabilities, using the signals the empirical defect- and vulnerability-prediction literature most consistently backs. This page is the command reference; Change-history metrics defines each signal and cites its source.

A single history walk runs once per invocation (never per file) and produces per-file signals over two configurable windows: a long window (default 12mo, about 365 days) and a recent window (default 90d).

クイックスタート

$ bca vcs --paths src --top 20
Change-history risk (long window 365d, recent 90d, formula v2)
 RANK      RISK  COMMITS rec/long  CHURN rec/long  AUTHORS long  FILE
    1       7.2             68/68     11634/11634             1  src/metrics/cyclomatic.rs
    2       6.9             68/68       7299/7299             1  src/metrics/npa.rs
    ...

With no --format, a human-readable ranked table is printed. Pass --format markdown|html for a rendered report page, or --format json|yaml|toml|cbor|csv for structured output. Unlike bca metrics / bca ops (whose --output-dir is a directory of per-file emissions), a change-history report is a single whole-repo document, so bca vcs --output <file> writes one file (CBOR, being binary, requires --output). The shared --paths / --include / --exclude / --no-ignore filters pick which tracked files to report.

bca vcs exits with an error when run outside a git working tree.

ファイルタイプのスコープ

By default bca vcs ranks only the files bca computes metrics for, the same set bca metrics would analyse. High-churn non-source files (CHANGELOG.md, Cargo.lock, generated config) carry no maintainability meaning yet maximise the churn / commit / author signals, so ranking them beside source code is noise. Scoping to files-with-metrics also keeps the standalone ranking aligned with the AST hotspot tables in bca report --vcs.

--file-types <SCOPE> でスコープを選択します。

値意味
metrics (デフォルト)bca が言語 / メトリクスを持つファイルのみ(拡張子で判定)
all追跡中の、バイナリでもシンボリックリンクでもないすべてのテキストファイル
rs,py,toml,…カンマ区切りの拡張子許可リスト(先頭のドットは省略可、大文字小文字は区別しない)
bca vcs                          # ソースファイルのみをランク付け(デフォルト)
bca vcs --file-types all         # 追跡中のすべてのテキストファイルをランク付け
bca vcs --file-types rs,py       # Rust と Python のファイルのみをランク付け

The check is extension-only (no file content is read) and ANDs with the --paths / --include / --exclude / --no-ignore filters: a file must pass both to be ranked. Extension-less files (Makefile, Dockerfile, LICENSE) and unknown extensions are out of the metrics scope; a custom list is a literal extension filter, so it can include a non-metrics type like toml. An empty or all-blank custom list is a clear error rather than a scope that silently ranks nothing.

レンダリングされたレポートページ

bca vcs --top 50 --format html --output vcs.html
bca vcs --top 50 --format markdown --output vcs.md

--format html produces a self-contained, sortable page styled like bca report html (click any column header to re-sort); --format markdown produces the same ranked table as GitHub-Flavored Markdown. Both render every signal column, the same data the structured formats carry. The column set is defined once and shared by both renderers, so they cannot drift.

ランキングをスタンドアロンページではなく集約品質レポートに組み込むには、bca report --vcs を渡します。これは report markdown / report html に「Change-history risk」セクションを追加します。

シグナル

フィールド型説明
commits_long / commits_recentu32各ウィンドウでファイルに触れた個別コミット数
churn_long / churn_recentu64各ウィンドウにおける Σ(added + deleted) 行数
authors_long / authors_recentu32各ウィンドウにおける正規化済み作者 ID の個別数
ownership_top_sharef64 ∈ [0,1]トップ作者に帰属する編集の割合(低いほど所有権の希薄化が進んでいる)
burstf64 ∈ [0,1]commits_recent / commits_long
bug_fix_commitsu32メッセージがバグ修正キーワードに一致する長期ウィンドウのコミット数
security_fix_commitsu32セキュリティキーワード(CVE-####、security、vuln、exploit、sanitize など)に一致する長期ウィンドウのコミット数
revert_commitsu32件名がリバート / ロールバックである長期ウィンドウのコミット数
age_daysu32ファイルのウィンドウ内で最初のコミットからの経過日数(長期ウィンドウが上限)
last_modified_daysu32ファイルのウィンドウ内で最も新しいコミットからの経過日数
change_entropy_long / change_entropy_recentf64ウィンドウごとの変更エントロピー(ビット単位、下記参照)
cochange_entropy_long / cochange_entropy_recentf64ウィンドウごとの共変更グラフエントロピー(ビット単位、下記参照)
risk_scoref64Composite, formula-versioned (see below); ordinal, not cardinal
hotspot_scoref64?complexity × churn_recent; present only when cyclomatic is among the computed metrics
risk_score_version / vcs_schema_versionu32Forward-compatibility version stamps. Carried once on the report envelope, alongside long_window_days / recent_window_days, not repeated inside each per-file vcs block (issue #635)

Author identities are canonicalised through the repository .mailmap and counted by lowercased email; Co-authored-by: trailers add participants. Bot identities (dependabot[bot], renovate[bot], github-actions[bot], and the like) are excluded by default. Both the built-in bot pattern and a custom --bot-pattern match case-insensitively against substrings of the author name and email. For example, --bot-pattern renovate matches Renovate Bot. Prefix the pattern with (?-i) for case-sensitive matching. Binary files and symlinks are skipped; an untracked file has no record at all (distinct from a tracked file with zero in-window activity).

変更エントロピーと共変更エントロピー

2 つのプロセスエントロピーシグナル(risk_score_version 2 で追加)は、ファイルの変更量だけでなく、「どのように」変更されるかを捉えます。

  • Change entropy (Hassan, 2009, Predicting Faults Using the Complexity of Code Changes). For each commit, the Shannon entropy (in bits) of its churn distribution across the files it touched measures how scattered that change was: a one-file commit is 0; a commit spreading churn evenly across n files approaches log₂(n). Each file is then credited its churn share pᵢ·H of every commit it took part in (Hassan’s History Complexity Metric). Higher means the file is repeatedly caught up in diffuse, cross-cutting changes. Later work (arXiv 2504.18511, below) measured file-level change entropy at a Pearson correlation up to 0.54 with defect counts on eight Apache projects.
  • Co-change graph entropy (arXiv 2504.18511, 2025). Files that change in the same commit are joined by a weighted edge (weight = number of shared commits). A file’s co-change entropy is the Shannon entropy of its edge-weight distribution: low when it always co-changes with the same partner, high when its changes ripple across many different files. Combined with change entropy it improved the area under the receiver operating characteristic curve (AUROC) in 82.5% of cases over the v1 signal set on eight Apache projects.

Both are reported per window. A 0.0 is computed, not missing: the file only ever changed alone (no co-change neighbours, or single-file commits with zero change entropy). Bulk-import commits touching more than 1000 files are excluded from the co-change graph, whose edge count grows O(width²), but still contribute their O(width) change entropy.

複合リスクスコア

デフォルトの加重(weighted) 式は、カテゴリ別の乗法的ボーナスを伴う、対数スケールの加重和です。

recency_churn  = ln(1 + churn_recent)
recency_count  = ln(1 + commits_recent)
long_count     = ln(1 + commits_long)
long_churn     = ln(1 + churn_long)
author_factor  = ln(1 + authors_long)
dilution       = (1 - ownership_top_share).clamp(0, 1)
fix_factor     = ln(1 + bug_fix_commits + 2 * security_fix_commits)
size_factor    = ln(1 + sloc)^2 / 100              // タイブレーカーではなく本来の係数
entropy_factor = 0.10 * change_entropy_recent + 0.05 * cochange_entropy_recent
new_file_bonus = 0.15 if age_days < recent_window_days else 0
dev_bonus      = 0.35 if authors_long >= 9 else 0.15 if authors_long >= 6 else 0

base = 0.30 * recency_churn
     + 0.25 * recency_count
     + 0.15 * long_count
     + 0.15 * author_factor * (1 + dilution)
     + 0.10 * fix_factor
     + 0.05 * long_churn
     + entropy_factor
     + size_factor

risk_score = base * (1 + dev_bonus + new_file_bonus)

各項の重みは文献に基づいています。直近のチャーンとコミット頻度が最大の重みを持ちます(Nagappan & Ball の相対チャーン、just-in-time 欠陥予測、Firefox の NumChanges PD 86)。作者ファクターは所有権の希薄化でスケールされます(Avelino の DoA / トラックファクター、Bird ら)。カテゴリ別の開発者数ボーナスは、9 人以上の開発者が触れたファイルは脆弱性を抱える可能性が約 16 倍だったという RHEL4 の知見をエンコードしています。セキュリティ修正には 2 倍の重みが付きます(Sentence-Level VFC 研究、PySecDB)。そして直近ウィンドウの変更エントロピーおよび共変更エントロピーの項は加算的に入ります(Hassan 2009、arXiv 2504.18511)。完全な導出は src/vcs/score.rs にあります。

The score is ordinal: only relative ranks have meaning. A single risk_score_version (now 2) versions both formulas: any change to the weighted sum or the --risk-formula percentile blend bumps it, and the recent entropy pair joins both.

--risk-formula percentile is an alternative: each signal is re-ranked to its percentile within the analyzed set, then averaged. The literature recommends relative triggers over hard thresholds for cross-project robustness.

フラグ

フラグデフォルト意味
--long-window <DUR>12mo長期ウィンドウ(12mo、2y、8w、365d、ISO 8601 の P1Y)
--recent-window <DUR>90d直近ウィンドウ
--top <N>50上位 N 件のみ表示(0 = すべて)
--file-types <SCOPE>metricsランク付け対象のファイル: metrics、all、または拡張子リスト(rs,py)
--ref <REF>HEAD解析対象のリビジョン
--full-historyオフDAG 全体を走査(デフォルト: first-parent のみ)
--include-mergesオフマージコミットを含める
--no-follow-renamesオフリネームの追跡を止める(デフォルト: 追跡する)
--no-exclude-bots / --bot-pattern <RE>除外ボット作者のフィルタリング
--as-of <WHEN>現在時刻Reference “now” (RFC 3339 / @unix / git date) for reproducible snapshots
--risk-formula {weighted|percentile}weighted複合スコアの式
--emit-author-detailsオフEmit SHA-256-hashed canonical author IDs
--author-hash-key <KEY>未設定Harden the emitted author digests into a keyed HMAC (see Author-detail privacy); requires --emit-author-details
--include-deletedオフ対象 ref で削除済みのファイルもランク付けする
--no-cacheオフ永続履歴キャッシュをスキップする(常に新規に走査)
--clear-cacheオフ実行前にこのリポジトリのキャッシュ済み履歴を消去する
--cache-dir <DIR>プラットフォーム標準のキャッシュキャッシュディレクトリを上書きする

キャッシュ

On large, active repositories, walking the long-window history dominates runtime. Most consecutive CI runs add only a few commits. bca vcs therefore keeps a persistent cache of history walks, keyed by the resolved HEAD SHA and repository identity:

  • 変更のないツリーでは前回の結果が再生され、履歴走査は行われません。
  • HEAD が前進した場合、走査は新しいコミットのみを訪れ、それをキャッシュ済み履歴に継ぎ足します。
  • フォースプッシュ(キャッシュされた head が新しい head の祖先でなくなった状態)では、完全走査にフォールバックします。

The cache is a pure optimization: a hit is bit-identical to a fresh walk, and the time windows are recomputed against the current moment on every run rather than frozen when the entry was written. An entry is ignored, and the history recomputed, whenever the schema, the score-formula version, the walk-affecting options, the repository’s effective .mailmap, or the git diff configuration that decides how gix diffs (diff.algorithm, core.bigFileThreshold, and a diff.<driver> section’s algorithm, binary, and whether it sets a textconv; a driver’s command never runs here and does not count), the diff attribute bindings that route a path to a diff driver or mark it -diff (the .gitattributes files in the index — committed or staged, never an unstaged edit, which the walk does not read either — and the full content of .git/info/attributes, core.attributesFile or its XDG default, and the system attributes file, so a reorder of two overlapping rules counts as much as an edit, as does a core.ignoreCase flip; each file is read once per run and both the walk and the entry’s stamp come from that one read), or the shallow boundary of a shallow clone (a git fetch --deepen or --unshallow leaves HEAD where it was but reaches commits the entry never saw) differ; in particular changing a window forces a fresh walk. A bca release that adds a term retires every existing entry, so the first bca vcs after such an upgrade re-walks the whole long window once. Nothing deletes an entry that no longer matches, since a run with other options may still use it; --clear-cache removes the repository’s entries, retired ones included. Finalization-only knobs such as --risk-formula, --emit-author-details, --author-hash-key, and --include-deleted are applied on replay, so they reuse the same cached walk; a cached walk even re-finalizes under a different author-hash key without re-walking.

By default the cache lives under $XDG_CACHE_HOME/big-code-analysis/vcs (%LOCALAPPDATA% on Windows, ~/.cache otherwise). Author identities are stored only as their SHA-256 digests, never plaintext, so the cache holds no raw author emails. This is pseudonymization, not anonymization: the digests are recoverable against a candidate email set (see --emit-author-details). The same cache transparently accelerates bca metrics --vcs and bca report --vcs.

# 最初の実行がキャッシュを準備し、2 回目はそれを再生します。
bca vcs --paths .
bca vcs --paths .                 # 前回の作業を再利用

bca vcs --no-cache --paths .      # この実行ではキャッシュを無視
bca vcs --clear-cache --paths .   # ゼロから再構築
bca vcs --cache-dir /tmp/bca-cache --paths .

REST(POST /v1/vcs)と Python(vcs.rank)の各インターフェースは、省略可能な no_cache / cache_dir パラメータを通じて同じ挙動を提供します。

The cache is specific to the file ranking. The trend and commit subcommands, and the /v1/vcs/trend and /v1/vcs/jit endpoints, do not use it, so the cache flags do not apply there: passing --no-cache / --cache-dir alongside a subcommand is a usage error, and the trend endpoint rejects a no_cache / cache_dir field rather than silently ignoring it (issue #961).

bca metrics での利用

bca metrics --vcs を渡すと、各ファイルの metrics に vcs ブロック(およびファイルの cyclomatic 合計から計算される hotspot_score)が付加されます:

$ bca metrics --vcs --paths src/parser.rs --format json
{ "name": "src/parser.rs",
  "metrics": { "cyclomatic": { ... },
    "vcs": { "commits_long": 15, "churn_recent": 211,
             "risk_score": 3.7, "hotspot_score": 7596.0, ... } } }

bca metrics --vcs はデフォルトのウィンドウと重み付き計算式を使用します。ウィンドウや計算式を調整したい場合は bca vcs を使用してください。

関数単位の帰属

bca metrics --vcs-per-function (which implies --vcs) additionally attaches a vcs block to every nested function, method, and class space. It blames each file once with git blame and buckets the surviving lines into the AST function spans, so you can rank the risky function inside a risky file:

$ bca metrics --vcs-per-function --paths src/parser.rs --format json
{ "name": "src/parser.rs",
  "metrics": { "vcs": { "risk_score": 3.7, ... } },   // ファイルレベルのブロック
  "spaces": [
    { "name": "parse", "kind": "function",
      "metrics": { "vcs": { "commits_long": 4, "churn_recent": 12,
                            "risk_score": 2.1, "hotspot_score": 144.0 } } } ] }

関数単位のブロックは現時点の blame のスナップショットであり、ファイルレベルのブロックと直接比較「できません」。その churn は、最終変更がウィンドウ内に収まる現存行を数えるものであり(過去の追加+削除のチャーンではありません)、所有権は変更したコミットごとに加算されます。ウィンドウ内で誰も変更していない関数のカウントはゼロと報告されます。最終変更が長期ウィンドウより前の行は、関数のサイズには寄与しますが、ウィンドウ付きのどのカウントにも寄与しません。

Each nested space’s hotspot_score multiplies its own cyclomatic (cyclomatic.value) by its churn_recent, not its subtree cyclomatic.sum, so a nested function’s complexity is not counted again at every enclosing level; the file-level block keeps the subtree sum. The Python bindings’ vcs_per_function=True reports the same scores.

Limitations. Blame follows file renames (so edits under a former path still attribute), but attributes a line moved between functions to its current position only. A function split into two has no record of its pre-split identity, and a deleted-then-recreated function attributes to the recreating commits. If a file cannot be blamed (untracked, or the rare gix-blame failure on pathologically repetitive content), its per-function blocks are omitted while the file-level block and the AST metrics still emit.

ジャストインタイム(コミットレベル)スコアリング

Where everything above ranks files at a ref, bca vcs commit <commit> scores a single commit for defect-induction risk, the unit a CI gate reviews at check-in. (The subcommand was renamed from bca vcs jit in 2.0; the old jit spelling keeps working as a hidden alias for one release cycle. “Just-in-time (JIT)” stays the literature term, below.) It is a static, rule-based scorer (no trained model, so nothing drifts as the project ages), with the feature groups and signs taken from the just-in-time defect-prediction literature: Kamei et al., A Large-Scale Empirical Study of Just-in-Time Quality Assurance, IEEE TSE 2013, with the open replications Commit Guru (FSE 2015) and McIntosh & Kamei, Are Fix-Inducing Changes a Moving Target? (IEEE TSE 2018).

$ bca vcs commit HEAD --pretty
{
  "jit_schema_version": 3,
  "jit_score_version": 1,
  "source": "commit",
  "risk_score": 4.40,
  "commit": { "id": "5176d3e…", "parent_count": 1, "is_merge": false,
              "purpose": { "is_fix": true, "is_security_fix": false,
                           "is_revert": false } },
  "features": {
    "size":       { "lines_added": 942, "lines_deleted": 60,
                    "files_touched": 19, "hunks": 78 },
    "diffusion":  { "subsystems": 5, "directories": 8, "entropy": 3.48 },
    "history":    { "prior_changes": 275, "prior_distinct_authors": 1,
                    "prior_bug_fix_commits": 237,
                    "prior_security_fix_commits": 21,
                    "file_risk_max": 10.97, "file_risk_mean": 3.87,
                    "new_files": 2 },
    "experience": { "author_prior_commits": 962,
                    "author_recent_commits": 962 }
  },
  "contributions": { "size": 2.74, "diffusion": 0.97, "history": 1.57,
                     "purpose": 0.15, "experience": -1.03 }
}

5 つの特徴量グループと、それぞれがスコアをどう動かすか:

グループ特徴量方向
サイズ追加 / 削除行数、変更ファイル数、diff ハンク数大きいほど ⇒ 高リスク
拡散度個別のサブシステム数とディレクトリ数、コミット内の変更エントロピー分散しているほど ⇒ 高リスク
履歴the touched files’ priors (prior changes, distinct authors, bug- and security-fix counts, and the composite risk_score), measured from history before the commit荒れたファイル履歴 ⇒ よりリスキー
経験作者の過去のコミット数(長期・直近)経験が多いほど ⇒ リスクは低下(このグループは減算)
目的メッセージの修正 / セキュリティ修正 / リバート分類修正は加算、リバートは減衰

contributions ブロックは、順序尺度である risk_score への各グループの符号付き寄与を報告するため、コミットが「なぜ」その順位になったのかを利用側が確認できます。ファイルレベルの risk_score と同様、このスコアは順序尺度です。スコアでコミットをランク付けしたり、リポジトリ自身の分布とコミットを比較したりできますが、その大きさを確率として読み取らないでください。計算式が変更されると jit_score_version が上がります(ファイルレベルの risk_score_version とは別です)。

The commit is scored against its first parent. A merge commit is flagged (is_merge, parent_count ≥ 2) and scored against that first parent. A root commit and any new files carry zero priors by construction; the score then leans on size and author experience, as the literature prescribes for changes with no file history.

ウィンドウ / --ref / ボット / マージ / リネームの各フラグは親コマンドの bca vcs と共有されます。commit 専用のフラグは、位置引数の <commit>(デフォルト HEAD)、--format json|yaml|toml|cbor(デフォルト json)、--output、--pretty、および次のとおりです:

# CI ゲート:コミットのスコアがしきい値以上のとき exit 2 で終了する。
bca vcs commit HEAD --fail-above 6.0

--fail-above は終了コード 2 を使用します(bca check と同じ「メトリクスゲート」の慣例です。終了コード 1 はツールエラー用に予約されたままです)。スコアは順序尺度なので、しきい値は絶対値として扱うのではなく、リポジトリ自身のコミットスコア分布に対して調整してください。

任意の diff のスコアリング(--diff)

bca vcs commit --diff <file> scores a git diff instead of a commit (use --diff - to read the diff from stdin). This suits a pre-commit hook or a code-review bot, where the change exists only as a diff and has not been committed yet.

git diff --cached | bca vcs commit --diff - --pretty

The input must be a git-style unified diff carrying diff --git file headers, as produced by git diff or git format-patch. Plain diff -u / diff -ru output (which has ---/+++ header lines but no diff --git header) parses to zero files, and combined / merge diffs (git diff --cc, with @@@ hunk headers) are rejected as a malformed diff; pipe a regular two-way git diff instead.

A bare diff carries no author, parent, or file history, so only the size and diffusion groups are computable. The output is therefore a deliberately partial report, a distinct shape from a commit report:

$ git diff | bca vcs commit --diff - --pretty
{
  "jit_schema_version": 3,
  "jit_score_version": 1,
  "source": "diff",
  "partial_risk_score": 1.83,
  "size":      { "lines_added": 42, "lines_deleted": 8,
                 "files_touched": 3, "hunks": 6 },
  "diffusion": { "subsystems": 2, "directories": 3, "entropy": 1.46 },
  "contributions": { "size": 1.18, "diffusion": 0.65 }
}

The source field is a permanent "diff" marker, and the history / experience / purpose groups are absent from the report entirely, not present as zero. Zero is a real value (a commit genuinely with no prior history scores those groups at zero); an absent group means “unavailable”, so a consumer can never mistake an unscored group for “low risk”. For the same reason the score field is named partial_risk_score, not risk_score.

A diff-only score is not comparable to a commit score. The partial score sums only size + diffusion; the full commit score for the same change also folds in history, experience, and purpose, and because experience is negatively signed the two are not even ordered (an experienced author can push the full score below the partial one). Rank diffs against other diffs, never against commit scores. --diff and the positional <commit> are mutually exclusive; --fail-above works in both modes (calibrate the diff-mode threshold against your own diff-score distribution).

パーサーは git のデフォルトである C 形式のパス引用(core.quotePath=true)を理解するため、非 ASCII 文字や空白を含む名前のファイル(git は "a/na\303\257ve.txt" のように出力します)に触れる diff は、diffusion の特徴量では引用付きの生文字列ではなく、デコードされたパスの下にグループ化されます。

REST と Python のパリティ

JIT スコアは CLI 以外からも利用できます:

  • REST: POST /v1/vcs/jit に { "id", "repo_path", "commit" } を渡すとコミットの JitReport JSON が返り、{ "id", "diff" } を渡すと部分的な diff レポートが返ります。REST API の利用を参照してください。
  • Python: vcs.commit(repo_path, commit=...) はコミットレポートを dict として返し、vcs.score_diff(diff) は部分的な diff レポートを返します。変更履歴(VCS)メトリクスを参照してください。

ML ベースの JIT モデルとサーバーサイドフック統合は、引き続きスコープ外です。

履歴トレンド(時系列)

A single bca vcs run answers “what is risky now”. bca vcs trend answers “is it getting better or worse”, the question a technical-debt programme acts on, by sampling the metrics at several points in time and emitting a per-file time series.

$ bca vcs --top 20 trend --points 12 --span 24mo --pretty
{
  "trend_schema_version": 1,
  "vcs_schema_version": 2,
  "risk_score_version": 2,
  "long_window_days": 365,
  "recent_window_days": 90,
  "truncated_shallow_clone": false,
  "as_of_points": [ 1700000000, 1705259520, ... ],
  "files": {
    "src/parser.rs": [
      null,                       // 最古の時点では存在しなかった
      { "as_of": 1705259520, "vcs": { "risk_score": 4.1, ... } },
      { "as_of": 1710519040, "vcs": { "risk_score": 6.8, ... } }
    ]
  },
  "deltas": {
    "improved":  [ { "path": "src/old.rs",    "delta": -3.2, ... } ],
    "regressed": [ { "path": "src/parser.rs", "delta":  2.7, ... } ]
  }
}

--points N 個の等間隔サンプル(両端点を含む)が --span DURATION をカバーし、--as-of(指定がなければ現在時刻)で終わります。as_of_points はサンプルのタイムスタンプを古い順に列挙し、各ファイルの配列はそれと 1:1 で対応します。null 要素は、その時点でファイルがまだ存在しなかったことを示します。deltas は、各ファイルの最初と最後に存在した時点の間で risk_score が最も下がったファイル(improved)と最も上がったファイル(regressed)をランク付けします。--top-deltas で各リストを切り詰められます。

Each point re-anchors at the mainline tip that existed at or before that moment; it does not just re-window today’s HEAD tree. That is what makes a file born later show as null at older points rather than leaking its present-day metrics backwards. Files kept in the series are the --top highest-risk by their most-recent sample.

親コマンド bca vcs から再利用されるフラグ:ウィンドウ(--long-window / --recent-window)、--ref、--file-types、ボット / マージ / リネームのトグル、--as-of(最新のアンカー)、および --top。-O は json(デフォルト)、yaml、cbor を受け付けます。存在しない時点は null としてシリアライズされ、TOML はこれを表現できないため、TOML は除外されています。時点数は、深い履歴でも時点ごとの履歴走査が扱いきれる範囲に収まるよう、2–120 に制限されています。

リネームに関する注意。 リネームは各サンプルの走査「内」では追跡されますが、2 つのサンプルの「間」でリネームされたファイルは、1 本の連続した線ではなく、2 つの別々のパス系列(旧名、次に新名)として現れます。サンプル間のリネームの縫合は後回しのフォローアップです。

バスファクター(ディレクトリ・リポジトリレベル)

Where the per-file ownership_top_share measures concentration within a file, the bus factor (also called the truck factor) measures it across a set of files: the minimum number of developers whose departure would leave more than half of a directory’s files without a knowledgeable maintainer. bca vcs emits it as a top-level vcs_aggregate object alongside the ranked files:

{
  "vcs_aggregate": {
    "bus_factor": {
      "bus_factor_schema_version": 2,
      "coverage_threshold": 0.5,
      "doa_threshold": 0.75,
      "repo": { "bus_factor": 3, "files": 412, "authors": 11 },
      "by_directory": [
        { "directory": "src", "bus_factor": 2, "files": 180, "authors": 7 },
        { "directory": "src/vcs", "bus_factor": 1, "files": 24, "authors": 3 }
      ]
    }
  }
}

Each developer’s authorship of each file is scored with the Avelino Degree-of-Authorship heuristic (Avelino, Passos, Hora & Valente, A Novel Approach for Estimating Truck Factors, ICPC 2016):

DoA(d, f) = 3.293 + 1.098·FA + 0.164·DL − 0.321·ln(1 + AC)

ここで FA は最初の作者性(d が f を作成したなら 1)、DL は d の f への提供(変更)回数、AC は「他の」開発者による変更回数です。ファイルの最大値で正規化した DoA が 0.75(論文のしきい値)を超えるとき、その開発者は f の作者とみなされます。トラックファクターは貪欲な除去で求めます。すなわち、まだカバーされているファイルを最も多く作者として持つ開発者を除去し、ファイルの --bus-factor-threshold(デフォルト 0.5、Avelino に準拠)超が孤児になるまで繰り返し、除去した人数を報告します。by_directory は各トップレベルディレクトリとその直下のサブディレクトリをカバーし、それぞれ再帰的に配下のすべてのファイルに対して計算されます。

構造上の注意点:

  • With the default threshold, a repository whose files all share one sole author has a bus factor of 1. Single-author files owned by different developers can produce a higher bus factor. This is the heuristic working as intended, not a bug; treat the number as a planning signal, not a guarantee.
  • ボットのアイデンティティは(ファイルごとのシグナルと同様に)フィルタリングされ、ウィンドウ内に活動のないファイルは作者性を持たず、分母から除外されます。
  • 「最初の作者性」は「長期ウィンドウ内で観測された」最古のコミットを意味し、必ずしもファイルの真の作成を意味しません。

The aggregate reflects the whole repository within the file-type scope: one history walk covers every in-scope file, by default the files-with-metrics set (see File-type scope), so --file-types all widens the bus factor to every tracked file. --paths / --include / --exclude scope only the ranked per-file list, not the bus factor. To focus on a subsystem, read its entry in by_directory rather than filtering the walk.

--emit-author-details adds a key_author_ids list to each group: the SHA-256-hashed identities of the removed key developers, in removal order (plaintext identities never leave the process). The aggregate is computed only for the dedicated bca vcs / bca report --vcs reports and the REST / Python endpoints; the per-file bca metrics --vcs injection path does not pay for it.

作者詳細のプライバシー

The key_author_ids digests are a stable pseudonym, not anonymization. Hashing keeps plaintext emails out of the report and the cache and deters casual disclosure, but the hash is not cryptographically irreversible. The pre-image is an email, which is low-entropy and enumerable, and commit histories are public, so anyone with a candidate set of emails can recover which digest belongs to whom by hashing each candidate or with a precomputed email→hash table. This is the same weakness that broke Gravatar’s email hashing.

公開された key_author_ids(およびファイルごとの author_ids)は、平文のメールアドレスの出力を避ける仮名化として扱い、執念深い攻撃者によって作者が再識別されないことの保証としては扱わないでください。その保証が必要なら、ダイジェストを公開しないでください。

強化モード:--author-hash-key

For stronger resistance, pass a secret key with --author-hash-key <KEY> (requires --emit-author-details). The emitted digests then become an HMAC, HMAC-SHA256(key, SHA-256(email)), instead of a bare hash: an attacker without the key can no longer hash a candidate email to recognise its digest, nor use a precomputed email→hash table, because both attacks need the secret key. Pick a high-entropy key and keep it secret; anyone who learns it can re-run the enumeration.

鍵は安定しています。同じ鍵なら、すべてのレポートにわたって、また永続キャッシュのリプレイをまたいでも同じダイジェストが得られるため、レポート間の相関付けとキャッシュは引き続き機能します。異なる鍵は無関係なダイジェストを生成するため、履歴を共有する 2 つのチームは、鍵を共有しない限り作者を相互にひも付けることはできません。

Prefer the BCA_AUTHOR_HASH_KEY environment variable over the flag: a key on the command line is visible to other local users via the process list (ps) and is saved in shell history. The flag takes precedence when both are set:

export BCA_AUTHOR_HASH_KEY="$(cat ~/.config/bca/author-key)"
bca vcs --emit-author-details

鍵がカバーしないもの:ディスク上の履歴キャッシュ(issue #334)は意図的に「鍵なしの」内側の SHA-256 ダイジェストを保存します。鍵は確定処理の段階で適用されるため、キャッシュ済みの走査は再走査なしに任意の鍵で再確定できるからです。キャッシュはローカル限定で公開されることはありませんが、脅威モデルにローカルのキャッシュディレクトリを読む攻撃者が含まれる場合は、キャッシュを無効化(--no-cache)するかクリア(--clear-cache)してください。同じ鍵オプションは REST エンドポイント(author_hash_key)と Python(vcs.Options(author_hash_key=…))でも利用できます。

このリポジトリでのドッグフーディング

This project runs bca vcs on its own source. make vcs prints the ranked table; path selection and the .bcaignore deny-set come from the repo-root bca.toml manifest, the same config make self-scan and make report use, and BCA_VCS_TOP overrides the row cap. The manifest’s [vcs] file_types key sets the default scope (the --file-types CLI flag replaces it when given). On every push to main, the Pages CI job includes the ranking in the published quality report (bca report html --vcs / report markdown --vcs), so reports/index.html shows the change-history risk section beside the AST hotspots. The same job publishes the full top-100 ranking as reports/vcs-report.json for tooling.

REST と Python

  • REST: JSON ボディ { "id": "...", "repo_path": "/path/to/repo", ... } を伴う POST /v1/vcs はランク付けされたレポートを返し、POST /v1/vcs/trend(同じフィールドに加えて points / span / top_deltas)は履歴の時系列を返します。REST API の利用を参照してください。
  • Python: big_code_analysis.vcs.rank(repo_path, …) はランク付けされたレポートを dict として返し、vcs.trend(repo_path, points=…, span=…, …) は時系列を返します。また analyze(path, vcs=True) は単一ファイルのメトリクスに vcs ブロックを付加します。

POST /v1/vcs と vcs.rank()(vcs.Options 経由)のどちらも、ランク付け対象のファイルをスコープする省略可能な file_types("metrics" / "all" / "rs,py")を受け付けます。これは CLI の --file-types に対応します。

どちらも結果に vcs_aggregate のバスファクターを含み、カバレッジの割合を調整する bus_factor_threshold(範囲は (0, 1))を受け付けます。