#!/bin/bash # # BMAD Epic Execute - Automated Story Execution with Context Isolation # # Usage: ./epic-execute.sh [options] # # Options: # --dry-run Show what would be executed without running # --skip-review Skip code review phase (not recommended) # --no-commit Stage changes but don't commit # --parallel Run independent stories in parallel (experimental) # --verbose Show detailed output # --start-from ID Start from a specific story (e.g., 31-2) # --skip-done Skip stories with Status: Done # --skip-arch Skip architecture compliance check # --skip-test-quality Skip test quality review # --skip-traceability Skip traceability check (not recommended) # set -e # ============================================================================= # Configuration # ============================================================================= SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" BMAD_DIR="$PROJECT_ROOT/bmad" STORIES_DIR="$PROJECT_ROOT/docs/stories" SPRINT_ARTIFACTS_DIR="$PROJECT_ROOT/docs/sprint-artifacts" SPRINTS_DIR="$PROJECT_ROOT/docs/sprints" EPICS_DIR="$PROJECT_ROOT/docs/epics" UAT_DIR="$PROJECT_ROOT/docs/uat" LOG_FILE="/tmp/bmad-epic-execute-$$.log" # ============================================================================= # BMAD Workflow Paths # ============================================================================= # Source workflow files from the BMAD-METHOD repository BMAD_SRC_DIR="$SCRIPT_DIR/.." WORKFLOWS_DIR="$BMAD_SRC_DIR/src/modules/bmm/workflows/4-implementation" CORE_TASKS_DIR="$BMAD_SRC_DIR/src/core/tasks" # Dev Story Workflow DEV_WORKFLOW_DIR="$WORKFLOWS_DIR/dev-story" DEV_WORKFLOW_YAML="$DEV_WORKFLOW_DIR/workflow.yaml" DEV_WORKFLOW_INSTRUCTIONS="$DEV_WORKFLOW_DIR/instructions.xml" DEV_WORKFLOW_CHECKLIST="$DEV_WORKFLOW_DIR/checklist.md" # Code Review Workflow REVIEW_WORKFLOW_DIR="$WORKFLOWS_DIR/code-review" REVIEW_WORKFLOW_YAML="$REVIEW_WORKFLOW_DIR/workflow.yaml" REVIEW_WORKFLOW_INSTRUCTIONS="$REVIEW_WORKFLOW_DIR/instructions.xml" REVIEW_WORKFLOW_CHECKLIST="$REVIEW_WORKFLOW_DIR/checklist.md" # Core workflow executor WORKFLOW_EXECUTOR="$CORE_TASKS_DIR/workflow.xml" # UAT Generation (from epic-execute workflow) UAT_STEP_TEMPLATE="$WORKFLOWS_DIR/epic-execute/steps/step-04-generate-uat.md" UAT_DOC_TEMPLATE="$WORKFLOWS_DIR/epic-execute/templates/uat-template.md" # New Quality Gate Steps ARCH_COMPLIANCE_STEP="$WORKFLOWS_DIR/epic-execute/steps/step-02b-arch-compliance.md" TEST_QUALITY_STEP="$WORKFLOWS_DIR/epic-execute/steps/step-03b-test-quality.md" TRACEABILITY_STEP="$WORKFLOWS_DIR/epic-execute/steps/step-03c-traceability.md" # Traceability output directory TRACEABILITY_DIR="$PROJECT_ROOT/docs/sprint-artifacts/traceability" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # ============================================================================= # Helper Functions # ============================================================================= log() { echo -e "${BLUE}[BMAD]${NC} $1" echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE" } log_success() { echo -e "${GREEN}[✓]${NC} $1" echo "[$(date '+%Y-%m-%d %H:%M:%S')] [SUCCESS] $1" >> "$LOG_FILE" } log_error() { echo -e "${RED}[✗]${NC} $1" echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $1" >> "$LOG_FILE" } log_warn() { echo -e "${YELLOW}[!]${NC} $1" echo "[$(date '+%Y-%m-%d %H:%M:%S')] [WARN] $1" >> "$LOG_FILE" } # ============================================================================= # Metrics Functions # ============================================================================= METRICS_DIR="" METRICS_FILE="" init_metrics() { METRICS_DIR="$SPRINT_ARTIFACTS_DIR/metrics" METRICS_FILE="$METRICS_DIR/epic-${EPIC_ID}-metrics.yaml" mkdir -p "$METRICS_DIR" local start_time=$(date -u +"%Y-%m-%dT%H:%M:%SZ") cat > "$METRICS_FILE" << EOF epic_id: "$EPIC_ID" execution: start_time: "$start_time" end_time: "" duration_seconds: 0 stories: total: 0 completed: 0 failed: 0 skipped: 0 fix_loop: total_fix_attempts: 0 stories_requiring_fixes: 0 max_retries_hit: 0 validation: gate_executed: false gate_status: "PENDING" issues: [] story_details: [] EOF log "Metrics initialized: $METRICS_FILE" } update_story_metrics() { local status="$1" # completed|failed|skipped if [ -z "$METRICS_FILE" ] || [ ! -f "$METRICS_FILE" ]; then return fi # Check if yq is available for YAML manipulation if command -v yq >/dev/null 2>&1; then case "$status" in completed) yq -i '.stories.completed += 1' "$METRICS_FILE" ;; failed) yq -i '.stories.failed += 1' "$METRICS_FILE" ;; skipped) yq -i '.stories.skipped += 1' "$METRICS_FILE" ;; esac else # Fallback: log warning (metrics will be finalized at end) [ "$VERBOSE" = true ] && log_warn "yq not found - metrics update deferred" fi } add_metrics_issue() { local story_id="$1" local issue_type="$2" local message="$3" if [ -z "$METRICS_FILE" ] || [ ! -f "$METRICS_FILE" ]; then return fi local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ") if command -v yq >/dev/null 2>&1; then yq -i ".issues += [{\"story\": \"$story_id\", \"type\": \"$issue_type\", \"message\": \"$message\", \"timestamp\": \"$timestamp\"}]" "$METRICS_FILE" fi } record_fix_attempt() { local story_id="$1" local attempt_num="$2" local outcome="$3" # success|failed|max_retries if [ -z "$METRICS_FILE" ] || [ ! -f "$METRICS_FILE" ]; then return fi local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ") if command -v yq >/dev/null 2>&1; then # Increment total fix attempts yq -i '.fix_loop.total_fix_attempts += 1' "$METRICS_FILE" # Track per-story fix details yq -i ".story_details += [{\"story\": \"$story_id\", \"fix_attempt\": $attempt_num, \"outcome\": \"$outcome\", \"timestamp\": \"$timestamp\"}]" "$METRICS_FILE" if [ "$outcome" = "max_retries" ]; then yq -i '.fix_loop.max_retries_hit += 1' "$METRICS_FILE" fi fi } record_story_required_fixes() { local story_id="$1" if [ -z "$METRICS_FILE" ] || [ ! -f "$METRICS_FILE" ]; then return fi if command -v yq >/dev/null 2>&1; then yq -i '.fix_loop.stories_requiring_fixes += 1' "$METRICS_FILE" fi } finalize_metrics() { local total_stories="$1" local completed="$2" local failed="$3" local skipped="$4" local duration="$5" if [ -z "$METRICS_FILE" ] || [ ! -f "$METRICS_FILE" ]; then return fi local end_time=$(date -u +"%Y-%m-%dT%H:%M:%SZ") if command -v yq >/dev/null 2>&1; then yq -i ".execution.end_time = \"$end_time\"" "$METRICS_FILE" yq -i ".execution.duration_seconds = $duration" "$METRICS_FILE" yq -i ".stories.total = $total_stories" "$METRICS_FILE" yq -i ".stories.completed = $completed" "$METRICS_FILE" yq -i ".stories.failed = $failed" "$METRICS_FILE" yq -i ".stories.skipped = $skipped" "$METRICS_FILE" else # Fallback: rewrite the file with final values cat > "$METRICS_FILE" << EOF epic_id: "$EPIC_ID" execution: start_time: "$EPIC_START_TIME" end_time: "$end_time" duration_seconds: $duration stories: total: $total_stories completed: $completed failed: $failed skipped: $skipped validation: gate_executed: false gate_status: "PENDING" fix_attempts: 0 issues: [] EOF fi log "Metrics finalized: $METRICS_FILE" } # ============================================================================= # Status Update Functions # ============================================================================= update_story_status() { local story_file="$1" local new_status="$2" local story_id=$(basename "$story_file" .md) if [ ! -f "$story_file" ]; then log_warn "Story file not found for status update: $story_file" return 1 fi # Update Status field in story file using sed # Matches "Status: " and replaces with "Status: " if grep -q "^Status:" "$story_file"; then sed -i.bak "s/^Status:.*$/Status: $new_status/" "$story_file" && rm -f "${story_file}.bak" log_success "Updated story file status: $story_id → $new_status" else log_warn "No Status field found in story file: $story_id" return 1 fi return 0 } update_sprint_status() { local story_id="$1" local new_status="$2" # Find sprint-status.yaml file local sprint_file="" for search_dir in "$SPRINT_ARTIFACTS_DIR" "$SPRINTS_DIR" "$PROJECT_ROOT/docs"; do if [ -f "$search_dir/sprint-status.yaml" ]; then sprint_file="$search_dir/sprint-status.yaml" break fi done if [ -z "$sprint_file" ] || [ ! -f "$sprint_file" ]; then [ "$VERBOSE" = true ] && log_warn "No sprint-status.yaml found - skipping sprint status update" return 0 fi # Extract story key from story_id (e.g., "1-2-user-auth" from various naming formats) # Story files can be named: 1-2-user-auth.md, story-1.2-user-auth.md, etc. local story_key="" # Try to extract the key pattern: {epic}-{seq}-{name} if [[ "$story_id" =~ ^([0-9]+)-([0-9]+)-(.+)$ ]]; then story_key="${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}" elif [[ "$story_id" =~ ^story-([0-9]+)\.([0-9]+)-(.+)$ ]]; then story_key="${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}" elif [[ "$story_id" =~ ^story-([0-9]+)-([0-9]+)-(.+)$ ]]; then story_key="${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}" else # Use story_id as-is if no pattern matches story_key="$story_id" fi # Check if yq is available for YAML manipulation if command -v yq >/dev/null 2>&1; then # Check if story key exists in development_status if yq -e ".development_status[\"$story_key\"]" "$sprint_file" >/dev/null 2>&1; then yq -i ".development_status[\"$story_key\"] = \"$new_status\"" "$sprint_file" log_success "Updated sprint status: $story_key → $new_status" else [ "$VERBOSE" = true ] && log_warn "Story key '$story_key' not found in sprint-status.yaml" fi else # Fallback: use sed for simple replacement # This handles the format: " 1-2-user-auth: in-progress" if grep -q "^[[:space:]]*${story_key}:" "$sprint_file"; then sed -i.bak "s/^\([[:space:]]*${story_key}:\).*/\1 $new_status/" "$sprint_file" && rm -f "${sprint_file}.bak" log_success "Updated sprint status: $story_key → $new_status (via sed)" else [ "$VERBOSE" = true ] && log_warn "Story key '$story_key' not found in sprint-status.yaml (sed fallback)" fi fi return 0 } mark_story_done() { local story_file="$1" local story_id=$(basename "$story_file" .md) log "Marking story as done: $story_id" # Update story file Status to done update_story_status "$story_file" "done" # Update sprint-status.yaml if it exists update_sprint_status "$story_id" "done" } # ============================================================================= # Argument Parsing # ============================================================================= EPIC_ID="" DRY_RUN=false SKIP_REVIEW=false NO_COMMIT=false PARALLEL=false VERBOSE=false START_FROM="" SKIP_DONE=false SKIP_ARCH=false SKIP_TEST_QUALITY=false SKIP_TRACEABILITY=false while [[ $# -gt 0 ]]; do case $1 in --dry-run) DRY_RUN=true shift ;; --skip-review) SKIP_REVIEW=true shift ;; --no-commit) NO_COMMIT=true shift ;; --parallel) PARALLEL=true shift ;; --verbose) VERBOSE=true shift ;; --start-from) START_FROM="$2" shift 2 ;; --skip-done) SKIP_DONE=true shift ;; --skip-arch) SKIP_ARCH=true shift ;; --skip-test-quality) SKIP_TEST_QUALITY=true shift ;; --skip-traceability) SKIP_TRACEABILITY=true shift ;; -*) echo "Unknown option: $1" exit 1 ;; *) EPIC_ID="$1" shift ;; esac done if [ -z "$EPIC_ID" ]; then echo "Usage: $0 [options]" echo "" echo "Options:" echo " --dry-run Show what would be executed" echo " --skip-review Skip code review phase" echo " --no-commit Don't commit after stories" echo " --parallel Parallel execution (experimental)" echo " --verbose Detailed output" echo " --start-from ID Start from a specific story (e.g., 31-2)" echo " --skip-done Skip stories with Status: Done" echo " --skip-arch Skip architecture compliance check" echo " --skip-test-quality Skip test quality review" echo " --skip-traceability Skip traceability check (not recommended)" exit 1 fi # ============================================================================= # Setup # ============================================================================= log "Starting epic execution for: $EPIC_ID" log "Project root: $PROJECT_ROOT" # ============================================================================= # Validate BMAD Workflow Files # ============================================================================= validate_workflows() { local missing=0 log "Validating BMAD workflow files..." # Core workflow executor if [ ! -f "$WORKFLOW_EXECUTOR" ]; then log_error "Missing: Core workflow executor at $WORKFLOW_EXECUTOR" ((missing++)) fi # Dev-story workflow if [ ! -f "$DEV_WORKFLOW_YAML" ]; then log_error "Missing: Dev workflow.yaml at $DEV_WORKFLOW_YAML" ((missing++)) fi if [ ! -f "$DEV_WORKFLOW_INSTRUCTIONS" ]; then log_error "Missing: Dev instructions.xml at $DEV_WORKFLOW_INSTRUCTIONS" ((missing++)) fi # Code-review workflow if [ ! -f "$REVIEW_WORKFLOW_YAML" ]; then log_error "Missing: Review workflow.yaml at $REVIEW_WORKFLOW_YAML" ((missing++)) fi if [ ! -f "$REVIEW_WORKFLOW_INSTRUCTIONS" ]; then log_error "Missing: Review instructions.xml at $REVIEW_WORKFLOW_INSTRUCTIONS" ((missing++)) fi if [ $missing -gt 0 ]; then log_error "Missing $missing required BMAD workflow files" log_error "Ensure you are running from the BMAD-METHOD repository" log_error "Workflows expected at: $WORKFLOWS_DIR" exit 1 fi log_success "All BMAD workflow files validated" if [ "$VERBOSE" = true ]; then echo " Dev workflow: $DEV_WORKFLOW_DIR" echo " Review workflow: $REVIEW_WORKFLOW_DIR" echo " Executor: $WORKFLOW_EXECUTOR" fi } validate_workflows # Ensure directories exist mkdir -p "$UAT_DIR" mkdir -p "$SPRINTS_DIR" # Initialize metrics collection EPIC_START_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") EPIC_START_SECONDS=$(date +%s) init_metrics # Find epic file (supports both epic-39-*.md and epic-039-*.md formats) EPIC_FILE="" # Pad epic ID with leading zero for 3-digit format (e.g., 40 -> 040) EPIC_ID_PADDED=$(printf "%03d" "$EPIC_ID" 2>/dev/null || echo "$EPIC_ID") for pattern in "epic-${EPIC_ID}.md" "epic-${EPIC_ID}-"*.md "epic-${EPIC_ID_PADDED}-"*.md "epic-0${EPIC_ID}-"*.md "${EPIC_ID}.md"; do found=$(find "$EPICS_DIR" -name "$pattern" 2>/dev/null | head -1) if [ -n "$found" ]; then EPIC_FILE="$found" break fi done if [ -z "$EPIC_FILE" ] || [ ! -f "$EPIC_FILE" ]; then log_error "Epic file not found for: $EPIC_ID" log_error "Searched in: $EPICS_DIR" exit 1 fi log "Found epic file: $EPIC_FILE" # ============================================================================= # Discover Stories # ============================================================================= log "Discovering stories..." # Search multiple locations for story files STORY_LOCATIONS=("$STORIES_DIR" "$SPRINT_ARTIFACTS_DIR" "$SPRINTS_DIR") STORIES=() for search_dir in "${STORY_LOCATIONS[@]}"; do if [ ! -d "$search_dir" ]; then continue fi # Method 1: Stories that reference this epic in content while IFS= read -r -d '' file; do if [[ ! " ${STORIES[*]} " =~ " ${file} " ]]; then STORIES+=("$file") fi done < <(grep -l -Z "Epic.*:.*${EPIC_ID}\|epic-${EPIC_ID}\|Epic.*${EPIC_ID}" "$search_dir"/*.md 2>/dev/null || true) # Method 2: {EpicNumber}-{StoryNumber}-{description}.md (e.g., 1-1-user-registration.md) while IFS= read -r -d '' file; do if [[ ! " ${STORIES[*]} " =~ " ${file} " ]]; then STORIES+=("$file") fi done < <(find "$search_dir" -name "${EPIC_ID}-*-*.md" -print0 2>/dev/null || true) # Method 3: story-{epic}.{seq}-*.md (BMAD standard) while IFS= read -r -d '' file; do if [[ ! " ${STORIES[*]} " =~ " ${file} " ]]; then STORIES+=("$file") fi done < <(find "$search_dir" -name "story-${EPIC_ID}.*-*.md" -print0 2>/dev/null || true) # Method 4: story-{epic}-*.md (BMAD alternate) while IFS= read -r -d '' file; do if [[ ! " ${STORIES[*]} " =~ " ${file} " ]]; then STORIES+=("$file") fi done < <(find "$search_dir" -name "story-${EPIC_ID}-*.md" -print0 2>/dev/null || true) done if [ ${#STORIES[@]} -eq 0 ]; then log_error "No stories found for epic: $EPIC_ID" log_error "Searched in: ${STORY_LOCATIONS[*]}" log_error "Looking for:" log_error " - Files containing 'Epic: $EPIC_ID'" log_error " - Files named: ${EPIC_ID}-*-*.md (e.g., ${EPIC_ID}-1-description.md)" log_error " - Files named: story-${EPIC_ID}.*.md or story-${EPIC_ID}-*.md" exit 1 fi log "Found ${#STORIES[@]} stories" # Sort stories for consistent execution order IFS=$'\n' STORIES=($(sort -V <<<"${STORIES[*]}")); unset IFS # Show which directories stories came from if [ "$VERBOSE" = true ]; then for story in "${STORIES[@]}"; do echo " - $story" done fi # ============================================================================= # Execution Functions # ============================================================================= execute_dev_phase() { local story_file="$1" local story_id=$(basename "$story_file" .md) log ">>> DEV PHASE: $story_id (using BMAD dev-story workflow)" # Verify workflow files exist if [ ! -f "$DEV_WORKFLOW_YAML" ] || [ ! -f "$DEV_WORKFLOW_INSTRUCTIONS" ]; then log_error "BMAD dev-story workflow files not found" log_error "Expected: $DEV_WORKFLOW_YAML" log_error "Expected: $DEV_WORKFLOW_INSTRUCTIONS" return 1 fi # Read workflow components local workflow_yaml=$(cat "$DEV_WORKFLOW_YAML") local workflow_instructions=$(cat "$DEV_WORKFLOW_INSTRUCTIONS") local workflow_checklist="" if [ -f "$DEV_WORKFLOW_CHECKLIST" ]; then workflow_checklist=$(cat "$DEV_WORKFLOW_CHECKLIST") fi local workflow_executor=$(cat "$WORKFLOW_EXECUTOR") local story_contents=$(cat "$story_file") # Build the dev prompt using BMAD workflow local dev_prompt="You are executing a BMAD dev-story workflow in automated mode. ## Workflow Execution Context You are running the BMAD dev-story workflow to implement a story. This is an AUTOMATED execution as part of an epic chain - execute the workflow completely without user interaction prompts. ### CRITICAL AUTOMATION RULES - Do NOT pause for user confirmation at any step - Do NOT ask questions - make reasonable decisions and proceed - Execute ALL workflow steps in exact order until completion or HALT condition - When workflow says 'ask user', make a reasonable autonomous decision instead - Complete the ENTIRE workflow in a single execution ## Workflow Executor Engine $workflow_executor ## Dev-Story Workflow Configuration $workflow_yaml ## Dev-Story Workflow Instructions $workflow_instructions ## Definition of Done Checklist $workflow_checklist ## Story to Implement **Story Path:** $story_file **Story ID:** $story_id $story_contents ## Execution Variables (Pre-resolved) - story_path: $story_file - story_key: $story_id - project_root: $PROJECT_ROOT - implementation_artifacts: $STORIES_DIR - sprint_status: $SPRINT_ARTIFACTS_DIR/sprint-status.yaml - date: $(date '+%Y-%m-%d') - user_name: Epic Executor - communication_language: English - user_skill_level: expert - document_output_language: English ## Completion Signals When the workflow completes successfully (all tasks done, tests pass, status set to 'review'): Output exactly: IMPLEMENTATION COMPLETE: $story_id If a HALT condition is triggered or implementation is blocked: Output exactly: IMPLEMENTATION BLOCKED: $story_id - [specific reason] ## Begin Execution Execute the dev-story workflow now. Follow all steps in exact order. Stage all changes with: git add -A (after implementation is complete)" if [ "$DRY_RUN" = true ]; then echo "[DRY RUN] Would execute BMAD dev-story workflow for $story_id" echo "[DRY RUN] Workflow: $DEV_WORKFLOW_DIR" return 0 fi # Execute in isolated context local result result=$(claude --dangerously-skip-permissions -p "$dev_prompt" 2>&1) || true echo "$result" >> "$LOG_FILE" if echo "$result" | grep -q "IMPLEMENTATION COMPLETE"; then log_success "Dev phase complete: $story_id" return 0 elif echo "$result" | grep -q "IMPLEMENTATION BLOCKED"; then log_error "Dev phase blocked: $story_id" echo "$result" | grep "IMPLEMENTATION BLOCKED" return 1 else log_error "Dev phase did not complete cleanly: $story_id" return 1 fi } # Global variable to store review findings for fix loop LAST_REVIEW_FINDINGS="" execute_review_phase() { local story_file="$1" local story_id=$(basename "$story_file" .md) # Reset findings LAST_REVIEW_FINDINGS="" log ">>> REVIEW PHASE: $story_id (using BMAD code-review workflow, fresh context)" # Verify workflow files exist if [ ! -f "$REVIEW_WORKFLOW_YAML" ] || [ ! -f "$REVIEW_WORKFLOW_INSTRUCTIONS" ]; then log_error "BMAD code-review workflow files not found" log_error "Expected: $REVIEW_WORKFLOW_YAML" log_error "Expected: $REVIEW_WORKFLOW_INSTRUCTIONS" return 1 fi # Read workflow components local workflow_yaml=$(cat "$REVIEW_WORKFLOW_YAML") local workflow_instructions=$(cat "$REVIEW_WORKFLOW_INSTRUCTIONS") local workflow_checklist="" if [ -f "$REVIEW_WORKFLOW_CHECKLIST" ]; then workflow_checklist=$(cat "$REVIEW_WORKFLOW_CHECKLIST") fi local workflow_executor=$(cat "$WORKFLOW_EXECUTOR") local story_contents=$(cat "$story_file") # Build the review prompt using BMAD workflow local review_prompt="You are executing a BMAD code-review workflow in automated mode. ## Workflow Execution Context You are running the BMAD code-review workflow to perform an ADVERSARIAL code review. This is an AUTOMATED execution as part of an epic chain. ### CRITICAL AUTOMATION RULES - Do NOT pause for user confirmation at any step - When workflow offers options (fix automatically, create action items, show details), ALWAYS choose option 1: Fix them automatically - Execute ALL workflow steps in exact order until completion - When workflow says 'ask user', automatically choose the option that fixes issues - You ARE an adversarial reviewer - find 3-10 specific issues minimum - Auto-fix all HIGH and MEDIUM severity issues - Complete the ENTIRE workflow in a single execution ## Workflow Executor Engine $workflow_executor ## Code-Review Workflow Configuration $workflow_yaml ## Code-Review Workflow Instructions $workflow_instructions ## Review Validation Checklist $workflow_checklist ## Story to Review **Story Path:** $story_file **Story ID:** $story_id $story_contents ## Execution Variables (Pre-resolved) - story_path: $story_file - story_key: $story_id - project_root: $PROJECT_ROOT - implementation_artifacts: $STORIES_DIR - planning_artifacts: $PROJECT_ROOT/docs - sprint_status: $SPRINT_ARTIFACTS_DIR/sprint-status.yaml - date: $(date '+%Y-%m-%d') - user_name: Epic Executor - communication_language: English - user_skill_level: expert - document_output_language: English ## Automated Decision Policy When the workflow presents options: - Step 4 asks what to do with issues → Choose option 1 (Fix them automatically) - Always auto-fix HIGH and MEDIUM severity issues - LOW severity issues: document only, do not fix ## Completion Signals When review passes (all HIGH/MEDIUM issues fixed, all ACs implemented, status set to 'done'): Output exactly: REVIEW PASSED: $story_id When review passes but required fixes: Output exactly: REVIEW PASSED WITH FIXES: $story_id - Fixed N issues If review fails (unfixable issues, missing acceptance criteria that YOU cannot fix): 1. First output a structured findings block: \`\`\` REVIEW FINDINGS START - [HIGH] Description of issue 1 (file:line if applicable) - [HIGH] Description of issue 2 - [MEDIUM] Description of issue 3 ... all HIGH and MEDIUM issues that need dev attention ... REVIEW FINDINGS END \`\`\` 2. Then output exactly: REVIEW FAILED: $story_id - [summary reason] ## Begin Execution Execute the code-review workflow now. Follow all steps in exact order. You are seeing this code for the FIRST TIME - review adversarially. Stage any fixes with: git add -A" if [ "$DRY_RUN" = true ]; then echo "[DRY RUN] Would execute BMAD code-review workflow for $story_id" echo "[DRY RUN] Workflow: $REVIEW_WORKFLOW_DIR" return 0 fi # Execute in isolated context local result result=$(claude --dangerously-skip-permissions -p "$review_prompt" 2>&1) || true echo "$result" >> "$LOG_FILE" if echo "$result" | grep -q "REVIEW PASSED"; then log_success "Review passed: $story_id" return 0 elif echo "$result" | grep -q "REVIEW FAILED"; then log_error "Review failed: $story_id" echo "$result" | grep "REVIEW FAILED" # Extract findings for fix loop LAST_REVIEW_FINDINGS=$(echo "$result" | sed -n '/REVIEW FINDINGS START/,/REVIEW FINDINGS END/p' | grep -E '^\s*-\s*\[(HIGH|MEDIUM)\]' || true) if [ -n "$LAST_REVIEW_FINDINGS" ]; then log "Captured ${#LAST_REVIEW_FINDINGS} bytes of review findings for fix loop" fi return 1 else log_warn "Review did not complete cleanly: $story_id" return 1 fi } execute_fix_phase() { local story_file="$1" local review_findings="$2" local attempt_num="$3" local story_id=$(basename "$story_file" .md) log ">>> FIX PHASE: $story_id (attempt $attempt_num, using BMAD dev-story workflow)" # Verify workflow files exist if [ ! -f "$DEV_WORKFLOW_YAML" ] || [ ! -f "$DEV_WORKFLOW_INSTRUCTIONS" ]; then log_error "BMAD dev-story workflow files not found for fix phase" return 1 fi # Read workflow components local workflow_yaml=$(cat "$DEV_WORKFLOW_YAML") local workflow_instructions=$(cat "$DEV_WORKFLOW_INSTRUCTIONS") local workflow_checklist="" if [ -f "$DEV_WORKFLOW_CHECKLIST" ]; then workflow_checklist=$(cat "$DEV_WORKFLOW_CHECKLIST") fi local workflow_executor=$(cat "$WORKFLOW_EXECUTOR") local story_contents=$(cat "$story_file") # Build the fix prompt using BMAD dev-story workflow with review context local fix_prompt="You are executing a BMAD dev-story workflow in FIX MODE to address code review findings. ## Fix Phase Context This is attempt $attempt_num of 3 to fix issues identified during code review. You MUST address ALL HIGH and MEDIUM severity issues listed below. ### CRITICAL FIX RULES - This is a TARGETED FIX session - only fix the issues listed below - Do NOT refactor unrelated code - Do NOT add new features - Fix each issue, run tests to verify, then move to the next - After fixing all issues, update the story file and stage changes ## Review Findings to Address The following issues were identified during code review and MUST be fixed: $review_findings ## Workflow Executor Engine $workflow_executor ## Dev-Story Workflow Configuration $workflow_yaml ## Dev-Story Workflow Instructions $workflow_instructions ## Definition of Done Checklist $workflow_checklist ## Story Being Fixed **Story Path:** $story_file **Story ID:** $story_id **Fix Attempt:** $attempt_num of 3 $story_contents ## Execution Variables (Pre-resolved) - story_path: $story_file - story_key: $story_id - project_root: $PROJECT_ROOT - implementation_artifacts: $STORIES_DIR - sprint_status: $SPRINT_ARTIFACTS_DIR/sprint-status.yaml - date: $(date '+%Y-%m-%d') - user_name: Epic Executor (Fix Phase) - communication_language: English - user_skill_level: expert - document_output_language: English ## Fix Process 1. For each issue in the review findings: a. Locate the problematic code b. Implement the fix c. Run relevant tests to verify d. Move to next issue 2. After all issues are fixed: a. Run full test suite b. Update story file Dev Agent Record with fix notes c. Stage all changes: git add -A ## Completion Signals When ALL review issues are successfully fixed: Output exactly: FIX COMPLETE: $story_id - Fixed [N] issues If unable to fix one or more issues: Output exactly: FIX INCOMPLETE: $story_id - [reason and which issues remain] ## Begin Execution Address all review findings now. This is attempt $attempt_num of 3." if [ "$DRY_RUN" = true ]; then echo "[DRY RUN] Would execute BMAD fix phase for $story_id (attempt $attempt_num)" return 0 fi # Execute in isolated context local result result=$(claude --dangerously-skip-permissions -p "$fix_prompt" 2>&1) || true echo "$result" >> "$LOG_FILE" if echo "$result" | grep -q "FIX COMPLETE"; then log_success "Fix phase complete: $story_id (attempt $attempt_num)" record_fix_attempt "$story_id" "$attempt_num" "success" return 0 elif echo "$result" | grep -q "FIX INCOMPLETE"; then log_error "Fix phase incomplete: $story_id (attempt $attempt_num)" echo "$result" | grep "FIX INCOMPLETE" record_fix_attempt "$story_id" "$attempt_num" "failed" return 1 else log_warn "Fix phase did not complete cleanly: $story_id (attempt $attempt_num)" record_fix_attempt "$story_id" "$attempt_num" "failed" return 1 fi } # Maximum number of fix attempts before giving up MAX_FIX_ATTEMPTS=3 MAX_ARCH_FIX_ATTEMPTS=2 MAX_TEST_QUALITY_FIX_ATTEMPTS=2 MAX_TRACEABILITY_FIX_ATTEMPTS=3 # Global variable to store arch violations for fix loop LAST_ARCH_VIOLATIONS="" # Global variable to store test quality issues for fix loop LAST_TEST_QUALITY_ISSUES="" # Global variable to store traceability gaps for fix loop LAST_TRACEABILITY_GAPS="" execute_arch_compliance_phase() { local story_file="$1" local story_id=$(basename "$story_file" .md) # Reset violations LAST_ARCH_VIOLATIONS="" log ">>> ARCH COMPLIANCE: $story_id (fresh context)" # Load architecture file local arch_file="" for search_path in "$PROJECT_ROOT/docs/architecture.md" "$PROJECT_ROOT/docs/architecture/architecture.md" "$PROJECT_ROOT/architecture.md"; do if [ -f "$search_path" ]; then arch_file="$search_path" break fi done if [ -z "$arch_file" ]; then log_warn "No architecture.md found - skipping compliance check" return 0 fi local arch_contents=$(cat "$arch_file") local story_contents=$(cat "$story_file") # Load step template if available local step_template="" if [ -f "$ARCH_COMPLIANCE_STEP" ]; then step_template=$(cat "$ARCH_COMPLIANCE_STEP") fi local arch_prompt="You are an Architecture Compliance Validator executing a BMAD compliance check. ## Your Task Validate architecture compliance for story: $story_id You are checking the staged changes against the project's established architecture patterns. This is a TARGETED CHECK - focus only on structural/architectural issues, not code quality. ### CRITICAL AUTOMATION RULES - Do NOT pause for user confirmation - Execute the full compliance check - Fix HIGH severity violations automatically - Document MEDIUM and LOW violations ## Architecture Reference $arch_contents ## Story Context $story_contents ## Staged Changes Run: git diff --staged --name-only Then for each changed file: git diff --staged ## Compliance Checklist ### 1. Layer Violations - UI/Presentation only handles display logic - Business logic in service/domain layer - Data access confined to repository/data layer - Controllers only orchestrate ### 2. Dependency Direction - No circular dependencies - Lower layers don't import from higher layers - Core doesn't depend on infrastructure ### 3. Pattern Conformance - State management uses project's standard - Error handling follows conventions - API calls use established patterns ### 4. Module Boundaries - Feature code in correct module - No cross-module imports bypassing interfaces ### 5. File Organization - Files in correct directories - Naming follows conventions ## Fix Policy | Severity | Action | |----------|--------| | HIGH | Fix immediately | | MEDIUM | Fix if possible, otherwise document | | LOW | Document only | ## Completion Signals If compliant (no HIGH/MEDIUM violations or all fixed): Output: ARCH COMPLIANT: $story_id Or: ARCH COMPLIANT WITH FIXES: $story_id - Fixed N violations If HIGH violations cannot be fixed: First output: \`\`\` ARCH VIOLATIONS START - [HIGH] Description (file:line) - [MEDIUM] Description (file:line) ARCH VIOLATIONS END \`\`\` Then: ARCH VIOLATIONS: $story_id - [summary] ## Begin Execution Check architecture compliance now. Stage any fixes with: git add -A" if [ "$DRY_RUN" = true ]; then echo "[DRY RUN] Would execute architecture compliance check for $story_id" return 0 fi local result result=$(claude --dangerously-skip-permissions -p "$arch_prompt" 2>&1) || true echo "$result" >> "$LOG_FILE" if echo "$result" | grep -q "ARCH COMPLIANT"; then log_success "Architecture compliant: $story_id" return 0 elif echo "$result" | grep -q "ARCH VIOLATIONS"; then log_error "Architecture violations found: $story_id" echo "$result" | grep "ARCH VIOLATIONS" # Extract violations for fix loop LAST_ARCH_VIOLATIONS=$(echo "$result" | sed -n '/ARCH VIOLATIONS START/,/ARCH VIOLATIONS END/p' | grep -E '^\s*-\s*\[(HIGH|MEDIUM)\]' || true) if [ -n "$LAST_ARCH_VIOLATIONS" ]; then log "Captured architecture violations for fix loop" fi return 1 else log_warn "Architecture check did not complete cleanly: $story_id" return 0 # Don't block on unclear result fi } execute_test_quality_phase() { local story_file="$1" local story_id=$(basename "$story_file" .md) # Reset issues LAST_TEST_QUALITY_ISSUES="" log ">>> TEST QUALITY: $story_id (fresh context)" local story_contents=$(cat "$story_file") local quality_prompt="You are a Test Architect (TEA) executing a test quality review. ## Your Task Review the tests created for story: $story_id Focus on test maintainability, determinism, isolation, and flakiness prevention. ### CRITICAL AUTOMATION RULES - Do NOT pause for user confirmation - Execute the full quality review - Fix CRITICAL and HIGH issues automatically - Document MEDIUM and LOW issues ## Story Context $story_contents ## Test Files to Review Find test files from Dev Agent Record: \`\`\`bash git diff --staged --name-only | grep -E '\\.(spec|test)\\.(ts|js|tsx|jsx)\$' \`\`\` ## Quality Criteria ### 1. BDD Format (Given-When-Then) ### 2. Test ID Conventions ({story_id}-E2E-001, etc.) ### 3. Hard Waits Detection (no sleep(), waitForTimeout()) ### 4. Determinism (no conditionals, no random values) ### 5. Isolation & Cleanup (afterEach hooks, no shared state) ### 6. Explicit Assertions (every test has expect/assert) ### 7. Test Length (≤300 lines) ### 8. Fixture Patterns ### 9. Data Factories (no hardcoded test data) ### 10. Network-First Pattern (intercept before navigate) ### 11. Flakiness Patterns ## Quality Score Starting: 100 - Critical violations: -10 each - High violations: -5 each - Medium violations: -2 each - Low violations: -1 each - Bonus for best practices: +5 each ## Fix Policy | Severity | Action | |----------|--------| | CRITICAL | Must fix | | HIGH | Fix if total issues > 3 | | MEDIUM | Document | | LOW | Document | ## Completion Signals If quality approved (score ≥70, no critical/high remaining): Output: TEST QUALITY APPROVED: $story_id - Score: N/100 Or: TEST QUALITY APPROVED WITH FIXES: $story_id - Score: N/100, Fixed M issues If quality concerns (score 60-69): Output: TEST QUALITY CONCERNS: $story_id - Score: N/100 If quality failed (score <60 or unfixable critical issues): First output: \`\`\` TEST QUALITY ISSUES START - [CRITICAL] Description (file:line) - [HIGH] Description (file:line) TEST QUALITY ISSUES END \`\`\` Then: TEST QUALITY FAILED: $story_id - Score: N/100 ## Begin Execution Review test quality now. Stage any fixes with: git add -A" if [ "$DRY_RUN" = true ]; then echo "[DRY RUN] Would execute test quality review for $story_id" return 0 fi local result result=$(claude --dangerously-skip-permissions -p "$quality_prompt" 2>&1) || true echo "$result" >> "$LOG_FILE" if echo "$result" | grep -q "TEST QUALITY APPROVED"; then log_success "Test quality approved: $story_id" return 0 elif echo "$result" | grep -q "TEST QUALITY CONCERNS"; then log_warn "Test quality concerns: $story_id" return 0 # Concerns don't block elif echo "$result" | grep -q "TEST QUALITY FAILED"; then log_error "Test quality failed: $story_id" echo "$result" | grep "TEST QUALITY FAILED" # Extract issues for fix loop LAST_TEST_QUALITY_ISSUES=$(echo "$result" | sed -n '/TEST QUALITY ISSUES START/,/TEST QUALITY ISSUES END/p' | grep -E '^\s*-\s*\[(CRITICAL|HIGH)\]' || true) if [ -n "$LAST_TEST_QUALITY_ISSUES" ]; then log "Captured test quality issues for fix loop" fi return 1 else log_warn "Test quality check did not complete cleanly: $story_id" return 0 # Don't block on unclear result fi } execute_traceability_phase() { log ">>> TRACEABILITY CHECK: Epic $EPIC_ID (fresh context)" # Reset gaps LAST_TRACEABILITY_GAPS="" # Ensure output directory exists mkdir -p "$TRACEABILITY_DIR" local epic_contents=$(cat "$EPIC_FILE") # Build story contents block local all_stories="" for story_file in "${STORIES[@]}"; do local story_id=$(basename "$story_file" .md) all_stories+=" $(cat "$story_file") " done local story_count=${#STORIES[@]} local trace_prompt="You are a Test Architect (TEA) executing requirements traceability analysis. ## Your Task Generate a traceability matrix for Epic: $EPIC_ID Map ALL acceptance criteria from ALL stories to their implementing tests. Identify coverage gaps and determine if the epic is ready for UAT. ### CRITICAL AUTOMATION RULES - Do NOT pause for user confirmation - Execute the full traceability analysis - Generate the traceability matrix document - If gaps found, output them in structured format for auto-fix ## Epic Definition $epic_contents ## Completed Stories ($story_count total) $all_stories ## Phase 1: Discover Tests \`\`\`bash find . -type f \\( -name \"*.spec.ts\" -o -name \"*.test.ts\" -o -name \"*.spec.js\" -o -name \"*.test.js\" \\) | head -100 \`\`\` ## Phase 2: Map Criteria to Tests For each acceptance criterion: - Search for test IDs, describe blocks - Classify: FULL, PARTIAL, NONE, UNIT-ONLY, INTEGRATION-ONLY ## Coverage Thresholds | Priority | Required | Gate Impact | |----------|----------|-------------| | P0 | 100% | FAIL if not met | | P1 | ≥90% | CONCERNS if 80-89%, FAIL if <80% | | P2 | ≥80% | Advisory | | P3 | None | Advisory | ## Phase 3: Gap Analysis Identify: - Critical gaps (P0 without coverage) - High priority gaps (P1 < 90%) - Medium priority gaps (P2 < 80%) ## Phase 4: Generate Deliverables Save traceability matrix to: $TRACEABILITY_DIR/epic-${EPIC_ID}-traceability.md ## Completion Signals If PASS (P0=100%, P1≥90%): Output: TRACEABILITY PASS: $EPIC_ID - P0: N%, P1: M%, Overall: O% If CONCERNS (P0=100%, P1 80-89%): Output: TRACEABILITY CONCERNS: $EPIC_ID - P1 at N% (below 90%) If FAIL (P0<100% or P1<80%): First output gaps for self-healing: \`\`\` TRACEABILITY GAPS START GAP: {story_id}|AC-{n}|{priority}|{description}|{recommended_test_id}|{test_level} SPEC: Given: {precondition} When: {action} Then: {expected result} GAP: ... TRACEABILITY GAPS END \`\`\` Then: TRACEABILITY FAIL: $EPIC_ID - P0: N%, P1: M%, X critical gaps ## Begin Execution Analyze traceability now." if [ "$DRY_RUN" = true ]; then echo "[DRY RUN] Would execute traceability analysis for Epic $EPIC_ID" return 0 fi local result result=$(claude --dangerously-skip-permissions -p "$trace_prompt" 2>&1) || true echo "$result" >> "$LOG_FILE" if echo "$result" | grep -q "TRACEABILITY PASS"; then log_success "Traceability passed: Epic $EPIC_ID" return 0 elif echo "$result" | grep -q "TRACEABILITY CONCERNS"; then log_warn "Traceability concerns: Epic $EPIC_ID" return 0 # Concerns don't block elif echo "$result" | grep -q "TRACEABILITY FAIL"; then log_error "Traceability failed: Epic $EPIC_ID" echo "$result" | grep "TRACEABILITY FAIL" # Extract gaps for self-healing LAST_TRACEABILITY_GAPS=$(echo "$result" | sed -n '/TRACEABILITY GAPS START/,/TRACEABILITY GAPS END/p' || true) if [ -n "$LAST_TRACEABILITY_GAPS" ]; then log "Captured traceability gaps for self-healing" fi return 1 else log_warn "Traceability check did not complete cleanly" return 0 # Don't block on unclear result fi } execute_traceability_fix_phase() { local gaps="$1" local attempt_num="$2" log ">>> TRACEABILITY FIX: Epic $EPIC_ID (attempt $attempt_num, generating missing tests)" local fix_prompt="You are a Test Architect generating tests to close coverage gaps. ## Your Task Generate missing tests for Epic: $EPIC_ID (attempt $attempt_num of $MAX_TRACEABILITY_FIX_ATTEMPTS) ### CRITICAL RULES - Generate ONLY the tests specified in the gaps - Follow existing test patterns in the codebase - Run each test to verify it passes - Stage changes: git add -A ## Gaps to Address $gaps ## Instructions For each GAP: 1. Parse the specification (Given/When/Then) 2. Create the test file if needed 3. Implement the test following the spec 4. Use existing patterns from codebase 5. Run the test 6. Stage changes ## Completion Signals If all tests generated: Output: TEST GENERATION COMPLETE: Generated N tests If partial success: Output: TEST GENERATION PARTIAL: Generated N of M tests - [reason] ## Begin Execution Generate missing tests now." if [ "$DRY_RUN" = true ]; then echo "[DRY RUN] Would generate missing tests for Epic $EPIC_ID (attempt $attempt_num)" return 0 fi local result result=$(claude --dangerously-skip-permissions -p "$fix_prompt" 2>&1) || true echo "$result" >> "$LOG_FILE" if echo "$result" | grep -q "TEST GENERATION COMPLETE"; then log_success "Test generation complete for Epic $EPIC_ID" return 0 elif echo "$result" | grep -q "TEST GENERATION PARTIAL"; then log_warn "Partial test generation for Epic $EPIC_ID" return 1 else log_error "Test generation did not complete cleanly" return 1 fi } execute_story_with_fix_loop() { local story_file="$1" local story_id=$(basename "$story_file" .md) local fix_attempt=0 local arch_fix_attempt=0 local test_quality_fix_attempt=0 local needs_fixes=false # DEV PHASE (Context 1) if ! execute_dev_phase "$story_file"; then log_error "Dev phase failed for $story_id" return 1 fi # ARCHITECTURE COMPLIANCE CHECK (Context 2) - Per Story if [ "$SKIP_ARCH" = false ]; then while true; do if execute_arch_compliance_phase "$story_file"; then log_success "Architecture compliant: $story_id" break fi # Check if we have violations to fix if [ -z "$LAST_ARCH_VIOLATIONS" ]; then log_warn "Arch check unclear, proceeding anyway" break fi ((arch_fix_attempt++)) if [ $arch_fix_attempt -gt $MAX_ARCH_FIX_ATTEMPTS ]; then log_error "Max arch fix attempts ($MAX_ARCH_FIX_ATTEMPTS) reached for $story_id" add_metrics_issue "$story_id" "arch_violations" "Architecture violations after $MAX_ARCH_FIX_ATTEMPTS attempts" # Don't fail the story, proceed with violations documented break fi log_warn "Arch violations found, attempting fix $arch_fix_attempt of $MAX_ARCH_FIX_ATTEMPTS" # Use the regular fix phase with arch context if ! execute_fix_phase "$story_file" "$LAST_ARCH_VIOLATIONS" "$arch_fix_attempt"; then log_warn "Arch fix incomplete, continuing..." fi done fi # REVIEW + FIX LOOP while true; do # REVIEW PHASE (Fresh Context) if execute_review_phase "$story_file"; then # Review passed - proceed to test quality log_success "Story passed review: $story_id" break fi # Review failed - check if we have findings to fix if [ -z "$LAST_REVIEW_FINDINGS" ]; then log_error "Review failed but no findings captured for $story_id" return 1 fi # First failure - record that this story required fixes if [ "$needs_fixes" = false ]; then needs_fixes=true record_story_required_fixes "$story_id" fi # Check if we've exhausted fix attempts ((fix_attempt++)) if [ $fix_attempt -gt $MAX_FIX_ATTEMPTS ]; then log_error "Max fix attempts ($MAX_FIX_ATTEMPTS) reached for $story_id" record_fix_attempt "$story_id" "$fix_attempt" "max_retries" add_metrics_issue "$story_id" "max_retries_exhausted" "Failed after $MAX_FIX_ATTEMPTS fix attempts" return 1 fi log_warn "Review failed, attempting fix $fix_attempt of $MAX_FIX_ATTEMPTS for $story_id" # FIX PHASE (New Context) if ! execute_fix_phase "$story_file" "$LAST_REVIEW_FINDINGS" "$fix_attempt"; then log_error "Fix phase failed for $story_id (attempt $fix_attempt)" # Continue to next attempt - the review will catch remaining issues fi # Loop back to review phase to verify fixes log "Re-running review after fix attempt $fix_attempt..." done # TEST QUALITY REVIEW (Fresh Context) - Per Story if [ "$SKIP_TEST_QUALITY" = false ]; then while true; do if execute_test_quality_phase "$story_file"; then log_success "Test quality approved: $story_id" break fi # Check if we have issues to fix if [ -z "$LAST_TEST_QUALITY_ISSUES" ]; then log_warn "Test quality check unclear, proceeding anyway" break fi ((test_quality_fix_attempt++)) if [ $test_quality_fix_attempt -gt $MAX_TEST_QUALITY_FIX_ATTEMPTS ]; then log_warn "Max test quality fix attempts ($MAX_TEST_QUALITY_FIX_ATTEMPTS) reached for $story_id" add_metrics_issue "$story_id" "test_quality_concerns" "Test quality issues after $MAX_TEST_QUALITY_FIX_ATTEMPTS attempts" # Don't fail the story, proceed with concerns documented break fi log_warn "Test quality issues found, attempting fix $test_quality_fix_attempt of $MAX_TEST_QUALITY_FIX_ATTEMPTS" # Use the regular fix phase with test quality context if ! execute_fix_phase "$story_file" "$LAST_TEST_QUALITY_ISSUES" "$test_quality_fix_attempt"; then log_warn "Test quality fix incomplete, continuing..." fi done fi return 0 } commit_story() { local story_id="$1" if [ "$NO_COMMIT" = true ]; then log "Skipping commit (--no-commit)" return 0 fi if [ "$DRY_RUN" = true ]; then echo "[DRY RUN] Would commit: feat(epic-$EPIC_ID): complete $story_id" return 0 fi git add -A git commit -m "feat(epic-$EPIC_ID): complete $story_id" || { log_warn "Nothing to commit for $story_id" } log_success "Committed: $story_id" } generate_uat() { log ">>> GENERATING UAT DOCUMENT (using BMAD UAT template, fresh context)" # Load UAT step template if available local uat_step_template="" if [ -f "$UAT_STEP_TEMPLATE" ]; then uat_step_template=$(cat "$UAT_STEP_TEMPLATE") fi # Load UAT document template if available local uat_doc_template="" if [ -f "$UAT_DOC_TEMPLATE" ]; then uat_doc_template=$(cat "$UAT_DOC_TEMPLATE") fi local epic_contents=$(cat "$EPIC_FILE") local all_stories="" for story_file in "${STORIES[@]}"; do local story_id=$(basename "$story_file" .md) all_stories+=" $(cat "$story_file") " done # Count stories local story_count=${#STORIES[@]} # Build the UAT generation prompt using BMAD workflow step local uat_prompt="You are executing BMAD UAT generation step in automated mode. ## Context This is Step 4 of the BMAD epic-execute workflow: Generate User Acceptance Testing Document. You are running in a completely fresh context - you see only the finished epic and story specifications. ### CRITICAL RULES - Write for NON-TECHNICAL users who can use software but don't know how it's built - Focus on user journeys, not implementation details - Generate clear, actionable test scenarios with binary pass/fail criteria - Complete the entire document in a single execution ## BMAD UAT Generation Step Instructions $uat_step_template ## BMAD UAT Document Template $uat_doc_template ## Epic Definition **Epic ID:** $EPIC_ID **Epic File:** $EPIC_FILE $epic_contents ## Completed Stories (${story_count} total) $all_stories ## Pre-resolved Variables - epic_id: $EPIC_ID - story_count: $story_count - date: $(date '+%Y-%m-%d') - output_path: $UAT_DIR/epic-${EPIC_ID}-uat.md ## Scenario Generation Guidelines ### Good Scenarios - Follow realistic user workflows - Build on each other (Scenario 2 assumes Scenario 1 completed) - Include at least one 'happy path' and one 'error path' - Test the boundaries (empty inputs, maximum values, etc.) ### Avoid - Testing implementation details - Requiring technical knowledge to execute - Ambiguous expected results - Overlapping scenarios that test the same thing ## Output 1. Generate the complete UAT document following the template structure 2. Save to: $UAT_DIR/epic-${EPIC_ID}-uat.md 3. Output exactly: UAT GENERATED: $UAT_DIR/epic-${EPIC_ID}-uat.md ## Begin Execution Generate the UAT document now." if [ "$DRY_RUN" = true ]; then echo "[DRY RUN] Would generate UAT document using BMAD template" echo "[DRY RUN] Template: $UAT_STEP_TEMPLATE" return 0 fi local result result=$(claude --dangerously-skip-permissions -p "$uat_prompt" 2>&1) || true echo "$result" >> "$LOG_FILE" if echo "$result" | grep -q "UAT GENERATED"; then log_success "UAT document generated" else log_warn "UAT generation may not have completed cleanly" fi # Commit UAT document if [ "$NO_COMMIT" = false ]; then git add "$UAT_DIR/epic-${EPIC_ID}-uat.md" 2>/dev/null || true git commit -m "docs(epic-$EPIC_ID): add UAT document" 2>/dev/null || true fi } # ============================================================================= # Main Execution Loop # ============================================================================= log "==========================================" log "Starting execution of ${#STORIES[@]} stories" log "==========================================" COMPLETED=0 FAILED=0 SKIPPED=0 START_TIME=$(date +%s) STARTED=false for story_file in "${STORIES[@]}"; do story_id=$(basename "$story_file" .md) # --start-from: Skip stories until we reach the specified one if [ -n "$START_FROM" ] && [ "$STARTED" = false ]; then if [[ "$story_id" == *"$START_FROM"* ]]; then STARTED=true else log_warn "Skipping $story_id (waiting for $START_FROM)" ((SKIPPED++)) update_story_metrics "skipped" continue fi fi # --skip-done: Skip stories with Status: done (case-insensitive) if [ "$SKIP_DONE" = true ]; then if grep -qi "^Status:.*done" "$story_file" 2>/dev/null; then log_warn "Skipping $story_id (Status: Done)" ((SKIPPED++)) update_story_metrics "skipped" continue fi fi echo "" log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" log "Story: $story_id" log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" # Execute story with fix loop (dev → review → fix loop if needed) if [ "$SKIP_REVIEW" = false ]; then # Full flow: dev → review (with fix loop if issues found) if ! execute_story_with_fix_loop "$story_file"; then log_error "Story execution failed for $story_id" ((FAILED++)) update_story_metrics "failed" continue fi else # Skip review: just run dev phase if ! execute_dev_phase "$story_file"; then log_error "Dev phase failed for $story_id" ((FAILED++)) update_story_metrics "failed" add_metrics_issue "$story_id" "dev_phase_failed" "Development phase did not complete" continue fi fi # MARK STORY AS DONE # Update both story file and sprint-status.yaml after successful review if [ "$DRY_RUN" = false ]; then mark_story_done "$story_file" else echo "[DRY RUN] Would mark story as done: $story_id" fi # COMMIT commit_story "$story_id" ((COMPLETED++)) update_story_metrics "completed" log_success "Story complete: $story_id ($COMPLETED/${#STORIES[@]})" done # ============================================================================= # Traceability Check (Per-Epic, with Self-Healing) # ============================================================================= if [ "$SKIP_TRACEABILITY" = false ]; then echo "" log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" log "Requirements Traceability Check" log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" trace_fix_attempt=0 while true; do if execute_traceability_phase; then log_success "Traceability check passed for Epic $EPIC_ID" break fi # Check if we have gaps to fix if [ -z "$LAST_TRACEABILITY_GAPS" ]; then log_warn "Traceability check unclear, proceeding to UAT" break fi ((trace_fix_attempt++)) if [ $trace_fix_attempt -gt $MAX_TRACEABILITY_FIX_ATTEMPTS ]; then log_warn "Max traceability fix attempts ($MAX_TRACEABILITY_FIX_ATTEMPTS) reached" add_metrics_issue "epic-$EPIC_ID" "traceability_gaps" "Coverage gaps remain after $MAX_TRACEABILITY_FIX_ATTEMPTS attempts" # Don't fail the epic, proceed with gaps documented break fi log_warn "Traceability gaps found, generating missing tests (attempt $trace_fix_attempt of $MAX_TRACEABILITY_FIX_ATTEMPTS)" if ! execute_traceability_fix_phase "$LAST_TRACEABILITY_GAPS" "$trace_fix_attempt"; then log_warn "Test generation incomplete, continuing..." fi # Commit any generated tests if [ "$NO_COMMIT" = false ] && [ "$DRY_RUN" = false ]; then git add -A git commit -m "test(epic-$EPIC_ID): generate missing tests for traceability (attempt $trace_fix_attempt)" 2>/dev/null || true fi log "Re-running traceability check..." done fi # ============================================================================= # UAT Generation (Fresh Context) # ============================================================================= echo "" log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" log "UAT Document Generation" log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" generate_uat # ============================================================================= # Summary # ============================================================================= END_TIME=$(date +%s) DURATION=$((END_TIME - START_TIME)) # Finalize metrics with final counts finalize_metrics "${#STORIES[@]}" "$COMPLETED" "$FAILED" "$SKIPPED" "$DURATION" echo "" log "==========================================" log "EPIC EXECUTION COMPLETE" log "==========================================" echo "" echo " Epic: $EPIC_ID" echo " Duration: ${DURATION}s" echo " Stories: ${#STORIES[@]}" echo " Skipped: $SKIPPED" echo " Completed: $COMPLETED" echo " Failed: $FAILED" echo "" echo " Deliverables:" echo " - Stories: $STORIES_DIR/" echo " - UAT: $UAT_DIR/epic-${EPIC_ID}-uat.md" echo " - Traceability: $TRACEABILITY_DIR/epic-${EPIC_ID}-traceability.md" echo " - Metrics: $METRICS_FILE" echo " - Log: $LOG_FILE" echo "" if [ $FAILED -gt 0 ]; then log_warn "$FAILED stories failed - check log for details" exit 1 fi log_success "All stories completed successfully" echo "" echo "Next step: Run UAT document with a human tester" echo ""