feat(epic-execute): add phase 2+3 improvements with modular architecture

- Add decision log module for context preservation across phases
- Add regression gate module for test baseline tracking
- Add design phase module for pre-implementation planning
- Enhance fix phase to include real tooling output
- Pass design and decision context to dev phase
- Add --skip-design and --skip-regression CLI flags
- Modularize into epic-execute-lib/ for maintainability

Implements improvements from bmad_improvements_v2.md:
- Phase 2.1: Real test output in fix loops
- Phase 2.2: Cumulative decision log
- Phase 2.3: Regression test gate
- Phase 3.1: Pre-implementation design phase

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Caleb 2026-01-26 14:23:16 -06:00
parent 3bad076884
commit fd744c96f3
4 changed files with 834 additions and 1 deletions

View File

@ -0,0 +1,102 @@
#!/bin/bash
#
# BMAD Epic Execute - Decision Log Module
#
# Provides functions to maintain a cumulative decision log across phases
# for context preservation during epic execution.
#
# Usage: Sourced by epic-execute.sh
#
# =============================================================================
# Decision Log Functions
# =============================================================================
DECISION_LOG=""
# Initialize the decision log for an epic
# Creates a new decision log file or appends to existing one
init_decision_log() {
if [ -z "$SPRINT_ARTIFACTS_DIR" ] || [ -z "$EPIC_ID" ]; then
log_warn "Cannot initialize decision log: SPRINT_ARTIFACTS_DIR or EPIC_ID not set"
return 1
fi
DECISION_LOG="$SPRINT_ARTIFACTS_DIR/epic-${EPIC_ID}-decisions.md"
mkdir -p "$(dirname "$DECISION_LOG")"
# Create new decision log if it doesn't exist
if [ ! -f "$DECISION_LOG" ]; then
cat > "$DECISION_LOG" << EOF
# Epic $EPIC_ID Decision Log
This file tracks implementation decisions for context continuity across phases.
**Epic:** $EPIC_ID
**Started:** $(date '+%Y-%m-%d %H:%M:%S')
---
EOF
log "Decision log initialized: $DECISION_LOG"
else
log "Using existing decision log: $DECISION_LOG"
fi
return 0
}
# Append a decision to the log
# Arguments:
# $1 - phase name (e.g., "DEV", "DESIGN", "FIX")
# $2 - story_id
# $3 - content (the decision details)
append_to_decision_log() {
local phase="$1"
local story_id="$2"
local content="$3"
if [ -z "$DECISION_LOG" ] || [ ! -f "$DECISION_LOG" ]; then
[ "$VERBOSE" = true ] && log_warn "Decision log not initialized"
return 1
fi
cat >> "$DECISION_LOG" << EOF
## $phase: $story_id
**Timestamp:** $(date '+%Y-%m-%d %H:%M:%S')
$content
---
EOF
[ "$VERBOSE" = true ] && log "Appended $phase decision for $story_id to decision log"
return 0
}
# Get the decision log contents for inclusion in prompts
# Returns the full contents of the decision log, or empty string if not available
get_decision_log_context() {
if [ -z "$DECISION_LOG" ] || [ ! -f "$DECISION_LOG" ]; then
echo ""
return
fi
cat "$DECISION_LOG"
}
# Get a summary of decisions for a specific story
# Arguments:
# $1 - story_id
get_story_decisions() {
local story_id="$1"
if [ -z "$DECISION_LOG" ] || [ ! -f "$DECISION_LOG" ]; then
echo ""
return
fi
# Extract sections related to this story
grep -A 50 "## .*: $story_id" "$DECISION_LOG" | head -60 || true
}

View File

@ -0,0 +1,201 @@
#!/bin/bash
#
# BMAD Epic Execute - Design Phase Module
#
# Provides pre-implementation design phase functionality to catch
# architectural issues early before coding begins.
#
# Usage: Sourced by epic-execute.sh
#
# =============================================================================
# Design Phase Variables
# =============================================================================
# Stores the last design output for passing to dev phase
LAST_DESIGN=""
# =============================================================================
# Design Phase Functions
# =============================================================================
# Execute pre-implementation design phase
# Generates an implementation plan before coding begins
# Arguments:
# $1 - story_file path
execute_design_phase() {
local story_file="$1"
local story_id=$(basename "$story_file" .md)
# Reset last design
LAST_DESIGN=""
log ">>> DESIGN PHASE: $story_id"
local story_contents=$(cat "$story_file")
# Load architecture file if available
local arch_contents=""
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_contents=$(cat "$search_path")
break
fi
done
# Load previous decisions for context
local decision_context=""
if type get_decision_log_context >/dev/null 2>&1; then
decision_context=$(get_decision_log_context)
fi
local design_prompt="You are a senior developer planning the implementation of a story.
## Your Task
Create an implementation plan for: $story_id
Do NOT write any code yet. Output only your design plan.
### CRITICAL RULES
- Plan thoroughly BEFORE any implementation
- Consider existing patterns in the codebase
- Map each acceptance criterion to specific files/functions
- Identify potential risks and dependencies
## Story to Plan
**Story Path:** $story_file
**Story ID:** $story_id
<story>
$story_contents
</story>
## Architecture Reference
<architecture>
$arch_contents
</architecture>
## Previous Decisions in This Epic
<decision-context>
$decision_context
</decision-context>
## Exploration Commands
First, explore the codebase to understand existing patterns:
\`\`\`bash
# Find similar implementations
find . -type f -name \"*.ts\" -o -name \"*.js\" | head -20
# Check project structure
ls -la src/ 2>/dev/null || ls -la
\`\`\`
## Required Output
Output your implementation plan in this exact format:
\`\`\`
DESIGN START
story_id: $story_id
files_to_modify:
- path: <file path>
action: create|modify
purpose: <why this file needs changes>
patterns_to_use:
- <pattern name>: <how it will be applied>
dependencies:
- <package>: <installed|needs-install>
acceptance_criteria_mapping:
- AC1: <which files/functions will implement this>
- AC2: <which files/functions will implement this>
risks:
- <potential issue and mitigation>
estimated_test_files:
- <test file path>: <what it will test>
implementation_order:
1. <first step>
2. <second step>
...
DESIGN END
\`\`\`
Be specific and concrete. This plan will guide the implementation phase.
## Completion Signal
After outputting the design block, output exactly:
DESIGN COMPLETE: $story_id"
if [ "$DRY_RUN" = true ]; then
echo "[DRY RUN] Would execute design phase for $story_id"
return 0
fi
local result
result=$(claude --dangerously-skip-permissions -p "$design_prompt" 2>&1) || true
echo "$result" >> "$LOG_FILE"
# Extract design block
LAST_DESIGN=$(echo "$result" | sed -n '/DESIGN START/,/DESIGN END/p')
if [ -n "$LAST_DESIGN" ]; then
# Save to decision log
if type append_to_decision_log >/dev/null 2>&1; then
append_to_decision_log "DESIGN" "$story_id" "$LAST_DESIGN"
fi
log_success "Design phase complete: $story_id"
return 0
else
log_error "Design phase did not produce valid output"
return 1
fi
}
# Get the last design for inclusion in dev phase prompt
# Returns the design output or empty string if not available
get_last_design() {
echo "$LAST_DESIGN"
}
# Build the design context block for dev phase prompt
# Returns formatted design context for inclusion in prompts
build_design_context_for_dev() {
local story_id="$1"
if [ -z "$LAST_DESIGN" ]; then
echo ""
return
fi
cat << EOF
## Pre-Implementation Design
The following design was created in the planning phase. Follow this plan:
<design-plan>
$LAST_DESIGN
</design-plan>
### Implementation Guidelines Based on Design
1. Follow the implementation_order specified
2. Create/modify files as listed in files_to_modify
3. Use the patterns specified in patterns_to_use
4. Ensure each acceptance_criteria_mapping is implemented
5. Be aware of the identified risks
EOF
}

View File

@ -0,0 +1,164 @@
#!/bin/bash
#
# BMAD Epic Execute - Regression Gate Module
#
# Provides functions to track test baselines and verify no regressions
# occur during epic execution.
#
# Usage: Sourced by epic-execute.sh
#
# =============================================================================
# Regression Gate Variables
# =============================================================================
BASELINE_PASSING_TESTS=0
BASELINE_COVERAGE=0
REGRESSION_INITIALIZED=false
# =============================================================================
# Regression Gate Functions
# =============================================================================
# Initialize regression baseline before epic starts
# Captures current test count and coverage (if available)
init_regression_baseline() {
if [ -z "$PROJECT_ROOT" ]; then
log_warn "Cannot initialize regression baseline: PROJECT_ROOT not set"
return 1
fi
log "Initializing regression baseline..."
# Detect project type and capture baseline
if [ -f "$PROJECT_ROOT/package.json" ]; then
# Node.js/TypeScript project
if grep -q '"test"' "$PROJECT_ROOT/package.json" 2>/dev/null; then
log "Capturing baseline test count..."
local test_output
test_output=$(cd "$PROJECT_ROOT" && npm test 2>&1) || true
# Try multiple patterns to extract passing test count
# Pattern 1: "X passing" (mocha/jest)
BASELINE_PASSING_TESTS=$(echo "$test_output" | grep -oE '[0-9]+ passing' | grep -oE '[0-9]+' | head -1 || echo "0")
# Pattern 2: "Tests: X passed" (jest)
if [ "$BASELINE_PASSING_TESTS" = "0" ]; then
BASELINE_PASSING_TESTS=$(echo "$test_output" | grep -oE 'Tests:.*[0-9]+ passed' | grep -oE '[0-9]+' | head -1 || echo "0")
fi
# Pattern 3: "X tests passed" (generic)
if [ "$BASELINE_PASSING_TESTS" = "0" ]; then
BASELINE_PASSING_TESTS=$(echo "$test_output" | grep -oE '[0-9]+ tests? passed' | grep -oE '[0-9]+' | head -1 || echo "0")
fi
log "Baseline passing tests: $BASELINE_PASSING_TESTS"
fi
# Capture baseline coverage if available
if grep -q '"coverage"' "$PROJECT_ROOT/package.json" 2>/dev/null; then
log "Capturing baseline coverage..."
local coverage_output
coverage_output=$(cd "$PROJECT_ROOT" && npm run coverage -- --json 2>/dev/null) || true
# Try to extract coverage percentage
if command -v jq >/dev/null 2>&1; then
BASELINE_COVERAGE=$(echo "$coverage_output" | jq '.total.lines.pct // 0' 2>/dev/null || echo "0")
fi
[ "$BASELINE_COVERAGE" != "0" ] && log "Baseline coverage: ${BASELINE_COVERAGE}%"
fi
elif [ -f "$PROJECT_ROOT/Cargo.toml" ]; then
# Rust project
log "Capturing baseline test count (Rust)..."
local test_output
test_output=$(cd "$PROJECT_ROOT" && cargo test 2>&1) || true
BASELINE_PASSING_TESTS=$(echo "$test_output" | grep -oE '[0-9]+ passed' | grep -oE '[0-9]+' | head -1 || echo "0")
log "Baseline passing tests: $BASELINE_PASSING_TESTS"
elif [ -f "$PROJECT_ROOT/go.mod" ]; then
# Go project
log "Capturing baseline test count (Go)..."
local test_output
test_output=$(cd "$PROJECT_ROOT" && go test ./... -v 2>&1) || true
BASELINE_PASSING_TESTS=$(echo "$test_output" | grep -c "^--- PASS" || echo "0")
log "Baseline passing tests: $BASELINE_PASSING_TESTS"
elif [ -f "$PROJECT_ROOT/requirements.txt" ] || [ -f "$PROJECT_ROOT/pyproject.toml" ]; then
# Python project
if command -v pytest >/dev/null 2>&1; then
log "Capturing baseline test count (Python)..."
local test_output
test_output=$(cd "$PROJECT_ROOT" && pytest --co -q 2>&1) || true
BASELINE_PASSING_TESTS=$(echo "$test_output" | grep -oE '[0-9]+ tests? collected' | grep -oE '[0-9]+' | head -1 || echo "0")
log "Baseline test count: $BASELINE_PASSING_TESTS"
fi
fi
REGRESSION_INITIALIZED=true
log_success "Regression baseline initialized: $BASELINE_PASSING_TESTS tests"
return 0
}
# Execute regression gate after a story completes
# Compares current test count against baseline
# Arguments:
# $1 - story_id
execute_regression_gate() {
local story_id="$1"
if [ "$REGRESSION_INITIALIZED" != true ]; then
log_warn "Regression gate skipped: baseline not initialized"
return 0
fi
log ">>> REGRESSION GATE: $story_id"
local current_tests=0
# Get current test count based on project type
if [ -f "$PROJECT_ROOT/package.json" ]; then
local test_output
test_output=$(cd "$PROJECT_ROOT" && npm test 2>&1) || true
current_tests=$(echo "$test_output" | grep -oE '[0-9]+ passing' | grep -oE '[0-9]+' | head -1 || echo "0")
if [ "$current_tests" = "0" ]; then
current_tests=$(echo "$test_output" | grep -oE 'Tests:.*[0-9]+ passed' | grep -oE '[0-9]+' | head -1 || echo "0")
fi
if [ "$current_tests" = "0" ]; then
current_tests=$(echo "$test_output" | grep -oE '[0-9]+ tests? passed' | grep -oE '[0-9]+' | head -1 || echo "0")
fi
elif [ -f "$PROJECT_ROOT/Cargo.toml" ]; then
local test_output
test_output=$(cd "$PROJECT_ROOT" && cargo test 2>&1) || true
current_tests=$(echo "$test_output" | grep -oE '[0-9]+ passed' | grep -oE '[0-9]+' | head -1 || echo "0")
elif [ -f "$PROJECT_ROOT/go.mod" ]; then
local test_output
test_output=$(cd "$PROJECT_ROOT" && go test ./... -v 2>&1) || true
current_tests=$(echo "$test_output" | grep -c "^--- PASS" || echo "0")
elif [ -f "$PROJECT_ROOT/requirements.txt" ] || [ -f "$PROJECT_ROOT/pyproject.toml" ]; then
if command -v pytest >/dev/null 2>&1; then
local test_output
test_output=$(cd "$PROJECT_ROOT" && pytest -v 2>&1) || true
current_tests=$(echo "$test_output" | grep -oE '[0-9]+ passed' | grep -oE '[0-9]+' | head -1 || echo "0")
fi
fi
# Check for regression
if [ "$current_tests" -lt "$BASELINE_PASSING_TESTS" ]; then
log_error "REGRESSION DETECTED: Test count decreased ($BASELINE_PASSING_TESTS -> $current_tests)"
add_metrics_issue "$story_id" "regression" "Test count decreased from $BASELINE_PASSING_TESTS to $current_tests"
return 1
fi
# Update baseline for next story (allow growth)
local previous_baseline=$BASELINE_PASSING_TESTS
BASELINE_PASSING_TESTS=$current_tests
log_success "Regression gate passed: $current_tests tests (baseline was $previous_baseline)"
return 0
}

View File

@ -15,6 +15,7 @@
# --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 (runs real tooling)
#
set -e
@ -27,6 +28,15 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
BMAD_DIR="$PROJECT_ROOT/bmad"
# =============================================================================
# Source Modular Components
# =============================================================================
LIB_DIR="$SCRIPT_DIR/epic-execute-lib"
[ -f "$LIB_DIR/decision-log.sh" ] && source "$LIB_DIR/decision-log.sh"
[ -f "$LIB_DIR/regression-gate.sh" ] && source "$LIB_DIR/regression-gate.sh"
[ -f "$LIB_DIR/design-phase.sh" ] && source "$LIB_DIR/design-phase.sh"
STORIES_DIR="$PROJECT_ROOT/docs/stories"
SPRINT_ARTIFACTS_DIR="$PROJECT_ROOT/docs/sprint-artifacts"
SPRINTS_DIR="$PROJECT_ROOT/docs/sprints"
@ -369,6 +379,9 @@ SKIP_DONE=false
SKIP_ARCH=false
SKIP_TEST_QUALITY=false
SKIP_TRACEABILITY=false
SKIP_STATIC_ANALYSIS=false
SKIP_DESIGN=false
SKIP_REGRESSION=false
while [[ $# -gt 0 ]]; do
case $1 in
@ -412,6 +425,18 @@ while [[ $# -gt 0 ]]; do
SKIP_TRACEABILITY=true
shift
;;
--skip-static-analysis)
SKIP_STATIC_ANALYSIS=true
shift
;;
--skip-design)
SKIP_DESIGN=true
shift
;;
--skip-regression)
SKIP_REGRESSION=true
shift
;;
-*)
echo "Unknown option: $1"
exit 1
@ -437,6 +462,9 @@ if [ -z "$EPIC_ID" ]; then
echo " --skip-arch Skip architecture compliance check"
echo " --skip-test-quality Skip test quality review"
echo " --skip-traceability Skip traceability check (not recommended)"
echo " --skip-static-analysis Skip static analysis gate (runs real tooling)"
echo " --skip-design Skip pre-implementation design phase"
echo " --skip-regression Skip regression test gate"
exit 1
fi
@ -509,6 +537,16 @@ EPIC_START_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
EPIC_START_SECONDS=$(date +%s)
init_metrics
# Initialize decision log (if module loaded)
if type init_decision_log >/dev/null 2>&1; then
init_decision_log
fi
# Initialize regression baseline (if module loaded and not skipped)
if [ "$SKIP_REGRESSION" = false ] && type init_regression_baseline >/dev/null 2>&1; then
init_regression_baseline
fi
# 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)
@ -623,6 +661,18 @@ execute_dev_phase() {
local workflow_executor=$(cat "$WORKFLOW_EXECUTOR")
local story_contents=$(cat "$story_file")
# Get decision log context if available
local decision_context=""
if type get_decision_log_context >/dev/null 2>&1; then
decision_context=$(get_decision_log_context)
fi
# Get design context if available (from design phase)
local design_context=""
if type build_design_context_for_dev >/dev/null 2>&1; then
design_context=$(build_design_context_for_dev "$story_id")
fi
# Build the dev prompt using BMAD workflow
local dev_prompt="You are executing a BMAD dev-story workflow in automated mode.
@ -670,6 +720,12 @@ $workflow_checklist
<story-contents>
$story_contents
</story-contents>
$design_context
## Previous Implementation Context
<decision-log>
$decision_context
</decision-log>
## Execution Variables (Pre-resolved)
@ -886,6 +942,7 @@ execute_fix_phase() {
local story_file="$1"
local review_findings="$2"
local attempt_num="$3"
local static_analysis_context="${4:-}" # Optional: real tooling output
local story_id=$(basename "$story_file" .md)
log ">>> FIX PHASE: $story_id (attempt $attempt_num, using BMAD dev-story workflow)"
@ -906,6 +963,19 @@ execute_fix_phase() {
local workflow_executor=$(cat "$WORKFLOW_EXECUTOR")
local story_contents=$(cat "$story_file")
# Build real tooling output section if available
local tooling_section=""
if [ -n "$static_analysis_context" ]; then
tooling_section="
## Actual Tooling Output
The following are REAL errors from running the project's tooling (not AI-generated).
These must be fixed first as they represent actual compilation/test failures:
$static_analysis_context
"
fi
# 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.
@ -928,7 +998,7 @@ The following issues were identified during code review and MUST be fixed:
<review-findings>
$review_findings
</review-findings>
$tooling_section
## Workflow Executor Engine
<workflow-executor>
@ -1028,11 +1098,255 @@ Address all review findings now. This is attempt $attempt_num of 3."
fi
}
# =============================================================================
# Static Analysis Gate - Real Tooling Verification
# =============================================================================
execute_static_analysis_gate() {
local story_file="$1"
local story_id=$(basename "$story_file" .md)
local failures=0
local failure_details=""
# Reset failures
LAST_STATIC_ANALYSIS_FAILURES=""
log ">>> STATIC ANALYSIS GATE: $story_id (running real tooling)"
if [ "$DRY_RUN" = true ]; then
echo "[DRY RUN] Would run static analysis gate for $story_id"
return 0
fi
# Detect project type and run appropriate checks
if [ -f "$PROJECT_ROOT/package.json" ]; then
log "Detected Node.js/TypeScript project"
# 1. Type checking (catches type errors AI might miss)
if grep -q '"typecheck"\|"type-check"\|"tsc"' "$PROJECT_ROOT/package.json" 2>/dev/null; then
log "Running type check..."
local typecheck_output
typecheck_output=$(cd "$PROJECT_ROOT" && npm run typecheck 2>&1) || {
local exit_code=$?
log_error "Type check failed (exit code: $exit_code)"
failure_details+="
### Type Check Failures
\`\`\`
$typecheck_output
\`\`\`
"
((failures++))
}
echo "$typecheck_output" >> "$LOG_FILE"
elif [ -f "$PROJECT_ROOT/tsconfig.json" ]; then
# Fallback: run tsc directly if tsconfig exists
log "Running tsc directly..."
local tsc_output
tsc_output=$(cd "$PROJECT_ROOT" && npx tsc --noEmit 2>&1) || {
local exit_code=$?
log_error "TypeScript compilation failed (exit code: $exit_code)"
failure_details+="
### TypeScript Compilation Failures
\`\`\`
$tsc_output
\`\`\`
"
((failures++))
}
echo "$tsc_output" >> "$LOG_FILE"
fi
# 2. Linting (catches code style/quality issues)
if grep -q '"lint"' "$PROJECT_ROOT/package.json" 2>/dev/null; then
log "Running lint..."
local lint_output
lint_output=$(cd "$PROJECT_ROOT" && npm run lint 2>&1) || {
local exit_code=$?
log_error "Lint failed (exit code: $exit_code)"
failure_details+="
### Lint Failures
\`\`\`
$lint_output
\`\`\`
"
((failures++))
}
echo "$lint_output" >> "$LOG_FILE"
fi
# 3. Build (catches compilation errors)
if grep -q '"build"' "$PROJECT_ROOT/package.json" 2>/dev/null; then
log "Running build..."
local build_output
build_output=$(cd "$PROJECT_ROOT" && npm run build 2>&1) || {
local exit_code=$?
log_error "Build failed (exit code: $exit_code)"
failure_details+="
### Build Failures
\`\`\`
$build_output
\`\`\`
"
((failures++))
}
echo "$build_output" >> "$LOG_FILE"
fi
# 4. Tests (catches actual test failures)
if grep -q '"test"' "$PROJECT_ROOT/package.json" 2>/dev/null; then
log "Running tests..."
local test_output
test_output=$(cd "$PROJECT_ROOT" && npm test 2>&1) || {
local exit_code=$?
log_error "Tests failed (exit code: $exit_code)"
failure_details+="
### Test Failures
\`\`\`
$test_output
\`\`\`
"
((failures++))
}
echo "$test_output" >> "$LOG_FILE"
fi
elif [ -f "$PROJECT_ROOT/Cargo.toml" ]; then
log "Detected Rust project"
# Cargo check (type checking)
log "Running cargo check..."
local cargo_output
cargo_output=$(cd "$PROJECT_ROOT" && cargo check 2>&1) || {
log_error "Cargo check failed"
failure_details+="
### Cargo Check Failures
\`\`\`
$cargo_output
\`\`\`
"
((failures++))
}
echo "$cargo_output" >> "$LOG_FILE"
# Cargo test
log "Running cargo test..."
local test_output
test_output=$(cd "$PROJECT_ROOT" && cargo test 2>&1) || {
log_error "Cargo tests failed"
failure_details+="
### Cargo Test Failures
\`\`\`
$test_output
\`\`\`
"
((failures++))
}
echo "$test_output" >> "$LOG_FILE"
elif [ -f "$PROJECT_ROOT/go.mod" ]; then
log "Detected Go project"
# Go build
log "Running go build..."
local build_output
build_output=$(cd "$PROJECT_ROOT" && go build ./... 2>&1) || {
log_error "Go build failed"
failure_details+="
### Go Build Failures
\`\`\`
$build_output
\`\`\`
"
((failures++))
}
echo "$build_output" >> "$LOG_FILE"
# Go test
log "Running go test..."
local test_output
test_output=$(cd "$PROJECT_ROOT" && go test ./... 2>&1) || {
log_error "Go tests failed"
failure_details+="
### Go Test Failures
\`\`\`
$test_output
\`\`\`
"
((failures++))
}
echo "$test_output" >> "$LOG_FILE"
elif [ -f "$PROJECT_ROOT/requirements.txt" ] || [ -f "$PROJECT_ROOT/pyproject.toml" ]; then
log "Detected Python project"
# pytest
if command -v pytest >/dev/null 2>&1; then
log "Running pytest..."
local test_output
test_output=$(cd "$PROJECT_ROOT" && pytest 2>&1) || {
log_error "Pytest failed"
failure_details+="
### Pytest Failures
\`\`\`
$test_output
\`\`\`
"
((failures++))
}
echo "$test_output" >> "$LOG_FILE"
fi
# mypy (if available)
if command -v mypy >/dev/null 2>&1 && [ -f "$PROJECT_ROOT/mypy.ini" ] || [ -f "$PROJECT_ROOT/setup.cfg" ]; then
log "Running mypy..."
local mypy_output
mypy_output=$(cd "$PROJECT_ROOT" && mypy . 2>&1) || {
log_error "Mypy type check failed"
failure_details+="
### Mypy Type Check Failures
\`\`\`
$mypy_output
\`\`\`
"
((failures++))
}
echo "$mypy_output" >> "$LOG_FILE"
fi
else
log_warn "No recognized project type found - skipping static analysis"
return 0
fi
# Check results
if [ $failures -gt 0 ]; then
log_error "Static analysis gate failed with $failures issue(s)"
# Store failures for fix phase
LAST_STATIC_ANALYSIS_FAILURES="## Static Analysis Failures for $story_id
The following REAL tooling failures were detected. These are NOT AI-generated - they are actual errors from running the project's tooling.
$failure_details
## Instructions
Fix ALL the errors shown above. These are real compilation/test failures that must be resolved."
add_metrics_issue "$story_id" "static_analysis_failed" "Static analysis gate failed with $failures issue(s)"
return 1
fi
log_success "Static analysis gate passed: $story_id"
return 0
}
# 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
MAX_STATIC_ANALYSIS_FIX_ATTEMPTS=3
# Global variable to store arch violations for fix loop
LAST_ARCH_VIOLATIONS=""
@ -1043,6 +1357,9 @@ LAST_TEST_QUALITY_ISSUES=""
# Global variable to store traceability gaps for fix loop
LAST_TRACEABILITY_GAPS=""
# Global variable to store static analysis failures for fix loop
LAST_STATIC_ANALYSIS_FAILURES=""
execute_arch_compliance_phase() {
local story_file="$1"
local story_id=$(basename "$story_file" .md)
@ -1534,12 +1851,51 @@ execute_story_with_fix_loop() {
local test_quality_fix_attempt=0
local needs_fixes=false
# DESIGN PHASE (Context 0) - Pre-implementation planning
if [ "$SKIP_DESIGN" = false ] && type execute_design_phase >/dev/null 2>&1; then
if ! execute_design_phase "$story_file"; then
log_warn "Design phase did not complete cleanly for $story_id - proceeding to dev"
# Don't fail - design is advisory
fi
fi
# DEV PHASE (Context 1)
if ! execute_dev_phase "$story_file"; then
log_error "Dev phase failed for $story_id"
return 1
fi
# STATIC ANALYSIS GATE (Real Tooling) - Per Story
local static_analysis_fix_attempt=0
if [ "$SKIP_STATIC_ANALYSIS" = false ]; then
while true; do
if execute_static_analysis_gate "$story_file"; then
log_success "Static analysis passed: $story_id"
break
fi
# Check if we have failures to fix
if [ -z "$LAST_STATIC_ANALYSIS_FAILURES" ]; then
log_warn "Static analysis unclear, proceeding anyway"
break
fi
((static_analysis_fix_attempt++))
if [ $static_analysis_fix_attempt -gt $MAX_STATIC_ANALYSIS_FIX_ATTEMPTS ]; then
log_error "Max static analysis fix attempts ($MAX_STATIC_ANALYSIS_FIX_ATTEMPTS) reached for $story_id"
add_metrics_issue "$story_id" "static_analysis_max_retries" "Static analysis failures after $MAX_STATIC_ANALYSIS_FIX_ATTEMPTS attempts"
# Fail the story - real tooling errors must be fixed
return 1
fi
log_warn "Static analysis failed, attempting fix $static_analysis_fix_attempt of $MAX_STATIC_ANALYSIS_FIX_ATTEMPTS"
# Use the regular fix phase with static analysis context
if ! execute_fix_phase "$story_file" "$LAST_STATIC_ANALYSIS_FAILURES" "$static_analysis_fix_attempt"; then
log_warn "Static analysis fix incomplete, re-running gate..."
fi
done
fi
# ARCHITECTURE COMPLIANCE CHECK (Context 2) - Per Story
if [ "$SKIP_ARCH" = false ]; then
while true; do
@ -1642,6 +1998,16 @@ execute_story_with_fix_loop() {
done
fi
# REGRESSION GATE (if module loaded and not skipped)
if [ "$SKIP_REGRESSION" = false ] && type execute_regression_gate >/dev/null 2>&1; then
if ! execute_regression_gate "$story_id"; then
log_error "Regression detected in $story_id"
add_metrics_issue "$story_id" "regression_detected" "Test count decreased after implementation"
# Don't fail the story - regression is a warning that should be investigated
log_warn "Proceeding despite regression - investigate manually"
fi
fi
return 0
}