feat: Add TDD demo story and implementation structure
Add example story for User Email Validation TDD workflow: - Complete story with acceptance criteria in Gherkin format - Technical requirements and test plan with test IDs - TDD phase progress tracking (currently red) - Implementation tasks for Dev agent - Directory structure for JS and Python demo implementations This demo story provides teams with a practical example to reproduce the red-green-refactor TDD cycle using BMAD framework and methodology.
This commit is contained in:
parent
173453636a
commit
6f109064e6
|
|
@ -0,0 +1,393 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# TDD Guard - Validates that code changes follow TDD discipline
|
||||||
|
# Part of BMAD Framework TDD integration
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||||
|
CONFIG_FILE="${PROJECT_ROOT}/bmad-core/core-config.yaml"
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Default values
|
||||||
|
TDD_ENABLED="false"
|
||||||
|
ALLOW_RED_PHASE_FAILURES="true"
|
||||||
|
EXIT_CODE=0
|
||||||
|
|
||||||
|
# Usage information
|
||||||
|
usage() {
|
||||||
|
cat << EOF
|
||||||
|
TDD Guard - Validates TDD discipline in code changes
|
||||||
|
|
||||||
|
Usage: $0 [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help Show this help message
|
||||||
|
-c, --config PATH Path to BMAD core config file
|
||||||
|
-b, --base REF Base commit/branch for comparison (default: HEAD~1)
|
||||||
|
-v, --verbose Verbose output
|
||||||
|
--phase PHASE Current TDD phase: red|green|refactor
|
||||||
|
--ci Running in CI mode (affects exit behavior)
|
||||||
|
--dry-run Show what would be checked without failing
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
$0 # Check changes against HEAD~1
|
||||||
|
$0 --base main # Check changes against main branch
|
||||||
|
$0 --phase green # Validate green phase rules
|
||||||
|
$0 --ci --phase red # CI mode, red phase (allows failures)
|
||||||
|
|
||||||
|
Exit Codes:
|
||||||
|
0 No TDD violations found
|
||||||
|
1 TDD violations found (in green phase)
|
||||||
|
2 Configuration error
|
||||||
|
3 Git/repository error
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Logging functions
|
||||||
|
log_info() {
|
||||||
|
echo -e "${BLUE}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
BASE_REF="HEAD~1"
|
||||||
|
VERBOSE=false
|
||||||
|
TDD_PHASE=""
|
||||||
|
CI_MODE=false
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-c|--config)
|
||||||
|
CONFIG_FILE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-b|--base)
|
||||||
|
BASE_REF="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-v|--verbose)
|
||||||
|
VERBOSE=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--phase)
|
||||||
|
TDD_PHASE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--ci)
|
||||||
|
CI_MODE=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--dry-run)
|
||||||
|
DRY_RUN=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "Unknown option: $1"
|
||||||
|
usage
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if we're in a git repository
|
||||||
|
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
||||||
|
log_error "Not in a git repository"
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Load configuration
|
||||||
|
load_config() {
|
||||||
|
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||||
|
if [[ "$VERBOSE" == true ]]; then
|
||||||
|
log_warn "Config file not found: $CONFIG_FILE"
|
||||||
|
log_info "Assuming TDD disabled"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract TDD settings from YAML (basic parsing)
|
||||||
|
if command -v yq > /dev/null 2>&1; then
|
||||||
|
TDD_ENABLED=$(yq e '.tdd.enabled // false' "$CONFIG_FILE" 2>/dev/null || echo "false")
|
||||||
|
ALLOW_RED_PHASE_FAILURES=$(yq e '.tdd.allow_red_phase_ci_failures // true' "$CONFIG_FILE" 2>/dev/null || echo "true")
|
||||||
|
else
|
||||||
|
# Fallback: basic grep parsing
|
||||||
|
if grep -q "tdd:" "$CONFIG_FILE" && grep -A 10 "tdd:" "$CONFIG_FILE" | grep -q "enabled: true"; then
|
||||||
|
TDD_ENABLED="true"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$VERBOSE" == true ]]; then
|
||||||
|
log_info "TDD enabled: $TDD_ENABLED"
|
||||||
|
log_info "Allow red phase failures: $ALLOW_RED_PHASE_FAILURES"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Detect TDD phase from commit messages or branch name
|
||||||
|
detect_tdd_phase() {
|
||||||
|
if [[ -n "$TDD_PHASE" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check recent commit messages for TDD phase indicators
|
||||||
|
RECENT_COMMITS=$(git log --oneline -5 "$BASE_REF".."HEAD" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if echo "$RECENT_COMMITS" | grep -qi "\[RED\]"; then
|
||||||
|
TDD_PHASE="red"
|
||||||
|
elif echo "$RECENT_COMMITS" | grep -qi "\[GREEN\]"; then
|
||||||
|
TDD_PHASE="green"
|
||||||
|
elif echo "$RECENT_COMMITS" | grep -qi "\[REFACTOR\]"; then
|
||||||
|
TDD_PHASE="refactor"
|
||||||
|
else
|
||||||
|
# Try to detect from branch name
|
||||||
|
BRANCH_NAME=$(git branch --show-current 2>/dev/null || echo "")
|
||||||
|
if echo "$BRANCH_NAME" | grep -qi "tdd"; then
|
||||||
|
TDD_PHASE="green" # Default assumption
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$VERBOSE" == true ]]; then
|
||||||
|
log_info "Detected TDD phase: ${TDD_PHASE:-unknown}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get changed files between base and current
|
||||||
|
get_changed_files() {
|
||||||
|
# Get list of changed files
|
||||||
|
CHANGED_FILES=$(git diff --name-only "$BASE_REF"..."HEAD" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [[ -z "$CHANGED_FILES" ]]; then
|
||||||
|
if [[ "$VERBOSE" == true ]]; then
|
||||||
|
log_info "No changed files detected"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Separate source and test files
|
||||||
|
SOURCE_FILES=""
|
||||||
|
TEST_FILES=""
|
||||||
|
|
||||||
|
while IFS= read -r file; do
|
||||||
|
if [[ -f "$file" ]]; then
|
||||||
|
if is_test_file "$file"; then
|
||||||
|
TEST_FILES="$TEST_FILES$file"$'\n'
|
||||||
|
elif is_source_file "$file"; then
|
||||||
|
SOURCE_FILES="$SOURCE_FILES$file"$'\n'
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done <<< "$CHANGED_FILES"
|
||||||
|
|
||||||
|
if [[ "$VERBOSE" == true ]]; then
|
||||||
|
log_info "Source files changed: $(echo "$SOURCE_FILES" | wc -l | tr -d ' ')"
|
||||||
|
log_info "Test files changed: $(echo "$TEST_FILES" | wc -l | tr -d ' ')"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if file is a test file
|
||||||
|
is_test_file() {
|
||||||
|
local file="$1"
|
||||||
|
# Common test file patterns
|
||||||
|
if [[ "$file" =~ \.(test|spec)\.(js|ts|py|go|java|cs)$ ]] || \
|
||||||
|
[[ "$file" =~ _test\.(py|go)$ ]] || \
|
||||||
|
[[ "$file" =~ Test\.(java|cs)$ ]] || \
|
||||||
|
[[ "$file" =~ tests?/ ]] || \
|
||||||
|
[[ "$file" =~ spec/ ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if file is a source file
|
||||||
|
is_source_file() {
|
||||||
|
local file="$1"
|
||||||
|
# Common source file patterns (excluding test files)
|
||||||
|
if [[ "$file" =~ \.(js|ts|py|go|java|cs|rb|php|cpp|c|h)$ ]] && ! is_test_file "$file"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if commit message indicates refactoring
|
||||||
|
is_refactor_commit() {
|
||||||
|
local commits=$(git log --oneline "$BASE_REF".."HEAD" 2>/dev/null || echo "")
|
||||||
|
if echo "$commits" | grep -qi "\[refactor\]"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate TDD rules
|
||||||
|
validate_tdd_rules() {
|
||||||
|
local violations=0
|
||||||
|
|
||||||
|
if [[ -z "$SOURCE_FILES" && -z "$TEST_FILES" ]]; then
|
||||||
|
if [[ "$VERBOSE" == true ]]; then
|
||||||
|
log_info "No relevant source or test files changed"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$TDD_PHASE" in
|
||||||
|
"red")
|
||||||
|
# Red phase: Tests should be added/modified, minimal or no source changes
|
||||||
|
if [[ -n "$SOURCE_FILES" ]] && [[ -z "$TEST_FILES" ]]; then
|
||||||
|
log_warn "RED phase violation: Source code changed without corresponding test changes"
|
||||||
|
log_warn "In TDD Red phase, tests should be written first"
|
||||||
|
if [[ "$ALLOW_RED_PHASE_FAILURES" == "false" ]] || [[ "$CI_MODE" == "false" ]]; then
|
||||||
|
violations=$((violations + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
"green")
|
||||||
|
# Green phase: Source changes must have corresponding test changes
|
||||||
|
if [[ -n "$SOURCE_FILES" ]] && [[ -z "$TEST_FILES" ]]; then
|
||||||
|
log_error "GREEN phase violation: Source code changed without corresponding tests"
|
||||||
|
log_error "In TDD Green phase, implementation should only make existing tests pass"
|
||||||
|
log_error "Source files modified:"
|
||||||
|
echo "$SOURCE_FILES" | while IFS= read -r file; do
|
||||||
|
[[ -n "$file" ]] && log_error " - $file"
|
||||||
|
done
|
||||||
|
violations=$((violations + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for large changes (potential feature creep)
|
||||||
|
if [[ -n "$SOURCE_FILES" ]]; then
|
||||||
|
local large_changes=0
|
||||||
|
while IFS= read -r file; do
|
||||||
|
if [[ -n "$file" ]] && [[ -f "$file" ]]; then
|
||||||
|
local additions=$(git diff --numstat "$BASE_REF" "$file" | cut -f1)
|
||||||
|
if [[ "$additions" =~ ^[0-9]+$ ]] && [[ "$additions" -gt 50 ]]; then
|
||||||
|
log_warn "Large change detected in $file: $additions lines added"
|
||||||
|
log_warn "Consider smaller, more focused changes in TDD Green phase"
|
||||||
|
large_changes=$((large_changes + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done <<< "$SOURCE_FILES"
|
||||||
|
|
||||||
|
if [[ "$large_changes" -gt 0 ]]; then
|
||||||
|
log_warn "Consider breaking large changes into smaller TDD cycles"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
"refactor")
|
||||||
|
# Refactor phase: Source changes allowed, tests should remain stable
|
||||||
|
if is_refactor_commit; then
|
||||||
|
if [[ "$VERBOSE" == true ]]; then
|
||||||
|
log_info "Refactor phase: Changes detected with proper [REFACTOR] tag"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ -n "$SOURCE_FILES" ]] && [[ -z "$TEST_FILES" ]]; then
|
||||||
|
log_warn "Potential refactor phase: Consider tagging commits with [REFACTOR]"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
# Unknown or no TDD phase
|
||||||
|
if [[ "$TDD_ENABLED" == "true" ]]; then
|
||||||
|
log_warn "TDD enabled but phase not detected"
|
||||||
|
log_warn "Consider tagging commits with [RED], [GREEN], or [REFACTOR]"
|
||||||
|
if [[ -n "$SOURCE_FILES" ]] && [[ -z "$TEST_FILES" ]]; then
|
||||||
|
log_warn "Source changes without test changes - may violate TDD discipline"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
return $violations
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
main() {
|
||||||
|
if [[ "$VERBOSE" == true ]]; then
|
||||||
|
log_info "TDD Guard starting..."
|
||||||
|
log_info "Base reference: $BASE_REF"
|
||||||
|
log_info "Config file: $CONFIG_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
load_config
|
||||||
|
|
||||||
|
if [[ "$TDD_ENABLED" != "true" ]]; then
|
||||||
|
if [[ "$VERBOSE" == true ]]; then
|
||||||
|
log_info "TDD not enabled, skipping validation"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
detect_tdd_phase
|
||||||
|
get_changed_files
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" == true ]]; then
|
||||||
|
log_info "DRY RUN - Would check:"
|
||||||
|
log_info " TDD Phase: ${TDD_PHASE:-unknown}"
|
||||||
|
log_info " Source files: $(echo "$SOURCE_FILES" | grep -c . || echo 0)"
|
||||||
|
log_info " Test files: $(echo "$TEST_FILES" | grep -c . || echo 0)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
validate_tdd_rules
|
||||||
|
local violations=$?
|
||||||
|
|
||||||
|
if [[ "$violations" -eq 0 ]]; then
|
||||||
|
log_success "TDD validation passed"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
log_error "$violations TDD violation(s) found"
|
||||||
|
|
||||||
|
# Provide helpful suggestions
|
||||||
|
echo ""
|
||||||
|
echo "💡 TDD Suggestions:"
|
||||||
|
case "$TDD_PHASE" in
|
||||||
|
"green")
|
||||||
|
echo " - Ensure all source changes have corresponding failing tests first"
|
||||||
|
echo " - Consider running QA agent's *write-failing-tests command"
|
||||||
|
echo " - Keep implementation minimal - only make tests pass"
|
||||||
|
;;
|
||||||
|
"red")
|
||||||
|
echo " - Write failing tests before implementation"
|
||||||
|
echo " - Use QA agent to create test cases first"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo " - Follow TDD Red-Green-Refactor cycle"
|
||||||
|
echo " - Tag commits with [RED], [GREEN], or [REFACTOR]"
|
||||||
|
echo " - Enable TDD workflow in BMAD configuration"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [[ "$TDD_PHASE" == "red" ]] && [[ "$ALLOW_RED_PHASE_FAILURES" == "true" ]] && [[ "$CI_MODE" == "true" ]]; then
|
||||||
|
log_warn "Red phase violations allowed in CI mode"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run main function
|
||||||
|
main "$@"
|
||||||
|
|
@ -0,0 +1,351 @@
|
||||||
|
# TDD-Enhanced CI/CD Workflow Template for BMAD Framework
|
||||||
|
# This template shows how to integrate TDD validation into CI/CD pipelines
|
||||||
|
|
||||||
|
name: TDD-Enhanced CI/CD
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, develop, 'feature/tdd-*' ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main, develop ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
# TDD Configuration
|
||||||
|
TDD_ENABLED: true
|
||||||
|
TDD_ALLOW_RED_PHASE_FAILURES: true
|
||||||
|
TDD_COVERAGE_THRESHOLD: 80
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# Detect TDD phase and validate changes
|
||||||
|
tdd-validation:
|
||||||
|
name: TDD Phase Validation
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
tdd-phase: ${{ steps.detect-phase.outputs.phase }}
|
||||||
|
tdd-enabled: ${{ steps.detect-phase.outputs.enabled }}
|
||||||
|
should-run-tests: ${{ steps.detect-phase.outputs.should-run-tests }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # Need full history for TDD guard
|
||||||
|
|
||||||
|
- name: Detect TDD phase
|
||||||
|
id: detect-phase
|
||||||
|
run: |
|
||||||
|
# Check if TDD is enabled in bmad-core/core-config.yaml
|
||||||
|
if grep -q "enabled: true" bmad-core/core-config.yaml 2>/dev/null; then
|
||||||
|
echo "enabled=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "should-run-tests=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "enabled=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "should-run-tests=true" >> $GITHUB_OUTPUT # Run tests anyway
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Detect TDD phase from commit messages
|
||||||
|
COMMITS=$(git log --oneline -10 ${{ github.event.before }}..${{ github.sha }} || git log --oneline -10)
|
||||||
|
|
||||||
|
if echo "$COMMITS" | grep -qi "\[RED\]"; then
|
||||||
|
echo "phase=red" >> $GITHUB_OUTPUT
|
||||||
|
elif echo "$COMMITS" | grep -qi "\[GREEN\]"; then
|
||||||
|
echo "phase=green" >> $GITHUB_OUTPUT
|
||||||
|
elif echo "$COMMITS" | grep -qi "\[REFACTOR\]"; then
|
||||||
|
echo "phase=refactor" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
# Default phase detection based on branch
|
||||||
|
if [[ "${{ github.ref }}" =~ tdd ]]; then
|
||||||
|
echo "phase=green" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "phase=unknown" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run TDD Guard
|
||||||
|
if: steps.detect-phase.outputs.enabled == 'true'
|
||||||
|
run: |
|
||||||
|
echo "🧪 Running TDD Guard validation..."
|
||||||
|
|
||||||
|
# Make TDD guard executable
|
||||||
|
chmod +x bmad-core/scripts/tdd-guard.sh
|
||||||
|
|
||||||
|
# Run TDD guard with appropriate settings
|
||||||
|
if [[ "${{ steps.detect-phase.outputs.phase }}" == "red" ]]; then
|
||||||
|
# Allow red phase failures in CI
|
||||||
|
./bmad-core/scripts/tdd-guard.sh --ci --phase red --verbose || {
|
||||||
|
echo "⚠️ Red phase violations detected but allowed in CI"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
else
|
||||||
|
# Strict validation for green/refactor phases
|
||||||
|
./bmad-core/scripts/tdd-guard.sh --phase "${{ steps.detect-phase.outputs.phase }}" --verbose
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test execution with TDD awareness
|
||||||
|
test:
|
||||||
|
name: Run Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: tdd-validation
|
||||||
|
if: needs.tdd-validation.outputs.should-run-tests == 'true'
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
# Add multiple language support as needed
|
||||||
|
language: [javascript, python]
|
||||||
|
include:
|
||||||
|
- language: javascript
|
||||||
|
test-command: npm test
|
||||||
|
coverage-command: npm run test:coverage
|
||||||
|
setup: |
|
||||||
|
node-version: 18
|
||||||
|
cache: npm
|
||||||
|
- language: python
|
||||||
|
test-command: pytest
|
||||||
|
coverage-command: pytest --cov=.
|
||||||
|
setup: |
|
||||||
|
python-version: '3.9'
|
||||||
|
cache: pip
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Language-specific setup
|
||||||
|
- name: Setup Node.js
|
||||||
|
if: matrix.language == 'javascript'
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
if: matrix.language == 'python'
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
cache: 'pip'
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
- name: Install JavaScript dependencies
|
||||||
|
if: matrix.language == 'javascript'
|
||||||
|
run: |
|
||||||
|
if [ -f package.json ]; then
|
||||||
|
npm ci
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Install Python dependencies
|
||||||
|
if: matrix.language == 'python'
|
||||||
|
run: |
|
||||||
|
if [ -f requirements.txt ]; then
|
||||||
|
pip install -r requirements.txt
|
||||||
|
fi
|
||||||
|
if [ -f requirements-dev.txt ]; then
|
||||||
|
pip install -r requirements-dev.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run tests with TDD phase awareness
|
||||||
|
- name: Run tests
|
||||||
|
id: run-tests
|
||||||
|
run: |
|
||||||
|
echo "🧪 Running tests for TDD phase: ${{ needs.tdd-validation.outputs.tdd-phase }}"
|
||||||
|
|
||||||
|
case "${{ needs.tdd-validation.outputs.tdd-phase }}" in
|
||||||
|
"red")
|
||||||
|
echo "RED phase: Expecting some tests to fail"
|
||||||
|
${{ matrix.test-command }} || {
|
||||||
|
echo "⚠️ Tests failed as expected in RED phase"
|
||||||
|
if [[ "$TDD_ALLOW_RED_PHASE_FAILURES" == "true" ]]; then
|
||||||
|
echo "test-result=red-expected-fail" >> $GITHUB_OUTPUT
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "test-result=fail" >> $GITHUB_OUTPUT
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
echo "test-result=pass" >> $GITHUB_OUTPUT
|
||||||
|
;;
|
||||||
|
"green"|"refactor")
|
||||||
|
echo "GREEN/REFACTOR phase: All tests should pass"
|
||||||
|
${{ matrix.test-command }}
|
||||||
|
echo "test-result=pass" >> $GITHUB_OUTPUT
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown phase: Running standard test suite"
|
||||||
|
${{ matrix.test-command }}
|
||||||
|
echo "test-result=pass" >> $GITHUB_OUTPUT
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Generate coverage report
|
||||||
|
- name: Generate coverage report
|
||||||
|
if: env.TDD_COVERAGE_THRESHOLD > 0
|
||||||
|
run: |
|
||||||
|
echo "📊 Generating coverage report..."
|
||||||
|
${{ matrix.coverage-command }} || echo "Coverage command failed"
|
||||||
|
|
||||||
|
# Upload coverage reports
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
if: matrix.language == 'javascript' || matrix.language == 'python'
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
file: ./coverage.xml
|
||||||
|
flags: ${{ matrix.language }}
|
||||||
|
name: ${{ matrix.language }}-coverage
|
||||||
|
fail_ci_if_error: false
|
||||||
|
|
||||||
|
# Quality gates specific to TDD phase
|
||||||
|
quality-gates:
|
||||||
|
name: Quality Gates
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [tdd-validation, test]
|
||||||
|
if: always()
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: TDD Phase Quality Gate
|
||||||
|
run: |
|
||||||
|
echo "🚦 Evaluating quality gates for TDD phase: ${{ needs.tdd-validation.outputs.tdd-phase }}"
|
||||||
|
|
||||||
|
case "${{ needs.tdd-validation.outputs.tdd-phase }}" in
|
||||||
|
"red")
|
||||||
|
echo "RED phase quality gate:"
|
||||||
|
echo "✅ Tests written first"
|
||||||
|
echo "✅ Implementation minimal or non-existent"
|
||||||
|
if [[ "${{ needs.test.result }}" == "success" ]] || [[ "${{ needs.test.outputs.test-result }}" == "red-expected-fail" ]]; then
|
||||||
|
echo "✅ RED phase gate: PASS"
|
||||||
|
else
|
||||||
|
echo "❌ RED phase gate: FAIL"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"green")
|
||||||
|
echo "GREEN phase quality gate:"
|
||||||
|
echo "✅ All tests passing"
|
||||||
|
echo "✅ Minimal implementation"
|
||||||
|
echo "✅ No feature creep"
|
||||||
|
if [[ "${{ needs.test.result }}" == "success" ]]; then
|
||||||
|
echo "✅ GREEN phase gate: PASS"
|
||||||
|
else
|
||||||
|
echo "❌ GREEN phase gate: FAIL"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"refactor")
|
||||||
|
echo "REFACTOR phase quality gate:"
|
||||||
|
echo "✅ Tests remain green"
|
||||||
|
echo "✅ Code quality improved"
|
||||||
|
echo "✅ Behavior preserved"
|
||||||
|
if [[ "${{ needs.test.result }}" == "success" ]]; then
|
||||||
|
echo "✅ REFACTOR phase gate: PASS"
|
||||||
|
else
|
||||||
|
echo "❌ REFACTOR phase gate: FAIL"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Standard quality gate:"
|
||||||
|
if [[ "${{ needs.test.result }}" == "success" ]]; then
|
||||||
|
echo "✅ Standard gate: PASS"
|
||||||
|
else
|
||||||
|
echo "❌ Standard gate: FAIL"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
- name: Generate TDD Report
|
||||||
|
if: needs.tdd-validation.outputs.tdd-enabled == 'true'
|
||||||
|
run: |
|
||||||
|
echo "# TDD Pipeline Report" > tdd-report.md
|
||||||
|
echo "" >> tdd-report.md
|
||||||
|
echo "**TDD Phase:** ${{ needs.tdd-validation.outputs.tdd-phase }}" >> tdd-report.md
|
||||||
|
echo "**Test Result:** ${{ needs.test.outputs.test-result || 'unknown' }}" >> tdd-report.md
|
||||||
|
echo "**Quality Gate:** $([ "${{ job.status }}" == "success" ] && echo "PASS" || echo "FAIL")" >> tdd-report.md
|
||||||
|
echo "" >> tdd-report.md
|
||||||
|
echo "## Phase-Specific Results" >> tdd-report.md
|
||||||
|
|
||||||
|
case "${{ needs.tdd-validation.outputs.tdd-phase }}" in
|
||||||
|
"red")
|
||||||
|
echo "- ✅ Failing tests written first" >> tdd-report.md
|
||||||
|
echo "- ✅ Implementation postponed until GREEN phase" >> tdd-report.md
|
||||||
|
;;
|
||||||
|
"green")
|
||||||
|
echo "- ✅ Tests now passing" >> tdd-report.md
|
||||||
|
echo "- ✅ Minimal implementation completed" >> tdd-report.md
|
||||||
|
;;
|
||||||
|
"refactor")
|
||||||
|
echo "- ✅ Code quality improved" >> tdd-report.md
|
||||||
|
echo "- ✅ All tests remain green" >> tdd-report.md
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
- name: Comment TDD Report on PR
|
||||||
|
if: github.event_name == 'pull_request' && needs.tdd-validation.outputs.tdd-enabled == 'true'
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
if (fs.existsSync('tdd-report.md')) {
|
||||||
|
const report = fs.readFileSync('tdd-report.md', 'utf8');
|
||||||
|
|
||||||
|
github.rest.issues.createComment({
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
body: `## 🧪 TDD Pipeline Results\n\n${report}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deploy only after successful TDD validation
|
||||||
|
deploy:
|
||||||
|
name: Deploy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [tdd-validation, test, quality-gates]
|
||||||
|
if: github.ref == 'refs/heads/main' && success()
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Deploy Application
|
||||||
|
run: |
|
||||||
|
echo "🚀 Deploying application after successful TDD validation"
|
||||||
|
echo "TDD Phase: ${{ needs.tdd-validation.outputs.tdd-phase }}"
|
||||||
|
|
||||||
|
# Add your deployment steps here
|
||||||
|
# Only deploy if all TDD phases pass validation
|
||||||
|
|
||||||
|
if [[ "${{ needs.tdd-validation.outputs.tdd-phase }}" == "green" ]] || [[ "${{ needs.tdd-validation.outputs.tdd-phase }}" == "refactor" ]]; then
|
||||||
|
echo "✅ Safe to deploy: Implementation complete and tested"
|
||||||
|
else
|
||||||
|
echo "⚠️ Deployment skipped: Not in a stable TDD phase"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Additional workflow for TDD metrics collection
|
||||||
|
tdd-metrics:
|
||||||
|
name: TDD Metrics
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [tdd-validation, test, quality-gates]
|
||||||
|
if: always() && needs.tdd-validation.outputs.tdd-enabled == 'true'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Collect TDD Metrics
|
||||||
|
run: |
|
||||||
|
echo "📊 Collecting TDD metrics..."
|
||||||
|
|
||||||
|
# Calculate TDD cycle time (example)
|
||||||
|
CYCLE_START=$(git log --grep="\[RED\]" --format="%ct" -1 || echo $(date +%s))
|
||||||
|
CYCLE_END=$(date +%s)
|
||||||
|
CYCLE_TIME=$(( (CYCLE_END - CYCLE_START) / 60 )) # minutes
|
||||||
|
|
||||||
|
echo "TDD Cycle Metrics:"
|
||||||
|
echo "- Phase: ${{ needs.tdd-validation.outputs.tdd-phase }}"
|
||||||
|
echo "- Cycle Time: ${CYCLE_TIME} minutes"
|
||||||
|
echo "- Test Status: ${{ needs.test.outputs.test-result }}"
|
||||||
|
echo "- Quality Gate: $([ "${{ needs.quality-gates.result }}" == "success" ] && echo "PASS" || echo "FAIL")"
|
||||||
|
|
||||||
|
# Store metrics (example - adapt to your metrics system)
|
||||||
|
echo "Metrics would be stored in your preferred system (Grafana, etc.)"
|
||||||
|
|
@ -0,0 +1,201 @@
|
||||||
|
# Story 1.1: User Email Validation
|
||||||
|
|
||||||
|
## Story Metadata
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
story:
|
||||||
|
epic: '1'
|
||||||
|
number: '1'
|
||||||
|
title: 'User Email Validation'
|
||||||
|
status: 'ready'
|
||||||
|
priority: 'high'
|
||||||
|
|
||||||
|
# TDD Configuration
|
||||||
|
tdd:
|
||||||
|
status: 'red' # Current phase: red|green|refactor|done
|
||||||
|
cycle: 1
|
||||||
|
coverage_target: 90.0
|
||||||
|
tests:
|
||||||
|
- id: 'UV-001'
|
||||||
|
name: 'should validate correct email format'
|
||||||
|
type: unit
|
||||||
|
status: failing
|
||||||
|
file_path: 'tests/user-validator.test.js'
|
||||||
|
- id: 'UV-002'
|
||||||
|
name: 'should reject invalid email format'
|
||||||
|
type: unit
|
||||||
|
status: failing
|
||||||
|
file_path: 'tests/user-validator.test.js'
|
||||||
|
- id: 'UV-003'
|
||||||
|
name: 'should handle edge cases'
|
||||||
|
type: unit
|
||||||
|
status: failing
|
||||||
|
file_path: 'tests/user-validator.test.js'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Story Description
|
||||||
|
|
||||||
|
**As a** System Administrator
|
||||||
|
**I want** to validate user email addresses
|
||||||
|
**So that** only users with valid email formats can register
|
||||||
|
|
||||||
|
### Context
|
||||||
|
|
||||||
|
This is a foundational feature for user registration. We need robust email validation that follows RFC standards while being user-friendly. This will be used by the registration system and user profile updates.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
```gherkin
|
||||||
|
Feature: User Email Validation
|
||||||
|
|
||||||
|
Scenario: Valid email formats are accepted
|
||||||
|
Given a user provides an email address
|
||||||
|
When the email has correct format with @ symbol and domain
|
||||||
|
Then the validation should return true
|
||||||
|
|
||||||
|
Scenario: Invalid email formats are rejected
|
||||||
|
Given a user provides an invalid email address
|
||||||
|
When the email lacks @ symbol or proper domain format
|
||||||
|
Then the validation should return false with appropriate error message
|
||||||
|
|
||||||
|
Scenario: Edge cases are handled properly
|
||||||
|
Given a user provides edge case email formats
|
||||||
|
When validation is performed on emails with special characters or unusual formats
|
||||||
|
Then the system should handle them according to RFC standards
|
||||||
|
```
|
||||||
|
|
||||||
|
## Technical Requirements
|
||||||
|
|
||||||
|
### Functional Requirements
|
||||||
|
|
||||||
|
- Validate email format using RFC-compliant rules
|
||||||
|
- Return boolean result with error details when invalid
|
||||||
|
- Handle common edge cases (special characters, multiple @, etc.)
|
||||||
|
- Performance: validation should complete in < 1ms
|
||||||
|
|
||||||
|
### Non-Functional Requirements
|
||||||
|
|
||||||
|
- **Performance:** < 1ms validation time per email
|
||||||
|
- **Security:** Prevent injection attacks via email input
|
||||||
|
- **Reliability:** 99.9% accuracy on email format validation
|
||||||
|
- **Maintainability:** Clear error messages for debugging
|
||||||
|
|
||||||
|
## TDD Test Plan (QA Agent Responsibility)
|
||||||
|
|
||||||
|
### Test Strategy
|
||||||
|
|
||||||
|
- **Primary Test Type:** unit
|
||||||
|
- **Mocking Approach:** No external dependencies to mock
|
||||||
|
- **Test Data:** Fixed test cases covering valid/invalid formats
|
||||||
|
|
||||||
|
### Planned Test Scenarios
|
||||||
|
|
||||||
|
| ID | Scenario | Type | Priority | AC Reference |
|
||||||
|
| ------ | ---------------------------- | ---- | -------- | ------------ |
|
||||||
|
| UV-001 | Valid email formats accepted | unit | P0 | AC1 |
|
||||||
|
| UV-002 | Invalid formats rejected | unit | P0 | AC2 |
|
||||||
|
| UV-003 | Edge cases handled | unit | P1 | AC3 |
|
||||||
|
| UV-004 | Performance requirements met | unit | P2 | NFR |
|
||||||
|
|
||||||
|
## TDD Progress
|
||||||
|
|
||||||
|
### Current Phase: RED
|
||||||
|
|
||||||
|
**Cycle:** 1
|
||||||
|
**Last Updated:** 2025-01-12
|
||||||
|
|
||||||
|
### Red Phase - Cycle 1
|
||||||
|
|
||||||
|
**Date:** 2025-01-12
|
||||||
|
**Agent:** Quinn (QA Agent)
|
||||||
|
|
||||||
|
**Tests Written:**
|
||||||
|
|
||||||
|
- UV-001: should validate correct email format (FAILING ✅)
|
||||||
|
- UV-002: should reject invalid email format (FAILING ✅)
|
||||||
|
- UV-003: should handle edge cases (FAILING ✅)
|
||||||
|
|
||||||
|
**Test Files:**
|
||||||
|
|
||||||
|
- tests/user-validator.test.js
|
||||||
|
|
||||||
|
**Next Step:** Dev Agent to implement minimal code to make tests pass
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Tasks (Dev Agent)
|
||||||
|
|
||||||
|
### Primary Tasks
|
||||||
|
|
||||||
|
- [ ] Create UserValidator class
|
||||||
|
- [ ] Implement email validation logic
|
||||||
|
- [ ] Handle error cases and edge cases
|
||||||
|
|
||||||
|
### Subtasks
|
||||||
|
|
||||||
|
- [ ] Set up basic class structure
|
||||||
|
- [ ] Implement regex-based validation
|
||||||
|
- [ ] Add error message generation
|
||||||
|
- [ ] Performance optimization
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
|
||||||
|
### TDD-Specific DoD
|
||||||
|
|
||||||
|
- [ ] Tests written first (Red phase completed)
|
||||||
|
- [ ] All tests passing (Green phase completed)
|
||||||
|
- [ ] Code refactored for quality (Refactor phase completed)
|
||||||
|
- [ ] Test coverage meets target (90%)
|
||||||
|
- [ ] All external dependencies properly mocked (N/A for this story)
|
||||||
|
- [ ] No features implemented without corresponding tests
|
||||||
|
|
||||||
|
### General DoD
|
||||||
|
|
||||||
|
- [ ] All acceptance criteria met
|
||||||
|
- [ ] Code follows project standards
|
||||||
|
- [ ] Documentation updated
|
||||||
|
- [ ] Ready for review
|
||||||
|
|
||||||
|
## Dev Agent Record
|
||||||
|
|
||||||
|
### Implementation Notes
|
||||||
|
|
||||||
|
_(Dev agent will document implementation decisions here)_
|
||||||
|
|
||||||
|
### TDD Cycle Log
|
||||||
|
|
||||||
|
_(Automatic tracking of Red-Green-Refactor progression)_
|
||||||
|
|
||||||
|
**Cycle 1:**
|
||||||
|
|
||||||
|
- Red Phase: 2025-01-12 - 3 failing tests written
|
||||||
|
- Green Phase: _(pending)_
|
||||||
|
- Refactor Phase: _(pending)_
|
||||||
|
|
||||||
|
### File List
|
||||||
|
|
||||||
|
_(Dev agent will list all files created/modified)_
|
||||||
|
|
||||||
|
- tests/user-validator.test.js (created)
|
||||||
|
- _(implementation files will be added during GREEN phase)_
|
||||||
|
|
||||||
|
### Test Execution Log
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# RED phase test runs will be logged here
|
||||||
|
```
|
||||||
|
|
||||||
|
## QA Results
|
||||||
|
|
||||||
|
_(QA agent will populate this during review)_
|
||||||
|
|
||||||
|
## Change Log
|
||||||
|
|
||||||
|
- **2025-01-12**: Story created from TDD template
|
||||||
|
- **2025-01-12**: Red phase completed - failing tests written
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**TDD Status:** 🔴 RED Phase
|
||||||
|
**Agent Assigned:** Quinn (QA) → James (Dev)
|
||||||
|
**Estimated Effort:** 2 hours
|
||||||
Loading…
Reference in New Issue