# P1 — Company / Venture Bootstrap Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 회사를 정의하기 전에 회사 문맥을 요구하는 순환의존을 끊는다 — `founder-context`를 입력으로 하는 별도 `venture-bootstrap` plan이 `company-context.yaml (status: provisional)`을 산출하고, 제품 cascade는 그것을 입력으로 소비한다. **Architecture:** 5개 phase. (A) 데이터/계약 SoT, (B) `lint_company_context.py` 파일 린터, (C) state_engine에 `venture-bootstrap` plan·6 predicate, (D) 강제기 확장(validate_report·acceptance_log·commit_company_context·guard_tools), (E) 커맨드·문서·배선. 각 phase는 독립적으로 테스트 가능한 증분을 낸다. 모든 상태 전이는 OPS-ORCH가 집행하며, 공식 `company-context.yaml`은 candidate→원자적 교체로만 갱신된다. **Tech Stack:** Python 3(표준 라이브러리 + `pyyaml`), 기존 하네스 hook 규약. 테스트는 pytest가 아니라 저장소 표준인 **standalone `check(name, ok)` 하니스**(`sys.exit(1 if failed else 0)`), `run_all.py`가 CI 진입점. **Spec:** `docs/superpowers/specs/2026-07-12-p1-company-venture-bootstrap-design.md` (모든 § 참조는 이 스펙). ## Global Constraints - **워크스페이스 필수:** 모든 hook/test 실행은 `CLAUDE_PROJECT_DIR="$PWD"` + `ORGOS_WORKSPACE=` 명시(미설정 시 `_workspace.py`가 `WorkspaceNotSetError`). 테스트는 격리 fixture 워크스페이스 사용. - **CI 게이트:** 매 phase 종료 시 `CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=_sandbox python3 .claude/tests/run_all.py` **green 유지**(doctor + lint_refs + 모든 test_*.py). - **테스트 규약:** pytest 금지. 새 테스트 파일은 `test_state_engine.py` 스켈레톤 복제 — `check(name, ok)`/`passed,failed` 카운터/`sys.exit(1 if failed else 0)`, 격리 워크스페이스 `FIX/-ws`. - **신뢰경계:** 에이전트/worker는 `state_engine.py transition`·원장·공식 `company-context.yaml`을 직접 쓰지 못한다(guard_tools). venture-bootstrap 모든 전이 `allowed-by: [OPS-ORCH]` 단독(§8.2). - **공식 status 3-상태:** 공식 `company-context.yaml` `status ∈ {template, provisional, operating}`. `bootstrap`은 공식 status 아님 — candidate 파일의 `candidate-status: bootstrap` + workflow stage `company-context-commit`로만 표현(§7.3). - **불변식:** 생성된 `.claude/agents/*.md`는 직접 편집 금지(role 변경은 `gen_agents.py` 재실행). 보고서는 불변(new_report). 커밋은 각 task 끝에서. - **증거 등급:** `fact/decision/hypothesis`(정보 종류)와 `E1~E5`(증거 강도)는 독립 — 항목별 provenance(§7.1). 회사 문맥 인용 상한은 **항목 단위**(전체 보고서 강등 금지, §9.1). - **커밋 트레일러:** 각 커밋 메시지 끝에 `Co-Authored-By: Claude Opus 4.8 (1M context) `. --- ## File Structure **신규 생성** - `org-os/01-company/founder-context.yaml` — 사람 입력(창업자 제약). `status: template|filled`. - `org-os/06-agent-work/venture-option-spec.yaml` — opportunity-cluster·venture-option·validation-result 스키마. - `org-os/06-agent-work/venture-validation-map.yaml` — 9-gate 역할 매핑 + opportunity-discovery 역할. - `.claude/hooks/lint_company_context.py` — company-context 파일 린터(Hard Fail + Warning + `--migrate`). - `.claude/hooks/commit_company_context.py` — candidate→공식 원자적 commit(trusted CLI). - `.claude/commands/venture-validate.md`, `.claude/commands/company-bootstrap.md` — 신규 커맨드. - `.claude/tests/test_company_context_lint.py` — 린터 단위테스트. - `.claude/tests/test_venture_bootstrap.py` — plan/predicate/commit 단위테스트. **수정** - `org-os/01-company/company-context.yaml` — schema-version 2 재구조화(facts/decisions/hypotheses, 3-상태). - `org-os/00-role-registry/state-transition-rules.yaml` — venture-bootstrap 전이 + condition-catalog. - `org-os/06-agent-work/execution-plans.yaml` — `venture-bootstrap` plan. - `.claude/hooks/state_engine.py` — 6 predicate + facts + `_PROTECTED_FACTS` + `company-context-ready` guard 서브커맨드. - `.claude/hooks/validate_report.py` — status 어휘(operating) + read-compat + hypothesis anchor ceiling. - `.claude/hooks/acceptance_log.py` + `.claude/schemas/acceptance-event.schema.json` — 선택 `report-sha256`. - `.claude/hooks/guard_tools.py` — 공식 company-context.yaml 직접쓰기 보호. - `.claude/hooks/doctor.py` — 신규 아티팩트·plan 배선 점검. - `.claude/commands/ceo-intake.md` — `--plan venture-bootstrap` + founder-context 유도. - `README.md`, `org-os/01-company/README.md`, `CLAUDE.md` — 흐름·정책 갱신. --- # Phase A — 데이터 모델 & 계약 (declarative SoT) ## Task 1: `founder-context.yaml` **Files:** - Create: `org-os/01-company/founder-context.yaml` **Interfaces:** - Produces: 파일 경로 `org-os/01-company/founder-context.yaml`, 최상위 `status` 필드(`template|filled`). Task 10의 `_founder_context_filled()`가 `status=="filled"`를 읽는다. - [ ] **Step 1: 파일 생성** ```yaml # founder-context.yaml — 창업자·사업 설계의 입력값(회사 정의 이전에 존재하는 실제 제약). # company-context.yaml 이 아니다 — 순환을 끊는 유일 입력이다(§5). 사람이 직접 채운다. # status: template(미채움) -> filled(채움). filled 여야 venture-bootstrap 의 founder-setup 통과. schema-version: 1 status: template # template | filled founder: strengths: [] # 예: [backend, database, infrastructure] available-time: "" # 예: "solo, 주 50h" available-capital: "" desired-business-size: "" # 예: "$1-5M ARR, solo-operable" preferred-market: "" distribution-capability: "" # self-serve? community-led? outbound? sales-tolerance: "" # low | med | high operation-tolerance: "" # low | med | high risk-tolerance: "" # low | med | high hard-constraints: [] # 예: [solo-operable, self-serve-distribution, no-enterprise-sales-dependency] strategic-preferences: [] # 예: [recurring-revenue, technical-moat, global-developer-market] ``` - [ ] **Step 2: 유효성 확인** Run: `python3 -c "import yaml; d=yaml.safe_load(open('org-os/01-company/founder-context.yaml')); assert d['status']=='template'; print('OK', d['schema-version'])"` Expected: `OK 1` - [ ] **Step 3: 커밋** ```bash git add org-os/01-company/founder-context.yaml git commit -m "P1(A): founder-context.yaml — 창업자 제약 입력(순환 해소 입력)" ``` --- ## Task 2: 벤처 계약 두 파일 (`venture-option-spec.yaml`, `venture-validation-map.yaml`) **Files:** - Create: `org-os/06-agent-work/venture-option-spec.yaml` - Create: `org-os/06-agent-work/venture-validation-map.yaml` **Interfaces:** - Produces: `venture-option.required`(kill-criteria 포함) 필드 목록, `venture-validation-map.gates[]`(gate/primary/auditor). `/venture-validate` 커맨드(Task 18)와 doctor(Task 20)가 참조. - [ ] **Step 1: `venture-option-spec.yaml` 생성** ```yaml # venture-option-spec.yaml — opportunity/venture-option 데이터 스키마(§6.1). # 변경 이유 = 산출물 구조 변화. 역할 매핑은 venture-validation-map.yaml(변경 이유 다름). venture-option-spec: version: 1 opportunity-cluster: # opportunity-discovery 산출(제품명 이전, 문제 클러스터) required: [id, problem-domain, target-user, triggering-event, current-alternative, why-now, founder-fit] venture-option: # venture-validation 산출(옵션별) required: [id, customer, painful-job, current-alternative, wedge, monetization, expected-price, reachable-customers, rough-revenue-ceiling, acquisition-channel, build-cost, operation-cost, founder-fit, defensibility, kill-criteria, unresolved-assumptions] notes: - "'unknown' 은 허용값(모른다고 적을 수 있어야 한다) — 단 unresolved-assumptions 에 명시." - "kill-criteria 는 필수 — 없으면 venture-validation→venture-decision 차단." validation-result: # 게이트별 판정 스냅샷 required: [option-id, gate, verdict, evidence, dissent] verdict-enum: [pass, fail, unknown] ``` - [ ] **Step 2: `venture-validation-map.yaml` 생성** (역할 보강 §6.2 반영) ```yaml # venture-validation-map.yaml — 9-gate 역할 매핑(§6.2). 변경 이유 = 조직·협업 방식 변화. # role-id 는 capability-families member-role-ids/lead 에 존재해야 한다(doctor 대조, Task 20). venture-validation-map: version: 1 synthesis-owner: EXEC-CEO # 종합(수렴)은 CEO, 최종 선택은 사람(HUMAN-001) gates: - { gate: problem-intensity, primary: [UX-RESEARCHER], auditor: [PROD-PM] } - { gate: competition-alternatives, primary: [GTM-CI], auditor: [STR-ANALYST] } - { gate: willingness-to-pay, primary: [GTM-PRICING], auditor: [EXEC-CFO] } - { gate: revenue-unit-economics, primary: [EXEC-CFO, GTM-REVOPS], auditor: [GTM-PRICING] } - { gate: tech-feasibility-moat, primary: [ARCH-TECH], auditor: [EXEC-CFO] } - { gate: operability, primary: [CONSULT-OPS], auditor: [ARCH-TECH] } - { gate: distribution, primary: [GTM-GROWTHPM, GTM-SALES], auditor: [EXEC-CFO] } - { gate: founder-fit, primary: [EXEC-CEO], auditor: [ARCH-TECH] } - { gate: kill-criteria, primary: [EXEC-CFO], auditor: [EXEC-CEO] } opportunity-discovery-roles: diverge: [EXEC-CEO, STR-ANALYST, PROD-PM, UX-RESEARCHER, GTM-PMM] contrarian: [EXEC-CFO] # 왜 실패하는가 — 초기 아이디어의 경제구조 반증(§6.2) ``` > **role-id 확정(§15):** Step 2의 `EXEC-CFO`/`EXEC-CEO` 등은 확정 전 `capability-families.yaml`의 member-role-ids와 대조하라. Task 20의 doctor 체크가 이 파일의 모든 role-id를 등록 집합과 대조해 미등록이면 실패시킨다. 대조 명령: > `python3 -c "import yaml; fams=yaml.safe_load(open('org-os/00-role-registry/capability-families.yaml'))['capability-families']['families']; reg={r for fam in fams for r in (fam.get('member-role-ids') or [])} | {fam.get('lead-role-id') for fam in fams}; m=yaml.safe_load(open('org-os/06-agent-work/venture-validation-map.yaml'))['venture-validation-map']; import itertools; used={x for g in m['gates'] for x in (g['primary']+g['auditor'])} | set(m['opportunity-discovery-roles']['diverge']+m['opportunity-discovery-roles']['contrarian']) | {m['synthesis-owner']}; print('UNREGISTERED:', sorted(used-reg))"` > 출력이 `UNREGISTERED: []` 가 되도록 role-id를 등록 표기로 교정(예: `EXEC-CFO`가 미등록이면 실제 CFO member-role-id로 치환). - [ ] **Step 3: 두 파일 YAML 파싱 확인** Run: `python3 -c "import yaml; yaml.safe_load(open('org-os/06-agent-work/venture-option-spec.yaml')); yaml.safe_load(open('org-os/06-agent-work/venture-validation-map.yaml')); print('OK')"` Expected: `OK` - [ ] **Step 4: role-id 등록 대조(위 명령) → `UNREGISTERED: []` 확인 후 커밋** ```bash git add org-os/06-agent-work/venture-option-spec.yaml org-os/06-agent-work/venture-validation-map.yaml git commit -m "P1(A): 벤처 계약 2파일 — venture-option-spec + venture-validation-map(9-gate 역할)" ``` --- ## Task 3: `company-context.yaml` 재구조화 (schema v2, 항목별 provenance) **Files:** - Modify: `org-os/01-company/company-context.yaml` **Interfaces:** - Produces: 최상위 `status ∈ {template, provisional, operating}`; `company.facts[]`/`company.strategic-decisions[]`/`company.hypotheses[]`(각 항목 id·provenance/accepted-by/validation-status), `company.validation-state`, `projects: []`. Task 4~7 린터, Task 10~11 predicate, Task 13~14 validate_report가 이 구조를 읽는다. - [ ] **Step 1: 파일 재작성** (현 `status: demo`·자유서술 `company:` → schema v2 template) ```yaml # company-context.yaml — 회사 + 프로젝트별 문맥 SoT (schema-version 2). # 공식 status 3-상태: template | provisional | operating (bootstrap 아님 — §7.3). # template : 초기(빈). 회사 미정의. # provisional: venture-bootstrap 산출. 결정은 유효, 시장 가설은 검증 상태에 종속. # operating : 실검증·실운영 데이터로 승격(operating 승격 acceptance event 필요). # 전이: template ─(atomic commit)→ provisional ─(human promotion)→ operating. # "작성 중"은 공식 status 가 아니라 candidate 파일의 candidate-status: bootstrap 로만 표현한다. # # 근거등급 정책(§7.1, finding #5): fact/decision/hypothesis 는 정보의 '종류', E1~E5 는 증거의 '강도' — # 독립이다. 블록 단위 cap 을 두지 않고 항목별 provenance 를 둔다. company 네임스페이스 인용 상한은 # validate_report 가 항목 단위로 강제한다(status != operating 이면 company 인용은 E2/Med 상한; # hypothesis 항목 인용은 status 무관 Med 상한). 외부·코드·테스트 증거는 원등급 유지. schema-version: 2 status: template # template | provisional | operating company: facts: [] # 예: # - id: FACT-001 # statement: "창업자는 백엔드·인프라 개발 역량을 보유한다." # category: founder-capability # provenance: [{ source-uri: org-os/01-company/founder-context.yaml, grade: E2 }] # verified-at: "2026-07-12" # status: active # active | retired strategic-decisions: [] # 예: # - id: DEC-001 # statement: "초기 고객은 소규모 백엔드·DB 운영팀으로 한정한다." # decision-type: target-market # accepted-by: HUMAN-001 # accepted-at: "2026-07-12" # source-decision-id: VD-001 # supporting-evidence: [{ source-uri: completion-records//exec-packet-*.report.yaml, grade: E2 }] # status: active hypotheses: [] # 예: # - id: HYP-001 # statement: "대상 고객은 월 $79 이상 지불 의사가 있다." # hypothesis-type: willingness-to-pay # confidence: Med # validation-status: untested # untested | validated | refuted # evidence: [{ source-uri: completion-records//pricing-*.report.yaml, grade: E2 }] # promotion-criteria: ["유료 사전판매 5+"] # falsification-criteria: ["2주 랜딩 유료전환 < 1%"] validation-state: stage: pre-traction # pre-traction | early-traction | operating validated: [] open: [] refuted: [] # --- 프로젝트별 manifest (제품 lifecycle 소관, 기존 유지) -------------------- projects: [] ``` - [ ] **Step 2: 파싱·구조 확인** Run: `python3 -c "import yaml; d=yaml.safe_load(open('org-os/01-company/company-context.yaml')); assert d['schema-version']==2 and d['status']=='template'; assert set(['facts','strategic-decisions','hypotheses','validation-state']) <= set(d['company']); print('OK')"` Expected: `OK` - [ ] **Step 3: 커밋** ```bash git add org-os/01-company/company-context.yaml git commit -m "P1(A): company-context.yaml schema v2 — facts/decisions/hypotheses 항목별 provenance + 3-상태" ``` > 주의: 이 시점 이후 `validate_report`의 구 `populated` 판정이 깨질 수 있다(status가 `template`). 기존 테스트는 회사 인용을 상한하는 쪽이라 green 유지되지만, Phase D(Task 13)에서 read-compat/operating을 반영하기 전까지 `run_all`을 돌리면 회사-인용 테스트가 여전히 "상한됨"을 기대하므로 통과한다. Phase A 종료 시 `run_all` 확인: - [ ] **Step 4: Phase A 회귀 확인** Run: `CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=_sandbox python3 .claude/tests/run_all.py` Expected: 전체 PASS(green). 실패 시 원인 확인 후 수정. --- # Phase B — `lint_company_context.py` (파일 린터) 새 테스트 파일 `test_company_context_lint.py`를 만들고, `check()` 하니스로 케이스를 추가한다. 린터는 **구조·참조·권한·상태 = Hard Fail, 의미 오분류 = Warning**(§9.2). ## Task 4: 린터 코어 + status/candidate Hard Fail **Files:** - Create: `.claude/hooks/lint_company_context.py` - Create: `.claude/tests/test_company_context_lint.py` **Interfaces:** - Produces: `lint_file(path, is_candidate=False) -> (hard_fails: list[str], warnings: list[str])`; CLI `python3 lint_company_context.py [--candidate] [PATH]`(exit 0 = hard_fails 없음, exit 1 = 있음). Task 8(run_all/doctor), Task 11(company-context-lint-passed predicate), Task 16(committer)이 `lint_file`을 import해 쓴다. - [ ] **Step 1: 실패 테스트 작성** — `.claude/tests/test_company_context_lint.py` ```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: []}}" else: 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: []}}" 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 == []) sys.exit(1 if failed else 0) ``` - [ ] **Step 2: 실패 확인** Run: `CLAUDE_PROJECT_DIR="$PWD" python3 .claude/tests/test_company_context_lint.py` Expected: FAIL — `ModuleNotFoundError: No module named 'lint_company_context'` - [ ] **Step 3: 린터 코어 구현** — `.claude/hooks/lint_company_context.py` ```python #!/usr/bin/env python3 """lint_company_context.py — company-context.yaml 내부 정합성 린터(§9.2). 구조·참조·권한·상태 = Hard Fail(exit 1). 의미상 오분류 가능성 = Warning(exit 0, stderr). 공식 파일: status ∈ {template, provisional, operating}, candidate-status 금지. candidate 파일: candidate-status: bootstrap 허용. CLI: lint_company_context.py [--candidate] [PATH] # 기본 PATH = org-os/01-company/company-context.yaml lint_company_context.py --migrate [PATH] # 구 스키마/어휘 1회 변환(Task 7) """ import os, sys ROOT = os.environ.get("CLAUDE_PROJECT_DIR") or os.path.dirname( os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) DEFAULT_PATH = os.path.join(ROOT, "org-os", "01-company", "company-context.yaml") OFFICIAL_STATUS = {"template", "provisional", "operating"} def _load(path): import yaml with open(path, encoding="utf-8") as fh: return yaml.safe_load(fh) or {} def lint_file(path, is_candidate=False): """(hard_fails, warnings) 반환. 예외 없이 파싱 실패도 hard_fail 로.""" hard, warn = [], [] try: doc = _load(path) except Exception as e: return ([f"파싱 실패: {e}"], []) status = str(doc.get("status", "")).strip().lower() # --- 상태·candidate 정합(Hard Fail) --- if is_candidate: # candidate 는 최종 목표 status(3-상태) + candidate-status: bootstrap if status not in OFFICIAL_STATUS: hard.append(f"candidate status '{status}' 는 {sorted(OFFICIAL_STATUS)} 밖") if str(doc.get("candidate-status", "")).strip().lower() != "bootstrap": hard.append("candidate 파일은 candidate-status: bootstrap 필요") else: if status == "demo" or status == "populated": warn.append(f"구 어휘 status='{status}' — --migrate 로 변환 필요(deprecated)") elif status not in OFFICIAL_STATUS: hard.append(f"공식 status '{status}' 는 {sorted(OFFICIAL_STATUS)} 밖(특히 'bootstrap'은 공식 status 아님)") if "candidate-status" in doc: hard.append("공식 파일에 candidate-status 필드가 남아있음(commit 시 제거돼야 함)") return (hard, warn) def main(argv): args = list(argv) is_candidate = "--candidate" in args if is_candidate: args.remove("--candidate") if "--migrate" in args: args.remove("--migrate") path = args[0] if args else DEFAULT_PATH return migrate(path) path = args[0] if args else DEFAULT_PATH hard, warn = lint_file(path, is_candidate=is_candidate) for w in warn: sys.stderr.write(f"[lint_company_context] WARN: {w}\n") for h in hard: sys.stderr.write(f"[lint_company_context] FAIL: {h}\n") if hard: return 1 print(f"[lint_company_context] OK ({path}) — hard-fails 0, warnings {len(warn)}") return 0 def migrate(path): # Task 7 에서 구현. 지금은 no-op 성공. print("[lint_company_context] --migrate 미구현(Task 7)"); return 0 if __name__ == "__main__": sys.exit(main(sys.argv[1:])) ``` - [ ] **Step 4: 통과 확인** Run: `CLAUDE_PROJECT_DIR="$PWD" python3 .claude/tests/test_company_context_lint.py` Expected: PASS (모든 케이스). - [ ] **Step 5: 커밋** ```bash git add .claude/hooks/lint_company_context.py .claude/tests/test_company_context_lint.py git commit -m "P1(B): lint_company_context 코어 — status/candidate 정합 Hard Fail" ``` --- ## Task 5: 항목 구조 Hard Fail (id·provenance·권한·참조) **Files:** - Modify: `.claude/hooks/lint_company_context.py:lint_file` - Modify: `.claude/tests/test_company_context_lint.py` **Interfaces:** - Consumes: `lint_file(path, is_candidate)` from Task 4. - Produces: 동일 시그니처, Hard Fail 규칙 확장(항목 id/중복, fact provenance, decision accepted-by/at/source-decision-id, hypothesis validation-status/confidence/falsification, hypothesis-id-as-fact, nonexistent evidence path, provisional-needs-human-decision, operating-needs-promotion-event). - [ ] **Step 1: 실패 테스트 추가** — `test_company_context_lint.py`의 `sys.exit` 직전에 삽입 ```python 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)) ``` - [ ] **Step 2: 실패 확인** Run: `CLAUDE_PROJECT_DIR="$PWD" python3 .claude/tests/test_company_context_lint.py` Expected: 새 케이스 FAIL(현재 코어는 상태만 검사). - [ ] **Step 3: `lint_file`에 항목 규칙 추가** — 상태 검사 `return` 직전에 삽입 ```python company = doc.get("company") or {} facts = company.get("facts") or [] decs = company.get("strategic-decisions") or [] hyps = company.get("hypotheses") or [] # 항목 id 수집(중복·hypothesis-id-as-fact 검사) seen = {} def _reg(idv, block): if not idv: hard.append(f"{block} 항목 id 누락") return if idv in seen: hard.append(f"중복 id '{idv}' ({seen[idv]} 와 {block})") else: seen[idv] = block def _evidence_paths_exist(items, block): for it in items: for ev in (it.get("provenance") or it.get("supporting-evidence") or it.get("evidence") or []): src = str((ev or {}).get("source-uri", "")).strip() if not src or src.lower().startswith("http"): continue # glob(*) 은 검사 생략(경로 패턴). 구체 경로만 실존 확인. if "*" in src: continue ap = src if os.path.isabs(src) else os.path.join(ROOT, src) if not os.path.exists(ap): hard.append(f"{block} 항목 evidence 경로 미존재: {src}") for it in facts: _reg(it.get("id"), "fact") if not (it.get("provenance")): hard.append(f"fact '{it.get('id')}' provenance 누락") for it in decs: _reg(it.get("id"), "decision") for k in ("accepted-by", "accepted-at", "source-decision-id"): if not it.get(k): hard.append(f"decision '{it.get('id')}' {k} 누락") hyp_ids = set() for it in hyps: _reg(it.get("id"), "hypothesis") hyp_ids.add(it.get("id")) for k in ("validation-status", "confidence", "falsification-criteria"): if not it.get(k): hard.append(f"hypothesis '{it.get('id')}' {k} 누락") # hypothesis-id 를 fact/decision 근거(source-decision-id 또는 evidence source)에 쓰면 hard for it in decs: if it.get("source-decision-id") in hyp_ids and it.get("source-decision-id"): hard.append(f"decision '{it.get('id')}' 가 hypothesis id 를 source-decision-id 로 사용") _evidence_paths_exist(facts, "fact") _evidence_paths_exist(decs, "decision") _evidence_paths_exist(hyps, "hypothesis") # 상태-권한 정합 if status == "provisional" and not is_candidate: if not any(d.get("accepted-by") for d in decs): hard.append("status=provisional 인데 human 승인(accepted-by) strategic-decision 이 하나도 없음") if status == "operating" and not is_candidate: # operating 승격은 별도 승격 이벤트(acceptance) 를 요구 — validation-state.stage 로 근사 검사. if str((company.get("validation-state") or {}).get("stage", "")).lower() != "operating": hard.append("status=operating 인데 validation-state.stage != operating(승격 근거 부재)") ``` - [ ] **Step 4: 통과 확인** Run: `CLAUDE_PROJECT_DIR="$PWD" python3 .claude/tests/test_company_context_lint.py` Expected: PASS (전체). - [ ] **Step 5: 커밋** ```bash git add .claude/hooks/lint_company_context.py .claude/tests/test_company_context_lint.py git commit -m "P1(B): lint_company_context 항목 구조 Hard Fail(id·provenance·권한·참조)" ``` --- ## Task 6: 의미 오분류 Warning **Files:** - Modify: `.claude/hooks/lint_company_context.py:lint_file` - Modify: `.claude/tests/test_company_context_lint.py` **Interfaces:** - Consumes/Produces: `lint_file`(동일). Warning만 추가 — Hard Fail 0 유지. - [ ] **Step 1: 실패 테스트 추가** ```python # fact 문장이 추정 표현 -> warning(hard 아님) p = _tmp(_doc("operating", 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 2: 실패 확인** Run: `CLAUDE_PROJECT_DIR="$PWD" python3 .claude/tests/test_company_context_lint.py` Expected: 새 케이스 FAIL. - [ ] **Step 3: Warning 규칙 추가** — `lint_file`의 `return (hard, warn)` 직전 ```python _ESTIMATIVE = ("예상", "일 것", "추정", "아마", "듯", "가능성이 높") for it in facts: s = str(it.get("statement", "")) if any(t in s for t in _ESTIMATIVE): warn.append(f"fact '{it.get('id')}' 문장이 추정 표현 포함 — hypothesis 여야 할 수 있음: {s[:40]}") if any(t in s for t in ("시장 규모", "WTP", "지불 의사", "market size")): warn.append(f"fact '{it.get('id')}' 가 시장/WTP 주장 — hypothesis 로 분류 검토") for it in hyps: if str(it.get("validation-status", "")).lower() == "untested" and not it.get("evidence"): warn.append(f"hypothesis '{it.get('id')}' 미검증+근거 없음 — 장기 방치 주의") ``` - [ ] **Step 4: 통과 확인 + 커밋** Run: `CLAUDE_PROJECT_DIR="$PWD" python3 .claude/tests/test_company_context_lint.py` Expected: PASS. ```bash git add .claude/hooks/lint_company_context.py .claude/tests/test_company_context_lint.py git commit -m "P1(B): lint_company_context 의미 오분류 Warning(추정 표현·시장/WTP·방치 가설)" ``` --- ## Task 7: `--migrate` + 읽기 호환 **Files:** - Modify: `.claude/hooks/lint_company_context.py:migrate` - Modify: `.claude/tests/test_company_context_lint.py` **Interfaces:** - Produces: `migrate(path) -> int` — 구 `demo→template`, `populated→operating`, schema-version 없으면 2로 승격, `company:` 자유서술을 보존하며 facts/decisions/hypotheses/validation-state 골격 주입. exit 0 성공. - [ ] **Step 1: 실패 테스트 추가** ```python # 구 어휘 파일 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"]) ``` - [ ] **Step 2: 실패 확인** Run: `CLAUDE_PROJECT_DIR="$PWD" python3 .claude/tests/test_company_context_lint.py` Expected: 새 케이스 FAIL(migrate no-op). - [ ] **Step 3: `migrate` 구현** (Task 4의 no-op 대체) ```python def migrate(path): """구 스키마/어휘를 schema-version 2 + 3-상태로 1회 변환. 자유서술 company 는 보존.""" import yaml try: doc = _load(path) except Exception as e: sys.stderr.write(f"[lint_company_context] migrate 파싱 실패: {e}\n"); return 1 st = str(doc.get("status", "")).strip().lower() doc["status"] = {"demo": "template", "populated": "operating"}.get(st, st if st in OFFICIAL_STATUS else "template") doc["schema-version"] = 2 comp = doc.get("company") if not isinstance(comp, dict): comp = {} # 자유서술 키(name/mission/constraints 등)는 보존하고 골격 블록만 보강 comp.setdefault("facts", []) comp.setdefault("strategic-decisions", []) comp.setdefault("hypotheses", []) comp.setdefault("validation-state", {"stage": "pre-traction", "validated": [], "open": [], "refuted": []}) doc["company"] = comp doc.pop("candidate-status", None) with open(path, "w", encoding="utf-8") as fh: yaml.safe_dump(doc, fh, allow_unicode=True, sort_keys=False) print(f"[lint_company_context] migrated -> status={doc['status']}, schema-version=2") return 0 ``` - [ ] **Step 4: 통과 확인 + 커밋** Run: `CLAUDE_PROJECT_DIR="$PWD" python3 .claude/tests/test_company_context_lint.py` Expected: PASS. ```bash git add .claude/hooks/lint_company_context.py .claude/tests/test_company_context_lint.py git commit -m "P1(B): lint_company_context --migrate(구 어휘->3-상태, schema v2, company 보존)" ``` --- ## Task 8: 린터를 run_all.py + doctor.py에 배선 **Files:** - Modify: `.claude/tests/run_all.py` - Modify: `.claude/hooks/doctor.py` **Interfaces:** - Consumes: `lint_company_context.py` CLI (exit code), `test_company_context_lint.py`. - Produces: run_all이 새 test 파일을 자동 수집(파일명 규약)하고, doctor가 공식 company-context.yaml에 대해 `lint_file`을 호출해 Hard Fail 0을 확인. - [ ] **Step 1: run_all이 새 테스트를 수집하는지 확인** Run: `grep -nE "glob|test_.*\.py|discover|for .*test" .claude/tests/run_all.py | head` 확인: run_all이 `test_*.py`를 자동 수집하면 추가 작업 불필요. 자동 수집이 아니면 명시 목록에 `test_company_context_lint.py`·`test_venture_bootstrap.py`(Task 9~) 추가. - [ ] **Step 2: (자동수집 아닐 때만) 명시 목록에 추가** — run_all의 테스트 목록 배열에 두 파일명 삽입. (자동수집이면 이 step은 no-op 처리하고 넘어간다.) - [ ] **Step 3: doctor에 company-context lint 점검 추가** — `doctor.py`의 점검 함수 모음에 삽입(기존 점검 패턴을 따른다; 아래는 독립 호출로도 안전한 형태) ```python def _check_company_context_lint(): """공식 company-context.yaml 이 lint_company_context Hard Fail 0 인지.""" try: import lint_company_context as L path = os.path.join(ROOT, "org-os", "01-company", "company-context.yaml") hard, warn = L.lint_file(path, is_candidate=False) if hard: return (False, "company-context.yaml lint Hard Fail: " + "; ".join(hard)) return (True, f"company-context.yaml lint OK (warnings {len(warn)})") except Exception as e: return (False, f"company-context lint 점검 오류: {e}") ``` 그리고 doctor의 점검 실행 목록(기존에 각 `_check_*`를 호출·집계하는 곳)에 `_check_company_context_lint`를 등록한다. (doctor의 정확한 등록 지점은 `grep -nE "def _check|checks =|append.*_check|run.*check" .claude/hooks/doctor.py`로 확인.) - [ ] **Step 4: doctor + run_all 확인** Run: `python3 .claude/hooks/doctor.py; CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=_sandbox python3 .claude/tests/run_all.py` Expected: doctor PASS(새 점검 포함), run_all green(새 lint 테스트 포함). - [ ] **Step 5: 커밋** ```bash git add .claude/tests/run_all.py .claude/hooks/doctor.py git commit -m "P1(B): lint_company_context 를 run_all + doctor 에 배선" ``` --- # Phase C — 상태머신 (venture-bootstrap plan + predicates) ## Task 9: `venture-bootstrap` plan + 전이 규칙 (declarative) **Files:** - Modify: `org-os/06-agent-work/execution-plans.yaml` - Modify: `org-os/00-role-registry/state-transition-rules.yaml` - Create: `.claude/tests/test_venture_bootstrap.py` **Interfaces:** - Produces: plan `venture-bootstrap`(stages 7종), 전이 6개(모두 `allowed-by: [OPS-ORCH]`), condition-catalog 6 신규 키. Task 10~11 predicate가 이 조건 키를 구현한다. `SE.allowed_next`/`SE._find_transition`가 새 stage/전이를 인식한다. - [ ] **Step 1: 실패 테스트 작성** — `.claude/tests/test_venture_bootstrap.py` (state_engine 테스트 스켈레톤 복제) ```python #!/usr/bin/env python3 """venture-bootstrap plan + predicate 단위테스트. standalone. exit 0 = all pass.""" import os, sys, shutil ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) HOOKS = os.path.join(ROOT, ".claude", "hooks") FIX = os.path.join(ROOT, ".claude", "tests", "fixtures") os.makedirs(FIX, exist_ok=True) WS = os.path.join(FIX, "venture-bootstrap-ws") shutil.rmtree(WS, ignore_errors=True); os.makedirs(WS, exist_ok=True) os.environ["CLAUDE_PROJECT_DIR"] = ROOT os.environ["ORGOS_WORKSPACE"] = WS sys.path.insert(0, HOOKS) import state_engine as SE # 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}") # plan 로드에 venture-bootstrap 존재 plans = SE.load_plans().get("plans", {}) check("venture-bootstrap plan 존재", "venture-bootstrap" in plans) vb = plans.get("venture-bootstrap", {}) check("stages 순서", vb.get("stages", [])[:3] == ["intake", "founder-setup", "opportunity-discovery"]) check("terminal-stage", vb.get("terminal-stage") == "bootstrap-complete") # 전이 존재 + allowed-by OPS-ORCH 단독 t = SE._find_transition("founder-setup", "opportunity-discovery") check("founder-setup->opportunity-discovery 전이 존재", bool(t)) check("전이 allowed-by = [OPS-ORCH] 단독", t and t.get("allowed-by") == ["OPS-ORCH"]) t2 = SE._find_transition("venture-decision", "company-context-commit") check("venture-decision->company-context-commit 존재", bool(t2)) t3 = SE._find_transition("company-context-commit", "bootstrap-complete") check("company-context-commit->bootstrap-complete 존재", bool(t3)) sys.exit(1 if failed else 0) ``` - [ ] **Step 2: 실패 확인** Run: `CLAUDE_PROJECT_DIR="$PWD" python3 .claude/tests/test_venture_bootstrap.py` Expected: FAIL(plan/전이 부재). - [ ] **Step 3: execution-plans.yaml에 plan 추가** — `plans:` 아래에 삽입 ```yaml venture-bootstrap: description: > 회사 수립(1회성). founder-context 를 입력으로 기회탐색→벤처검증→벤처결정→ company-context commit 을 거쳐 company-context.yaml(provisional) 을 산출한다. 제품 cascade 의 선행이며 별도 lifecycle 이다. 모든 전이는 OPS-ORCH 가 집행. stages: [intake, founder-setup, opportunity-discovery, venture-validation, venture-decision, company-context-commit, bootstrap-complete] terminal-stage: bootstrap-complete default-tier: standard outputs: "org-os/01-company/company-context.yaml (status: provisional)" ``` - [ ] **Step 4: state-transition-rules.yaml에 전이 + 조건 추가** — `workflow-stage-transitions.transitions:` 리스트에 6개 추가하고, `condition-catalog:`에 6개 키 추가. 또한 `unified-stage-graph`에 새 stage를 반영(문서적). `transitions:` 에 추가: ```yaml # ── venture-bootstrap(회사 수립, 별도 lifecycle) — 전이 집행은 OPS-ORCH 단독 ── - { from: intake, to: founder-setup, allowed-by: [OPS-ORCH], required-conditions: [decision-brief-present] } - { from: founder-setup, to: opportunity-discovery, allowed-by: [OPS-ORCH], required-conditions: [founder-context-present] } - { from: opportunity-discovery, to: venture-validation, allowed-by: [OPS-ORCH], required-conditions: [opportunity-clusters-present] } - { from: venture-validation, to: venture-decision, allowed-by: [OPS-ORCH], required-conditions: [venture-options-validated] } - { from: venture-decision, to: company-context-commit, allowed-by: [OPS-ORCH], required-conditions: [venture-decision-accepted, human-acceptance-receipt-present] } - { from: company-context-commit, to: bootstrap-complete, allowed-by: [OPS-ORCH], required-conditions: [company-context-provisional-committed, company-context-lint-passed, company-context-artifact-recorded] } ``` `condition-catalog:` 에 추가: ```yaml founder-context-present: "org-os/01-company/founder-context.yaml status=filled" opportunity-clusters-present: "opportunity-cluster 산출 ≥ 2(ledger.artifacts design-type=opportunity-cluster 또는 ledger.opportunity-clusters 길이)" venture-options-validated: "venture-validation accepted 산출물 존재(각 옵션 required 필드+kill-criteria)" venture-decision-accepted: "venture-decision accepted 산출물 존재(acceptance_log accepted)" human-acceptance-receipt-present: "HUMAN-001 accepted 이벤트가 그 venture-decision report-id/report-sha256/workflow-id 에 바인딩" company-context-provisional-committed: "공식 company-context.yaml status ∈ {provisional, operating}(commit_company_context 원자 교체)" company-context-lint-passed: "lint_company_context 공식 파일 Hard Fail 0(live 평가)" company-context-artifact-recorded: "ledger artifacts 에 design-type=company-context 등록(commit receipt)" ``` `unified-stage-graph`(문서 라인)에 stage를 병기(강제는 plan+transitions가 함): 주석으로 `# venture-bootstrap: founder-setup, opportunity-discovery, venture-validation, venture-decision, company-context-commit, bootstrap-complete` 추가. - [ ] **Step 5: 통과 확인** Run: `CLAUDE_PROJECT_DIR="$PWD" python3 .claude/tests/test_venture_bootstrap.py` Expected: PASS (plan/전이 케이스). predicate 케이스는 아직 없음. - [ ] **Step 6: 커밋** ```bash git add org-os/06-agent-work/execution-plans.yaml org-os/00-role-registry/state-transition-rules.yaml .claude/tests/test_venture_bootstrap.py git commit -m "P1(C): venture-bootstrap plan + 전이 6종(OPS-ORCH 단독) + condition-catalog" ``` --- ## Task 10: predicates part 1 — founder / opportunity / venture-options **Files:** - Modify: `.claude/hooks/state_engine.py` (facts 빌더 `_facts`, `_PREDICATES`, helper) - Modify: `.claude/tests/test_venture_bootstrap.py` **Interfaces:** - Consumes: `_facts(wf, led, ctx)` 패턴, `_present`, `_accepted_design_types`. - Produces: facts 키 `founder_context_present`, `opportunity_cluster_count`, `venture_options_validated`; `_PREDICATES` 항목 `founder-context-present`/`opportunity-clusters-present`/`venture-options-validated`. - [ ] **Step 1: 실패 테스트 추가** — `test_venture_bootstrap.py`의 `sys.exit` 직전 ```python def led_at(stage, **extra): d = {"workflow-id": "vb1", "stage": stage, "plan": "venture-bootstrap", "tier": "standard", "artifacts": []} d.update(extra); return d # founder-context-present: 실제 파일 status 에 의존. filled 가정 불가하므로 조건 평가만 확인. f = SE._facts("vb1", led_at("founder-setup")) ok, _ = SE._eval_condition("founder-context-present", f) check("founder-context-present 평가 가능", isinstance(ok, bool)) # opportunity-clusters-present: <2 면 차단, >=2 면 통과(ledger 명시 리스트) f = SE._facts("vb1", led_at("opportunity-discovery", **{"opportunity-clusters": [{"id": "OC1"}]})) ok, _ = SE._eval_condition("opportunity-clusters-present", f) check("opportunity clusters 1개 -> 차단", ok is False) f = SE._facts("vb1", led_at("opportunity-discovery", **{"opportunity-clusters": [{"id": "OC1"}, {"id": "OC2"}]})) ok, _ = SE._eval_condition("opportunity-clusters-present", f) check("opportunity clusters 2개 -> 통과", ok is True) ``` - [ ] **Step 2: 실패 확인** Run: `CLAUDE_PROJECT_DIR="$PWD" python3 .claude/tests/test_venture_bootstrap.py` Expected: 새 케이스 FAIL(`미지의 조건`). - [ ] **Step 3: helper + facts + predicates 추가** — `state_engine.py` `_COMPANY_CTX`/`FOUNDER_CTX` 경로 상수와 helper를 `_facts` 위쪽(예: `_present` 근처)에 추가: ```python _FOUNDER_CTX = os.path.join(ROOT, "org-os", "01-company", "founder-context.yaml") _COMPANY_CTX_PATH = os.path.join(ROOT, "org-os", "01-company", "company-context.yaml") def _founder_context_filled(): try: return str(_load_yaml(_FOUNDER_CTX).get("status", "")).strip().lower() == "filled" except Exception: return False ``` `_facts` 내부(예: `f["decision_brief_present"] = ...` 근처)에 facts 주입: ```python # --- venture-bootstrap facts (P1) --- f["founder_context_present"] = _founder_context_filled() oc = led.get("opportunity-clusters") or led.get("opportunity_clusters") or [] oc_art = sum(1 for a in arts if isinstance(a, dict) and a.get("design-type") == "opportunity-cluster") f["opportunity_cluster_count"] = max(len(oc) if isinstance(oc, list) else 0, oc_art) f["venture_options_validated"] = bool(accepted_types & {"venture-validation", "venture-option"}) ``` `_PREDICATES` 딕셔너리에 추가: ```python "founder-context-present": lambda f: (bool(f.get("founder_context_present")), "founder-context.yaml status != filled"), "opportunity-clusters-present": lambda f: (f.get("opportunity_cluster_count", 0) >= 2, f"opportunity-cluster ≥2 필요(현재 {f.get('opportunity_cluster_count', 0)})"), "venture-options-validated": lambda f: (bool(f.get("venture_options_validated")), "venture-validation accepted 산출물 없음(kill-criteria 포함 옵션 검증 필요)"), ``` `_PROTECTED_FACTS`에 추가(자기신고 방지): `"founder_context_present", "venture_options_validated"`. (`opportunity_cluster_count`는 발산 카운트라 오버라이드 허용 — option_set_count와 동일 취급.) - [ ] **Step 4: 통과 확인 + 커밋** Run: `CLAUDE_PROJECT_DIR="$PWD" python3 .claude/tests/test_venture_bootstrap.py` Expected: PASS. ```bash git add .claude/hooks/state_engine.py .claude/tests/test_venture_bootstrap.py git commit -m "P1(C): predicates part1 — founder-context/opportunity-clusters/venture-options-validated" ``` --- ## Task 11: predicates part 2 — decision·human-receipt(hash)·commit·lint·artifact **Files:** - Modify: `.claude/hooks/state_engine.py` - Modify: `.claude/tests/test_venture_bootstrap.py` **Interfaces:** - Consumes: `acceptance_log.read_events`/`_resolve_report_path`(Task 15에서 report-sha256 지원), `lint_company_context.lint_file`(Task 4~7), `_evidence_receipts`, `record_artifact`. - Produces: facts + `_PREDICATES` 항목 `venture-decision-accepted`/`human-acceptance-receipt-present`/`company-context-provisional-committed`/`company-context-lint-passed`/`company-context-artifact-recorded`. 모두 `_PROTECTED_FACTS`. > 의존: 이 task는 Task 15(acceptance_log report-sha256)와 상호참조한다. 순서상 Task 11의 hash 검증 helper는 Task 15가 이벤트에 `report-sha256`를 넣어야 실동작한다. **권장 실행순서: Task 15 → Task 11.** (구현은 독립적이나 통합 테스트는 15 이후.) - [ ] **Step 1: 실패 테스트 추가** ```python # company-context-lint-passed: 공식 파일이 template(정상)이면 통과 f = SE._facts("vb1", led_at("company-context-commit")) ok, _ = SE._eval_condition("company-context-lint-passed", f) check("company-context-lint-passed 평가 가능", isinstance(ok, bool)) # company-context-provisional-committed: 공식 status=template 이면 False ok, _ = SE._eval_condition("company-context-provisional-committed", f) check("template 상태 -> committed False", ok is False) # human-acceptance-receipt-present: 이벤트 없으면 False(boolean 자기신고 거부) f2 = SE._facts("vb1", led_at("venture-decision", **{"facts": {"human_acceptance_receipt_present": True}})) ok, _ = SE._eval_condition("human-acceptance-receipt-present", f2) check("human boolean 자기신고 -> 거부(receipt 없음)", ok is False) ``` - [ ] **Step 2: 실패 확인** Run: `CLAUDE_PROJECT_DIR="$PWD" python3 .claude/tests/test_venture_bootstrap.py` Expected: 새 케이스 FAIL. - [ ] **Step 3: helper + facts + predicates 추가** helper(state_engine.py): ```python def _company_ctx_status(): try: return str(_load_yaml(_COMPANY_CTX_PATH).get("status", "")).strip().lower() except Exception: return "" def _company_ctx_lint_ok(): try: import lint_company_context as L hard, _ = L.lint_file(_COMPANY_CTX_PATH, is_candidate=False) return not hard except Exception: return False def _company_ctx_artifact_recorded(arts): return any(isinstance(a, dict) and a.get("design-type") == "company-context" for a in (arts or [])) def _venture_decision_receipt_ok(wf): """HUMAN-001 accepted 이벤트가 venture-decision report 의 현재 해시와 일치 바인딩(§9.4). 이벤트에 report-sha256 이 있고(Task 15), report 파일이 실존하며 해시 일치할 때만 True.""" try: import acceptance_log as AL, hashlib for ev in reversed(list(AL.read_events())): if ev.get("decision") != "accepted": continue if ev.get("workflow-id") not in (None, wf): continue if str(ev.get("role-id", "")).upper() != "HUMAN-001".upper(): continue sha = ev.get("report-sha256") rid = ev.get("accepted-report-id") or ev.get("report-id") if not (sha and rid): continue path = AL._resolve_report_path(rid, wf) if not path or not os.path.exists(path): continue actual = hashlib.sha256(open(path, "rb").read()).hexdigest() if actual == sha: return True return False except Exception: return False ``` `_facts` 내부에 주입: ```python f["venture_decision_accepted"] = bool(accepted_types & {"venture-decision", "ExecutiveDecisionPacket", "decision-packet"}) f["human_acceptance_receipt_present"] = _venture_decision_receipt_ok(wf) _st = _company_ctx_status() f["company_context_provisional_committed"] = _st in ("provisional", "operating") f["company_context_lint_passed"] = _company_ctx_lint_ok() f["company_context_artifact_recorded"] = _company_ctx_artifact_recorded(arts) ``` `_PREDICATES`에 추가: ```python "venture-decision-accepted": lambda f: (bool(f.get("venture_decision_accepted")), "venture-decision accepted 산출물 없음"), "human-acceptance-receipt-present": lambda f: (bool(f.get("human_acceptance_receipt_present")), "HUMAN-001 accepted 이벤트(report-sha256 바인딩) 없음 — boolean 자기신고 불가"), "company-context-provisional-committed": lambda f: (bool(f.get("company_context_provisional_committed")), "공식 company-context.yaml status != provisional/operating"), "company-context-lint-passed": lambda f: (bool(f.get("company_context_lint_passed")), "company-context lint Hard Fail 존재"), "company-context-artifact-recorded": lambda f: (bool(f.get("company_context_artifact_recorded")), "company-context commit receipt(artifact) 없음"), ``` `_PROTECTED_FACTS`에 5개 키 추가: `"venture_decision_accepted", "human_acceptance_receipt_present", "company_context_provisional_committed", "company_context_lint_passed", "company_context_artifact_recorded"`. - [ ] **Step 4: 통과 확인 + 커밋** Run: `CLAUDE_PROJECT_DIR="$PWD" python3 .claude/tests/test_venture_bootstrap.py` Expected: PASS. ```bash git add .claude/hooks/state_engine.py .claude/tests/test_venture_bootstrap.py git commit -m "P1(C): predicates part2 — decision/human-receipt(hash)/committed/lint/artifact(전부 PROTECTED)" ``` --- ## Task 12: `company-context-ready` seam predicate + guard 서브커맨드 (제품 cascade 진입) **Files:** - Modify: `.claude/hooks/state_engine.py` (predicate + CLI 서브커맨드) - Modify: `.claude/tests/test_venture_bootstrap.py` **Interfaces:** - Produces: predicate `company-context-ready`(advisory, 기존 cascade 전이 조건에 **추가하지 않음** — 회귀 방지) + CLI `state_engine.py check-company-context-ready --workflow WF`(exit 0 ready / exit 2 not). 제품 커맨드(/ground·/decide)가 진입 시 호출. - [ ] **Step 1: 실패 테스트 추가** ```python # company-context-ready: 공식 status=template 이면 not ready f = SE._facts("vb1", led_at("intake")) ok, _ = SE._eval_condition("company-context-ready", f) check("template -> cascade not ready", ok is False) ``` - [ ] **Step 2: 실패 확인** Run: `CLAUDE_PROJECT_DIR="$PWD" python3 .claude/tests/test_venture_bootstrap.py` Expected: 새 케이스 FAIL. - [ ] **Step 3: predicate + CLI 추가** facts(이미 `company_context_provisional_committed` 존재하므로 재사용): ```python f["company_context_ready"] = f.get("company_context_provisional_committed") and not f.get("blocker_open") ``` `_PREDICATES`: ```python "company-context-ready": lambda f: (bool(f.get("company_context_ready")), "company-context 미준비(status provisional/operating 아님 또는 blocker)"), ``` `_PROTECTED_FACTS`에 `"company_context_ready"` 추가. CLI: state_engine의 argparse/명령 분기(기존 `guard` 처리부 근처)에 서브커맨드 추가: ```python # (main 의 명령 분기에서) if cmd == "check-company-context-ready": wf = ns.workflow f = _facts(wf, _load_ledger_safe(wf)) ok, reason = _eval_condition("company-context-ready", f) if ok: print("[state_engine] company-context READY") return 0 sys.stderr.write(f"[state_engine] NOT READY: {reason}\n") return 2 ``` (정확한 argparse 등록은 기존 `guard` 서브커맨드 정의를 복제 — `grep -nE "add_parser|guard|def main" .claude/hooks/state_engine.py`로 위치 확인.) - [ ] **Step 4: 통과 확인 + 커밋** Run: `CLAUDE_PROJECT_DIR="$PWD" python3 .claude/tests/test_venture_bootstrap.py` Expected: PASS. ```bash git add .claude/hooks/state_engine.py .claude/tests/test_venture_bootstrap.py git commit -m "P1(C): company-context-ready seam predicate + check-company-context-ready guard(cascade 회귀 없음)" ``` - [ ] **Step 5: Phase C 회귀 확인** Run: `CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=_sandbox python3 .claude/tests/run_all.py` Expected: green(기존 cascade 테스트 포함 — 새 조건을 기존 전이에 넣지 않았으므로 무회귀). --- # Phase D — 강제기 확장 ## Task 13: validate_report status 어휘(operating) + 읽기 호환 **Files:** - Modify: `.claude/hooks/validate_report.py:_company_context_populated` - Modify: `.claude/tests/test_enforcement.py` **Interfaces:** - Consumes: `_is_unpopulated_company_ref`(항목 단위 상한, 기존). - Produces: `_company_context_populated()`가 `operating`(및 구 `populated`)일 때만 True. `template/provisional`(및 구 `demo`)은 False(회사 인용 항목 E2 상한 유지). 외부/코드 증거는 무영향. - [ ] **Step 1: 실패 테스트 추가** — `test_enforcement.py`의 실행 케이스 영역에 추가(파일의 `check`/assert 규약을 따른다; 아래는 함수 단위 호출 예) ```python # validate_report._company_context_populated 는 status=operating 에서만 True. # 실제 SoT 파일을 건드리지 않도록 모듈 상수 VR._COMPANY_CTX 를 임시파일로 monkeypatch(크래시해도 원본 무손상). import sys, os, tempfile, yaml sys.path.insert(0, os.path.join(ROOT, ".claude", "hooks")) import validate_report as VR _orig_ctx = VR._COMPANY_CTX def _set_ctx(status): fd, p = tempfile.mkstemp(suffix=".yaml"); os.close(fd) with open(p, "w", encoding="utf-8") as fh: yaml.safe_dump({"schema-version": 2, "status": status, "company": {"facts": [], "strategic-decisions": [], "hypotheses": [], "validation-state": {"stage": "pre-traction", "validated": [], "open": [], "refuted": []}}, "projects": []}, fh, allow_unicode=True) VR._COMPANY_CTX = p def _restore(): VR._COMPANY_CTX = _orig_ctx _set_ctx("operating") check("operating -> populated True", VR._company_context_populated() is True) _set_ctx("provisional") check("provisional -> populated False(회사인용 상한 유지)", VR._company_context_populated() is False) _set_ctx("template") check("template -> populated False", VR._company_context_populated() is False) _restore() ``` > `test_enforcement.py`가 `check()` 하니스가 아니라 다른 규약이면, 파일 상단 규약(assert/함수)에 맞춰 케이스를 삽입한다(`sed -n '1,40p' .claude/tests/test_enforcement.py`로 확인). - [ ] **Step 2: 실패 확인** Run: `CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=_sandbox python3 .claude/tests/test_enforcement.py` Expected: `provisional -> populated False`는 통과(현재 로직상 populated만 True)지만 `operating -> populated True`는 FAIL(현재는 'populated' 문자열만 True). - [ ] **Step 3: `_company_context_populated` 수정** ```python def _company_context_populated(): """공식 company-context 가 '실데이터로 운영 중'이면 True → 회사 인용 상한 해제. 신 어휘 status=='operating' 만 True. 구 'populated' 는 읽기 호환(operating 취급). template/provisional(및 구 'demo')은 False = 회사 인용 항목 E2/Med 상한 유지(§9.1).""" try: import yaml as _y # noqa: E402 doc = _y.safe_load(open(_COMPANY_CTX, encoding="utf-8")) or {} st = str(doc.get("status", "")).strip().lower() if st == "populated": sys.stderr.write("[validate_report] WARN: status='populated' deprecated → 'operating'\n") return True return st == "operating" except Exception: return False ``` (`sys` import 확인 — 파일 상단에 이미 있으면 재사용.) - [ ] **Step 4: 통과 확인 + 커밋** Run: `CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=_sandbox python3 .claude/tests/test_enforcement.py` Expected: PASS. ```bash git add .claude/hooks/validate_report.py .claude/tests/test_enforcement.py git commit -m "P1(D): validate_report status 어휘 operating + populated 읽기호환(회사인용 상한은 항목단위 유지)" ``` --- ## Task 14: validate_report hypothesis anchor ceiling **Files:** - Modify: `.claude/hooks/validate_report.py` (evidence 상한 로직) - Modify: `.claude/tests/test_enforcement.py` **Interfaces:** - Consumes: evidence 항목의 `source-uri`(예: `org-os/01-company/company-context.yaml#HYP-001`). - Produces: 회사 문맥 인용의 anchor가 `#HYP-`이면 그 항목 근거를 **E2/Med 상한**(status=operating이어도) — 가설 기반 회사 결론 상한. `#FACT-`/`#DEC-`는 기존 네임스페이스 규칙. - [ ] **Step 1: 실패 테스트 추가** ```python # _is_hypothesis_company_ref 는 source-uri anchor 문자열만 파싱한다(파일 접근 없음 → 파일 조작 불필요). check("HYP anchor -> hypothesis ref True", VR._is_hypothesis_company_ref("org-os/01-company/company-context.yaml#HYP-001") is True) check("FACT anchor -> hypothesis ref False", VR._is_hypothesis_company_ref("org-os/01-company/company-context.yaml#FACT-001") is False) check("no anchor -> False", VR._is_hypothesis_company_ref("org-os/01-company/company-context.yaml") is False) ``` - [ ] **Step 2: 실패 확인** Run: `CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=_sandbox python3 .claude/tests/test_enforcement.py` Expected: FAIL(`_is_hypothesis_company_ref` 부재). - [ ] **Step 3: helper 추가 + 상한 로직 결선** `_is_unpopulated_company_ref` 근처에 helper 추가: ```python def _is_hypothesis_company_ref(src): """company-context 의 hypothesis 항목을 anchor(#HYP-...)로 인용하면 True. 가설 기반 회사 결론은 status=operating 이어도 E2/Med 상한(§9.1).""" if not src: return False raw = str(src).strip().strip("'\"`").replace("\\", "/") if "#" not in raw: return False path, _, anchor = raw.partition("#") base = path.rsplit("/", 1)[-1] return base == "company-context.yaml" and anchor.upper().startswith("HYP-") ``` evidence 상한 판정부(현재 `_is_unpopulated_company_ref(e.get("source-uri"))`로 E3+ 차단하는 곳, 스펙 §9.1 line ~474)에 OR 조건 추가 — hypothesis anchor면 populated 여부와 무관하게 상한: ```python src = e.get("source-uri") if gnum >= 3 and (_is_unpopulated_company_ref(src) or _is_hypothesis_company_ref(src)): # ... 기존 E2 강등/경고 메시지 재사용, 사유에 'hypothesis 기반' 추가 ``` (정확한 삽입 위치·메시지는 기존 블록을 그대로 확장; `grep -n "_is_unpopulated_company_ref" .claude/hooks/validate_report.py`로 호출부 확인.) - [ ] **Step 4: 통과 확인 + 커밋** Run: `CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=_sandbox python3 .claude/tests/test_enforcement.py` Expected: PASS. ```bash git add .claude/hooks/validate_report.py .claude/tests/test_enforcement.py git commit -m "P1(D): validate_report hypothesis anchor(#HYP-) ceiling — 가설기반 회사결론 Med/E2 상한" ``` --- ## Task 15: acceptance_log `report-sha256` 바인딩 **Files:** - Modify: `.claude/hooks/acceptance_log.py` (`build_event`, CLI append) - Modify: `.claude/schemas/acceptance-event.schema.json` - Modify: `.claude/tests/test_p1_acceptance.py` **Interfaces:** - Produces: `build_event(..., report_sha256=None)`이 지정 시 이벤트에 `report-sha256` 추가; CLI `append ... --report-sha256 `. schema에 optional `report-sha256`. Task 11의 `_venture_decision_receipt_ok`가 이 필드를 읽는다. - [ ] **Step 1: 실패 테스트 추가** — `test_p1_acceptance.py` 규약에 맞춰 ```python import acceptance_log as AL ev = AL.build_event("r1", "accepted", workflow="wf1", role="HUMAN-001", report_sha256="abc123") check("build_event report-sha256 포함", ev.get("report-sha256") == "abc123") ev2 = AL.build_event("r1", "accepted", workflow="wf1") check("report-sha256 미지정 시 부재", "report-sha256" not in ev2) ``` - [ ] **Step 2: 실패 확인** Run: `CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=_sandbox python3 .claude/tests/test_p1_acceptance.py` Expected: FAIL(`build_event`가 report_sha256 인자 없음). - [ ] **Step 3: `build_event` 시그니처 확장** (Task 4에서 본 정의) `def build_event(..., report_sha256=None)` 추가하고 본문에: ```python if report_sha256: ev["report-sha256"] = report_sha256 ``` CLI는 argparse가 아니라 커스텀 `_argval(args, "--flag")` 파서 + `def main()`(≈line 289)을 쓴다. `main()`의 `append` 분기에서 `report_sha256=_argval(args, "--report-sha256")`를 `build_event(...)` 호출 인자에 추가한다(기존 `workflow=_argval(args, "--workflow")` 라인들과 나란히). usage 문자열에도 `[--report-sha256 HEX]` 추가. schema(`acceptance-event.schema.json`)의 `properties`에 추가: ```json "report-sha256": { "type": "string", "description": "선택. 수락 대상 report 파일의 sha256 — venture-decision human-gate 위조·재사용 방지 바인딩(P1 §9.4)." } ``` - [ ] **Step 4: 통과 확인 + 커밋** Run: `CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=_sandbox python3 .claude/tests/test_p1_acceptance.py` Expected: PASS. ```bash git add .claude/hooks/acceptance_log.py .claude/schemas/acceptance-event.schema.json .claude/tests/test_p1_acceptance.py git commit -m "P1(D): acceptance_log report-sha256 바인딩(venture-decision human-gate 위조 방지)" ``` --- ## Task 16: `commit_company_context.py` — candidate → 원자적 교체 **Files:** - Create: `.claude/hooks/commit_company_context.py` - Modify: `.claude/tests/test_venture_bootstrap.py` **Interfaces:** - Consumes: `lint_company_context.lint_file`, `state_engine.record_artifact`, `os.replace`. - Produces: CLI `commit_company_context.py --workflow WF --candidate `(lint→acceptance 검증→candidate-status 제거→os.replace→record_artifact). exit 0 성공/1 차단. 실패 시 공식 파일 무변경. - [ ] **Step 1: 실패 테스트 추가** — `test_venture_bootstrap.py` ```python import subprocess, tempfile, yaml def _write(path, obj): with open(path, "w", encoding="utf-8") as fh: yaml.safe_dump(obj, fh, allow_unicode=True, sort_keys=False) # lint 실패 candidate -> 공식 파일 무변경 + exit 1 official = os.path.join(ROOT, "org-os", "01-company", "company-context.yaml") before = open(official, encoding="utf-8").read() cand = os.path.join(WS, "bad.candidate.yaml") _write(cand, {"schema-version": 2, "status": "provisional", "candidate-status": "bootstrap", "company": {"facts": [], "strategic-decisions": [], "hypotheses": [], "validation-state": {"stage": "pre-traction", "validated": [], "open": [], "refuted": []}}, "projects": []}) # provisional 인데 human decision 없음 -> lint hard fail r = subprocess.run([sys.executable, os.path.join(HOOKS, "commit_company_context.py"), "--workflow", "vb1", "--candidate", cand], capture_output=True, text=True, env={**os.environ}) check("lint 실패 candidate -> exit 1", r.returncode == 1) check("공식 파일 무변경", open(official, encoding="utf-8").read() == before) ``` - [ ] **Step 2: 실패 확인** Run: `CLAUDE_PROJECT_DIR="$PWD" python3 .claude/tests/test_venture_bootstrap.py` Expected: FAIL(파일 부재). - [ ] **Step 3: `commit_company_context.py` 구현** ```python #!/usr/bin/env python3 """commit_company_context.py — candidate → 공식 company-context.yaml 원자적 교체(trusted CLI, §9.3). 절차: schema/lint(Hard Fail 0) → (선택) venture-decision human acceptance 검증 → candidate-status 제거 → 임시파일 write → os.replace(원자) → state_engine.record_artifact(design-type=company-context). 실패 시 공식 파일 **무변경**. OPS-ORCH 가 실행(에이전트는 guard_tools 로 공식 파일 직접쓰기 차단). CLI: commit_company_context.py --workflow WF --candidate [--require-human] """ import os, sys, argparse, tempfile ROOT = os.environ.get("CLAUDE_PROJECT_DIR") or os.path.dirname( os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) OFFICIAL = os.path.join(ROOT, "org-os", "01-company", "company-context.yaml") sys.path.insert(0, os.path.join(ROOT, ".claude", "hooks")) def _fail(msg): sys.stderr.write(f"[commit_company_context] FAIL: {msg}\n"); return 1 def main(argv): ap = argparse.ArgumentParser() ap.add_argument("--workflow", required=True) ap.add_argument("--candidate", required=True) ap.add_argument("--require-human", action="store_true") ns = ap.parse_args(argv) import yaml if not os.path.exists(ns.candidate): return _fail(f"candidate 없음: {ns.candidate}") import lint_company_context as L hard, warn = L.lint_file(ns.candidate, is_candidate=True) for w in warn: sys.stderr.write(f"[commit_company_context] WARN: {w}\n") if hard: return _fail("candidate lint Hard Fail: " + "; ".join(hard)) if ns.require_human: try: import state_engine as SE if not SE._venture_decision_receipt_ok(ns.workflow): return _fail("HUMAN-001 acceptance receipt(report-sha256 바인딩) 없음") except Exception as e: return _fail(f"human acceptance 검증 오류: {e}") # candidate → 공식: candidate-status 제거 with open(ns.candidate, encoding="utf-8") as fh: doc = yaml.safe_load(fh) or {} doc.pop("candidate-status", None) # 최종 공식 형태 재-lint(안전) tmp_fd, tmp_path = tempfile.mkstemp(dir=os.path.dirname(OFFICIAL), suffix=".tmp") try: with os.fdopen(tmp_fd, "w", encoding="utf-8") as fh: yaml.safe_dump(doc, fh, allow_unicode=True, sort_keys=False) hard2, _ = L.lint_file(tmp_path, is_candidate=False) if hard2: os.unlink(tmp_path) return _fail("최종 공식형 lint Hard Fail: " + "; ".join(hard2)) os.replace(tmp_path, OFFICIAL) # 원자적 교체 except Exception as e: if os.path.exists(tmp_path): os.unlink(tmp_path) return _fail(f"원자 교체 실패(공식 파일 무변경): {e}") # 아티팩트 등록(company-context-artifact-recorded predicate 근거) try: import state_engine as SE SE.record_artifact(ns.workflow, design_type="company-context", path=OFFICIAL) except Exception as e: sys.stderr.write(f"[commit_company_context] WARN: record_artifact 실패: {e}\n") print(f"[commit_company_context] OK — {OFFICIAL} (status={doc.get('status')})") return 0 if __name__ == "__main__": sys.exit(main(sys.argv[1:])) ``` - [ ] **Step 4: 통과 확인 + 커밋** Run: `CLAUDE_PROJECT_DIR="$PWD" python3 .claude/tests/test_venture_bootstrap.py` Expected: PASS. ```bash git add .claude/hooks/commit_company_context.py .claude/tests/test_venture_bootstrap.py git commit -m "P1(D): commit_company_context — candidate→공식 원자적 교체(lint+human 게이트, 실패시 무변경)" ``` --- ## Task 17: guard_tools — 공식 company-context.yaml 직접쓰기 보호 **Files:** - Modify: `.claude/hooks/guard_tools.py` - Modify: `.claude/tests/test_p1_permissions.py` (또는 test_p0_trust_boundary.py — 어느 파일이 guard_tools를 테스트하는지 `grep -l guard_tools .claude/tests/*.py`) **Interfaces:** - Produces: guard_tools가 Edit/Write 대상 경로가 `org-os/01-company/company-context.yaml`이면 **차단**(commit_company_context 경로만 허용). founder-context.yaml은 사람 편집이므로 별도 규칙(에이전트 쓰기 차단, 사람은 세션 밖). - [ ] **Step 1: guard_tools 구조 확인** Run: `grep -nE "def |PROTECT|immutable|report\.yaml|deny|block|path|Edit|Write" .claude/hooks/guard_tools.py | head -40` 확인: 기존에 `.report.yaml` 불변성/보호경로를 어떻게 매칭하는지(정규식/접미사/목록). 그 패턴에 공식 company-context 경로를 추가한다. - [ ] **Step 2: 실패 테스트 추가** — guard_tools 테스트 파일의 규약에 맞춰(예: PreToolUse payload 시뮬레이션) ```python # Write/Edit 로 공식 company-context.yaml 직접 수정 시도 -> 차단(exit != 0 또는 deny) payload = {"tool_name": "Write", "tool_input": {"file_path": "org-os/01-company/company-context.yaml", "content": "x"}} rc = run_guard(payload) # 테스트 파일의 기존 헬퍼 사용(guard_tools 호출 래퍼) check("공식 company-context 직접 Write -> 차단", rc != 0) ``` > 테스트 파일에 guard 호출 헬퍼(`run_guard`)가 없으면, 같은 파일의 기존 케이스가 guard_tools를 호출하는 방식을 복제한다(예: `subprocess`로 hook 실행 + PreToolUse JSON stdin). - [ ] **Step 3: 실패 확인** Run: `CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=_sandbox python3 .claude/tests/test_p1_permissions.py` Expected: FAIL(현재 미보호). - [ ] **Step 4: guard_tools에 보호경로 추가** — 기존 보호경로 매칭부에 삽입(정확한 형태는 Step 1의 패턴을 따름). 예시(접미사 매칭 방식일 때): ```python _PROTECTED_SOT = ("org-os/01-company/company-context.yaml",) # ... Edit/Write 분기에서: norm = str(file_path).replace("\\", "/") if any(norm.endswith(s) for s in _PROTECTED_SOT): return _deny("공식 company-context.yaml 직접 수정 금지 — commit_company_context.py(원자 교체)만 허용(P1 §9.3/§9.5)") ``` (founder-context.yaml도 에이전트 쓰기 차단이 필요하면 동일 목록에 추가하되, 사람 입력 파일이므로 보호 메시지에 "사람이 세션 밖에서 편집" 명시.) - [ ] **Step 5: 통과 확인 + Phase D 회귀 + 커밋** Run: `CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=_sandbox python3 .claude/tests/test_p1_permissions.py && CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=_sandbox python3 .claude/tests/run_all.py` Expected: 둘 다 green. ```bash git add .claude/hooks/guard_tools.py .claude/tests/test_p1_permissions.py git commit -m "P1(D): guard_tools — 공식 company-context.yaml 직접쓰기 차단(commit CLI만 허용)" ``` --- # Phase E — 커맨드 & 문서 & 배선 ## Task 18: 커맨드 — /ceo-intake 보강 + /venture-validate + /company-bootstrap **Files:** - Modify: `.claude/commands/ceo-intake.md` - Create: `.claude/commands/venture-validate.md` - Create: `.claude/commands/company-bootstrap.md` **Interfaces:** - Consumes: `state_engine.py guard/transition`, `context_package.py`, `venture-validation-map.yaml`, `commit_company_context.py`, converge 계약(collaboration-modes.yaml). - Produces: 3개 커맨드 문서. lint_refs(Task 20)가 커맨드→에이전트/파일 참조 무결성을 검사한다. - [ ] **Step 1: 기존 커맨드 형식 확인** Run: `sed -n '1,30p' .claude/commands/decide.md` 확인: frontmatter·guard/transition 호출·context_package 지시·산출물 경로 규약. `/venture-validate`·`/company-bootstrap`는 이 규약을 그대로 따른다. - [ ] **Step 2: `/ceo-intake` 보강** — `ceo-intake.md`에 다음 절 추가(기존 내용 보존) ```markdown ## 회사 부트스트랩 진입(venture-bootstrap) 새 **회사/제품군을 처음 세우는** 경우에만 `--plan venture-bootstrap`을 명시한다(자동 선택 금지 — 기존 제품 cascade와 충돌 방지). 1. `org-os/01-company/founder-context.yaml`의 `status`를 확인한다. `template`이면 **사람에게 채우도록 요청**하고(창업자 강점·시간·자본·유통역량·리스크 내성·hard-constraints), `status: filled`로 바뀌기 전에는 다음 단계로 진행하지 않는다(founder-setup 게이트가 `founder-context-present`를 강제). 2. Decision Brief를 작성하고 `plan=venture-bootstrap`, `tier`를 선언한다. 3. 상태 초기화 후 다음: `/venture-validate`. 기존 회사(공식 company-context.status ∈ {provisional, operating})면 이 절을 건너뛰고 제품 cascade(`/ground` 등)로 간다. 제품 커맨드는 진입 시 `python3 .claude/hooks/state_engine.py check-company-context-ready --workflow `로 회사 문맥 준비를 확인한다(template면 거부). ``` - [ ] **Step 3: `/venture-validate.md` 생성** ```markdown --- description: 기회탐색(발산)→벤처검증(9-gate)로 opportunity-cluster와 검증된 venture-option을 산출한다. venture-bootstrap 2단계. --- 당신은 Orchestrator다. **venture-bootstrap: opportunity-discovery + venture-validation.** 회사 정의 이전이므로 company-context를 강근거로 쓰지 않는다(§7.1 상한). 입력: `org-os/01-company/founder-context.yaml`(must-read), `org-os/06-agent-work/venture-option-spec.yaml`, `org-os/06-agent-work/venture-validation-map.yaml`. 1. **guard:** `python3 .claude/hooks/state_engine.py guard --workflow --to opportunity-discovery`. 2. **opportunity-discovery(발산):** `venture-validation-map.opportunity-discovery-roles.diverge` 역할 + `contrarian`(CFO=경제구조 반증)로 **divergent** fan-out. 각 워커는 context_package로 spawn(mode=divergent, must-read=founder-context+venture-option-spec). 산출: opportunity-cluster ≥2(중복·완전성 검사). 각 cluster는 spec의 `opportunity-cluster.required` 필드 충족. 제품명 이전, **문제 클러스터**부터. 3. **전이:** OPS-ORCH가 `transition --to opportunity-discovery`(조건 `opportunity-clusters-present`). 4. **venture-validation:** 각 옵션 × 9-gate를 `venture-validation-map.gates`의 primary/auditor로 fan-out(dissent 보존). `unknown` 허용, `kill-criteria` 필수. 산출: venture-option 보고서(spec `venture-option.required` 충족) + validation-result. 종합은 EXEC-CEO가 하되 상태 전이는 하지 않는다. 5. **전이:** OPS-ORCH가 `transition --to venture-validation`(조건 `venture-options-validated`). 6. **다음:** `/company-bootstrap`. 산출물: `completion-records//opportunity-clusters-*.report.yaml`, `venture-option-*.report.yaml`(불변, new_report). 모든 spawn은 context_package 컴파일러+validator를 거친다. ``` - [ ] **Step 4: `/company-bootstrap.md` 생성** ```markdown --- description: 벤처결정(C-Level converge + 사람 승인)→company-context candidate→원자적 commit. venture-bootstrap 3단계. --- 당신은 Orchestrator다. **venture-bootstrap: venture-decision + company-context-commit.** `/venture-validate`의 검증된 옵션을 하나로 수렴해 회사 문맥을 확정한다. 재사용 단위는 `/decide` 명령이 아니라 **공통 converge 계약**(`org-os/06-agent-work/collaboration-modes.yaml`의 `converge`: synthesis + report-header). 1. **guard:** `python3 .claude/hooks/state_engine.py guard --workflow --to venture-decision`. 2. **venture-decision(수렴):** C-Level(CPO·CFO·CTO·COO·CPTO)이 검증된 옵션을 **독립 평가**(divergent) → EXEC-CEO가 **converge** 종합해 **ExecutiveDecisionPacket**(선택 옵션·트레이드오프·기각사유·dissent 보존·go/no-go) 산출. `report-type: decision`/`design-type: venture-decision`. 3. **사람 승인(바인딩):** 사람이 하나를 선택하면 그 report의 sha256을 계산해 `python3 .claude/hooks/acceptance_log.py append --report-id --decision accepted --workflow --role HUMAN-001 --report-sha256 `. (boolean 자기신고 불가 — predicate가 report-sha256 바인딩을 검사.) 4. **전이:** OPS-ORCH가 `transition --to venture-decision`(조건 `venture-decision-accepted` + `human-acceptance-receipt-present`). 5. **company-context candidate 작성:** `/completion-records//company-context.candidate.yaml` 생성 — 선택된 결정을 `strategic-decisions`(accepted-by: HUMAN-001, source-decision-id=venture-decision id), 확정 사실을 `facts`(provenance), 시장 가정을 `hypotheses`(validation-status: untested, falsification-criteria). `status: provisional`, `candidate-status: bootstrap`. 6. **atomic commit:** `python3 .claude/hooks/commit_company_context.py --workflow --candidate --require-human`. (lint Hard Fail 0 + human receipt 검증 통과 시에만 공식 파일 원자 교체. 실패 시 공식 파일 무변경.) 7. **전이(종료):** OPS-ORCH가 `transition --to company-context-commit` 후 `transition --to bootstrap-complete`(조건 `company-context-provisional-committed` + `company-context-lint-passed` + `company-context-artifact-recorded`). 8. **다음:** 제품 cascade(`/ground` …)가 이제 `company-context-ready`를 통과한다. 제품 intake는 `company-context-ref`·`venture-decision-id`·`company-decision-ids`를 참조한다(§11). 공식 `company-context.yaml`은 이 커맨드의 commit CLI로만 갱신된다(직접 Edit/Write는 guard_tools가 차단). ``` - [ ] **Step 5: lint_refs 확인 + 커밋** Run: `python3 .claude/hooks/lint_refs.py` Expected: 참조 무결성 PASS(커맨드가 참조하는 파일·hook 실존). 실패 시 경로 오타 교정. ```bash git add .claude/commands/ceo-intake.md .claude/commands/venture-validate.md .claude/commands/company-bootstrap.md git commit -m "P1(E): 커맨드 — /ceo-intake --plan 보강 + /venture-validate + /company-bootstrap" ``` --- ## Task 19: 문서 — README / 01-company/README / CLAUDE.md **Files:** - Modify: `README.md` - Modify: `org-os/01-company/README.md` - Modify: `CLAUDE.md` **Interfaces:** 없음(문서). lint_refs가 링크 무결성 검사. - [ ] **Step 1: README에 부트스트랩 진입 경로 추가** — "어떤 커맨드부터?" 절에 삽입 ```markdown ### 새 회사/제품을 처음 세울 때(venture-bootstrap) company-context가 아직 `template`이면 제품 cascade 전에 회사부터 세운다: 1. `org-os/01-company/founder-context.yaml`을 채운다(status: filled). 2. `/ceo-intake --plan venture-bootstrap` → `/venture-validate`(기회탐색+9-gate 검증) → `/company-bootstrap`(C-Level 수렴 + 사람 승인 + company-context 원자 commit). 3. 완료되면 공식 company-context.status = `provisional`. 이제 `/ground`부터 제품 cascade를 탄다. 기존 회사(status ∈ {provisional, operating})면 곧장 `/ceo-intake` → `/ground`. ``` - [ ] **Step 2: `org-os/01-company/README.md` 갱신** — status 어휘 + facts/decisions/hypotheses 정책 ```markdown ## company-context.yaml 상태·근거 정책 (P1) - 공식 `status`는 3-상태: **template → provisional → operating** (`bootstrap`은 공식 status 아님 — 작성 중은 candidate 파일의 `candidate-status: bootstrap`로만 표현). - `company`는 **facts / strategic-decisions / hypotheses**로 분리하며 **항목별 provenance**를 둔다(fact=provenance, decision=accepted-by/at/source-decision-id, hypothesis=validation-status/confidence/falsification-criteria). `fact/decision/hypothesis`(정보 종류)와 `E1~E5`(증거 강도)는 독립이다. - 회사 인용 상한(finding #5)은 **항목 단위**: status != operating이면 company 인용 항목은 E2/Med 상한; hypothesis 항목 인용은 status 무관 Med 상한. 외부·코드·테스트 증거는 원등급 유지. - 공식 파일은 `commit_company_context.py`(원자 교체)로만 갱신(직접 Edit/Write 차단). 내부 정합은 `lint_company_context.py`가 강제. - 구 `demo/populated`는 읽기 호환(deprecation warning) + `lint_company_context.py --migrate`로 1회 변환. ``` - [ ] **Step 3: `CLAUDE.md` 갱신** — 실행 흐름 절에 venture-bootstrap 추가 + finding #5 어휘 갱신 CLAUDE.md의 "Cascade 커맨드" 절 위에 삽입: ```markdown ### Venture Bootstrap(회사 수립 — 제품 cascade의 선행, 1회성) ``` /ceo-intake --plan venture-bootstrap → /venture-validate → /company-bootstrap ``` - 순환 해소: founder-context(사람 입력) → 기회탐색·9-gate 검증 → C-Level 수렴 + 사람 승인 → company-context.yaml(provisional) 원자 commit. 이후 제품 cascade가 이를 입력으로 소비. - 공식 company-context status: template|provisional|operating(3-상태). 모든 전이는 OPS-ORCH 집행. ``` 그리고 "회사 문맥 상한(#5)" 규칙 문장의 `status: populated`를 `status: operating`(구 populated 읽기호환)으로, 항목별 provenance 정책을 반영. - [ ] **Step 4: lint_refs + 커밋** Run: `python3 .claude/hooks/lint_refs.py` Expected: PASS. ```bash git add README.md org-os/01-company/README.md CLAUDE.md git commit -m "P1(E): 문서 — venture-bootstrap 진입 경로 + company-context 상태/근거 정책 + finding#5 어휘" ``` --- ## Task 20: doctor 배선 + 전체 회귀 green **Files:** - Modify: `.claude/hooks/doctor.py` **Interfaces:** doctor가 신규 아티팩트(founder-context·venture 계약·commit hook)·venture-bootstrap plan·venture-validation-map role-id 등록을 점검. - [ ] **Step 1: doctor 점검 추가** — 기존 `_check_*` 패턴에 맞춰 ```python def _check_venture_bootstrap_wiring(): """P1: 신규 SoT/hook 실존 + venture-bootstrap plan + validation-map role-id 등록.""" import yaml missing = [] for p in ("org-os/01-company/founder-context.yaml", "org-os/06-agent-work/venture-option-spec.yaml", "org-os/06-agent-work/venture-validation-map.yaml", ".claude/hooks/lint_company_context.py", ".claude/hooks/commit_company_context.py"): if not os.path.exists(os.path.join(ROOT, p)): missing.append(p) if missing: return (False, "P1 신규 파일 누락: " + ", ".join(missing)) plans = yaml.safe_load(open(os.path.join(ROOT, "org-os/06-agent-work/execution-plans.yaml")))["execution-plans"]["plans"] if "venture-bootstrap" not in plans: return (False, "execution-plans 에 venture-bootstrap plan 없음") # validation-map role-id 등록 대조 fams = yaml.safe_load(open(os.path.join(ROOT, "org-os/00-role-registry/capability-families.yaml")))["capability-families"]["families"] reg = {r for fam in fams for r in (fam.get("member-role-ids") or [])} | {fam.get("lead-role-id") for fam in fams} reg = {str(x).upper() for x in reg if x} m = yaml.safe_load(open(os.path.join(ROOT, "org-os/06-agent-work/venture-validation-map.yaml")))["venture-validation-map"] used = {x for g in m["gates"] for x in (g["primary"] + g["auditor"])} | set(m["opportunity-discovery-roles"]["diverge"] + m["opportunity-discovery-roles"]["contrarian"]) | {m["synthesis-owner"]} unreg = sorted({u for u in used if str(u).upper() not in reg and not str(u).upper().startswith("HUMAN")}) if unreg: return (False, "venture-validation-map 미등록 role-id: " + ", ".join(unreg)) return (True, "venture-bootstrap 배선 OK") ``` 그리고 doctor의 점검 실행 목록에 `_check_venture_bootstrap_wiring` 등록. - [ ] **Step 2: doctor 통과 확인** Run: `python3 .claude/hooks/doctor.py` Expected: PASS(새 점검 포함). `미등록 role-id`가 나오면 Task 2의 대조 명령으로 role-id 교정. - [ ] **Step 3: 전체 회귀** Run: `CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=_sandbox python3 .claude/tests/run_all.py` Expected: **전부 green**(doctor + lint_refs + 모든 test_*.py, 신규 test_company_context_lint·test_venture_bootstrap 포함). - [ ] **Step 4: 커밋** ```bash git add .claude/hooks/doctor.py git commit -m "P1(E): doctor — venture-bootstrap 배선·validation-map role-id 등록 점검 + 전체 green" ``` --- ## Self-Review (스펙 대비) **Spec coverage:** - §5 founder-context → Task 1. §6 벤처계약 2파일 → Task 2. §7 company-context v2/3-상태 → Task 3. §8 plan/전이/OPS-ORCH단독/human-gate → Task 9,11. §9.1 validate_report 항목상한/operating → Task 13. §9.1 hypothesis anchor → Task 14. §9.2 lint Hard Fail/Warning → Task 4~6. §9.3 atomic commit → Task 16. §9.4 acceptance report-sha256 → Task 15. §9.5 guard → Task 17. §10 커맨드/converge → Task 18. §11 seam(company-context-ready) → Task 12. §12 migration → Task 7. §14 테스트 14종 → 각 Task의 TDD(2 human-boolean=Task11, 3/4 hash=Task11+15, 5/6 atomic=Task16, 7/8 cascade entry=Task12, 9 external-E4=Task13, 10 hypothesis=Task14, 11 demo/populated=Task7/13, 1 worker-transition=allowed-by 구조 Task9). §16 → 전체. - 갭 점검: 테스트 #12(재실행 idempotent fan-out)·#13(candidate commit idempotent)·#14(decision 변경 시 이전 candidate 거부)는 커맨드/오케스트레이션 레벨 — Task 16의 commit은 hash 바인딩으로 #14를 커버(decision 바뀌면 receipt hash 불일치 → human 게이트 실패). #12/#13은 커맨드 실행 시 guard가 이미 완료된 stage 재전이를 거부하는 것으로 근사(별도 강제 테스트는 후속). → 스펙 §14에 "커버리지 근사" 주석과 일치. **Placeholder scan:** "확인" 지시(grep으로 기존 등록 지점 찾기)는 실제 파일 구조 의존 지점으로, 정확한 삽입 위치를 찾는 실행 가능한 명령을 제시했다(placeholder 아님). role-id 교정은 실행 가능한 대조 명령 제공. **Type consistency:** predicate 키(하이픈, 예 `founder-context-present`)와 facts 키(언더스코어, 예 `founder_context_present`)의 매핑은 `_eval_condition`/`_normkey` 규약과 일치. `lint_file(path, is_candidate)`·`build_event(..., report_sha256)`·`_venture_decision_receipt_ok(wf)`·`record_artifact(wf, design_type=, path=)` 시그니처는 Task 간 일관. **실행 순서 주의:** Task 15(acceptance report-sha256) → Task 11(hash predicate) 순서 권장(상호참조). Phase 내 나머지는 순차.