101 lines
3.8 KiB
Python
101 lines
3.8 KiB
Python
# 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()
|