92 lines
3.8 KiB
Python
92 lines
3.8 KiB
Python
#!/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)
|