iterative-development について
Claude Code の Stop hook で各応答後にテストやチェックを実行し、コード上の失敗をモデルに返す TDD ループを作るスキルです。合否を明確に判定できる開発作業に向いています。
主な機能
- 01Stop hook によるテストと lint の反復
- 02失敗内容を Claude に返却
- 03反復回数の上限設定
- 04任意の書き込み前 lint hook
- 05セッション開始時のコンテキスト注入
活用シーン
- 01テストを確認しながらの機能実装
- 02失敗テストを使ったバグ修正
- 03回帰チェックを伴うリファクタリング
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ alinaqi/ maggy --skill iterative-developmentインストール後、Claude Code で/iterative-developmentを入力して読み込まれたか確認します。
詳細
反復開発スキル(Stop フックによる TDD ループ)
概要: Claude Code の Stop フックは、Claude が応答を完了する直前に実行されます。終了コード 2 を返すと、stderr がモデルにフィードバックされ、会話が続きます。これにより、プラグインを使わずに実際の TDD ループを実現できます。
実際の動作
Claude Code には、Claude が応答を終了しようとすると実行される Stop フック があります。フックスクリプトがコード 2 で終了すると、その stderr がモデルに表示され、会話が自動的に続きます。
┌─────────────────────────────────────────────────────────────┐
│ 1. User asks Claude to implement a feature │
├─────────────────────────────────────────────────────────────┤
│ 2. Claude writes tests + implementation │
├─────────────────────────────────────────────────────────────┤
│ 3. Claude finishes its response │
├─────────────────────────────────────────────────────────────┤
│ 4. Stop hook runs: executes tests, lint, typecheck │
├─────────────────────────────────────────────────────────────┤
│ 5a. All pass (exit 0) → Claude stops, work is done │
│ 5b. Failures (exit 2) → stderr fed back to Claude │
├─────────────────────────────────────────────────────────────┤
│ 6. Claude sees failures, fixes code, response ends │
├─────────────────────────────────────────────────────────────┤
│ 7. Stop hook runs again → repeat until green or max tries │
└─────────────────────────────────────────────────────────────┘
重要なポイント: 偽のプラグインや /ralph-loop コマンドは不要です。このフックは、Claude Code の実際のインフラストラクチャであり、自動的に実行されます。
セットアップ:Stop フックの設定
プロジェクトの .claude/settings.json に次の内容を追加します。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "scripts/tdd-loop-check.sh",
"timeout": 60,
"statusMessage": "Running tests..."
}
]
}
]
}
}
TDD ループチェック用スクリプト
プロジェクトに scripts/tdd-loop-check.sh を作成します。
#!/bin/bash
# TDD Loop Check - runs after each Claude response
# Exit 0 = all good, Claude stops
# Exit 2 = failures, stderr fed back to Claude to fix
MAX_ITERATIONS=25
ITERATION_FILE=".claude/.tdd-iteration-count"
# Track iteration count
if [ -f "$ITERATION_FILE" ]; then
count=$(cat "$ITERATION_FILE")
count=$((count + 1))
else
count=1
fi
echo "$count" > "$ITERATION_FILE"
# Safety: stop after max iterations
if [ "$count" -ge "$MAX_ITERATIONS" ]; then
rm -f "$ITERATION_FILE"
echo "Max iterations ($MAX_ITERATIONS) reached. Stopping loop." >&2
exit 0
fi
# Skip if no test files exist yet
if ! find . -name "*.test.*" -o -name "*.spec.*" -o -name "test_*" 2>/dev/null | grep -q .; then
rm -f "$ITERATION_FILE"
exit 0
fi
# Run tests
TEST_OUTPUT=$(npm test 2>&1) || {
echo "ITERATION $count/$MAX_ITERATIONS - Tests failing:" >&2
echo "$TEST_OUTPUT" | tail -30 >&2
echo "" >&2
echo "Fix the failing tests and try again." >&2
exit 2
}
# Run lint (if configured)
if [ -f "package.json" ] && grep -q '"lint"' package.json; then
LINT_OUTPUT=$(npm run lint 2>&1) || {
echo "ITERATION $count/$MAX_ITERATIONS - Lint errors:" >&2
echo "$LINT_OUTPUT" | tail -20 >&2
echo "" >&2
echo "Fix lint errors and try again." >&2
exit 2
}
fi
# Run typecheck (if configured)
if [ -f "tsconfig.json" ]; then
TYPE_OUTPUT=$(npx tsc --noEmit 2>&1) || {
echo "ITERATION $count/$MAX_ITERATIONS - Type errors:" >&2
echo "$TYPE_OUTPUT" | tail -20 >&2
echo "" >&2
echo "Fix type errors and try again." >&2
exit 2
}
fi
# All green - reset counter and let Claude stop
rm -f "$ITERATION_FILE"
exit 0
Python 版
#!/bin/bash
# Python TDD Loop Check
MAX_ITERATIONS=25
ITERATION_FILE=".claude/.tdd-iteration-count"
if [ -f "$ITERATION_FILE" ]; then
count=$(cat "$ITERATION_FILE")
count=$((count + 1))
else
count=1
fi
echo "$count" > "$ITERATION_FILE"
if [ "$count" -ge "$MAX_ITERATIONS" ]; then
rm -f "$ITERATION_FILE"
echo "Max iterations ($MAX_ITERATIONS) reached." >&2
exit 0
fi
if ! find . -name "test_*" -o -name "*_test.py" 2>/dev/null | grep -q .; then
rm -f "$ITERATION_FILE"
exit 0
fi
TEST_OUTPUT=$(pytest -v 2>&1) || {
echo "ITERATION $count/$MAX_ITERATIONS - Tests failing:" >&2
echo "$TEST_OUTPUT" | tail -30 >&2
exit 2
}
if command -v ruff &>/dev/null; then
LINT_OUTPUT=$(ruff check . 2>&1) || {
echo "ITERATION $count/$MAX_ITERATIONS - Lint errors:" >&2
echo "$LINT_OUTPUT" | tail -20 >&2
exit 2
}
fi
if command -v mypy &>/dev/null; then
TYPE_OUTPUT=$(mypy . 2>&1) || {
echo "ITERATION $count/$MAX_ITERATIONS - Type errors:" >&2
echo "$TYPE_OUTPUT" | tail -20 >&2
exit 2
}
fi
rm -f "$ITERATION_FILE"
exit 0
品質を確保するための追加フック
PreToolUse フック:ファイル書き込み前に Lint を実行
Write/Edit が適用される前に Linter を実行します。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "scripts/pre-write-lint.sh",
"timeout": 10,
"statusMessage": "Checking code quality..."
}
]
}
]
}
}
SessionStart フック:コンテキストを自動挿入
セッション開始時に実行され、プロジェクト情報を挿入します。
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo 'TDD loop active. Tests run automatically after each response. Fix failures to continue.'",
"statusMessage": "Loading project context..."
}
]
}
]
}
}
基本方針
┌─────────────────────────────────────────────────────────────┐
│ ITERATION > PERFECTION │
│ ───────────────────────────────────────────────────────── │
│ Don't aim for perfect on first try. │
│ Let the loop refine the work. Each iteration builds on │
│ previous attempts visible in files and git history. │
├─────────────────────────────────────────────────────────────┤
│ FAILURES ARE DATA │
│ ───────────────────────────────────────────────────────── │
│ Failed tests, lint errors, type mismatches are signals. │
│ The Stop hook feeds them directly to Claude as context. │
├─────────────────────────────────────────────────────────────┤
│ CLEAR COMPLETION CRITERIA │
│ ───────────────────────────────────────────────────────── │
│ The hook defines "done": tests pass, lint clean, types ok. │
│ No ambiguity about when to stop. │
└─────────────────────────────────────────────────────────────┘
エラーの分類
すべての失敗でループを続けるべきではありません。フックスクリプトでは、次のように区別する必要があります。
| 種類 | 例 | 対応 |
|---|---|---|
| コードエラー | ロジックのバグ、誤ったアサーション、型の不一致 | 終了コード 2 → ループを続行 |
| アクセスエラー | API キーがない、DB への接続が拒否された | 終了コード 0 → 停止してユーザーに報告 |
| 環境エラー | パッケージがない、ランタイムのバージョンが違う | 終了コード 0 → 停止してユーザーに報告 |
上記のサンプルスクリプトでは、この処理が行われます。終了コード 2 を返すのはテスト、Lint、型の失敗のみで、環境の問題ではありません。
TDD ループを使う場面
適している場面
| ユースケース | 理由 |
|---|---|
| 機能開発 | テストにより合格/不合格が明確に分かる |
| バグ修正 | 失敗するテストを書き、修正して、成功するまでループする |
| リファクタリング | 既存のテストでリグレッションを検出できる |
| API 開発 | 各エンドポイントを個別にテストできる |
適していない場面
| ユースケース | 理由 |
|---|---|
| UI/UX 作業 | 人による判断が必要 |
| 一度限りの操作 | 繰り返す必要がない |
| 要件が不明確な場合 | 明確な「完了」の基準がない |
| 主観的なデザイン | 客観的な成功指標がない |
ループを無効にする
セッション中に TDD ループを一時的に無効にするには、次のいずれかを行います。
.claude/settings.jsonの Stop フックを削除するか、名前を変更します- またはスクリプトで
MAX_ITERATIONS=1を設定します - または
scripts/tdd-loop-check.shを削除します
スクリプトが存在し、設定されている場合にのみフックが実行されます。
Gitignore への追加
# TDD loop state
.claude/.tdd-iteration-count