Files
company-haness/docs/superpowers/plans/2026-07-12-p1-company-venture-bootstrap.md
T

87 KiB
Raw Blame History

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.pyWorkspaceNotSetError). 테스트는 격리 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/<name>-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) <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.yamlventure-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: 파일 생성

# 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]); 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

#!/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.pysys.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_filereturn (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.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의 점검 함수 모음에 삽입(기존 점검 패턴을 따른다; 아래는 독립 호출로도 안전한 형태)

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.pysys.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 전이 조건에 추가하지 않음 — 회귀 방지) + CLI state_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.pycheck() 하니스가 아니라 다른 규약이면, 파일 상단 규약(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 추가; CLI append ... --report-sha256 <hex>. schema에 optional report-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: populatedstatus: 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 내 나머지는 순차.