コードベース全体のレビュー・監査。perf/sec/test/arch/cq/docs の6観点を並列委譲し、優先度付き issue ファイルと観点サマリをメモリディレクトリに生成する。コードベース全体の監査・定期レビュー・リリース前品質確認の依頼時、/codebase-review 実行時に使用。境界: PR 単位は pr-review、自ブランチの提出前確認は self-review。
writing-code
コードを書く時の実装原則(deep module 設計・seam・テスタビリティ・シーム限定 TDD・コメント規約・リーダブルコード)。新しい module・関数・エンドポイント・画面の追加、DB への書き込み、認可・入力検証の追加、既存構造を変えるリファクタを含む実装の開始時に使用。CSS・HTML・テンプレート等のフロントエンド資産も対象。既存関数内の数行修正と lint/CI 等の設定ファイルでは使わない。TypeScript/Python/Go の言語別ガイドを同梱。境界: バグ・エラーの原因調査は systematic-debugging が先、提出前の diff 確認は self-review。
インストール方法を見る含まれるファイル(4)
- SKILL.md23.7 KB
- references/go.md2.2 KB
- references/python.md2.1 KB
- references/typescript.md4.8 KB
SKILL.md(原文)
インストールする前に、エージェントに与えられる指示の中身を確認できます。
Writing Code
人間に読みやすいコードは、生成AIにとっても読み・変更しやすい。本スキルは、コードを書く時の設計判断を毎回同じプロセスに固定する。
バグ・エラーの修正では、先に /systematic-debugging スキルで根本原因を特定し、修正の実装段階で本スキルの原則に従う。
実装前ゲート: 既存パターンの確認と踏襲
コードを書き始める前に実行する:
- 触るものが同じ既存実装を洗い出して読む — 「同類」は機能の類似ではなく、触るものの同一性で選ぶ。変更が読み書きするもの(テーブル・レコード・セッションキー・URL空間・外部API・共有状態)と、担うUIの役割(フォームの結果表示 / トグル / モーダル / エラー・完了通知)を列挙し、それぞれについて既存の触っている箇所を検索ですべて挙げる。UIも機能も全く違っても、同じレコードを書くなら同類。UI役割が同じものからは、アクセシビリティ属性(
role/aria-*)と状態表示の作りを引き継ぐ - 各既存経路が持つガードを列挙して突き合わせる — 洗い出した各経路について、そこにあるガード(認可チェック / ロック / 入力検証 / 履歴記録 / 冪等性 / 削除ガード)を列挙し、自分の経路に要るか要らないかを1つずつ判断する
- 新たに依存する共通ラッパーは実装を読む — 自分が新しく呼ぶ共通ラッパー・ミドルウェア・フレームワークAPI(監査ログの計装、トランザクション境界、認可ガード、状態フック等)は、失敗時の挙動・副作用の実行順序・状態が反映されるタイミングを実装で確認する。「型が通る」「登録要件を満たした」は確認したことにならない
- 適切な理由がない限り踏襲する — 「シンプルだから」「今回は例外」は理由にならない。本スキルの設計原則と既存パターンが食い違う場合も、既存パターンを優先する
- 逸脱はユーザー確認を経てのみ — 既存パターンが明らかな技術的負債になると判断した場合に限り、負債と考える根拠を添えてユーザーに確認を取った上で(Claude Code: AskUserQuestion)逸脱してよい。無断の逸脱は禁止
なぜ「機能の類似」で選んではいけないか: 新機能は見た目が似た画面・似た名前のmoduleに引きずられて選ばれるが、データ整合性を守るガードは同じレコードを書く別機能の側にある。似た画面を1つ読んで「踏襲した」と判断すると、読んだファイルの中で印象に残った部分だけがコピーされ、そのファイルにある他のガードはコピーから漏れる。
完了基準: 「触るもの × 既存経路」の対応表が書けている(各経路のガードと、自分の経路での要否・不要な場合の理由まで埋まっている)。「既存実装を読んだ」「パスを挙げられる」は通過条件にしない — 読んだかどうかは検証できず、1件読めば自己申告で満たせてしまうため。同類が存在しない場合はその旨を記録する。逸脱する場合はユーザー確認済みである。
制約を追加するときの経路網羅
認可チェック・関門・レート制限・redirectガード・バリデーションを新しく置くときは、その制約が守る対象への到達経路を列挙してから実装する。最低限:
- URL直打ち — 途中の画面を飛ばして到達できるか。フレームワークの評価順(layoutとpage、middlewareとhandler)で制約が先に走るか
- クライアント側遷移 — soft navigationで制約が再評価されるか、初回描画時にしか評価されないか
- API直接呼び出し — 画面に置いた制約ではAPIの直接呼び出しを防げない。同じ判定がサーバー側にあるか
1経路にだけ置いた制約は未完成とみなす。 守らない経路があるなら、なぜ守らなくてよいかを書く。
実装前ゲートの2と対になる。ゲート2が「既存の制約 → 新しい経路」への転記漏れを防ぎ、本節が「新しい制約 → 既存の経路」への展開漏れを防ぐ。どちらも「制約 × 経路」の直積が埋まっていないことが原因で、ゲート2と本節のどちらか一方だけでは埋まらない。
検証境界の確定(過剰防御の抑制)
防御的コード(nullチェック・再バリデーション・フォールバック)は局所的には常に正当化できる(「nullかもしれない」)ため、迷い自体を追加の根拠にしない。書く前に保証元を確認する:
- 入力ごとに保証元を確認する — 変更が受け取る各入力について「どこで検証済みか」(型・上流のスキーマバリデーション・DB制約・フレームワーク保証)を特定する。保証済みの値には再ガードを書かない(parse, don't validate: 境界で検証して型に落とし、内部は型を信頼する)
- 保証が確認できないときは、足すのではなく前提を確認する — 確認せずにガードを足すと検証境界の位置が実装のたびに変わり、防御コードが増え続ける。境界をどこに置くかは既存方針・ユーザーへの確認事項として扱う
- 前節との関係 — 経路網羅は「制約を置くべき経路の漏れ」を防ぎ、本節は「保証済み領域への重複配置」を防ぐ。制約は境界に1箇所、内部はゼロが目標
レビュー指摘で防御追加を求められた場合も同じ基準で判断する(保証済みなら却下候補。類型は ~/.claude/context/code-review-checklist.md §16)。
設計語彙(Glossary)
設計判断の記述・議論では以下の用語を正確に使う。コード上の既存命名・フレームワーク公式用語(React component等)はそのまま尊重する(既存パターン踏襲と両立させる)。
- Module — interfaceとimplementationを持つすべて。スケール非依存: 関数・クラス・パッケージのどれでもよい。設計議論でunit / component / serviceと言い換えない
- Interface — 呼び出し側がmoduleを正しく使うために知るべき全て。型シグネチャに加え、不変条件・呼び出し順序の制約・エラーモード・必要な設定・性能特性を含む
- Seam(Feathers) — その場所を編集せずに振る舞いを差し替えられる場所。interfaceを置く位置。boundaryと言わない(DDDのbounded contextと衝突するため)
- Depth — interfaceにおけるleverage。呼び出し側が学ぶinterface量あたりに引き出せる振る舞いの量。実装行数とinterface行数の比ではない(水増しを誘発するため)
- Adapter — seamでinterfaceを満たす具体物。役割の名前であり、中身の大小は問わない
設計原則
- Deep moduleを設計する — 小さいinterfaceの背後に多くの振る舞いを置く。interfaceを設計する時に自問する: メソッド数を減らせるか / パラメータを単純化できるか / より多くの複雑さを内側に隠せるか
- The deletion test — そのmoduleを削除したと想像する。複雑さが消えるなら通過型(pass-through)であり不要。複雑さがN箇所の呼び出し側に再出現するなら、moduleは価値を生んでいる
- The interface is the test surface — 呼び出し側とテストは同じseamを通る。interfaceの内側をテストしたくなったら、moduleの形が間違っているサイン
- One adapter = hypothetical seam. Two adapters = real seam. — 実際に差し替わらないものにseam(port・抽象層)を作らない。adapterが1つしかないseamはただの間接参照であり、Speculative Generality(投機的一般化)
- 依存は生成せず受け取る(DI) — module内部で依存をnew・生成せず、引数で受け取る。時刻・乱数も依存として扱う
- 副作用より戻り値 — 引数を変異させたり外部状態を書き換えるより、結果を値として返す。テスト容易性と参照透過性が上がる
リーダブルコード原則
- 命名: 名前だけで役割が分かること。プロジェクトの用語集(CONTEXT.md等があれば)と一致させ、同じ概念には全体で同じ語を使う
- 命名は「それが何か」で付け、「どこから来たか・なぜ入ったか」で付けない: 識別子(モジュール・パッケージ・テーブル・変数・設定名)は現在のシステム内での役割・ドメイン概念から命名する。由来・経緯(移行元/連携先のシステム名・案件名・依頼者・新旧や暫定を示す相対語)はWhyであり、コミットログ・ADR・仕様書に置く。識別子に固定化すると、由来を知らない読み手への説明コストと、改名されないまま残る負債になる。公開契約のリソース名は「システムが外に見せている名前」なので機能名として使ってよい。迷ったら命名表を作りユーザー確認(Claude Code: AskUserQuestion)
- docstring: 既存形式(JSDoc / docstring)を踏襲。本スキルで書き方を重複定義しない
コメント配分の四象限: How=コード / What=テスト / Why=コミットログ / Why not=コードコメント
役割を混同しない。コードコメントはWhy not専用(自明な代替案をなぜ採らなかったか)。Why(変更した理由・背景)はコミットログの仕事、What(振る舞いの仕様)はテストコードの仕事、How(処理の流れ)はコード自体で表す。コードを読めば分かるWhat / Howをコメントに書くのは、二重メンテナンスの負債・コード変更時の陳腐化リスク・レビュー時のノイズを増やすだけ。削除するとレビュアーが混乱するかを自問し、混乱しないなら書かない。
本節は新規コード、および確立したコメント規約を持たないコードへのデフォルト方針。既存ファイルに確立されたコメント密度・慣習がある場合はそちらを優先する。
書かない例(Anti-pattern):
// ❌ 名前と重複(What)
// ユーザーを取得
const user = getUser(id);
// ❌ How をなぞる
// i を 1 増やす
i++;
// ❌ 明白な null チェック
// null チェック
if (user == null) return;
// ❌ 関数名と重複する docstring
/**
* getUser
* ユーザーを取得する
* @returns User
*/
function getUser(id: string): User { ... }
// ❌ タスク/PR 番号や依頼者を書く(履歴は git / PR にある)
// PR #123 で追加
// used by X
書く例(Why not):
// ✅ workaround の理由(外部要因)
// Cloudflare Workers の instanceof が bundler で false を返すことがあるため name 判定を併用
if (err instanceof DomainError || err.name === "DomainError") { ... }
// ✅ 見た目に反する動作の意図(実装ミス誤解の防止)
// 終了日は 23:59:59 まで含める(半開区間ではなく閉区間で扱う仕様)
const toEpoch = dateInputToEpoch(dateInput, 23, 59, 59);
// ✅ 隠れた不変条件
// page_revisions.has_source=1 ⇔ R2 オブジェクト存在(R2 先行書き込みで整合)
// ✅ 意図的な逸脱の理由(biome-ignore 系)
// pageId 変更を検知する目的(setter だけを呼ぶ effect)
// biome-ignore lint/correctness/useExhaustiveDependencies: pageId 変更を検知する目的
useEffect(() => setVotesPage(1), [pageId]);
// ✅ 有効な TODO(条件付き)
// TODO: OAuth 4.0 対応時にこの workaround を削除(issue #456)
判断規則:
- コメントを消してもコードだけで意図が分かる → 消す
- 「なぜこの変更をしたか」を残したい → commit message / PR説明 / ADRに書く(コード内に書くと内容が古くなる。Whyの置き場所はコミットログ)
- コメントが3行以上 → 「関数抽出 + 命名で表現できないか」を先に検討
- コメントが「なぜこの自明でない選択をしたか(Why not)」を説明していない → 書き換える or 消す
テスト規律(シーム限定TDD)
テスト基盤が存在するPJでプロダクションコードを書く時に適用する。適用外: テスト基盤が存在しないPJ / 設定・ドキュメントのみの変更 / UIの微調整。適用外で進めた場合は完了報告にその旨を明示する。
ループの規則
- Test at pre-agreed seams — テストを書く前に、テスト対象のseamを列挙する。実装計画(30_plan.md等)にseamが列挙済みならそれを合意とみなし自律実行してよい。計画外の公開interface新設・変更が必要になった時のみユーザーに確認する(Claude Code: AskUserQuestion)
- Red before green — 失敗するテストを先に書き、それを通す最小限のコードだけを書く。将来のテストを先取りした投機的実装をしない
- Vertical slices — 1テスト→1実装→繰り返し。各テストは前のサイクルの学びに応答するtracer bullet。全テストの先書き(horizontal slicing)は想像上の振る舞いを固定するため行わない
- リファクタはループの外 — red→greenのサイクル内でリファクタしない。green後・レビュー段階の責務として分離する
テストの質
- テストはpublic interfaceを通して振る舞いを検証する。内部実装が全て変わってもテストは変わらないのが良いテスト。良いテストは仕様書のように読める(「user can checkout with valid cart」)
- トートロジー禁止 — 期待値をコードと同じ方法で再計算しない(
expect(add(a, b)).toBe(a + b)は構造的に必ず通る)。期待値は独立した真実源から取る: 既知の正しいリテラル・手計算した例・仕様書 - Mockは外部境界のみ — 外部API・DB(テストDBを優先)・時刻/乱数・ファイルシステム。自分のmodule・内部コラボレータ・自分が制御するものはmockしない。内部をmockしたテストは、振る舞いが変わっていないリファクタで失敗する
Pure関数の回帰テスト固定
Parser / diff / tokenizer / formatter / 日付境界ヘルパー等、入力から出力が決定論的に定まるpure関数を新設・変更する時は、以下を必ず整備する:
- 境界値の網羅 —
0/ 空文字 / 空配列 /null/undefined/MAX_SAFE_INTEGER/ 極端に長い文字列。日付はtimezone境界 / DST / 閏年。数値はinteger overflow / floating point誤差 - CJKとASCIIの両方 — Tokenize / diff / word-wrap等は必ず両方をテスト(片方だけだと日本語圏で失敗するバグを見逃す)
- バグ修正時の回帰固定 — 修正したバグの bug-triggering inputを回帰テストとして残す(後日同じロジック変更で再発した時に検出する)
- Property-based testingの判断 — Invariantを明示できるロジック(
mapToDsl(dslToMap(x)) === xのround-trip、sort(sort(arr)) === sort(arr)のidempotency、可換性、逆演算成立)はfast-check等を優先。個別ケース列挙より状態空間の網羅性が上がる
完了基準に追加: 新設・変更したpure関数について境界値網羅とbug-triggering inputの回帰テストが揃っている。
Falsy check(言語横断のよくある罠)
- ID / count / revision番号は
0を有効値として持ちうる。!!id/if (id)は0を無効値として扱う != null(nullとundefinedのみfalse) をdefaultにする- 「truthyチェックかnullチェックか」を意識しない実装が本番バグの典型的な原因。lint rule化を検討する
部分更新の undefined
optionalフィールドを更新処理に渡すとき、undefined が「変更しない」なのか「削除する」なのかを、呼び出し側と保存側の両方で確認する。保存側が全列を書く実装(渡されなかった列を null で埋める)なら、optionalをそのまま渡すと意図しない削除になる。
型は両者を区別しない(どちらも T | undefined)ため、シグネチャを見ても判別できない。保存側の実装を読むまで結論を出さない。
不可逆な状態遷移とコミット後の副作用
一度実行するとユーザーが自力で戻せない状態遷移(確定・昇格・削除・フラグの片道更新)を書くときは、コミットの後ろに並ぶ副作用(外部API呼び出し・通知・キュー投入・別テーブルへの追記)を列挙し、それぞれが失敗したときにユーザーが次に何をできるかを決めてから実装する。
- コミット済みの遷移に対して「失敗を投げて終わる」は選べない。呼び出し側には失敗に見えるのに状態は進んでおり、再試行は「もう遷移済み」ガードで拒否される。想定内の失敗(期限切れ・既存行との衝突)は救済経路を用意し、想定外はログに記録して完了として扱う
- 同じ行を触る他経路がロックを取っているかを先に確認する。取っているなら自分も取る(一方の経路だけがロックを取ると、他経路のロックも防御として機能しない)。ロック取得後は、判定に使う前提を読み直してから進める
破壊的操作の参照者
delete / disable / revoke / 無効化 / 全件上書き を書くときは、その対象を今参照している他者を列挙する: 別セッション・別ユーザー・共有済みのURLやトークン・キャッシュ・外部システム。
「今の画面から見て不要」は削除の理由にならない。発行済みのものを無効化する処理は、発行先が自分とは限らない。
アンチパターン(実装中の自己検知)
書いている最中に以下の兆候を検知したら手を止めて設計を見直す。いずれも判断材料でありハード違反ではない。PJの明文規約を常に優先する。
| Smell | 兆候 | 対処 |
|---|---|---|
| Duplicated Code | 同じロジックが2箇所以上 | 共通化を検討(3箇所目で必須検討) |
| Duplicated State | サーバ/永続層が持つ状態を、クライアントのstateや操作の戻り値にも保持して表示に使う | 真実源を一方に決める。複製するなら、サーバ側だけが変わる経路(他ユーザーの更新・別タブ・再検証・複合操作の部分的失敗)を列挙し、追従を設計する |
| Feature Envy | 他moduleのデータばかり触るメソッド | ロジックをデータ側へ移す |
| Primitive Obsession | ドメイン概念を裸のstring/intで表現 | 専用型・値オブジェクトへ |
| Data Clumps | 同じ引数群がいつも一緒に移動 | まとめて型にする |
| Shotgun Surgery | 1つの変更が多数ファイルに飛散 | 変更が集まるようmoduleを再配置(localityの欠如) |
| Speculative Generality | 「いつか使うかも」の抽象層・引数 | 削除(two adapters ruleで判定) |
| Redundant Guard | 型・上流・DB制約で保証済みの値への再チェック | 保証元を確認し、あるなら書かない。無いなら境界(入口)に1箇所置く |
| Message Chains | a.b().c().d() の連鎖 | 深いinterfaceで隠蔽 |
言語別ガイド(Read when)
新規module作成・公開interface変更・テスト設計を伴う実装の開始時に、対象言語のガイドをReadする。既存コードの数行修正では読まない。
- TypeScript: references/typescript.md
- Python: references/python.md
- Go: references/go.md
完了基準
実装を完了とする前に、以下をすべて確認する:
- 実装前ゲートを通過している(「触るもの × 既存経路」の対応表が埋まっており、各経路のガードの要否を判断済み。既存パターンからの逸脱がある場合はユーザー確認済み)
- 新しく置いた制約について、守る対象への到達経路(URL直打ち / クライアント側遷移 / API直接呼び出し)を数え上げた
- 永続化される副作用を2つ以上持つ処理について、途中で失敗したときに呼び出し側へ返す状態が実態と一致するか確認した(原子的でないなら、状態は返さず再取得させる)
- 部分更新に渡したoptionalフィールドについて、保存側が
undefinedを「変更しない」と扱うか「削除する」と扱うかを確認した - 不可逆な状態遷移について、コミット後に走る副作用を列挙し、各失敗時にユーザーが復帰できる形になっている(throwして終わる形にしていない)ことを確認した
- 破壊的操作(削除・無効化・全件上書き)について、対象を参照している他者を列挙した
- 新設した各ガード(nullチェック・バリデーション・フォールバック)について、上流に保証元が無いこと(またはそこが検証境界であること)を確認した
- 変更した各moduleの振る舞いがinterface経由でテストされている(テスト規律の適用外条件に該当する場合は、完了報告にその旨を明示した)
- 新規に公開した各module(export)にdeletion testを自問した(削除して複雑さが消えるだけの通過型を公開していない)
- 新設したseam・抽象層にadapterが2つ以上ある。1つしかないものは削除した
- 設計判断の記述・コミットメッセージの用語がGlossaryと一致している
- PJ規定の品質チェック(lint / format / typecheck / test)が通っている
- 新設・変更したpure関数について境界値網羅 + CJK/ASCII両方 + bug-triggering inputの回帰テストが揃っている
- falsy check(
!!idで0を無効値として扱っていないか、!= nullを使っているか)を全diffで確認した
出典: obra/superpowersおよびmattpocock/skills(いずれもMIT License)を翻案。詳細はリポジトリルートのNOTICE.mdを参照。
レビュー
まだレビューはありません。使ってみた感想をお寄せください。
同じリポジトリのスキル
概要と使いどころ
変更をコミットする。/commit 実行時、「コミットして」「pushして」等の依頼時に使用。--push 引数または「pushして」の依頼で push も行う。
PR を Draft で作成する。PR テンプレートを全セクション埋め、対象 repo の既存 PR の分量に合わせて書きすぎを削る。PR 作成の依頼時、実装が一段落して PR 化する時、/create-draft-pr 実行時に使用(gh pr create を直接実行しない)。引数でベースブランチを指定できる。境界: 個別レビューコメントへの対応は pr-comment。
スキルを新規作成する。「スキルを作って」「この手順をスキル化して」等の依頼時、/create-skill 実行時に使用。AGENTS.md・context・既存スキルと整合させ、重複・競合を避ける。境界: 既存スキル・指示ファイルの修正は update-inst、指示ファイル全体の監査は instructions-audit。
抽象的な要件・事業側の要求を深掘りし、既存実装との整合を確認して実装マスタとシステム要件書を作成する。「こういう機能を作りたい」「この要求を満たす機能を設計して」等の抽象要件の提示時、既存機能の拡張や Phase 分割の要件定義開始時、/design-feature 実行時に使用。境界: 要件確定後の実装計画書・PR 分割と実装進行は plan-feature-prs。
画面の設計判断(ナビゲーション・重ね方・通知と空状態と読み込み・外枠とトークンと状態表現)の型を決定表で選び、アプリ内の全画面で揃える。画面・ページ・レイアウト・ナビゲーション・ダイアログ・コンポーネントを新しく作る・作り直す時、モックやダッシュボードを作る時、サイドバー・タブ・モーダル・シート・toast・バナー・空状態のどれを使うか決める時に使用。PJ に components.json があれば shadcn の部品とトークンへの対応も扱う。主要なデザインシステム(Material・HIG・Carbon・Primer・Atlassian・Fluent・GOV.UK・NN/g・WAI-ARIA APG・WCAG)の一致点を規則にし、食い違いの採否を定めてある。境界: 画面の文言は writing-ui-text、画面に何を出すかと提示前の完了基準は context/ui-artifact-standards.md、コードの実装原則は writing-code、shadcn 部品の API と組み立ては shadcn。