ADR-23 — 診断トリアージコマンド(`rigor triage`)
ステータス: Accepted、2026-05-20;スライス(slice)1+2+3+4はv0.1.9で実装済み。
lib/rigor/triage/がカタログを担い、rigor triageは本番サブコマンド。プラグイン提供認識器(WD2拡張ポイント)は見送り。check派生のサブコマンド設計を記録する。プロジェクトの診断ストリームを要約し — ルールIDの分布、ファイルごとのホットスポット、ヒューリスティックな「なぜ」ヒントを提示する。ADR-22の仲間:ADR-22は今日あるもの(ベースライン(baseline))を記録し;ADR-23はそれが何を意味するかと次に何をすべきかを説明する。
コンテキスト
Section titled “コンテキスト”5プロジェクトサーベイ(docs/notes/20260519-oss-library-survey.md)および後続のMastodon測定(1303ファイル)により、成熟したコードベースへの最初のrigor checkは、少数の大きな診断クラスタに支配されることが判明した — その原因は構造的であり、無関係なバグが散らばっているのではない:
- Mastodon、デフォルト設定:488件の診断。≈73%はActiveSupportの
core_extセレクタ(3.days、5.minutes、"x".squish、Time.currentなど)のcall.undefined-method— 純粋な設定ギャップであり、rigor-activesupport-core-extRBSバンドルを配線することで修正される。そのバンドルを適用すると:488 → 88(−82%)。 - 残り88件自体もクラスタ化されていた:≈55件のActiveRecordアソシエーションに対するnil-receiver診断、≈13件の
Array[String]と誤推論されたARクエリメソッド、RBSカバレッジギャップの末尾部分。
実際的な教訓:生の診断リストは新規参入者にとって間違った最初のアーティファクト。488行のダンプは488の問題として読める;有用な読み方は「≈360件がひとつのことを言っている — ひとつのRBSバンドルを有効化せよ」。ADR-22はアドプション側(残余をベースラインとしてスナップショットし、リグレッションのみを表面化)に対処する。診断側 — どのクラスタが設定ギャップで、どれがプロジェクトのmonkey-patchの可能性が高く、どれがRBSカバレッジのホールで、どれが最初に修正すべき本物の局所化されたバグかをユーザーに伝えること — には対処していない。
ADR-22のrigor-project-init SKILLフェーズ7(「ルールごとの診断数をカウントし、インタラクティブに修正可能なほど小さいルールを提案する」)とrigor-baseline-reduceフェーズ1(「ルールごとにグループ化、カウントでソート」)は、今日この種のトリアージを — しかしアドホックに、生のストリームに対するLLM側のカウントとして — 行っている。それは非決定論的であり、テスト不可能であり、SKILL呼び出しのたびに再導出される。データレイヤーはSKILLが呼び出す決定論的でspec対応のコマンドであるべき。
rigor triageを追加する — check派生のサブコマンド。rigor checkと同じ解析を実行し、生のファイルごとの診断ストリームの代わりに3セクションレポートを出力する:
- ルールID分布 — 深刻度別に分割した、ルールごとの診断数のプロジェクト全体ヒストグラム。
- ホットスポットファイル — 診断が最も多く(および最も集中して)あるファイル、そのルールごとの内訳付き。
- ヒューリスティックヒント — 診断ストリームに対してパターン認識を行い、大きなクラスタそれぞれに対して考えられる原因と推奨アクションを名指しするパターン認識器(§「ヒューリスティックカタログ」参照)。
--format jsonは同じコンテンツをADR-22 SKILLと他のツール向けに機械可読形式でemitする。このコマンドは読み取り専用かつ助言的 — .rigor.ymlを編集せず、ベースラインも書かない;その出力に対してアクションを取るのはユーザー(またはSKILL)の判断。
作業上の決定
Section titled “作業上の決定”WD1 — フラグではなくサブコマンド
Section titled “WD1 — フラグではなくサブコマンド”rigor triageはcheck / baseline / lsp / sig-genと並列であり、rigor check --triageではない。
- トリアージレポートと生の診断ストリームは代替的なビューであり、加法的ではない。
checkに--triageフラグを追加しても488行ダンプが出力され、レポートはそれを置き換えるために存在する。 rigor checkはすでに--explain(フォールバック診断)と--stats(RunStats:ファイル / RBSクラス数)に費やされている。3つ目のオーバーロードされたフラグは混雑したサーフェス(surface)をさらに曇らせる。- 発見可能性:
rigor --helpにtriageがcheckと並んで表示されることでオンボーディングパスが広告される。埋もれたフラグではそうならない。 - ADR-22 SKILLは安定した
--format json契約(contract)を持つ名前付きサブコマンドを呼び出す;それはcheckのサブセクションをパースするよりもクリーン。
rigor triageはrigor checkと同じ位置引数pathsおよび同じ.rigor.yml / --config解決を受け入れる。
WD2 — v1向けの固定組み込みヒューリスティックカタログ
Section titled “WD2 — v1向けの固定組み込みヒューリスティックカタログ”認識器(§「ヒューリスティックカタログ」)はRigorが管理する固定セットとして同梱される。プラグイン提供の認識器はv1では見送り。
v1カタログは小さく(6つの認識器)、具体的なサーベイデータに基づいている;プラグイン拡張ポイントは組み込みセットがその形を実際のプロジェクトに対して実証する前では投機的。カタログはひとつのモジュールにあるため、認識器の追加はシングルファイルの変更。
WD3 — 認識器はまずruleをキーに、次にメッセージテキストをキーに
Section titled “WD3 — 認識器はまずruleをキーに、次にメッセージテキストをキーに”認識器の主キーは構造化されたrule識別子(call.undefined-method、nullable-receiverなど) — リリース間で安定。認識器がさらにレシーバー型やメソッド名(下記H1/H2/H3/H4)を必要とする場合、v1は診断メッセージ(undefined method 'X' for TYPE)をパースして抽出する。
このメッセージパースは認識器をメッセージの文言に結合するため脆弱であると認識されており、まさにADR-22 WD1がベースラインで回避した結合と同じ。緩和策:
- パースは少数のルール(
undefined method 'M' for T)のプレフィックス形状のみをターゲットにしており、v0.1.x系統にわたって安定している — 任意のメッセージボディではない。 - メッセージのパースに失敗した認識器は「この診断をスキップ」に縮退し、クラッシュや誤ったヒントには縮退しない。
堅牢な修正 — Analysis::Diagnosticにルールが持つオプショナルな構造化フィールド(receiver_type、method_name)を追加し、それを持つルールが埋める — はスライス4として記録されており、現在実装済み:単一のcall.undefined-method emitサイト(CheckRules#build_undefined_method_diagnostic)がそのペアを埋め、カタログがフィールドを読み取り、不在の場合のみメッセージパースにフォールバックする。メッセージ文言の結合はエンジンemitパスから解消された。
WD4 — トリアージは助言的;アクションしない
Section titled “WD4 — トリアージは助言的;アクションしない”rigor triageはヒントを出力する;設定を編集せず、ベースラインも書かない。根拠:
- 関心の分離:トリアージは診断。
.rigor.ymlの編集(プラグイン有効化、signature_paths:配線)と.rigor-baseline.ymlの書き込みは治療 —rigor-project-initSKILL(選択肢をユーザーにエスカレート)とrigor baseline generateが担う。 - 読み取りに見える動詞でサイレントに設定を書き換えるコマンドはADR-22 WD2と同じ「マジックなし」スタンスに違反する。
ヒントは人間 / エージェントが実行するためにアクションを命令形で表現する(「signature_paths:に追加せよ」)。自動適用される編集ではない。
WD5 — triageはADR-22 SKILLの下にあるデータレイヤー
Section titled “WD5 — triageはADR-22 SKILLの下にあるデータレイヤー”rigor triageとADR-22はパイプラインとして構成される:
| ステージ | ツール | 答える問い |
|---|---|---|
| 診断 | rigor triage | N件の診断がなぜある? どのクラスタ? |
| 決定 | rigor-project-init / rigor-baseline-reduce SKILL | どのプラグインを有効化するか? どのルールを修正 / サプレス / ベースライン化するか? |
| 記録 | rigor baseline generate | 合意された残余をスナップショット。 |
SKILLは生のストリームを自分でカウントする代わりにrigor triage --format jsonを呼び出す。ADR-22のrigor-project-initフェーズ7(「集中したルールを可能性の高い実バグとして表面化」)とrigor-baseline-reduceフェーズ1(「ルールごとにグループ化、カウントでソート」)は、アドホックなLLM算術ではなく — 決定論的でspec対応の — トリアージJSONの薄いラッパーになる。
ヒューリスティックカタログ(v1)
Section titled “ヒューリスティックカタログ(v1)”6つの認識器。それぞれが診断ストリームをスキャンし — そのパターンが閾値を超えるクラスタにマッチしたとき — エビデンスサマリーと推奨アクションを伴うひとつのヒントをemitする。すべてのヒントは[おそらく…]と枠組みされ、レポートヘッダーは「ヒューリスティック — アクションの前に確認」と述べる:認識器はシグナルであり、判定ではない。
| # | ヒント | 検出 | 推奨アクション |
|---|---|---|---|
| H1 | おそらくActiveSupportのcore_ext | call.undefined-method、レシーバーがコアクラス(Integer / Float / Numeric / String / Symbol / Hash / Array / Object / NilClass / Time / Date / DateTime / Range)に属し、メソッド名がバンドルされたASセレクタセットに含まれる | signature_paths:でrigor-activesupport-core-extを配線 |
| H2 | おそらくプロジェクトのmonkey-patch / refinement | ≥Kファイル(デフォルトK=3)にわたって同じメソッド名が未定義、レシーバーがコアクラスまたはプロジェクト定義クラス、メソッドがすべてのRBSソースに存在しない | pre_eval:で定義ファイルを登録(ADR-17)、またはRBSオーバーレイを追加 |
| H3 | gemがRBSを同梱していない | call.undefined-methodでレシーバークラスが(依存関係ソースインデックス経由で)RBSのないGemfile.lock gemに帰属する | rbs collection install、またはgemをdependencies.source_inference:にオプトイン(ADR-10) |
| H4 | ActiveRecordリレーション誤推論の可能性 | ARクエリメソッド名(where / joins / includes / order / distinct / group / pluckなど)がArray[...]と推論されたレシーバーでcall.undefined-methodとフラグされる | rigor-activerecordを有効化;それが持続するならエンジン推論ギャップとしてRigor側のissueが値する |
| H5 | システミックなシングルファイルクラスタ | 単一の(ファイル、ルール)バケットのカウントが閾値≥ | ひとつの修正が多くをクリアできる;またはADR-22ベースラインの強力な候補 |
| H6 | おそらく本物のバグ — 最初に確認を | 低い総カウント、ファイルをまたいで散在するルール(集中していない) | これらのサイトを最初にレビュー — 低カウントの散在した診断はRigorが検出した局所化されたバグ |
H1のASセレクタセットはrigor-activesupport-core-extバンドルがカバーする≈50のセレクタ。認識器はリストを複製するのではなく、そのバンドルのsig/を読んでそれを導出すべき(SHOULD)、二者が決してずれないように(スライス2の実装詳細)。
H2の「すべてのRBSソースに存在しない」とH3のgem帰属はどちらも、アナライザーがすでに保持しているデータ — RBSリフレクションサーフェスとDependencySourceInference gem-to-classマップ — を再利用するため、どちらの認識器も2回目の解析パスを必要としない。
CLIサーフェス
Section titled “CLIサーフェス”$ rigor triage [paths...] → 解析を実行し、3セクションレポートを出力(分布、 ホットスポット、ヒント)。`rigor check`と同様に .rigor.yml / --configを尊重する。
--format text|json text(デフォルト) | 機械可読 --hints-only ヒューリスティックヒントセクションのみ出力 --top N ホットスポットファイル数(デフォルト10) --no-hints 分布 + ホットスポットのみrigor triageは診断数によらず0で終了 — 検査コマンドであり、ゲートではない(rigor checkがゲートのまま)。
出力スケッチ — text
Section titled “出力スケッチ — text”Diagnostic distribution — 488 total (480 error / 8 warning) call.undefined-method 437 ████████████████ nullable-receiver 31 ██ always-truthy-condition 8 ▏
Hotspot files app/models/status.rb 42 call.undefined-method ×40 nullable-receiver ×2 app/models/account.rb 27 ...
Hints — heuristics, verify before acting [likely ActiveSupport core_ext] ~287 diagnostics undefined-method on Integer/Numeric: days ×34 minutes ×68 hours ×26 … → ActiveSupport monkey-patches Numeric. Add rigor-activesupport-core-ext to `signature_paths:` in .rigor.yml. [likely a project monkey-patch] 12 diagnostics `to_widget` undefined on String across 5 files … → Register the defining file via `pre_eval:` (ADR-17), or add an RBS overlay.出力スケッチ — json
Section titled “出力スケッチ — json”{ "summary": { "total": 488, "error": 480, "warning": 8 }, "distribution": [ { "rule": "call.undefined-method", "count": 437 } ], "hotspots": [ { "file": "app/models/status.rb", "count": 42, "by_rule": { "call.undefined-method": 40 } } ], "hints": [ { "id": "activesupport-core-ext", "confidence": "likely", "diagnostics": 287, "evidence": { "...": "..." }, "action": "Wire rigor-activesupport-core-ext via signature_paths:" } ]}- オンボーディングが正しく読める。新規参入者は488の未分化な問題ではなく「488件のうち≈360件がひとつのことを言っている」と見る — サーベイが特定した最大の摩擦ポイント。
- ADR-22 SKILLが決定論的なデータレイヤーを得る。フェーズ7 / フェーズ1のカウントがアドホックなLLM算術でなくなり、安定したJSON契約を持つspec対応コマンドになる。
- 設定ギャップ診断が修正に結びつく。H1/H3が「大量のundefined-methodノイズ」を「このバンドルを有効化 / このRBSをインストールせよ」に変換 — 記述的なだけでなくアクション可能。
triage+baselineがクリーンなペアになる。トリアージは何を修正するかサプレスするかを言い;ベースラインがその決定を記録する。
- メッセージパースの脆弱性(WD3) — スライス4が構造化された
Diagnosticフィールドを追加するまで、受信器 / メソッド抽出がメッセージ文言に結合される。有界:パース失敗はスキップに縮退する。 - ヒューリスティックは誤解を招く可能性がある。ヒントは推測;H2は特に繰り返された本物のタイポをmonkey-patchと誤認する可能性がある。
[おそらく…]の枠組みと「アクションの前に確認」ヘッダーは荷重を担う — コマンドはヒントを判定として提示してはならない。 - CLIサーフェスにサブコマンドがひとつ増える。ADR-22が既に意味するオンボーディングの自然なエントリーポイントであることで軽減される。
- プラグイン提供の認識器拡張ポイント(WD2)と構造化された
Diagnosticフィールドの堅牢性修正(WD3 / スライス4)は見送りであり、却下ではない。
実装のスライス分け
Section titled “実装のスライス分け”スライス1 — rigor triageスケルトン + 分布 + ホットスポット — LANDED (v0.1.9)
Section titled “スライス1 — rigor triageスケルトン + 分布 + ホットスポット — LANDED (v0.1.9)”Runner#runを再利用する新しいRigor::CLItriageサブコマンド。- 新しい
Rigor::Triageモジュール:分布集計 + ホットスポットランキング + text / jsonレンダラー。 - ヒントはまだなし(
--no-hints動作がコマンド全体)。
スライス2 — ヒューリスティックカタログ — LANDED (v0.1.9)
Section titled “スライス2 — ヒューリスティックカタログ — LANDED (v0.1.9)”Rigor::Triage::Hint認識器インターフェース + 6つのH1〜H6認識器。H1はセレクタセットをrigor-activesupport-core-extバンドルのsig/から導出する。- 両レンダラーにヒントを配線。
スライス3 — ADR-22 SKILL統合 — LANDED (v0.1.9)
Section titled “スライス3 — ADR-22 SKILL統合 — LANDED (v0.1.9)”rigor-project-initフェーズ7とrigor-baseline-reduceフェーズ1をrigor triage --format json呼び出しに書き換え。
スライス4 — 構造化されたDiagnosticフィールド(実装済み) + プラグイン認識器(見送り)
Section titled “スライス4 — 構造化されたDiagnosticフィールド(実装済み) + プラグイン認識器(見送り)”- 実装済み —
Analysis::Diagnosticのオプショナルなreceiver_type/method_nameがcall.undefined-methodルールによって埋められる。カタログが構造化ペアを読み取り(不在の場合のみメッセージパースにフォールバック)、エンジンemitパスのWD3メッセージパース結合を解消。 - 見送り — プラグインが認識器を提供できる
Pluginフック。
再評価トリガー
Section titled “再評価トリガー”- ヒューリスティックヒントが実際のプロジェクトで有用より誤解招くと判明 → カタログを分布 + ホットスポットのみに削減するか、すべての認識器の信頼度閾値を引き上げる。
- 4つ目のADR-22ファミリーのコンシューマーがトリアージJSONを必要とする → JSON形状を文書化された安定した契約に昇格させる。
- メッセージ文言の変更がWD3パースを繰り返し壊す → スライス4(構造化フィールド)を前に引き出す。
- ADR-22 — ベースラインメカニズム +
rigor triageがフィードする2つのオンボーディングSKILL。 - ADR-17 —
pre_eval:;H2が提案するアクション。 - ADR-10 —
dependencies.source_inference:;H3が提案するアクション。 docs/notes/20260519-oss-library-survey.md— クラスタ分類を生んだ5プロジェクトサーベイ。
© 2026 TypedDuck. Licensed under CC BY-SA 4.0.