Notes
TILにノートタイプを導入してテンプレートを分ける
TILを全て同じフォーマットで書くと、内容の性質に合わない構成になりがち。「バグ修正の話」と「ツール紹介」では読み手が期待する情報構造が違う。
ノートタイプ(insight, tried, automation, harness, architecture, bugfix)を定義し、タイプごとにテンプレートとスタイルガイドを分けた。
templates/
insight.md # 知見: コード例必須、Before/After推奨
tried.md # 試した: 良かった点・気になった点・向いている場面
automation.md # 自動化: Before(手動)→After(自動)の対比
harness.md # AI制御: 設計判断+プロンプト例
architecture.md # 設計: 構造図+トレードオフ
bugfix.md # バグ: 症状→原因→解決
- AIエージェントが抽出時にタイプを自動判定し、対応するテンプレートで生成する
- タイプごとのスタイルガイド(
writing-style.md)で「insightはコード例なしだと却下」のようなルールを管理する - フロントマターに
type: insightを記録して、後から集計や検索に使える
タイプを増やしすぎると分類に迷うので、6種類くらいが運用しやすい。新タイプの追加はフィードバックから「既存タイプに当てはまらないTILが繰り返し出る」場合のみにする。
AIエージェントが作るPRはdraftをデフォルトにする
AIエージェントにPR作成を任せるスキルで、gh pr create にデフォルトで --draft を付けるようにした。
エージェントが作ったPRがready状態で出てしまうと、レビュワーに通知が飛んで「まだ見なくていいのに」となる。人間が確認してからreadyにする方が安全。
# Before: readyで作成される
gh pr create --title "feat: add feature" --body "..."
# After: draftで作成される
gh pr create --draft --title "feat: add feature" --body "..."
ポイント:
- エージェントの出力は「人間のレビュー待ち」をデフォルトにしておくと安全
- readyにするのは人間の判断。エージェント側で勝手にreadyにしない
- スキルのテンプレートレベルで
--draftを埋め込んでおけば、毎回指示する必要がない
Issue起点のAIエージェント自動実装パイプラインを組む
Claude CodeやCodexにはworktree機能が組み込まれているが、「issueを読む→worktreeを作る→実装する→テストする→PRを出す」までを一気通貫で自動化するには、エージェントの上にオーケストレーション層が要る。
仕組み:
- issueのステータス(Ready/In Progress/In Review)を状態マシンとして管理する
- 各issueに対して独立したworktreeを作成し、そのworktree内でAIエージェントのセッションを起動する
- エージェントにissueのPlanとDoDを読ませ、実装→verify→自動修正のループを回す
- verifyが通ったらPRを作成し、issueをIn Reviewに遷移する
# 1 issue = 1 worktree = 1 PR を厳守
# issueごとにロックファイルで重複実行を防止
story-dev --story 41 # issue #41のworktreeで実装開始
story-dev --story 79 # 別ターミナルでissue #79を並行実装
やってみてどうだったか:
- エージェント組み込みのworktreeは「ブランチを切って作業する」単位。パイプライン全体の管理(issue選定、ステータス遷移、verify条件の判定)は自前で組む必要がある
- issueにPlan/DoD/verifyコマンドを書いておくと、エージェントが「何をもって完了か」を自律判断できる
- 並行実行時はgit common dir配下にissue単位のロックファイルを置いて衝突を防ぐ
- 人間のレビューがボトルネックになるので、2〜3本の並行が実用的な上限
ローカル認証バイパスでAIエージェントの動作確認を可能にする
クラウド認証(Cloudflare Access等)に依存するアプリでは、AIコーディングエージェントがローカル開発サーバーで認証画面をテストできない。認証トンネルの設定が必要で、エージェントには操作できない。
仕組み:
- 開発サーバー起動時にフラグ(環境変数やクエリパラメータ)を渡す
- 開発モードの場合のみ、固定のテストユーザーで認証をバイパスするミドルウェアを挿入する
- 本番コードには一切影響しない条件分岐にする
// 開発専用の認証バイパスミドルウェア
function devAuthMiddleware(req, res, next) {
if (process.env.NODE_ENV !== "development") return next();
// テスト用ユーザーをセッションに注入
req.user = { id: "dev-user", email: "dev@localhost" };
next();
}
やってみてどうだったか:
- エージェントが認証付きページのスクリーンショットテストを全ページ実行できるようになった
- テスト用アカウントのデータが存在しない場合もあるので、シードデータも合わせて用意する必要がある
- 本番環境への誤デプロイ防止として、
NODE_ENVチェックだけでなくビルド時にデッドコード除去される仕組みが望ましい
大規模スキーマ移行を6フェーズに分けてADRで管理する
複数モジュールが共有しているリソースの所有モデルを変更するとき、一気にやるとリスクが高い。ADR(Architecture Decision Record)で設計根拠を残しつつ、6フェーズに分けて段階的に移行するパターンがうまくいった。
- スキーマ追加 — 新しいカラムを追加するだけ。既存コードに影響なし
- 新規書き込みの切り替え — 新規レコードから新カラムに書き込む
- 既存データのバックフィル — 既存レコードに遡及的に値を付与
- 読み取りの切り替え — 新カラムベースのクエリに段階的に移行
- 旧カラムの参照停止 — 旧キーへの依存を完全に除去
- クリーンアップ — 旧カラム削除、不要コードの除去
各フェーズが独立してデプロイ・ロールバックできるのがポイント。途中で問題が起きても前のフェーズに戻せる。
ADRとissue管理を連携させると効果的:
- ADRからissueへの参照を張る — 「なぜこの順番か」「完了条件は何か」をトレースできる
- フェーズごとにissueを切る — 進捗が可視化され、レビュー単位も小さくなる
- 設計の根拠がADRに残る — 半年後に「なぜこうしたか」を追える
「書き込みを先に切り替え → バックフィル → 読み取りを切り替え」の順序が重要。逆にすると、バックフィル前の古いデータが新しいクエリで漏れる。
lefthookでシークレット漏洩と破壊的操作をpre-commitで防ぐ
Git hooksツールの lefthook を使って、コミット前にシークレット漏洩チェックと破壊的操作の検出を自動化した。
Before: シークレットの混入や rm -rf、DROP TABLE のような危険なコマンドがそのままコミットされうる状態。
After: lefthook の pre-commit フックで自動検出し、コミットをブロックする。
# lefthook.yml
pre-commit:
commands:
config-guard:
run: bash scripts/config-guard.sh
glob: "*.{env,yml,yaml,toml,json,sh}"
config-guard スクリプトでは以下をチェックする:
- シークレット検出 — ステージされたファイル内の
token=,secret=,password=などのパターンをgrepで検知 - 破壊的操作の検出 —
rm -rf /,DROP TABLE,git push --forceなど危険なコマンドパターン - 設定ファイルの妥当性 — 環境変数ファイルに本番値が混入していないか
lefthook を選んだ理由は、YAML一枚で設定が完結し、glob でファイルタイプごとにフックを分けられること。husky + lint-staged でも似たことはできるが、lefthook の方がポリグロット(多言語)プロジェクトでの設定がシンプル。
テストも用意する。fixture ファイル(意図的に違反を含むファイル)を置いて、スクリプトが正しく検出するかを検証する。CIでもこのテストを回すことで、ガード自体の品質を担保できる。
grepベースのカスタムlintでAIエージェントのアンチパターンを検出する
汎用lint(ESLint, oxlint等)だけではカバーしきれないプロジェクト固有のルールを、シェルスクリプト+grepで実装してCIに組み込む方法。AIエージェントが繰り返しやすいアンチパターン(レイヤー境界違反、秘密値のログ出力など)を自動検出できる。
やり方はシンプルで、grep -rn でパターンマッチし、違反があれば非ゼロで終了するスクリプトを書くだけ。
#!/bin/bash
# 例: domain層から外側レイヤーへのimportを禁止
violations=$(grep -rn "from ['\"].*/(application|presentation|infrastructure)/" src/domain/)
if [ -n "$violations" ]; then
echo "ERROR: domain層から外側レイヤーへのimport違反"
echo "$violations"
exit 1
fi
このアプローチの良いところ:
- 導入コストが低い — ESLintプラグインを書くより圧倒的に速い。grepとシェルスクリプトだけで完結する
- fixtureテストが簡単 — 違反するコードと正常なコードのサンプルファイルを置いて、スクリプトの検出結果を確認するだけ
- AIエージェントとの相性が良い — エラーメッセージにルールIDと修正指示を含めておくと、エージェントが自動修正しやすい
運用上気をつけるポイント:
- 誤検知率を計測する — 1スプリント(2週間)で誤検知率20%を超えたらパターンを見直す
- ルール対応表をドキュメント化する — ルールID・カテゴリ・実装状態を一覧にして、チームで共有する
- 既存CIに相乗りさせる —
package.jsonのlintスクリプトにカスタムlintを追加すれば、特別なCI設定は不要
招待承認のような複合操作はD1 batchでアトミックにする
プロジェクトへの招待→承認→メンバー追加のフローは、invitation更新とmember挿入の2クエリが必要になる。片方だけ成功すると「承認済みだがメンバーでない」という不整合が起きる。
Cloudflare D1の db.batch() を使えば、複数のprepared statementをトランザクション相当でまとめて実行できる。
const results = await db.batch([
db.prepare(
"UPDATE invitations SET status = 'accepted', accepted_by = ?1, accepted_at = ?2 WHERE id = ?3"
).bind(userId, now, invitationId),
db.prepare(
"INSERT INTO members (project_id, user_id, role) VALUES (?1, ?2, ?3)"
).bind(projectId, userId, role),
]);
ポイント:
- D1にはBEGIN/COMMITはないが、
batch()が同等の保証を提供する - 招待の重複チェック(既にpendingがあるか)はbatchの前にSELECTで行う
- 招待→承認→メンバー追加のような状態遷移を伴う複合操作は、batch対象の筆頭候補
