mcpField
S
specs-e2e-verification

specs-e2e-verification

github.com/giuseppe-trisciuoglioテスト・品質365874 回インストール7 週間前に更新
リポジトリ

specs-e2e-verification について

実際の実行時動作に基づいて、実装済みの仕様を検証するスキルです。ローカル環境を判定して起動し、`[IMP]` の受け入れ基準からテストを作成、結果を基準に対応付けてレポートを生成し、環境を終了します。

主な機能

  1. 01アプリケーション種別とポートを検出
  2. 02ローカル環境を起動して確認
  3. 03`[IMP]` 基準からテストを作成
  4. 04テスト結果を基準に対応付け
  5. 05検証レポートを生成
  6. 06実行環境を終了

活用シーン

  1. 01実装後の機能検証
  2. 02稼働中の API 動作確認
  3. 03仕様完了前の検証記録作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill specs-e2e-verification

インストール後、Claude Code で/specs-e2e-verificationを入力して読み込まれたか確認します。

同梱ファイルreferences/test-execution-patterns.md

詳細

仕様の E2E 検証

概要

仕様の実装とクリーンアップが完了した後に、実環境での検証を行います。このスキルは、ユニットテスト済みのコードと、実行時に観察できる動作とのギャップを次の手順で埋めます。

  1. プロジェクトファイルからアプリケーションの種類を検出する
  2. ローカルのランタイムを起動する(Docker Compose、開発サーバー、Spring Boot など)
  3. 機能仕様の [IMP] 受け入れ条件からテストを導出する
  4. 実際のテストを実行する(curl、Playwright、コンピューター操作)
  5. 結果を受け入れ条件に対応付ける
  6. 検証レポートを生成する
  7. 環境を終了する

入力:docs/specs/[id]/(機能仕様とタスクを含む仕様フォルダー)
出力:docs/specs/[id]/e2e-report-YYYY-MM-DD-HHMMSS.md

使用するタイミング

  • 機能が実際に動作することを確認するため、specs.task-implementation と specs.code-cleanup の後に使用します。
  • 開発者が「実際にテストして」「API が本当に動作するか検証して」「E2E チェックを実行して」「受け入れ条件を実環境で検証して」と依頼した場合に使用します。
  • 仕様を完了とする前に、機能の完了を示す証拠を生成するために使用します。
  • ユニットテスト、静的解析、コードレビューには使用しないでください。このスキルは実行時の動作検証のみを対象とします。

引数

引数必須説明
--specはい仕様フォルダーへのパス(例:docs/specs/001-feature/)
--taskいいえ検証範囲を限定する特定のタスク ID(例:TASK-003)
--keep-aliveいいえ指定した場合、終了処理を省略して環境を起動したままにします
--timeoutいいえ起動とテストのタイムアウト秒数(デフォルト:120)
--insecureいいえ指定した場合、curl で -k / --insecure の使用を許可します(TLS バイパスのオプトイン)

ベストプラクティス

  • 安全を最優先:破壊的なコマンド(rm -rf、docker system prune、sudo)は実行しないでください。
  • 推測せずに検出:ファイルのヒューリスティックを使ってアプリケーションの種類を判定し、曖昧な場合にのみユーザーに確認してください。
  • 受け入れ条件に基づく:すべてのテストは、機能仕様の [IMP] 受け入れ条件にたどれる必要があります。
  • クリーンアップ:--keep-alive が指定されていない限り、必ず終了処理を行ってください。残存プロセスがある場合は警告してください。
  • コードを変更しない:このスキルはソースコードに対して読み取り専用です。レポートは作成できますが、ロジックにパッチを適用することはありません。
  • TodoWrite を使用:8 つのフェーズすべての進捗を追跡してください。

手順

フェーズ 1:引数の解析とコンテキストの読み込み
  1. $ARGUMENTS を解析します。

    • --spec(必須):仕様フォルダーのパスです。ディレクトリが存在し、機能仕様ファイル(YYYY-MM-DD--*.md)が少なくとも 1 つ含まれていることを検証します。存在しないか無効な場合は、エラーで中止します。
    • --task(任意):タスク ID フィルターです(例:TASK-003)。指定された場合は、仕様フォルダー内に tasks/<task-id>.md が存在することを検証します。
    • --keep-alive(任意):ブール値のフラグです。指定された場合、最後の終了処理を省略します。
    • --timeout(任意):秒数を表す正の整数です。デフォルトは 120 です。値が正の整数であることを検証し、そうでない場合はエラーで中止します。
    • --insecure(任意):ブール値のフラグです。指定された場合、自己署名証明書を使うローカル開発では curl コマンドで -k / --insecure を使用できます。デフォルトでは TLS バイパスは禁止されています(REQ-NR003)。
  2. 機能仕様を読み込み、次の内容を抽出します。

    • 分類タグ([IMP]、[SEF]、[EXT])が付いたすべての受け入れ条件
    • 実行時テストを生成するのは [IMP] 条件のみです
  3. --task が指定されている場合は、タスクファイルを読み込み、その provides ファイルと関連する受け入れ条件に範囲を限定します。

  4. TodoWrite を使って、8 つのフェーズすべての Todo リストを作成します。

フェーズ 1.5:セキュリティ検証ゲート

コマンドを実行する前に、次のセキュリティチェックを実行します。

  1. コマンドのホワイトリストチェック:

    • フェーズ 3 で導出した起動コマンドは、references/test-execution-patterns.md に記載されたパターンに一致する必要があります。
    • コマンドがホワイトリストに含まれていない場合 → AskUserQuestion を使用して、実行前にユーザーへ明示的な確認を求めます。
    • ホワイトリストに含まれるコマンドは次のとおりです:docker compose up -d --build、./mvnw spring-boot:run、./gradlew bootRun、npm run dev、npm run start:dev、npm start、cargo tauri build --debug、cargo tauri dev、npm run electron:dev、npx electron .、open *.app、および同等のローカルプロセス起動コマンドです。
    • sudo、rm -rf、docker system prune、mkfs、dd、または同様の破壊的操作を含むコマンドはホワイトリストに含まれず、拒否する必要があります。
  2. 禁止パターンのスキャン(REQ-NR001):

    • 導出した起動コマンドと生成したすべてのテストコマンドを、次のパターンについてスキャンします:
      • sudo → 次のメッセージを表示して中止します:"Forbidden: sudo is not permitted during E2E verification."
      • rm -rf → 次のメッセージを表示して中止します:"Forbidden: rm -rf is not permitted during E2E verification."
      • docker system prune → 次のメッセージを表示して中止します:"Forbidden: docker system prune is not permitted during E2E verification."
      • データベース、ボリューム、またはローカルデータを対象とする rm、drop、destroy、prune → 次のメッセージを表示して中止します:"Forbidden: destructive data operations are not permitted."
    • 禁止パターンが検出された場合 → 直ちに中止し、起動に進んではなりません。
  3. TLS 適用チェック(REQ-NR003):

    • 生成した curl コマンドについて:
      • -k または --insecure が含まれ、かつ --insecure が渡されていない場合 → 次のメッセージを表示して中止します:"Forbidden: curl TLS bypass (-k / --insecure) is disabled by default. Pass --insecure to opt-in."
      • --insecure が渡されている場合 → 警告を記録します:"WARNING: TLS certificate verification is disabled. Use only for local development."
  4. データ整合性の事前チェック(REQ-NR004):

    • 起動コマンドを実行する前に、既存データを上書きまたは削除するパターン(例:rm、drop、prune、ボリューム削除フラグ)がないか確認します。
    • コマンドが既存のデータベース、ボリューム、またはローカルデータディレクトリを変更する場合 → 次のメッセージを表示して中止します:"Forbidden: startup commands must not overwrite or delete existing data."
フェーズ 2:アプリケーションの種類を検出し、ポートを特定する
  1. .git を含むディレクトリ、または --spec の親ディレクトリを PROJECT_ROOT に設定します。

  2. 以下のヒューリスティックを使って PROJECT_ROOT 内の設定ファイルを調べます(記載順に実行します)。

    Docker 管理(最優先):

    [ -f "$PROJECT_ROOT/docker-compose.yml" ] || [ -f "$PROJECT_ROOT/docker-compose.yaml" ] || [ -f "$PROJECT_ROOT/compose.yml" ]
    

    これらのファイルのいずれかが存在する場合は、他のフレームワーク設定にかかわらず、Docker 管理に分類します。

    JVM/Spring Boot:

    [ -f "$PROJECT_ROOT/pom.xml" ] || [ -f "$PROJECT_ROOT/build.gradle" ] || [ -f "$PROJECT_ROOT/build.gradle.kts" ]
    

    AND ソースディレクトリが存在することを確認します:

    [ -d "$PROJECT_ROOT/src/main/java" ]
    

    両方の条件を満たす場合は、JVM ベースのサービスに分類します。

    NestJS:

    [ -f "$PROJECT_ROOT/package.json" ] && grep -q '"@nestjs/core"' "$PROJECT_ROOT/package.json"
    

    条件を満たす場合は、NestJSに分類します。

    Web SPA(React/Vue/Angular):

    [ -f "$PROJECT_ROOT/package.json" ] && ( grep -q '"react"' "$PROJECT_ROOT/package.json" || grep -q '"vue"' "$PROJECT_ROOT/package.json" || grep -q '"@angular/core"' "$PROJECT_ROOT/package.json" )
    

    条件を満たす場合は、Web SPAに分類します。

    デスクトップアプリ:

    [ -f "$PROJECT_ROOT/src-tauri/Cargo.toml" ] || ( [ -f "$PROJECT_ROOT/package.json" ] && grep -q '"electron"' "$PROJECT_ROOT/package.json" ) || [ -n "$(find "$PROJECT_ROOT" -maxdepth 2 -name '*.csproj' -print -quit 2>/dev/null)" ]
    

    条件を満たす場合は、デスクトップアプリに分類します。

    Python:

    [ -f "$PROJECT_ROOT/requirements.txt" ] || [ -f "$PROJECT_ROOT/pyproject.toml" ] || [ -f "$PROJECT_ROOT/app.py" ] || [ -f "$PROJECT_ROOT/manage.py" ]
    

    条件を満たす場合は、Pythonに分類します。

  3. 優先順位のルールを適用します:

    • Docker Compose は常に優先されます:Docker Compose ファイルとフレームワーク設定の両方が存在する場合は、Docker 管理に分類します。Compose スタックが実行環境を定義します。
    • Docker 以外の設定が複数検出された場合(例:Docker Compose なしで pom.xml と package.json の両方が存在する場合):
      • 仕様のドメインからバックエンドとフロントエンドのどちらかが明確に分かる場合(例:仕様のタイトルに「API」、「backend」、「service」が含まれる場合)は、JVM または NestJS を優先します。
      • 仕様のドメインからフロントエンドであることが明確に分かる場合(例:仕様のタイトルに「UI」、「page」、「component」が含まれる場合)は、Web SPA を優先します。
      • それでも判断できない場合は、ユーザーへの質問に進みます(ステップ 4)。
  4. 認識可能な設定が見つからない場合、または Docker 以外の設定が複数存在し、仕様のドメインが曖昧な場合は、AskUserQuestion を使い、次の選択肢をそのまま提示します:

    • "REST API"
    • "Web SPA"
    • "Desktop"
    • "Skip"
  5. ポートの特定:アプリケーションの種類が判明したら、次の順にフレームワーク設定ファイルを調べ、対象ポートを特定します。

    • Vite プロジェクト(vite.config.ts または vite.config.js):

      grep -oE 'port:\s*[0-9]+' "$PROJECT_ROOT/vite.config.ts" 2>/dev/null | grep -oE '[0-9]+' || \
      grep -oE 'port:\s*[0-9]+' "$PROJECT_ROOT/vite.config.js" 2>/dev/null | grep -oE '[0-9]+'
      
    • Spring Boot(application.yml):

      grep -A5 '^server:' "$PROJECT_ROOT/src/main/resources/application.yml" 2>/dev/null | grep 'port:' | head -1 | tr -dc '0-9'
      
    • Spring Boot(application.properties):

      grep '^server.port=' "$PROJECT_ROOT/src/main/resources/application.properties" 2>/dev/null | cut -d= -f2 | tr -dc '0-9'
      
    • Node.js/package.json のスクリプト:

      grep -oE -- '--port [0-9]+' "$PROJECT_ROOT/package.json" 2>/dev/null | grep -oE '[0-9]+' | head -1
      

      スクリプト内の PORT 環境変数も確認します:

      grep -oE 'PORT=[0-9]+' "$PROJECT_ROOT/package.json" 2>/dev/null | grep -oE '[0-9]+' | head -1
      
    • フォールバックのデフォルト値(どの設定ファイルにもポートが見つからない場合):

      アプリケーションの種類デフォルトポート
      Node.js/NestJS3000
      Spring Boot(JVM)8080
      Angular4200
      Vite(React/Vue)5173
      Python8000
  6. 検出した種類と特定したポートを記録します。どちらもレポートに記載されます。

フェーズ 3:環境を起動し、準備完了を待つ

コマンドの対応表については、references/test-execution-patterns.md(このスキルに同梱)を読みます。検出結果に基づいて処理します。

データ整合性の事前確認(REQ-NR004):起動コマンドを実行する前に、既存のデータベース、ボリューム、またはローカルデータを上書きまたは削除するパターン(例:rm、--volumes、prune、drop)が含まれていないことを確認します。破壊的なパターンが検出された場合は、直ちに次のメッセージを表示して中止します:"起動を中止しました:コマンドによって既存のデータが破壊される可能性があります。"

  1. ランタイム状態を初期化します:

    • STARTUP_COMMAND="" — 環境の起動に使用する正確なコマンド
    • HEALTH_CHECK_METHOD="" — 準備完了をどのように判定したかの説明
    • STARTUP_LOGS_FILE="$(mktemp)" — 起動時の stdout/stderr を記録する一時ファイル
    • STARTUP_TIMEOUT="${TIMEOUT:-120}" — 準備完了まで待機する秒数
    • START_TIME="$(date +%s)"
    • STARTUP_PID="" — バックグラウンドプロセスの PID(ローカルプロセス用)
  2. 起動前のポート確認(すべての種類):

    if lsof -i :"$TARGET_PORT" >/dev/null 2>&1 || nc -z localhost "$TARGET_PORT" 2>/dev/null; then
        echo "Port $TARGET_PORT is already in use."
        EXISTING_PID=$(lsof -ti:"$TARGET_PORT" | head -n1)
        EXISTING_CMD=$(ps -p "$EXISTING_PID" -o comm= 2>/dev/null || echo "unknown")
        if [ "$APP_TYPE" = "Docker-managed" ] && docker ps --format '{{.Names}}' 2>/dev/null | grep -q "$EXISTING_CMD"; then
            echo "Existing Docker container detected on port $TARGET_PORT; reusing it."
            STARTUP_COMMAND="(existing container reused)"
        elif [ "$APP_TYPE" = "JVM-based service" ] && echo "$EXISTING_CMD" | grep -q "java"; then
            echo "Existing Java process detected on port $TARGET_PORT; reusing it."
            STARTUP_COMMAND="(existing Java process reused)"
        else
            echo "Port $TARGET_PORT is occupied by an unrelated process ($EXISTING_CMD). Free the port and retry."
            exit 1
        fi
    fi
    
  3. Docker 管理下での起動:

    • Docker デーモンに接続できることを確認します:
      if ! docker info >/dev/null 2>&1; then
          echo "Docker を利用できません。Docker Desktop を起動するか、フレームワークを直接起動してください。"
          exit 1
      fi
      
    • STARTUP_COMMAND="docker compose up -d --build" を設定します
    • 次を実行します:
      cd "$PROJECT_ROOT" && docker compose up -d --build >> "$STARTUP_LOGS_FILE" 2>&1
      
    • ヘルスチェックループ(タイムアウトを適用):
      HEALTH_CHECK_METHOD="docker compose ps --format json"
      READY=false
      while [ $(( $(date +%s) - START_TIME )) -lt "$STARTUP_TIMEOUT" ]; do
          # オプション A:Docker ネイティブのヘルスステータス(JSON 配列と NDJSON の両方に対応)
          if docker compose ps --format json 2>/dev/null | jq -s -e '.[] | select(.Health=="healthy")' >/dev/null 2>&1; then
              READY=true
              HEALTH_CHECK_METHOD="docker compose ps (HEALTHCHECK=healthy)"
              break
          fi
          # オプション B:HEALTHCHECK が定義されていない場合のフォールバックとしてポートをポーリング
          if curl -sf "http://localhost:${TARGET_PORT}" >/dev/null 2>&1; then
              READY=true
              HEALTH_CHECK_METHOD="port polling via curl on localhost:${TARGET_PORT} (every 3s)"
              break
          fi
          sleep 3
      done
      if [ "$READY" != "true" ]; then
          echo "Docker Compose の起動がタイムアウトしました(${STARTUP_TIMEOUT}s)。"
          docker compose logs --tail=50 >> "$STARTUP_LOGS_FILE" 2>&1
          # 孤立したコンテナを避けるため、クリーンアップを試みます(REQ-NR006)
          docker compose down >/dev/null 2>&1 || true
          exit 1
      fi
      
  4. Spring Boot の起動:

    • ビルドツールを判定し、ログを記録しながらバックグラウンドで起動します:
      if [ -f "$PROJECT_ROOT/pom.xml" ]; then
          cd "$PROJECT_ROOT" && ./mvnw spring-boot:run -Dspring-boot.run.profiles=e2e >> "$STARTUP_LOGS_FILE" 2>&1 &
          STARTUP_PID=$!
          STARTUP_COMMAND="./mvnw spring-boot:run -Dspring-boot.run.profiles=e2e"
      elif [ -f "$PROJECT_ROOT/build.gradle" ] || [ -f "$PROJECT_ROOT/build.gradle.kts" ]; then
          cd "$PROJECT_ROOT" && ./gradlew bootRun --args='--spring.profiles.active=e2e' >> "$STARTUP_LOGS_FILE" 2>&1 &
          STARTUP_PID=$!
          STARTUP_COMMAND="./gradlew bootRun --args='--spring.profiles.active=e2e'"
      fi
      
    • ヘルスチェックループ(タイムアウトを適用):
      HEALTH_CHECK_METHOD="Spring Boot actuator /actuator/health"
      READY=false
      while [ $(( $(date +%s) - START_TIME )) -lt "$STARTUP_TIMEOUT" ]; do
          # オプション A:Actuator のヘルスエンドポイント
          if curl -sf "http://localhost:${TARGET_PORT}/actuator/health" >/dev/null 2>&1; then
              READY=true
              HEALTH_CHECK_METHOD="Spring Boot actuator /actuator/health"
              break
          fi
          # オプション B:フォールバックとしてポートの応答を確認
          if nc -z localhost "$TARGET_PORT" 2>/dev/null; then
              READY=true
              HEALTH_CHECK_METHOD="port polling via nc on localhost:${TARGET_PORT} (every 3s)"
              break
          fi
          # バックグラウンドプロセスが早期に終了した場合は、ただちに失敗とする
          if [ -n "$STARTUP_PID" ] && ! kill -0 "$STARTUP_PID" 2>/dev/null; then
              echo "Spring Boot process exited before reaching healthy state."
              break
          fi
          sleep 3
      done
      if [ "$READY" != "true" ]; then
          echo "Startup timeout (${STARTUP_TIMEOUT}s) exceeded for Spring Boot."
          # レポート用に起動ログの末尾を取得
          tail -n 100 "$STARTUP_LOGS_FILE" >> "$STARTUP_LOGS_FILE".final 2>&1 || true
          # クリーンアップを試みる(REQ-NR006)
          [ -n "$STARTUP_PID" ] && kill -TERM "$STARTUP_PID" 2>/dev/null || true
          exit 1
      fi
      
  5. NestJS / Node.js の起動:

    • 原因が分かりにくいエラーを避けるため、node_modules が存在することを確認します:
      if [ ! -d "$PROJECT_ROOT/node_modules" ]; then
          echo "node_modules not found. Run 'npm install' before verification."
          exit 1
      fi
      
    • package.json のスクリプトから起動コマンドを判定します:
      if [ "$APP_TYPE" = "NestJS" ] && grep -q '"start:dev"' "$PROJECT_ROOT/package.json" 2>/dev/null; then
          cd "$PROJECT_ROOT" && npm run start:dev >> "$STARTUP_LOGS_FILE" 2>&1 &
          STARTUP_PID=$!
          STARTUP_COMMAND="npm run start:dev"
      elif grep -q '"dev"' "$PROJECT_ROOT/package.json" 2>/dev/null; then
          cd "$PROJECT_ROOT" && npm run dev >> "$STARTUP_LOGS_FILE" 2>&1 &
          STARTUP_PID=$!
          STARTUP_COMMAND="npm run dev"
      else
          cd "$PROJECT_ROOT" && npm start >> "$STARTUP_LOGS_FILE" 2>&1 &
          STARTUP_PID=$!
          STARTUP_COMMAND="npm start"
      fi
      
    • ヘルスチェックループ(タイムアウトを適用):
      HEALTH_CHECK_METHOD="port polling via nc/curl on localhost:${TARGET_PORT} (every 3s)"
      READY=false
      while [ $(( $(date +%s) - START_TIME )) -lt "$STARTUP_TIMEOUT" ]; do
          # Option A: HTTP readiness via curl
          if curl -sf "http://localhost:${TARGET_PORT}" >/dev/null 2>&1; then
              READY=true
              HEALTH_CHECK_METHOD="curl on localhost:${TARGET_PORT}"
              break
          fi
          # Option B: Raw port readiness via nc
          if nc -z localhost "$TARGET_PORT" 2>/dev/null; then
              READY=true
              HEALTH_CHECK_METHOD="nc -z localhost:${TARGET_PORT}"
              break
          fi
          # Fail fast if the background process exited early
          if [ -n "$STARTUP_PID" ] && ! kill -0 "$STARTUP_PID" 2>/dev/null; then
              echo "Node.js/NestJS process exited before reaching healthy state."
              break
          fi
          sleep 3
      done
      if [ "$READY" != "true" ]; then
          echo "Startup timeout (${STARTUP_TIMEOUT}s) exceeded for Node.js/NestJS."
          tail -n 100 "$STARTUP_LOGS_FILE" >> "$STARTUP_LOGS_FILE".final 2>&1 || true
          # Attempt cleanup (REQ-NR006)
          [ -n "$STARTUP_PID" ] && kill -TERM "$STARTUP_PID" 2>/dev/null || true
          exit 1
      fi
      
    • コンソールの準備完了検出:準備完了を確認した後、$STARTUP_LOGS_FILE をスキャンして、一般的なサーバー準備完了メッセージ(例:Nest application successfully started、Local:、ready in、Server running)を探し、最初に一致した行を起動成功の証拠としてレポートに記録します。
  6. Python(FastAPI / Django / Flask)(references/test-execution-patterns.md を参照):

    • uvicorn main:app --reload、python manage.py runserver、または flask run を実行します
    • ポートの準備が整うまで待機します
  7. デスクトップアプリ(Tauri / Electron / .NET MAUI)(references/test-execution-patterns.md を参照):

    フレームワークの検出:

    DESKTOP_FRAMEWORK=""
    if [ -f "$PROJECT_ROOT/src-tauri/Cargo.toml" ]; then
        DESKTOP_FRAMEWORK="tauri"
    elif [ -f "$PROJECT_ROOT/package.json" ] && grep -q '"electron"' "$PROJECT_ROOT/package.json"; then
        DESKTOP_FRAMEWORK="electron"
    elif [ -n "$(find "$PROJECT_ROOT" -maxdepth 2 -name '*.csproj' -print -quit 2>/dev/null)" ]; then
        DESKTOP_FRAMEWORK="dotnet-maui"
    fi
    

    ビルド手順(ビルド済みのデバッグバイナリが存在しない場合、またはソースがターゲットより新しい場合に実行):

    • Tauri:
      if [ "$DESKTOP_FRAMEWORK" = "tauri" ]; then
          if ! command -v cargo >/dev/null 2>&1; then
              echo "ERROR: Rust/Cargo is not installed. Tauri build requires cargo."
              exit 1
          fi
          BUILD_COMMAND="cargo tauri build --debug"
          cd "$PROJECT_ROOT" && $BUILD_COMMAND >> "$STARTUP_LOGS_FILE" 2>&1
          BUILD_EXIT_CODE=$?
          if [ "$BUILD_EXIT_CODE" -ne 0 ]; then
              echo "Tauri build failed (exit code $BUILD_EXIT_CODE). See startup logs for details."
              exit 1
          fi
          STARTUP_COMMAND="$BUILD_COMMAND (build succeeded)"
      fi
      
    • Electron:
      if [ "$DESKTOP_FRAMEWORK" = "electron" ]; then
          if [ ! -d "$PROJECT_ROOT/node_modules" ]; then
              echo "ERROR: node_modules not found. Run 'npm install' before verification."
              exit 1
          fi
          BUILD_COMMAND="(no separate build required for dev mode)"
          STARTUP_COMMAND="npm run electron:dev"
          if grep -q '"electron:build"' "$PROJECT_ROOT/package.json" 2>/dev/null; then
              BUILD_COMMAND="npm run electron:build"
              cd "$PROJECT_ROOT" && $BUILD_COMMAND >> "$STARTUP_LOGS_FILE" 2>&1
              BUILD_EXIT_CODE=$?
              if [ "$BUILD_EXIT_CODE" -ne 0 ]; then
                  echo "Electron build failed (exit code $BUILD_EXIT_CODE). See startup logs for details."
                  exit 1
              fi
          fi
      fi
      

    ビルド済みアプリケーションのバイナリを起動する:

    • Tauri(macOS):

      APP_BUNDLE=$(find "$PROJECT_ROOT/src-tauri/target/debug/bundle" -name "*.app" -print -quit 2>/dev/null)
      if [ -n "$APP_BUNDLE" ]; then
          open "$APP_BUNDLE" >> "$STARTUP_LOGS_FILE" 2>&1 &
          STARTUP_PID=$!
          STARTUP_COMMAND="open $APP_BUNDLE"
      else
          DEV_BINARY=$(find "$PROJECT_ROOT/src-tauri/target/debug" -maxdepth 1 -type f -executable ! -name '*.dylib' ! -name '*.so' -print -quit 2>/dev/null)
          if [ -n "$DEV_BINARY" ]; then
              "$DEV_BINARY" >> "$STARTUP_LOGS_FILE" 2>&1 &
              STARTUP_PID=$!
              STARTUP_COMMAND="$DEV_BINARY"
          else
              cargo tauri dev >> "$STARTUP_LOGS_FILE" 2>&1 &
              STARTUP_PID=$!
              STARTUP_COMMAND="cargo tauri dev"
          fi
      fi
      
    • Tauri(Linux):

      APP_BINARY=$(find "$PROJECT_ROOT/src-tauri/target/debug" -maxdepth 1 -type f -executable ! -name '*.so' -print -quit 2>/dev/null)
      if [ -n "$APP_BINARY" ]; then
          "$APP_BINARY" >> "$STARTUP_LOGS_FILE" 2>&1 &
          STARTUP_PID=$!
          STARTUP_COMMAND="$APP_BINARY"
      else
          cargo tauri dev >> "$STARTUP_LOGS_FILE" 2>&1 &
          STARTUP_PID=$!
          STARTUP_COMMAND="cargo tauri dev"
      fi
      
    • Electron:

      if grep -q '"electron:dev"' "$PROJECT_ROOT/package.json" 2>/dev/null; then
          cd "$PROJECT_ROOT" && npm run electron:dev >> "$STARTUP_LOGS_FILE" 2>&1 &
          STARTUP_PID=$!
          STARTUP_COMMAND="npm run electron:dev"
      elif grep -q '"start"' "$PROJECT_ROOT/package.json" 2>/dev/null; then
          cd "$PROJECT_ROOT" && npm start >> "$STARTUP_LOGS_FILE" 2>&1 &
          STARTUP_PID=$!
          STARTUP_COMMAND="npm start"
      else
          cd "$PROJECT_ROOT" && npx electron . >> "$STARTUP_LOGS_FILE" 2>&1 &
          STARTUP_PID=$!
          STARTUP_COMMAND="npx electron ."
      fi
      

    ヘルスチェック(プロセスの出現を確認、タイムアウトを適用):

    HEALTH_CHECK_METHOD="process polling via ps/kill -0 (every 3s)"
    READY=false
    while [ $(( $(date +%s) - START_TIME )) -lt "$STARTUP_TIMEOUT" ]; do
        if [ -n "$STARTUP_PID" ] && kill -0 "$STARTUP_PID" 2>/dev/null; then
            READY=true
            HEALTH_CHECK_METHOD="process PID ${STARTUP_PID} confirmed alive"
            break
        fi
        sleep 3
    done
    if [ "$READY" != "true" ]; then
        echo "Startup timeout (${STARTUP_TIMEOUT}s) exceeded for Desktop app."
        tail -n 100 "$STARTUP_LOGS_FILE" >> "$STARTUP_LOGS_FILE".final 2>&1 || true
        [ -n "$STARTUP_PID" ] && kill -TERM "$STARTUP_PID" 2>/dev/null || true
        exit 1
    fi
    
  8. 起動後の記録処理(すべての種類):

    • レポートのメタデータにSTARTUP_COMMANDとHEALTH_CHECK_METHODを記録します(AC-010)。
    • STARTUP_LOGS_FILEの内容をRaw Outputとしてレポートに追記します(REQ-020)。
    • 起動に失敗した場合は、理由を問わず、利用可能なすべてのログを取得し、明確なエラーメッセージを出して中止します。
フェーズ4:テストの生成と実行

重要:[IMP]の受け入れ基準のみをテストします。それぞれを、具体的な実行時アクションに1つ以上変換します。

REST API テスト(curl)

前提条件の確認:テストを生成する前に、curlがインストールされていることを確認します:

if ! command -v curl >/dev/null 2>&1; then
    echo "ERROR: curl is not installed."
    echo "Install instructions:"
    echo "  macOS:    brew install curl"
    echo "  Ubuntu:   sudo apt-get install curl"
    echo "  Windows:  choco install curl   or   winget install curl"
    exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
    echo "WARNING: jq is not installed. JSON body assertions will fall back to grep (less precise)."
    echo "Install instructions:"
    echo "  macOS:    brew install jq"
    echo "  Ubuntu:   sudo apt-get install jq"
    echo "  Windows:  choco install jq   or   winget install jqlang.jq"
fi

1. エンドポイントのヒントについて[IMP] ACを解析する 仕様内の各 [IMP] 受け入れ基準について、以下を実行します。

  • キーワード GET、POST、PUT、PATCH、DELETE(大文字と小文字を区別しません)を検索して、HTTP メソッドを抽出します。
  • /api/users、/v1/health など、/ の後に英数字のセグメントが続くパターンを検索して、エンドポイントパスを抽出します。
  • 数値パターン 2xx、3xx、4xx、5xx、または 200、201、204、400、401、403、404、500 などの特定のコードを検索して、期待される HTTP ステータスコードを抽出します。
  • application/json、text/plain、text/html などを検索して、期待される Content-Type を抽出します。
  • AC のテキストから、期待されるレスポンス本文のヒント(フィールド名、配列の有無、文字列値)を抽出します。

[IMP] AC に解析可能なエンドポイントパスとメソッドが含まれていない場合は、MANUAL CHECK REQUIRED とマークし、次の基準に進みます。

2. 認証情報を検出する

curl コマンドを作成する前に、PROJECT_ROOT 内の以下のファイルを順番にスキャンして、テスト用認証情報を探します。

ファイルキーパターン
.env.testE2E_AUTH_TOKEN=...、E2E_USERNAME=...、E2E_PASSWORD=...
.env.localE2E_AUTH_TOKEN=...、E2E_USERNAME=...、E2E_PASSWORD=...
application-test.ymle2e.auth-token: ...、e2e.username: ...、e2e.password: ...
application-test.propertiese2e.auth-token=...、e2e.username=...、e2e.password=...
e2e.credentials.jsonトップレベルのキー E2E_AUTH_TOKEN、E2E_USERNAME、E2E_PASSWORD

検出ロジック:

# .env files
[ -f "$PROJECT_ROOT/.env.test" ] && export $(grep -E '^(E2E_AUTH_TOKEN|E2E_USERNAME|E2E_PASSWORD)=' "$PROJECT_ROOT/.env.test" | xargs)
[ -f "$PROJECT_ROOT/.env.local" ] && export $(grep -E '^(E2E_AUTH_TOKEN|E2E_USERNAME|E2E_PASSWORD)=' "$PROJECT_ROOT/.env.local" | xargs)

# Spring YAML
[ -f "$PROJECT_ROOT/src/main/resources/application-test.yml" ] && \
  E2E_AUTH_TOKEN=$(grep -A1 'e2e:' "$PROJECT_ROOT/src/main/resources/application-test.yml" | grep 'auth-token:' | sed 's/.*: *//')

# Spring properties
[ -f "$PROJECT_ROOT/src/main/resources/application-test.properties" ] && \
  E2E_AUTH_TOKEN=$(grep '^e2e.auth-token=' "$PROJECT_ROOT/src/main/resources/application-test.properties" | cut -d= -f2-)

# JSON credentials file
[ -f "$PROJECT_ROOT/e2e.credentials.json" ] && \
  E2E_AUTH_TOKEN=$(jq -r '.E2E_AUTH_TOKEN // empty' "$PROJECT_ROOT/e2e.credentials.json")

すべてのファイルをスキャンしても認証情報が見つからず、かつ AC のテキストから認証が必要だと判断できる場合(「auth」、「login」、「token」、「protected」、「bearer」、「API key」に言及している場合)は、AskUserQuestion を使用してユーザーに以下を尋ねます。

  • 「E2E_AUTH_TOKEN を入力してください(何も入力しない場合は、認証情報なしとして扱います)」
  • 「E2E_USERNAME を入力してください(何も入力しない場合は、認証情報なしとして扱います)」
  • 「E2E_PASSWORD を入力してください(何も入力しない場合は、認証情報なしとして扱います)」

セキュリティ:E2E レポートではトークンの値を伏せ、ヘッダー名のみを表示します(例:Authorization: Bearer <redacted>)。

3. curl コマンドを生成する

解析可能な各 [IMP] AC について、以下の正確な形式で curl コマンドを作成します。

curl -s -w "\n%{http_code}" -o /tmp/e2e_resp.json \
  -X <METHOD> \
  -H "Content-Type: application/json" \
  <AUTH_HEADER> \
  -d '<REQUEST_BODY>' \
  "http://localhost:${TARGET_PORT}<PATH>"

ルール:

  • 常に -s -w "\n%{http_code}" -o /tmp/e2e_resp.json を含めます。
  • -X <METHOD>:メソッドが GET 以外の場合にのみ追加します。GET の場合は -X を完全に省略します。
  • -H "Content-Type: application/json":POST、PUT、PATCH の場合にのみ追加します。
  • -d '<REQUEST_BODY>':AC でリクエスト本文が説明されている場合にのみ追加します。本文が説明されていない場合は -d を省略します。
  • <AUTH_HEADER>:
    • E2E_AUTH_TOKEN が設定されている場合:-H "Authorization: Bearer ${E2E_AUTH_TOKEN}"
    • E2E_USERNAME と E2E_PASSWORD が設定されている場合:-u "${E2E_USERNAME}:${E2E_PASSWORD}"
    • 認証情報が見つからない場合:認証ヘッダーを省略します。
  • TLS の適用(REQ-NR003):スキルの実行時に --insecure フラグが明示的に渡されない限り、curl コマンドに -k または --insecure を決して追加しないでください。--insecure が渡された場合は、TLS 検証が無効になっていることを警告として記録します。
  • 生のレスポンス本文を /tmp/e2e_resp.json に保存し、ステータスコードを標準出力の最終行に出力します。

4. curl を実行してアサーションする(再試行なし)

各 curl コマンドを正確に 1 回実行します(REQ-NR008)。失敗した場合も再試行しないでください。

HTTP_CODE=$(curl -s -w "\n%{http_code}" -o /tmp/e2e_resp.json <curl args> | tail -n 1)

アサーション(すべて合格した場合にのみ、AC を VERIFIED とします):

a. HTTP ステータスコード:

if [ "$HTTP_CODE" -ne "$EXPECTED_STATUS" ]; then
    echo "FAIL: Expected status $EXPECTED_STATUS, got $HTTP_CODE"
    STATUS="FAILED"
fi

b. Content-Type ヘッダー(AC で指定されている場合のみ):

ACTUAL_CT=$(curl -s -o /dev/null -D - <curl args> | grep -i "Content-Type:" | head -1 | sed 's/Content-Type: //i' | tr -d '\r')
if [ -n "$EXPECTED_CT" ] && ! echo "$ACTUAL_CT" | grep -qi "$EXPECTED_CT"; then
    echo "FAIL: Expected Content-Type '$EXPECTED_CT', got '$ACTUAL_CT'"
    STATUS="FAILED"
fi

c. レスポンス本文の構造(jq を推奨、利用できない場合は grep を使用):

  • jq がインストールされていて、レスポンスが JSON の場合:
    # フィールドが存在することをアサート
    jq -e '.fieldName' /tmp/e2e_resp.json >/dev/null || { echo "FAIL: Missing .fieldName"; STATUS="FAILED"; }
    
    # フィールドが期待値と一致することをアサート
    jq -e '.fieldName == "expectedValue"' /tmp/e2e_resp.json >/dev/null || { echo "FAIL: .fieldName mismatch"; STATUS="FAILED"; }
    
    # 配列の長さをアサート
    jq -e '(.items | length) > 0' /tmp/e2e_resp.json >/dev/null || { echo "FAIL: .items is empty"; STATUS="FAILED"; }
    # ネストされたフィールドを検証
    jq -e '.data.user.email' /tmp/e2e_resp.json >/dev/null || { echo "FAIL: .data.user.email がありません"; STATUS="FAILED"; }
    
  • jq がインストールされていない場合は、代わりに grep を使用します。
    grep -q '"fieldName"' /tmp/e2e_resp.json || { echo "FAIL: fieldName がありません"; STATUS="FAILED"; }
    

5. 結果を記録する

各 curl テストについて、以下を記録します。

  • AC ID とテキスト(省略形)
  • 生成した curl コマンド(認証トークンは伏せ字にする)
  • 期待されるステータスコード、Content-Type、本文の検証内容
  • 実際のステータスコード、Content-Type、本文の抜粋(先頭 500 文字)
  • 合否ステータス
  • 実行時間(任意。レポートの参考用)

検証に失敗した場合は、直ちに AC を FAILED とします。再試行しないでください。

Web SPA テスト(Playwright)

前提条件の確認:SPA テストを生成する前に、Playwright がインストールされていることを確認します。

PLAYWRIGHT_VERSION=$(npx playwright --version 2>/dev/null || echo "")
if [ -z "$PLAYWRIGHT_VERSION" ]; then
    echo "ERROR: Playwright is not installed."
    echo "Install instructions:"
    echo "  npm install -D @playwright/test"
    echo "  npx playwright install chromium"
    echo ""
    echo "Skipping all Web SPA tests. Acceptance criteria for SPA interactions will be marked MANUAL CHECK REQUIRED."
    for ac_id in $(get_spa_ac_ids); do
        record_result "$ac_id" "MANUAL CHECK REQUIRED" "Playwright not installed"
    done
    # Continue to next test category; do not abort the whole verification
fi
echo "Playwright version: $PLAYWRIGHT_VERSION"

Playwright が見つからない場合、スキルは上記のインストールコマンドとともに不足していることを報告し、SPA テストをスキップしなければなりません。自動インストールを試みないでください。

1. アーティファクトディレクトリを準備する

ARTIFACT_DIR="${SPEC_FOLDER}/e2e-artifacts"
mkdir -p "$ARTIFACT_DIR"

2. UI の動作に関するヒントを [IMP] AC から解析する

Web SPA の動作に関連する仕様内の各 [IMP] 受け入れ基準について、以下を行います。

  • UI 操作のキーワードを抽出します:click、fill、type、select、submit、navigate、scroll、hover。
  • 次のパターンを検索して、対象セレクターを抽出します。
    • data-testid="..." または data-testid='...' → [data-testid=...]
    • id="..." または id='...' → #...
    • class="..." または class='...' → .class-name(スペースをドットに置き換える)
    • ボタン/リンクのテキストへの言及 → text=...
    • URL パスへの言及 → /path
  • 期待される表示状態を抽出します:テキスト内容の期待値、URL の期待値、要素の有無、件数の期待値。
  • フォームフィールド名と期待される入力値を抽出します。

[IMP] AC に解析可能な UI の動作または表示状態のヒントが含まれていない場合は、MANUAL CHECK REQUIRED として、次の基準に進みます。

3. ヘッドレスブラウザーコンテキストを起動する

ブラウザーはデフォルトでヘッドレスにしなければなりません。ユーザーが明示的に --headed を指定した場合に限り、ヘッドありモードを使用します。

各 SPA テストについて、一時的な Playwright スクリプトを生成し、node で実行します。

DEV_SERVER_URL="http://localhost:${TARGET_PORT}"
TEST_SCRIPT="$(mktemp /tmp/e2e-spa-XXXXXX.js)"

cat > "$TEST_SCRIPT" << 'PLAYWRIGHT_EOF'
const { chromium } = require('playwright');
(async () => {
  const browser = await chromium.launch({ headless: true });
  const context = await browser.newContext({
    viewport: { width: 1280, height: 720 },
    userAgent: 'DeveloperKit-E2E/1.0'
  });
  const page = await context.newPage();
  // Actions and assertions injected here
  await browser.close();
})();
PLAYWRIGHT_EOF

node "$TEST_SCRIPT"

4. AC を Playwright のアクションに変換する

解析した各 UI 操作について、一時スクリプト内に対応する Playwright アクションを生成します。

AC の説明パターンPlaywright アクション
"click [selector]"await page.click('[data-testid=refresh]');
"fill [selector] with [value]"await page.fill('#username', 'testuser');
"type [value] into [selector]"await page.type('input[name=search]', 'query');
"select [value] in [selector]"await page.selectOption('select[name=country]', 'US');
"submit [form]"await page.click('button[type=submit]');
"navigate to [path]"await page.goto('http://localhost:${TARGET_PORT}/path');
"hover over [selector]"await page.hover('.tooltip-trigger');
"scroll to [selector]"await page.locator('[data-testid=footer]').scrollIntoViewIfNeeded();

セレクターの優先順位(具体性の高いものから低いもの):

  1. [data-testid=...] — 推奨。最も安定しています
  2. #id — 一意な要素 ID
  3. .class-name — CSS クラス
  4. [name=...] — フォーム要素名
  5. text=... — 表示テキストの内容(フォールバック)

5. 表示状態を検証する

期待される各表示状態について、一時スクリプト内に対応する検証を生成します。

AC の記述パターンPlaywright のアサーション
「ページに[テキスト]が表示される」await expect(page.locator('body')).toContainText('text');
「[セレクター]に[value]というテキストがある」await expect(page.locator('[data-testid=title]')).toHaveText('value');
「[セレクター]に[テキスト]が含まれる」await expect(page.locator('.message')).toContainText('text');
「テーブルに[N]行ある」expect(await page.locator('table tbody tr').count()).toBe(N);
「URL が[path]である」expect(page.url()).toBe('http://localhost:${TARGET_PORT}/path');
「URL に[フラグメント]が含まれる」expect(page.url()).toContain('/fragment');
「[セレクター]が表示されている」await expect(page.locator('[data-testid=modal]')).toBeVisible();
「[セレクター]が非表示である」await expect(page.locator('[data-testid=spinner]')).toBeHidden();

6. タイムアウトを適用してテストを実行する

無期限に停止することを防ぐため、Playwright の各テストにはテストごとのタイムアウトを必ず設定する必要があります(REQ-NR007)。ラッパースクリプトでは timeout コマンドを使用します。

TEST_TIMEOUT_SEC=30

npx playwright --version >/dev/null 2>&1 || {
    echo "Playwright not installed; skipping SPA tests."
    record_spa_manual_check
    continue
}

# Build the inline test script
TEST_SCRIPT="$(mktemp /tmp/e2e-spa-XXXXXX.js)"
cat > "$TEST_SCRIPT" << EOF
const { chromium } = require('playwright');
(async () => {
  const browser = await chromium.launch({ headless: true });
  const page = await browser.newPage();
  page.setDefaultTimeout(${TEST_TIMEOUT_SEC}000);
  page.setDefaultNavigationTimeout(${TEST_TIMEOUT_SEC}000);
  try {
    await page.goto('${DEV_SERVER_URL}');
    // --- GENERATED ACTIONS ---
    // --- GENERATED ASSERTIONS ---
    console.log('RESULT: PASS');
  } catch (error) {
    console.error('RESULT: FAIL:', error.message);
    const screenshotPath = '${ARTIFACT_DIR}/screenshot-' + Date.now() + '-ac-${AC_ID}.png';
    await page.screenshot({ path: screenshotPath, fullPage: true }).catch(() => {});
    console.error('SCREENSHOT:', screenshotPath);
    process.exitCode = 1;
  } finally {
    await browser.close();
  }
})();
EOF

# Run with timeout wrapper; if the test hangs, it is killed and marked FAILED
if timeout --signal=TERM $((TEST_TIMEOUT_SEC + 5)) node "$TEST_SCRIPT"; then
    STATUS="VERIFIED"
else
    EXIT_CODE=$?
    if [ "$EXIT_CODE" -eq 124 ]; then
        echo "FAIL: Test hung and was terminated after ${TEST_TIMEOUT_SEC}s timeout"
    fi
    STATUS="FAILED"
fi
rm -f "$TEST_SCRIPT"

テストプロセスがタイムアウトを超えて停止した場合、timeout コマンドが SIGTERM を送信し、AC は FAILED としてマークされ、証跡には「テストが停止し、${TEST_TIMEOUT_SEC}s 後に終了されました」と記録されます。

7. 失敗時にスクリーンショットを撮影する

アサーションまたはアクションが失敗した場合:

  1. page.screenshot({ path: ..., fullPage: true }) を使用して、ページ全体のスクリーンショットを撮影します。
  2. ${ARTIFACT_DIR}/screenshot-<timestamp>-ac-<AC_ID>.png に保存します。
  3. テスト結果の証跡にスクリーンショットのパスを記録します。

テストが成功した場合、スクリーンショットの撮影は任意です。--capture-success が渡された場合にのみ撮影します。

8. 結果を記録する

各 SPA テストについて、以下を記録します。

  • AC ID とテキスト(省略形)
  • 実行した Playwright のアクション
  • 実施したアサーション
  • 成功/失敗のステータス
  • スクリーンショットのパス(失敗時)
  • 実行時間
  • エラーメッセージ(失敗時)

アサーションが失敗した場合、AC を直ちに FAILED としてマークします。再試行しないでください。

エッジケース — エラー処理:

  • Playwright がインストールされていない:npm install -D @playwright/test と npx playwright install chromium を提案する明確なエラーを報告します。すべての SPA AC を MANUAL CHECK REQUIRED としてマークします。他のテストカテゴリは続行します。
  • 開発サーバーが起動していない:page.goto() が net::ERR_CONNECTION_REFUSED などをスローした場合、「${DEV_SERVER_URL} で開発サーバーに接続できません。検証の前にサーバーが起動していることを確認してください。」と報告します。該当する AC を FAILED としてマークします。
  • ブラウザーの起動に失敗した:Chromium の起動に失敗した場合(システム依存関係の不足など)、エラーを報告し、npx playwright install-deps chromium を提案します。該当する AC を MANUAL CHECK REQUIRED としてマークします。
デスクトップテスト(コンピューター操作/MCP)

前提条件の確認:デスクトップテストを生成する前に、コンピューター操作または MCP ベースの GUI 自動化ツールが利用可能であることを確認します。このスキルは、これらのツールを自動インストールしません。

GUI_TOOLS_AVAILABLE=false
GUI_TOOL_NAME=""

if [ -n "$CLAUDE_COMPUTER_USE_AVAILABLE" ] || command -v computer-use >/dev/null 2>&1; then
    GUI_TOOLS_AVAILABLE=true
    GUI_TOOL_NAME="computer-use"
fi

if [ -n "$MCP_GUI_SERVER_URL" ] || command -v mcp-gui >/dev/null 2>&1; then
    GUI_TOOLS_AVAILABLE=true
    GUI_TOOL_NAME="mcp-gui"
fi

if [ "$GUI_TOOLS_AVAILABLE" != "true" ]; then
    echo "ERROR: Desktop testing tools are not available."
    echo "This skill requires computer-use or MCP GUI automation tools to verify desktop applications."
    echo ""
    echo "To enable desktop verification:"
    echo "  - Run in an environment with computer-use support, OR"
    echo "  - Install and configure an MCP GUI automation server"
    echo ""
    echo "Aborting desktop verification. No tests will be executed."
    exit 1
fi
echo "Desktop GUI tool detected: $GUI_TOOL_NAME"

1. 成果物ディレクトリを準備する

ARTIFACT_DIR="${SPEC_FOLDER}/e2e-artifacts"
mkdir -p "$ARTIFACT_DIR"

2. [IMP] AC からデスクトップの動作に関するヒントを解析する

仕様内でデスクトップアプリの動作に関連する各 [IMP] 受け入れ条件について:

  • ウィンドウタイトルのヒントを抽出します:「ウィンドウが開く」「ダイアログが表示される」「モーダルに表示される」「タイトルに含まれる」「画面に表示される」などの表現を探します。
  • UI 要素の説明を抽出します:ボタンのラベル、入力欄のプレースホルダー、メニュー項目、チェックボックスのラベル、トグル名を探します。
  • ワークフローの手順を抽出します:「…をクリックしてから…」「…を選択して…を押す」「…を切り替える」などの操作手順を探します。
  • 期待される状態変化を抽出します:「テーマがダークに変わる」「ステータスインジケーターが緑色になる」「リストが更新される」などの視覚的な変化を探します。

[IMP] AC に解析可能なデスクトップ動作のヒント(ウィンドウ、要素、ワークフローの説明)が含まれていない場合は、MANUAL CHECK REQUIRED としてマークし、次の条件に進みます。

3. アプリケーションが実行中であることを確認する

Phase 3 でデスクトップアプリが起動されていない場合、またはそのプロセスが終了している場合は、Phase 3 のコマンドを使用して再起動します。

if [ -n "$STARTUP_PID" ] && ! kill -0 "$STARTUP_PID" 2>/dev/null; then
    echo "Desktop app process is not running. Re-launching..."
    # Re-run the appropriate launch command from Phase 3 based on DESKTOP_FRAMEWORK
fi

4. ウィンドウ/UI 要素を視覚検査またはアクセシビリティツリー検査で確認する

ウィンドウまたは UI 要素について記述している各 AC(AC-022)に対して、次のように確認します。

  • 視覚検査(コンピューター操作):

    • デスクトップのスクリーンショットを撮影します。
    • 画像を分析し、期待されるウィンドウタイトル、ボタンラベル、または視覚要素を確認します。
    • 例:「Settings ウィンドウが開く」→ スクリーンショットを撮影し、「Settings」というタイトルのウィンドウが表示されていることを確認します。
  • アクセシビリティツリー検査(MCP GUI 自動化):

    • アクセシビリティツリーを照会し、期待される名前、ロール、または状態に一致する要素を検索します。
    • 例:「Settings ウィンドウが開く」→ 名前に「Settings」を含むウィンドウ要素を照会します。
    • 例:「ダークモードを切り替える」→ ラベルに「Dark mode」を含むスイッチ/チェックボックス要素を照会します。

5. GUI 自動化でユーザーワークフローをシミュレートする

ユーザーワークフローについて記述している各 AC(AC-023)に対して、説明を次の GUI 自動化アクションに変換します。

AC の説明パターンGUI 自動化アクション
「[ボタンラベル]をクリックする」一致するアクセシブル名またはラベルを持つ UI 要素をクリックします
「[フィールド]に[値]を入力する」入力フィールドにフォーカスし、値を入力します
「[スイッチ/チェックボックス]を切り替える」スイッチまたはチェックボックス要素をクリックします
「ドロップダウンから[オプション]を選択する」ドロップダウンを開き、オプション要素をクリックします
「[メニュー項目]に移動する」一致するラベルのメニュー項目をクリックします
「[フィールド]に[値]を入力する」フィールドにフォーカスし、値を入力します

6. 各検証ステップでスクリーンショットを撮影する

各ステップ(アクションの前、アクションの後、アサーション時)で、必ずスクリーンショットを撮影します。

SCREENSHOT_PATH="${ARTIFACT_DIR}/screenshot-$(date +%s)-ac-${AC_ID}-step-${STEP_NUM}.png"
# computer-use: capture screenshot and save to SCREENSHOT_PATH
# MCP: use the GUI automation server's screenshot capability

合否にかかわらず、すべてのステップでスクリーンショットが保存され、AC-024(SEF)を満たします。

7. テストごとのタイムアウトを適用する

無期限のハングを防ぐため、各デスクトップテストには必ずテストごとのタイムアウトを適用します(REQ-NR007)。

TEST_TIMEOUT_SEC=60
TEST_START_TIME=$(date +%s)
TEST_STATUS="RUNNING"

while [ "$TEST_STATUS" = "RUNNING" ]; do
    if [ $(( $(date +%s) - TEST_START_TIME )) -gt "$TEST_TIMEOUT_SEC" ]; then
        echo "FAIL: Desktop test hung and was terminated after ${TEST_TIMEOUT_SEC}s timeout"
        STATUS="FAILED"
        TEST_STATUS="TIMEOUT"
        FINAL_SCREENSHOT="${ARTIFACT_DIR}/screenshot-$(date +%s)-ac-${AC_ID}-timeout.png"
        # capture screenshot to FINAL_SCREENSHOT
        break
    fi
    # Execute next workflow step; if all steps complete, set TEST_STATUS="COMPLETED"
done

テストがタイムアウトした場合:

  • AC を FAILED としてマークします
  • 最終タイムアウト時のスクリーンショットを撮影します
  • 証拠として「テストがハングしたため、${TEST_TIMEOUT_SEC}秒のタイムアウト後に終了しました」と記録します
  • 次の AC の前に、アプリケーションの状態をリセットする(ウィンドウを閉じて再度開く)ことを試みます

8. 結果を記録する

各デスクトップテストについて、次の内容を記録します。

  • AC ID とテキスト(省略形)
  • 実行した GUI 自動化アクション(ステップ一覧)
  • 撮影したスクリーンショット(ステップの説明を含むパス一覧)
  • 合否
  • エラーメッセージ、タイムアウトの理由、またはアサーションの不一致(失敗時)

アサーションまたはアクションが失敗した場合は、直ちに AC を FAILED としてマークします。再試行しないでください。

エッジケース — エラー処理:

  • コンピューター操作ツールまたは MCP ツールが利用できない:明確なエラーメッセージを表示して検証を中止します。デスクトップテストを黙って省略してはいけません。
  • ビルド失敗:ビルド出力は $STARTUP_LOGS_FILE に保存され、報告されます。起動前に検証を中止します。
  • テスト中のアプリのクラッシュ:テスト中にデスクトッププロセスが終了した場合は、ログからクラッシュ出力を取得し、現在の AC を FAILED としてマークして、次の AC に向けてアプリの再起動を試みます。
  • ウィンドウ/要素が見つからない:期待されるウィンドウまたは要素がテストのタイムアウトまでに表示されない場合は、AC を FAILED としてマークし、現在のデスクトップ状態のスクリーンショットを撮影します。

フォールバック:人間による美的判断が必要な場合など、自動テストに変換できない特定の AC は、MANUAL CHECK REQUIRED としてマークし、続行します。

フェーズ 5:結果を受け入れ基準に対応付ける

目標:すべてのテスト実行結果を機能仕様の [IMP] 受け入れ基準と照合し、それぞれについて判定結果を確定します。

5.1 結果テーブルの初期化

AC ID をキーとする連想型の結果構造(例:シェルの連想配列、JSON オブジェクト、一時ファイル)を作成します。フェーズ 1 で抽出した [IMP] 受け入れ基準ごとに、次の内容で行を事前に作成します。

フィールド初期値
ac_idAC 識別子(例:AC-012)
ac_text基準の全文。表示用に 120 文字で切り捨てます
statusPENDING(5.2 で更新)
evidence空文字列(5.2 で更新)
actual空文字列(不一致時のみ入力)
expected空文字列(不一致時のみ入力)

仕様書の受け入れ基準テーブルを確認し、分類タグが [IMP] の行のみを含めます。[SEF] および [EXT] の基準は実行時検証の対象外です。付録に記載してもかまいませんが、ステータス判定は不要です。

5.2 判定ルールの適用

フェーズ 4 でテスト対象となった各 AC に対し、次のルールを順番に適用します。

  1. VERIFIED(REQ-016):

    • テストがアサーション失敗、タイムアウト、ツールエラーなしで完了した場合
    • status = "VERIFIED" に設定します
    • evidence に簡潔な説明を設定します。
      • REST API:curl returned HTTP <code> in <N>ms
      • Web SPA:Playwright assertion passed: <selector> <condition>
      • デスクトップ:GUI automation confirmed: <window/element> present
  2. FAILED(REQ-018):

    • アサーションが失敗した、テストがタイムアウトした、ツールがクラッシュした、または開発サーバーに接続できなかった場合
    • status = "FAILED" に設定します
    • actual に観測値を設定します(例:HTTP 400、element not found、timeout after 30s)
    • expected に AC が要求する値を設定します(例:HTTP 201、element visible)
    • evidence に、actual と expected を比較した人が読める文章を設定し、利用可能な場合は取得した成果物(スクリーンショット、レスポンスダンプ)のパスも含めます。
  3. MANUAL CHECK REQUIRED(REQ-017):

    • 次の理由により AC のテストが生成されなかった場合:
      • AC のテキストに、解析可能なエンドポイント/UI/デスクトップ動作の手がかりがなかった
      • 必要なツールが不足していた(例:Playwright がインストールされていない)
      • AC が人による判断を記述している(例:「UI が正しく見える」)
    • status = "MANUAL CHECK REQUIRED" に設定します
    • evidence に理由を設定します:No automated test could be derived: <reason>

重要:すべての [IMP] AC は、最終ステータスを VERIFIED、FAILED、または MANUAL CHECK REQUIRED のいずれかにする必要があります。フェーズ 5 の終了時に PENDING のままの AC があってはなりません。

5.3 サマリー数の算出

すべての行に値を設定したら、次を算出します。

TOTAL_IMP=$(count_implementation_acs)
VERIFIED_COUNT=$(grep -c '"status":"VERIFIED"' "$RESULTS_FILE")
FAILED_COUNT=$(grep -c '"status":"FAILED"' "$RESULTS_FILE")
MANUAL_COUNT=$(grep -c '"status":"MANUAL CHECK REQUIRED"' "$RESULTS_FILE")

これらの件数を保存します。レポートのサマリーセクション(REQ-020)で必要になります。

フェーズ 6:レポートの生成

目標:フェーズ 6.3 で定義された形式に従い、秘密情報を含まない、決定性のある読みやすい Markdown レポートを作成します。

6.1 レポートのパスとディレクトリの準備
REPORT_TIMESTAMP=$(date +%Y-%m-%d-%H%M%S)
REPORT_DIR="${SPEC_FOLDER}"
ARTIFACT_DIR="${REPORT_DIR}/e2e-artifacts"
REPORT_FILE="${REPORT_DIR}/e2e-report-${REPORT_TIMESTAMP}.md"

mkdir -p "$ARTIFACT_DIR"
  • SPEC_FOLDER は --spec 引数の値です(例:docs/specs/001-real-e2e-verification/)。
  • レポートのファイル名は、正確なパターン e2e-report-YYYY-MM-DD-HHMMSS.md を使用する必要があります(AC-026)。
  • 成果物のパスをレポートに記載する前に、成果物ディレクトリが存在している必要があります。
6.2 生の出力から秘密情報を伏せる

レポートにコマンド出力を書き込む前に、生ログに対して情報を伏せる処理を実行します(REQ-NR002)。

# Redact Authorization header values
sed -E 's/(Authorization:[[:space:]]*[Bb]earer[[:space:]]+)[^[:space:]]+/\1***REDACTED***/g' "$STARTUP_LOGS_FILE" > "$STARTUP_LOGS_FILE.redacted"

# Redact tokens in JSON bodies
sed -E 's/("token"[[:space:]]*:[[:space:]]*")[^"]+/\1***REDACTED***/g' "$STARTUP_LOGS_FILE.redacted" > "$STARTUP_LOGS_FILE.redacted2"

# Redact passwords in curl -u arguments
sed -E 's/(-u[[:space:]]+[^:]*:)[^[:space:]]+/\1***REDACTED***/g' "$STARTUP_LOGS_FILE.redacted2" > "$STARTUP_LOGS_FILE.redacted3"

# Redact E2E_* environment variables (REQ-NR002)
sed -E 's/(E2E_[A-Z_]+=)[^[:space:]]+/\1***REDACTED***/g' "$STARTUP_LOGS_FILE.redacted3" > "$STARTUP_LOGS_FILE.redacted_final"

レポートには、伏せ字処理済みのバージョン($STARTUP_LOGS_FILE.redacted_final)のみを含めます。元の一時ファイルは破棄します。

6.3 レポートの各セクションを作成する

以下の正確な順序で各セクションを追記して、レポートを作成します。レポート内のすべてのパスは、レポートファイルの場所からの相対パスでなければなりません。

セクション 1 — 概要

## Summary

| Metric | Count |
|--------|-------|
| Total AC Evaluated | ${TOTAL_IMP} |
| ✅ VERIFIED | ${VERIFIED_COUNT} |
| ❌ FAILED | ${FAILED_COUNT} |
| ⚠️ MANUAL CHECK REQUIRED | ${MANUAL_COUNT} |

セクション 2 — 環境

## Environment

| Property | Value |
|----------|-------|
| Application Type | ${APP_TYPE} |
| Startup Command | \`${STARTUP_COMMAND}\` |
| Target Port | ${TARGET_PORT} |
| Health Endpoint | ${HEALTH_CHECK_METHOD} |
| Runtime Version | ${RUNTIME_VERSION} |
| Verification Started | ${ISO8601_START_TIME} |
  • RUNTIME_VERSION:起動時に検出されたランタイムのバージョンを記録します。
    • Docker:docker --version
    • Node.js:node --version
    • Java:java -version 2>&1 | head -1
    • Python:python --version または python3 --version
  • ISO8601_START_TIME:Phase 3 の開始時刻を ISO-8601 形式で記録します。

セクション 3 — テスト結果

## Test Results

| AC ID | Criterion (truncated) | Status | Evidence |
|-------|----------------------|--------|----------|
| AC-012 | WHEN the app is classified... | VERIFIED | \`curl\` returned 200 |
| AC-013 | WHEN a curl test executes... | FAILED | Expected 201, got 400 |
| AC-014 | WHEN authentication is required... | MANUAL CHECK REQUIRED | No credentials found |
  • 「Criterion (truncated)」列は 120 文字に切り詰め、120 文字を超える場合は ... を追加しなければなりません。
  • 「Status」列は VERIFIED、FAILED、MANUAL CHECK REQUIRED のいずれかでなければなりません。
  • 「Evidence」列では、該当する場合、相対パスを使って成果物を参照しなければなりません(例:./e2e-artifacts/ac-019-screenshot.png)。

セクション 4 — 生の出力

## Raw Output

### Startup Logs
\`\`\`
[contents of $STARTUP_LOGS_FILE.redacted_final]
\`\`\`

### Test Commands
\`\`\`
[For each test: the exact command executed (auth redacted) and its first 2000 chars of stdout/stderr]
\`\`\`
  • テストで出力がなかった場合は、(no output) と記載します。
  • レポートが過度に長くならないよう、各コードブロックは 2000 行までとします。ログがそれより長い場合は、次の注記を追記します:(truncated; full logs available in <path>)

セクション 5 — 成果物

## Artifacts

| AC ID | Type | Path |
|-------|------|------|
| AC-019 | Screenshot | ./e2e-artifacts/screenshot-1717189200-ac-019.png |
| AC-014 | Response Dump | ./e2e-artifacts/ac-014-response.json |
  • $ARTIFACT_DIR にある成果物ファイルをすべて列挙します。
  • 成果物が生成されなかった場合は、No artifacts captured for this run. と記載します。

セクション 6 — ティアダウンのステータス

## Teardown Status

| Property | Value |
|----------|-------|
| Teardown Executed | ${TEARDOWN_EXECUTED} |
| Port Released | ${PORT_RELEASED} |
| Remaining Processes | ${REMAINING_PROCESSES} |
  • TEARDOWN_EXECUTED:true / false / skipped (--keep-alive)
  • PORT_RELEASED:true / false / unknown
  • REMAINING_PROCESSES:ティアダウン後も稼働している PID またはコンテナー名をカンマ区切りで列挙します。該当するものがない場合は none とします。
6.4 レポートを確定して保存する
cat > "$REPORT_FILE" << 'REPORT_EOF'
[all sections assembled above]
REPORT_EOF

echo "E2E report saved to: $REPORT_FILE"
  • レポートは有効な Markdown でなければなりません(基本的な markdownlint ルールに準拠すること)。見出しレベルを適切にし、行末の空白をなくし、パイプで区切る表の区切り文字を統一します。
  • レポートを書き込んだ後は、変更してはなりません(REQ-NR005)。
  • Phase 8 で使用するため、変数 REPORT_PATH="$REPORT_FILE" を設定します。
Phase 7:環境をティアダウンする

目標:Phase 3 で起動したすべてのランタイムリソースを正常に停止し、ポートとプロセスが完全に解放されたことを確認して、その結果を記録します。--keep-alive が渡された場合は、ティアダウンを完全にスキップしてユーザーに警告します。

7.1 --keep-alive ガード
if [ "$KEEP_ALIVE" = "true" ]; then
    TEARDOWN_EXECUTED="skipped (--keep-alive)"
    PORT_RELEASED="skipped"
    REMAINING_PROCESSES="skipped"
    echo "WARNING: --keep-alive was passed. The runtime environment is still running."
    echo "You are responsible for cleaning up:"
    if [ "$APP_TYPE" = "Docker-managed" ]; then
        echo "  docker compose -f '${PROJECT_ROOT}/docker-compose.yml' down"
    else
        echo "  kill $(lsof -ti:${TARGET_PORT})"
    fi
    echo "Report was saved before teardown; artifacts are in ${ARTIFACT_DIR}."
    # Skip remaining teardown steps and proceed to Phase 8
    # (Phase 7 cleanup is bypassed when --keep-alive is active)
fi
7.2 アプリケーションの種類ごとに正常にティアダウンする

Docker 管理対象(AC-029):

TEARDOWN_EXECUTED="true"
DOCKER_TEARDOWN_FAILED=false

if [ "$APP_TYPE" = "Docker-managed" ]; then
    cd "$PROJECT_ROOT"
    if docker compose down >> "$STARTUP_LOGS_FILE" 2>&1; then
        echo "Docker Compose stack stopped successfully."
    else
        echo "WARNING: docker compose down exited with a non-zero code."
        DOCKER_TEARDOWN_FAILED=true
    fi
fi

ローカルプロセス(JVM、Node.js/NestJS、Python、デスクトップ)(AC-029):

if [ -n "$STARTUP_PID" ] && kill -0 "$STARTUP_PID" 2>/dev/null; then
    echo "Sending SIGTERM to process $STARTUP_PID ..."
    kill -TERM "$STARTUP_PID" 2>/dev/null || true
    # Wait up to 10 seconds for graceful exit
    GRACEFUL_WAIT=0
    while [ "$GRACEFUL_WAIT" -lt 10 ] && kill -0 "$STARTUP_PID" 2>/dev/null; do
        sleep 1
        GRACEFUL_WAIT=$((GRACEFUL_WAIT + 1))
    done
    # If still alive, send SIGKILL
    if kill -0 "$STARTUP_PID" 2>/dev/null; then
        echo "Process $STARTUP_PID did not exit after 10s; sending SIGKILL."
        kill -KILL "$STARTUP_PID" 2>/dev/null || true
        sleep 2
    fi
fi

# Also terminate any child processes that may still hold the port
PORT_PIDS=$(lsof -ti:"$TARGET_PORT" 2>/dev/null || true)
if [ -n "$PORT_PIDS" ]; then
    echo "Additional PIDs holding port $TARGET_PORT: $PORT_PIDS"
    for pid in $PORT_PIDS; do
        kill -TERM "$pid" 2>/dev/null || true
    done
    sleep 3
    for pid in $PORT_PIDS; do
        if kill -0 "$pid" 2>/dev/null; then
            kill -KILL "$pid" 2>/dev/null || true
        fi
    done
fi

緊急クリーンアップ — クラッシュしたテストの孤立プロセス:

# If the original STARTUP_PID is empty or already dead, but the port is still occupied,
# this indicates an orphan process from a crash or a detached child.
if [ -z "$STARTUP_PID" ] || ! kill -0 "$STARTUP_PID" 2>/dev/null; then
    ORPHAN_PIDS=$(lsof -ti:"$TARGET_PORT" 2>/dev/null || true)
    if [ -n "$ORPHAN_PIDS" ]; then
        echo "WARNING: Orphan process(es) detected on port $TARGET_PORT: $ORPHAN_PIDS"
        for pid in $ORPHAN_PIDS; do
            kill -TERM "$pid" 2>/dev/null || true
        done
        sleep 3
        for pid in $ORPHAN_PIDS; do
            if kill -0 "$pid" 2>/dev/null; then
                kill -KILL "$pid" 2>/dev/null || true
            fi
        done
    fi
fi
7.3 ポートの解放を確認する(AC-030)
PORT_RELEASED="false"
PORT_CHECK_WAIT=0
MAX_PORT_CHECK_WAIT=15

while [ "$PORT_CHECK_WAIT" -lt "$MAX_PORT_CHECK_WAIT" ]; do
    if ! lsof -i :"$TARGET_PORT" >/dev/null 2>&1 && ! nc -z localhost "$TARGET_PORT" 2>/dev/null; then
        PORT_RELEASED="true"
        echo "Port $TARGET_PORT is free."
        break
    fi
    sleep 1
    PORT_CHECK_WAIT=$((PORT_CHECK_WAIT + 1))
done

if [ "$PORT_RELEASED" != "true" ]; then
    echo "WARNING: Port $TARGET_PORT is still occupied after teardown."
fi
7.4 残存リソースを特定する(REQ-023)
REMAINING_PROCESSES="none"

if [ "$APP_TYPE" = "Docker-managed" ]; then
    REMAINING_CONTAINERS=$(docker compose ps --format '{{.Name}}' 2>/dev/null | tr '\n' ',' | sed 's/,$//')
    if [ -n "$REMAINING_CONTAINERS" ]; then
        REMAINING_PROCESSES="containers: $REMAINING_CONTAINERS"
    fi
else
    REMAINING_PIDS=$(lsof -ti:"$TARGET_PORT" 2>/dev/null | tr '\n' ' ' | sed 's/ $//')
    if [ -n "$REMAINING_PIDS" ]; then
        REMAINING_PROCESSES="PIDs: $REMAINING_PIDS"
    fi
fi
7.5 ティアダウンが不完全な場合にユーザーへ警告する
if [ "$PORT_RELEASED" != "true" ] || [ "$DOCKER_TEARDOWN_FAILED" = "true" ]; then
    echo "WARNING: Teardown did not complete cleanly. Manual cleanup may be required."
    if [ "$APP_TYPE" = "Docker-managed" ]; then
        echo "  Remaining containers: ${REMAINING_PROCESSES}"
        echo "  Manual command: docker compose -f '${PROJECT_ROOT}/docker-compose.yml' down"
    else
        echo "  Remaining PIDs on port ${TARGET_PORT}: ${REMAINING_PIDS}"
        echo "  Manual command: kill ${REMAINING_PIDS}"
    fi
fi
7.6 ティアダウンのステータスを記録する

レポートファイルをその場で更新します(Phase 6 でティアダウンのステータスが書き込まれていない場合はそのセクションを追記するか、Phase 6 でレポートを確定する前に変数が正しく設定されていることを確認します)。推奨される順序は次のとおりです。

  1. Phase 6 で、ティアダウンの変数をプレースホルダーとしてレポートに書き込みます。
  2. Phase 7 でティアダウンを実行します。
  3. Phase 7 で、インプレース編集(例:sed)を使ってプレースホルダーを実際の値に更新します。
sed -i.bak \
    -e "s/\${TEARDOWN_EXECUTED}/${TEARDOWN_EXECUTED}/g" \
    -e "s/\${PORT_RELEASED}/${PORT_RELEASED}/g" \
    -e "s/\${REMAINING_PROCESSES}/${REMAINING_PROCESSES}/g" \
    "$REPORT_FILE" && rm -f "$REPORT_FILE.bak"

ティアダウン前にレポートがすでに確定している場合(有効な代替手順)は、レポートの末尾に ティアダウン後の更新 セクションを追記します。

## Post-Teardown Update

| Property | Value |
|----------|-------|
| Teardown Executed | ${TEARDOWN_EXECUTED} |
| Port Released | ${PORT_RELEASED} |
| Remaining Processes | ${REMAINING_PROCESSES} |

重要:ティアダウン中にレポートを削除したり、仕様ファイルやタスクファイルを変更したりしてはなりません(REQ-NR005)。

Phase 8:結果を提示する
  1. 簡潔な概要をユーザーに表示します。
    • ✅ 検証済み:N | ❌ 失敗:N | ⚠️ 手動確認:N
    • レポートのパス
    • ティアダウンのステータス(または --keep-alive の通知)
  2. 失敗した AC がある場合:
    • 失敗した AC と証拠を強調表示します。
    • 失敗したタスクに対して /developer-kit-specs:specs.task-implementation を再実行するよう推奨します。
  3. すべての ToDo を完了済みにします。

例

Spring Boot REST API の検証
/developer-kit-specs:specs.e2e-verify --spec=docs/specs/001-user-auth/

アクション:

  1. pom.xml と docker-compose.yml を検出 → Docker 管理の Spring Boot
  2. docker compose up -d を実行
  3. localhost:8080/actuator/health を待機
  4. 仕様書から AC を読み取り、ログインエンドポイントを記述した [IMP] AC を見つける
  5. 次を実行:curl -s -w "\n%{http_code}" -X POST http://localhost:8080/api/login -d '{"email":"[email protected]","password":"secret"}'
  6. ステータスが 200 で、レスポンスに token が含まれることを確認
  7. レポートを生成:docs/specs/001-user-auth/e2e-report-2026-05-31-143022.md
  8. docker compose down を実行
React SPA の検証
/developer-kit-specs:specs.e2e-verify --spec=docs/specs/002-dashboard/

アクション:

  1. react 依存関係を含む package.json を検出
  2. npm run dev を実行し、localhost:5173 を待機
  3. Playwright を起動し、/dashboard に移動
  4. AC に「ユーザーは[更新]をクリックしてデータを再読み込みできます」とある場合 → page.click('[data-testid=refresh]') を実行し、テーブルの行が更新されることを確認
  5. 成功時にスクリーンショットを撮影し、失敗時にキャプチャを取得
  6. スクリーンショットの成果物を含めてレポートを生成
  7. 開発サーバーのプロセスを終了
デスクトップアプリの検証
/developer-kit-specs:specs.e2e-verify --spec=docs/specs/003-settings-app/

アクション:

  1. src-tauri/Cargo.toml を検出 → Tauri デスクトップアプリ
  2. cargo tauri build --debug を実行し、.app/.exe を起動
  3. コンピューター操作を使って、設定ウィンドウが開くことを確認
  4. AC に「ユーザーはダークモードを切り替えられます」とある場合 → 切り替えをクリックしてスクリーンショットを撮影し、テーマが変わることを確認
  5. 各手順のスクリーンショットを含めてレポートを生成
  6. アプリを閉じ、プロセスが終了したことを確認

コマンドの許可リスト

references/test-execution-patterns.md に記載されたコマンドのみ、自動実行できます。許可リストの対象は次のとおりです。

カテゴリ許可されるコマンド
Dockerdocker compose up -d --build、docker compose down、docker compose ps、docker compose logs
JVM/Spring Boot./mvnw spring-boot:run、./gradlew bootRun
Node.js/NestJSnpm run dev、npm run start:dev、npm start、node server.js
Pythonuvicorn main:app --reload、python manage.py runserver、flask run
デスクトップ(Tauri)cargo tauri build --debug、cargo tauri dev、open *.app、バイナリの直接実行
デスクトップ(Electron)npm run electron:dev、npx electron .
テストcurl(--insecure が渡された場合を除き、-k/--insecure を付けない)、npx playwright、node(インラインの Playwright スクリプト用)
ティアダウンdocker compose down、kill -TERM、kill -KILL(停止したプロセスのみ)、lsof、nc

許可リスト外のコマンドに関するポリシー:導出されたコマンドが許可リストにない場合は、実行前に AskUserQuestion を使って、ユーザーに明示的な確認を求めます。ユーザーの承認が必要です。承認されない場合は中止します。

セキュリティガードと禁止要件

次の禁止要件(REQ-NR001~REQ-NR008)は、スキルのワークフロー内の複数の段階で適用されます。

REQ-NR001:破壊的なコマンドの禁止

システムは、起動、テスト、ティアダウンの実行中に、sudo、rm -rf、docker system prune、mkfs、dd、またはその他の破壊的なシステムコマンドを実行してはなりません。

適用方法:

  • フェーズ 1.5 では、実行前にすべてのコマンドを走査し、禁止パターンがないか確認します。
  • フェーズ 3 のデータ整合性に関する事前チェックでは、データを破壊するコマンドを拒否します。
  • ティアダウンコマンドは、docker compose down と正常なプロセス終了(停止した孤立プロセスに対する kill -TERM/kill -KILL のみ)に制限されます。
REQ-NR002:レポートにシークレットを含めない

システムは、E2E レポートにシークレット、API キー、またはパスワードを含めてはなりません。

適用方法:

  • フェーズ 6 のマスキング処理では、次の内容を削除します。
    • Authorization: Bearer <token> → Authorization: Bearer ***REDACTED***
    • E2E_AUTH_TOKEN=...、E2E_USERNAME=...、E2E_PASSWORD=... → E2E_xxx=***REDACTED***
    • "token": "..." → "token": "***REDACTED***"
    • curl -u user:password 内のパスワード → curl -u user:***REDACTED***
  • レポートには、マスキング済みのログのみが書き込まれます。
  • マスキング直後に、マスキング前の一時ファイルを破棄します。
REQ-NR003: デフォルトで TLS をバイパスしない

システムは、デフォルトで curl の TLS 証明書検証(-k/--insecure)を無効にしてはなりません。

適用:

  • Phase 1.5 では、--insecure フラグが明示的に渡されていない限り、-k または --insecure を含む curl コマンドを拒否します。
  • Phase 4 の curl コマンド生成ルールでは、-k/--insecure の追加を明示的に禁止します。
  • --insecure が渡された場合 → 次の警告をログに記録します:「TLS 証明書の検証が無効になっています。ローカル開発でのみ使用してください。」
REQ-NR004: 起動時にデータを上書きしない

システムは、起動コマンド自体が既存のデータベース、ボリューム、またはローカルデータを上書きまたは削除する場合を除き、それらを上書きまたは削除してはなりません。

適用:

  • Phase 1.5 のデータ整合性事前チェックでは、rm、--volumes、prune、drop、または同様の破壊的なデータ操作パターンを含むコマンドを拒否します。
  • Phase 3 のデータ整合性事前チェックでは、コマンドによって既存データが破壊される場合、起動を中止します。
REQ-NR005: 仕様ファイルやタスクファイルを変更しない

システムは、レポートの生成中またはクリーンアップ中に、機能仕様やタスクファイルを変更してはなりません。

適用:

  • Phase 6 では、レポートを新しいファイル(e2e-report-YYYY-MM-DD-HHMMSS.md)としてのみ書き込みます。
  • Phase 7 のクリーンアップでは、レポートに「クリーンアップ後の更新」セクションのみを追記し、仕様ファイルやタスクファイルは変更しません。
  • このスキルは、ソースコード、仕様、タスクに対して読み取り専用です。
REQ-NR006: 失敗時に孤立プロセスを残さない

システムは、起動に失敗した場合、孤立したプロセスやコンテナーを実行中のまま残してはなりません。

適用:

  • Phase 3 の Docker 起動では、タイムアウト時に終了する前に docker compose down を実行します。
  • Phase 3 の Spring Boot/Node.js 起動では、タイムアウト時に終了する前に kill -TERM $STARTUP_PID を実行します。
  • Phase 7 のクリーンアップでは、対象ポートをまだ使用しているプロセスを緊急クリーンアップします。
REQ-NR007: テストごとのタイムアウトを適用する

システムは、停止したテストによって無期限にブロックされてはなりません。

適用:

  • Phase 4 の Playwright テストは timeout --signal=TERM <N> でラップします。タイムアウトした場合、AC を FAILED とします。
  • Phase 4 のデスクトップテストでは、手動のポーリングループで経過時間を TEST_TIMEOUT_SEC と比較します。制限時間を超えた場合、AC を FAILED とします。
  • テストが停止した場合は終了させて FAILED とし、VERIFIED または PENDING にはしません。
REQ-NR008: 失敗した curl テストを再試行しない

システムは、失敗した curl リクエストを再試行してはなりません。

適用:

  • Phase 4 の curl 実行では、各コマンドを正確に 1 回だけ実行します。
  • --retry フラグ、再試行ループ、フォールバックによる再実行は一切行いません。
  • 最初の結果を直ちに記録し、失敗した場合は AC を FAILED とします。

制約と警告

  • ソースは読み取り専用:このスキルは、アプリケーションのソースコード、テストファイル、設定、機能仕様、タスクファイルを編集しません。
  • 自動インストールなし:Playwright または Docker が見つからない場合、このスキルは不足を報告し、インストールコマンドを提案します。npm install -g や brew install を自動的に実行することはありません。
  • ローカルのみ:テストは localhost に対して実行します。リモート URL、ステージング環境、本番環境のエンドポイントは対象外です。
  • 破壊的操作のガード:起動、テスト、クリーンアップのコマンドは references/test-execution-patterns.md に従ってホワイトリストに登録されています。ホワイトリストにないコマンドには、AskUserQuestion によるユーザー確認が必要です。
  • シークレットの取り扱い:認証ヘッダー、ベアラートークン、E2E_* 環境変数、パスワードはレポート内でマスキングされます。表示されるのはヘッダー名とマスキング済みの値のみです。
  • TLS のデフォルト設定:curl コマンドはデフォルトで厳格な TLS 検証を行います。自己署名証明書を使ったローカル開発の場合に限り、--insecure を渡してください。
  • クリーンアップの責任:--keep-alive が渡された場合、手動でのクリーンアップはユーザーの責任となります。
  • 永続的なテストスイートなし:このスキルは検証用の一時的なテストを生成します。プロジェクト内に永続的なテストファイルを作成することはありません。

コメント