Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:47:30 +08:00
commit 48d6099939
30 changed files with 5747 additions and 0 deletions

View 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'
]

View 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()

View 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()