#!/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)