Plugin boilerplate reduction — phased plan
Status: Plan, 2026-06-02. 20260601-plugin-mechanism-pre-1.0-review.md §1のボイラープレートの所見に対する実装ロードマップ。非規範的です。公開サーフェスへの追加(新しいRigor::Source/Plugin::Baseヘルパー)は、着地するたびにADR-2/ADR-37に記録され、spec/rigor/public_api_drift_spec.rbにピン留めされます。
指針となる原則
Section titled “指針となる原則”ボイラープレートが存在する理由を取り除く。単にDRYにするのではない。最大の単一重複 — 約25個のプラグインにある手作りのAST走査器 — は、diagnostics_for_fileが生のrootを渡し、作者にそれを自分で走査せよと指示するという理由だけで存在します。ADR-37のNodeRuleはエンジンに走査を所有させるので、それらの走査器は理由ごと消えます。したがって、巧妙な共有walkヘルパーを手で作り込むのは無駄な作業になります。
そこで作業は単一のテストで分けられます。その重複はADR-37を生き延びるか?
- 生き延びる → 今すぐ抽出する(後悔なし)。これらは即座に報われ、インターフェース分離リファクタリングに触れられません。
- ADR-37に吸収される → ヘルパーを手で作り込まない。リファクタリングに取り除かせる。
インベントリの分割(レビュー §1.1のカウント)
Section titled “インベントリの分割(レビュー §1.1のカウント)”| Duplication | Count | Resolution | Survives ADR-37? |
|---|---|---|---|
| Literal Symbol/String extraction | 20 + 4 in core | Rigor::Source::Literals | ✅ extract now |
Diagnostic construction (start_column + 1) | 23 | Diagnostic.from_node / Base#diagnostic | ✅ extract now |
| levenshtein / did-you-mean | 4 | Base#suggest (DidYouMean::SpellChecker) | ✅ extract now |
config.fetch + DEFAULT_* | 17 | config_schema default: slot | ✅ extract now |
| Inflector copies | several | Plugin::Inflector | ✅ extract now |
Discoverer skeleton + NameKeyedIndex + load-error rescue + cache idiom | 4 + 6 + 10 | ClassDiscoverer / NameKeyedIndex base (FactProvider side, orthogonal to NodeRule) | ✅ mid |
| AST walker | 25 | NodeRule (engine-owned walk) | ❌ do not hand-build |
| dispatch-loop duplication / fan-out | 2 | ADR-37 slice 2 (indexed registry) | ❌ absorbed |
カウントが最も多い2項目(走査器25、ディスパッチ2)は意図的に手でパッチしません — ADR-37がそれらを取り除きます。
フェーズ(依存順)
Section titled “フェーズ(依存順)”Phase 0 — 後悔のない抽出
Section titled “Phase 0 — 後悔のない抽出”それぞれが、ADR-37を生き延びる重複を畳み込み、かつコア自身のコピーを脱重複するので、プラグイン境界の両側で報われます。
- 0a
Rigor::Source::Literals—symbol_or_string(node)、symbol(node)、symbol_arguments(call)、symbol_arg(call, index)。4つのコアのコピー(sig_gen/observation_collector、sig_gen/generator、analysis/dependency_source_inference/return_type_heuristic、inference/synthetic_method_scanner)をまず純粋なリファクタリングとして畳み込み、その後プラグイン向けに公開します。 - 0b
Diagnostic.from_node(node, path:, message:, severity:, rule:)+Plugin::Base#diagnostic(node, …)— 要となるstart_column + 1の慣習を内部化します。analysis/check_rules.rbのインラインパターン(15箇所以上)を畳み込み、プラグインにビルダーを与えます。 - 0c
Plugin::Base#suggest(name, candidates)—DidYouMean::SpellCheckerのラッパー。手作りのlevenshteinコピーを退役させます。 - 0d
config_schemadefault:スロット —Manifestスキーマの変更。Base#configが宣言されたデフォルトをマージするので、DEFAULT_*定数イディオムが退役します。 - 0e
Rigor::Plugin::Inflector— 1つの語形変化モジュール。routes(×2)、activerecord、actionmailer/actionpackのunderscoreコピーを退役させます。 ADR-39による再構成:singularize/pluralizeのコピーは偽陽性に敏感(FP-sensitive)です(ルートヘルパー/モデル名解決に供給されるため)。そのため近似を統一するのではなく、このモジュールは許可リスト(allow-list)+rescueハーネスを通して実際のActiveSupport::Inflectorを呼び出し、config/initializers/inflections.rbを静的に解析してプロジェクト独自のルールを取り込みます。純粋なunderscoreのケース変換は、ADR-39の作業から独立して着地できる安全な部分集合です。
Phase 1 — ブリッジ(最小投資)
Section titled “Phase 1 — ブリッジ(最小投資)”既存のRigor::Source::NodeWalkerをプラグインサーフェスに公開+文書化します。require1つ+文書化のみ — プラグインがNodeRuleへ移行するにつれて自然に退役するので、ここにそれ以上の投資はしません。
Phase 2 — 発見の基底(FactProvider側、NodeRuleと直交)
Section titled “Phase 2 — 発見の基底(FactProvider側、NodeRuleと直交)”ClassDiscoverer基底+NameKeyedIndex+discover_ruby_files(ruby_files_under+read_safely+rescue三つ組を畳み込む)+標準のglob_descriptorキャッシュイディオム。4つのRails発見プラグイン(activejob/actioncable/activestorage/actionmailer)を移行し、ほぼ同一のAST走査+インデックスコードのファイル約4個を削除します。
Phase 3 — ADR-37に吸収される
Section titled “Phase 3 — ADR-37に吸収される”NodeRuleの着地が、25個の走査器、23箇所のインラインDiagnostic構築(今やBase#diagnostic経由)、そして複製されたディスパッチループを取り除きます。ProtocolContractChecker基底(hanami/webのそっくりな重複)をここに追加します。
移行の機構(安全性)
Section titled “移行の機構(安全性)”- 各抽出は純粋なリファクタリングであり、挙動を保存します。安全網は、ゴールデンマスターとして使う既存の
run_plugin統合スペックと、すべてのスライスでグリーンのままのmake verify(テスト並列+リント+セルフチェック)です。 - PRごとに1つのプラグインファミリーを移行します。各PRは「重複N → 1、テストは不変」を実証します。
- レビュー §1.1の重複カウントを前後で追跡し、進捗を定量的にします。
- 公開サーフェスへの追加は
spec/rigor/public_api_drift_spec.rbのスナップショットを更新し、ADR-2/ADR-37に記録されます。
Phase 0a(Rigor::Source::Literals)。最高のROI: 4つのコアのコピーと20個のプラグインを脱重複し、ADR-37を無傷で生き延び、リグレッションリスク最小の純粋なリファクタリングです。
© 2026 TypedDuck. Licensed under CC BY-SA 4.0.