- Sonnet 4.6
- How I Use Claude Code | Boris Tane を参考にちょっとアレンジ
- 不具合をしらみつぶしに探し、分析した結果を research.md に出力させる
- resaerch.md の結果とコードを照らし合わせて修正計画を立て plan.md に出力させる
- plan.md を人間がレビューする
- plan.md にそって実装させる
- 実装後、コードレビューエージェントでレビュー実施、 review.md に出力させる
- review.md に基づき fix_plan.md を出力させる
- fix_plan.md を人間がレビューする
- fix_plan.md に沿って実装させる
- 途中生成した *.md を対象を分析し、得られた洞察を review.md に出力させる
- review.md をもとにソフトウェアエンジニアリングの観点で開発プロセスの改善点を next_action.md に出力する
next_action.md の内容は以下の通り
根拠: retrospective.md の因果分析
対象: research → plan → 実装 の各フェーズ
retrospective.md が明らかにした最重要の構造は次の 2 点である。
- バグの 1/3(4件)はレビューまで発見されなかった。そのうち 2 件(H1・H2)は research と plan の段階で埋め込まれたものであり、実装は計画の誤りを忠実に再現しただけだった。
- 「発見できたはずの機会」が 2 回あった。Bug 2 発見時に同一パターンを全コードパスで検索していれば H1 は防げた。Fix 7 計画時にリソース管理の構造まで記述していれば H2 は防げた。
したがって改善の優先順位は「実装の精度向上」ではなく「research と plan の質的強化」にある。
問題: Bug 2(result-clicked の emitSelectionUpdate 漏れ)を発見したとき、result-double-clicked という対称コードパスの検索が行われなかった。
原因の構造: バグ発見は「症状から原因へ」の逆向き推論であり、発見した問題の「鏡像」が別の場所に存在することを見落としやすい。
改善アクション: バグを記述する際に「このバグパターンが他のコードパスにも存在しないか」の検索を必須ステップにする。具体的には、research.md に各バグの根本原因を書いた直後に、以下を記録する:
パターン検索結果:
検索クエリ: notifyResultClicked / notifyResultDoubleClicked
発見箇所: result-clicked (Bug 2 対象), result-double-clicked (問題なし or 問題あり)
判断: result-double-clicked にも同様の漏れあり → Bug X として追記
ソフトウェアエンジニアリングの根拠: これは Boundary Value Analysis の変形である。「問題箇所の境界(clicked vs double-clicked, show vs hide)を系統的に列挙し、各境界で同じ不変条件が成立しているか」を確認する。単一の問題発見で満足するのは、テストで1つの境界値のみを検証して終わることと同じ誤りである。
問題: 今回発見した 8 件のうち Bug 1・Bug 2・H1 はすべて「マルチウィンドウ分離による emitResults の責務」という同一の設計制約から生まれていた。しかし research.md はその関連を個別に発見しただけで、「この設計制約から何件の潜在バグが生まれるか」を上位から俯瞰していなかった。
改善アクション: 調査を開始する前に、対象コンポーネントの「設計制約から導出される不変条件リスト」を作成する。
例(今回のケースに当てはめると):
Snotra マルチウィンドウ制約から生まれる不変条件:
IC-1: main と results はシグナルを共有しない → 状態変更後は emitResults が必要
IC-2: listen() で登録したリスナーはライフサイクル終了時に解除が必要
IC-3: IPC コマンドは invoke_handler に登録されなければ到達しない
IC-4: platform スレッドへのコマンドは非同期送信のため処理順の保証がない
調査: 各 IC が破られているコードパスを全列挙
これにより「バグを見つけてから調査する」ではなく「制約から問いを立て、コードを検索する」という上向きのアプローチになる。
ソフトウェアエンジニアリングの根拠: FMEA(Failure Mode and Effects Analysis) の考え方。設計の制約・インターフェース境界・非同期処理の合流点などの「故障が起きやすい箇所の型」を事前に列挙し、各型について実装を精査する。
問題: Bug 1 は「emitResults を呼ばない」と「setFolderState(null) の順序誤りで effect が早期リターン」という 2 つの独立した失敗が重なっていた。research.md はこれを1つのバグとして記述したが、修正が両方の要因に対処しているかの確認が曖昧になりやすい。
改善アクション: Critical・High バグの記述において、症状を引き起こしている失敗要因を独立して箇条書きにする:
Bug 1 失敗要因:
F1: exitFolderExpansion が emitResults を呼ばない
F2: setFolderState(null) が setQuery より後にある(effect の早期 return)
修正チェック: F1・F2 の両方が解消されているか → plan で確認
問題: Fix 2 の仕様変更(ホバー不要・クリックのみ)を受けて result-clicked に変更を加えたとき、result-double-clicked への影響判断を「変更なし(ハイライト更新不要)」と誤って結論づけた。この判断が plan.md に書き込まれたことで、実装フェーズは「計画通り」に誤りを実現した。
改善アクション:
ある Fix がコードパス X を変更するとき、計画書に以下のセクションを追記する:
対称コードパス確認:
変更対象: result-clicked
対称ペア: result-double-clicked
判断: 同様に emitSelectionUpdate が必要 → Fix 2 のスコープに含める
または
判断: ダブルクリックは起動後にウィンドウを閉じるため不要 → 理由を明記して除外
「変更なし」という判断の記録は、「対称性チェックを実施した上で除外した」ことの証明として機能する。チェックなしの「変更なし」は曖昧な沈黙と区別がつかない。
対称ペアのカタログ(このコードベース向け):
| 変更コードパス | 確認すべき対称ペア |
|---|---|
result-clicked リスナー |
result-double-clicked リスナー |
show_main_and_emit() |
show_on_startup の手動表示コード |
enterFolderExpansion() |
exitFolderExpansion() |
setFolderFilter の effect |
query の effect |
indexing-complete リスナー |
results-updated リスナー |
問題: plan.md の「result-double-clicked は変更なし。activateSelected がアイテムを起動するためハイライト更新は不要。」という記述は、ファイル起動ケースしか考慮していなかった。フォルダ展開ケースでは refreshResults() が後続の emitResults を生成するため問題が見えにくく、ファイル起動ケースでの永続的なズレを見落とした。
改善アクション: 「変更なし」と判断するとき、その根拠を「全ケース列挙」で裏付ける:
result-double-clicked の変更不要判断の根拠:
ケース A: ファイル起動 → launchItem のみ、emitResults は発行されない
→ emitSelectionUpdate がなければ ResultsWindow の選択表示がズレたまま ← 問題あり
ケース B: フォルダ展開 → enterFolderExpansion → refreshResults → emitResults 発行
→ 直後に results-updated が来るため問題なし
結論: ケース A で問題があるため変更必要 → スコープに追加
この「全ケース列挙」は、Equivalence Partitioning の発想である。コードパスを「起動するもの(ファイル)」と「展開するもの(フォルダ)」に分けて、それぞれで不変条件が成立するかを確認する。
問題: plan.md の Fix 7 サンプルコードは if (listRef) ブロック内に onCleanup を配置していた。実装フェーズはこの配置を踏襲し、listen() とのバランスが崩れた(H2)。コードの配置が持つ意味がサンプルに記述されていなければ、実装者は他の合理的な選択肢を選ぶことができない。
改善アクション: リソース管理・非同期処理・ガード条件に関するサンプルコードには、配置の理由をコメントとして明示する:
onMount(() => {
// ResizeObserver: listRef に依存するため if (listRef) 内に配置
// クリーンアップも同じブロック内に置き、生成と破棄を近接させる
if (listRef) {
const ro = new ResizeObserver(...);
ro.observe(listRef);
onCleanup(() => ro.disconnect()); // ← ro と同一スコープに配置(理由: 対応関係を明確にする)
}
// listen: listRef の有無に関わらず登録するため if ブロックの外に置く
// onCleanup は listen() より前の同期部分に登録する(理由: await や .then() 後ではリアクティブコンテキストが失われる)
let unlisten: (() => void) | undefined;
onCleanup(() => unlisten?.()); // ← listen() の前に同期的に登録(重要)
void listen(...).then((fn) => { unlisten = fn; });
});ソフトウェアエンジニアリングの根拠: Communicating Intent(意図の伝達) の原則。コードの「何をするか」はコードが語る。しかし「なぜこの構造か」はコードが語れない。計画書のサンプルコードで「構造の理由」を欠落させると、実装者は合理的に見える別の構造を選ぶ(そして誤る)。
問題: show_main_and_emit() という関数を定義しながら show_on_startup コードパスでは使わず手書きしていた(M4)。関数を作ることと、その関数を既存コードに適用することは別の判断として分離されていた。
改善アクション: Fix で関数を新規定義・変更するとき、以下のステップを計画書に追記する:
show_main_and_emit の定義/変更にともなう既存コードパス検索:
検索クエリ: w.show(), w.set_focus(), main.show()
発見箇所:
- show_main_and_emit 本体 (対象)
- main.rs:342 show_on_startup ← この関数を使えるか判断する
判断: show_on_startup も show_main_and_emit に統一 → Fix M4 として追加
これは DRY 原則の能動的な適用である。「重複を見つけて除去する」だけでなく「関数を作ったとき、その関数で置き換えられる既存コードを探す」という前向きな検索。
問題: plan.md の Fix 7 は「unlisten 変数に保存する」という対症療法にとどまった。「どこで・どのような構造で呼ぶか」まで記述しなかったことで、実装時に onCleanup の配置が誤った(H2)。
改善アクション: リソースを生成するコードを計画書に書く際は、必ず破棄側も同時に記述する:
リソース管理計画: results-updated リスナー
生成: void listen<...>("results-updated", handler)
破棄: .then((fn) => { unlisten = fn }) で変数に保存
破棄の場所: onMount 内の同期部分で onCleanup(() => unlisten?.()) を登録
(理由: .then() 内では SolidJS リアクティブコンテキストが失われるため)
構造: ResizeObserver のクリーンアップとは独立した onCleanup として登録
(理由: ResizeObserver は listRef 依存、listen は非依存 → 結合すると非対称が生まれる)
問題: 実装フェーズは計画を精確に実行することを責務とするが、計画自体が誤りを含む場合に「計画通り」の実装が誤りを完成させる。
改善アクション: 計画書で「変更なし」「対象外」と明記されたコードパスについて、実装中に一度確認する。特に以下の条件に当てはまる場合は要注意:
- 変更対象コードパスに対称ペアが存在する(clicked/double-clicked, show/hide)
- 計画書の「変更なし」判断に具体的な根拠が書かれていない
- 変更する関数・変数が複数のコードパスから参照されている
これは「計画を疑う」ことではなく「計画の盲点を実装時の視点で補完する」ことである。
改善アクション: 実装完了後、以下の grep を実行する:
# 変更した関数・処理の「手書き重複」がないか確認
# 例: show_main_and_emit を変更・追加した後
grep -n "\.show()" src-tauri/src/main.rs
grep -n "\.set_focus()" src-tauri/src/main.rs
# 例: emitResults を変更した後
grep -n "emit.*results-updated" ui/src/stores/search.tsこれは変更の「適用漏れ」と「DRY 違反の残存」を同時にチェックする。
改善アクション: plan.md の各 Fix に記述された「不変条件」を、実装後に手動または自動で確認する。
例:
Fix 1 の不変条件: 「exitFolderExpansion() が返った直後、ResultsWindow の内容はフォルダ展開前と一致する」
実装後確認: フォルダ展開 → Escape → ResultsWindow が元の結果を表示するか(手動)
Fix 5 の不変条件: 「ホットキー登録成功を確認してから config.save() を呼ぶ」
実装後確認: cargo check + 処理順序を目視確認(自動)
今回のサイクルで、レビューは「実装が計画通りか」の確認にとどまらず 「計画そのものの正しさ」を検証する フェーズとして機能した。この価値を明示的に位置づける。
| 責務 | 内容 | 本サイクルでの例 |
|---|---|---|
| 実装確認 | Fix が計画通りに実装されたか | Fix 5・Fix 6 は正しく実装されていた |
| 計画検証 | 計画の判断が正しかったか | H1(計画の「変更なし」判断が誤り)、M3・M4(計画に記載がなかった問題の発見) |
「計画検証」の観点では特に以下を重点的に見る:
- 計画で「変更なし」とされたコードパスは正しく対象外か
- 計画で新規定義した関数が使えるのに使われていないコードパスがないか
- 計画で言及されたリソース管理の構造は意図通りに実装されたか
以上の改善をプロセスに定着させるために、CLAUDE.md の「開発ワークフロー」セクションへの追記案を示す。
2. 影響範囲(読む/触る/触らない)を列挙する
+ **対称コードパス確認**: 変更対象に対称ペア(clicked/double-clicked, show/hide 等)が
存在する場合、それぞれへの適用要否を明示的に判断して記録する
+ **関数使用箇所検索**: 新規定義・変更した関数が使える既存コードパスを grep で列挙し、
適用の要否を判断する
+ **同一パターン全コードパス検索**: バグを発見したとき、そのバグパターン(根本原因)を
コードベース全体で検索し、同一パターンの別箇所を列挙する
+ **変更なしの根拠明示**: 「変更なし」と判断するとき、全ケース列挙(Equivalence
Partitioning)で根拠を裏付ける
3. 事前調査(レビュー未然防止)を実施する
+ **リソース管理は生成/破棄ペアで計画する**: listen(), ResizeObserver など生成を計画する
際は、破棄の「場所・構造・理由」まで同時に記述する
+ **サンプルコードに構造の理由を付記する**: リソース管理・非同期処理の配置が問われる
コードは「なぜこの構造か」のコメントを入れるui/CLAUDE.md に以下を追加する:
## マルチウィンドウ通信の不変条件
main と results は別 WebviewWindow であり JavaScript コンテキストを共有しない。
以下の不変条件を守ること:
- `search.ts` の状態(results, selected)を変更したとき、ResultsWindow に
通知する必要がある場合は必ず `emitResults()` または `emitSelectionUpdate()` を呼ぶ
- `listen()` で登録したリスナーは必ず `onCleanup()` で後始末する
(特に: onCleanup は `listen()` の前、同期コンテキストで登録すること)
- 新しいイベントハンドラを追加するとき、対称ペアのハンドラも同様の処理が必要か確認する| ID | フェーズ | アクション | 効果 | コスト |
|---|---|---|---|---|
| R-1 | Research | 同一パターンの全コードパス検索 | H1 相当の見落とし防止 | 低 |
| P-1 | Plan | 対称コードパス一覧を必須化 | H1 相当の計画ミス防止 | 低 |
| P-3 | Plan | サンプルコードに構造の理由を付記 | H2 相当の実装誤配置防止 | 低 |
| P-4 | Plan | 関数追加時の既存使用箇所検索 | M4 相当の DRY 違反防止 | 低 |
| P-5 | Plan | リソース管理を生成/破棄ペアで記述 | H2 相当の構造的問題防止 | 中 |
| R-2 | Research | 設計制約から不変条件を先に導出 | Bug 1・2・H1 の同時発見 | 中 |
| P-2 | Plan | 変更なし判断に全ケース列挙を必須化 | H1 相当の判断ミス防止 | 中 |
| I-1 | 実装 | 計画の変更なし判断を再評価 | 計画ミスの最終防衛 | 低 |
| V-1 | レビュー | 計画検証と実装確認を分けて実施 | M3・M4 相当の新規発見 | 中 |
今回の 12 件のバグのうち、研究と計画の質的強化で防げたものは少なくとも 3 件(H1・H2・M4)である。実装の精度は「計画の精度の上限を超えられない」という構造上の制約があり、計画に誤りが埋め込まれれば実装はそれを忠実に完成させる。
改善の核心は 2 つのシフトである:
- 「バグを見つけてから調査する」→「制約から問いを立ててから調査する」(R-2)
- 「計画通りに実装する」→「計画の盲点を実装時に補完する」(I-1)
これら 2 つは互いを補完する。上流で制約から不変条件を網羅すれば計画の盲点が減り、下流で計画の対象外を再評価すれば計画の誤りが実装段階で止まる。