feat(epic-execute): implement low-priority UX improvements (L1-L5)
- L1: Add checkpoint/resume capability with --resume flag - load_checkpoint(), save_checkpoint(), clear_checkpoint() in utils.sh - Auto-saves progress after each story, 7-day expiration - L2: Add comprehensive --help option with grouped options and examples - show_help() function with environment variables and file locations - L3: Add verbose Claude output streaming - execute_claude_verbose() for real-time output when --verbose set - L4: Add metrics file archival to prevent unbounded growth - Archives to metrics/archive/, keeps last 10 per epic - L5: Add workflow file content validation - validate_yaml_content(), validate_xml_content() with fallbacks - Integrated into validate_workflows() Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
06ce7e7fca
commit
51e31ada91
|
|
@ -657,7 +657,9 @@ check_branch_protection
|
|||
|
||||
Nice-to-have improvements for better UX and maintainability.
|
||||
|
||||
### L1. No Progress Persistence / Resume Capability
|
||||
### L1. No Progress Persistence / Resume Capability ✅ DONE
|
||||
|
||||
> **Implemented in `scripts/epic-execute-lib/utils.sh`** - Added `load_checkpoint()`, `save_checkpoint()`, `clear_checkpoint()`, and `get_resume_index()` functions. Added `--resume` flag to epic-execute.sh. Checkpoint includes story index, completed/failed/skipped counts, and timestamp. Old checkpoints (>7 days) are automatically ignored.
|
||||
|
||||
**Problem:** If script fails at story 5/10, user must use `--start-from` manually. No automatic resume.
|
||||
|
||||
|
|
@ -700,7 +702,9 @@ load_checkpoint() {
|
|||
|
||||
---
|
||||
|
||||
### L2. Missing --help Option
|
||||
### L2. Missing --help Option ✅ DONE
|
||||
|
||||
> **Implemented in `scripts/epic-execute.sh`** - Added `show_help()` function with comprehensive documentation of all options, examples, and environment variables. Added `-h` and `--help` flag handling at start of argument parsing.
|
||||
|
||||
**Problem:** No built-in help. Users must read script header comments.
|
||||
|
||||
|
|
@ -768,7 +772,9 @@ EOF
|
|||
|
||||
---
|
||||
|
||||
### L3. No Verbose Logging Option for Claude Output
|
||||
### L3. No Verbose Logging Option for Claude Output ✅ DONE
|
||||
|
||||
> **Implemented in `scripts/epic-execute-lib/utils.sh`** - Added `execute_claude_verbose()` function that streams Claude output to both terminal and log file when `--verbose` is set. Includes timeout handling and prompt size logging.
|
||||
|
||||
**Problem:** Claude output only goes to log file. Debugging requires reading `/tmp/bmad-epic-execute-$$.log`.
|
||||
|
||||
|
|
@ -794,7 +800,9 @@ execute_claude_prompt() {
|
|||
|
||||
---
|
||||
|
||||
### L4. Metrics File Can Grow Unbounded
|
||||
### L4. Metrics File Can Grow Unbounded ✅ DONE
|
||||
|
||||
> **Implemented in `scripts/epic-execute.sh`** - Updated `init_metrics()` to archive existing metrics files before creating new ones. Archives stored in `metrics/archive/` directory. Automatically cleans up old archives, keeping only the last 10 per epic.
|
||||
|
||||
**Problem:** YAML metrics with arrays (issues, story_details) grow indefinitely across multiple runs.
|
||||
|
||||
|
|
@ -819,7 +827,9 @@ init_metrics() {
|
|||
|
||||
---
|
||||
|
||||
### L5. No Validation of Workflow Files Content
|
||||
### L5. No Validation of Workflow Files Content ✅ DONE
|
||||
|
||||
> **Implemented in `scripts/epic-execute-lib/utils.sh`** - Added `validate_yaml_content()`, `validate_xml_content()`, and `validate_workflow_content()` functions. Uses yq for YAML validation (with fallback basic syntax checks) and xmllint for XML validation. Updated `validate_workflows()` in epic-execute.sh to call content validation.
|
||||
|
||||
**Problem:** Script checks if workflow files exist but not if they're valid YAML/XML.
|
||||
|
||||
|
|
@ -1210,11 +1220,15 @@ Output: TEST QUALITY FAILED: $story_id - Score: N/100"
|
|||
| M4 | Cross-platform sed | Low | ✅ Done |
|
||||
| M5 | Branch Protection | Low | ✅ Done |
|
||||
|
||||
### Phase 5: Low Priority (As Time Permits)
|
||||
### Phase 5: Low Priority ✅ COMPLETE
|
||||
|
||||
| ID | Improvement | Effort |
|
||||
|----|-------------|--------|
|
||||
| L1-L5 | UX Improvements | Low-Medium |
|
||||
| ID | Improvement | Effort | Status |
|
||||
|----|-------------|--------|--------|
|
||||
| L1 | Progress Persistence / Resume | Medium | ✅ Done |
|
||||
| L2 | --help Option | Low | ✅ Done |
|
||||
| L3 | Verbose Claude Output | Low | ✅ Done |
|
||||
| L4 | Metrics File Archival | Low | ✅ Done |
|
||||
| L5 | Workflow Content Validation | Medium | ✅ Done |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -1225,11 +1239,11 @@ The epic-execute library has a solid architecture with multi-phase validation an
|
|||
### Completed
|
||||
- ✅ **High-Priority Issues (5)** - All fixed (H1-H5) in commit `ce2f9fb3`
|
||||
- ✅ **Medium-Priority Issues (5)** - All fixed (M1-M5) via new `utils.sh` module
|
||||
- ✅ **Low-Priority Issues (5)** - All fixed (L1-L5) for better UX
|
||||
|
||||
### Remaining
|
||||
- ⏳ **Critical Issues (5)** - Must be fixed to ensure basic reliability
|
||||
- ⏳ **BMAD Integration Gaps (4)** - Custom prompts should leverage existing workflows for consistency
|
||||
- ⏳ **Low-Priority Issues (5)** - UX improvements for better usability
|
||||
|
||||
### Implementation Summary
|
||||
|
||||
|
|
@ -1239,9 +1253,12 @@ The epic-execute library has a solid architecture with multi-phase validation an
|
|||
- M3: `check_phase_completion_fuzzy()` - Case-insensitive pattern matching
|
||||
- M4: `sed_inplace()` / `sed_inplace_backup()` - Cross-platform sed
|
||||
- M5: `check_branch_protection()` - Prevents commits to main/master
|
||||
- L1: `load_checkpoint()` / `save_checkpoint()` / `clear_checkpoint()` - Resume capability
|
||||
- L3: `execute_claude_verbose()` - Verbose Claude output streaming
|
||||
- L5: `validate_yaml_content()` / `validate_xml_content()` / `validate_workflow_content()` - Content validation
|
||||
|
||||
**Updated Files:**
|
||||
- `scripts/epic-execute.sh` - Sources utils.sh, uses cross-platform sed, branch protection on startup
|
||||
- `scripts/epic-execute.sh` - Sources utils.sh, uses cross-platform sed, branch protection on startup, --help option, --resume flag, metrics archival, workflow content validation
|
||||
- `scripts/epic-execute-lib/json-output.sh` - Enhanced JSON extraction, fuzzy matching fallback
|
||||
|
||||
The epic-execute script is now more reliable with better error handling, cross-platform support, and safety checks.
|
||||
The epic-execute script is now more reliable with better error handling, cross-platform support, safety checks, and improved UX.
|
||||
|
|
|
|||
|
|
@ -445,6 +445,313 @@ get_current_branch() {
|
|||
echo ""
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# L1: Checkpoint / Resume Capability
|
||||
# =============================================================================
|
||||
|
||||
# Global checkpoint state
|
||||
CHECKPOINT_FILE=""
|
||||
CHECKPOINT_LOADED=false
|
||||
CHECKPOINT_STORY_INDEX=0
|
||||
CHECKPOINT_COMPLETED=0
|
||||
CHECKPOINT_FAILED=0
|
||||
CHECKPOINT_SKIPPED=0
|
||||
|
||||
# Load checkpoint from previous interrupted run
|
||||
# Arguments:
|
||||
# $1 - epic ID
|
||||
# $2 - sprint artifacts directory
|
||||
# Returns: 0 if checkpoint loaded successfully, 1 if no checkpoint
|
||||
load_checkpoint() {
|
||||
local epic_id="$1"
|
||||
local artifacts_dir="$2"
|
||||
|
||||
CHECKPOINT_FILE="$artifacts_dir/.epic-${epic_id}-checkpoint"
|
||||
|
||||
if [ ! -f "$CHECKPOINT_FILE" ]; then
|
||||
[ "$VERBOSE" = true ] && log "No checkpoint file found for epic $epic_id"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check checkpoint age (ignore checkpoints older than 7 days)
|
||||
local checkpoint_age=0
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
checkpoint_age=$(( $(date +%s) - $(stat -f %m "$CHECKPOINT_FILE" 2>/dev/null || echo 0) ))
|
||||
else
|
||||
checkpoint_age=$(( $(date +%s) - $(stat -c %Y "$CHECKPOINT_FILE" 2>/dev/null || echo 0) ))
|
||||
fi
|
||||
|
||||
local max_age=$((7 * 24 * 60 * 60)) # 7 days in seconds
|
||||
if [ "$checkpoint_age" -gt "$max_age" ]; then
|
||||
log_warn "Checkpoint file is older than 7 days - ignoring"
|
||||
rm -f "$CHECKPOINT_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Source checkpoint file to load variables
|
||||
# shellcheck source=/dev/null
|
||||
source "$CHECKPOINT_FILE" 2>/dev/null || {
|
||||
log_warn "Failed to read checkpoint file"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Validate checkpoint data
|
||||
if [ -z "${LAST_STORY_INDEX:-}" ]; then
|
||||
log_warn "Checkpoint file is invalid - missing LAST_STORY_INDEX"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Load checkpoint values into global state
|
||||
CHECKPOINT_LOADED=true
|
||||
CHECKPOINT_STORY_INDEX="${LAST_STORY_INDEX:-0}"
|
||||
CHECKPOINT_COMPLETED="${COMPLETED:-0}"
|
||||
CHECKPOINT_FAILED="${FAILED:-0}"
|
||||
CHECKPOINT_SKIPPED="${SKIPPED:-0}"
|
||||
|
||||
log "Checkpoint loaded from previous run:"
|
||||
log " Last story index: $CHECKPOINT_STORY_INDEX"
|
||||
log " Completed: $CHECKPOINT_COMPLETED, Failed: $CHECKPOINT_FAILED, Skipped: $CHECKPOINT_SKIPPED"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Save checkpoint after completing a story
|
||||
# Arguments:
|
||||
# $1 - current story index
|
||||
# $2 - story ID
|
||||
# $3 - completed count
|
||||
# $4 - failed count
|
||||
# $5 - skipped count
|
||||
save_checkpoint() {
|
||||
local story_index="$1"
|
||||
local story_id="$2"
|
||||
local completed="$3"
|
||||
local failed="$4"
|
||||
local skipped="$5"
|
||||
|
||||
if [ -z "$CHECKPOINT_FILE" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local timestamp
|
||||
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
cat > "$CHECKPOINT_FILE" << EOF
|
||||
# Epic checkpoint - $timestamp
|
||||
# Auto-generated by epic-execute.sh
|
||||
LAST_STORY_INDEX=$story_index
|
||||
LAST_STORY_ID=$story_id
|
||||
COMPLETED=$completed
|
||||
FAILED=$failed
|
||||
SKIPPED=$skipped
|
||||
TIMESTAMP=$timestamp
|
||||
EOF
|
||||
|
||||
[ "$VERBOSE" = true ] && log "Checkpoint saved: story $story_id (index $story_index)"
|
||||
}
|
||||
|
||||
# Clear checkpoint file after successful completion
|
||||
clear_checkpoint() {
|
||||
if [ -n "$CHECKPOINT_FILE" ] && [ -f "$CHECKPOINT_FILE" ]; then
|
||||
rm -f "$CHECKPOINT_FILE"
|
||||
log "Checkpoint cleared (epic completed successfully)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Get resume story index from checkpoint
|
||||
# Returns the next story index to process (LAST_STORY_INDEX + 1)
|
||||
get_resume_index() {
|
||||
if [ "$CHECKPOINT_LOADED" = true ]; then
|
||||
echo $((CHECKPOINT_STORY_INDEX + 1))
|
||||
else
|
||||
echo 0
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# L3: Verbose Claude Output Logging
|
||||
# =============================================================================
|
||||
|
||||
# Execute Claude prompt with optional verbose output streaming
|
||||
# Arguments:
|
||||
# $1 - prompt
|
||||
# $2 - phase name (for logging)
|
||||
# $3 - optional timeout (default: CLAUDE_TIMEOUT)
|
||||
# Returns: Claude's response
|
||||
execute_claude_verbose() {
|
||||
local prompt="$1"
|
||||
local phase_name="${2:-claude}"
|
||||
local timeout="${3:-${CLAUDE_TIMEOUT:-600}}"
|
||||
|
||||
local prompt_size=${#prompt}
|
||||
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
log ">>> Claude $phase_name prompt (${prompt_size} bytes)"
|
||||
log ">>> Streaming output to terminal..."
|
||||
|
||||
# Execute with output tee'd to both terminal and log file
|
||||
local result
|
||||
result=$(timeout "$timeout" claude --dangerously-skip-permissions -p "$prompt" 2>&1 | tee -a "$LOG_FILE")
|
||||
local exit_code=$?
|
||||
|
||||
if [ $exit_code -eq 124 ]; then
|
||||
log_error "Claude timed out after ${timeout}s"
|
||||
echo "TIMEOUT"
|
||||
return 124
|
||||
fi
|
||||
|
||||
echo "$result"
|
||||
return $exit_code
|
||||
else
|
||||
# Non-verbose mode: capture output silently
|
||||
local result
|
||||
result=$(timeout "$timeout" claude --dangerously-skip-permissions -p "$prompt" 2>&1)
|
||||
local exit_code=$?
|
||||
|
||||
# Log to file only
|
||||
{
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] >>> Claude $phase_name prompt (${prompt_size} bytes)"
|
||||
echo "$result"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] <<< Claude $phase_name complete (exit: $exit_code)"
|
||||
} >> "$LOG_FILE"
|
||||
|
||||
if [ $exit_code -eq 124 ]; then
|
||||
log_error "Claude timed out after ${timeout}s"
|
||||
echo "TIMEOUT"
|
||||
return 124
|
||||
fi
|
||||
|
||||
echo "$result"
|
||||
return $exit_code
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# L5: Workflow File Content Validation
|
||||
# =============================================================================
|
||||
|
||||
# Validate YAML content using yq or basic syntax check
|
||||
# Arguments:
|
||||
# $1 - file path
|
||||
# Returns: 0 if valid, 1 if invalid
|
||||
validate_yaml_content() {
|
||||
local file="$1"
|
||||
|
||||
if [ ! -f "$file" ]; then
|
||||
log_error "YAML validation: File not found: $file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Try yq first (most reliable)
|
||||
if [ "$YQ_AVAILABLE" = true ]; then
|
||||
if yq '.' "$file" >/dev/null 2>&1; then
|
||||
return 0
|
||||
else
|
||||
local error
|
||||
error=$(yq '.' "$file" 2>&1 || true)
|
||||
log_error "Invalid YAML in: $file"
|
||||
[ "$VERBOSE" = true ] && log_error " Error: $error"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fallback: basic syntax check (look for common YAML errors)
|
||||
# Check for tabs at start of lines (YAML uses spaces)
|
||||
if grep -q $'^\t' "$file" 2>/dev/null; then
|
||||
log_warn "Potential YAML issue in $file: tabs found (YAML requires spaces)"
|
||||
fi
|
||||
|
||||
# Check for unbalanced quotes
|
||||
local single_quotes double_quotes
|
||||
single_quotes=$(grep -o "'" "$file" 2>/dev/null | wc -l | tr -d ' ')
|
||||
double_quotes=$(grep -o '"' "$file" 2>/dev/null | wc -l | tr -d ' ')
|
||||
|
||||
if [ $((single_quotes % 2)) -ne 0 ]; then
|
||||
log_warn "Potential YAML issue in $file: unbalanced single quotes"
|
||||
fi
|
||||
if [ $((double_quotes % 2)) -ne 0 ]; then
|
||||
log_warn "Potential YAML issue in $file: unbalanced double quotes"
|
||||
fi
|
||||
|
||||
# Without yq, we can't fully validate - return success with warning
|
||||
[ "$VERBOSE" = true ] && log_warn "yq not available - YAML validation limited for: $file"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Validate XML content using xmllint or basic syntax check
|
||||
# Arguments:
|
||||
# $1 - file path
|
||||
# Returns: 0 if valid, 1 if invalid
|
||||
validate_xml_content() {
|
||||
local file="$1"
|
||||
|
||||
if [ ! -f "$file" ]; then
|
||||
log_error "XML validation: File not found: $file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Try xmllint first (most reliable)
|
||||
if command -v xmllint >/dev/null 2>&1; then
|
||||
if xmllint --noout "$file" 2>/dev/null; then
|
||||
return 0
|
||||
else
|
||||
local error
|
||||
error=$(xmllint --noout "$file" 2>&1 || true)
|
||||
log_error "Invalid XML in: $file"
|
||||
[ "$VERBOSE" = true ] && log_error " Error: $error"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fallback: basic syntax check
|
||||
# Check for matching opening/closing root tag
|
||||
local first_tag last_tag
|
||||
first_tag=$(grep -oE '<[a-zA-Z][a-zA-Z0-9_-]*' "$file" 2>/dev/null | head -1 | tr -d '<' || true)
|
||||
last_tag=$(grep -oE '</[a-zA-Z][a-zA-Z0-9_-]*>' "$file" 2>/dev/null | tail -1 | tr -d '</>' || true)
|
||||
|
||||
if [ -n "$first_tag" ] && [ -n "$last_tag" ] && [ "$first_tag" != "$last_tag" ]; then
|
||||
log_warn "Potential XML issue in $file: root tag mismatch ($first_tag vs $last_tag)"
|
||||
fi
|
||||
|
||||
# Without xmllint, we can't fully validate - return success with warning
|
||||
[ "$VERBOSE" = true ] && log_warn "xmllint not available - XML validation limited for: $file"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Validate workflow file content based on extension
|
||||
# Arguments:
|
||||
# $1 - file path
|
||||
# Returns: 0 if valid, 1 if invalid
|
||||
validate_workflow_content() {
|
||||
local file="$1"
|
||||
|
||||
if [ ! -f "$file" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local extension="${file##*.}"
|
||||
|
||||
case "$extension" in
|
||||
yaml|yml)
|
||||
validate_yaml_content "$file"
|
||||
return $?
|
||||
;;
|
||||
xml)
|
||||
validate_xml_content "$file"
|
||||
return $?
|
||||
;;
|
||||
md|txt)
|
||||
# Markdown/text files don't need validation
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
# Unknown extension - skip validation
|
||||
[ "$VERBOSE" = true ] && log_warn "Unknown file type, skipping validation: $file"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Initialization
|
||||
# =============================================================================
|
||||
|
|
|
|||
|
|
@ -378,6 +378,24 @@ init_metrics() {
|
|||
METRICS_FILE="$METRICS_DIR/epic-${EPIC_ID}-metrics.yaml"
|
||||
mkdir -p "$METRICS_DIR"
|
||||
|
||||
# L4: Archive existing metrics file to prevent unbounded growth
|
||||
if [ -f "$METRICS_FILE" ]; then
|
||||
local archive_name="epic-${EPIC_ID}-metrics.$(date +%Y%m%d%H%M%S).yaml"
|
||||
local archive_dir="$METRICS_DIR/archive"
|
||||
mkdir -p "$archive_dir"
|
||||
mv "$METRICS_FILE" "$archive_dir/$archive_name"
|
||||
log "Archived previous metrics to: archive/$archive_name"
|
||||
|
||||
# Clean up old archives (keep last 10)
|
||||
local archive_count
|
||||
archive_count=$(find "$archive_dir" -name "epic-${EPIC_ID}-metrics.*.yaml" 2>/dev/null | wc -l | tr -d ' ')
|
||||
if [ "$archive_count" -gt 10 ]; then
|
||||
log "Cleaning up old metrics archives (keeping last 10)..."
|
||||
find "$archive_dir" -name "epic-${EPIC_ID}-metrics.*.yaml" -type f | \
|
||||
sort | head -n -10 | xargs rm -f 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
local start_time=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
cat > "$METRICS_FILE" << EOF
|
||||
|
|
@ -630,6 +648,90 @@ mark_story_done() {
|
|||
update_sprint_status "$story_id" "done"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Help Function
|
||||
# =============================================================================
|
||||
|
||||
show_help() {
|
||||
cat << 'EOF'
|
||||
BMAD Epic Execute - Automated Story Execution with Context Isolation
|
||||
|
||||
USAGE:
|
||||
epic-execute.sh <epic-id> [OPTIONS]
|
||||
|
||||
ARGUMENTS:
|
||||
epic-id Numeric ID of the epic to execute (e.g., 1, 42)
|
||||
|
||||
OPTIONS:
|
||||
Execution Control:
|
||||
--dry-run Show what would be executed without running
|
||||
--start-from ID Start from a specific story (e.g., 31-2)
|
||||
--resume Resume from last checkpoint (auto-detected)
|
||||
--parallel Run independent stories in parallel (experimental)
|
||||
--verbose Show detailed output including Claude responses
|
||||
--legacy-output Use legacy text-based output parsing (no JSON)
|
||||
|
||||
Gate Skipping:
|
||||
--skip-review Skip code review phase (not recommended)
|
||||
--skip-arch Skip architecture compliance check
|
||||
--skip-test-quality Skip test quality review
|
||||
--skip-traceability Skip traceability check (not recommended)
|
||||
--skip-static-analysis Skip static analysis gate
|
||||
--skip-regression Skip regression test gate
|
||||
|
||||
TDD/Testing Options:
|
||||
--skip-design Skip pre-implementation design phase
|
||||
--skip-tdd Skip all test-first development phases
|
||||
--skip-test-spec Skip test specification phase only
|
||||
--skip-test-impl Skip test implementation phase only
|
||||
|
||||
Commit Control:
|
||||
--no-commit Stage changes but don't commit
|
||||
--skip-done Skip stories with Status: Done
|
||||
|
||||
Help:
|
||||
-h, --help Show this help message
|
||||
|
||||
EXAMPLES:
|
||||
# Execute epic 1 with all quality gates
|
||||
./epic-execute.sh 1
|
||||
|
||||
# Dry run to preview what will be executed
|
||||
./epic-execute.sh 1 --dry-run --verbose
|
||||
|
||||
# Resume from last checkpoint (after interruption)
|
||||
./epic-execute.sh 1 --resume
|
||||
|
||||
# Start from a specific story
|
||||
./epic-execute.sh 1 --start-from 1-3
|
||||
|
||||
# Skip already-completed stories
|
||||
./epic-execute.sh 1 --skip-done
|
||||
|
||||
# Fast mode (skip optional quality gates)
|
||||
./epic-execute.sh 1 --skip-arch --skip-traceability
|
||||
|
||||
# Development mode (no commits, verbose output)
|
||||
./epic-execute.sh 1 --no-commit --verbose
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
CLAUDE_TIMEOUT Timeout for Claude invocations (default: 600s)
|
||||
PROJECT_ROOT Override project root detection
|
||||
PROTECTED_BRANCHES Space-separated list of protected branches (default: "main master")
|
||||
MAX_PROMPT_SIZE Maximum prompt size in bytes (default: 150000)
|
||||
RETRY_MAX_ATTEMPTS Max retry attempts for transient failures (default: 3)
|
||||
RETRY_INITIAL_DELAY Initial retry delay in seconds (default: 5)
|
||||
|
||||
FILES:
|
||||
Logs: /tmp/bmad-epic-execute-$$.log
|
||||
Metrics: docs/sprint-artifacts/metrics/epic-<id>-metrics.yaml
|
||||
Checkpoint: docs/sprint-artifacts/.epic-<id>-checkpoint
|
||||
|
||||
For more information, see: docs/bmad_improvements_v2_fixes.md
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Argument Parsing
|
||||
# =============================================================================
|
||||
|
|
@ -641,6 +743,7 @@ NO_COMMIT=false
|
|||
PARALLEL=false
|
||||
VERBOSE=false
|
||||
START_FROM=""
|
||||
RESUME_FROM_CHECKPOINT=false
|
||||
SKIP_DONE=false
|
||||
SKIP_ARCH=false
|
||||
SKIP_TEST_QUALITY=false
|
||||
|
|
@ -653,8 +756,16 @@ SKIP_TEST_SPEC=false
|
|||
SKIP_TEST_IMPL=false
|
||||
LEGACY_OUTPUT=false
|
||||
|
||||
# Check for help flag before processing other arguments
|
||||
if [[ "${1:-}" =~ ^(-h|--help)$ ]]; then
|
||||
show_help
|
||||
fi
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
show_help
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
|
|
@ -679,6 +790,10 @@ while [[ $# -gt 0 ]]; do
|
|||
START_FROM="$2"
|
||||
shift 2
|
||||
;;
|
||||
--resume)
|
||||
RESUME_FROM_CHECKPOINT=true
|
||||
shift
|
||||
;;
|
||||
--skip-done)
|
||||
SKIP_DONE=true
|
||||
shift
|
||||
|
|
@ -771,6 +886,7 @@ log "Project root: $PROJECT_ROOT"
|
|||
|
||||
validate_workflows() {
|
||||
local missing=0
|
||||
local invalid=0
|
||||
|
||||
log "Validating BMAD workflow files..."
|
||||
|
||||
|
|
@ -778,26 +894,61 @@ validate_workflows() {
|
|||
if [ ! -f "$WORKFLOW_EXECUTOR" ]; then
|
||||
log_error "Missing: Core workflow executor at $WORKFLOW_EXECUTOR"
|
||||
((missing++))
|
||||
else
|
||||
# L5: Validate XML content
|
||||
if type validate_workflow_content >/dev/null 2>&1; then
|
||||
if ! validate_workflow_content "$WORKFLOW_EXECUTOR"; then
|
||||
((invalid++))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Dev-story workflow
|
||||
if [ ! -f "$DEV_WORKFLOW_YAML" ]; then
|
||||
log_error "Missing: Dev workflow.yaml at $DEV_WORKFLOW_YAML"
|
||||
((missing++))
|
||||
else
|
||||
# L5: Validate YAML content
|
||||
if type validate_workflow_content >/dev/null 2>&1; then
|
||||
if ! validate_workflow_content "$DEV_WORKFLOW_YAML"; then
|
||||
((invalid++))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ ! -f "$DEV_WORKFLOW_INSTRUCTIONS" ]; then
|
||||
log_error "Missing: Dev instructions.xml at $DEV_WORKFLOW_INSTRUCTIONS"
|
||||
((missing++))
|
||||
else
|
||||
# L5: Validate XML content
|
||||
if type validate_workflow_content >/dev/null 2>&1; then
|
||||
if ! validate_workflow_content "$DEV_WORKFLOW_INSTRUCTIONS"; then
|
||||
((invalid++))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Code-review workflow
|
||||
if [ ! -f "$REVIEW_WORKFLOW_YAML" ]; then
|
||||
log_error "Missing: Review workflow.yaml at $REVIEW_WORKFLOW_YAML"
|
||||
((missing++))
|
||||
else
|
||||
# L5: Validate YAML content
|
||||
if type validate_workflow_content >/dev/null 2>&1; then
|
||||
if ! validate_workflow_content "$REVIEW_WORKFLOW_YAML"; then
|
||||
((invalid++))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ ! -f "$REVIEW_WORKFLOW_INSTRUCTIONS" ]; then
|
||||
log_error "Missing: Review instructions.xml at $REVIEW_WORKFLOW_INSTRUCTIONS"
|
||||
((missing++))
|
||||
else
|
||||
# L5: Validate XML content
|
||||
if type validate_workflow_content >/dev/null 2>&1; then
|
||||
if ! validate_workflow_content "$REVIEW_WORKFLOW_INSTRUCTIONS"; then
|
||||
((invalid++))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $missing -gt 0 ]; then
|
||||
|
|
@ -807,6 +958,10 @@ validate_workflows() {
|
|||
exit 1
|
||||
fi
|
||||
|
||||
if [ $invalid -gt 0 ]; then
|
||||
log_warn "$invalid workflow files have content issues (may still work)"
|
||||
fi
|
||||
|
||||
log_success "All BMAD workflow files validated"
|
||||
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
|
|
@ -839,6 +994,24 @@ EPIC_START_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|||
EPIC_START_SECONDS=$(date +%s)
|
||||
init_metrics
|
||||
|
||||
# L1: Load checkpoint for resume capability
|
||||
RESUME_START_INDEX=0
|
||||
if [ "$RESUME_FROM_CHECKPOINT" = true ] && type load_checkpoint >/dev/null 2>&1; then
|
||||
if load_checkpoint "$EPIC_ID" "$SPRINT_ARTIFACTS_DIR"; then
|
||||
RESUME_START_INDEX=$(get_resume_index)
|
||||
# Restore counters from checkpoint
|
||||
COMPLETED="${CHECKPOINT_COMPLETED:-0}"
|
||||
FAILED="${CHECKPOINT_FAILED:-0}"
|
||||
SKIPPED="${CHECKPOINT_SKIPPED:-0}"
|
||||
log "Will resume from story index: $RESUME_START_INDEX"
|
||||
else
|
||||
log "No checkpoint found - starting from beginning"
|
||||
fi
|
||||
elif [ -n "$START_FROM" ]; then
|
||||
# Manual --start-from takes precedence
|
||||
log "Using --start-from: $START_FROM"
|
||||
fi
|
||||
|
||||
# Initialize decision log (if module loaded)
|
||||
if type init_decision_log >/dev/null 2>&1; then
|
||||
init_decision_log
|
||||
|
|
@ -2732,15 +2905,33 @@ log "=========================================="
|
|||
log "Starting execution of ${#STORIES[@]} stories"
|
||||
log "=========================================="
|
||||
|
||||
COMPLETED=0
|
||||
FAILED=0
|
||||
SKIPPED=0
|
||||
# Initialize counters (may be restored from checkpoint)
|
||||
if [ -z "$COMPLETED" ] || [ "$COMPLETED" = "0" ]; then
|
||||
COMPLETED=0
|
||||
fi
|
||||
if [ -z "$FAILED" ] || [ "$FAILED" = "0" ]; then
|
||||
FAILED=0
|
||||
fi
|
||||
if [ -z "$SKIPPED" ] || [ "$SKIPPED" = "0" ]; then
|
||||
SKIPPED=0
|
||||
fi
|
||||
START_TIME=$(date +%s)
|
||||
STARTED=false
|
||||
|
||||
# Track current story index for checkpoint/resume
|
||||
STORY_INDEX=0
|
||||
|
||||
for story_file in "${STORIES[@]}"; do
|
||||
story_id=$(basename "$story_file" .md)
|
||||
|
||||
# L1: Resume capability - skip stories before resume index
|
||||
if [ "$RESUME_START_INDEX" -gt 0 ] && [ "$STORY_INDEX" -lt "$RESUME_START_INDEX" ]; then
|
||||
log_warn "Skipping $story_id (resuming from index $RESUME_START_INDEX)"
|
||||
((STORY_INDEX++))
|
||||
CURRENT_STORY_INDEX=$STORY_INDEX
|
||||
continue
|
||||
fi
|
||||
|
||||
# --start-from: Skip stories until we reach the specified one
|
||||
if [ -n "$START_FROM" ] && [ "$STARTED" = false ]; then
|
||||
if [[ "$story_id" == *"$START_FROM"* ]]; then
|
||||
|
|
@ -2748,7 +2939,8 @@ for story_file in "${STORIES[@]}"; do
|
|||
else
|
||||
log_warn "Skipping $story_id (waiting for $START_FROM)"
|
||||
((SKIPPED++))
|
||||
((CURRENT_STORY_INDEX++))
|
||||
((STORY_INDEX++))
|
||||
CURRENT_STORY_INDEX=$STORY_INDEX
|
||||
update_story_metrics "skipped"
|
||||
continue
|
||||
fi
|
||||
|
|
@ -2759,7 +2951,8 @@ for story_file in "${STORIES[@]}"; do
|
|||
if grep -qi "^Status:.*done" "$story_file" 2>/dev/null; then
|
||||
log_warn "Skipping $story_id (Status: Done)"
|
||||
((SKIPPED++))
|
||||
((CURRENT_STORY_INDEX++))
|
||||
((STORY_INDEX++))
|
||||
CURRENT_STORY_INDEX=$STORY_INDEX
|
||||
update_story_metrics "skipped"
|
||||
continue
|
||||
fi
|
||||
|
|
@ -2776,8 +2969,13 @@ for story_file in "${STORIES[@]}"; do
|
|||
if ! execute_story_with_fix_loop "$story_file"; then
|
||||
log_error "Story execution failed for $story_id"
|
||||
((FAILED++))
|
||||
((CURRENT_STORY_INDEX++))
|
||||
((STORY_INDEX++))
|
||||
CURRENT_STORY_INDEX=$STORY_INDEX
|
||||
update_story_metrics "failed"
|
||||
# Save checkpoint on failure too
|
||||
if type save_checkpoint >/dev/null 2>&1; then
|
||||
save_checkpoint "$STORY_INDEX" "$story_id" "$COMPLETED" "$FAILED" "$SKIPPED"
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
else
|
||||
|
|
@ -2785,9 +2983,14 @@ for story_file in "${STORIES[@]}"; do
|
|||
if ! execute_dev_phase "$story_file"; then
|
||||
log_error "Dev phase failed for $story_id"
|
||||
((FAILED++))
|
||||
((CURRENT_STORY_INDEX++))
|
||||
((STORY_INDEX++))
|
||||
CURRENT_STORY_INDEX=$STORY_INDEX
|
||||
update_story_metrics "failed"
|
||||
add_metrics_issue "$story_id" "dev_phase_failed" "Development phase did not complete"
|
||||
# Save checkpoint on failure too
|
||||
if type save_checkpoint >/dev/null 2>&1; then
|
||||
save_checkpoint "$STORY_INDEX" "$story_id" "$COMPLETED" "$FAILED" "$SKIPPED"
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
|
@ -2808,7 +3011,13 @@ for story_file in "${STORIES[@]}"; do
|
|||
log_success "Story complete: $story_id ($COMPLETED/${#STORIES[@]})"
|
||||
|
||||
# Track progress for checkpoint/resume
|
||||
((CURRENT_STORY_INDEX++))
|
||||
((STORY_INDEX++))
|
||||
CURRENT_STORY_INDEX=$STORY_INDEX
|
||||
|
||||
# L1: Save checkpoint after each completed story
|
||||
if type save_checkpoint >/dev/null 2>&1; then
|
||||
save_checkpoint "$STORY_INDEX" "$story_id" "$COMPLETED" "$FAILED" "$SKIPPED"
|
||||
fi
|
||||
done
|
||||
|
||||
# =============================================================================
|
||||
|
|
@ -2901,9 +3110,15 @@ echo ""
|
|||
|
||||
if [ $FAILED -gt 0 ]; then
|
||||
log_warn "$FAILED stories failed - check log for details"
|
||||
log "Checkpoint preserved for resume capability"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# L1: Clear checkpoint on successful completion
|
||||
if type clear_checkpoint >/dev/null 2>&1; then
|
||||
clear_checkpoint
|
||||
fi
|
||||
|
||||
log_success "All stories completed successfully"
|
||||
echo ""
|
||||
echo "Next step: Run UAT document with a human tester"
|
||||
|
|
|
|||
Loading…
Reference in New Issue