markharness

0009: CLIをDomain/Application/Infrastructureへレイヤー分離する

ステータス

Accepted(Phase 1〜5は2026-08-18に実行済み。Phase 5ではKnowledgeSource、Git tree直読、再構築可能索引、Backfill処理量制限を導入した)。

背景

現在の実装(単一Rust crate)は機能別のフラットな.rsファイル群で構成されており、コード量が増えるにつれて以下の問題が具体的に確認されている。

ユーザーから提供された設計提案「markharness アーキテクチャ設計提案」(2026-08-18)は、これらをDomain/Application/Infrastructureへ分離するレイヤー構成を示した。レビューの結果、現状分析は実装と一致しており(上記の各行はコード確認済み)、13章「採用しない設計」の判断もCLAUDE.mdの「後方互換性のための設計を排除し常に最善を目指す」「デメリットに工数を含めない」という運用ルールと整合していた。一方で、提案のChangeAnalyzerインターフェースがdecisions/0008のロードマップと1点衝突していたため、本ADRで修正の上、採否を決定する。

決定

1. 5つのDomain Moduleを採用する

KnowledgeWorkspaceTestcaseCompilerChangeAnalyzerVerificationEngineBackfillCoordinatorをDomain層の中核とする。各Moduleが呼び出し側に見せるInterfaceと内部へ隠す処理の詳細は設計文書を正とする。

2. CLI → Application → Domain → Infrastructureの一方向依存を採用する

CommandOutcome型とPresenter traitを導入し、Domain層・Application層からprintln!/eprintln!/std::process::exitを排除する。人間向け出力とJSON出力は同じCommandOutcomeから生成する。

3. ChangeAnalyzerの版参照はMilestoneRef固定ではなくCommitRefベースに一般化する(元提案からの修正)

元提案はChangeAnalyzer::compute(from: MilestoneRef, to: MilestoneRef, options: ChangeOptions)としていたが、decisions/0008 Stage 2は「milestone-only UXを、PR base/headをfirst-classに追加した共通version rangeへ一般化する」ことを既に決定済みである。MilestoneRefに型を固定したままPhase4でChangeAnalyzerを確定させると、Stage 2着手時に中核Interfaceの再設計が必要になり手戻りが生じる。そのため本ADRでは以下を採用する。

enum CommitRef {
    Milestone(MilestoneId),  // タグ名。内部でgit tagをcommitへ解決する
    Commit(CommitId),        // 任意のcommit(PRのbase/head SHA等)
}

impl ChangeAnalyzer {
    fn compute(
        &self,
        from: CommitRef,
        to: CommitRef,
        options: ChangeOptions,
    ) -> Result<ChangeSet>;
}

既存のmarkharness changes computebackfill runCommitRef::Milestoneを使い続ける(挙動は変わらない)。decisions/0008 Stage 2で追加するPR Verification Plan機能は、同じChangeAnalyzerに対してCommitRef::Commitを渡すだけで済み、Interfaceの再設計を要しない。

4. GitRepository traitは今は導入せず、まずgit.rsへの集約を先行する(元提案からの修正)

元提案7.1はGitRepository trait定義を完全な形で先出ししていたが、これは提案自身の13章が掲げる「一つしか実装がない依存まで抽象化しない」という原則と整合しない。本ADRでは、Phase1でchanges.rs内の直接git呼び出しをgit.rsへ集約するところまでを決定する。trait化は、テスト用のfake実装が具体的に必要になった、または複数Adapterが要件化した、など明確な必要性が生じた段階の判断に委ね、現時点でtraitの型は確定しない。

5. KnowledgeSource traitを採用する

WorkingTreeKnowledgeSource/GitTreeKnowledgeSourceの2 Adapterが最初から明確なため、上記4とは異なりtrait化の妥当性がある。GitTreeKnowledgeSourceにより、historical_testcases_by_featuregit worktree add/removeをcommit配下のtree/blob直接読込に置き換え、backfillのスケールコストを下げる。

6. 生成物更新の原子性を採用する

generateによるTestCaseとtraceability indexの更新を、ディレクトリ全体でトランザクション化する(一時ディレクトリへ全生成→検証→generated/への切り替え)。途中失敗時は既存生成物を保持する。

7. 全生成を正準動作として維持し、増分生成は最適化として追加する

初期段階から増分生成のみで運用することは採用しない。増分生成を追加する場合も、CIでの定期的な全生成による検証を前提とする(13章の判断を維持)。

8. 段階的移行計画を採用する

Phase 内容
Phase 1 compute_changesのbool引数をChangeOptionsへ、changes.rs内の直接git呼び出しをgit.rsへ集約。既存動作とCLI契約をCharacterization Testで固定。ディレクトリ構成は変更しない。
Phase 2 CommandOutcome導入。CLIからstd::process::exitをPresenterへ移動。人間向け/JSON Presenterを分離。generatechanges computeverify pendingからApplication Use Caseを抽出。
Phase 3 TestCaseとtraceability indexの生成を一時領域で行い、成功後にgenerated/へ反映する原子性を追加。
Phase 4 KnowledgeSnapshot導入。TestcaseCompilerをファイルシステムから分離。VerificationのData LoaderとEngineを分離。ChangeAnalyzerは決定3のCommitRefベースで確定させる。
Phase 5 KnowledgeSource導入。GitTreeKnowledgeSourceでworktreeを置き換え。Feature/ChangeEvent/Executionの再構築可能な索引を追加。Backfillに--max-pairs/--time-budget等の処理量制限を追加。

詳細なInterface定義、Mermaid図、コード構成、テスト戦略はdomain-application-infrastructure-layering-design.mdを参照。

結果

検討したが採用しない選択肢