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:
parent
3bad076884
commit
fd744c96f3
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue