- 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>
475 lines
17 KiB
Python
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 {}
|