Notes
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対象の筆頭候補
issue作成スキルに種別自動判定を入れて適切なテンプレートを選ぶ
AIエージェントのissue作成スキルで、ユーザーの依頼内容からissueの種別(story / refactor / chore)を自動判定し、種別ごとに異なるヒアリングフローとテンプレートを適用するようにした。
仕組み:
- キーワードベースの自動判定 +
--typeフラグによる明示指定の2段構え - storyなら「ユーザーストーリー形式(As a … I want … So that …)」でヒアリング
- refactorなら「現状 → 目標 → 制約」の構造でヒアリング
- choreなら「何を・なぜ・影響範囲」のシンプルな構造
# 判定の優先順位
1. --type フラグが指定されていればそれを使う
2. キーワードマッチ:
- "リファクタ" / "refactor" / "整理" → refactor
- "依存更新" / "upgrade" / "CI" / "設定" → chore
- それ以外 → story(デフォルト)
やってみてどうだったか:
- refactorやchoreにユーザーストーリー形式を強制していたときより、自然なissueが作れるようになった
- キーワード判定はそこまで精度が要らない。間違えてもエージェントがヒアリング中に気づいて修正できるので、ざっくりでよい
エージェントのタスク実行をPlan→Approve→Execute→Verifyに分離する
AIエージェントにissueの実装を任せるとき、計画と実行を一気にやらせると意図と違う方向に進みがち。Plan→Approve→Execute→Verifyの4フェーズに分離した。
仕組み:
- Plan — issueを読み、実装計画(# Plan)と完了定義(# DoD)をissueに書き出す。コードは書かない
- Approve — 人間が計画をレビューして承認する。修正指示があればPlanに戻る
- Execute — 承認済みの計画に従ってコードを書く。実行開始時にPlanとDoDの存在をゲートでチェック
- Verify — DoDに基づいてセルフチェックし、PRを作成する
# 実行ゲートの擬似コード
plan=$(gh issue view $ISSUE --json body | jq -r '.body' | grep '# Plan')
dod=$(gh issue view $ISSUE --json body | jq -r '.body' | grep '# DoD')
if [ -z "$plan" ] || [ -z "$dod" ]; then
echo "Error: Plan and DoD must exist before execution"
exit 1
fi
やってみてどうだったか:
- 計画段階で「この方針でいいか」を確認できるので、大きな手戻りが減った
- issueに計画が残るので、後から「なぜこの実装にしたか」を追えるようになった
- ゲートはシンプルなテキスト存在チェックで十分。厳密なフォーマット検証は運用が重くなる
refs: https://nyosegawa.github.io/posts/harness-engineering-best-practices-2026
git hookスクリプトでstdinがブロックする問題をTTY判定で回避する
lefthookからシェルスクリプトを呼び出したとき、cat や read でstdinを読もうとするとスクリプトがハングする問題があった。Codex(OpenAI)に修正を任せたらサクッと直してくれた。
原因:
lefthookはスクリプトのstdinをパイプで接続する。ターミナルから直接実行した場合はstdinにユーザー入力が来るが、lefthook経由だとパイプの先に何もないので cat がEOFを待ち続けてブロックする。
解決:
# stdinがTTY(ターミナル)かパイプかを判定
if [ -t 0 ]; then
# ターミナルから直接実行 → stdinを読める
FILES=$(cat)
else
# パイプ経由(lefthookなど) → stdinを読まずにfallback
FILES=$(git diff --cached --name-only)
fi
調査の勘所:
[ -t 0 ]はファイルディスクリプタ0(stdin)がターミナルに接続されているかを判定するPOSIXの方法- git hookスクリプトはターミナル直接実行とhookマネージャ経由の両方で動くことを想定して、stdin以外のfallback(
git diff --cached等)を用意しておくとよい - この手のシェルスクリプトの環境依存バグは、AIエージェントに任せると原因特定から修正まで速い
AIエージェントの設定ファイル編集をhookでブロックする
AIエージェントにコード修正を任せると、linter設定やCI設定を勝手に緩めることがある。git hookでステージされたファイルをチェックし、保護対象ファイルへの変更をブロックする仕組みを入れた。
仕組み:
- pre-commitフックで、ステージされたファイル名を保護リストと照合する
- 完全一致(
biome.json)とサフィックス一致(lefthook.yml)の2パターンでマッチング - マッチしたら固定フォーマットのエラーメッセージを出して中断
# 保護対象の定義例
PROTECTED_EXACT=("biome.json" ".oxlintrc.json" "lefthook.yml")
PROTECTED_SUFFIX=("settings.json" "package.json")
for file in "$@"; do
for pattern in "${PROTECTED_EXACT[@]}"; do
[[ "$(basename "$file")" == "$pattern" ]] && block "$file"
done
for pattern in "${PROTECTED_SUFFIX[@]}"; do
[[ "$file" == *"$pattern" ]] && block "$file"
done
done
やってみてどうだったか:
- エージェントが「lintエラーを直すために設定を変更しよう」とする行動を確実にブロックできた
- 環境変数
CONFIG_GUARD_ALLOW=1でオーバーライドできるようにしておくと、意図的な設定変更時に困らない - lefthookから呼ばれるとstdinがパイプになるので、TTY判定を入れてstdinの読み取りをスキップする必要があった(次のTIL参照)
AI専用hookから標準git hookに移行する
AIエージェント向けに PreToolUse / PostToolUse / Stop といったベンダー固有のhookで品質ゲートを実装していたが、lefthook(標準的なgit hook管理ツール)に統合した。
Before(手動 / AI固有hook):
AIエージェントのライフサイクルイベントごとにスクリプトを設定していた。エージェント経由でしかチェックが走らず、手動コミットではゲートをすり抜ける。設定ファイルもAIツール固有の形式に依存。
After(lefthook):
# lefthook.yml
pre-commit:
commands:
config-guard:
run: bash scripts/config-guard.sh {staged_files}
format:
run: npx biome format --write {staged_files}
pre-push:
commands:
lint:
run: npm run lint
typecheck:
run: npm run typecheck
test:
run: npm run test
ポイント:
- git hookに統一することで、AIエージェント経由でも手動でも同じチェックが走る
- lefthookの
{staged_files}プレースホルダーで対象ファイルを自然に渡せる - pre-commitは高速なチェック(format, config guard)、pre-pushは重いチェック(lint, typecheck, test)と分けると開発体験がよい
- 移行時はスクリプト名からもベンダー固有の接頭辞(pretooluse-等)を除去して、汎用的な命名にしておく
エージェントワークフローの状態遷移にゲートを設ける
AIエージェントの自律ワークフロー(実装→レビュー→完了)で、各フェーズの完了条件を明示的にチェックしてから次に進む。
仕組みの概要:
- ワークフローの各状態遷移ポイントにゲートスクリプトを挟む
- ゲートは「このフェーズで達成すべき条件」をチェックリストとして検証する
- 条件未達なら遷移をブロックし、不足項目をエージェントに通知する
[実装] --gate--> [レビュー] --gate--> [完了]
| |
lint通過? テスト通過?
型チェック通過? レビュー承認?
テスト追加? CI緑?
設計判断のポイント:
- ゲートはフェーズの「出口」に置く — 入口ではなく出口でチェックすることで、フェーズ内の試行錯誤は自由にさせつつ、次に進む前に品質を担保する
- PostToolUseフックを活用する — ファイル書き込み後に自動でlint/formatを走らせ、品質ゲートの達成率を上げる
- 完了ゲートは厳しく、中間ゲートは緩く — 開発中は警告レベルで、最終完了時のみブロックにすると、エージェントの生産性と品質のバランスが取れる
ADRテンプレート + CIリントで軽量な設計記録を始める
Architecture Decision Records(ADR)を導入する際、テンプレートとCI上の自動リントをセットで入れると定着しやすい。
Before(手動):
設計判断の背景がPRコメントやSlackに散逸し、後から「なぜこの設計にしたか」を追えなかった。
After(自動):
docs/adr/
template.md # ADRテンプレート
0001-use-xxx.md # 個別のADR
scripts/
adr-lint.sh # フォーマットチェック
# CI workflow
jobs:
adr-lint:
steps:
- run: bash scripts/adr-lint.sh
PRテンプレートにもADRチェックリストを追加し、設計変更を含むPRではADR作成を促す。
ポイント:
- テンプレートは最小限にする — Status / Context / Decision / Consequences の4セクションだけで十分。項目が多いと書くハードルが上がる
- リントはフォーマットの検証に留める — 内容の良し悪しは人が判断する。リントは必須セクションの存在・ファイル命名規則の確認程度にする
- PRテンプレートと連動させる — 「この変更にADRは必要か?」のチェックボックスをPRテンプレートに入れ、意識的な判断を促す
PreToolUseフックでAIエージェントの設定ファイル改変を防ぐ
AIエージェントがタスク実行中にlinter設定やフォーマッタ設定を勝手に変更してしまう問題を、PreToolUseフックでガードする。
仕組みの概要:
- エージェントがファイル編集ツールを呼ぶ直前にフックが起動する
- 編集対象のファイルパスを保護リストと照合する
- 保護対象なら操作をブロックし、理由をエージェントに返す
# config-guard.sh(簡略版)
PROTECTED_FILES="biome.json .eslintrc tsconfig.json lefthook.yml"
for file in $PROTECTED_FILES; do
if echo "$INPUT" | grep -q "$file"; then
echo "BLOCKED: $file is a protected config file"
exit 1
fi
done
設計判断のポイント:
- ブロックリスト方式にする — 許可リスト方式だと新規ファイル追加時に漏れるため、明示的にガード対象を列挙する方が安全
- stdinからの入力を正しく処理する — フックはパイプ経由で入力を受け取る。対話的なプロンプト(read)を使うとブロックするため、stdinを一括読み取りする設計にする
- エージェントへのフィードバックを明確にする — なぜブロックされたかをメッセージで返すと、エージェントが代替手段を自律的に選べる
コーディングエージェントのフックを汎用フックランナーに移行する
AIエージェントツール固有のフック機構に品質ゲートを載せていたが、標準的なGitフックランナー(lefthook)に統一した。ツール依存を減らし、チーム全員が同じルールで開発できるようにする。
Before(手動 / ツール固有):
エージェントツールの設定ファイル(settings.json)にフックを定義。エージェント経由の操作にしか品質ゲートが効かず、通常の git commit や CI では素通りしていた。
After(自動 / lefthook):
# lefthook.yml
pre-commit:
commands:
lint:
run: npx biome check --write . && git add -u
type-check:
run: npx tsc --noEmit
pre-push:
commands:
test:
run: npx vitest run
ポイント:
段階的に移行する — 一度に全フックを切り替えず、pre-commit → pre-push の順に移行し、各段階でCIと挙動が一致することを確認する
既存のガードスクリプトを再利用する — フックランナー側からシェルスクリプトを呼ぶだけにして、ロジックの重複を避ける
エージェント固有フックは参照専用に残す — 完全削除ではなく、エージェントが特別な事前チェック(設定ファイル保護等)を行う場合のみ残す
バックログリファインメントをAIスキルワークフロー化する
バックログリファインメント(完了ストーリーの反映、新ストーリーの起票、優先度の整理)を手作業で行うと漏れが出やすい。一連の手順をAIスキルとして定義し、ストーリーマップの同期からicebox投入まで1コマンドで実行できるようにする。
仕組み:
- 完了ストーリーの同期 — マージ済みPRからdoneになったストーリーを検出し、ストーリーマップのステータスを自動更新
- UXギャップの発見 — 完了ストーリーの実装内容をレビューし、ユーザー体験の抜け漏れ(セッション切れ対応、初回利用ガイド等)を新ストーリーとして提案
- iceboxへの投入 — 提案をユーザーが承認したらGitHub issueとして起票し、ストーリーマップに追加
/backlog-refinement
1. git log + gh pr list → 完了ストーリーを検出
2. ストーリーマップ更新 (done列に移動)
3. 実装内容からUXギャップを分析
4. ユーザーに新ストーリー候補を提示
5. 承認されたら gh issue create → icebox追加
設計判断のポイント:
- 新ストーリーの起票は必ずユーザー承認を挟む。AIが勝手にissueを量産すると、バックログがノイズだらけになる
- 「UXギャップの発見」は完了ストーリーの実装diff起点にすることで、実際のコードに裏打ちされた提案になる。抽象的なブレストではなく、具体的な画面遷移やエラーケースから導出する
データフェッチhookでignoreフラグによるrace conditionを防ぐ
Reactで認証状態やユーザー情報をフェッチするカスタムhookを作るとき、コンポーネントのアンマウントや再レンダリングによるrace conditionを防ぐには、useEffectのクリーンアップでignoreフラグを使う。
function useCurrentUser() {
const [user, setUser] = useState<User | null>(null)
useEffect(() => {
let ignore = false
fetch('/api/auth/me')
.then(res => res.json())
.then(data => {
if (!ignore) setUser(data)
})
return () => { ignore = true }
}, [])
return user
}
ポイント:
ignoreフラグにより、アンマウント後のsetStateが抑制される。React 18のStrict Modeでは開発時にuseEffectが2回実行されるため、このパターンがないと古いレスポンスで状態が上書きされる- AbortControllerでfetchをキャンセルする方法もあるが、ignoreフラグの方がシンプルで、fetch以外の非同期処理にも使える
- 認証チェックのようにページ遷移のたびに呼ばれるhookでは特に重要。未認証時のリダイレクト判定が古いレスポンスに基づくと、チラつきやリダイレクトループの原因になる
自律型コーディングエージェントのworktree依存解決を堅牢化する
AIエージェントがgit worktreeで機能開発する際、依存関係のインストールが不完全だとビルドやテストが失敗する。「依存解決 → 検証」のプリフライトポリシーをスキルに組み込むことで、エージェントの自律実行の成功率が上がる。
仕組み:
- 決定論的ブートストラップ — worktree作成直後に
installを必ず実行し、lockfileから依存を復元する。エージェントが勝手にパッケージを追加する(npm install <pkg>)ことを禁止するポリシーを明文化 - 検証のauto-retry — ビルド・テスト実行時にツール未検出エラーが出たら、依存解決を再実行して1回だけリトライする
- ランタイムポリシーの文書化 — 「何をしてよいか・してはいけないか」をテンプレートファイルとしてworktreeに配置し、エージェントが参照できるようにする
# Worktree Runtime Policy
- ✅ `npm install` (lockfileからの復元)
- ❌ `npm install <package>` (新規依存の追加)
- ✅ ビルドエラー時の依存再解決 → リトライ (1回まで)
設計判断のポイント:
- エージェントに「自由に依存を追加してよい」とすると、lockfileとの乖離やバージョン不整合が起きやすい。制約を明示する方が結果的に成功率が高い
- リトライは1回に限定する。無限リトライはエージェントが間違った方向に深く進むリスクがある
