#!/bin/bash set -euo pipefail # Feature Development E2E Test Script # Tests the complete Claude Code + SDLC feature development workflow: # 1. Create composable project with skeleton # 2. Add service component # 3. Create SDLC feature via API # 4. Invoke Claude to generate spec (via /builds with skeleton command) # 5. Approve spec via API # 6. Invoke Claude to generate design # 7. Approve design via API # 8. Invoke Claude to break down tasks # 9. Approve tasks via API # 10. Invoke Claude to implement first task # 11. Verify code was generated # 12. Teardown # # Usage: ./cookbooks/scripts/feature-dev-test.sh # Commands: run, status, teardown # # This is the definitive E2E test for the SDLC + Claude Code integration. # It exercises the full feature development workflow from project creation # to implemented code. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/common.sh" # Parse --auto-teardown flag from args ARGS=("$@") for i in "${!ARGS[@]}"; do if [[ "${ARGS[$i]}" == "--auto-teardown" ]]; then AUTO_TEARDOWN="true" unset 'ARGS[$i]' fi done ARGS=("${ARGS[@]}") # Re-index array COMMAND="${ARGS[0]:-}" PROJECT_NAME="${ARGS[1]:-}" FEATURE_SLUG="add-hello-endpoint" # Register cleanup trap for auto-teardown register_cleanup_trap if [[ -z "$COMMAND" || -z "$PROJECT_NAME" ]]; then echo "Feature Development E2E Test Script" echo "" echo "Tests the complete Claude Code + SDLC feature development workflow." echo "" echo "Usage: $0 [--auto-teardown]" echo "" echo "Commands:" echo " run - Execute full feature development flow" echo " status - Check project and SDLC status" echo " teardown - Delete the project" echo "" echo "Examples:" echo " $0 run feature-dev-test" echo " $0 run test-feat --auto-teardown" echo " $0 status feature-dev-test" echo " $0 teardown feature-dev-test" echo "" echo "Environment Variables:" echo " RDEV_API_URL - rdev API base URL" echo " RDEV_API_KEY - rdev API key" echo " GITEA_TOKEN - Gitea API token (optional, for repo verification)" exit 1 fi # Check response for success/error check_response() { local response="$1" local step="$2" local error error=$(echo "$response" | jq -r '.error // ""') if [[ -n "$error" && "$error" != "" && "$error" != "null" ]]; then print_error "$step failed: $error" echo "$response" | jq '.' return 1 fi return 0 } # Wait for a build to complete # Arguments: task_id step_name [max_attempts] wait_for_build_step() { local task_id="$1" local step_name="$2" local max_attempts="${3:-120}" # 10 minutes default (5s * 120) local attempt=0 echo " Waiting for build to complete (task: $task_id)..." while [[ $attempt -lt $max_attempts ]]; do local result result=$(api_call GET "/builds/$task_id") local status status=$(echo "$result" | jq -r '.data.status // .status // "unknown"') case "$status" in completed) local success success=$(echo "$result" | jq -r '.data.result.success // .result.success // false') if [[ "$success" == "true" ]]; then print_success "$step_name completed successfully" return 0 else print_error "$step_name completed but failed:" echo "$result" | jq '.data.result // .result' return 1 fi ;; failed) print_error "$step_name failed:" echo "$result" | jq '.' return 1 ;; running) echo " Build running... (attempt $((attempt + 1))/$max_attempts)" ;; pending) echo " Build pending... (attempt $((attempt + 1))/$max_attempts)" ;; *) echo " Unknown status: $status (attempt $((attempt + 1))/$max_attempts)" ;; esac sleep 5 ((attempt++)) done print_error "Timeout waiting for $step_name to complete" return 2 } # Submit a build and wait for completion # Arguments: project_id prompt step_name submit_build_and_wait() { local project_id="$1" local prompt="$2" local step_name="$3" local git_owner git_owner=$(get_git_owner) local git_clone_url="https://git.threesix.ai/${git_owner}/${project_id}.git" echo " Submitting build: $prompt" local payload payload=$(jq -n \ --arg prompt "$prompt" \ --arg git_clone_url "$git_clone_url" \ '{ prompt: $prompt, auto_commit: true, auto_push: true, git_clone_url: $git_clone_url }') local result result=$(api_call POST "/projects/$project_id/builds" "$payload") if ! check_response "$result" "Submit build"; then return 1 fi local task_id task_id=$(echo "$result" | jq -r '.data.task_id // .task_id // ""') if [[ -z "$task_id" ]]; then print_error "No task_id in response" echo "$result" | jq '.' return 1 fi wait_for_build_step "$task_id" "$step_name" } # Approve an SDLC artifact # Arguments: project_id feature_slug artifact_type approve_artifact() { local project_id="$1" local feature_slug="$2" local artifact_type="$3" echo " Approving $artifact_type artifact..." local result result=$(api_call POST "/projects/$project_id/sdlc/features/$feature_slug/artifacts/$artifact_type/approve" "{}") if check_response "$result" "Approve $artifact_type"; then print_success "$artifact_type artifact approved" return 0 fi return 1 } # Add a component to the project add_component() { local comp_type="$1" local comp_name="$2" echo "Adding $comp_type component: $comp_name" local payload payload=$(jq -n \ --arg type "$comp_type" \ --arg name "$comp_name" \ '{type: $type, name: $name}') local result result=$(api_call POST "/projects/$PROJECT_NAME/components" "$payload") local path path=$(echo "$result" | jq -r '.data.path // .path // ""') if [[ -z "$path" ]]; then print_error "Failed to add component" echo "$result" | jq '.' return 1 fi print_success "Added $comp_type/$comp_name at $path" return 0 } # Verify the feature artifacts exist in Gitea verify_artifacts() { local project_id="$1" local feature_slug="$2" local git_owner git_owner=$(get_git_owner) print_header "Verifying Feature Artifacts" local artifacts=("spec.md" "design.md" "tasks.md") local all_found=true for artifact in "${artifacts[@]}"; do local path=".sdlc/features/$feature_slug/$artifact" echo " Checking $path..." local check check=$(curl -s "https://git.threesix.ai/api/v1/repos/$git_owner/$project_id/contents/$path" \ -H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | jq -r '.name // "not found"') if [[ "$check" == "$artifact" ]]; then print_success " $artifact exists" else print_warning " $artifact not found" all_found=false fi done if [[ "$all_found" == "true" ]]; then print_success "All artifacts verified" return 0 else print_warning "Some artifacts missing (may still be propagating)" return 0 # Don't fail on verification, may be timing fi } # Main run flow run_flow() { print_header "Feature Development E2E Test" echo "Project: $PROJECT_NAME" echo "Feature: $FEATURE_SLUG" echo "" # ========================================================================= # Step 1: Create project skeleton # ========================================================================= print_header "Step 1: Creating project skeleton" local create_payload create_payload=$(jq -n \ --arg name "$PROJECT_NAME" \ --arg desc "Feature development E2E test" \ '{name: $name, description: $desc}') local create_result create_result=$(api_call POST "/project" "$create_payload") echo "$create_result" | jq '.' local domain domain=$(echo "$create_result" | jq -r '.data.domain // .domain // ""') if [[ -z "$domain" ]]; then print_error "Failed to create project" exit 1 fi # Track project for auto-cleanup CLEANUP_PROJECT="$PROJECT_NAME" print_success "Project created with domain: $domain" # ========================================================================= # Step 2: Add service component # ========================================================================= print_header "Step 2: Adding service component" if ! add_component "service" "api"; then exit 1 fi # Wait for git sync echo " Waiting for git sync..." sleep 10 # ========================================================================= # Step 3: Create SDLC feature via build (using worker pod) # ========================================================================= # Note: Composable projects don't have dedicated pods, so we use builds # to execute SDLC commands on worker pods that clone the repo. print_header "Step 3: Creating SDLC feature via build" # Initialize SDLC and create feature in one build if ! submit_build_and_wait "$PROJECT_NAME" \ "Initialize SDLC for this project and create a feature with slug '$FEATURE_SLUG' and title 'Add /hello endpoint to API service'. Use the sdlc CLI: 'sdlc init' then 'sdlc feature create $FEATURE_SLUG \"Add /hello endpoint to API service\"'" \ "SDLC initialization"; then print_error "SDLC initialization failed" exit 1 fi # ========================================================================= # Step 4: Generate spec via Claude # ========================================================================= print_header "Step 4: Claude generates spec" if ! submit_build_and_wait "$PROJECT_NAME" "/spec-feature $FEATURE_SLUG" "Spec generation"; then print_error "Spec generation failed" exit 1 fi # ========================================================================= # Step 5: Approve spec via build # ========================================================================= print_header "Step 5: Approve spec" if ! submit_build_and_wait "$PROJECT_NAME" \ "Approve the spec artifact for feature $FEATURE_SLUG using: sdlc artifact approve $FEATURE_SLUG spec" \ "Spec approval"; then print_warning "Spec approval may have failed, continuing..." fi # ========================================================================= # Step 6: Generate design via Claude # ========================================================================= print_header "Step 6: Claude generates design" if ! submit_build_and_wait "$PROJECT_NAME" "/design-feature $FEATURE_SLUG" "Design generation"; then print_error "Design generation failed" exit 1 fi # ========================================================================= # Step 7: Approve design via build # ========================================================================= print_header "Step 7: Approve design" if ! submit_build_and_wait "$PROJECT_NAME" \ "Approve the design artifact for feature $FEATURE_SLUG using: sdlc artifact approve $FEATURE_SLUG design" \ "Design approval"; then print_warning "Design approval may have failed, continuing..." fi # ========================================================================= # Step 8: Break down into tasks via Claude # ========================================================================= print_header "Step 8: Claude breaks down into tasks" if ! submit_build_and_wait "$PROJECT_NAME" "/breakdown-feature $FEATURE_SLUG" "Task breakdown"; then print_error "Task breakdown failed" exit 1 fi # ========================================================================= # Step 9: Approve tasks via build # ========================================================================= print_header "Step 9: Approve tasks" if ! submit_build_and_wait "$PROJECT_NAME" \ "Approve the tasks artifact for feature $FEATURE_SLUG using: sdlc artifact approve $FEATURE_SLUG tasks" \ "Tasks approval"; then print_warning "Tasks approval may have failed, continuing..." fi # ========================================================================= # Step 10: Implement first task via Claude # ========================================================================= print_header "Step 10: Claude implements first task" # First task is typically T1 local first_task_id="T1" echo " First task ID: $first_task_id" if ! submit_build_and_wait "$PROJECT_NAME" "/implement-task $FEATURE_SLUG $first_task_id" "Task implementation"; then print_error "Task implementation failed" exit 1 fi # ========================================================================= # Step 11: Verify artifacts exist # ========================================================================= verify_artifacts "$PROJECT_NAME" "$FEATURE_SLUG" # ========================================================================= # Summary # ========================================================================= print_header "E2E Test Results" print_success "Feature development flow completed!" echo "" echo " Project: $PROJECT_NAME" echo " Feature: $FEATURE_SLUG" echo " Domain: https://$domain" echo " Git: https://git.threesix.ai/$(get_git_owner)/$PROJECT_NAME" echo " CI: https://ci.threesix.ai/$(get_git_owner)/$PROJECT_NAME" echo "" echo "Workflow completed:" echo " 1. Project skeleton created" echo " 2. Service component added" echo " 3. SDLC initialized + feature created (via build)" echo " 4. Claude generated spec" echo " 5. Spec approved" echo " 6. Claude generated design" echo " 7. Design approved" echo " 8. Claude broke down into tasks" echo " 9. Tasks approved" echo " 10. Claude implemented first task" echo "" echo "View the feature artifacts at:" echo " https://git.threesix.ai/$(get_git_owner)/$PROJECT_NAME/src/branch/main/.sdlc/features/$FEATURE_SLUG" } # Show status check_status() { print_header "Project Status: $PROJECT_NAME" # Get project info echo "Project:" api_call GET "/project/$PROJECT_NAME" | jq '.data // .' echo "" # Get components echo "Components:" api_call GET "/projects/$PROJECT_NAME/components" | jq '.data // .' echo "" # Get recent builds print_header "Recent Builds" api_call GET "/projects/$PROJECT_NAME/builds?limit=10" | jq '.data.builds[:10] // .builds[:10] // []' echo "" # Note about SDLC status echo "Note: SDLC status is stored in the git repo at .sdlc/" echo "View at: https://git.threesix.ai/$(get_git_owner)/$PROJECT_NAME/src/branch/main/.sdlc" } # Teardown teardown() { print_header "Tearing down: $PROJECT_NAME" local result result=$(api_call DELETE "/project/$PROJECT_NAME") echo "$result" | jq '.' echo "" print_success "Project deleted. Gitea repo preserved." } # Dispatch case "$COMMAND" in run) run_flow ;; status) check_status ;; teardown) teardown ;; *) echo "Unknown command: $COMMAND" echo "Valid commands: run, status, teardown" exit 1 ;; esac