#!/usr/bin/env python3 """P1-F (#8) tests: implementation loop is a first-class contract + proportional governance (light path) in build.md, embedded in FAM-ENG-* agent bodies, and a reusable build-loop skill exists. Standalone; exit 0 = all pass. Does NOT edit or import test_enforcement.py. """ import os import re import sys ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) AG = os.path.join(ROOT, ".claude", "agents") passed, failed = 0, 0 def check(name, ok): global passed, failed if ok: passed += 1 print(f" PASS {name}") else: failed += 1 print(f" FAIL {name}") def read(*p): with open(os.path.join(ROOT, *p), encoding="utf-8") as f: return f.read() # 구현 루프의 7단계 마커(순서 무관하게 존재 검증) LOOP_STEPS = ["inspect", "targeted verify", "broader verify"] print("== build.md: implementation loop is first-class ==") _build = read(".claude", "commands", "build.md") check("build.md has explicit implementation loop steps", all(s in _build for s in LOOP_STEPS) and "smallest safe change" in _build.lower() and "구현 루프" in _build) check("build.md loop names inspect-own-diff + honest report (verified vs not run)", "diff" in _build and ("실행하지 않은 것" in _build or "실행하지 않았는지" in _build)) check("build.md references reusable build-loop skill", "build-loop" in _build) print("== build.md: proportional governance (light path for simple changes) ==") # 단순 변경(버그픽스/문서/ops/작은 수정)에 full PRD/API-contract/data-model/threat-model을 일괄 요구하지 않는 경로 check("build.md has an explicit light/simple-change path", ("light path" in _build or "light 경로" in _build) and "단순 변경" in _build and "버그픽스" in _build) check("build.md light path does NOT require full design paperwork blanket", ("불요" in _build or "일괄" in _build) and "PRD" in _build and "위협모델" in _build) check("build.md scales paperwork with risk/tier", ("비례" in _build) and ("risk" in _build.lower() or "위험도" in _build) and ("tier" in _build.lower())) check("build.md keeps 'design Accepted before implementation' for substantial work", ("substantial" in _build.lower()) and "Accepted" in _build) print("== selected concrete ENG agent bodies carry the implementation loop ==") for fam in ("eng-be", "eng-fe", "eng-desktop"): body = "" p = os.path.join(AG, fam + ".md") if os.path.exists(p): with open(p, encoding="utf-8") as f: body = f.read() check(f"{fam}.md carries implementation-loop steps", "구현 루프" in body and all(s in body for s in LOOP_STEPS) and "호출부" in body) print("== build-loop skill exists with valid frontmatter ==") _skill_path = os.path.join(ROOT, ".claude", "skills", "build-loop", "SKILL.md") check("build-loop SKILL.md exists", os.path.exists(_skill_path)) _skill = read(".claude", "skills", "build-loop", "SKILL.md") if os.path.exists(_skill_path) else "" _fm = re.match(r"^---\n(.*?)\n---\n", _skill, re.DOTALL) check("build-loop SKILL.md has frontmatter block", bool(_fm)) _fmtext = _fm.group(1) if _fm else "" check("build-loop frontmatter has name: build-loop", bool(re.search(r"^name:\s*build-loop\s*$", _fmtext, re.MULTILINE))) check("build-loop frontmatter has non-empty description", bool(re.search(r"^description:\s*\S", _fmtext, re.MULTILINE))) check("build-loop skill body documents the 7-step loop + proportional gate", all(s in _skill for s in LOOP_STEPS) and "smallest safe change" in _skill.lower() and ("proportional" in _skill.lower() or "비례" in _skill)) print(f"\n{passed} passed, {failed} failed") sys.exit(1 if failed else 0)