87 KiB
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=<ws>명시(미설정 시_workspace.py가WorkspaceNotSetError). 테스트는 격리 fixture 워크스페이스 사용. - CI 게이트: 매 phase 종료 시
CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=_sandbox python3 .claude/tests/run_all.pygreen 유지(doctor + lint_refs + 모든 test_*.py). - 테스트 규약: pytest 금지. 새 테스트 파일은
test_state_engine.py스켈레톤 복제 —check(name, ok)/passed,failed카운터/sys.exit(1 if failed else 0), 격리 워크스페이스FIX/<name>-ws. - 신뢰경계: 에이전트/worker는
state_engine.py transition·원장·공식company-context.yaml을 직접 쓰지 못한다(guard_tools). venture-bootstrap 모든 전이allowed-by: [OPS-ORCH]단독(§8.2). - 공식 status 3-상태: 공식
company-context.yamlstatus ∈ {template, provisional, operating}.bootstrap은 공식 status 아님 — candidate 파일의candidate-status: bootstrap+ workflow stagecompany-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) <noreply@anthropic.com>.
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-bootstrapplan..claude/hooks/state_engine.py— 6 predicate + facts +_PROTECTED_FACTS+company-context-readyguard 서브커맨드..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: 파일 생성
# 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: 커밋
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생성
# 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 반영)
# 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: []확인 후 커밋
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 47 린터, Task 1011 predicate, Task 13~14 validate_report가 이 구조를 읽는다. -
Step 1: 파일 재작성 (현
status: demo·자유서술company:→ schema v2 template)
# 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/<wf>/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/<wf>/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: 커밋
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]); CLIpython3 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
#!/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
#!/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: 커밋
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직전에 삽입
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직전에 삽입
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: 커밋
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: 실패 테스트 추가
# 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)직전
_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.
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: 실패 테스트 추가
# 구 어휘 파일 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 대체)
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.
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.pyCLI (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의 점검 함수 모음에 삽입(기존 점검 패턴을 따른다; 아래는 독립 호출로도 안전한 형태)
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: 커밋
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 테스트 스켈레톤 복제)
#!/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:아래에 삽입
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: 에 추가:
# ── 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: 에 추가:
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: 커밋
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직전
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 근처)에 추가:
_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 주입:
# --- 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 딕셔너리에 추가:
"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.
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: 실패 테스트 추가
# 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):
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 내부에 주입:
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에 추가:
"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.
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 전이 조건에 추가하지 않음 — 회귀 방지) + CLIstate_engine.py check-company-context-ready --workflow WF(exit 0 ready / exit 2 not). 제품 커맨드(/ground·/decide)가 진입 시 호출. -
Step 1: 실패 테스트 추가
# 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 존재하므로 재사용):
f["company_context_ready"] = f.get("company_context_provisional_committed") and not f.get("blocker_open")
_PREDICATES:
"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 처리부 근처)에 서브커맨드 추가:
# (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.
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 규약을 따른다; 아래는 함수 단위 호출 예)
# 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수정
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.
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: 실패 테스트 추가
# _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 추가:
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 여부와 무관하게 상한:
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.
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추가; CLIappend ... --report-sha256 <hex>. schema에 optionalreport-sha256. Task 11의_venture_decision_receipt_ok가 이 필드를 읽는다. -
Step 1: 실패 테스트 추가 —
test_p1_acceptance.py규약에 맞춰
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) 추가하고 본문에:
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에 추가:
"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.
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 <path>(lint→acceptance 검증→candidate-status 제거→os.replace→record_artifact). exit 0 성공/1 차단. 실패 시 공식 파일 무변경. -
Step 1: 실패 테스트 추가 —
test_venture_bootstrap.py
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구현
#!/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 <path> [--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.
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 시뮬레이션)
# 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의 패턴을 따름). 예시(접미사 매칭 방식일 때):
_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.
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에 다음 절 추가(기존 내용 보존)
## 회사 부트스트랩 진입(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 <wf>`로 회사 문맥 준비를 확인한다(template면 거부).
- Step 3:
/venture-validate.md생성
---
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 <wf> --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/<wf>/opportunity-clusters-*.report.yaml`, `venture-option-*.report.yaml`(불변, new_report). 모든 spawn은 context_package 컴파일러+validator를 거친다.
- Step 4:
/company-bootstrap.md생성
---
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 <wf> --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 <rid> --decision accepted --workflow <wf> --role HUMAN-001 --report-sha256 <sha>`. (boolean 자기신고 불가 — predicate가 report-sha256 바인딩을 검사.)
4. **전이:** OPS-ORCH가 `transition --to venture-decision`(조건 `venture-decision-accepted` + `human-acceptance-receipt-present`).
5. **company-context candidate 작성:** `<workspace>/completion-records/<wf>/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 <wf> --candidate <candidate-path> --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 실존). 실패 시 경로 오타 교정.
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에 부트스트랩 진입 경로 추가 — "어떤 커맨드부터?" 절에 삽입
### 새 회사/제품을 처음 세울 때(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 정책
## 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 커맨드" 절 위에 삽입:
### 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.
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_*패턴에 맞춰
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: 커밋
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 내 나머지는 순차.