# IR 验证器:Schema 校验 + Principles 规则检查 import json import logging import jsonschema import yaml from server.config import settings from server.services.ir_engine.generator import ir_generator logger = logging.getLogger(__name__) class IRValidator: """Validate IR against skill schema and soul principles.""" async def validate(self, ir_yaml: str, skill_name: str = "default") -> dict: """Validate IR YAML content and return {valid, issues}.""" issues = [] # 1. Parse YAML try: ir_data = yaml.safe_load(ir_yaml) except yaml.YAMLError as e: return {"valid": False, "issues": [{ "severity": "error", "message": f"YAML 解析失败: {e}", }]} if not isinstance(ir_data, dict): return {"valid": False, "issues": [{ "severity": "error", "message": "IR 内容必须是 YAML 字典/对象", }]} # 2. Schema validation skill = ir_generator.load_skill(skill_name) schema = skill.get("schema", {}) if schema: try: jsonschema.validate(instance=ir_data, schema=schema) except jsonschema.ValidationError as e: issues.append({ "severity": "error", "message": f"Schema 校验失败: {e.message}", "location": ".".join(str(p) for p in e.absolute_path), }) # 3. Principles validation principles = self._load_principles() for principle in principles: rule = principle.get("rule", "") pid = principle.get("id", "") # Check completeness: each feature must have both positive and negative paths if pid == "completeness": features = ir_data.get("features", []) if not features: issues.append({ "severity": "warning", "message": f"原则 '{pid}': 未检测到功能点,无法验证完整性", }) # Check traceability: features should have descriptions if pid == "traceability": features = ir_data.get("features", []) for i, feat in enumerate(features): if not feat.get("description"): issues.append({ "severity": "warning", "message": f"原则 '{pid}': 功能点 {feat.get('feature_name', f'#{i}')} 缺少描述", }) # Check no-hallucination: warn about potentially vague features if pid == "no-hallucination": features = ir_data.get("features", []) for feat in features: desc = feat.get("description", "") if "应该" in desc or "可能" in desc or "猜测" in desc: issues.append({ "severity": "warning", "message": f"原则 '{pid}': 功能点 '{feat.get('feature_name', '')}' 描述包含不确定措辞", }) valid = len([i for i in issues if i["severity"] == "error"]) == 0 logger.info("IR validation: valid=%s, issues=%d", valid, len(issues)) return {"valid": valid, "issues": issues} def _load_principles(self) -> list[dict]: """Load principles from .zeekerwatchmen/soul/principles.yaml.""" principles_path = settings.SOUL_DIR / "principles.yaml" if not principles_path.exists(): return [] with open(principles_path, "r", encoding="utf-8") as f: data = yaml.safe_load(f) return data.get("principles", []) if data else [] ir_validator = IRValidator()