


ユーザーの作業を失わせず、承認前のコードを動かさないコマンドラインツールを作る
すでにユーザーがいるCLIを、AIに作らせる・直させる時に使う。フラグと設定ファイルの互換性を守り、作業を壊す前にエラーで止め、プロジェクトのフックは承認の後にだけ動かし、すべてのサブプロセスを記録し、ネットの結果より先に手元の結果を出す。
このAIルールを使うには
## ツールが育っても、設定ファイルとコマンドのフラグの互換性を守る
ツールに実際のユーザーがつくようになったら、きれいな設計と互換性のバランスを取る。再設計そのものは、ユーザーを困らせてよい理由にはならない。
### 外部への影響を伴う変更には必ず理由をつける
外部インターフェースを変更するには、単なる整理ではなく、ユーザーにとって具体的な改善が必要になる。黙って壊すのではなく、非推奨警告と移行パスを用意する方を選ぶ。
### 保護すべき範囲を明確にする
どのインターフェースが契約(設定ファイルの形式、コマンドラインのフラグや引数)で、どれが自由に変更してよいもの(内部API、ログの出力先、人が読む出力の書式)かを決める。前者は守り、後者は柔軟なままにしておくことで、リファクタリングのコストを低く保つ。
### サポート期間の境界を決める
サポートするランタイムやツールチェーンの最小バージョンを明示し、その場しのぎではなく予測可能なメンテナンス周期に沿って引き上げていく。
## 1つの物事には1つの言葉を使い、文書・ヘルプ・コメントでそろえる
概念ごとに用語を1つ決め、ドキュメント・ヘルプテキスト・コードコメントで同じ言葉を使い続ける。用語がぶれていくと、ユーザーとメンテナーの両方を静かに混乱させる。
### 用語集を書き残す
各語の意味と、避けるべき類義語を短くまとめたリストを保守する。「元のチェックアウトと2つ目のチェックアウト」「操作の出力先とその操作が実行されるワークスペース」のような、似て非なる概念は明示的に区別する。
### 重要な意味を持つ言葉を使い回さない
ある言葉がすでに1つの概念を指しているなら、それを別の概念にも使い回さない。1つの文の中では使い回しが自然に読めてしまう場合でも、2つ目の概念には別の言葉を選ぶ。
## 動きを変えたらヘルプも同じ変更で直し、ずれをテストで見つける
挙動を変更する時は、同じ編集の中でドキュメントの変更も必ず行う。判断基準はシンプルで、「ヘルプテキストは今もコードの実際の動作を正しく説明しているか」を問えばよい。
### 1つの事実につき正本は1つに保つ
自動生成されるドキュメントについては、正本(コードに埋め込まれたヘルプやコマンド定義)を編集してから、生成される側のミラーを再生成する。生成済みファイルを手で直接編集してはいけない。1つのディレクトリに生成ミラーと手書きページが混在している場合は、編集前にそのファイルがどちらの役割かを確認する。
### テストで同期を強制する
生成結果と正本を比較するドキュメント同期テストを用意すれば、ずれを機械的に検知できる。ヘルプテキストを編集すると生成されるスナップショットも変わるので、同じ変更の中でそれらを再生成しレビューし、テストをグリーンに保つ。
## 同意なしにユーザーのファイルを壊さず、エラーで止める
ユーザーデータは神聖なものとして扱う。作業内容を保ったまま失敗するコマンドは、黙って作業を破壊した上で「成功」したことにするコマンドより優れている。
### 黙って失うくらいなら失敗させる
未追跡のファイル、コミットしていない変更、その他のユーザーデータを破壊する可能性がある操作は、そのまま進めるのではなくエラーで止める。
### 破壊するには明示的な同意を必須にする
データの強制削除や上書きのような破壊的な操作は、ユーザーが明示的に求めた時だけ実行する。無関係な操作の副作用として削除や上書きを行ってはいけない。クリーンアップはユーザー自身が選んで実行する、独立したステップにする。
### 失敗する方の実装を選び、チェックと利用の間の隙間に注意する
同じ操作に2つの実装がある場合は、競合時に強行するものではなく、失敗する方を選ぶ。安全性チェックとそれが守る操作の実行との間に時間差がある場合は慎重になる。その間に状態が変わりうるため、クリーンアップの際に強行してはいけない。
## すべてのサブプロセスを、ログと時間を記録する1つのラッパーに通す
外部コマンドはすべて、プロセスAPIを直接呼び出すのではなく、単一の実行ラッパーを経由させる。経路を1つにまとめれば、一貫したデバッグログと計測が自動的に得られ、挙動を変更する場所も1か所で済む。
### ラッパーも出力元も1つに
トレースの書式を管理するコンポーネントは厳密に1つにし、そこだけが基盤となるログ呼び出しを行うようにする。こうすれば、サブプロセスはそのガードを通ってトレースされるか、まったく記録が残らないかのどちらかになる。
### 新しい起動箇所は明示的に組み込む
一部の起動形態はラッパーに収まらず、トレース記録を手動で組み立てる必要がある。起動の直前に開始し、成功時に完了させ、エラー時には失敗として記録する。このガードは「使用必須」な値にして、未解決のまま破棄されたらアサーションが発火するようにする。そうすれば、完了処理の書き忘れは静かに未トレースになるのではなく、テストが落ちる形で発覚する。デタッチされたバックグラウンドの子プロセスと対話的なヘルパーだけは、文書化された未トレースの例外とする。
### 継承された探索用の環境変数を消す
一部の環境変数は、子プロセスが自身の作業ディレクトリを参照するより前に解決されるため、継承された値がツール側で選んだディレクトリを黙って上書きしてしまうことがある。ユーザーのコマンドをツールが選んだディレクトリへ移して実行する起動箇所では、必ずこれらの変数をクリアする。一方、ユーザー自身のコンテキストで実行される子プロセスは、継承したコンテキストを絶対パス化した上でそのまま保持する。
## メッセージの文ではなく、終了コードやJSONの出力で判断する
人間向けのメッセージをパースするのではなく、終了コード・機械可読なステータス・構造化フォーマットを優先する。人間向けメッセージは、ロケール・バージョン・言い回しの変更で簡単に壊れる。
### 構造化されたチャンネルを選ぶ
標準の人間向け出力ではなく、porcelain形式やJSON出力、数値のステータスフラグを読む。コマンドが終了コードに意味を持たせている場合は、メッセージ文字列ではなくコードで分岐する。
### 避けられない脆さは明記しておく
構造化された代替手段が存在せず、テキストをパースせざるを得ない場合は、そのパース箇所にインラインで脆さを書き残す。次にそのコードを読む人が、それが安定した契約ではなく既知のリスクだと分かるようにする。
## 手元のデータで先に表示し、ネットの情報は後から流して出す
応答性はそれ自体が機能である。まずローカルのデータで描画し、時間のかかる詳細情報はその後から届くようにする。
### 最初の描画をネットワークで止めない
ネットワークに触れるのは、ユーザーのリクエストがそれを必要とする時だけにする。どのコマンドも、最初の出力はローカルのデータから描画し、ネットワーク由来の詳細情報は段階的にストリーミングすべきである。リモート呼び出しが返るまで最初の描画すらできないコマンドは失敗パターンであり、初回セットアップ時にはとりわけ深刻になる。シェルプロンプトのような同期的なホットパスは、段階的にであってもネットワークに一切触れてはいけない。
### リアルタイムのリモートデータはユーザーが区切る文脈で扱う
コマンドの出力はバッファリングせず、1行ずつストリーミングする。対話的なピッカーは、ネットワーク処理を置く場所として最も許容度が高い。即座に描画され、遅い呼び出しが返る前にユーザーが操作すれば、そのリクエストは単純に破棄されるからだ。実行完了までを待つコマンドは、すべての呼び出しが返るまで終了できない。だからこそ、リアルタイムのリモートデータはピッカーの中で段階的に取得する。
## ユーザーが割り込んだら、実行中のコマンドのループを全部止める
子プロセスがシグナル(割り込みや終了)によって死んだ場合、フォアグラウンドの実行経路にあるすべてのループは、次の反復へ進むのではなく必ず中断しなければならない。
### 構造化されたシグナルチャンネルを読む
シグナルに由来する終了は、構造化されたシグナルフィールドから検知する。終了コードが128以上かどうかを見たり、エラーテキストをパースしたりして検知してはいけない。標準化された「割り込みによる終了」結果を伝播させ、ループを抜ける。
### エラーを飲み込む分岐より前にチェックする
割り込みのチェックは、どんな失敗処理の方針よりも先に行う。そうすれば「警告して継続する」というポリシーが割り込みを飲み込んでしまうことがなくなる。子プロセスに対してループする新しいコードは、必ず反復ごとにこのチェックを行う。そうしないと、複数ステップの操作に対する1回の割り込みが、残りのステップをそのまま突き進んでしまう。
## プロジェクトで決めたフックやエイリアスは、承認の後にだけ動かす
プロジェクト定義のコマンド(ライフサイクルフック、エイリアス、プロジェクト設定から読み込まれる実行本体)は、ユーザーがたった今クローンしたかもしれないリポジトリに同梱された、任意のコードである。これらは、承認システムがそれを通した後にのみ実行する。
### そのゲートがセキュリティ境界のすべてである
そのゲートを通さずにプロジェクト定義のコマンドを実行するコードパスを、絶対に作らない。「この操作はすでに検証済みだから、そのフックも一緒に実行してよい」という近道は、プロジェクト設定に触れるすべてのコマンドをリモートコード実行に変えてしまう。リポジトリをクローンすることと、インターネットからパイプで流し込まれたスクリプトをフックが実行することの間に立っているのは、そのゲートだけである。
### 承認済みの集合は一度だけ、不変な形で選ぶ
判断を行う時点で、コマンド集合を不変な承認済みプランへとゲートし、実行者はその値だけを消費するようにする。再導出できる元データは一切保持させない。こうすれば、未承認のコマンドを再選択することは、レビューでのチェック漏れではなくコンパイルエラーになる。プロンプトを出せない文脈では、承認状態を読み取り専用で参照し、すでに承認済みの部分集合だけを実行する。
## カバレッジの検査が落ちている間は、ユーザーの許可なしにマージしない
カバレッジゲートが失敗している変更を、ユーザーの明示的な承認なしにマージしてはいけない。プラットフォーム上でそのゲートが「必須ではない」と表示されていても、実質的にはブロッキングであることに変わりはない。
### そのシグナルがどう報告されるかを把握する
カバレッジツールは、結果をコミットステータスではなくチェックランとして投稿することがあり、その場合は異なるAPI経由で読む必要がある。合成ステータスのクエリではチェックランを完全に見落とすことがあり、結果はジョブ完了から数分後に届くこともある。変更がグリーンだと結論づける前に、正しいAPIをポーリングする。
### ギャップは直す、見逃して通さない
カバレッジが失敗した場合は、原因を調査してギャップを埋める。テストを追加するか、使われていないコードを削除する。汎用パスへのフォールスルーで十分な特殊ケースのハンドラーも対象に含める。誤検知だと思う場合も、独断で判断せずマージ前にユーザーに確認する。CIパイプラインと同じフィーチャーフラグでカバレッジを実行し、「その機能はオフだった」という理由で失敗を片付けられないようにする。
## すでにある依存関係を使い、警告は黙らせずに直す
ユーティリティを自前で書く前に、既存の依存関係を確認する。パスの正規化、シェルエスケープ、色付け、テンプレート変数の検出といった用途では、実績あるライブラリの方が、エッジケースの扱いを誤りがちな場当たり的な再実装より優れている。
### 警告を上辺だけ取り繕わない
警告を一律に抑制して黙らせることは絶対にしない。デッドコードは削除するか、日付と理由を添えたマーカーを残し、具体的な今後の対応と結びつけておく。
### 複雑なシステムはその場でドキュメント化する
一見して分かりにくいシステム(状態機械、キャッシュされた状態、モジュール横断の協調処理、微妙な無効化ロジックなど)には、目的・重要な設計判断・契約・不変条件を網羅したモジュールレベルの仕様書を添え、常に最新に保つ。
### テスト用のコードをライブラリコードに混ぜない
ライブラリの型に、テスト専用の便利メソッドを追加しない。テストは実際のAPIを呼ぶか、独自のヘルパーを定義する。複数行のリテラルでは、空白を黙って取り除いてしまう行継続エスケープよりも、実際の改行を埋め込む方を選ぶ。
## エラーには何が失敗したかを付け、クラッシュに変えない
すべてのエラーに文脈を持たせる。想定される失敗状態(クリーンでないワークスペース、存在しないブランチ、不正な状態など)には明示的なビジネスロジックエラーを使い、I/Oや外部コマンドの失敗には文脈を包むラッピングを使う。
### 失敗を絶対に飲み込まない
エラー型を返す関数の中では、値が無い時にパニックする呼び出しやunwrap系の呼び出しを使って、回復可能な失敗をクラッシュに変えてはいけない。エラー演算子で伝播させるか、明示的なエラーを送出するか、エラーを返す。ラップされたエラーは、どの操作が失敗し、なぜ失敗したのかを読み手に伝えるものでなければならない。
## 古い設定のキーは黙って捨てず、新しい形へ移す
設定の互換性処理はすべて1つの層にまとめる。パーサーが読み込む前に、非推奨のパターンを正規の形式へ書き換える。古い設定キーを黙って捨ててはいけない。それは黙って挙動を変えることに等しいので、代わりに移行させる。
### 検知と移行は1つの述語で行う
非推奨パターンの書き換えと、変更内容の報告の両方を、1つの冪等な関数に担わせる。こうすれば検知と移行は同じ述語を共有し、ずれが生じない。検知は、同じ関数群を作業用コピーに適用して行う。そうすれば、実際にファイルを更新したら変わる場合にちょうど警告が出る。この等価性はテストで固定しておく。
### 各ルールのタイミングを分類する
ルールごとに、書き換えをいつ適用するかを決める。読み込みのたびに行うのか、明示的な更新時だけなのか、警告なしで黙って行うのか、それとも後のリリースで採用される保留中のデフォルトとして扱うのか。安全に書き換えられない値(不正な形式のものや、移行先のキーがすでに使われているもの)はそのまま手を付けず、パーサー自身の型エラーにメッセージを語らせる。
## アクセサの名前の頭で重さが分かるようにし、キャッシュの置き場所を決める
アクセサの名前に意図を込め、呼び出し側がプレフィックスだけでコストと失敗モードを予測できるようにする。
### プレフィックスに契約を語らせる
一貫した方式を採用する。名詞だけの名前は副作用なしで値を返しキャッシュしてもよいもの、`set`プレフィックスは状態を書き込みエラーになりうるもの、`require`プレフィックスは値が無い時にエラーになるもの、`fetch`プレフィックスはネットワークI/Oを行うもの、`load`プレフィックスはファイルI/Oを行うもの、とする。名詞だけの方が読みやすい場面では、冗長な`get`プレフィックスを避ける。
### キャッシュされた状態をどこに置くか決める
読み取り専用のキャッシュ値は、クローンしても共有される参照カウント方式のキャッシュを通じて共有する。層はコストと利用頻度で選ぶ。低コストで頻繁に使う値はメモリ上でget-or-createし、コストの高い値はディスクに永続化する。両方の性質を持つ値は、永続化されたバックエンドの手前にメモリ上のフロントを置く。何がキャッシュされていて何がされていないか、そして変更後にどうキャッシュを更新すべきかという不変条件を、ドキュメントに残す。
