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