はじめに
AIに手伝ってもらえば、コードを書く作業はかなり速くなります。ただ、期待どおりに動く状態を保ち続けるには、別の工夫が必要でした。
私は、phpMyAdminを参考に、TypeScriptでDB管理ツール tsmyadmin を作っています。画面構成や機能を参考にした独自の実装で、phpMyAdminのPHPコードを直接移植したものではありません。
この記事では、その開発でよかったことと失敗したことを振り返ります。そのうえで、PHPからTypeScriptへの移行のように、元の動きを引き継ぐ開発にどう応用できるかをまとめます。
中心となるのは、一度得た教訓を、次の実装でも生かすための仕組みづくりです。Claude Codeのルールやフックを使った具体例も紹介します。
なぜ作ったか
「値がちゃんと保存されたか、サクッと確認したい」「一時的にタイトルだけ変えたい」「テーブルの定義をちょっと見たい」。
そんな小さな用事のたびに、自分にとって手軽に使えるDB管理ツールが意外と見つからないと感じていました。思い出したのが、昔ワンコインのレンタルサーバーに付いてきたphpMyAdminです。あれは地味に便利だったな、と。
そこに、PHPで作られたプロダクトをTypeScriptで作り直すと何が起きるのか、という個人的な興味も重なりました。そこで、phpMyAdminを参考にしながら、自分が使いたい機能を加えたツールを作ることにしました。
特に重視したのは、次の2つです。
- ローカル開発環境で動いているDBのDockerコンテナを自動で見つけ、そのまま選べること
- MySQLとPostgreSQLを同じ画面で扱えること
プロジェクトの概要
構成はBun workspacesを使ったモノレポです。
| 場所 | 役割・主な技術 |
|---|---|
apps/api |
API。Honoを使用 |
apps/web |
画面。Vite + React 19 + TanStackを使用 |
packages/shared |
共通のZodスキーマ |
packages/adapter |
MySQLとPostgreSQLの違いを吸収する層 |
画面はphpMyAdminにならった、サーバー → データベース → テーブルの3階層です。DDL(テーブルの作成や変更をするSQL)やアカウント操作は、生成したSQLをプレビューしてから実行する2段階にしています。
初日の2026年9月1日には、土台づくりからSQLコンソール、DDLのプレビューと実行までを実装しました。2026年9月27日の確認時点では、開発開始から1か月足らずで350コミットになっています。
同時点の CLAUDE.md に記載されているテストの規模は、次のとおりです。
- ユニット/API/Webテスト:916定義
- MySQL・PostgreSQL共通のconformanceテスト:195定義
- E2Eテスト:200定義
これらはテストコード中の定義数です。共通テストを両方のDBで実行したり、複数のブラウザで実行したりするため、実際の実行件数とは異なります。集計方法は検査スクリプトで管理しています。
はじめからやってよかったこと
1. MySQLとPostgreSQLを共通のテストで確かめた
MySQLとPostgreSQLでは、SQLの書き方や動きが少しずつ違います。この違いを、一般にSQLの方言と呼びます。
そこで、両者に共通する DatabaseAdapter インターフェースを用意しました。メソッドを追加したら、対応するテストも追加し、両方のDBで実行するルールにしています。これがconformanceテストです。
確認するのは、それぞれの実装が、インターフェースで決めた同じ振る舞いを満たしているかどうかです。片方のDBだけで確認して終わることを防ぎやすくなりました。
DBごとの差は、実際によく出てきます。利用者が操作する前に、テストの段階で気づけるようになったのは大きかったです。
この考え方は、言語移行にも応用できます。移行元と移行先を、同じ振る舞いを満たす2つの実装として扱う方法です。後半で詳しく紹介します。
2. 生成SQLは、プレビューしてから実行する形にした
DDLやアカウント操作では、サーバーが組み立てたSQLを一度ユーザーに見せてから実行します。パスワードを含む場合は、伏せ字にして表示します。
これにより、意図しないテーブルの変更やアカウント操作に、実行前に気づけるようにしました。
ただし、プレビューだけでSQLの正しさを保証できるわけではありません。表示と実行の内容が対応しているか、実際に実行できるかは、テストでも確かめる必要があります。後述するように、見た目では気づきにくいバグも起きました。
言語移行でも、AIが書いたコードを人が確認することと、動作をテストすることの両方が必要だと感じています。
3. 画面を増やすと、検査対象も増えるようにした
新しい画面を作るたびに、共通の検査を手作業で追加する運用では、書き忘れが起きます。
そこで、ルーティングの定義から画面を洗い出し、それぞれの初期表示に次の検査をかける仕組みにしました。
- axeによるアクセシビリティの自動検査
- 自前のレイアウト検査。矢印の重なり、入力欄とチェックボックスの高さのずれ、横スクロールなどを確認
ルーティングに画面を追加すれば、検査対象も増えます。
ただし、初期表示の検査だけでは、ダイアログを開いた後や、特定の条件で表示される要素までは確認できません。そうした状態は、別のテストで操作してから検査しています。
言語移行でも、対象の一覧と共通テストを結びつけておけば、画面やAPIが増えたときの確認漏れを減らせます。
4. 大事なルールを会話の外に残した
開発の大半は、Claude Codeとのペアプログラミングで進めています。会話が長くなったり、別のセッションに移ったりしても、同じ前提で作業を続けられるようにしたいと考えました。
そこで、SQLの組み立て方や、DDLを必ずプレビューすることなど、重要なルールを CLAUDE.md にまとめました。
CLAUDE.md は、プロジェクトの指示をClaude Codeに継続して伝えるためのファイルです。ただし、指示を置くことと、実行を強制することは別です。機械で確かめられるルールには、テストや検査スクリプトも用意しています。公式の説明
会話の中で何度も注意するより、ルールと確認方法を残すほうが、次の作業につなげやすくなりました。
失敗したこと
同時書き込みへの対策に、抜け穴が残っていた
2要素認証の設定を、読んで、変更して、書き戻す処理がありました。別のタブから同時に操作すると、片方の変更が消えるバグが起きました。
バージョン番号を使って競合を検出する仕組みを入れても、直しきれませんでした。削除・全消去の処理だけが、別の経路で無条件に書き込んでいたためです。
通常の更新処理を直しても、その経路を通らない処理に抜け穴が残っていました。
教訓は、同時更新への対策を、すべての書き込み経路に適用することです。
今回使ったCASは、読み取ったときのバージョンと現在のバージョンが一致する場合だけ、更新する方式です。比較と更新は、途中に別の書き込みが入らないよう、一体として行う必要があります。
通常の更新だけでなく、削除・全消去も確認することを、APIのルールに残しました。
プレビューでは正しく見えるのに、実行すると壊れた
MySQLのストアドプロシージャなどを実行するときは、本体に含まれるセミコロンで文を分割しないようにする必要があります。そのため、実行用のスクリプトでは、DELIMITERで区切り文字を切り替えていました。
ところが、本体の最後の行が行コメントで終わっていると、その後に付けた区切り文字までコメントに飲み込まれるバグがありました。その結果、文を正しく分割できず、実行時に構文エラーになります。
プレビューに表示されるSQL本体を読むだけでは、実行用スクリプトを組み立てる段階の不具合に気づけませんでした。
教訓は、生成したものを、実際に使う経路でテストすることです。
修正では、終端の区切り文字の前に改行を入れました。あわせて、本体が行コメントで終わるケースをテストに追加しています。修正記録
トランザクションの状態を、SQLから推測しようとした
SQLコンソールには、実行終了時に未終了のトランザクションが残っているかを判定する処理があります。
最初はSQL文を読み、コミットが起きるかを推測する方式で実装しました。ところが、取りこぼしと誤検知を繰り返しました。SQLの種類だけでなく、実行結果によっても状態が変わるからです。
最終的には、DBサーバーやドライバが返すトランザクション状態を使う方式に変更しました。修正記録
教訓は、状態を取得できるなら、推測する前にその情報を使うことです。
言語移行でも、コードを読むだけで元の動きを判断せず、実際に動かして確かめることが大切だと感じました。
メモに残したバグが、別の場所で再発した
これが、いちばん身にしみた失敗です。
見つけた不具合と再現手順は、レビュー役のエージェントが使うメモ(agent-memory)に詳しく残していました。ところが、当時の運用では、実装する側はそのメモを参照していませんでした。
その結果、一度直したはずの複合キーのバグが、別の場所で再発しました。複数の名前を文字列としてつなぎ、1つのキーにする処理です。
たとえば、2つの名前を . でつなぐとします。名前自体にも . を使える場合、a.b と c、a と b.c が、どちらも a.b.c になってしまいます。区切り文字を入れるだけでは、衝突を防げません。
記録は残っていても、次の実装に届いていませんでした。
教訓は、不具合の記録を、実装時のルールや回帰テストにつなぐことです。
再発防止のチェック自体が、役に立っていなかった
複合キーのバグを防ごうと、レビュー用のチェック項目に、正規表現を使ったgrepを追加しました。
後で実際に走らせると、過去に起きたバグを検出できませんでした。一方で、問題のないコードを60箇所以上も拾っていました。
チェックを追加したことに満足し、検出できるかを試していなかったのが原因です。
教訓は、新しいチェックにも動作確認が必要だということです。
狙ったバグを見つけられることと、正しいコードを誤って指摘しすぎないことの両方を、導入前に確かめるべきでした。
教訓を、次の実装に生かすために変えたこと
失敗を振り返って分かったのは、記録を増やすだけでは再発を防げないということでした。
そこで、何を伝えるかに加えて、いつ参照されるか、どう確認するかを整理しました。ここでは、tsmyadminで取り入れた方法を紹介します。
1. 共通ルールと、層ごとのルールを分ける
プロジェクト全体で守りたいことは CLAUDE.md に、特定の層だけに関係することは .claude/rules/ に置いています。
たとえば、SQLの組み立て方と、DBドライバを使ってよい場所は、全体のルールです。記事向けに表現を整理すると、次のようになります。
- 識別子は `quoteIdent` / `quoteTable` で処理し、
値は `Params` のプレースホルダ経由で渡す。
値を文字列補間でSQLへ直接埋め込まない。
- SQLを組み立てる処理は、指定したビルダーに集約する。
配置と書き方の検査: `bun run check:sql-safety`
- `mysql2` / `pg` のimportは `packages/adapter/src/**` に限定する。
依存関係の検査: `bun run check:arch`
ルールの横には、対応する検査コマンドを書いています。何を実行すれば確認できるかが、Claudeにも人にも分かるようにするためです。
コマンドを書くだけで自動実行されるわけではないので、フックやCIにも組み込んでいます。また、これらの検査ですべての不正なSQLを検出できるわけではありません。実際の動作はテストでも確かめます。
層ごとのルールには、対象となるファイルのパスを指定します。
---
paths:
- "packages/adapter/**"
---
paths: を付けたルールは、指定に一致するファイルをClaudeが読むときに読み込まれます。公式のパス別ルールの説明
この形なら、DBの方言に関する詳しい注意点を、画面だけを変更する作業で毎回読ませずに済みます。
2. 編集後の確認をフックで実行する
型チェックや関連テストは、Claudeに毎回お願いするだけでなく、フックから実行しています。
使っているのは、ツールの実行成功後に動く PostToolUse です。tsmyadminでは Write|Edit を対象にし、編集したファイルの場所に応じて検査内容を切り替えています。
たとえば、DBアダプターを編集したときは、型チェックと関連テストを実行します。次は、その検査部分を記事向けに整理した例です。
bunx tsc --noEmit -p packages/adapter &&
bunx vitest run --project adapter --reporter=dot
プロジェクトのルートで実行する想定です。型チェックが成功した場合だけテストへ進み、どちらかが失敗すれば、この一連のコマンドも失敗を返します。
ただし、PostToolUse はすべてのファイル変更を監視する仕組みではありません。Write|Edit を対象にした設定では、シェル経由の編集は対象外です。また、編集後に動くため、失敗した検査が編集を取り消すわけでもありません。公式のフック仕様
フックは、作業中に早く問題を見つけるために使っています。コミット時には変更に関係するテストを、プッシュ時にはDB不要の検査一式を実行し、CIでも確認します。
ほかにも、セッション開始時にCIの結果・型エラーの数・テストDBの起動状態を表示しています。実行前のフックと権限設定では、rm -rf や git reset --hard など、指定した危険なコマンドを止めるようにしています。実際の設定は .claude/settings.json にあります。
3. 実装・テスト・レビューの役割を分ける
エージェントには、それぞれ別の役割を持たせています。
| 担当 | 役割 |
|---|---|
| 実装担当 | 機能を実装する |
test-developer |
テストを書く |
code-reviewer-agent |
問題を確認し、修正せずに報告する |
レビュー役には、修正を行わず報告するよう明記しています。memory: project も設定し、発見した不具合や再現手順をメモに残す運用です。
テスト役には isolation: worktree を指定し、実装担当とは別の作業コピーでテストを書かせています。設定の詳細はサブエージェントの公式ドキュメントで確認できます。
人間のチームでも、書いた本人は自分の間違いに気づきにくいものです。役割を分けることで、実装時の判断を別の視点で見直す機会を作っています。
4. メモをルールやチェックに移す
記録の置き場所は、用途に応じて使い分けています。
| 置き場所 | 置くもの |
|---|---|
| agent-memory | 発見した不具合と再現手順 |
| スキル | 繰り返し使う手順やチェックリスト |
.claude/rules/ |
特定の層やファイルに関する注意点 |
CLAUDE.md |
プロジェクト全体の共通ルール |
| 検査スクリプト・テスト | 機械で判定できる条件 |
フックやCIは、検査スクリプトやテストを実行するタイミングを担います。
不具合の記録は、次の流れで見直すことにしました。
- 発見した不具合と再現手順をメモに残す
- 同じ間違いが再発したら、実装時に参照するルールやレビュー項目にも反映する
- 自動で判定できるものは、テストや検査スクリプトにする
- 元のメモに反映先を書き、対応状況を分かるようにする
再発を待ってからテストを書く、という意味ではありません。再発したら、修正だけでなく、知見の残し方にも問題がないかを見直す目安にしています。
実際にコミット履歴・CHANGELOG・メモ・ルールを照合すると、メモにだけ残っていた注意点が4件見つかりました。
| 注意点 | 反映先 |
|---|---|
| DELIMITERの終端が行コメントに飲み込まれる | .claude/rules/adapter.md |
| 削除・全消去がCASを通らずに書き込む |
.claude/rules/api-routes.md、/self-review の項目14 |
| 並列の統合テストで一時テーブル名が衝突する | .claude/rules/fixtures.md |
| 複数の名前を連結したキーが衝突する |
/self-review の項目15 |
複合キーのチェックでは、前述のgrepをやめました。代わりに、差分を読み、DB名・テーブル名・カラム名などをキーにするとき、異なる組み合わせが同じ文字列にならないかを確認する手順に変えています。
さらに、/self-review の項目17に、追加した回帰テストの確認手順を入れました。
修正前のコードでは失敗し、修正後のコードでは成功することを確かめる。
これにより、テストが通るだけでなく、対象のバグを検出できるかまで確認します。これらの手順は、実際のレビュースキルにまとめています。
次の言語移行で取り入れたいこと
ここまでは、tsmyadminでの経験と改善です。次に実際の言語移行を進めるなら、これらに加えて、移行元との比較を開発の中心に置きたいと考えています。
移行元と移行先に、同じテストを実行する
まず、移行後も保ちたい動きを、移行元で確認してテストに残します。
APIや画面を通して操作するテストなら、実装言語がPHPでもTypeScriptでも、同じ入力と結果を確認できます。MySQLとPostgreSQLに共通のテストを使った経験を、移行元と移行先の比較に応用する考え方です。
日時や自動採番など、そのままでは一致しない値は比較方法を決めておきます。また、意図して変更する仕様は、維持する仕様と区別します。
こうしておけば、AIが速く実装しても、テストで確認している範囲では、元の動きとのずれを早く見つけられます。
層ごとの判断を、その場で残す
移行中には、この処理を元の動きに合わせるか、移行を機に変更するか、といった判断が増えます。
全体に関わる判断は CLAUDE.md に、特定の層だけに関わる判断は .claude/rules/ に残します。理由も短く添えておけば、別のセッションで触ったときに、同じ判断をやり直さずに済みます。
実装時の確認項目には、移行元の対応箇所と、その動きを確かめるテストを含めます。
メモとルールの照合を定期的に行う
今回、4件の漏れを見つけた照合作業は手作業でした。次はスキルにまとめ、定期的に実行できるようにする予定です。
状態を推測する前に取得できる情報を探す、という教訓も、実装時に参照するルールへ反映したいと考えています。
新しく作るチェックは、過去に起きたバグに一度当て、検出できることと、誤検知が多すぎないことを確かめてから使います。
まとめ
AIと開発すると、コードを書く速さはすぐに実感できます。一方で、一度直した問題が別の場所で再発したり、メモに残した注意点が実装に届かなかったりしました。
今回学んだのは、教訓を残すだけでなく、次の作業で参照され、確認される形にすることの大切さです。
言語移行でも、次の3つを軸に進めたいと考えています。
- 元の動きをテストに残し、移行先でも確かめる
- 判断や注意点を、実装時に参照するルールへ反映する
- 自動で確認できるものは、フックやCIで検査する
AIに任せられる作業を増やすためにも、判断の理由を残し、繰り返し確認する作業を仕組みにしておく。その準備が、速さを継続的な開発につなげる土台になると感じています。