1969 lines
59 KiB
Bash
Executable File
1969 lines
59 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# BMAD Epic Execute - Automated Story Execution with Context Isolation
|
|
#
|
|
# Usage: ./epic-execute.sh <epic-id> [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: <anything>" and replaces with "Status: <new_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 <epic-id> [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>
|
|
$workflow_executor
|
|
</workflow-executor>
|
|
|
|
## Dev-Story Workflow Configuration
|
|
|
|
<workflow-yaml>
|
|
$workflow_yaml
|
|
</workflow-yaml>
|
|
|
|
## Dev-Story Workflow Instructions
|
|
|
|
<workflow-instructions>
|
|
$workflow_instructions
|
|
</workflow-instructions>
|
|
|
|
## Definition of Done Checklist
|
|
|
|
<validation-checklist>
|
|
$workflow_checklist
|
|
</validation-checklist>
|
|
|
|
## Story to Implement
|
|
|
|
**Story Path:** $story_file
|
|
**Story ID:** $story_id
|
|
|
|
<story-contents>
|
|
$story_contents
|
|
</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>
|
|
$workflow_executor
|
|
</workflow-executor>
|
|
|
|
## Code-Review Workflow Configuration
|
|
|
|
<workflow-yaml>
|
|
$workflow_yaml
|
|
</workflow-yaml>
|
|
|
|
## Code-Review Workflow Instructions
|
|
|
|
<workflow-instructions>
|
|
$workflow_instructions
|
|
</workflow-instructions>
|
|
|
|
## Review Validation Checklist
|
|
|
|
<validation-checklist>
|
|
$workflow_checklist
|
|
</validation-checklist>
|
|
|
|
## Story to Review
|
|
|
|
**Story Path:** $story_file
|
|
**Story ID:** $story_id
|
|
|
|
<story-contents>
|
|
$story_contents
|
|
</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>
|
|
$review_findings
|
|
</review-findings>
|
|
|
|
## Workflow Executor Engine
|
|
|
|
<workflow-executor>
|
|
$workflow_executor
|
|
</workflow-executor>
|
|
|
|
## Dev-Story Workflow Configuration
|
|
|
|
<workflow-yaml>
|
|
$workflow_yaml
|
|
</workflow-yaml>
|
|
|
|
## Dev-Story Workflow Instructions
|
|
|
|
<workflow-instructions>
|
|
$workflow_instructions
|
|
</workflow-instructions>
|
|
|
|
## Definition of Done Checklist
|
|
|
|
<validation-checklist>
|
|
$workflow_checklist
|
|
</validation-checklist>
|
|
|
|
## Story Being Fixed
|
|
|
|
**Story Path:** $story_file
|
|
**Story ID:** $story_id
|
|
**Fix Attempt:** $attempt_num of 3
|
|
|
|
<story-contents>
|
|
$story_contents
|
|
</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
|
|
|
|
<architecture>
|
|
$arch_contents
|
|
</architecture>
|
|
|
|
## Story Context
|
|
|
|
<story>
|
|
$story_contents
|
|
</story>
|
|
|
|
## 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>
|
|
$story_contents
|
|
</story>
|
|
|
|
## 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+="
|
|
<story id=\"$story_id\">
|
|
$(cat "$story_file")
|
|
</story>
|
|
"
|
|
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>
|
|
$epic_contents
|
|
</epic>
|
|
|
|
## 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+="
|
|
<story id=\"$story_id\">
|
|
$(cat "$story_file")
|
|
</story>
|
|
"
|
|
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>
|
|
$uat_step_template
|
|
</uat-step-template>
|
|
|
|
## BMAD UAT Document Template
|
|
|
|
<uat-doc-template>
|
|
$uat_doc_template
|
|
</uat-doc-template>
|
|
|
|
## Epic Definition
|
|
|
|
**Epic ID:** $EPIC_ID
|
|
**Epic File:** $EPIC_FILE
|
|
|
|
<epic>
|
|
$epic_contents
|
|
</epic>
|
|
|
|
## 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 ""
|