rdev/cookbooks/scripts/feature-dev-test.sh
jordan 64ccf0b85d feat: add feature development E2E test and SDLC handler fixes
- Add feature-dev-test.sh: full 10-step E2E test for SDLC + Claude Code workflow
- Update feature-development.md cookbook with complete workflow documentation
- Fix SDLC orchestrator and project management handler improvements
- Update scaffold-test.sh with minor fixes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 20:12:40 -07:00

494 lines
16 KiB
Bash
Executable File

#!/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 <command> <project-name>
# 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 <command> <project-name> [--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