ADR-22 — ベースラインメカニズム + プロジェクトオンボーディングSKILL
ステータス: Accepted、2026-05-19; v0.1.7〜v0.1.9全体で完全実装。
スライス(slice)1〜5がランド;スライス6(IDE / LSPガター統合)は見送り。プロジェクトのプロジェクトごとのエラーレベル実用主義に対するスタンスを記録する: ベースライン(baseline)ファイル(PHPStan形)と2つのコンパニオンエージェントSKILL(プロジェクト初期化とベースライン削減)。この組み合わせにより、成熟したコードベースがすべての診断を先に修正することなくRigorを採用できる一方、新しいリグレッションがすぐに表面化するという保証を維持する。
コンテキスト
Section titled “コンテキスト”docs/notes/20260519-oss-library-survey.mdの5プロジェクトサーベイは、成熟したRubyコードベースはRigorとの最初の接触で通常数百から数千の静的解析診断を抱えていることを示した。v0.1.6 / v0.1.7トラックのプラグインとエンジン改善(D1〜D6)でいくつかの系統的偽陽性クラスがクローズされた後でも、主要合計は以下に落ち着いた:
| プロジェクト | 合計診断 | エラー |
|---|---|---|
| Mastodon | 2,401 | 678 |
| Redmine | 939 | 381 |
| Solidus | 47 | 41 |
| tdiary-core | 65 | 20 |
| dependabot-core | 5 | 2 |
これらのプロジェクトにわたって残留診断を検査した3つの観察:
- 一部は実在するが経験的に安全。静的解析は
T | nilを見る;本番コードベースはアクティブなテストスイートとライブトラフィックによって実行され、コールサイトに到達する前に常にスロットを初期化する。静的な読み取りは最悪ケースの健全性(soundness)の意味では正しい;ランタイムは最悪ケースを観察しない。 - 一部はスタイル的。アクティブなコードベースの数十のファイルにわたって同じパターンが繰り返される場合 —
instance_variable_getの防御的ガード、既知の有限タグセット上の動的sendディスパッチ、アナライザーのナローイング(narrowing)が追従しない慣用的なobj&.methodチェーン — そのパターンはプロジェクトのスタイルそのものだ。すべてのサイトを書き直すことを強制することは、機能しているイディオムに反する。 - 一部はRigorが捕捉したバグ。本物の
nilレシーバークラッシュで、行がめったに実行されないために潜伏していた。これがRigorが提供する価値だ。
ナイーブなゼロ診断必須ポリシーはカテゴリー1 + 2 + 3を単一の「すべて修正」バケットに集約する。さらに悪いことに、採用を阻む: MastodonでRigorを試すメンテナーが初日に678エラーに直面し、カテゴリー3から本物のバグ修正を抽出する前に実験を放棄する。PHPStan、mypy、Sorbet、そしてSteepはすべて同じ答えに収束した:
今日あるものをベースラインとして記録する。新しい診断のみを表面化する。ベースラインの削減を別の、オプトインワークフローとして扱う。
これがRigorが採用する設計原則だ。
Rigorはすでに3つの診断抑制レイヤーを出荷しているが、「今日ある状態をスナップショットする」ユースケースには適合しない:
# rigor:disable <rule>(行ごと) — 特定の既知安全な行に対する著者意図のコメント。数百のサイトにわたって適用するには冗長。# rigor:disable-file <rule>(ファイルごと) — ファイルレベルの包括的抑制。粗い;カウント可視性を失う。severity_profile: lenient/balanced/strict(実行ごと) — すべてのルールの重要度をグローバルに再スタンプ。ファイルごとのターゲティングなし。
設計会話からのユーザー向け推論:
静的には
T|nilが観察されるかもしれないが、実際には値は常に初期化されている —nilケースは実際には発生しない。アクティブなプロジェクトでは同じパターンがそのまま残っている場合、スタイルと見なせる。最低限、本番 / テストコードが動作するという事実は静的解析推論より重要だ。しかし初期状態の既知パターンを見過ごすと、将来の潜在的なエラーが蓄積される。
ベースラインメカニズムはその張力を明示的に収容するものだ: 初期状態は保持される;新しい発生が表面化する;ベースラインの削減は独自のSKILLを持つ認識されたワークフロー。
このADRは3つの成果物にコミットする。互いにロードベアリングなため一緒にスケジュールされた:
- ベースラインファイルメカニズム — ベースライン生成時に既知のすべての(file、rule)ペアのカウントを記録するプロジェクトローカルYAMLファイル。実行時に観察された診断のうちベースラインで説明されるものは沈黙する;過剰な診断は現在の実行の「新しい発見」として表面化する。
rigor-project-initSKILL — 新しいプロジェクトをオンボーディングするためのエージェント向けワークフロー:.rigor.ymlを書き、プロジェクトのスタックに合ったプラグインを選択し、適切なseverity_profileを選び、初期ベースラインを生成し、オプションで開発者オーバーライド規約に従い.rigor.dist.ymlを発行する。rigor-baseline-reduceSKILL — 日和見的な品質改善のためのエージェント向けワークフロー: 優先順(最小ルール先行;集中的な修正を持つパターン先行)でルールごとにベースラインを歩き、サンプルコールサイト + 提案された修正を提示し、ユーザーが実際に修正を着地させるにつれてカウントを減らす。
メカニズムはプロジェクトごとにオプトイン — .rigor-baseline.ymlが存在しないことは現在の動作(すべての診断が表面化する)を意味する。SKILLはエージェント向けであり、CLIコマンドではない;CLIはSKILLが駆動する狭いrigor baseline {generate, dump, prune}サブコマンドファミリーを追加する。
ワーキング決定
Section titled “ワーキング決定”主要な設計選択を記録し、将来の「なぜこの形?」という質問が書かれた前提に対して解決できるようにする。
WD1 — ベースラインマッチ粒度: デフォルトはルールID、オプトインでメッセージパターン
Section titled “WD1 — ベースラインマッチ粒度: デフォルトはルールID、オプトインでメッセージパターン”検討した3つの候補粒度:
| 粒度 | プロ | コン |
|---|---|---|
| (file、rule、count)— Rigorのデフォルト | リファクタロバスト(行移動がベースラインを無効化しない)。コンパクト(ファイル × ルールごとに1行)。診断の文言を調整するパッチリリースにわたって安定。 | 異なる行 / レシーバーの同じルールの2つの診断を区別できない。 |
| (file、rule、message、count)— オプトイン、PHPStanのデフォルト | コールサイトごとのピンポイント精度(異なるundefined method foo vs undefined method barが別バケット)。ファイル内の同じルールの診断すべてを沈黙させずに特定の既知の問題をベースライン化できる。 | rigorリリースにわたる文言調整で脆弱 — ルールの診断が言い換えられたすべてのRigorパッチリリース後にベースラインが再生成される。 |
| (file、rule、line、count) | 正確なリグレッション位置を表面化。 | 最も脆弱 — 上に1行追加するとすべてのベースラインエントリーがシフトする。 |
決定: WD1はルールID(デフォルト)とメッセージパターン(オプトイン)の両形式を同じファイル内で行ごとにサポートする。CLIのrigor baseline generateは--match-mode messageが渡されない限りルールID行を書く。
2つのモードが存在する理由:
- Rigorのアナライザーサーフェス(surface)はPHPStanより若い;メッセージの文言はリリースごとにまだ精緻化されている。文言にピン留めするデフォルトでは、任意のルールのメッセージがタイポ修正を受けたすべてのパッチリリース後にユーザーが再生成を強いられる。ルールIDデフォルトはRigorバージョン間のベースラインチャーンを最小化する。
- ピンポイント精度は時にロードベアリングだ。メンテナーは同じファイルの将来のすべての
undefined-methodを沈黙させずに「このサイトの既知のundefined method 'bar' for Foo」をベースライン化したい。オプトインメッセージモードがそのケースをカバーする。
両モードはWD4 ALL-or-NOTHING閾値セマンティクスを共有する — 「バケット」は行が持つどのキーによっても定義される:
- ルールID行はバケット
(file, rule)を定義する。 - メッセージ行はバケット
(file, rule, message)を定義する。
異なるモードの行が同じファイルに共存できる。フィルタはすべての行を順番に歩く;ある診断のキーに一致する最初の行がその診断が貢献するバケットカウンタだ。それ以降の行はすでに主張された診断を見ない。
ベースラインファイルの形(混合例):
# .rigor-baseline.yml — generated by `rigor baseline generate`# Tracks diagnostics known at <ISO-8601 timestamp>. Reducing# rows is the `rigor-baseline-reduce` SKILL's job.version: 1
ignored: # Rule-ID rows (the default form `generate` writes) — every # diagnostic with the named rule under the named file # contributes to this bucket's count. - file: app/models/spree/address.rb rule: call.undefined-method count: 3 - file: app/services/fan_out_on_write_service.rb rule: call.undefined-method count: 1 - file: app/services/fan_out_on_write_service.rb rule: nullable-receiver count: 2
# Message-pattern rows (opt-in via `--match-mode message`, # or hand-edited): tighter precision. `message` is a Ruby # `Regexp`-compatible pattern (no surrounding `/.../`). # Diagnostics with the named rule in the named file whose # `message` matches the regex contribute to this bucket; # other diagnostics fall through to the next row. - file: app/lib/activitypub/linked_data_signature.rb rule: call.undefined-method message: "undefined method `merge' for Array" count: 1メッセージモードのregex構文: リテラル部分文字列またはRuby regexソース。ジェネレータはリテラルメッセージをRegexp.escapeでクォートする(新たに導入された(parens)や[brackets]がメッセージ内でサイレントオーバーマッチを引き起こさないように)。手編集行はRuby regex文法全体を使える。
生成時のモード選択:
rigor baseline generate # default: rule-ID rowsrigor baseline generate --match-mode messagerigor baseline generate --match-mode mixed # per-rule heuristic (see below)mixedヒューリスティック — 初期実装ではなくフォローアップスライスとして書かれる — はルールごとに選択する: 安定した文言のルール(Rigor::Analysis::RuleCatalogでそのようにカタログ化)にはルールID;文言が進化中のルールにはメッセージモード。これは将来のエルゴノミクス調整;初期リリースはルールIDデフォルト + オプトインメッセージモード。
WD2 — ベースラインファイルの場所とオプトインロード
Section titled “WD2 — ベースラインファイルの場所とオプトインロード”2つの質問を一緒に回答する:
(a)デフォルトファイル名 + 場所。プロジェクトルートの.rigor-baseline.yml、.rigor.yml / .rigor.dist.ymlの兄弟。これはrigor baseline generateがデフォルトで書くパスであり、プロジェクトを最初にスキャフォールドするときにproject-init SKILLが書くパスだ。ファイルは意図的にバージョン管理される(プロジェクトの状態を文書化する)。
拒否された代替案:
| 場所 | 拒否理由 |
|---|---|
.rigor/baseline.yml | キャッシュディレクトリは規約でgitignoreされている;ベースラインはそこから脱出しなければならず、プロジェクトの状態は設定より1レベル深く隠される。 |
.rigor.ymlのbaseline:キー内 | ベースラインコンテンツのスケール(行 × 数百ファイル)は設定ファイルに適していない — diffを泥濁りにし、.rigor.ymlの編集とベースライン編集をロックステップにする。 |
(b)ロードセマンティクス: 明示的のみ、暗黙的なし。ディスク上の.rigor-baseline.ymlの存在はrigor checkの動作を変えない。ベースラインは.rigor.yml(または.rigor.dist.yml)が明示的にそれを名指ししたときのみロードされる:
# .rigor.yml — opt-in baseline referencebaseline: .rigor-baseline.yml# (or any other path the project chose)キーが省略されると、rigor checkはベースラインが存在しないかのように実行される — Rigorが今日持っている同じ動作。これは「マジックなし」のスタンスだ: プロジェクトルートに座っているファイルは診断セマンティクスをサイレントに変えてはならない。
明示的にする理由の背景:
- 監査可能性: 設定ファイルは診断出力を変えるものを記録する単一の文書だ。
.rigor.ymlを読んでいるレビュアーはbaseline: .rigor-baseline.ymlを見てベースラインがアクティブだと知る;その行なしではベースラインファイルは休眠状態(または不在)だ。レビュアーが見逃した別の貢献者がチェックインしたファイルからの驚きはない。 - CI柔軟性: プロジェクトはCIでの抑制をアクティベートせずに
rigor-baseline-reduceSKILLのドリフト検査のために.rigor-baseline.ymlをコミットしたままにできる。2つの設定:# .rigor.dist.yml — production CI uses the baselinebaseline: .rigor-baseline.yml# .rigor.yml — a contributor's local override that doesn'tbaseline: false # or just omit the key - マイグレーションエルゴノミクス: サイクル中盤でベースラインを削除するのは1行の編集であり、ファイル削除ではない。「以前はN診断を抑制していた」という履歴がYAMLに残る。
- テスト安定性: rigorの統合specとサードパーティプラグインspecは合成プロジェクトに対して
rigor checkを実行する。ベースラインロードが存在時に暗黙的だとすれば、spec著者はtmpdirの迷子.rigor-baseline.ymlファイルを追跡しなければならない;明示的ロードはそのフットガンを取り除く。
決定: WD2 = プロジェクトルートの.rigor-baseline.ymlを規約パスとして、.rigor.yml / .rigor.dist.ymlがbaseline: <path>を宣言する場合のみロードする。CLIフラグ--baseline=PATHは実行ごとのオーバーライドとして存在し(§「CLIサーフェス」を参照)、設定にbaseline:を置かずにベースラインを使用する唯一の方法だ — 主にCIエスケープハッチであり、意図されたワークフローではない。
WD3 — スコープは重要度ではなくルールごと
Section titled “WD3 — スコープは重要度ではなくルールごと”ベースラインはルール識別子(call.undefined-method / nullable-receiver / plugin.activerecord.unknown-column / …)を記録し、重要度レベル(:error / :warning)は記録しない。2つの理由:
- 重要度はサイクル中に変化する:
severity_profile: strictが設定されると、ルールは:warningから:errorに移動できる。ベースラインはそのトグルにわたって安定したままでなければならない。 - ルールごとのスコープは既存の
# rigor:disable <rule>サーフェスを反映する。同じ識別子語彙;学ぶべき2番目の分類スキームなし。
WD4 — 閾値セマンティクス: (file、rule)バケットごとのALL-or-NOTHING
Section titled “WD4 — 閾値セマンティクス: (file、rule)バケットごとのALL-or-NOTHING”ベースラインのcountは「最初のNを沈黙させる」マスクではなく閾値として機能する。(file、rule)ペアごとの2つの状態:
| 実際 | 動作 |
|---|---|
actual ≤ baseline.count | バケット内のすべての診断が沈黙される — プロジェクトは記録されたエンベロープ内にある。 |
actual > baseline.count | バケット内のすべての診断が通常の重要度で表面化する — カウントがまだ閾値以下だったときに沈黙されていたはずのものも含む。 |
根拠: (file、rule)バケットが閾値を超えると、チームのレビューフォーカスは「このファイルのこのルールで何が起きているか」だ — 「N診断のどれが新しいか」ではない。バケット内の行番号はリファクタにわたってシフトする;「最初の3つを沈黙、#4と#5だけを表面化」というルールはベースライン生成の瞬間と現在の実行の間で移動した可能性のある位置を指し示す。バケット全体を表面化することでレビュアーはルールを総括的に監査できる。
実例: ベースラインが(foo.rb, call.undefined-method)にcount: 3を記録する。
- 現在の実行が3サイトを報告 → 0件表面化(閾値以内;沈黙)。
- 現在の実行が5サイトを報告 → 5件全部表面化(閾値超過;バケットは今アクティブな懸念)。
- 現在の実行が2サイトを報告 → 0件表面化(閾値未満;ドリフト機会、WD5を参照)。
実装: ベースラインフィルタは(file, rule)でキーされたバケットごとのゲートだ。actual ≤ baselineのとき、バケット内のすべての診断がドロップする;actual > baselineのとき、バケット内のすべての診断がパスする。バケット中間の部分的な状態はない。
副次的な利益: ルールは対称で、人間のレビュアーとCIゲートの両方に説明しやすい。「あなたのコミットがfoo.rbのcall.undefined-methodカウントを3から4に押し上げた — 閾値超過;4つのサイトすべてがここにある」は明確に読める。代替の「あなたのコミットが4番目のサイトを追加した;サイト#4はここだ」では、CIメッセージがどの特定のサイトが新しいかを宣言しなければならない。(file、rule、count)粒度はそれを意図的にできない(行位置が追跡されないため)。
WD5 — ドリフト検出はオプトイン、強制ではない
Section titled “WD5 — ドリフト検出はオプトイン、強制ではない”PHPStanのストリクトモードはactual < baselineを失敗として扱う(修正とロックステップでベースライン削減を強制する)。Rigorはそうしない。理由: 複数貢献者コードベースでは、並行ブランチが正当にどちらの方向でもベースラインドリフトを生成する可能性がある;ドリフトでCIを失敗させると本物の正確性を得ることなくマージ順序の摩擦が生まれる。
代わりに:
rigor baseline drift— 読み取り専用の検査。delta != 0の(file, rule, baseline.count, actual.count, delta)行を報告する。-baseline-reduceSKILLはこれを参照する。rigor baseline prune— ゼロカウントエントリー(診断クラスがまったく観察されなくなったファイル)のインタラクティブな削除。rigor baseline regenerate— 現在の診断からの完全な書き直し。破壊的(ファイルを上書き);バルク修正後に使用。
WD6 — ベースラインは# rigor:disableの後、severity_profileの後にフィルタリングする
Section titled “WD6 — ベースラインは# rigor:disableの後、severity_profileの後にフィルタリングする”診断パイプラインの順序:
emit → per-line `# rigor:disable` filter → per-file `# rigor:disable-file` filter → severity_profile re-stamp → baseline filter (NEW) → outputベースラインフィルタは最後の抑制レイヤーだ。著者意図のコメントが優先する(「この特定の行は安全だ」と言う著者は、プロジェクトの集合的な「ここにN個あることは知っている」より優先する)。ベースラインは# rigor:disableされたサイトを消費しない;上流フィルタが通過させたものだけを見る。
WD7 — 診断カウントメタデータが実行出力に保持される
Section titled “WD7 — 診断カウントメタデータが実行出力に保持される”CLIは診断ストリームの後に1行サマリーを追加する:
3,099 → 121 surfaced (2,978 silenced by .rigor-baseline.yml)そのため、ベースラインが大きくてもpresuppressionの事実が見える — 誰も追跡していない2,978の潜在的な問題を持つプロジェクトでCIがサイレントにパスする状況を防ぐ。既存の--statsフラグにはベースラインセクションが追加される。サマリー行は診断ではなく素のstderrなので、機械可読な出力を汚染しない。
WD8 — 2つの新しいSKILL、skills/下の外部著者向け
Section titled “WD8 — 2つの新しいSKILL、skills/下の外部著者向け”両SKILLは自分のプロジェクトでRigorを新たに採用するユーザー — gem install rigortypeを実行して自分のコードベースにrigor checkを向けるgem著者、アプリケーション開発者、プロジェクトプライベートプラグインメンテナー — をターゲットにする。これはrigorモノリポの貢献者ワークフローではない。オーディエンスの帰結:
- 公開済み
rigortypegemサーフェスを消費する — モノリポのチェックアウトからbundle exec exe/rigorではなく、Bundler経由でインストールされたrigor実行ファイル。make verifyなし、Nix Flakeなし、spec/integration/...の前提なし。 - 公開CLIフラグと設定キーのみを参照する — エンドユーザーが
rigor --helpで見る同じサーフェス。内部ヘルパー(Rigor::Analysis::Runner.new(...)、Phoenix形の内部専用モジュール)は対象外。 - ROADMAPがv0.2.0外部SKILLトラック用に予約した
skills/トップレベルツリーの下に住む(docs/ROADMAP.md§「エージェントワークフロー / SKILL」を参照)。skills/rigor-project-init/とskills/rigor-baseline-reduce/ディレクトリは、来たるskills/rigor-plugin-author/外部バリアントと並んでそのツリーの最初の具体的な占有者になる。3つのSKILLはv0.2.0外部ユーザートラックの一貫したオンボーディング + 継続的品質 + プラグイン拡張トリオを形成する。 rigor-plugin-authorがskills/下に一時的にあったとき(コミット25e98cc/f2dcc5a)に確立されたポータブル / agentskills.io互換規約に従う: 自己完結、クロスリポジトリ参照の絶対GitHubURL(相対../../パスではない)、name:+description:+ オプションのmetadata: {version:, homepage:}フロントマター、wazaのモジュール数アドバイザリーをクリアするための最大4つのreferences/モジュール。
スケジューリングへの影響: WD8は2つのSKILLをv0.1.9サイクルにコミットする — 先行バージョン(v0.1.7 / v0.1.8)は実地からの実際のプロジェクトエラーデータを収集・対処するために予約され、SKILLがデフォルトのプラグイン / 重要度 / ベースラインルール選択の背後に具体的な実証的シグナルを持って出荷されるようにする。外部rigor-plugin-authorの再定式化も同じv0.1.9の列に乗る。ADRのスライシングセクションはそれらをスライス3 + 4として外部出荷可能な作業として配置し、貢献者実験としてではない。
繰り越し: ベースラインファイル形式とrigor baseline {...}CLIサブコマンドファミリー(スライス1 + 2)はv0.1.9にゲートされていない — それらはSKILLが着地する前に貢献者とフィールドサーベイ実行が実証的ベースラインデータを収集できるように通常のv0.1.xサイクル(v0.1.7から)で出荷する。
2つのSKILLは以下の§§「rigor-project-init」と「rigor-baseline-reduce」でスケッチされる。
WD9 — 専用ベースラインファイルスキーマ(設定インクルード再利用との比較)
Section titled “WD9 — 専用ベースラインファイルスキーマ(設定インクルード再利用との比較)”PHPStanの実際のアプローチはこのADRがスライス1として記録するものと構造的に異なる。PHPStanのphpstan-baseline.neonはparameters.ignoreErrorsエントリーのみを含む通常のPHPStan設定ファイルだ;メインのphpstan.neonはincludes:配列経由でそれを吸収する。ファイルはスキーマではなく規約によって「ベースライン」だ — すべてのキーがメイン設定と同じだ。
Rigorの既存のサーフェスはすでに同じプリミティブを提供する: .rigor.ymlはincludes:リストを受け入れる(既存の設定ローダーによる)。つまりPHPStanスタイルのアプローチは利用可能だ: 任意の設定レベルで有効な単一のignored:キーを定義してインクルードからマージできる。
2つの候補形:
| 側面 | (A)設定インクルード再利用(PHPStanスタイル) | (B)専用ベースラインスキーマ(スライス1) |
|---|---|---|
| スキーマ | .rigor.ymlと同じ;ignored:(または類似)キー下のベースライン行。既存のincludes:配管経由でマージ。 | 独自のトップレベル: version: 1 + ignored:のみ。専用のbaseline:キー経由でロード。 |
| ジェネレータ出力 | ignoreセクションのみが入力された設定ファイルを書く。 | 自己完結したベースラインファイルを書く。 |
| スキーマ進化 | ベースライン形式が設定スキーマバンプに結合。 | ベースライン形式は独立してバージョン管理(version: 1)。 |
| インラインオプション | あり — 小プロジェクトはignored:を.rigor.ymlに直接置ける。 | なし — 外部ファイルを参照しなければならない。 |
| ツールエルゴノミクス | 汎用設定ツールがファイルを処理。 | カスタムBaselineクラスがload / filter / driftを所有;よりクリーンなツールごとのAPI。 |
| 新参者のメンタルモデル | 「どこにでも設定ファイル;スタックする。」 | 「設定は1つのもの、ベースラインは別のもの。」 |
| ジェネレータフットプリント | Configurationライターを再利用。 | Baselineクラス約270行(すでに書かれている)。 |
| ドリフト / プルーンセマンティクス | 汎用 — 設定形ファイル上で操作。 | ベースラインツールのフレームに固有。 |
決定: WD9 = (B)— 専用ベースラインスキーマ。ACCEPTED(2026-05-19、スライス1が着地した後)— 代替案が明示的に検討され、将来の「なぜPHPStanの方法ではないのか?」という質問が書かれた前提に対して解決できるように選択がここに記録されている。
コアフレーミング — 短い形:
スキーマを統一すると1つのignore-rule形式がプロジェクト全体の設定(
paths:とignored:を同じファイルに)として二重機能できる。これは直接著作にとって本物の利益だ。しかしベースラインは手で著作されない —rigor baseline generateによって生成されrigor-baseline-reduceSKILLによって削減される。人間がファイルを直接読み書きしない場合、スキーマ統一の価値(UX学習容易性、1つの設定文法)は生まれない。分離コスト(追加Baselineクラス、カスタムロードパス)は有界で一回限り;統一の利益は1つのスキーマに安定 / チャーニングコンテンツを混合する形でリリースサイクルごとに払われることになる。
根拠(ロードベアリング重みでランク付け):
-
懸念の分離は運用の現実に一致する。設定ファイル(
paths:/plugins:/severity_profile:/ …)は安定している — プロジェクトがどのように解析されたいかを記述する。ベースライン(ignored:行 × 数百ファイル)はチャーニングしている — すべての修正、すべてのリファクタ、すべてのrigorパッチリリースがバケットカウントをシフトできる。1つのスキーマに共存させるとは、同じファイル形式が2つの異なるケイデンスを運ぶことを意味し、読者のメンタルモデルに漏れる(「どのスロットが安定でどれがチャーン?」)。 -
version: 1はベースライン形式が設定の残りを動かさずに進化できるようにする。スライス5のregenerateと将来の形式マイグレーション(例えば行にオプションのlast_seen:タイムスタンプを追加、メッセージフィールドのエスケープ文法を切り替え)はベースライン内部の懸念だ;外部の.rigor.yml対応ツールが追跡しなければならない設定スキーマバンプを強制すべきでない。 -
ジェネレータセマンティクスがよりクリーン。
rigor baseline generateはすべての行が意味を持つファイルを書く — 「これは技術的には有効な設定だが、ほとんどのスロットはデフォルト」という混乱がない。生成されたファイルを開くレビュアーはignoreルールとそれ以外何も見ない。 -
ドリフト / プルーンツールがスキーマを所有する。
rigor baseline drift(スライス2)はignore形エントリーを探して設定ツリーを歩く必要がない —version: 1ファイルを読んで単一の懸念について推論する。 -
キー名の競合がない。(B)では、
.rigor.ymlのbaseline: <path>が専用ファイルをクリーンに参照する。(A)では、同じbaseline:キーがファイルごとのignored:配列と衝突し、あまり発見しやすくない名前変更(例えばbaseline_path:/include_baseline_at:)を強制する。 -
既存のサーフェスはすでに分離されている。
.rigor.ymlの安定した形はこのADRより前に存在していた;高チャーンのignored:キーを折り込むと、プロジェクトがそれ以外の点では具体的なタスクごとのファイル(.rigor.dist.yml/.rigor-baseline.yml/ 将来のトピックごとの設定)に向かって絞り込んでいる正にその瞬間に設定の責任スコープが拡大する。
(A)の利点は本物だが現在のミックスではより低い重み:
- 「スキーマのシンプルさ」は形式著者にとっては真だが、ユーザーはベースラインをほとんど手編集しない — regenerate / pruneサブコマンドがそれを所有する。よって「学ぶべき1つのスキーマ」の利益はrigor自身の貢献者に不均衡に着地し、外部ユーザー(v0.1.9 SKILLトリオのターゲットオーディエンス)ではない。
- 「インライン
ignored:」オプションは〜3つのignoreルールを持つプロジェクトにとって重要だが、それは十分にまれなので、それらのプロジェクトに小さな.rigor-baseline.ymlファイルを保持するよう求めるコストは無視できる。 - 「汎用設定ツールが動作する」— 真だが推測的;rigorはPHPStan(
phpstan/extension-installer等がneonパーシングに依存する)のような外部設定ツールエコシステムを持たない。そのようなエコシステムが成熟したとき、トレードオフは再検討できる。
WD9の再検討時期
Section titled “WD9の再検討時期”以下のいずれかが真になった場合、この決定は再争う価値がある:
- 複数の「トピック」設定ファイルが登場する(i18n固有のルールオーバーライドのための
.rigor-i18n.yml、プラグインのみ設定のための.rigor-plugins.ymlなど)。その時点でincludes:機構がロードベアリングなプリミティブになり、ベースラインをそこに折り込むコストが下がる。 - ルールごとのignoreErrors形式インライン設定が機能として着地する(例えば
.rigor.ymlサイドのdisabled:と並ぶignored:キー)。その時点でスキーマがいずれにせよ収束しマージがシンプルになる。 - 将来のSKILLまたは評価ツールが両方を同時に読む必要がある(
.rigor.yml+ ベースライン)し、2スキーマコストが分離の利益を上回る。
実装注: Baselineクラスは今日、設定インクルード形式を代替ロードパスとして受け入れるよう拡張できる(ヒューリスティック: version:フィールドが存在 → 専用;不在 + paths: / plugins:が存在 → 設定形)。WD9が再検討される場合にスライス5+としてキューする価値がある;現在のスライスのスコープ外。
CLIサーフェス
Section titled “CLIサーフェス”3つの新しいサブコマンド、すべて同じベースラインI/Oモジュールに支持される。
$ rigor baseline generate [--force] → Writes .rigor-baseline.yml from current `rigor check` results. Refuses (exits 1) if the file exists; --force overrides.
$ rigor baseline dump [--rule <rule>] [--file <glob>] → Read-only inspection. Shows the current baseline grouped by rule, file, or both. Supports `--format json` for tooling.
$ rigor baseline drift → Reports baseline-vs-actual deltas. Exits 0 even on drift; the user / agent decides whether to act.
$ rigor baseline prune → Drops baseline rows whose `actual.count == 0`. Confirms the rows interactively before writing (or `--force` to skip).
$ rigor baseline regenerate → Equivalent to `generate --force` after an `prune`. The common end-of-quality-improvement-session refresh.rigor check自体は--baseline=PATHフラグと--no-baselineオプトアウトを追加する。アクティブなベースラインパスの解決順(WD2(b)に従い — 明示的ロードのみ):
- CLIの
--no-baseline→ ベースラインはロードされない、.rigor.yml/.rigor.dist.ymlの内容に関わらず。 - CLIの
--baseline=PATH→ その特定のパスをロード。 .rigor.yml(または.rigor.dist.yml)がbaseline: PATHを持つ → そのパスをロード。baseline: falseは明示的無効化形式。- フラグも設定キーも設定されていない → ベースラインはロードされない(現在のデフォルト動作が保持される)。
ディスク上の.rigor-baseline.ymlの存在はトリガーではない。プロジェクトはrigor baseline generateでファイルをスキャフォールドし、バージョン管理し、それでも設定からbaseline:キーを省略することで意図的に抑制を休眠状態にできる。意図されたワークフローはrigor baseline generateがファイルと.rigor.dist.ymlへの一致するbaseline: .rigor-baseline.yml行の両方を書く(またはその行が欠けているときユーザーに警告する)ことだ;rigor-project-init SKILLは1ステップとしてこの配線を処理する。
SKILL: rigor-project-init
Section titled “SKILL: rigor-project-init”新しいプロジェクトをRigorにオンボーディングするためのエンドツーエンドのエージェントワークフロー。ユーザーが「このプロジェクトにRigorをセットアップして」「Xのためにrigorを設定して」と言うか、.rigor.ymlを持たないGemfileを持つディレクトリでrigorを実行し始めたときにトリガーされる。
フェーズアウトライン
Section titled “フェーズアウトライン”- プロジェクト形を検出する —
Gemfileを読んでフレームワークファミリーを検出する(Rails / Sinatra / dry-rb / 素のRuby / …);Gemfile.lockを読んでロックされたgemバージョンとrbs_collection.lock.yamlの存在 / 不在を検出する。 - プラグイン選択 — 検出されたスタックに一致するプラグインセットを提案する。デフォルト:
- Rails形プロジェクト →
rigor-actionpack、rigor-activerecord、rigor-actionmailer、rigor-rails-routes、rigor-rails-i18n、加えてGemfileに存在するDevise / Pundit / Sidekiq / Sorbetなどのgemごとのプラグイン。 - dry-rb形プロジェクト →
rigor-dry-types+rigor-dry-struct(存在すればschema / validationも)。 - RSpecテストスイート →
rigor-rspec。
- Rails形プロジェクト →
- 重要度プロファイル — 最初の実行で100エラーを超えるプロジェクトには
lenientを提案(「インクリメンタル採用」ユースケースに一致);それ以外にはbalancedを提案。strictプロファイルはCI最終ゲーティングのためにオプトインのまま。 .rigor.dist.ymlを書く(規約はdistファイルをコミット、オプションの.rigor.ymlローカルオーバーライド)と検出された設定。rigor triage --format jsonを実行して診断ストリームを診断する(ルール分布、ホットスポット、ヒューリスティックヒント) — ADR-23 WD5に従いSKILLは生のrigor checkストリームを自身でカウントするのではなくtriage JSONを消費する。.rigor-baseline.ymlを書く(rigor baseline generate経由)。そしてステップ4で書かれた.rigor.dist.ymlにbaseline: .rigor-baseline.ymlを追加する — WD2(b)に従いファイルの存在だけでは休眠状態;設定がそれを名指しする必要がある。SKILLは両方の編集を1ステップで行い、生成されたのにサイレントに何もしないベースラインをユーザーが抱える事態を防ぐ。抑制サマリーを表示: 「Nの診断がベースラインとして記録された;Mが後続の実行で表面化する」。- 本物のバグを表面化する: ベースラインでルールごとに診断をカウントする。インタラクティブに修正するには小さすぎる2〜3のルールを提案する(これらはRigorが捕捉した本物のバグである可能性が高い — 低いカウントの集中したルールはシステム的なパターン対局所的な問題を示すことが多い)。
採用モード — 実現されたフェーズの形
Section titled “採用モード — 実現されたフェーズの形”構築されたとおり(v0.1.9)、SKILLはフェーズ3 + 6を2つの独立したノブとしてではなく、ユーザーが最初に行う単一の採用モード選択としてフレーミングする:
- acknowledgeモード(容認モード、ベースライン採用)—
severity_profile: lenient(小プロジェクトにはbalanced)、フェーズ6が実行される: 今日の診断がベースラインにスナップショットされ、プロジェクトは括弧で括られたサイトのランタイム正確性をカバーするためにテスト / specスイートに頼る。静的なT | nilの読み取りは最悪ケースの健全;スイートは最悪ケースが当たらないという証拠だ(コンテキスト § 観察1)。 - strictモード(厳格モード、妥協なし)—
severity_profile: strict、フェーズ6はスキップされる: ベースラインなし、すべての診断がライブのまま、各々が修正されるか著者意図の理由とともに# rigor:disableで注釈される。
両モードはリグレッション保証を保つ — 新しい診断はどちらでも表面化する。初日に存在する診断の扱いだけが異なる。フェーズ3の>100エラーヒューリスティックは、どちらのモードをデフォルトにするかの推奨になる。
SKILLはまた、クイック修正でも正直なベースライン素材でもないクラスタ向けに2つのエスカレーションパスを表面化する: アプリケーション固有のメタプログラミング → プロジェクトプライベートプラグインを書く(rigor-plugin-authorに引き渡す);サポートされていない外部gem → rbs collection install / dependencies.source_inference: / Rigor issueを開く。
SKILLがユーザーにエスカレートする決定ポイント
Section titled “SKILLがユーザーにエスカレートする決定ポイント”- 「acknowledgeモードかstrictモードか?」 — 中心的な選択、設定が書かれる前に行われる(上記を参照)。
- 「このプロジェクトは場所によってHAMLを使用し他ではERBを使用している —
rigor-actionpackの拡張されたテンプレート拡張セットを有効にすべきか、それとも制限すべきか?」(P3形のトレードオフ。) - 「ベースラインが非常に大きい(>2,000エントリー)。まず
paths:からvendor//spec//test/を除外することを検討してください。」 - 「ロックされたgem X、Y、ZにRBSカバレッジがない;それらには
dependencies.source_inference:を検討してください。」
SKILL: rigor-baseline-reduce
Section titled “SKILL: rigor-baseline-reduce”日和見的な品質改善のためのエンドツーエンドのエージェントワークフロー。ユーザーが「rigorベースラインを削減して」/「いくつかのベースライン診断を修正して」/「次に何を修正すべき?」と言ったときにトリガーされる。
フェーズアウトライン
Section titled “フェーズアウトライン”.rigor-baseline.ymlを読む — ルールでグループ化し、昇順カウントでソートする(最小ルール先行 → 本物のバグや限定的なパターンの可能性が高い)。- 各ルールに対して(優先順に):
a. 影響を受けるファイルにフィルタリングして
rigor checkを実行;ユーザーがメッセージを見られるように実際の診断ストリームを表面化する。 b. 3〜5の異なるサイトをサンプリングし、ユーザーに各々を分類するよう求める: 「本物のバグ」/「スタイル的 / 安全」/「FP — Rigorはこれを捕捉すべき」。 c. 「本物のバグ」の場合: 修正を提案;適用を申し出る。 d. 「スタイル的 / 安全」の場合: サイトに# rigor:disable <rule>コメントを追加する(ファイルごとではなく行ごと — 可視性を保持);ベースラインカウントを減らす。 e. 「FP」の場合: ベースラインに残し、Rigor側のissueを開く / フラグを立てる(ルール自体がさらに絞り込まれるべき)。rigorリポジトリ内のこのSKILLの貢献者向けバリアントでは、「Rigor側のissueをフラグする」はspec/rigor/...下のリグレッションspecとdocs/notes/下のサーベイノートのドラフトを意味する。 - 各ルール処理後: 残差をリフレッシュするために
rigor baseline drift;ルールがファイルから完全にクリアされた場合はrigor baseline prune。 - 停止条件: ユーザーが停止を合図する;次のルールのカウントが設定可能なセッションバジェットを超える(デフォルト: 20コールサイト);セッションが設定可能なwall-timeバジェットに達する(デフォルト: 60分)。
SKILLがユーザーにエスカレートする決定ポイント
Section titled “SKILLがユーザーにエスカレートする決定ポイント”- 「このルールは14ファイルにわたって200サイトある — 系統的に見える。プラグイン / エンジン修正がバルクでクリアするかどうか調査するか、それとも特定のファイルを選んでそこで削減するか?」
- 「このファイルの診断形はファイルごとの
# rigor:disable-file形が行ごとより維持しやすいことを示唆している;切り替えるか?」 - 「診断メッセージがRigorバージョン間で変更された;ベースラインが一致しない。再生成するか、pruneしてから再生成するか?」
- 採用速度: メンテナーは5分でRigorをオンボーディングし、ベースライン以降に表示された診断のみを即座に見ることができる。レガシーノイズは括弧で括られ、ブロッキングではない。
- インクリメンタルな品質改善はメトリクス(ベースラインサイズ)を付けた認識されたワークフローを持つ。「このスプリントでベースラインを10%削減する」が追跡された目標になる。
- SKILLペアがワークフローをエージェント駆動可能にする。ユーザーはベースライン文法を知る必要がない;init SKILLがそれを書き、reduce SKILLがそれを歩く。
- 既存の抑制メカニズムが保持される。行ごとの
# rigor:disableは著者意図の最も細かい粒度のプリミティブ;ファイルごとの# rigor:disable-fileは懸念ブロックをカバー;severity_profileが再スタンプ;ベースラインが残りの「今日をスナップショット」残差を吸収する。
- プロジェクトルートにもう1つのYAMLファイル。規約は:
.rigor.yml、.rigor.dist.yml、.rigor-baseline.ymlのいずれか1つ。PHPStan / RuboCop / Sorbetは同等のフットプリントを持つ;これはRuby静的解析エコシステムで珍しくない。 - リファクタ下でのベースラインドリフトは合計カウントが等しいままの場合、新たに導入された問題を隠すことができる。(file、rule、count)デフォルト粒度はリファクタロバスト性のためのトレードだ;コールサイトごとの精度を望むユーザーはWD1に従って
--match-mode messageにオプトインし、rigorパッチリリースが文言を調整するときにベースラインを再生成するコストを払う。両モードはベースラインなしで同じseverity_profile: strictプラスstrict CIゲートに縮退する。 - CI統合は別の決定だ。このADRはexit-code契約(contract)(閾値超過ベースライン → 既存の
rigor checkセマンティクスに従い非ゼロexit)を超えたCI動作を指定しない。チームはドリフトでCIも失敗させるかどうかを選択する;それは.rigor.yml/ パイプラインの決定だ。
- 2つのSKILLは
.claude/skills/下の貢献者向けアーティファクトとして出荷する。v0.2.0にキューされた外部著者バリアント(docs/ROADMAP.md§「エージェントワークフロー / SKILL(コミット済み: v0.2.0)」に従い)は、rigorモノリポの外で自分のgem / プロジェクトチェックアウト内でRigorを実行するユーザーに対して同じワークフロー形をカバーする。 - 命名: このADRは一貫してbaselineを使用する。CLIサブコマンドファミリーは
rigor baseline {...}下に住む。
実装スライシング
Section titled “実装スライシング”直交着地のためにスライス分け;各スライスはそれ自体で出荷可能。
スライス1 — ベースラインファイルI/O + rigor baseline generate — LANDED (v0.1.7)
Section titled “スライス1 — ベースラインファイルI/O + rigor baseline generate — LANDED (v0.1.7)”- 新しい
Rigor::Analysis::Baseline値オブジェクト(frozen)。WD1形に従い.rigor-baseline.ymlをロード / 書く。ルールID行(デフォルト)とメッセージパターン行(オプトイン)の両方をサポート。メッセージパターン行はRubyRegexpソースを使用;ジェネレータのリテラルメッセージパスは書き前にRegexp.escapeを通す。 - 新しい
Rigor::CLI::BaselineCommandとgenerateサブコマンド。初期フラグセット:--match-mode {rule,message}(デフォルトrule)、--force。 rigor checkは--baseline=PATH/--no-baselineを獲得する。ベースラインがロードされると、既存のパイプライン後に診断をフィルタリングする(WD6に従い)。- stderrにサマリー行を追加(WD7)。
スライス2 — ドリフト検査(dump、drift、prune) — LANDED (v0.1.7)
Section titled “スライス2 — ドリフト検査(dump、drift、prune) — LANDED (v0.1.7)”rigor baseline dump— 読み取り専用の検査。rigor baseline drift— ベースライン対実際のデルタを計算。rigor baseline prune— ゼロカウントエントリーをドロップ。
スライス3 — rigor-project-init SKILL — LANDED (v0.1.9)
Section titled “スライス3 — rigor-project-init SKILL — LANDED (v0.1.9)”.claude/skills/rigor-project-init/SKILL.md(ルーター)。skills/rigor-project-init/SKILL.md(ルーター;agentskills.io形フロントマター;クロスリポジトリ参照の絶対GitHubURL)。skills/rigor-project-init/references/01-detect.md(Gemfile / Gemfile.lockウォーク;プラグインマッチング)。skills/rigor-project-init/references/02-configure.md(重要度プロファイル選択;.rigor.yml/.rigor.dist.ymlテンプレート;ベースラインパス宣言)。skills/rigor-project-init/references/03-baseline.md(ユーザーのプロジェクトに対してrigor checkを実行;ベースラインを生成;集中したルールを本物のバグの可能性として表面化)。- オーディエンス帰結: モノリポチェックアウトから
bundle exec exe/rigorではなく公開済みrigorバイナリ(Bundlerインストール)を呼び出す。公開CLIフラグと設定キーのみを参照。 - WD8に従いv0.1.9にコミット。
スライス4 — rigor-baseline-reduce SKILL — LANDED (v0.1.9)
Section titled “スライス4 — rigor-baseline-reduce SKILL — LANDED (v0.1.9)”skills/rigor-baseline-reduce/SKILL.md(ルーター;agentskills.io形)。skills/rigor-baseline-reduce/references/01-classify.md(ルールごとのウォークスルー;サンプルと分類プロトコル;本物のバグ / スタイル的 / FPトリアージ)。skills/rigor-baseline-reduce/references/02-fix-or-suppress.md(本物のバグの修正パターン;# rigor:disable配置の決定;FPをrigorに対するGitHub issueとしてエスカレーション — 外部ユーザーはrigorを拡張するためのspec/を持たない)。- オーディエンス帰結: スライス3と同じ — 外部ユーザーサーフェスのみ。
- WD8に従いv0.1.9にコミット。
スライス5 — regenerate + drift-as-warningモード — LANDED (v0.1.8 / v0.1.9)
Section titled “スライス5 — regenerate + drift-as-warningモード — LANDED (v0.1.8 / v0.1.9)”rigor baseline regenerate(破壊的書き直し)。--baseline-strictフラグで閾値超過またはドリフトを非ゼロexitにする(それを望むチームのためのstrict CIゲート)。
スライス6(このADRのスコープ外)— IDE / LSP統合
Section titled “スライス6(このADRのスコープ外)— IDE / LSP統合”ADR-19による言語サーバーはベースライン化された診断を新しい診断と異なる形で表面化できる(例えばガターにゴースト表示)。これはフォローアップ;このADRによってコミットされていない。
再評価トリガー
Section titled “再評価トリガー”このADRの設計は以下のいずれかが真になると再争われる:
- PHPStanスタイルの行精度ベースラインがコミュニティデフォルトになる(これまでmypy / Psalm / PHPStan / Sorbetのスナップショット形式にわたって(file、rule、count)形が保持されている;RuboCopの
--auto-gen-config形式がRubyエコシステムで勝てば、WD1を再検討する)。 - 複数のメンテナーがbaseline-drift-hides-bugインシデントを報告する。WD5を強制的にstrict-driftデフォルトに反転させる。
- 2つのSKILLがそのユースケースの>50%を外部gem著者から見る。コミットより早くv0.2.0外部著者バリアントを強制する。
- 別の抑制レイヤーが先にユースケースを吸収する — 例えば行ごとの
# rigor:disableがカウントとして「N件の発生」を受け入れるよう拡張される。ありそうにないが記録された。
- PHPStanのベースライン: https://phpstan.org/user-guide/baseline
- Sorbetの厳密性レベル + エスケープハッチ: https://sorbet.org/docs/static
- RuboCopの
--auto-gen-config: https://docs.rubocop.org/rubocop/configuration.html#automatically-generated-configuration - ADR-8 — このADRが上に構築する重要度プロファイル(前のレイヤー)。
docs/notes/20260519-oss-library-survey.md— 設計ニーズを駆動した5プロジェクトサーベイ。docs/notes/20260521-mastodon-v4.5-regression-sweep.md— Mastodonのv4.5.xラインにわたる16タグのベースラインドリフト掃引: 表面化した診断はリリースライン全体で0のままで、acknowledgeモードの「一度採用し、リグレッションのみを表面化する」契約を実証的に検証した。docs/ROADMAP.md§「エージェントワークフロー / SKILL(コミット済み: v0.2.0)」— このADRのSKILLが貢献するコンパニオン外部著者SKILLトラック。
© 2026 TypedDuck. Licensed under CC BY-SA 4.0.