sync: update all skills from latest workspace code
CI / test (push) Successful in 8s

doc_parser_skill:
- New: verify_flowchart.py (flowchart validation)
- Updated: LLM.py (multi-provider: DeepSeek + DashScope)
- Updated: image_parser.py (logic tree support, external prompts)
- Updated: SKILL.md, prompts/image_prompt.md

conflict_detection_skill:
- Updated: LLM.py (multi-provider sync)
- Updated: detect_conflicts.py (logic tree text conversion)

ir_generation_skill:
- Replaced old scripts/LLM.py + ir_generator.py with standalone project
- New: main.py, config.py, step1-3_*.py, ensemble_merge.py
- New: prompts/, tests/ subdirectories

tests:
- New: acceptance/ test suite with schema validation
- Fixed: conftest no longer globally skips non-acceptance tests
- Updated: test_sample.py for new ir_generation structure

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-30 22:45:08 +08:00
parent db64df2da1
commit fec4c09ee0
35 changed files with 8021 additions and 530 deletions
@@ -0,0 +1,152 @@
"""
Tests for Stage 2.5 (Branch Coverage Auto-Completion).
Validates:
- Path enumeration exists and is non-empty
- Auto-complete fragments have valid structure
- No duplicate unit_ids in autocomplete fragments
- Path coverage improved after autocomplete (if applicable)
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import config
PASS = "[PASS]"
FAIL = "[FAIL]"
WARN = "[WARN]"
def load_path_enumeration():
"""Load path_enumeration.json."""
try:
return config.load_json(config.PATH_ENUM_JSON)
except FileNotFoundError:
print(f"{FAIL} path_enumeration.json 未找到: {config.PATH_ENUM_JSON}")
print(" 请先运行 step2_5_branch_coverage.py")
sys.exit(1)
def load_autocomplete_fragments():
"""Load ir_autocomplete_fragments.json, or return [] if absent."""
path = config.IR_AUTOCOMPLETE_FRAGMENTS_JSON
if not Path(path).exists():
return None
return config.load_json(path)
def check_path_enumeration(data: dict) -> list[str]:
"""Check path enumeration has valid structure."""
errors = []
paths = data.get("logic_tree_paths", {})
if not paths:
errors.append("logic_tree_paths 为空")
total = data.get("total_paths", 0)
if total <= 0:
errors.append(f"total_paths = {total}, 期望 > 0")
for image_id, image_paths in paths.items():
if not image_paths:
errors.append(f"{image_id}: 路径列表为空")
continue
for i, p in enumerate(image_paths):
if not p.get("path_id"):
errors.append(f"{image_id}[{i}]: 缺少 path_id")
if not p.get("image_id"):
errors.append(f"{image_id}[{i}]: 缺少 image_id")
if not p.get("node_ids"):
errors.append(f"{image_id}[{i}]: 缺少 node_ids")
return errors
def check_autocomplete_fragments(fragments: list[dict] | None) -> list[str]:
"""Check auto-complete fragments have valid structure."""
if fragments is None:
return ["ir_autocomplete_fragments.json 未生成 (可能无需补全)"]
errors = []
seen_unit_ids = set()
for frag in fragments:
uid = frag.get("unit_id", "")
if not uid:
errors.append("fragment 缺少 unit_id")
continue
if uid in seen_unit_ids:
errors.append(f"unit_id '{uid}' 重复")
seen_unit_ids.add(uid)
if not frag.get("auto_generated"):
errors.append(f"{uid}: auto_generated 应为 true")
rules = frag.get("rules", [])
for j, rule in enumerate(rules):
rid = rule.get("rule_id", f"rule[{j}]")
if not rule.get("path"):
errors.append(f"{rid}: path 字段缺失")
precond = rule.get("precondition", {})
if not precond.get("geographic_scope"):
errors.append(f"{rid}: precondition.geographic_scope 缺失")
return errors
def run_all_tests():
print("=" * 60)
print("Step 2.5 自检测试")
print("=" * 60)
all_errors = []
# Test 1: Path enumeration exists
try:
path_data = load_path_enumeration()
except SystemExit:
return False
errors = check_path_enumeration(path_data)
if errors:
print(f"\n{FAIL} 路径枚举检查: {len(errors)} 个错误")
for e in errors:
print(f" - {e}")
all_errors.extend(errors)
else:
total = path_data.get("total_paths", 0)
n_images = len(path_data.get("logic_tree_paths", {}))
print(f"\n{PASS} 路径枚举检查: {total} 条路径, {n_images} 个逻辑树")
# Test 2: Auto-complete fragments
fragments = load_autocomplete_fragments()
errors = check_autocomplete_fragments(fragments)
if fragments is None:
print(f"\n{WARN} 自动补全片段: 未生成 (可能所有路径已覆盖)")
elif errors:
print(f"\n{FAIL} 自动补全片段检查: {len(errors)} 个错误")
for e in errors[:10]:
print(f" - {e}")
all_errors.extend(errors)
else:
auto_rules = sum(len(f.get("rules", [])) for f in fragments)
print(f"\n{PASS} 自动补全片段检查: "
f"{len(fragments)} 个片段, {auto_rules} 条规则")
# Summary
print(f"\n{'='*60}")
total_failures = len(all_errors)
if total_failures == 0:
print(f"{PASS} 所有测试通过!")
else:
print(f"{FAIL} 测试失败: {total_failures} 个错误")
return total_failures == 0
if __name__ == "__main__":
success = run_all_tests()
sys.exit(0 if success else 1)