diff --git a/scripts/epic-execute-lib/decision-log.sh b/scripts/epic-execute-lib/decision-log.sh new file mode 100644 index 000000000..93998560a --- /dev/null +++ b/scripts/epic-execute-lib/decision-log.sh @@ -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 +} diff --git a/scripts/epic-execute-lib/design-phase.sh b/scripts/epic-execute-lib/design-phase.sh new file mode 100644 index 000000000..8a2b31cc2 --- /dev/null +++ b/scripts/epic-execute-lib/design-phase.sh @@ -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_contents + + +## Architecture Reference + + +$arch_contents + + +## Previous Decisions in This Epic + + +$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: + action: create|modify + purpose: + +patterns_to_use: + - : + +dependencies: + - : + +acceptance_criteria_mapping: + - AC1: + - AC2: + +risks: + - + +estimated_test_files: + - : + +implementation_order: + 1. + 2. + ... +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: + + +$LAST_DESIGN + + +### 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 +} diff --git a/scripts/epic-execute-lib/regression-gate.sh b/scripts/epic-execute-lib/regression-gate.sh new file mode 100644 index 000000000..61e61925e --- /dev/null +++ b/scripts/epic-execute-lib/regression-gate.sh @@ -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 +} diff --git a/scripts/epic-execute.sh b/scripts/epic-execute.sh index 018c052c5..b3e80c076 100755 --- a/scripts/epic-execute.sh +++ b/scripts/epic-execute.sh @@ -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 +$design_context +## Previous Implementation Context + + +$decision_context + ## 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 - +$tooling_section ## Workflow Executor Engine @@ -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 }