93 lines
3.8 KiB
Python
93 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
||
"""P1-A: lens 다양성 ≠ domain/sub-specialty 커버리지 (리뷰 finding #12).
|
||
|
||
lens_cap이 2축(lens 다양성 × sub-specialty 커버리지)으로 판정하는지 검증한다.
|
||
- 아키텍트 7명(전부 LENS-TECH지만 distinct sub-specialty)이 standard에서 통과(정확한 회귀).
|
||
- 같은 role 2회는 여전히 위반(같은/미분화 sub-specialty 중복).
|
||
- tier distinct-sub-specialty 상한 초과는 위반(fan-out 폭 가드).
|
||
- heavy는 permissive.
|
||
- case-insensitive (registry는 UPPER, 호출은 어느 case든).
|
||
|
||
실행: CLAUDE_PROJECT_DIR="$PWD" python3 .claude/tests/test_p1_lens.py
|
||
"""
|
||
import os
|
||
import subprocess
|
||
import sys
|
||
|
||
ROOT = os.environ.get("CLAUDE_PROJECT_DIR") or os.path.dirname(
|
||
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||
)
|
||
HOOK = os.path.join(ROOT, ".claude", "hooks", "lens_cap.py")
|
||
|
||
_fail = 0
|
||
|
||
|
||
def check(name, cond):
|
||
global _fail
|
||
print(("ok " if cond else "FAIL ") + "- " + name)
|
||
if not cond:
|
||
_fail += 1
|
||
|
||
|
||
def rc(tier, roles):
|
||
return subprocess.run(
|
||
[sys.executable, HOOK, "--tier", tier, "--roles", roles],
|
||
capture_output=True, text=True,
|
||
).returncode
|
||
|
||
|
||
print("== P1-A lens_cap 2축(lens × sub-specialty) ==")
|
||
|
||
ARCH7 = "arch-ea,arch-solution,arch-app,arch-tech,arch-it,arch-sysanalyst,arch-swat"
|
||
|
||
# 1) 정확한 회귀: 아키텍트 7명(전부 LENS-TECH, 서로 다른 sub-specialty) → standard 통과.
|
||
check("7 distinct architects @ standard -> 0 (finding #12 회귀, was 2)",
|
||
rc("standard", ARCH7) == 0)
|
||
# case-insensitive: 대문자 role-id도 동일하게 통과.
|
||
check("7 distinct architects (UPPER) @ standard -> 0 (case-insensitive)",
|
||
rc("standard", ARCH7.upper()) == 0)
|
||
|
||
# 2) 같은 role 2회 → 같은 sub-specialty 중복 → 여전히 위반.
|
||
check("same role twice @ standard -> 2 (undifferentiated duplicate)",
|
||
rc("standard", "arch-app,arch-app") == 2)
|
||
check("distinct pair + one dup role @ standard -> 2",
|
||
rc("standard", "arch-app,arch-tech,arch-app") == 2)
|
||
|
||
# 3) tier distinct-sub-specialty 상한 초과 → 위반 (light 상한=3, standard 상한=8).
|
||
check("3 distinct sub-specialties @ light (cap 3) -> 0 (경계 이내)",
|
||
rc("light", "arch-ea,arch-solution,arch-app") == 0)
|
||
check("4 distinct sub-specialties @ light (cap 3) -> 2 (상한 초과)",
|
||
rc("light", "arch-ea,arch-solution,arch-app,arch-tech") == 2)
|
||
check("9 distinct LENS-TECH sub-specialties @ standard (cap 8) -> 2",
|
||
rc("standard", ARCH7 + ",arch-data,data-engineer") == 2)
|
||
|
||
# 4) heavy → permissive(무엇이든 통과).
|
||
check("heavy same role twice -> 0 (permissive)",
|
||
rc("heavy", "arch-app,arch-app") == 0)
|
||
check("heavy 9 distinct LENS-TECH -> 0 (permissive)",
|
||
rc("heavy", ARCH7 + ",arch-data,data-engineer") == 0)
|
||
|
||
# 5) 서로 다른 lens는 서로 영향 없음.
|
||
check("distinct lenses (PROD-PM + UX-RESEARCHER) @ standard -> 0",
|
||
rc("standard", "prod-pm,ux-researcher") == 0)
|
||
|
||
# 6) 같은 lens라도 서로 다른 sub-specialty 2명은 통과(진짜 중복 아님).
|
||
# (이 케이스가 test_enforcement.py의 'standard dup-lens SEC-ENGINEER,SEC-APPSEC->2'를 뒤집는다: 이제 0.)
|
||
check("2 distinct security sub-specialties @ standard -> 0 (진짜 중복 아님)",
|
||
rc("standard", "sec-engineer,sec-appsec") == 0)
|
||
check("2 distinct product sub-specialties (PM vs TPO) @ standard -> 0",
|
||
rc("standard", "prod-pm,prod-tpo") == 0)
|
||
check("2 distinct design sub-specialties (product vs platform) @ standard -> 0",
|
||
rc("standard", "des-prod,des-platform") == 0)
|
||
|
||
# 7) 미등록 role은 lens 판별 불가·무시 → 위반 아님(위반 전용은 아님).
|
||
check("unknown role ignored @ standard -> 0",
|
||
rc("standard", "not-a-real-role,arch-app") == 0)
|
||
|
||
print()
|
||
if _fail:
|
||
print(f"FAILED: {_fail}")
|
||
sys.exit(1)
|
||
print("ALL PASS")
|
||
sys.exit(0)
|