☕️

cc-sddの実装を引数なしで自動実行できる脳死コマンドを作ってみた

に公開

こんにちは、とまだです。

みなさん、cc-sddで仕様駆動開発を行っていますか?

cc-sddは Gota さんが開発した国産の仕様駆動開発ツールでして、私は現場でも愛用しています。

https://github.com/gotalab/cc-sdd

使っている方はご存知かもしれませんが、実装フェーズのコマンドは /kiro:spec-impl です。

このコマンドでは feature名(spec名)とtask番号を指定します。

/kiro:spec-impl user-dashboard 6.1

対象を明確に指定する形なので並列実装もしやすく、完成度が高いです。

ただ、一部のユースケースではもっと楽をしたいと思うことがあるので、そのためのカスタムコマンドを作ってみました。

記事の末尾に全文を掲載していますので、そちらをご覧ください。

また、未完了のタスクがなくなるまで実行し続けるスクリプトも作成しましたので、そちらも記事の末尾に掲載しています。

cc-sddの標準コマンドの説明

まずおさらいから。

cc-sddの/kiro:spec-implコマンドは、以下のように feature名とtask番号を指定します。

/kiro:spec-impl user-dashboard 6.1

また、タスク番号を指定しなければ、**すべてのタスクを実行してくれます。

/kiro:spec-impl user-dashboard

この仕組みは、確実に対象タスクを実行していくという点で申し分ありません。
しかも複数タスクの並列実行もしやすいように配慮されていますし、途中で人間の目によるチェックを挟みやすい設計になっています。

実際、現場のように重要かつ手戻りを許せない場面では、このコマンドを使うことが多いです。

ただ、時と場合によってはちょっと手間に感じることがあります。

雑に進めたいユースケースもある

私が個人開発、特に自分用のツールを作る場合、とりあえずばーっと一気に作り上げることもあります。
(要件定義や設計はしっかりと確認している前提)

まずは動かしてみてから最終調整をする、いわば「雑に進めて最後にまとめてレビュー&調整」という場面があります。

このユースケースだと、毎回feature名やtask番号を確認する作業が地味にストレスになります。
特に、複数のリポジトリの開発を並行して進めている場合、「なんだっけ?」となることが多いです。

※feature名についてはコマンド履歴を遡ればすぐに思い出せますが。

しかも、20個以上のサブタスクがある大きなfeatureでは、この確認作業で集中力が途切れることもあります。

先述の通りタスク番号を指定しなければ、すべてのタスクを実行してくれますが、コンテキストを圧迫して効率が落ちる可能性があります。
また、Claude Code だと auto compact 前に自主的に中断されることがあるので、個人的にはあまり使いません。

そこで考えたのが、引数なしで次のタスクを自動実行するラッパーコマンドです。

/implコマンドの3つのルール

ここからは、このコマンドの背景にある考え方を説明します。

このコマンドは3つのシンプルなルールに基づいています。それぞれ見ていきましょう。

/implの動作は、たったこれだけで成り立っています。

ルール1: アクティブなspecは常に1つ

具体的には、.kiro/specs/ディレクトリには作業中のfeatureを1つだけ置きます。そして、完了したspecは.kiro/specs/archives/に移動します。

.kiro/specs/
├── user-dashboard/          # ← アクティブなspec(1つだけ)
│   ├── requirements.md
│   ├── design.md
│   └── tasks.md
└── archives/
    ├── auth-feature/        # ← 完了済み
    └── profile-settings/    # ← 完了済み

なぜこのルールが必要なのでしょうか。複数のspecが同時にアクティブだと、AIがどれを実行するか判断に迷ってしまいます。「今取り組んでいるのはこれ」と明確にしておくことで、自動検出が確実に動作するのです。

これにより、毎回feature名を引数で指定する手間が省けます。

CLAUDE.md に記載する方法もあるかもしれませんが、より確実な方法として、このルールを採用しました。

ルール2: 次に実行するタスクは自明

tasks.mdには、未完了タスクが- [ ]形式でリストアップされています。つまり、最初の未完了サブタスクを選べば、それが「次にやるべきこと」というわけです。

具体的には、以下のような形式になっています。

- [x] 1. Setup project structure
  - [x] 1.1 Initialize repository
  - [x] 1.2 Configure build tools
- [ ] 2. Implement core features
  - [ ] 2.1 Create user model       ← これを自動選択
  - [ ] 2.2 Add validation logic
  - [ ] 2.3 Write unit tests

「次は2.1だな」と考える手間が不要になります。機械的に決まるので、判断する必要がないのです。

ルール3: 実行は1タスクずつ

一度に1サブタスクだけ実行することで、コンテキストの消費を最小限に抑えます。

では、なぜ1つずつなのでしょうか。理由はシンプルで、AIのコンテキストウィンドウには限りがあるからです。大量のタスクを一度に投入すると、途中で溢れてしまうリスクがあります。

1タスクが完了したら結果を確認し、問題なければ次の/implを実行する。

このリズムで進めると、途中で問題が起きても影響範囲が限定されます。また、各タスクの完了を確認しながら進められるので、品質も担保しやすくなります。

標準コマンド(/kiro:spec-impl)との使い分け

両者の違いを表にまとめると、このようになります。

項目 /kiro:spec-impl /impl
feature名 明示的に指定 自動検出
task番号 明示的に指定 自動選択(最初の未完了)
実行単位 柔軟(1つでも複数でも) 1タスク固定
並列実行 しやすい 想定していない
途中チェック 挟みやすい 最後にまとめて

/kiro:spec-impl柔軟性と確実性を重視した設計で、とてもよく出来ていると思います。
一方で、/implは「雑に進めて最後に調整」というユースケースに特化し、「何も考えずに次へ進む」状態を作っています。

どちらが優れているという話ではなく、開発スタイルに合わせて選ぶのが正解ですね。

実際の使い方と運用

では、具体的な使い方を見ていきましょう。

前提条件

/implを使う前に、以下の状態になっていることを確認してください。

  • cc-sddがセットアップ済み
  • specが作成済み(requirements → design → tasksのフェーズが完了)
  • .kiro/specs/{feature}/tasks.mdが存在する
  • アクティブなspecが1つだけ(archivesを除く)

これらが揃っていれば、すぐに使い始められます。

たとえば、以下のようなディレクトリ構成であればOKです。

.kiro/specs/
├── user-dashboard # 現在進行形のfeature
│   ├── spec.json
│   ├── requirements.md
│   ├── design.md
│   └── tasks.md
└── archives/
    └── authentication-feature # 過去のfeature

基本ワークフロー

日々の開発はこのリズムで進みます。

1. /impl を実行
2. AIが1つのサブタスクを実装
3. 生成されたコードを確認
4. 問題なければコミット
5. /impl で次のタスクへ
6. 2-5を繰り返し
7. 全タスク完了したらspecをarchivesへ移動

実際の /impl コマンドを実行すると、こんな流れで処理が進みます。

/impl

→ アクティブなspec検出: user-dashboard
→ tasks.md解析: 最初の未完了タスクは 2.1
→ タスク2.1を実装
→ 無事に完了したらチェックマークをつける

archives運用の注意点

featureの全タスクが完了したら、specをarchivesに移動しましょう。ここは手動で行う必要があります。

mv .kiro/specs/user-dashboard .kiro/specs/archives/

コマンドの中身と効率的な理由

続いて、このコマンドがどのように動作するのかを見ていきましょう。

/implコマンドは.claude/commands/impl.mdに配置します。中身はシンプルなMarkdownで、AIへの指示を記述しています。

処理の流れは3つの段階に分かれています。

段階1: 自動検出

.kiro/specs/内のディレクトリを走査し、archivesを除外した上で残った1つをアクティブなfeatureとして認識します。

複数あればユーザーにエラーを通知してくれるはずです。

段階2: タスク選択

tasks.mdを読み込み、- [ ]形式の未完了サブタスクを上から順に探します。

そして、見つかった1つ目を実行対象として選択します。

段階3: あとはcc-sddと同じ流れで実装

私の場合は Claude Code のサブエージェントオプションで cc-sdd をインストールしているので、そのサブエージェントを使って実装を依頼しています。

これは cc-sdd デフォルトの /kiro:spec-impl コマンドからお借りしているだけです。

補足: Claude Code 以外での使い方

Claude Code 以外であれば、このあたりは spec-impl.md からうまいこと持ってくると良いでしょう。

以下のようなプロンプトをAIに投げてあげれば、いい感じに調整してくれるかと思います。

たとえば Cursor ならこんな感じでしょうか。

# 背景
./claude/commands/impl.md には、引数の指定なく次のタスクを自動実行するコマンドが記載されています。

# 課題
これは他の人からもらってきたコマンドです。
そのため当プロジェクトで使っている .cursor/commands/kiro/spec-impl.md とは若干異なる点があります。

# 命令

以下のステップで `impl.md` を調整してください。

1. `.claude/commands/impl.md` の内容を理解する。
2. `.cursor/commands/kiro/spec-impl.md` の内容を理解する。
3. 両者の違いを把握する。
4. `.cursor/commands/kiro/spec-impl.md` の内容に合わせて `.claude/commands/impl.md` を調整する。

まとめ

/implコマンドは、cc-sddの上に乗せた私の一部ユースケース特化のラッパーです。

つまり、「雑に進めて最後に調整」という開発スタイル向けに、引数なしで次のタスクを自動実行するものです。ただし、cc-sddの標準コマンドは柔軟性と確実性に優れているので、開発スタイルに合わせて選んでください。

繰り返しになりますが、デフォルトの /kiro:spec-impl コマンドは非常によくできているので、重要&都度確認したい場面ではそちらを使うことをおすすめします!

関連記事

cc-sddやカスタムコマンドについて詳しく知りたい方は、こちらの記事も参考にしてください。

以下のリンクが参考になります。

付録1: impl.mdの全文

お待たせしました。
cc-sdd を Claude Code のサブエージェントオプションでインストールしている場合の impl.md です。
以下を.claude/commands/impl.mdとして保存してください。

---
description: Execute single pending subtask using TDD methodology
allowed-tools: Read, Task, Glob
---

# Single Task Executor

## Auto-detect Feature

Find the active feature directory in `.kiro/specs/`:
1. List directories in `.kiro/specs/`
2. Exclude `archives` directory
3. There should be exactly one remaining directory - that's the active feature

If no feature found or multiple features exist (excluding archives), inform user to check spec structure.

## Validate

Check that tasks have been generated:
- Verify `.kiro/specs/{feature}/tasks.md` exists

If validation fails, inform user to complete tasks generation first.

## Task Selection Logic

**Find the first pending subtask**:
1. Read `.kiro/specs/{feature}/tasks.md`
2. Find all unchecked subtasks (`- [ ]` with format like `6.1`, `6.2`, etc.)
3. Select **only the first one** (e.g., if 6.1, 6.2, 6.3 are pending, select only 6.1)

## Invoke Subagent

Delegate TDD implementation to spec-tdd-impl-agent:

Use the Task tool to invoke the Subagent with file path patterns:

```
Task(
  subagent_type="spec-tdd-impl-agent",
  description="Execute TDD implementation",
  prompt="""
Feature: {feature}
Spec directory: .kiro/specs/{feature}/
Target task: {first pending subtask number, e.g., "6.1"}

File patterns to read:
- .kiro/specs/{feature}/*.{json,md}
- .kiro/steering/*.md

TDD Mode: strict (test-first)

IMPORTANT: Execute ONLY this single subtask. Do not proceed to subsequent tasks.
"""
)
```

## Display Result

Show Subagent summary to user, then provide next step guidance.

## Update Phase Completion Status

After the subagent completes its task successfully, check if the parent phase should be marked as complete:

1. **Re-read tasks.md**: Read `.kiro/specs/{feature}/tasks.md` to get the current state
2. **Identify parent phase**: Extract the phase number from the completed subtask (e.g., "6.1" → "6")
3. **Find all subtasks for this phase**: Gather all lines matching pattern `- [x| ] {phase}.{number}` (e.g., `6.1`, `6.2`)
4. **Check completion**: Verify ALL subtasks under this phase are checked (`- [x]`)
5. **Update phase status**: If ALL subtasks are complete, use the Edit tool to change `- [ ] {phase}. ...` to `- [x] {phase}. ...`

**Example**: If subtask 5.2 was just completed:
- Check: Is `- [x] 5.1` present? Yes
- Check: Is `- [x] 5.2` present? Yes
- Check: Are there any `- [ ] 5.{n}` remaining? No
- → Update `- [ ] 5. バリデーション...` to `- [x] 5. バリデーション...`

**IMPORTANT**: Only update the phase checkbox if ALL its subtasks are complete. Do not update if any subtask remains unchecked.

### Next Steps

After completion:
- Run `/impl` again to execute the next pending subtask
- Run `/kiro:spec-status {feature}` to check overall progress

### Purpose

This command is for executing tasks **one at a time** without specifying task numbers manually.
Use `/kiro:spec-impl {feature} 1.1` if you want to specify a particular task.

付録2: 自動でアクティブなfeature(spec)の実装を全部進めるスクリプト

これは完全に自分用なのですが、アクティブなfeature(spec)の実装を一つずつ、そして全部進めるスクリプトを作ってみました。
Claude Code 用です。

scripts/auto-impl.sh ファイルを作成して、以下の内容を貼り付け、chmod +x scripts/auto-impl.sh で実行権限を付与してください。

# 処理の流れ
1. アクティブなfeature(spec)を検出
2. Claude Code のヘッドレスモードで /impl コマンドを実行
3. コミット
4. 未完了タスクがなくなるまで繰り返す

1個注意点としては、dangerously-skip-permissions フラグを付けて、パーミッションの確認をスキップしています。
安全のため、コンテナなど隔離した環境で実行することをおすすめします。

#!/bin/bash
################################################################################
# Kiro Spec 自動実装スクリプト
#
# 機能: .kiro/specs/ 内の有効な仕様に対して、未完了タスクがなくなるまで
#       /impl コマンドをループ実行し、各タスク完了後にコミットする
#
# 前提条件:
#   - .kiro/specs/ に archives 以外のディレクトリが1つだけ存在
#   - spec.json の ready_for_implementation が true
#   - tasks.md が存在
#
# 使い方:
#   ./scripts/auto-impl.sh
################################################################################

set -euo pipefail

# ============================================================================
# 設定
# ============================================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
SPECS_DIR="${PROJECT_DIR}/.kiro/specs"
MAX_ITERATIONS=100

# 検出されたフィーチャー名(グローバル)
FEATURE_NAME=""
FEATURE_DIR=""

# ============================================================================
# シグナルハンドラー(Ctrl+C 対応)
# ============================================================================

cleanup() {
    echo ""
    echo "Interrupted by user (Ctrl+C)"
    exit 130
}

trap cleanup SIGINT SIGTERM

# ============================================================================
# エラー終了
# ============================================================================

error_exit() {
    echo "ERROR: $1"
    exit 1
}

# ============================================================================
# フィーチャー検出: archives 以外のディレクトリを検出
# ============================================================================

detect_active_feature() {
    local dirs=()

    # .kiro/specs/ 内のディレクトリを取得(archives を除外)
    while IFS= read -r -d '' dir; do
        local name=$(basename "$dir")
        if [ "$name" != "archives" ]; then
            dirs+=("$name")
        fi
    done < <(find "$SPECS_DIR" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null)

    # ディレクトリ数をチェック
    if [ ${#dirs[@]} -eq 0 ]; then
        error_exit "No active feature found in $SPECS_DIR (excluding archives)"
    fi

    if [ ${#dirs[@]} -gt 1 ]; then
        error_exit "Multiple features found: ${dirs[*]}. Expected exactly one (excluding archives)"
    fi

    FEATURE_NAME="${dirs[0]}"
    FEATURE_DIR="${SPECS_DIR}/${FEATURE_NAME}"

    echo "Detected feature: $FEATURE_NAME"
}

# ============================================================================
# 仕様バリデーション: spec.json の ready_for_implementation チェック
# ============================================================================

validate_spec() {
    local spec_file="${FEATURE_DIR}/spec.json"

    if [ ! -f "$spec_file" ]; then
        error_exit "spec.json not found at $spec_file"
    fi

    # jq があれば使用、なければ grep でフォールバック
    local ready="false"
    if command -v jq &> /dev/null; then
        ready=$(jq -r '.ready_for_implementation // false' "$spec_file")
    else
        if grep -q '"ready_for_implementation"[[:space:]]*:[[:space:]]*true' "$spec_file"; then
            ready="true"
        fi
    fi

    if [ "$ready" != "true" ]; then
        error_exit "Feature '$FEATURE_NAME' is not ready for implementation (ready_for_implementation != true)"
    fi

    echo "Spec validation passed: ready_for_implementation = true"
}

# ============================================================================
# タスクファイル存在チェック
# ============================================================================

validate_tasks_file() {
    local tasks_file="${FEATURE_DIR}/tasks.md"

    if [ ! -f "$tasks_file" ]; then
        error_exit "tasks.md not found at $tasks_file"
    fi

    echo "Tasks file found: $tasks_file"
}

# ============================================================================
# 未完了タスクチェック
# ============================================================================

has_pending_tasks() {
    local tasks_file="${FEATURE_DIR}/tasks.md"

    # "- [ ]" で始まる行をカウント(未完了タスク)
    local count=$(grep -c '^- \[ \]' "$tasks_file" 2>/dev/null || echo "0")

    if [ "$count" -gt 0 ]; then
        return 0  # 未完了タスクあり
    else
        return 1  # 未完了タスクなし
    fi
}

# ============================================================================
# 未完了タスク数を取得
# ============================================================================

get_pending_task_count() {
    local tasks_file="${FEATURE_DIR}/tasks.md"
    grep -c '^- \[ \]' "$tasks_file" 2>/dev/null || echo "0"
}

# ============================================================================
# /impl コマンド実行
# ============================================================================

run_impl() {
    local iteration=$1

    echo ""
    echo "=== Iteration $iteration: Running /impl ==="

    # claude コマンド実行
    if ! claude -p '/impl' --dangerously-skip-permissions; then
        echo "ERROR: /impl failed at iteration $iteration"
        return 1
    fi

    echo "/impl completed successfully"
    return 0
}

# ============================================================================
# コミット実行(テスト・Lint・型チェック確認後)
# ============================================================================

run_commit() {
    local iteration=$1

    echo ""
    echo "=== Iteration $iteration: Running commit check ==="

    # テスト・Lint・型チェック確認後にコミット
    if ! claude -p 'テストやLint、型チェックでエラーが出ないことを確認したら未コミットの内容をコミットする' --dangerously-skip-permissions; then
        echo "ERROR: Commit check failed at iteration $iteration"
        return 1
    fi

    echo "Commit completed successfully"
    return 0
}

# ============================================================================
# メインワークフロー
# ============================================================================

run_workflow() {
    echo "Starting auto-impl workflow"
    echo ""

    # 前提条件チェック
    detect_active_feature
    validate_spec
    validate_tasks_file

    local iteration=0
    local initial_count=$(get_pending_task_count)

    echo ""
    echo "Initial pending tasks: $initial_count"

    # メインループ
    while has_pending_tasks; do
        iteration=$((iteration + 1))

        # 無限ループ防止
        if [ $iteration -gt $MAX_ITERATIONS ]; then
            error_exit "Maximum iterations ($MAX_ITERATIONS) reached. Stopping."
        fi

        local remaining=$(get_pending_task_count)
        echo ""
        echo "=========================================="
        echo "Iteration $iteration (remaining: $remaining tasks)"
        echo "=========================================="

        # /impl 実行
        if ! run_impl "$iteration"; then
            error_exit "/impl failed. Manual intervention required."
        fi

        # コミット実行
        if ! run_commit "$iteration"; then
            error_exit "Commit failed. Manual intervention required."
        fi

        # API制限対策で少し待機
        sleep 3
    done

    echo ""
    echo "=========================================="
    echo "All tasks completed!"
    echo "Total iterations: $iteration"
    echo "=========================================="
}

# ============================================================================
# メイン処理
# ============================================================================

main() {
    run_workflow && exit 0 || exit 1
}

main "$@"

Discussion