Files
company-haness/.claude/tests/test_company_context_lint.py

113 lines
7.8 KiB
Python

#!/usr/bin/env python3
"""lint_company_context.py 단위테스트. standalone(no pytest). exit 0 = all pass."""
import os, sys, tempfile
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
HOOKS = os.path.join(ROOT, ".claude", "hooks")
sys.path.insert(0, HOOKS)
os.environ["CLAUDE_PROJECT_DIR"] = ROOT
import lint_company_context as L # noqa: E402
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 _tmp(text):
fd, p = tempfile.mkstemp(suffix=".yaml"); os.write(fd, text.encode()); os.close(fd); return p
# 공식 파일에 status: bootstrap 이면 Hard Fail
p = _tmp("schema-version: 2\nstatus: bootstrap\ncompany: {facts: [], strategic-decisions: [], hypotheses: [], validation-state: {stage: pre-traction, validated: [], open: [], refuted: []}}\nprojects: []\n")
hf, wn = L.lint_file(p, is_candidate=False)
check("official status=bootstrap -> hard fail", any("bootstrap" in h or "status" in h for h in hf))
# 공식 파일에 candidate-status 잔존 -> Hard Fail
p = _tmp("schema-version: 2\nstatus: provisional\ncandidate-status: bootstrap\ncompany: {facts: [], strategic-decisions: [{id: DEC-1, statement: x, decision-type: t, accepted-by: HUMAN-001, accepted-at: '2026-07-12', source-decision-id: VD-1, supporting-evidence: []}], hypotheses: [], validation-state: {stage: pre-traction, validated: [], open: [], refuted: []}}\nprojects: []\n")
hf, wn = L.lint_file(p, is_candidate=False)
check("official candidate-status -> hard fail", any("candidate-status" in h for h in hf))
# 허용된 status 3종은 통과(구조 정상 최소본)
for st in ("template", "provisional", "operating"):
if st == "template":
body = "company: {facts: [], strategic-decisions: [], hypotheses: [], validation-state: {stage: pre-traction, validated: [], open: [], refuted: []}}"
elif st == "provisional":
body = "company: {facts: [], strategic-decisions: [{id: DEC-1, statement: x, decision-type: t, accepted-by: HUMAN-001, accepted-at: '2026-07-12', source-decision-id: VD-1, supporting-evidence: []}], hypotheses: [], validation-state: {stage: pre-traction, validated: [], open: [], refuted: []}}"
else: # operating
body = "company: {facts: [], strategic-decisions: [{id: DEC-1, statement: x, decision-type: t, accepted-by: HUMAN-001, accepted-at: '2026-07-12', source-decision-id: VD-1, supporting-evidence: []}], hypotheses: [], validation-state: {stage: operating, validated: [], open: [], refuted: []}}"
p = _tmp(f"schema-version: 2\nstatus: {st}\n{body}\nprojects: []\n")
hf, wn = L.lint_file(p, is_candidate=False)
check(f"official status={st} -> no hard fail", hf == [])
# candidate 파일: candidate-status: bootstrap + 유효 status -> hard fail 없음
p = _tmp("schema-version: 2\nstatus: provisional\ncandidate-status: bootstrap\ncompany: {facts: [], strategic-decisions: [{id: DEC-1, statement: x, decision-type: t, accepted-by: HUMAN-001, accepted-at: '2026-07-12', source-decision-id: VD-1, supporting-evidence: []}], hypotheses: [], validation-state: {stage: pre-traction, validated: [], open: [], refuted: []}}\nprojects: []\n")
hf, wn = L.lint_file(p, is_candidate=True)
check("candidate w/ candidate-status:bootstrap -> no hard fail", hf == [])
# candidate 파일인데 candidate-status 누락 -> hard fail
p = _tmp("schema-version: 2\nstatus: provisional\ncompany: {facts: [], strategic-decisions: [], hypotheses: [], validation-state: {stage: pre-traction, validated: [], open: [], refuted: []}}\nprojects: []\n")
hf, wn = L.lint_file(p, is_candidate=True)
check("candidate w/o candidate-status -> hard fail", any("candidate-status" in h for h in hf))
# 비-dict 루트(bare list) -> hard fail(예외 아님)
p = _tmp("- a\n- b\n")
hf, wn = L.lint_file(p, is_candidate=False)
check("non-dict root -> hard fail not exception", any("매핑" in h for h in hf))
# Step 1: 항목 구조 Hard Fail 테스트 케이스 (Task 5)
def _doc(status, facts="[]", decs="[]", hyps="[]"):
return (f"schema-version: 2\nstatus: {status}\ncompany:\n facts: {facts}\n"
f" strategic-decisions: {decs}\n hypotheses: {hyps}\n"
f" validation-state: {{stage: pre-traction, validated: [], open: [], refuted: []}}\nprojects: []\n")
# fact provenance 누락 -> hard fail
p = _tmp(_doc("operating", facts="[{id: FACT-1, statement: x, category: c, status: active}]"))
hf, _ = L.lint_file(p); check("fact w/o provenance -> hard", any("provenance" in h for h in hf))
# 중복 id -> hard fail
p = _tmp(_doc("operating",
facts="[{id: X, statement: a, category: c, provenance: [{source-uri: org-os/01-company/founder-context.yaml, grade: E2}], status: active}]",
hyps="[{id: X, statement: b, hypothesis-type: wtp, confidence: Med, validation-status: untested, evidence: [], falsification-criteria: [z]}]"))
hf, _ = L.lint_file(p); check("dup id across blocks -> hard", any("중복" in h or "dup" in h.lower() for h in hf))
# decision accepted-by 누락 -> hard fail
p = _tmp(_doc("provisional", decs="[{id: DEC-1, statement: x, decision-type: t, accepted-at: '2026-07-12', source-decision-id: VD-1, supporting-evidence: []}]"))
hf, _ = L.lint_file(p); check("decision w/o accepted-by -> hard", any("accepted-by" in h for h in hf))
# hypothesis falsification-criteria 누락 -> hard fail
p = _tmp(_doc("operating", hyps="[{id: H1, statement: x, hypothesis-type: wtp, confidence: Med, validation-status: untested, evidence: []}]"))
hf, _ = L.lint_file(p); check("hypothesis w/o falsification -> hard", any("falsification" in h for h in hf))
# provisional 인데 human 승인 decision 없음 -> hard fail
p = _tmp(_doc("provisional"))
hf, _ = L.lint_file(p); check("provisional w/o human decision -> hard", any("human" in h.lower() and "decision" in h.lower() for h in hf))
# 존재하지 않는 evidence 경로 -> hard fail
p = _tmp(_doc("operating", facts="[{id: F1, statement: x, category: c, provenance: [{source-uri: org-os/01-company/NOPE.yaml, grade: E2}], status: active}]"))
hf, _ = L.lint_file(p); check("nonexistent evidence path -> hard", any("NOPE" in h or "존재" in h for h in hf))
# hypothesis-id 를 decision 의 source-decision-id 로 사용 -> hard fail
p = _tmp(_doc("provisional",
decs="[{id: DEC-1, statement: x, decision-type: t, accepted-by: HUMAN-001, accepted-at: '2026-07-12', source-decision-id: H1, supporting-evidence: []}]",
hyps="[{id: H1, statement: y, hypothesis-type: wtp, confidence: Med, validation-status: untested, evidence: [], falsification-criteria: [z]}]"))
hf, _ = L.lint_file(p); check("hypothesis-id as decision source -> hard", any("hypothesis id" in h for h in hf))
# operating 인데 validation-state.stage != operating -> hard fail
p = _tmp(_doc("operating",
decs="[{id: DEC-1, statement: x, decision-type: t, accepted-by: HUMAN-001, accepted-at: '2026-07-12', source-decision-id: VD-1, supporting-evidence: []}]"))
hf, _ = L.lint_file(p); check("operating w/ stage!=operating -> hard", any("operating" in h and "stage" in h for h in hf))
# Step 1: 의미 오분류 Warning 테스트 케이스 (Task 6)
# fact 문장이 추정 표현 -> warning(hard 아님)
p = _tmp(_doc("template", facts="[{id: F1, statement: '고객은 지불할 것으로 예상된다', category: c, provenance: [{source-uri: org-os/01-company/founder-context.yaml, grade: E2}], status: active}]"))
hf, wn = L.lint_file(p)
check("estimative fact -> warning not hard", hf == [] and any("추정" in w or "예상" in w for w in wn))
# Step 1: 구 어휘 파일 migrate -> 신 어휘
p = _tmp("status: demo\ncompany:\n name: X\n mission: Y\nprojects: []\n")
rc = L.migrate(p)
import yaml as _y
d = _y.safe_load(open(p))
check("migrate demo->template + schema v2", rc == 0 and d["status"] == "template" and d.get("schema-version") == 2 and "facts" in d["company"])
sys.exit(1 if failed else 0)