prevent-recurrence

ユーザーが繰り返し指摘している間違いを集め、確実に防げて、変更後のコードが保守しやすく追いやすい方法で、同じ間違いを起こせない repository に変更する skill。

SKILL.md

GitHub

Prevent Recurrence

この skill は明示的に呼び出された場合のみ使用する。

$prevent-recurrence
$prevent-recurrence <繰り返している指摘>
  • 引数なし: repository の履歴と現在の会話から、繰り返している間違いを探す。
  • 引数あり: 指定された指摘は、ユーザーが繰り返していると判断したものとして対象にする。同じ種類の間違いも探す。

目的

ユーザーが同じ指摘を繰り返さずに済むよう、同じ間違いを起こせない repository にする。

文書の規則は、読み飛ばされても何も失敗しない。 構造、型、lint、テストで、間違いを書けないか、書いたら失敗する状態にする。

変更する担当は、次のように振る舞う前提で考える。

  • 開いたファイルしか見ない
  • 近くにある例を真似する
  • 通る中で最も短い方法を選ぶ

1 つのファイルから見て正しい変更が、repository 全体でも正しくなる構造にする。

Workflow

  1. 間違いの種類を集める
  2. 種類ごとに防ぎ方を決める
  3. ユーザーと合意する
  4. 防ぎ方を加えて確かめる
  5. 規則と、それを守らせる仕組みの対応表を更新する

1. 間違いの種類を集める

次を読み、指摘された間違いを種類ごとにまとめる。

  • 現在の会話でユーザーが指摘したこと
  • 直近 7 日の commit と revert
  • 直近 7 日に merge された PR で、ユーザーが書いたレビュー指摘
  • repository 内のエージェント向け指示ファイル
  • 回避策を説明しているコメント。見つかった間違いと関わるファイルだけを調べる

ユーザーが調べる範囲を指定した場合は、それに従う。

引数で指定されたものを除き、同じ種類の間違いが 2 回以上起きているものを対象にする。 1 回だけのものは対象にしない。

指示ファイルにある対応表と、既存の検査も確認する。 すでに検査や構造で防いでいる種類は、対象にしない。

対象が 1 つもなければ、調べた範囲とともにそう報告して終える。

2. 防ぎ方を決める

防ぎ方には、次のものがある。

  • 構造でなくす
    • 状態の持ち主を 1 つにし、同じことをする方法を 1 つにする
    • 内部を公開せず、誤った import が失敗するようにする
    • 手で同期している一覧を、1 つの正本にまとめる
    • 真似されうる古い方法と、使われていないコードを消す
  • 型で書けなくする
  • lint や CI の検査で失敗させる
    • 検査のエラー文には、代わりに使うファイル、型、関数を書く
    • すでに多く使われている書き方なら、新しく増やす変更だけを失敗させる
  • 振る舞いをテストする
    • 振る舞いが壊れても通るテストは、直すか消す
    • 内部の実装を変えただけで失敗するテストも、直すか消す
  • 文書やエージェント向けの規則に書く

種類ごとに、次の基準で選ぶ。

  1. 確実に防げること。間違いを書けないか、書いたら失敗する状態にする
  2. 防いだあとのコードが保守しやすいこと
  3. 呼び出し元から、主要な判断、実行順序、副作用、結果、失敗時の扱いを追いやすいこと

変更の大きさだけを、防ぎ方を選ばない理由にしない。 既存コードの書き換えが多くても、変更後のほうが保守しやすいなら、その方法を選ぶ。 一方、防ぐための層や仕組みが処理を追いにくくするなら、より軽い方法で確実に防げないかを考える。

同じ意図の書き方が複数あって間違いが起きているなら、1 つに揃える。 意図が違う書き方を取り違えているなら、名前や型で違いが見えるようにする。

文書に書くのは、状況による判断が必要で、他の方法で確実に防げないものに限る。 文書の規則は、読み飛ばされても何も失敗しないため。

3. 合意する

変更する前に、種類ごとの防ぎ方を一覧で一度に示し、合意を得る。 各種類は次の形で書く。

間違い: 日付の文字列を各所で直接変換し、タイムゾーンを取り違えている
起きた場所: 直近 3 件の PR のレビュー指摘
- A: 構造。日付の変換を 1 つの関数にまとめて 30 か所を置き換え、新しい直接変換は lint で失敗させる
     → 書き方が 1 つに揃い、どれを真似るべきか迷わなくなる
- B: lint だけ。新しい直接変換だけを失敗させる → 既存コードは変わらないが、30 か所の古い書き方が残る
推奨: A。書き方を 1 つに揃えることを優先する。既存の 30 か所が、新しく書く人の真似る元になっているため
B にしない理由: 既存コードを変えずに済む利点より、古い書き方が真似され続ける影響のほうが大きいため
既存の違反: 30 か所。A では置き換える。B では検査の対象から外すため、例外としての承認が要る
変更するファイル: 日付を直接変換している 30 ファイル、lint の設定、指示ファイルの対応表
  • 間違いには、いつ、どこで起きたかの根拠を添える
  • 選択肢ごとに、選んだ結果として何が変わるかを 1 文で書く
  • 推奨には、何を優先してそれを選んだかと、根拠になった事実を書く
  • 推奨しない選択肢ごとに、その選択肢の利点を手放してよい理由を書く
  • 選ばない理由は、変更後の保守のしやすさや追いやすさで書く。変更が大きいことだけを理由にしない
  • 既存の違反が何か所あり、各案でどう扱うかを書く
  • 変更するファイルを、CI の設定と指示ファイルも含めて示す。同じ変更をする多数のファイルは、まとめて書いてよい

推奨の理由だけでは、他の選択肢を選ばずに何を手放したのかが分からない。 選ばない理由まで示し、ユーザーが自分の判断として説明できるようにする。

4. 加えて確かめる

起きた回数の多い種類から、1 種類ずつ順に加えて確かめる。 commit は、ユーザーに頼まれた場合だけ行う。

書き換えの途中で、意図して違う書き方をしている箇所や失敗するテストが見つかったら、自分で直さずにその種類の作業を止める。 どこまで書き換えたかと、元に戻すか続けるかの案を報告し、手順 3 から合意を取り直す。 途中まで書き換えた状態のまま、次の種類に進まない。

加えた検査が、実際に過去に起きた間違いで失敗することを確かめる。 過去の間違いを再現できない検査は、防げている根拠にならない。

過去の間違いは、過去の commit や一時的なファイルを検査して再現する。 作業ツリーのファイルに書き戻した場合は、確かめたあとに自分が加えた差分だけを取り消す。Git の破壊的な操作で戻さない。

過去の間違いで失敗しない検査は、加えない。 手順 2 に戻って防ぎ方を選び直し、手順 3 から合意を取り直す。 書き換えを済ませていた場合は、元に戻すか残すかを報告し、合意を得てから次に進む。 選び直せなければ、その種類は防げなかったものとして報告し、次の種類に進む。

ローカルと CI で同じコマンドが実行されるよう、CI の設定を揃える。 CI を動かすための push はしない。

検査の例外は、ユーザーに確認して決める。 既存の違反を検査の対象から外すことも、例外に含める。 例外を認める場合は、その行に理由、期限、ユーザーが示した承認者を書く。

5. 対応表を更新する

対応表は、repository 内のエージェント向け指示ファイルに置く。repository の外にある設定ファイルには書かない。 表には、型、lint、CI、テストのような検査で守らせている規則と、それを守らせる仕組みを並べる。

  • 検査で防いだ規則を、守らせる仕組みと一緒に加える
  • 構造で書けなくした間違いは、守らせる規則がないので表に加えない
  • 表にある規則の間違いが、構造の変更で書けなくなったら、表から消す
  • 守らせる仕組みがない規則で同じ間違いが起きたら、手順 2 の基準で防ぎ方を選び直す

完了時の報告

調べた範囲を書いたうえで、種類ごとに次を書く。

  • 間違いと、それが起きた根拠
  • 選んだ防ぎ方
  • 他の方法を選ばなかった理由
  • 過去の間違いで検査が失敗することを確かめた結果
  • 途中で止めた種類があれば、どこまで変更したか

Rules

  • 引数で指定されたものを除き、2 回以上起きた間違いだけを対象にする
  • 確実に防げる方法のうち、変更後のコードが保守しやすく追いやすいものを選ぶ
  • 変更の大きさだけを、防ぎ方を選ばない理由にしない
  • 文書の規則は、他の方法で確実に防げないものに限る
  • 変更する前に、防ぎ方の合意を得る
  • 加えた検査は、過去の間違いで失敗することを確かめる
  • 検査の例外は、ユーザーが決める