Files
claude-skills-reference/engineering-team/tdd-guide/scripts/tdd_workflow.py
Alireza Rezvani f062cc9354 fix(skill): rewrite tdd-guide with proper structure and concise SKILL.md (#71) (#135)
- Reduce SKILL.md from 288 to 118 lines
- Add trigger phrases: generate tests, analyze coverage, TDD workflow, etc.
- Add Table of Contents
- Remove marketing language
- Move Python tools to scripts/ directory (8 files)
- Move sample files to assets/ directory
- Create references/ with TDD best practices, framework guide, CI integration
- Use imperative voice consistently

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 12:31:24 +01:00

475 lines
17 KiB
Python

"""
TDD workflow guidance module.
Provides step-by-step guidance through red-green-refactor cycles with validation.
"""
from typing import Dict, List, Any, Optional
from enum import Enum
class TDDPhase(Enum):
"""TDD cycle phases."""
RED = "red" # Write failing test
GREEN = "green" # Make test pass
REFACTOR = "refactor" # Improve code
class WorkflowState(Enum):
"""Current state of TDD workflow."""
INITIAL = "initial"
TEST_WRITTEN = "test_written"
TEST_FAILING = "test_failing"
TEST_PASSING = "test_passing"
CODE_REFACTORED = "code_refactored"
class TDDWorkflow:
"""Guide users through TDD red-green-refactor workflow."""
def __init__(self):
"""Initialize TDD workflow guide."""
self.current_phase = TDDPhase.RED
self.state = WorkflowState.INITIAL
self.history = []
def start_cycle(self, requirement: str) -> Dict[str, Any]:
"""
Start a new TDD cycle.
Args:
requirement: User story or requirement to implement
Returns:
Guidance for RED phase
"""
self.current_phase = TDDPhase.RED
self.state = WorkflowState.INITIAL
return {
'phase': 'RED',
'instruction': 'Write a failing test for the requirement',
'requirement': requirement,
'checklist': [
'Write test that describes desired behavior',
'Test should fail when run (no implementation yet)',
'Test name clearly describes what is being tested',
'Test has clear arrange-act-assert structure'
],
'tips': [
'Focus on behavior, not implementation',
'Start with simplest test case',
'Test should be specific and focused'
]
}
def validate_red_phase(
self,
test_code: str,
test_result: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
Validate RED phase completion.
Args:
test_code: The test code written
test_result: Test execution result (optional)
Returns:
Validation result and next steps
"""
validations = []
# Check test exists
if not test_code or len(test_code.strip()) < 10:
validations.append({
'valid': False,
'message': 'No test code provided'
})
else:
validations.append({
'valid': True,
'message': 'Test code provided'
})
# Check for assertions
has_assertion = any(keyword in test_code.lower()
for keyword in ['assert', 'expect', 'should'])
validations.append({
'valid': has_assertion,
'message': 'Contains assertions' if has_assertion else 'Missing assertions'
})
# Check test result if provided
if test_result:
test_failed = test_result.get('status') == 'failed'
validations.append({
'valid': test_failed,
'message': 'Test fails as expected' if test_failed else 'Test should fail in RED phase'
})
all_valid = all(v['valid'] for v in validations)
if all_valid:
self.state = WorkflowState.TEST_FAILING
self.current_phase = TDDPhase.GREEN
return {
'phase_complete': True,
'next_phase': 'GREEN',
'validations': validations,
'instruction': 'Write minimal code to make the test pass'
}
else:
return {
'phase_complete': False,
'current_phase': 'RED',
'validations': validations,
'instruction': 'Address validation issues before proceeding'
}
def validate_green_phase(
self,
implementation_code: str,
test_result: Dict[str, Any]
) -> Dict[str, Any]:
"""
Validate GREEN phase completion.
Args:
implementation_code: The implementation code
test_result: Test execution result
Returns:
Validation result and next steps
"""
validations = []
# Check implementation exists
if not implementation_code or len(implementation_code.strip()) < 5:
validations.append({
'valid': False,
'message': 'No implementation code provided'
})
else:
validations.append({
'valid': True,
'message': 'Implementation code provided'
})
# Check test now passes
test_passed = test_result.get('status') == 'passed'
validations.append({
'valid': test_passed,
'message': 'Test passes' if test_passed else 'Test still failing'
})
# Check for minimal implementation (heuristic)
is_minimal = self._check_minimal_implementation(implementation_code)
validations.append({
'valid': is_minimal,
'message': 'Implementation appears minimal' if is_minimal
else 'Implementation may be over-engineered'
})
all_valid = all(v['valid'] for v in validations)
if all_valid:
self.state = WorkflowState.TEST_PASSING
self.current_phase = TDDPhase.REFACTOR
return {
'phase_complete': True,
'next_phase': 'REFACTOR',
'validations': validations,
'instruction': 'Refactor code while keeping tests green',
'refactoring_suggestions': self._suggest_refactorings(implementation_code)
}
else:
return {
'phase_complete': False,
'current_phase': 'GREEN',
'validations': validations,
'instruction': 'Make the test pass before refactoring'
}
def validate_refactor_phase(
self,
original_code: str,
refactored_code: str,
test_result: Dict[str, Any]
) -> Dict[str, Any]:
"""
Validate REFACTOR phase completion.
Args:
original_code: Original implementation
refactored_code: Refactored implementation
test_result: Test execution result after refactoring
Returns:
Validation result and cycle completion status
"""
validations = []
# Check tests still pass
test_passed = test_result.get('status') == 'passed'
validations.append({
'valid': test_passed,
'message': 'Tests still pass after refactoring' if test_passed
else 'Tests broken by refactoring'
})
# Check code was actually refactored
code_changed = original_code != refactored_code
validations.append({
'valid': code_changed,
'message': 'Code was refactored' if code_changed
else 'No refactoring applied (optional)'
})
# Check code quality improved
quality_improved = self._check_quality_improvement(original_code, refactored_code)
if code_changed:
validations.append({
'valid': quality_improved,
'message': 'Code quality improved' if quality_improved
else 'Consider further refactoring for better quality'
})
all_valid = all(v['valid'] for v in validations if v.get('valid') is not None)
if all_valid:
self.state = WorkflowState.CODE_REFACTORED
self.history.append({
'cycle_complete': True,
'final_state': self.state
})
return {
'phase_complete': True,
'cycle_complete': True,
'validations': validations,
'message': 'TDD cycle complete! Ready for next requirement.',
'next_steps': [
'Commit your changes',
'Start next TDD cycle with new requirement',
'Or add more test cases for current feature'
]
}
else:
return {
'phase_complete': False,
'current_phase': 'REFACTOR',
'validations': validations,
'instruction': 'Ensure tests still pass after refactoring'
}
def _check_minimal_implementation(self, code: str) -> bool:
"""Check if implementation is minimal (heuristic)."""
# Simple heuristics:
# - Not too long (< 50 lines for unit tests)
# - Not too complex (few nested structures)
lines = code.split('\n')
non_empty_lines = [line for line in lines if line.strip() and not line.strip().startswith('#')]
# Check length
if len(non_empty_lines) > 50:
return False
# Check nesting depth (simplified)
max_depth = 0
current_depth = 0
for line in lines:
stripped = line.lstrip()
if stripped:
indent = len(line) - len(stripped)
depth = indent // 4 # Assuming 4-space indent
max_depth = max(max_depth, depth)
# Max nesting of 3 levels for simple implementation
return max_depth <= 3
def _check_quality_improvement(self, original: str, refactored: str) -> bool:
"""Check if refactoring improved code quality."""
# Simple heuristics:
# - Reduced duplication
# - Better naming
# - Simpler structure
# Check for reduced duplication (basic check)
original_lines = set(line.strip() for line in original.split('\n') if line.strip())
refactored_lines = set(line.strip() for line in refactored.split('\n') if line.strip())
# If unique lines increased proportionally, likely extracted duplicates
if len(refactored_lines) > len(original_lines):
return True
# Check for better naming (longer, more descriptive names)
original_avg_identifier_length = self._avg_identifier_length(original)
refactored_avg_identifier_length = self._avg_identifier_length(refactored)
if refactored_avg_identifier_length > original_avg_identifier_length:
return True
# If no clear improvement detected, assume refactoring was beneficial
return True
def _avg_identifier_length(self, code: str) -> float:
"""Calculate average identifier length (proxy for naming quality)."""
import re
identifiers = re.findall(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b', code)
# Filter out keywords
keywords = {'if', 'else', 'for', 'while', 'def', 'class', 'return', 'import', 'from'}
identifiers = [i for i in identifiers if i.lower() not in keywords]
if not identifiers:
return 0.0
return sum(len(i) for i in identifiers) / len(identifiers)
def _suggest_refactorings(self, code: str) -> List[str]:
"""Suggest potential refactorings."""
suggestions = []
# Check for long functions
lines = code.split('\n')
if len(lines) > 30:
suggestions.append('Consider breaking long function into smaller functions')
# Check for duplication (simple check)
line_counts = {}
for line in lines:
stripped = line.strip()
if len(stripped) > 10: # Ignore very short lines
line_counts[stripped] = line_counts.get(stripped, 0) + 1
duplicates = [line for line, count in line_counts.items() if count > 2]
if duplicates:
suggestions.append(f'Found {len(duplicates)} duplicated code patterns - consider extraction')
# Check for magic numbers
import re
magic_numbers = re.findall(r'\b\d+\b', code)
if len(magic_numbers) > 5:
suggestions.append('Consider extracting magic numbers to named constants')
# Check for long parameter lists
if 'def ' in code or 'function' in code:
param_matches = re.findall(r'\(([^)]+)\)', code)
for params in param_matches:
if params.count(',') > 3:
suggestions.append('Consider using parameter object for functions with many parameters')
break
if not suggestions:
suggestions.append('Code looks clean - no obvious refactorings needed')
return suggestions
def generate_workflow_summary(self) -> str:
"""Generate summary of TDD workflow progress."""
summary = [
"# TDD Workflow Summary\n",
f"Current Phase: {self.current_phase.value.upper()}",
f"Current State: {self.state.value.replace('_', ' ').title()}",
f"Completed Cycles: {len(self.history)}\n"
]
summary.append("## TDD Cycle Steps:\n")
summary.append("1. **RED**: Write a failing test")
summary.append(" - Test describes desired behavior")
summary.append(" - Test fails (no implementation)\n")
summary.append("2. **GREEN**: Make the test pass")
summary.append(" - Write minimal code to pass test")
summary.append(" - All tests should pass\n")
summary.append("3. **REFACTOR**: Improve the code")
summary.append(" - Clean up implementation")
summary.append(" - Tests still pass")
summary.append(" - Code is more maintainable\n")
return "\n".join(summary)
def get_phase_guidance(self, phase: Optional[TDDPhase] = None) -> Dict[str, Any]:
"""
Get detailed guidance for a specific phase.
Args:
phase: TDD phase (uses current if not specified)
Returns:
Detailed guidance dictionary
"""
target_phase = phase or self.current_phase
if target_phase == TDDPhase.RED:
return {
'phase': 'RED',
'goal': 'Write a failing test',
'steps': [
'1. Read and understand the requirement',
'2. Think about expected behavior',
'3. Write test that verifies this behavior',
'4. Run test and ensure it fails',
'5. Verify failure reason is correct (not syntax error)'
],
'common_mistakes': [
'Test passes immediately (no real assertion)',
'Test fails for wrong reason (syntax error)',
'Test is too broad or tests multiple things'
],
'tips': [
'Start with simplest test case',
'One assertion per test (focused)',
'Test should read like specification'
]
}
elif target_phase == TDDPhase.GREEN:
return {
'phase': 'GREEN',
'goal': 'Make the test pass with minimal code',
'steps': [
'1. Write simplest code that makes test pass',
'2. Run test and verify it passes',
'3. Run all tests to ensure no regression',
'4. Resist urge to add extra features'
],
'common_mistakes': [
'Over-engineering solution',
'Adding features not covered by tests',
'Breaking existing tests'
],
'tips': [
'Fake it till you make it (hardcode if needed)',
'Triangulate with more tests if needed',
'Keep implementation simple'
]
}
elif target_phase == TDDPhase.REFACTOR:
return {
'phase': 'REFACTOR',
'goal': 'Improve code quality while keeping tests green',
'steps': [
'1. Identify code smells or duplication',
'2. Apply one refactoring at a time',
'3. Run tests after each change',
'4. Commit when satisfied with quality'
],
'common_mistakes': [
'Changing behavior (breaking tests)',
'Refactoring too much at once',
'Skipping this phase'
],
'tips': [
'Extract methods for better naming',
'Remove duplication',
'Improve variable names',
'Tests are safety net - use them!'
]
}
return {}