- 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>
494 lines
16 KiB
Bash
Executable File
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
|