Notes
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回に限定する。無限リトライはエージェントが間違った方向に深く進むリスクがある
JWT認証ミドルウェアを関心事で分割する
JWT認証を1ファイルで実装すると、JWKS取得・署名検証・ルーティングが混在して見通しが悪くなる。関心事ごとに3つのモジュールに分離すると、テスタビリティと再利用性が上がる。
分離の構成:
- JWKS取得・キャッシュ — 公開鍵の取得とTTLベースのキャッシュ(例: 5分)。ネットワーク障害時のフォールバックもここで吸収する
- JWT署名検証 — RS256署名の検証、base64urlデコード、audience・有効期限のチェック。純粋な関数として実装できる
- 認証エンドポイント定義 — ログイン・ログアウト・認証情報取得のパスを共有モジュールにまとめる
auth/
jwks.ts # JWKS取得 + TTLキャッシュ
jwt-verify.ts # RS256検証 + claims検証
endpoints.ts # 認証関連パスの定数
middleware.ts # ↑を組み合わせたミドルウェア本体
ポイント:
- JWKS取得を分離すると、テスト時に固定の公開鍵を注入できる。テストが外部依存なしで動く
- JWT検証を純粋関数にすると、改ざん検知やexpiry検証のユニットテストが書きやすい
- エンドポイントパスを共有モジュールにすると、フロントエンドからも参照できて不整合が起きにくい
AIコードレビューの知見をファイルに自動蓄積する
AIエージェントにコードレビューさせる際、レビュー結果を使い捨てにせず、アーキテクチャやドメインの知見をファイルに永続化する仕組みを追加した。
レビューテンプレートの末尾に「知見永続化フェーズ」を設ける:
## Knowledge Persistence
レビュー中に発見した以下の知見をファイルに追記してください:
- **アーキテクチャ知見** → `skills/clean-architecture-reviewer/references/knowledge.md`
例: 「このプロジェクトではrepository層でneverthrowを使う」
- **ドメイン知見** → `skills/product-owner-reviewer/references/knowledge.md`
例: 「storyのpriorityは0が最高優先度」
次回のレビューではこの知見ファイルが自動的にコンテキストとして読み込まれるので、同じ指摘を繰り返さず、プロジェクト固有の判断基準が蓄積されていく。
実質的に「レビュアーが学習する」仕組みをファイルベースで実現している。RAGのようなベクトルDBは不要で、Markdownファイルの追記だけで機能する。
GitHub issueコメントからAIコード生成を安全にトリガーする
GitHub Actionsの issue_comment イベントを使って、issueやPRのコメントからAIコーディングエージェントを起動するワークフローを構築した。/codex のようなプレフィックスをトリガーにし、全コメントで発火しないようにする。
安全に運用するためのポイント:
- コマンドインジェクション対策 — コメント本文を
${{ github.event.comment.body }}で直接シェルに展開すると任意コマンド実行の脆弱性になる。環境変数経由で渡す:
env:
COMMENT_BODY: ${{ github.event.comment.body }}
steps:
- run: |
# $COMMENT_BODY はシェル変数として安全に展開される
INSTRUCTION="${COMMENT_BODY#/codex }"
- 同時実行の制御 —
concurrencyグループをissue/PR番号単位で設定し、同じissueへの連続コメントで重複実行を防ぐ:
concurrency:
group: codex-${{ github.event.issue.number || github.event.pull_request.number }}
cancel-in-progress: true
実行状況の可視化 — 開始時にリアクション(👀)を付け、完了時に結果をコメントとして投稿する。失敗時もエラー内容を投稿して、ワークフローログを見に行かなくても状況が分かるようにする
入力バリデーション — 指示文の最大長を制限し、あまりに短い指示には確認を返す
neverthrowでリポジトリ層のエラーをResult型に統一する
TypeScriptプロジェクトでリポジトリ層(DB操作)のエラーハンドリングをtry-catchからneverthrowのResult型に切り替えた。
Before:
// try-catchだと呼び出し側でエラー型が不明
async function createProject(data: NewProject): Promise<Project> {
try {
const result = await db.insert(projects).values(data).returning();
return result[0];
} catch (e) {
throw new Error("Failed to create project");
}
}
After:
import { ok, err, ResultAsync } from "neverthrow";
async function createProject(
data: NewProject
): ResultAsync<Project, RepositoryError> {
return ResultAsync.fromPromise(
db.insert(projects).values(data).returning(),
(e) => new RepositoryError("CREATE_FAILED", e)
).map((rows) => rows[0]);
}
usecase層では .andThen() でチェインし、presentation層で .match() でHTTPレスポンスに変換する。エラーが型で追跡できるので、「このusecaseがどんなエラーを返しうるか」がシグネチャだけで分かる。
テストではリポジトリのモックを集約ファイルに切り出し、ok()/err() で返り値を差し替えると、成功・失敗パスの両方を簡潔にテストできる。
AIエージェントの実装→レビュー→PRをシェルスクリプトで自動化する
Claude CLIのようなAIコーディングエージェントに、issue単位の開発ライフサイクル全体をシェルスクリプトで自動化させる仕組みを作った。6フェーズ構成:
- worktree作成 —
git worktree addでmainから隔離ブランチを切る。エージェントの作業がメインの作業ディレクトリに影響しない - 実装 —
claude -p(CLIの非対話モード)にプロンプトテンプレートとストーリーファイルを渡して実装させる - 3観点レビュー — 実装完了後、同じくCLI経由でレビュープロンプトを実行。受け入れ条件チェック・アーキテクチャレビュー・PO視点レビューの3つを1回で回し、結果に
<signal>REVIEW_PASSED</signal>orREVIEW_FAILEDを出力させてシェルスクリプトで判定する - PR作成 —
gh pr createでPRを自動作成 - マージ待ち — 30秒間隔で
gh pr view --json stateをポーリングし、人間のマージを待つ - クリーンアップ —
git worktree removeでworktreeを削除し、ストーリーファイルをdoneディレクトリに移動
レビューのゲート設計がポイント。レビュー結果をstructured outputではなく、本文末尾の <signal> タグで表現すると、シェルスクリプトの grep だけで合否判定できる:
REVIEW_OUTPUT=$(claude -p "$REVIEW_PROMPT" --cwd "$WORKTREE_DIR")
if echo "$REVIEW_OUTPUT" | grep -q "REVIEW_FAILED"; then
echo "Review failed. Aborting PR creation."
exit 1
fi
進捗ログはストーリーファイル自体の ## Activities セクションに追記する方式にした。別ファイルに書くと散逸するが、ストーリーファイルに書けば作業経緯がissue定義と一体化して残る。
VSCode Workspaceでマルチリポジトリ構成のAIアシスタントが認識するコンテキスト境界
複数リポジトリを1つのVSCode Workspaceで開いている場合、AI拡張機能が認識するコンテキスト(cwd)はアクティブファイルの属するワークスペースフォルダに依存する。.code-workspaceファイルでフォルダ順を制御できるが、AIのコンテキスト選択に直接影響しないことがある。
対策パターン:
- リポジトリごとにウィンドウを分ける — 最も確実だが切り替えコストがある
- ワークスペース設定でデフォルトcwdを指定する — 拡張機能がサポートしていれば有効
- プロジェクトルートにコンテキストファイルを配置する —
CLAUDE.mdやAGENTS.mdを各リポジトリルートに置き、AIが自動で読み込むようにする
パターン3はAI側の規約に沿ったファイルを置くだけで済むため低コストで効果が高い。
AIエージェントループをCLIツールチェインに統合する段階的パターン
自律的にタスクを消化するAIエージェントループ(PRDを入力にコードを生成し続けるツール等)を既存の開発ツールチェインに組み込む際、統合度と実装コストのバランスで4段階に分けて考えると導入判断がしやすい。
- マーケットプレイス/プラグインをそのまま利用 — 最小コスト。まず動作確認に使う
- セットアップスクリプトとして同梱 — プロジェクトテンプレートに組み込み、毎回同じ構成で再現可能にする
- MCPツールとしてラップ — 自然言語で「初期化して」「5イテレーション回して」と呼べる。統合度は最高だが実装コストも高い
- Skillファイルで手順を誘導 — 低コストで手順をドキュメント化。実行自体はBashに委ねる
ポイントは「エージェントループ自体がCLIツールを実行エンジンとして使う」構造を理解すること。統合の本質は、エージェントの実行コマンドを開発者体験にどう自然に載せるかにある。段階2+3の組み合わせ(セットアップ自動化+MCP統合)が、導入と運用の両方を揃える筋の良い選択肢になることが多い。
refs: https://github.com/snarktank/ralph
Android ビルド失敗: PNG ファイル形式の不一致
問題
Expo で Android preview ビルド(eas build)が以下のエラーで失敗:
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:mergeReleaseResources'.
> Android resource compilation failed
ERROR: .../drawable-mdpi/assets_images_icon.png: AAPT: error: file failed to compile.
原因
ファイル拡張子と実際のフォーマットの不一致
該当ファイルは .png 拡張子だが、実際は JPEG 形式だった:
$ file assets/images/icon.png
assets/images/icon.png: JPEG image data, JFIF standard 1.01, ...
Android の aapt2(Android Asset Packaging Tool)は、拡張子と実際のフォーマットが一致しない画像をコンパイルできない。
影響を受けたファイル
assets/images/icon.pngassets/images/onboarding_1.pngassets/images/onboarding_2.pngassets/images/onboarding_3.pngassets/images/onboarding_4.png
これらのファイルはAntigravityを使ってnano bananaで生成したものだった
解決方法
macOS の sips コマンドで正しい PNG 形式に変換:
for file in assets/images/icon.png assets/images/onboarding_{1,2,3,4}.png; do
sips -s format png "$file" --out "$file"
done
変換後の確認
$ file assets/images/icon.png
assets/images/icon.png: PNG image data, 1024 x 1024, 8-bit/color RGB, non-interlaced
予防策
画像ファイルを追加する際は
fileコマンドで実際の形式を確認する画像編集ソフトで「名前を付けて保存」する際、拡張子だけでなく形式も正しく選択する
CI/CD で画像形式チェックを追加することも検討
参考
AAPT2 は Android Studio / Gradle で使用されるリソースコンパイラ
iOS は拡張子と形式の不一致を許容するが、Android は厳格
