ADR 0002: ayame-editor の diff / sortdiff 受け入れアーキテクチャ¶
- ステータス: Accepted(2026-07-10)
- 関連 Issue: hjosugi/ayame-diff#4
- 実装 Issue: hjosugi/ayame-diff#5 #6 #7 #8 #9
- 移管元 Epic: hjosugi/ayame-editor#104
背景¶
ayame-editor(Rust)から diff 関連機能を本プロジェクト(Go・依存ゼロ方針)へ 移管する。移管対象と参照実装:
crates/ayame-cli/src/diff.rs(610 行)cmd_diff: bounded resync window 方式の行 diff。全行 LCS 行列を 持たず、アンカー行から前方window行だけ走査して再同期するため O(n)・メモリ有界で巨大ファイルに耐える。出力は unified(既定)/--side-by-side/--json/--summary。--max-hunks--max-lines--window--widthで制御。cmd_sortdiff: 両ファイルを外部ソートで UTF-8 一時ファイルへ書き出し、 同じdiff_documentsに通す。--key/-k--delim/-t--quote--numeric/-n--reverse/-r--csv--budget--spill-dirを持つ。- データモデル:
DiffResult/DiffHunk/DiffKind{Insert,Delete,Replace}。 serve/ops.rs:968-1134(/api/diff)、web/src/search.ts:539-741(diff ビュー) → GUI 側(#10 #11)で受ける。
決定¶
1. 移植方式: Go への再実装¶
Rust→Go の FFI やサブプロセス連携は採らず、Go で純粋に再実装する。
依存ゼロ方針(標準ライブラリのみ)と整合し、単一バイナリ配布・go install・
クロスコンパイルの容易さを維持する。参照実装のアルゴリズム(bounded resync
window)とデータモデル(Insert/Delete/Replace ハンク)をそのまま踏襲する。
2. CLI サーフェス: サブコマンド化(後方互換つき)¶
ayame-diff csv [flags] --left A --right B # 既存: CSV/TSV キー比較
ayame-diff text [flags] LEFT RIGHT # 新規(#5): 行 diff(resync window)
ayame-diff sorted [flags] LEFT RIGHT # 新規(#7): 外部ソート後に text diff
ayame-diff [flags] # 無印 = csv 互換(後方互換)
補足(2026-07-10): 対話式 TUI ウィザードは #25 で撤去済み。現状の無印・引数なし 起動は使い方を表示して終了する。サブコマンド実装(#5)で無印を csv に割り当てる。
安全なデフォルト + 上級者向け逃げ道(Sindre Sorhus 流):
- 既存ユーザーの
ayame-diff --left ... --right ...は 無印 = csv に ディスパッチして壊さない(サブコマンド実装後)。 - 新機能は明示的なサブコマンドの下に置き、無関係なフラグが混ざらないように
する(
--modeフラグ方式を採らない理由:モードごとに有効フラグが違うため、 サブコマンドで名前空間を分けた方がヘルプ・検証が明快になる)。 - 出力既定は unified(人間可読)。機械可読が要るときだけ
--json。
サブコマンド・ディスパッチャの実装は #5 で行う(本 ADR では方式のみ確定)。
将来の serve(#10)/ gui(#14)も同じ第 1 引数サブコマンドとして自然に増設できる。
3. 共用エンジンの範囲(小さく焦点の絞れた部品)¶
参照した部品分割方針(Sindre Sorhus: Small Focused Modules)に沿い、巨大な
単一クラスにせず境界を切る。既存 internal/engine(外部ソート・パーティション
基盤)を土台に:
| パッケージ(予定) | 責務 | 由来 |
|---|---|---|
internal/engine(既存 = fcsv) |
CSV/TSV パース・キー比較・外部ソート/パーティション | 現行 |
internal/linediff(新 #5) |
bounded resync window の行 diff・Hunk{Insert/Delete/Replace} |
diff.rs 移植 |
internal/diffout(新 #6) |
unified / side-by-side / JSON / summary の整形(linediff から分離) | diff.rs 移植 |
internal/worddiff(新 #8) |
Replace ハンク内の語単位 LCS ハイライト | search.ts 移植 |
sortedは新規に外部ソートを書かない。既存internal/engineの ソート/スピル基盤を再利用し、その出力をlinediffに渡す(cmd_sortdiffと同じ構図)。job-control(並列・バックプレッシャ・キャンセル)も engine 側の 既存機構を共用する。linediffは I/O とアルゴリズムを分離し、出力整形(diffout)に依存しない 純粋なコアに保つ(テスト容易性・GUI からの再利用のため)。
4. エンコーディング対応¶
参照実装は UTF-8 / Shift_JIS / EUC-JP / UTF-16 に対応するが、これは #9 に分離する。初期移植(#5〜#8)は UTF-8 前提で進め、非 UTF-8 は #9 で 別途受け入れる。
5. 依存ゼロ方針¶
維持する。 標準ライブラリのみ。例外を許容する基準を明文化しておく:
- 追加してよいのは、標準ライブラリに存在せず自前実装が現実的でない領域に
限る(例: 非 UTF-8 デコード =
golang.org/x/text/encodingは #9 で 可否を再検討、GUI の WebView など)。 - 例外を入れる場合は当該 Issue で「なぜ標準ライブラリで不可能か」を記録し、
THIRD_PARTY_NOTICES.mdを更新する。 - CLI コア(csv/text/sorted)は依存ゼロを死守する。
完了条件(本 ADR で満たすもの)¶
移植方式(Go 再実装)・CLI 設計(サブコマンド + 後方互換)・共用範囲 (engine 再利用、linediff/diffout/worddiff の分割)が確定し、実装 Issue (#5 行 diff / #6 出力 / #7 sortdiff / #8 単語 diff / #9 エンコーディング)が 着手可能になった。
却下した案¶
--mode=csv|text|sortedフラグ方式: モードごとに有効フラグ集合が 異なり、ヘルプと検証が複雑化するため却下(サブコマンドで名前空間分離)。- Rust バイナリの同梱/サブプロセス呼び出し: 単一バイナリ配布・依存ゼロ・ クロスコンパイルの利点を失うため却下。