774 lines
22 KiB
Bash
Executable File
774 lines
22 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
|
|
#
|
|
|
|
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"
|
|
|
|
# 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
|
|
validation:
|
|
gate_executed: false
|
|
gate_status: "PENDING"
|
|
fix_attempts: 0
|
|
issues: []
|
|
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
|
|
}
|
|
|
|
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"
|
|
}
|
|
|
|
# =============================================================================
|
|
# Argument Parsing
|
|
# =============================================================================
|
|
|
|
EPIC_ID=""
|
|
DRY_RUN=false
|
|
SKIP_REVIEW=false
|
|
NO_COMMIT=false
|
|
PARALLEL=false
|
|
VERBOSE=false
|
|
START_FROM=""
|
|
SKIP_DONE=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
|
|
;;
|
|
-*)
|
|
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"
|
|
exit 1
|
|
fi
|
|
|
|
# =============================================================================
|
|
# Setup
|
|
# =============================================================================
|
|
|
|
log "Starting epic execution for: $EPIC_ID"
|
|
log "Project root: $PROJECT_ROOT"
|
|
|
|
# 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"
|
|
|
|
local story_contents=$(cat "$story_file")
|
|
|
|
# Build the dev prompt
|
|
local dev_prompt="You are the Dev agent executing a BMAD story implementation.
|
|
|
|
## Your Task
|
|
|
|
Implement story: $story_id
|
|
|
|
## Story Specification
|
|
|
|
<story>
|
|
$story_contents
|
|
</story>
|
|
|
|
## Implementation Requirements
|
|
|
|
1. Read the story file completely before writing any code
|
|
2. Follow existing patterns in the codebase
|
|
3. Implement ALL acceptance criteria
|
|
4. Write tests for each criterion
|
|
5. Run tests and fix any failures
|
|
6. Update documentation as needed
|
|
|
|
## When Complete
|
|
|
|
1. Update the story file:
|
|
- Change Status to: In Review
|
|
- Fill in the Dev Agent Record section with:
|
|
- Implementation Summary
|
|
- Files Created/Modified
|
|
- Key Decisions
|
|
- Tests Added
|
|
- Test Results (summary of test run)
|
|
- Notes for Reviewer
|
|
- Acceptance Criteria Status (checklist with file references)
|
|
|
|
2. Stage changes: git add -A
|
|
|
|
3. Output exactly: IMPLEMENTATION COMPLETE: $story_id
|
|
|
|
If blocked, output: IMPLEMENTATION BLOCKED: $story_id - [reason]"
|
|
|
|
if [ "$DRY_RUN" = true ]; then
|
|
echo "[DRY RUN] Would execute dev phase for $story_id"
|
|
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
|
|
}
|
|
|
|
execute_review_phase() {
|
|
local story_file="$1"
|
|
local story_id=$(basename "$story_file" .md)
|
|
|
|
log ">>> REVIEW PHASE: $story_id (fresh context)"
|
|
|
|
local story_contents=$(cat "$story_file")
|
|
|
|
# Build the review prompt with severity-based fix logic
|
|
local review_prompt="You are a Senior Code Reviewer performing a BMAD code review.
|
|
|
|
## Your Task
|
|
|
|
Review the implementation of story: $story_id
|
|
|
|
You are seeing this code for the first time. You have no knowledge of the implementation process.
|
|
|
|
## Story Specification and Dev Context
|
|
|
|
<story>
|
|
$story_contents
|
|
</story>
|
|
|
|
The story file contains:
|
|
- Acceptance criteria (what must be verified)
|
|
- Dev Agent Record (implementation notes from the developer)
|
|
- Notes for Reviewer (areas of concern flagged by dev)
|
|
|
|
## Review Process
|
|
|
|
1. Run: git diff --staged
|
|
2. Verify each acceptance criterion is implemented and tested
|
|
3. Check code quality, security, and patterns
|
|
4. Collect and categorize all issues by severity
|
|
|
|
## Issue Severity Definitions
|
|
|
|
- **HIGH**: Security vulnerabilities, missing error handling, no tests for new code, N+1 queries, exposed credentials
|
|
- **MEDIUM**: Pattern violations, missing edge cases, hardcoded config values, code duplication
|
|
- **LOW**: Naming inconsistencies, minor style issues, missing comments
|
|
|
|
## Issue Fix Policy (IMPORTANT)
|
|
|
|
Apply this logic after collecting all issues:
|
|
|
|
\`\`\`
|
|
1. Always fix ALL HIGH severity issues
|
|
2. If TOTAL issues > 5, also fix ALL MEDIUM severity issues
|
|
3. LOW severity issues: document only, do NOT fix
|
|
\`\`\`
|
|
|
|
## Review Checklist
|
|
|
|
### Acceptance Criteria
|
|
For each criterion: implemented? tested? matches requirement?
|
|
|
|
### Code Quality
|
|
- Follows existing patterns (MEDIUM)
|
|
- No security issues (HIGH)
|
|
- Error handling appropriate (HIGH)
|
|
- Tests exist and meaningful (HIGH)
|
|
- No hardcoded secrets (HIGH)
|
|
|
|
## After Review
|
|
|
|
1. Compile issues found with severity
|
|
2. Count: HIGH=?, MEDIUM=?, LOW=?, TOTAL=?
|
|
3. Apply fix policy: fix HIGH always, fix MEDIUM if total > 5
|
|
4. For each fix: make change, run tests, verify
|
|
5. Stage any fixes: git add -A
|
|
|
|
## Update Story File
|
|
|
|
Add Code Review Record section:
|
|
|
|
\`\`\`markdown
|
|
## Code Review Record
|
|
|
|
**Reviewer**: Code Review Agent
|
|
**Date**: $(date '+%Y-%m-%d %H:%M')
|
|
|
|
### Issues Found
|
|
| # | Description | Severity | Status |
|
|
|---|-------------|----------|--------|
|
|
|
|
**Totals**: X HIGH, Y MEDIUM, Z LOW
|
|
|
|
### Fixes Applied
|
|
[List what was fixed]
|
|
|
|
### Remaining Issues
|
|
[Low severity items for future cleanup]
|
|
|
|
### Final Status
|
|
Approved / Approved with fixes / Rejected
|
|
\`\`\`
|
|
|
|
## Completion
|
|
|
|
If PASSED (no unfixed HIGH/MEDIUM issues):
|
|
1. Update story Status to: Done
|
|
2. Output: REVIEW PASSED: $story_id
|
|
or: REVIEW PASSED WITH FIXES: $story_id - Fixed N issues
|
|
|
|
If FAILED (unfixable issues or missing acceptance criteria):
|
|
1. Update story Status to: Blocked
|
|
2. Output: REVIEW FAILED: $story_id - [reason]"
|
|
|
|
if [ "$DRY_RUN" = true ]; then
|
|
echo "[DRY RUN] Would execute review phase for $story_id"
|
|
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"
|
|
return 1
|
|
else
|
|
log_warn "Review did not complete cleanly: $story_id"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
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 (fresh context)"
|
|
|
|
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
|
|
|
|
local uat_prompt="You are a QA Specialist creating a User Acceptance Testing document.
|
|
|
|
## Your Task
|
|
|
|
Generate a UAT document for Epic: $EPIC_ID
|
|
|
|
## Epic Definition
|
|
|
|
<epic>
|
|
$epic_contents
|
|
</epic>
|
|
|
|
## Completed Stories
|
|
|
|
$all_stories
|
|
|
|
## Requirements
|
|
|
|
Create a UAT document for NON-TECHNICAL users with:
|
|
|
|
1. **Overview**: What was built (plain language)
|
|
2. **Prerequisites**: Test environment, accounts, setup
|
|
3. **Test Scenarios**: Step-by-step instructions with expected results
|
|
4. **Success Criteria**: Checklist of what must work
|
|
5. **Sign-off Section**: For human approval
|
|
|
|
Write for someone who can use the software but doesn't know how it's built.
|
|
|
|
## Output
|
|
|
|
Save to: $UAT_DIR/epic-${EPIC_ID}-uat.md
|
|
|
|
When complete, output: UAT GENERATED: $UAT_DIR/epic-${EPIC_ID}-uat.md"
|
|
|
|
if [ "$DRY_RUN" = true ]; then
|
|
echo "[DRY RUN] Would generate UAT document"
|
|
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
|
|
if [ "$SKIP_DONE" = true ]; then
|
|
if grep -q "^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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
|
# DEV PHASE (Context 1)
|
|
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
|
|
|
|
# REVIEW PHASE (Context 2 - Fresh)
|
|
if [ "$SKIP_REVIEW" = false ]; then
|
|
if ! execute_review_phase "$story_file"; then
|
|
log_error "Review phase failed for $story_id"
|
|
((FAILED++))
|
|
update_story_metrics "failed"
|
|
add_metrics_issue "$story_id" "review_failed" "Code review phase failed"
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
# COMMIT
|
|
commit_story "$story_id"
|
|
|
|
((COMPLETED++))
|
|
update_story_metrics "completed"
|
|
log_success "Story complete: $story_id ($COMPLETED/${#STORIES[@]})"
|
|
done
|
|
|
|
# =============================================================================
|
|
# UAT Generation (Context 3 - Fresh)
|
|
# =============================================================================
|
|
|
|
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 " - 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 ""
|