from .models import RequirementNode, NodeType from .schemas import QualityReport def validate_requirement(node: RequirementNode, children: list[RequirementNode]) -> QualityReport: """Run quality gates on a requirement node. Quality gates: 1. Traceable — auto-calculated: has parent (non-pillar) + description 2. Measurable — user toggle (manually set by the user) 3. Consistent — user toggle (manually set by the user) 4. Single-purpose — user toggle (manually set by the user) """ errors: list[str] = [] warnings: list[str] = [] # 1. Traceable — auto-calculated traceable = True if node.node_type != NodeType.pillar: if not node.parent_id: errors.append("Non-pillar node must have a parent (traceability)") traceable = False if not node.description: warnings.append("Missing description — hard to trace how this serves parent") else: if not node.description: warnings.append("Pillar should have a description explaining its purpose") # 2. Measurable — user toggle, but forced false without test_spec measurable = bool(node.measurable) if not node.acceptance_criteria: warnings.append("Missing acceptance criteria") if not node.test_spec: errors.append("Missing test specification (GIVEN/WHEN/THEN) — cannot be measurable") measurable = False # 3. Consistent — user toggle consistent = bool(node.consistent) # 4. Single-purpose — user toggle, but warn on heuristic single_purpose = bool(node.single_purpose) title_lower = node.title.lower() if " and " in title_lower and " and " not in title_lower.split("(")[0]: warnings.append("Title contains 'and' — may serve multiple purposes") return QualityReport( traceable=traceable, measurable=measurable, consistent=consistent, single_purpose=single_purpose, errors=errors, warnings=warnings, valid=len(errors) == 0, )