Initial commit
This commit is contained in:
26
scripts/utils/validators/__init__.py
Normal file
26
scripts/utils/validators/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""Validators for Bubble Tea Designer."""
|
||||
|
||||
from .requirement_validator import (
|
||||
RequirementValidator,
|
||||
validate_description_clarity,
|
||||
validate_requirements_completeness,
|
||||
ValidationReport,
|
||||
ValidationResult,
|
||||
ValidationLevel
|
||||
)
|
||||
|
||||
from .design_validator import (
|
||||
DesignValidator,
|
||||
validate_component_fit
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'RequirementValidator',
|
||||
'validate_description_clarity',
|
||||
'validate_requirements_completeness',
|
||||
'DesignValidator',
|
||||
'validate_component_fit',
|
||||
'ValidationReport',
|
||||
'ValidationResult',
|
||||
'ValidationLevel'
|
||||
]
|
||||
425
scripts/utils/validators/design_validator.py
Normal file
425
scripts/utils/validators/design_validator.py
Normal file
@@ -0,0 +1,425 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Design validators for Bubble Tea Designer.
|
||||
Validates design outputs (component selections, architecture, workflows).
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Optional
|
||||
from .requirement_validator import ValidationReport, ValidationResult, ValidationLevel
|
||||
|
||||
|
||||
class DesignValidator:
|
||||
"""Validates TUI design outputs."""
|
||||
|
||||
def validate_component_selection(
|
||||
self,
|
||||
components: Dict,
|
||||
requirements: Dict
|
||||
) -> ValidationReport:
|
||||
"""
|
||||
Validate component selection against requirements.
|
||||
|
||||
Args:
|
||||
components: Selected components dict
|
||||
requirements: Original requirements
|
||||
|
||||
Returns:
|
||||
ValidationReport
|
||||
"""
|
||||
report = ValidationReport()
|
||||
|
||||
# Check 1: At least one component selected
|
||||
primary = components.get('primary_components', [])
|
||||
has_components = len(primary) > 0
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="has_components",
|
||||
level=ValidationLevel.CRITICAL,
|
||||
passed=has_components,
|
||||
message=f"Primary components selected: {len(primary)}"
|
||||
))
|
||||
|
||||
# Check 2: Components cover requirements
|
||||
features = set(requirements.get('features', []))
|
||||
if features and primary:
|
||||
# Check if components mention required features
|
||||
covered_features = set()
|
||||
for comp in primary:
|
||||
justification = comp.get('justification', '').lower()
|
||||
for feature in features:
|
||||
if feature.lower() in justification:
|
||||
covered_features.add(feature)
|
||||
|
||||
coverage = len(covered_features) / len(features) * 100 if features else 0
|
||||
report.add(ValidationResult(
|
||||
check_name="feature_coverage",
|
||||
level=ValidationLevel.WARNING,
|
||||
passed=coverage >= 50,
|
||||
message=f"Feature coverage: {coverage:.0f}% ({len(covered_features)}/{len(features)})"
|
||||
))
|
||||
|
||||
# Check 3: No duplicate components
|
||||
comp_names = [c.get('component', '') for c in primary]
|
||||
duplicates = [name for name in comp_names if comp_names.count(name) > 1]
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="no_duplicates",
|
||||
level=ValidationLevel.WARNING,
|
||||
passed=len(duplicates) == 0,
|
||||
message="No duplicate components" if not duplicates else
|
||||
f"Duplicate components: {set(duplicates)}"
|
||||
))
|
||||
|
||||
# Check 4: Reasonable number of components (not too many)
|
||||
reasonable_count = len(primary) <= 6
|
||||
report.add(ValidationResult(
|
||||
check_name="reasonable_count",
|
||||
level=ValidationLevel.INFO,
|
||||
passed=reasonable_count,
|
||||
message=f"Component count: {len(primary)} ({'reasonable' if reasonable_count else 'may be too many'})"
|
||||
))
|
||||
|
||||
# Check 5: Each component has justification
|
||||
all_justified = all('justification' in c for c in primary)
|
||||
report.add(ValidationResult(
|
||||
check_name="all_justified",
|
||||
level=ValidationLevel.INFO,
|
||||
passed=all_justified,
|
||||
message="All components justified" if all_justified else
|
||||
"Some components missing justification"
|
||||
))
|
||||
|
||||
return report
|
||||
|
||||
def validate_architecture(self, architecture: Dict) -> ValidationReport:
|
||||
"""
|
||||
Validate architecture design.
|
||||
|
||||
Args:
|
||||
architecture: Architecture specification
|
||||
|
||||
Returns:
|
||||
ValidationReport
|
||||
"""
|
||||
report = ValidationReport()
|
||||
|
||||
# Check 1: Has model struct
|
||||
has_model = 'model_struct' in architecture and architecture['model_struct']
|
||||
report.add(ValidationResult(
|
||||
check_name="has_model_struct",
|
||||
level=ValidationLevel.CRITICAL,
|
||||
passed=has_model,
|
||||
message="Model struct defined" if has_model else "Missing model struct"
|
||||
))
|
||||
|
||||
# Check 2: Has message handlers
|
||||
handlers = architecture.get('message_handlers', {})
|
||||
has_handlers = len(handlers) > 0
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="has_message_handlers",
|
||||
level=ValidationLevel.CRITICAL,
|
||||
passed=has_handlers,
|
||||
message=f"Message handlers defined: {len(handlers)}"
|
||||
))
|
||||
|
||||
# Check 3: Has key message handler (keyboard)
|
||||
has_key_handler = 'tea.KeyMsg' in handlers or 'KeyMsg' in handlers
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="has_keyboard_handler",
|
||||
level=ValidationLevel.WARNING,
|
||||
passed=has_key_handler,
|
||||
message="Keyboard handler present" if has_key_handler else
|
||||
"Missing keyboard handler (tea.KeyMsg)"
|
||||
))
|
||||
|
||||
# Check 4: Has view logic
|
||||
has_view = 'view_logic' in architecture and architecture['view_logic']
|
||||
report.add(ValidationResult(
|
||||
check_name="has_view_logic",
|
||||
level=ValidationLevel.CRITICAL,
|
||||
passed=has_view,
|
||||
message="View logic defined" if has_view else "Missing view logic"
|
||||
))
|
||||
|
||||
# Check 5: Has diagrams
|
||||
diagrams = architecture.get('diagrams', {})
|
||||
has_diagrams = len(diagrams) > 0
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="has_diagrams",
|
||||
level=ValidationLevel.INFO,
|
||||
passed=has_diagrams,
|
||||
message=f"Architecture diagrams: {len(diagrams)}"
|
||||
))
|
||||
|
||||
return report
|
||||
|
||||
def validate_workflow_completeness(self, workflow: Dict) -> ValidationReport:
|
||||
"""
|
||||
Validate workflow has all necessary phases and tasks.
|
||||
|
||||
Args:
|
||||
workflow: Workflow specification
|
||||
|
||||
Returns:
|
||||
ValidationReport
|
||||
"""
|
||||
report = ValidationReport()
|
||||
|
||||
# Check 1: Has phases
|
||||
phases = workflow.get('phases', [])
|
||||
has_phases = len(phases) > 0
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="has_phases",
|
||||
level=ValidationLevel.CRITICAL,
|
||||
passed=has_phases,
|
||||
message=f"Workflow phases: {len(phases)}"
|
||||
))
|
||||
|
||||
if not phases:
|
||||
return report
|
||||
|
||||
# Check 2: Each phase has tasks
|
||||
all_have_tasks = all(len(phase.get('tasks', [])) > 0 for phase in phases)
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="all_phases_have_tasks",
|
||||
level=ValidationLevel.WARNING,
|
||||
passed=all_have_tasks,
|
||||
message="All phases have tasks" if all_have_tasks else
|
||||
"Some phases are missing tasks"
|
||||
))
|
||||
|
||||
# Check 3: Has testing checkpoints
|
||||
checkpoints = workflow.get('testing_checkpoints', [])
|
||||
has_testing = len(checkpoints) > 0
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="has_testing",
|
||||
level=ValidationLevel.WARNING,
|
||||
passed=has_testing,
|
||||
message=f"Testing checkpoints: {len(checkpoints)}"
|
||||
))
|
||||
|
||||
# Check 4: Reasonable phase count (2-6 phases)
|
||||
reasonable_phases = 2 <= len(phases) <= 6
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="reasonable_phases",
|
||||
level=ValidationLevel.INFO,
|
||||
passed=reasonable_phases,
|
||||
message=f"Phase count: {len(phases)} ({'good' if reasonable_phases else 'unusual'})"
|
||||
))
|
||||
|
||||
# Check 5: Has time estimates
|
||||
total_time = workflow.get('total_estimated_time')
|
||||
has_estimate = bool(total_time)
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="has_time_estimate",
|
||||
level=ValidationLevel.INFO,
|
||||
passed=has_estimate,
|
||||
message=f"Time estimate: {total_time or 'missing'}"
|
||||
))
|
||||
|
||||
return report
|
||||
|
||||
def validate_design_report(self, report_data: Dict) -> ValidationReport:
|
||||
"""
|
||||
Validate complete design report.
|
||||
|
||||
Args:
|
||||
report_data: Complete design report
|
||||
|
||||
Returns:
|
||||
ValidationReport
|
||||
"""
|
||||
report = ValidationReport()
|
||||
|
||||
# Check all required sections present
|
||||
required_sections = ['requirements', 'components', 'patterns', 'architecture', 'workflow']
|
||||
sections = report_data.get('sections', {})
|
||||
|
||||
for section in required_sections:
|
||||
has_section = section in sections and sections[section]
|
||||
report.add(ValidationResult(
|
||||
check_name=f"has_{section}_section",
|
||||
level=ValidationLevel.CRITICAL,
|
||||
passed=has_section,
|
||||
message=f"Section '{section}': {'present' if has_section else 'MISSING'}"
|
||||
))
|
||||
|
||||
# Check has summary
|
||||
has_summary = 'summary' in report_data and report_data['summary']
|
||||
report.add(ValidationResult(
|
||||
check_name="has_summary",
|
||||
level=ValidationLevel.WARNING,
|
||||
passed=has_summary,
|
||||
message="Summary present" if has_summary else "Missing summary"
|
||||
))
|
||||
|
||||
# Check has scaffolding
|
||||
has_scaffolding = 'scaffolding' in report_data and report_data['scaffolding']
|
||||
report.add(ValidationResult(
|
||||
check_name="has_scaffolding",
|
||||
level=ValidationLevel.INFO,
|
||||
passed=has_scaffolding,
|
||||
message="Code scaffolding included" if has_scaffolding else
|
||||
"No code scaffolding"
|
||||
))
|
||||
|
||||
# Check has next steps
|
||||
next_steps = report_data.get('next_steps', [])
|
||||
has_next_steps = len(next_steps) > 0
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="has_next_steps",
|
||||
level=ValidationLevel.INFO,
|
||||
passed=has_next_steps,
|
||||
message=f"Next steps: {len(next_steps)}"
|
||||
))
|
||||
|
||||
return report
|
||||
|
||||
|
||||
def validate_component_fit(component: str, requirement: str) -> bool:
|
||||
"""
|
||||
Quick check if component fits requirement.
|
||||
|
||||
Args:
|
||||
component: Component name (e.g., "viewport.Model")
|
||||
requirement: Requirement description
|
||||
|
||||
Returns:
|
||||
True if component appears suitable
|
||||
"""
|
||||
component_lower = component.lower()
|
||||
requirement_lower = requirement.lower()
|
||||
|
||||
# Simple keyword matching
|
||||
keyword_map = {
|
||||
'viewport': ['scroll', 'view', 'display', 'content'],
|
||||
'textinput': ['input', 'text', 'search', 'query'],
|
||||
'textarea': ['edit', 'multi-line', 'text area'],
|
||||
'table': ['table', 'tabular', 'rows', 'columns'],
|
||||
'list': ['list', 'items', 'select', 'choose'],
|
||||
'progress': ['progress', 'loading', 'installation'],
|
||||
'spinner': ['loading', 'spinner', 'wait'],
|
||||
'filepicker': ['file', 'select file', 'choose file']
|
||||
}
|
||||
|
||||
for comp_key, keywords in keyword_map.items():
|
||||
if comp_key in component_lower:
|
||||
return any(kw in requirement_lower for kw in keywords)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Test design validator."""
|
||||
print("Testing Design Validator\n" + "=" * 50)
|
||||
|
||||
validator = DesignValidator()
|
||||
|
||||
# Test 1: Component selection validation
|
||||
print("\n1. Testing component selection validation...")
|
||||
components = {
|
||||
'primary_components': [
|
||||
{
|
||||
'component': 'viewport.Model',
|
||||
'score': 95,
|
||||
'justification': 'Scrollable display for log content'
|
||||
},
|
||||
{
|
||||
'component': 'textinput.Model',
|
||||
'score': 90,
|
||||
'justification': 'Search query input'
|
||||
}
|
||||
]
|
||||
}
|
||||
requirements = {
|
||||
'features': ['display', 'search', 'scroll']
|
||||
}
|
||||
report = validator.validate_component_selection(components, requirements)
|
||||
print(f" {report.get_summary()}")
|
||||
assert not report.has_critical_issues(), "Should pass for valid components"
|
||||
print(" ✓ Component selection validated")
|
||||
|
||||
# Test 2: Architecture validation
|
||||
print("\n2. Testing architecture validation...")
|
||||
architecture = {
|
||||
'model_struct': 'type model struct {...}',
|
||||
'message_handlers': {
|
||||
'tea.KeyMsg': 'handle keyboard',
|
||||
'tea.WindowSizeMsg': 'handle resize'
|
||||
},
|
||||
'view_logic': 'func (m model) View() string {...}',
|
||||
'diagrams': {
|
||||
'component_hierarchy': '...'
|
||||
}
|
||||
}
|
||||
report = validator.validate_architecture(architecture)
|
||||
print(f" {report.get_summary()}")
|
||||
assert report.all_passed(), "Should pass for complete architecture"
|
||||
print(" ✓ Architecture validated")
|
||||
|
||||
# Test 3: Workflow validation
|
||||
print("\n3. Testing workflow validation...")
|
||||
workflow = {
|
||||
'phases': [
|
||||
{
|
||||
'name': 'Phase 1: Setup',
|
||||
'tasks': [
|
||||
{'task': 'Initialize project'},
|
||||
{'task': 'Install dependencies'}
|
||||
]
|
||||
},
|
||||
{
|
||||
'name': 'Phase 2: Core',
|
||||
'tasks': [
|
||||
{'task': 'Implement viewport'}
|
||||
]
|
||||
}
|
||||
],
|
||||
'testing_checkpoints': ['After Phase 1', 'After Phase 2'],
|
||||
'total_estimated_time': '2 hours'
|
||||
}
|
||||
report = validator.validate_workflow_completeness(workflow)
|
||||
print(f" {report.get_summary()}")
|
||||
assert report.all_passed(), "Should pass for complete workflow"
|
||||
print(" ✓ Workflow validated")
|
||||
|
||||
# Test 4: Complete design report validation
|
||||
print("\n4. Testing complete design report validation...")
|
||||
design_report = {
|
||||
'sections': {
|
||||
'requirements': {...},
|
||||
'components': {...},
|
||||
'patterns': {...},
|
||||
'architecture': {...},
|
||||
'workflow': {...}
|
||||
},
|
||||
'summary': 'TUI design for log viewer',
|
||||
'scaffolding': 'package main...',
|
||||
'next_steps': ['Step 1', 'Step 2']
|
||||
}
|
||||
report = validator.validate_design_report(design_report)
|
||||
print(f" {report.get_summary()}")
|
||||
assert report.all_passed(), "Should pass for complete report"
|
||||
print(" ✓ Design report validated")
|
||||
|
||||
# Test 5: Component fit check
|
||||
print("\n5. Testing component fit check...")
|
||||
assert validate_component_fit("viewport.Model", "scrollable log display")
|
||||
assert validate_component_fit("textinput.Model", "search query input")
|
||||
assert not validate_component_fit("spinner.Model", "text input field")
|
||||
print(" ✓ Component fit checks working")
|
||||
|
||||
print("\n✅ All tests passed!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
393
scripts/utils/validators/requirement_validator.py
Normal file
393
scripts/utils/validators/requirement_validator.py
Normal file
@@ -0,0 +1,393 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Requirement validators for Bubble Tea Designer.
|
||||
Validates user input and extracted requirements.
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ValidationLevel(Enum):
|
||||
"""Severity levels for validation results."""
|
||||
CRITICAL = "critical"
|
||||
WARNING = "warning"
|
||||
INFO = "info"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ValidationResult:
|
||||
"""Single validation check result."""
|
||||
check_name: str
|
||||
level: ValidationLevel
|
||||
passed: bool
|
||||
message: str
|
||||
details: Optional[Dict] = None
|
||||
|
||||
|
||||
class ValidationReport:
|
||||
"""Collection of validation results."""
|
||||
|
||||
def __init__(self):
|
||||
self.results: List[ValidationResult] = []
|
||||
|
||||
def add(self, result: ValidationResult):
|
||||
"""Add validation result."""
|
||||
self.results.append(result)
|
||||
|
||||
def has_critical_issues(self) -> bool:
|
||||
"""Check if any critical issues found."""
|
||||
return any(
|
||||
r.level == ValidationLevel.CRITICAL and not r.passed
|
||||
for r in self.results
|
||||
)
|
||||
|
||||
def all_passed(self) -> bool:
|
||||
"""Check if all validations passed."""
|
||||
return all(r.passed for r in self.results)
|
||||
|
||||
def get_warnings(self) -> List[str]:
|
||||
"""Get all warning messages."""
|
||||
return [
|
||||
r.message for r in self.results
|
||||
if r.level == ValidationLevel.WARNING and not r.passed
|
||||
]
|
||||
|
||||
def get_summary(self) -> str:
|
||||
"""Get summary of validation results."""
|
||||
total = len(self.results)
|
||||
passed = sum(1 for r in self.results if r.passed)
|
||||
critical = sum(
|
||||
1 for r in self.results
|
||||
if r.level == ValidationLevel.CRITICAL and not r.passed
|
||||
)
|
||||
|
||||
return (
|
||||
f"Validation: {passed}/{total} passed "
|
||||
f"({critical} critical issues)"
|
||||
)
|
||||
|
||||
def to_dict(self) -> Dict:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
'passed': self.all_passed(),
|
||||
'summary': self.get_summary(),
|
||||
'warnings': self.get_warnings(),
|
||||
'critical_issues': [
|
||||
r.message for r in self.results
|
||||
if r.level == ValidationLevel.CRITICAL and not r.passed
|
||||
],
|
||||
'all_results': [
|
||||
{
|
||||
'check': r.check_name,
|
||||
'level': r.level.value,
|
||||
'passed': r.passed,
|
||||
'message': r.message
|
||||
}
|
||||
for r in self.results
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class RequirementValidator:
|
||||
"""Validates TUI requirements and descriptions."""
|
||||
|
||||
def validate_description(self, description: str) -> ValidationReport:
|
||||
"""
|
||||
Validate user-provided description.
|
||||
|
||||
Args:
|
||||
description: Natural language TUI description
|
||||
|
||||
Returns:
|
||||
ValidationReport with results
|
||||
"""
|
||||
report = ValidationReport()
|
||||
|
||||
# Check 1: Not empty
|
||||
report.add(ValidationResult(
|
||||
check_name="not_empty",
|
||||
level=ValidationLevel.CRITICAL,
|
||||
passed=bool(description and description.strip()),
|
||||
message="Description is empty" if not description else "Description provided"
|
||||
))
|
||||
|
||||
if not description:
|
||||
return report
|
||||
|
||||
# Check 2: Minimum length (at least 10 words)
|
||||
words = description.split()
|
||||
min_words = 10
|
||||
has_min_length = len(words) >= min_words
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="minimum_length",
|
||||
level=ValidationLevel.WARNING,
|
||||
passed=has_min_length,
|
||||
message=f"Description has {len(words)} words (recommended: ≥{min_words})"
|
||||
))
|
||||
|
||||
# Check 3: Contains actionable verbs
|
||||
action_verbs = ['show', 'display', 'view', 'create', 'select', 'navigate',
|
||||
'edit', 'input', 'track', 'monitor', 'search', 'filter']
|
||||
has_action = any(verb in description.lower() for verb in action_verbs)
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="has_actions",
|
||||
level=ValidationLevel.WARNING,
|
||||
passed=has_action,
|
||||
message="Description contains action verbs" if has_action else
|
||||
"Consider adding action verbs (show, select, edit, etc.)"
|
||||
))
|
||||
|
||||
# Check 4: Contains data type mentions
|
||||
data_types = ['file', 'text', 'data', 'table', 'list', 'log', 'config',
|
||||
'message', 'package', 'item', 'entry']
|
||||
has_data = any(dtype in description.lower() for dtype in data_types)
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="has_data_types",
|
||||
level=ValidationLevel.INFO,
|
||||
passed=has_data,
|
||||
message="Data types mentioned" if has_data else
|
||||
"No explicit data types mentioned"
|
||||
))
|
||||
|
||||
return report
|
||||
|
||||
def validate_requirements(self, requirements: Dict) -> ValidationReport:
|
||||
"""
|
||||
Validate extracted requirements structure.
|
||||
|
||||
Args:
|
||||
requirements: Structured requirements dict
|
||||
|
||||
Returns:
|
||||
ValidationReport
|
||||
"""
|
||||
report = ValidationReport()
|
||||
|
||||
# Check 1: Has archetype
|
||||
has_archetype = 'archetype' in requirements and requirements['archetype']
|
||||
report.add(ValidationResult(
|
||||
check_name="has_archetype",
|
||||
level=ValidationLevel.CRITICAL,
|
||||
passed=has_archetype,
|
||||
message=f"TUI archetype: {requirements.get('archetype', 'MISSING')}"
|
||||
))
|
||||
|
||||
# Check 2: Has features
|
||||
features = requirements.get('features', [])
|
||||
has_features = len(features) > 0
|
||||
report.add(ValidationResult(
|
||||
check_name="has_features",
|
||||
level=ValidationLevel.CRITICAL,
|
||||
passed=has_features,
|
||||
message=f"Features identified: {len(features)}"
|
||||
))
|
||||
|
||||
# Check 3: Has interactions
|
||||
interactions = requirements.get('interactions', {})
|
||||
keyboard_interactions = interactions.get('keyboard', [])
|
||||
has_interactions = len(keyboard_interactions) > 0
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="has_interactions",
|
||||
level=ValidationLevel.WARNING,
|
||||
passed=has_interactions,
|
||||
message=f"Keyboard interactions: {len(keyboard_interactions)}"
|
||||
))
|
||||
|
||||
# Check 4: Has view specification
|
||||
views = requirements.get('views', '')
|
||||
has_views = bool(views)
|
||||
report.add(ValidationResult(
|
||||
check_name="has_view_spec",
|
||||
level=ValidationLevel.WARNING,
|
||||
passed=has_views,
|
||||
message=f"View type: {views or 'unspecified'}"
|
||||
))
|
||||
|
||||
# Check 5: Completeness (has all expected keys)
|
||||
expected_keys = ['archetype', 'features', 'interactions', 'data_types', 'views']
|
||||
missing_keys = set(expected_keys) - set(requirements.keys())
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="completeness",
|
||||
level=ValidationLevel.INFO,
|
||||
passed=len(missing_keys) == 0,
|
||||
message=f"Complete structure" if not missing_keys else
|
||||
f"Missing keys: {missing_keys}"
|
||||
))
|
||||
|
||||
return report
|
||||
|
||||
def suggest_clarifications(self, requirements: Dict) -> List[str]:
|
||||
"""
|
||||
Suggest clarifying questions based on incomplete requirements.
|
||||
|
||||
Args:
|
||||
requirements: Extracted requirements
|
||||
|
||||
Returns:
|
||||
List of clarifying questions to ask user
|
||||
"""
|
||||
questions = []
|
||||
|
||||
# Check if archetype is unclear
|
||||
if not requirements.get('archetype') or requirements['archetype'] == 'general':
|
||||
questions.append(
|
||||
"What type of TUI is this? (file manager, installer, dashboard, "
|
||||
"form, viewer, etc.)"
|
||||
)
|
||||
|
||||
# Check if features are vague
|
||||
features = requirements.get('features', [])
|
||||
if len(features) < 2:
|
||||
questions.append(
|
||||
"What are the main features/capabilities needed? "
|
||||
"(e.g., navigation, selection, editing, search, filtering)"
|
||||
)
|
||||
|
||||
# Check if data type is unspecified
|
||||
data_types = requirements.get('data_types', [])
|
||||
if not data_types:
|
||||
questions.append(
|
||||
"What type of data will the TUI display? "
|
||||
"(files, text, tabular data, logs, etc.)"
|
||||
)
|
||||
|
||||
# Check if interaction is unspecified
|
||||
interactions = requirements.get('interactions', {})
|
||||
if not interactions.get('keyboard') and not interactions.get('mouse'):
|
||||
questions.append(
|
||||
"How should users interact? Keyboard only, or mouse support needed?"
|
||||
)
|
||||
|
||||
# Check if view type is unspecified
|
||||
if not requirements.get('views'):
|
||||
questions.append(
|
||||
"Should this be single-view or multi-view? Need tabs or navigation?"
|
||||
)
|
||||
|
||||
return questions
|
||||
|
||||
|
||||
def validate_description_clarity(description: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Quick validation of description clarity.
|
||||
|
||||
Args:
|
||||
description: User description
|
||||
|
||||
Returns:
|
||||
Tuple of (is_clear, message)
|
||||
"""
|
||||
validator = RequirementValidator()
|
||||
report = validator.validate_description(description)
|
||||
|
||||
if report.has_critical_issues():
|
||||
return False, "Description has critical issues: " + report.get_summary()
|
||||
|
||||
warnings = report.get_warnings()
|
||||
if warnings:
|
||||
return True, "Description OK with suggestions: " + "; ".join(warnings)
|
||||
|
||||
return True, "Description is clear"
|
||||
|
||||
|
||||
def validate_requirements_completeness(requirements: Dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
Quick validation of requirements completeness.
|
||||
|
||||
Args:
|
||||
requirements: Extracted requirements dict
|
||||
|
||||
Returns:
|
||||
Tuple of (is_complete, message)
|
||||
"""
|
||||
validator = RequirementValidator()
|
||||
report = validator.validate_requirements(requirements)
|
||||
|
||||
if report.has_critical_issues():
|
||||
return False, "Requirements incomplete: " + report.get_summary()
|
||||
|
||||
warnings = report.get_warnings()
|
||||
if warnings:
|
||||
return True, "Requirements OK with warnings: " + "; ".join(warnings)
|
||||
|
||||
return True, "Requirements complete"
|
||||
|
||||
|
||||
def main():
|
||||
"""Test requirement validator."""
|
||||
print("Testing Requirement Validator\n" + "=" * 50)
|
||||
|
||||
validator = RequirementValidator()
|
||||
|
||||
# Test 1: Empty description
|
||||
print("\n1. Testing empty description...")
|
||||
report = validator.validate_description("")
|
||||
print(f" {report.get_summary()}")
|
||||
assert report.has_critical_issues(), "Should fail for empty description"
|
||||
print(" ✓ Correctly detected empty description")
|
||||
|
||||
# Test 2: Good description
|
||||
print("\n2. Testing good description...")
|
||||
good_desc = "Create a file manager TUI with three-column view showing parent directory, current directory, and file preview"
|
||||
report = validator.validate_description(good_desc)
|
||||
print(f" {report.get_summary()}")
|
||||
print(" ✓ Good description validated")
|
||||
|
||||
# Test 3: Vague description
|
||||
print("\n3. Testing vague description...")
|
||||
vague_desc = "Build a TUI"
|
||||
report = validator.validate_description(vague_desc)
|
||||
print(f" {report.get_summary()}")
|
||||
warnings = report.get_warnings()
|
||||
if warnings:
|
||||
print(f" Warnings: {warnings}")
|
||||
print(" ✓ Vague description detected")
|
||||
|
||||
# Test 4: Requirements validation
|
||||
print("\n4. Testing requirements validation...")
|
||||
requirements = {
|
||||
'archetype': 'file-manager',
|
||||
'features': ['navigation', 'selection', 'preview'],
|
||||
'interactions': {
|
||||
'keyboard': ['arrows', 'enter', 'backspace'],
|
||||
'mouse': []
|
||||
},
|
||||
'data_types': ['files', 'directories'],
|
||||
'views': 'multi'
|
||||
}
|
||||
report = validator.validate_requirements(requirements)
|
||||
print(f" {report.get_summary()}")
|
||||
assert report.all_passed(), "Should pass for complete requirements"
|
||||
print(" ✓ Complete requirements validated")
|
||||
|
||||
# Test 5: Incomplete requirements
|
||||
print("\n5. Testing incomplete requirements...")
|
||||
incomplete = {
|
||||
'archetype': '',
|
||||
'features': []
|
||||
}
|
||||
report = validator.validate_requirements(incomplete)
|
||||
print(f" {report.get_summary()}")
|
||||
assert report.has_critical_issues(), "Should fail for incomplete requirements"
|
||||
print(" ✓ Incomplete requirements detected")
|
||||
|
||||
# Test 6: Clarification suggestions
|
||||
print("\n6. Testing clarification suggestions...")
|
||||
questions = validator.suggest_clarifications(incomplete)
|
||||
print(f" Generated {len(questions)} clarifying questions:")
|
||||
for i, q in enumerate(questions, 1):
|
||||
print(f" {i}. {q}")
|
||||
print(" ✓ Clarifications generated")
|
||||
|
||||
print("\n✅ All tests passed!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user