# P3-A 프롬프트/skill 분리 (구조 인프라) 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:** 에이전트 카드에 인라인 중복 삽입되던 실무 절차(working-method)를 역할별 method-skill로 분리해, 카드=정체성·경계 / skill=절차 / context-package=문맥 3층으로 나눈다. **범위=구조 이동(P3-A)** — 절차 내용을 재작성·심화하지 않는다. 절차를 실행 계약으로 강화하는 것은 **P3-B**(spec §17, 별도 사이클). **Architecture:** `role-working-methods.yaml`(유일 편집 SoT) → `gen_method_skills.py`가 역할별 `.claude/skills/generated/-method/SKILL.md` 생성 → `method-skill-registry.yaml`(role→skill 배선 SoT) → `gen_agents.py`가 카드에 얇은 spine + `skills:` frontmatter 방출. 무결성은 `doctor.py`·`lint_refs.py` 게이트가 강제(참조 실존·고아0·drift0). 품질 중립(절차 내용 위치만 이동). **Tech Stack:** Python 3(stdlib + PyYAML), 기존 하네스 hook 패턴. 테스트=standalone `check()` 컨벤션(pytest 아님), `run_all.py`가 `test_*.py` 자동 발견. **Spec:** `docs/superpowers/specs/2026-07-13-p3-prompt-skill-separation-design.md` ## Global Constraints - **품질 중립(내용 불변)**: working-method 문장을 재작성·개선하지 않는다. 카드 embed → skill로 **위치만 이동**. 생성 skill 본문 = YAML의 working-method/key-frameworks/evidence-they-use/sources를 그대로 옮긴 것. - **공통 하네스 불변식 재복제 금지**: 고유관점/비종합/근거+반증/실물≠요약은 카드의 기존 전용 섹션(`## When invoked`·`## Fan-out 워커 계약`·`## Output contract`)에만 유지. spine·생성 skill self-check에 재나열 금지. - **spine = 역할-파생 얇은 3줄**: `working-method[0]`(essence) + `key-frameworks[:3]` **이름만**(정의 문장 없음) + method-skill pointer/load-guard. 그 외 절차 문장 금지. - **생성물 편집 금지**: `.claude/skills/generated/*/SKILL.md`·`.claude/agents/*.md`는 생성물. 헤더 경고 + `--check` drift 게이트. 수기 편집 금지. - **협업역할 3정책**: 워커(43)=자기 method-skill / lead(3)=자기 method-skill만 / router(10)=멤버 method 제거·pointer table·family capability만 / family agent(16, 실제 수행자)=멤버 method-skill union + family capability. - **불변 개수**: 에이전트 72(43 워커 + 3 lead + 16 family + 10 router), 역할 75, method-skill 75(=agent-bound 역할). 75==75==75. - **registry 위치**: `org-os/00-role-registry/method-skill-registry.yaml`(다른 registry와 동거). generated-dir: `.claude/skills/generated`(Phase 0 확정). - **실행 환경**: 테스트는 `CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=_sandbox`. 커밋 trailer: `Co-Authored-By: Claude Opus 4.8 `. 커밋 전 churn된 render fixture는 `git checkout -- .claude/tests/fixtures/`로 복구. - **생성 순서**: gen_method_skills(skill 먼저 실존) → gen_agents(카드가 skill 참조). doctor가 순서 위반(참조 있는데 skill 없음)을 FAIL. ## File Structure **신설:** - `org-os/00-role-registry/method-skill-registry.yaml` — role→method-skill(+capability-skills), family→policy 배선 SoT. - `.claude/hooks/gen_method_skills.py` — role-working-methods.yaml → 생성 method-skill(+`--check` drift). - `.claude/hooks/skill_refs.py` — 공유 헬퍼(`.claude/skills/**` 스캔 → skill 이름 집합, `skills:` frontmatter 파싱). doctor·lint_refs가 소비. - `.claude/skills/generated/-method/SKILL.md` × 75 — 생성물. - `.claude/tests/test_p3_prompt_skill_separation.py` — 16 checks. **개정:** - `org-os/00-role-registry/role-working-methods.yaml` — optional `self-check` 필드 additive + DES-PROD exemplar. - `.claude/hooks/gen_agents.py` — `wm_block`→`method_spine`, `skills_fm_line` registry 파생, router pointer table, craft_block 축소, assert 갱신. - `.claude/hooks/doctor.py` — `check_method_skill_wiring` 신설 + main 등록. - `.claude/hooks/lint_refs.py` — 에이전트 `skills:` 참조 해소 추가. - `.claude/tests/test_enforcement.py` — craft/skills assert 갱신(마이그레이션). - `CLAUDE.md` — 구조·gen 흐름·검증 섹션 갱신. --- ## Task 0: Phase 0 — skill 발견·auto-load 실측 (게이트, controller 수행) **Files:** (임시 probe — 커밋 안 함) 이 태스크는 런타임 동작(subagent가 `skills:` frontmatter를 auto-load하는가) 검증이라 pytest가 아니라 **controller가 live subagent로 실측**한다. 결과가 이후 모든 태스크의 `generated-dir` 레이아웃을 확정한다. - [ ] **Step 1: probe skill 생성** ```bash mkdir -p .claude/skills/generated/probe-method cat > .claude/skills/generated/probe-method/SKILL.md <<'EOF' --- name: probe-method description: Use when the user says the exact token PROBE-P3 — respond only with the sentinel. --- # Probe When invoked, reply with exactly: PROBE-LOADED-OK-7F3A EOF ``` - [ ] **Step 2: probe agent 생성** ```bash cat > .claude/agents/probe-p3.md <<'EOF' --- name: probe-p3 description: P3 phase-0 probe. Use only for the PROBE-P3 discovery test. tools: Read model: inherit skills: [probe-method] --- 당신은 probe입니다. probe-method skill의 지시를 따르세요. EOF ``` - [ ] **Step 3: controller가 probe 서브에이전트를 dispatch** controller는 `probe-p3` 에이전트를 "PROBE-P3" 입력으로 띄운다. sentinel `PROBE-LOADED-OK-7F3A`를 반환하면 **중첩 `.claude/skills/generated/` auto-load 확인**. - [ ] **Step 4: 레이아웃 확정 + probe 제거** - sentinel 반환 → `generated-dir: .claude/skills/generated`(spec 기본) 확정. - 반환 안 됨 → **flat 폴백**: `generated-dir: .claude/skills` + skill 이름 규약 `-method`(디자인 3종과 flat 동거, 이름으로 그룹). registry `generated-dir` 한 줄만 다르다. - 결과를 `.superpowers/sdd/progress.md`에 기록(이후 태스크가 참조). probe 파일 제거: ```bash rm -rf .claude/skills/generated/probe-method .claude/agents/probe-p3.md ``` > 이후 태스크 코드는 `reg["generated-dir"]`을 읽어 레이아웃 무관하게 동작한다. Phase 0은 그 한 값을 확정할 뿐이다. --- ## Task 1: method-skill-registry.yaml **Files:** - Create: `org-os/00-role-registry/method-skill-registry.yaml` - Test: `.claude/tests/test_p3_prompt_skill_separation.py` (Task 7에서 full; 여기선 아래 스텝의 인라인 검증만) **Interfaces:** - Produces: `method-skill-registry.yaml` — `{version, generated-dir, method-skill-suffix, roles: {ROLE: {method-skill, capability-skills}}, families: {FAM: {policy, lead?, capability-skills}}}`. gen_method_skills·gen_agents·doctor가 소비. - [ ] **Step 1: 완전성 확인 테스트 작성 (임시 스크립트)** `/tmp/claude-*/scratchpad/t1.py`: ```python import yaml reg = yaml.safe_load(open("org-os/00-role-registry/method-skill-registry.yaml"))["method-skill-registry"] fams = yaml.safe_load(open("org-os/00-role-registry/capability-families.yaml"))["capability-families"]["families"] bound = set() for f in fams: bound |= set(f["member-role-ids"]) roles = reg["roles"] assert bound == set(roles), f"registry roles != agent-bound: {bound ^ set(roles)}" assert all(r["method-skill"] == rid.lower()+"-method" for rid, r in roles.items()), "method-skill 이름 규약 위반" famset = {f["family-id"] for f in fams} assert set(reg["families"]) == famset, "families 키 불일치" print("OK task1 registry", len(roles), "roles", len(reg["families"]), "families") ``` - [ ] **Step 2: 실패 확인** Run: `python3 /tmp/claude-*/scratchpad/t1.py` Expected: FAIL (파일 없음). - [ ] **Step 3: registry 작성** `org-os/00-role-registry/method-skill-registry.yaml` (아래 전체 — 75 roles + 28 families): ```yaml # method-skill-registry — role→skill 배선의 단일 정본(SoT). (P3) # role-working-methods.yaml 이 절차 SoT, gen_method_skills 가 method-skill 을 생성, # gen_agents 가 이 파일로 카드의 skills: frontmatter 를 방출한다. # 기존 gen_agents 하드코딩(CRAFT/SKILLS_FM/IMPL_FAMILIES)을 이 registry 로 흡수·단일화. # capability-skills = 수제 공용 skill(design-craft/build-loop/diagram-craft). # 멤버 method-skill 은 member-role-ids 에서 파생하므로 families 에 재나열하지 않는다. method-skill-registry: version: 1 generated-dir: .claude/skills/generated # Phase 0(Task 0)에서 확정. flat 폴백이면 .claude/skills method-skill-suffix: -method roles: EXEC-CEO: { method-skill: exec-ceo-method, capability-skills: [] } OPS-ORCH: { method-skill: ops-orch-method, capability-skills: [] } EXEC-CTO: { method-skill: exec-cto-method, capability-skills: [] } EXEC-CPO: { method-skill: exec-cpo-method, capability-skills: [] } EXEC-CFO: { method-skill: exec-cfo-method, capability-skills: [] } EXEC-COO: { method-skill: exec-coo-method, capability-skills: [] } EXEC-CPTO: { method-skill: exec-cpto-method, capability-skills: [] } EXEC-VPENG: { method-skill: exec-vpeng-method, capability-skills: [] } PROD-PM: { method-skill: prod-pm-method, capability-skills: [] } PROD-PO: { method-skill: prod-po-method, capability-skills: [] } PROD-TPO: { method-skill: prod-tpo-method, capability-skills: [] } PROD-PPO: { method-skill: prod-ppo-method, capability-skills: [] } UX-RESEARCHER: { method-skill: ux-researcher-method, capability-skills: [] } DATA-ANALYST: { method-skill: data-analyst-method, capability-skills: [] } DES-DIRECTOR: { method-skill: des-director-method, capability-skills: [] } DES-PROD: { method-skill: des-prod-method, capability-skills: [design-craft] } DES-PLATFORM: { method-skill: des-platform-method, capability-skills: [design-craft] } DES-INTERNAL: { method-skill: des-internal-method, capability-skills: [design-craft] } DES-VISUAL: { method-skill: des-visual-method, capability-skills: [design-craft] } STR-ANALYST: { method-skill: str-analyst-method, capability-skills: [] } ENG-FE: { method-skill: eng-fe-method, capability-skills: [] } ENG-FEPLAT: { method-skill: eng-feplat-method, capability-skills: [] } ENG-FEUX: { method-skill: eng-feux-method, capability-skills: [] } ENG-BE: { method-skill: eng-be-method, capability-skills: [] } ENG-BEGEN: { method-skill: eng-begen-method, capability-skills: [] } ENG-PRODSERVER: { method-skill: eng-prodserver-method, capability-skills: [] } ENG-PLATSERVER: { method-skill: eng-platserver-method, capability-skills: [] } ENG-PRODUCTMINDED: { method-skill: eng-productminded-method, capability-skills: [] } ENG-SW: { method-skill: eng-sw-method, capability-skills: [] } ENG-DESKTOP: { method-skill: eng-desktop-method, capability-skills: [] } ENG-PRODCHAPTER: { method-skill: eng-prodchapter-method, capability-skills: [] } INFRA-DEV: { method-skill: infra-dev-method, capability-skills: [] } INFRA-PLATFORM: { method-skill: infra-platform-method, capability-skills: [] } INFRA-DEVOPS: { method-skill: infra-devops-method, capability-skills: [] } SRE: { method-skill: sre-method, capability-skills: [] } SEC-DEVSECOPS: { method-skill: sec-devsecops-method, capability-skills: [] } ARCH-EA: { method-skill: arch-ea-method, capability-skills: [] } ARCH-SOLUTION: { method-skill: arch-solution-method, capability-skills: [] } ARCH-APP: { method-skill: arch-app-method, capability-skills: [] } ARCH-TECH: { method-skill: arch-tech-method, capability-skills: [] } ARCH-IT: { method-skill: arch-it-method, capability-skills: [] } ARCH-SYSANALYST: { method-skill: arch-sysanalyst-method, capability-skills: [] } ARCH-SWAT: { method-skill: arch-swat-method, capability-skills: [] } ARCH-BA: { method-skill: arch-ba-method, capability-skills: [] } ARCH-BIZANALYST: { method-skill: arch-bizanalyst-method, capability-skills: [] } ARCH-DATA: { method-skill: arch-data-method, capability-skills: [] } DATA-ENGINEER: { method-skill: data-engineer-method, capability-skills: [] } DATA-BIGDATA: { method-skill: data-bigdata-method, capability-skills: [] } QA: { method-skill: qa-method, capability-skills: [] } SEC-ENGINEER: { method-skill: sec-engineer-method, capability-skills: [] } SEC-APPSEC: { method-skill: sec-appsec-method, capability-skills: [] } SEC-CHAMPION: { method-skill: sec-champion-method, capability-skills: [] } OPS-CH: { method-skill: ops-ch-method, capability-skills: [] } OPS-CREW: { method-skill: ops-crew-method, capability-skills: [] } GTM-GROWTHPM: { method-skill: gtm-growthpm-method, capability-skills: [] } GTM-DEMANDGEN: { method-skill: gtm-demandgen-method, capability-skills: [] } GTM-PMM: { method-skill: gtm-pmm-method, capability-skills: [] } GTM-CI: { method-skill: gtm-ci-method, capability-skills: [] } GTM-SALES: { method-skill: gtm-sales-method, capability-skills: [] } GTM-CS: { method-skill: gtm-cs-method, capability-skills: [] } GTM-PARTNER: { method-skill: gtm-partner-method, capability-skills: [] } GTM-REVOPS: { method-skill: gtm-revops-method, capability-skills: [] } GTM-PRICING: { method-skill: gtm-pricing-method, capability-skills: [] } GTM-LEGAL: { method-skill: gtm-legal-method, capability-skills: [] } CONSULT-EM: { method-skill: consult-em-method, capability-skills: [] } CONSULT-STRAT: { method-skill: consult-strat-method, capability-skills: [] } CONSULT-OPS: { method-skill: consult-ops-method, capability-skills: [] } CONSULT-ORG: { method-skill: consult-org-method, capability-skills: [] } CONSULT-DIGITAL: { method-skill: consult-digital-method, capability-skills: [] } CONSULT-FIN: { method-skill: consult-fin-method, capability-skills: [] } DOC-LEAD: { method-skill: doc-lead-method, capability-skills: [] } DOC-WRITER: { method-skill: doc-writer-method, capability-skills: [] } DOC-IA: { method-skill: doc-ia-method, capability-skills: [] } DOC-VISUAL: { method-skill: doc-visual-method, capability-skills: [design-craft, diagram-craft] } DOC-EDU: { method-skill: doc-edu-method, capability-skills: [] } families: FAM-CEO: { policy: fan-out, capability-skills: [] } FAM-ORCH: { policy: n/a, capability-skills: [] } FAM-CTO: { policy: fan-out, capability-skills: [] } FAM-CPO: { policy: fan-out, capability-skills: [] } FAM-CFO: { policy: fan-out, capability-skills: [] } FAM-COO: { policy: fan-out, capability-skills: [] } FAM-CPTO: { policy: fan-out, capability-skills: [] } FAM-VPENG: { policy: fan-out, capability-skills: [] } FAM-PRODUCT-MGMT: { policy: fan-out, capability-skills: [] } FAM-UX-RESEARCH: { policy: fan-out, capability-skills: [] } FAM-DESIGN: { policy: fan-out, lead: DES-DIRECTOR, capability-skills: [] } FAM-STRATEGY: { policy: fan-out, capability-skills: [] } FAM-ENG-FRONTEND: { policy: collapse, capability-skills: [build-loop] } FAM-ENG-BACKEND: { policy: collapse, capability-skills: [build-loop] } FAM-ENG-SPECIAL: { policy: collapse, capability-skills: [build-loop] } FAM-PLATFORM-INFRA: { policy: collapse, capability-skills: [build-loop] } FAM-ARCHITECTURE-TECH: { policy: fan-out, capability-skills: [] } FAM-ARCHITECTURE-BIZ: { policy: fan-out, capability-skills: [] } FAM-DATA: { policy: fan-out, capability-skills: [] } FAM-QA: { policy: collapse, capability-skills: [] } FAM-SECURITY: { policy: fan-out, capability-skills: [] } FAM-OPS-DELIVERY: { policy: collapse, capability-skills: [] } FAM-GTM-GROWTH: { policy: fan-out, capability-skills: [] } FAM-GTM-SALES: { policy: fan-out, capability-skills: [] } FAM-REVOPS: { policy: fan-out, capability-skills: [] } FAM-LEGAL: { policy: fan-out, capability-skills: [] } FAM-CONSULTING: { policy: fan-out, lead: CONSULT-EM, capability-skills: [] } FAM-DOC-CONSULT: { policy: fan-out, lead: DOC-LEAD, capability-skills: [] } ``` > 참고: 이 내용은 `capability-families.yaml`의 member-role-ids로부터 기계적으로 산출된 것(계획 저자가 스크립트로 생성). 이후 새 역할이 생기면 이 파일을 직접 편집하고 doctor가 sync를 강제한다. - [ ] **Step 4: 통과 확인** Run: `python3 /tmp/claude-*/scratchpad/t1.py` Expected: `OK task1 registry 75 roles 28 families` - [ ] **Step 5: Commit** ```bash git add org-os/00-role-registry/method-skill-registry.yaml git commit -m "P3 Task1: method-skill-registry.yaml (75 roles + 28 families, CRAFT/IMPL 흡수) Co-Authored-By: Claude Opus 4.8 " ``` --- ## Task 2: gen_method_skills.py + optional self-check 필드 + 75 skill 생성 **Files:** - Create: `.claude/hooks/gen_method_skills.py` - Modify: `org-os/00-role-registry/role-working-methods.yaml` (DES-PROD에 optional `self-check` 추가 + field-schema 주석) - Create: `.claude/skills/generated/-method/SKILL.md` × 75 (생성물) **Interfaces:** - Produces: `gen_method_skills.build_all() -> {skill_name: content_str}`; CLI `--check`(drift, exit 1 on mismatch) / 무인자(생성). doctor가 `--check`를 subprocess로 호출. - [ ] **Step 1: self-check 필드 추가 (role-working-methods.yaml)** `field-schema` 블록에 한 줄 추가(파일 상단): ```yaml self-check: (선택) 역할 고유 자기검증 문항 — 공통 하네스 불변식은 넣지 않는다(카드 계약 섹션이 담당) ``` DES-PROD 엔트리 끝(`sources:` 리스트 다음)에 exemplar 추가: ```yaml self-check: - 사용자 행동 근거 없이 시각 취향으로 결정하지 않았나? - 핵심 흐름과 예외 상태를 모두 설계했나? - generic한 "modern/clean" 표현으로 방향을 대체하지 않았나? ``` - [ ] **Step 2: 실패 테스트 작성 (임시)** `/tmp/claude-*/scratchpad/t2.py`: ```python import importlib.util, os spec = importlib.util.spec_from_file_location("g", ".claude/hooks/gen_method_skills.py") g = importlib.util.module_from_spec(spec); spec.loader.exec_module(g) out = g.build_all() assert len(out) == 75, len(out) c = out["des-prod-method"] assert c.startswith("---\nname: des-prod-method\n"), "frontmatter" assert "generated-from: role-working-methods.yaml#DES-PROD" in c assert "Double Diamond" in c, "working-method 내용 보존" assert "## 자기검증" in c and "시각 취향" in c, "self-check role-specific" # 공통 불변식은 self-check 에 없어야 assert "다른 역할" not in c.split("## 자기검증")[1], "공통 불변식 재복제 금지" # self-check 없는 역할은 섹션 생략 assert "## 자기검증" not in out["arch-app-method"], "self-check optional" print("OK task2", len(out)) ``` - [ ] **Step 3: 실패 확인** Run: `python3 /tmp/claude-*/scratchpad/t2.py` Expected: FAIL (모듈 없음). - [ ] **Step 4: gen_method_skills.py 작성** ```python #!/usr/bin/env python3 """Generate .claude/skills/generated/-method/SKILL.md from role-working-methods.yaml. (P3) 절차(How I work) 층. 카드에 인라인 embed 하던 working-method 를 역할별 method-skill 로 분리한다. role-working-methods.yaml = 유일 편집 SoT, 이 스크립트 = 생성물(수기편집 금지). Usage: python3 .claude/hooks/gen_method_skills.py # (재)생성 python3 .claude/hooks/gen_method_skills.py --check # drift 검증만(파일 안 씀, exit 1 on mismatch) """ import glob import os import shutil import sys import yaml ROOT = os.environ.get("CLAUDE_PROJECT_DIR") or os.path.dirname( os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) REG = os.path.join(ROOT, "org-os", "00-role-registry") WORKING_METHODS = os.path.join(REG, "role-working-methods.yaml") PROFILES = os.path.join(REG, "role-profiles.yaml") REGISTRY = os.path.join(REG, "method-skill-registry.yaml") GEN_HEADER = ("") def _load(p): return yaml.safe_load(open(p)) or {} def _registry(): return _load(REGISTRY)["method-skill-registry"] def _gen_dir(): return os.path.join(ROOT, _registry()["generated-dir"]) def _first_sentence(text, n=140): s = str(text or "").strip().split(". ")[0] return (s[:n] + "…") if len(s) > n else s def method_skill_md(rid, wm, prof, skill_name): role_name = prof.get("role-name", rid) desc = (f"Use when working AS the {role_name} ({rid}) role — the step-by-step working " f"method, frameworks, and evidence types for this role. " f"Auto-loaded via the {rid.lower()} agent's skills: frontmatter.") lines = [ "---", f"name: {skill_name}", f"description: \"{desc.replace(chr(34), chr(39))}\"", f"generated-from: role-working-methods.yaml#{rid}", "---", GEN_HEADER, "", f"# {role_name} ({rid}) 실무 절차 (일하는 방식)", "", "## 절차 (working-method)", ] for s in (wm.get("working-method") or []): lines.append(f"- {s}") if wm.get("key-frameworks"): lines += ["", "## 주요 프레임워크"] lines += [f"- {f}" for f in wm["key-frameworks"]] if wm.get("evidence-they-use"): lines += ["", "## 판단 근거 자료 (evidence)"] lines += [f"- {e}" for e in wm["evidence-they-use"]] if wm.get("sources"): lines += ["", "## 참고 출처"] lines += [f"- {u}" for u in wm["sources"]] if wm.get("self-check"): # optional, role-specific only (§5). 공통 불변식 금지. lines += ["", "## 자기검증 (self-check) — 역할 고유 검증만"] lines += [f"- {c}" for c in wm["self-check"]] return "\n".join(lines).rstrip() + "\n" def build_all(): """{skill_name: content_str} for every registry role (=agent-bound). SoT=YAML.""" wm_root = _load(WORKING_METHODS).get("role-working-methods") or {} profiles = {p["role-id"]: p for p in _load(PROFILES)["role-profiles"]["profiles"]} roles = _registry()["roles"] out = {} for rid, r in roles.items(): wm = wm_root.get(rid) assert wm, f"role-working-methods.yaml 에 {rid} 없음(registry가 참조)" out[r["method-skill"]] = method_skill_md(rid, wm, profiles.get(rid, {}), r["method-skill"]) return out def main(): check = "--check" in sys.argv out = build_all() gen_dir = _gen_dir() if check: problems = [] for skill, content in out.items(): p = os.path.join(gen_dir, skill, "SKILL.md") if not os.path.exists(p): problems.append(f"missing: {skill}") elif open(p).read() != content: problems.append(f"drift: {skill}") for d in glob.glob(os.path.join(gen_dir, "*", "SKILL.md")): name = os.path.basename(os.path.dirname(d)) # flat 폴백 시 수제 skill(design-craft 등)은 제외 — method-skill 만 대상 if name.endswith("-method") and name not in out: problems.append(f"orphan: {name}") if problems: print("GEN-METHOD-SKILLS CHECK FAIL: %d건" % len(problems)) for p in problems: print(f" - {p}") return 1 print(f"OK gen_method_skills --check: {len(out)} method-skills match SoT") return 0 # write: 기존 method-skill 디렉터리만 정리(수제 skill 보존) for d in glob.glob(os.path.join(gen_dir, "*")): if os.path.isdir(d) and os.path.basename(d).endswith("-method"): shutil.rmtree(d) for skill, content in out.items(): sd = os.path.join(gen_dir, skill) os.makedirs(sd, exist_ok=True) with open(os.path.join(sd, "SKILL.md"), "w") as f: f.write(content) print(f"OK gen_method_skills: {len(out)} method-skills written -> {gen_dir}") return 0 if __name__ == "__main__": sys.exit(main()) ``` - [ ] **Step 5: 통과 확인 (build_all)** Run: `CLAUDE_PROJECT_DIR="$PWD" python3 /tmp/claude-*/scratchpad/t2.py` Expected: `OK task2 75` - [ ] **Step 6: 75 skill 생성 + drift 자기확인** ```bash CLAUDE_PROJECT_DIR="$PWD" python3 .claude/hooks/gen_method_skills.py CLAUDE_PROJECT_DIR="$PWD" python3 .claude/hooks/gen_method_skills.py --check ls .claude/skills/generated | wc -l # 75 ``` Expected: 생성 75 → `--check` OK → 75. - [ ] **Step 7: Commit** ```bash git add .claude/hooks/gen_method_skills.py org-os/00-role-registry/role-working-methods.yaml .claude/skills/generated git commit -m "P3 Task2: gen_method_skills.py + optional self-check + 75 생성 method-skill Co-Authored-By: Claude Opus 4.8 " ``` --- ## Task 3: skill_refs.py 공유 헬퍼 **Files:** - Create: `.claude/hooks/skill_refs.py` - Test: 임시 인라인. **Interfaces:** - Produces: `known_skill_names(root) -> set[str]`(`.claude/skills/**/SKILL.md` 스캔, frontmatter name + 디렉터리명), `parse_skills(fm_value) -> list[str]`. doctor·lint_refs가 소비. - [ ] **Step 1: 실패 테스트 (임시)** `/tmp/claude-*/scratchpad/t3.py`: ```python import importlib.util, os spec = importlib.util.spec_from_file_location("sr", ".claude/hooks/skill_refs.py") sr = importlib.util.module_from_spec(spec); spec.loader.exec_module(sr) names = sr.known_skill_names(os.getcwd()) assert "des-prod-method" in names and "design-craft" in names, sorted(names)[:5] assert sr.parse_skills(["a", "b"]) == ["a", "b"] assert sr.parse_skills(None) == [] print("OK task3", len(names)) ``` - [ ] **Step 2: 실패 확인** — Run: `python3 /tmp/claude-*/scratchpad/t3.py` → FAIL(모듈 없음). - [ ] **Step 3: skill_refs.py 작성** ```python #!/usr/bin/env python3 """skill_refs — .claude/skills 참조 무결성 공유 헬퍼 (P3). doctor·lint_refs 소비.""" import glob import os import yaml def known_skill_names(root): """.claude/skills/**/SKILL.md 를 스캔해 알려진 skill 이름 집합(frontmatter name + 디렉터리명).""" names = set() base = os.path.join(root, ".claude", "skills") for p in glob.glob(os.path.join(base, "**", "SKILL.md"), recursive=True): names.add(os.path.basename(os.path.dirname(p))) try: fm = yaml.safe_load(p and open(p).read().split("---\n")[1]) or {} if isinstance(fm, dict) and fm.get("name"): names.add(str(fm["name"])) except Exception: # noqa: BLE001 — 깨진 frontmatter는 디렉터리명으로만 등록 pass return names def parse_skills(val): """agent frontmatter 의 skills: 값 -> 이름 리스트. list/문자열('[a, b]') 모두 허용.""" if not val: return [] if isinstance(val, list): return [str(x).strip() for x in val if str(x).strip()] return [s.strip() for s in str(val).strip("[]").split(",") if s.strip()] ``` - [ ] **Step 4: 통과 확인** — Run: `python3 /tmp/claude-*/scratchpad/t3.py` → `OK task3 `. - [ ] **Step 5: Commit** ```bash git add .claude/hooks/skill_refs.py git commit -m "P3 Task3: skill_refs.py 공유 헬퍼(skill 이름 스캔·skills frontmatter 파싱) Co-Authored-By: Claude Opus 4.8 " ``` --- ## Task 4: gen_agents.py 개정 (spine + registry skills + router pointer) + 72 재생성 **Files:** - Modify: `.claude/hooks/gen_agents.py` - Regenerate: `.claude/agents/*.md` (72) **Interfaces:** - Consumes: `method-skill-registry.yaml`(MREG), `skill_refs`(불요), `role-working-methods.yaml`(wm_map). - Produces: 72 카드 — 워커/lead에 `skills:[method,..]` + `## 핵심 작업 방법`; router에 `skills:[family capability]` + `## fan-out 멤버 → method-skill`; family에 `skills:[member methods union,..]` + spine. `## 일하는 방식` full embed 제거. - [ ] **Step 1: 실패 테스트 (임시)** `/tmp/claude-*/scratchpad/t4.py`: ```python import subprocess, os r = subprocess.run(["python3", ".claude/hooks/gen_agents.py"], env={**os.environ, "CLAUDE_PROJECT_DIR": os.getcwd()}, capture_output=True, text=True) print(r.stdout, r.stderr) assert r.returncode == 0, "gen_agents failed" dp = open(".claude/agents/des-prod.md").read() assert "## 핵심 작업 방법" in dp and "## 일하는 방식" not in dp, "spine not swapped" assert "des-prod-method" in dp and "design-craft" in dp, "skills" # 공통 불변식 spine 재나열 금지 spine = dp.split("## 핵심 작업 방법")[1].split("##")[0] assert "다른 역할" not in spine and "반증" not in spine, "공통 불변식 spine 재복제" fd = open(".claude/agents/fam-design.md").read() # router assert "fan-out 멤버 → method-skill" in fd, "router pointer" assert "## 일하는 방식" not in fd, "router still has member method embed" assert "des-prod-method" in fd, "router pointer names skill" fb = open(".claude/agents/fam-eng-backend.md").read() # collapse family assert "eng-be-method" in fb, "collapse family loads member method-skill" print("OK task4") ``` - [ ] **Step 2: 실패 확인** — Run: `python3 /tmp/claude-*/scratchpad/t4.py` → FAIL(현재 카드엔 `## 일하는 방식`). - [ ] **Step 3: gen_agents.py 개정** **(a) 모듈 상단** — registry 로드용 전역 추가 + frontmatter 하드코딩 dict 제거. **`SKILLS_FM`·`IMPL_FAMILIES` 삭제**(registry가 대체). **`CRAFT` dict은 유지**(`craft_block`이 body 리마인더 선택에 계속 사용 — frontmatter가 아님). 아래 추가(파일 상단 `TOOLS = {}` 근처): ```python MREG = {} # method-skill-registry (main 에서 로드; TOOLS 패턴과 동일) REGISTRY = os.path.join(REG, "method-skill-registry.yaml") def load_method_registry(fams): root = yaml.safe_load(open(REGISTRY))["method-skill-registry"] roles, families = root["roles"], root["families"] fam_ids = {f["family-id"] for f in fams} for fid in families: assert fid in fam_ids, f"method-skill-registry.families 미지 family-id: {fid}" bound = set() for f in fams: bound |= set(f["member-role-ids"]) missing = bound - set(roles) assert not missing, f"method-skill-registry.roles 미등록 agent-bound 역할: {sorted(missing)}" return root def _fw_name(fw): """key-frameworks 항목에서 이름만 추출('OKR (전략...)' -> 'OKR', 'design-brief (...): ...' -> 'design-brief').""" return str(fw).split(" (")[0].split(":")[0].strip() def method_spine(rids, wm_map, names=None): """카드에 남는 얇은 절차 잔여(역할-파생). 공통 불변식 재나열 안 함(§7).""" entries = [(rid, wm_map.get(rid)) for rid in rids if wm_map.get(rid)] if not entries: return "" multi = len(entries) > 1 lines = ["## 핵심 작업 방법 (전체 절차는 skill)"] for rid, wm in entries: if multi: lines.append(f"### {(names or {}).get(rid, rid)}") wmlist = wm.get("working-method") or [] if wmlist: lines.append(f"- 핵심 접근: {wmlist[0]}") fws = wm.get("key-frameworks") or [] if fws: lines.append("- 주요 프레임워크: " + ", ".join(_fw_name(f) for f in fws[:3])) skill = MREG["roles"][rid]["method-skill"] lines.append(f"- 전체 실무 절차·체크리스트·자기검증·handoff는 `{skill}` skill을 따른다. " "skill 미적재 시 작업 시작 금지.") if multi: lines.append("") return "\n".join(lines).rstrip() ``` **(b) `wm_block` 함수 삭제** (lines 87-106). `method_spine`이 대체. **(c) `skills_fm_line` 함수 교체** (기존 133-140 전체 교체): ```python def skills_fm_line(kind, *, rid=None, fid=None, members=None): """registry 파생 skills: frontmatter 줄. kind: role|lead|router|family.""" roles, families = MREG["roles"], MREG["families"] sk = [] if kind in ("role", "lead"): sk.append(roles[rid]["method-skill"]) sk += roles[rid].get("capability-skills") or [] elif kind == "router": sk += (families.get(fid) or {}).get("capability-skills") or [] elif kind == "family": for m in members: sk.append(roles[m]["method-skill"]) sk += roles[m].get("capability-skills") or [] sk += (families.get(fid) or {}).get("capability-skills") or [] sk = dedup(sk) return f"skills: [{', '.join(sk)}]\n" if sk else "" ``` **(d) `craft_block` 축소** (155-160 반환 문자열 교체 — design-craft가 skills:에 이미 있으므로 1줄 pointer + anti-generic self-check만): ```python return ("## 디자인 craft 표준 (skill `design-craft` 참조)\n" "- 제약>묘사: design-brief(무엇/누구/달성)→references(구체 신호, 'modern/clean' 금지)" "→tokens(값+의도+경계)→decisions→donts. 전체 절차는 design-craft skill.\n" "- anti-generic self-check: 내 산출을 'modern/clean'으로 설명할 수 있으면 generic이다.") ``` 그리고 diagram-craft 분기(148-154)도 축소: ```python if skill == "diagram-craft": return ("## 다이어그램 craft 표준 (skill `diagram-craft`+`design-craft` 참조)\n" "- abstraction-first(C4 레벨·독자·메시지 먼저), one diagram one message.\n" "- 엔진: **D2 우선**(아키텍처·의존성·중첩) → Excalidraw → Mermaid(폴백만). " "Mermaid로 도망치지 않는다. 전체 절차는 diagram-craft skill.") ``` **(e) `build_role_agent`** (280-341): `wm_text = wm_block([rid], wm_map or {})` → `wm_text = method_spine([rid], wm_map or {})`. frontmatter `{skills_fm_line(rid, fid)}` → `{skills_fm_line('role', rid=rid)}`. **(f) `build_lead_agent`** (344-412): `wm_text = wm_block([rid], wm_map or {})` → `method_spine([rid], wm_map or {})`. lead frontmatter에 skills 줄이 없으므로 **추가**: fm 문자열의 `f"role-id: {rid}\n"` 다음에 `f"{skills_fm_line('lead', rid=rid)}"` 삽입. **(g) `build_router_agent`** (415-504): 멤버 method embed 제거 → pointer table. - `wm_text = wm_block(members, wm_map or {}, names)` / `wm_section = ...` 블록(468-469)을 아래로 교체: ```python pointer_lines = ["## fan-out 멤버 → method-skill (라우팅 대상)"] for rid in members: pointer_lines.append( f"- {profiles[rid]['role-name']} ({rid}) — agent: {rid.lower()}, " f"skill: {MREG['roles'][rid]['method-skill']}") wm_section = "\n".join(pointer_lines) + "\n\n" ``` - router frontmatter에 skills 추가: fm 문자열 `f"family: {fid}\n"` 다음에 `f"{skills_fm_line('router', fid=fid)}"` 삽입. - 단독 경로 문구(484): "위 역할별 관점·시야·책임을 각 역할 섹션으로 구조화해" 뒤에 "(각 멤버의 method-skill을 이름으로 로드)" 추가. **(h) `build_agent`** (175-272, family agent): `wm_text = wm_block(members, wm_map or {}, names)` → `method_spine(members, wm_map or {}, names)`. frontmatter `{skills_fm_line(fid, fid)}` → `{skills_fm_line('family', fid=fid, members=members)}`. **(i) `main`** — registry 로드 + 전역 세팅. `wm_map = load_working_methods()` 다음에: ```python global MREG MREG = load_method_registry(fams) ``` **(j) assert 갱신** (605-606): ```python if wm_map: # P3: working-method 는 이제 method-skill 로 분리 — 카드엔 spine + skills 참조 assert "## 핵심 작업 방법" in content, f"{name} missing method spine" meta2 = yaml.safe_load(content.split("---\n")[1]) if kind in ("role", "lead", "family"): assert meta2.get("skills"), f"{name} missing skills frontmatter" assert "## 일하는 방식" not in content, f"{name} still has full method embed" ``` craft assert(591)의 문구가 바뀌었으면 갱신: `"디자인 craft 표준" in content` 유지(문구 보존). doc-visual `"D2" in content and "폴백" in content` 유지. - [ ] **Step 4: 통과 확인 (재생성 + 테스트)** ```bash CLAUDE_PROJECT_DIR="$PWD" python3 .claude/hooks/gen_agents.py # OK ... 72 agents python3 /tmp/claude-*/scratchpad/t4.py # OK task4 ``` Expected: gen_agents 72 written, t4 OK. - [ ] **Step 5: Commit** ```bash git add .claude/hooks/gen_agents.py .claude/agents git commit -m "P3 Task4: gen_agents spine+registry skills(router pointer/collapse union) + 72 재생성 Co-Authored-By: Claude Opus 4.8 " ``` --- ## Task 5: doctor.py check_method_skill_wiring **Files:** - Modify: `.claude/hooks/doctor.py` - Test: 임시(Task 7에 통합). **Interfaces:** - Consumes: registry, `skill_refs.known_skill_names`, gen_method_skills `--check`(subprocess). - Produces: `check_method_skill_wiring(report)` — 완전성·실존·참조해소·고아0·drift0·키정합. main 등록 + section "10.". - [ ] **Step 1: 실패 테스트 (임시)** `/tmp/claude-*/scratchpad/t5.py`: ```python import subprocess, os r = subprocess.run(["python3", ".claude/hooks/doctor.py"], env={**os.environ, "CLAUDE_PROJECT_DIR": os.getcwd()}, capture_output=True, text=True) assert "method-skill 배선(P3)" in r.stdout, "section missing" assert "10. method-skill 배선(P3)" in r.stdout assert "[FAIL]" not in r.stdout.split("method-skill 배선(P3)")[1][:200] or "OK" in r.stdout, r.stdout[-400:] print("OK task5 doctor has P3 section") ``` - [ ] **Step 2: 실패 확인** — Run → FAIL(섹션 없음). - [ ] **Step 3: doctor.py 개정** 상단 import 근처(HOOKS_DIR 정의 이후)에 없음 — `check_method_skill_wiring`를 `check_design_direction_wiring` 다음에 추가: ```python def check_method_skill_wiring(report): """10. method-skill 배선(P3): registry 완전성·실존·참조해소·고아0·drift0·키정합.""" section = "10. method-skill 배선(P3)" if HOOKS_DIR not in sys.path: sys.path.insert(0, HOOKS_DIR) try: import glob as _glob import subprocess as _sp import yaml from skill_refs import known_skill_names, parse_skills reg_path = os.path.join(REPO, "org-os/00-role-registry/method-skill-registry.yaml") if not os.path.exists(reg_path): report.fail(section, "method-skill-registry.yaml 없음"); return reg = yaml.safe_load(open(reg_path))["method-skill-registry"] roles = reg["roles"] gen_dir = os.path.join(REPO, reg["generated-dir"]) fams = yaml.safe_load(open(os.path.join(REPO, "org-os/00-role-registry/capability-families.yaml")))["capability-families"]["families"] bound = set() for f in fams: bound |= set(f["member-role-ids"]) # 1. 완전성 miss = sorted(bound - set(roles)) if miss: report.fail(section, "registry 미등록 역할: " + ", ".join(miss)); return # 6. 키 정합 roles_txt = open(os.path.join(REPO, "org-os/00-role-registry/roles.yaml")).read() badk = [r for r in roles if r not in roles_txt] if badk: report.fail(section, "registry 미지 role-id: " + ", ".join(badk)); return # 2/4. 실존 + 고아 want = {r["method-skill"] for r in roles.values()} miss_sk = sorted(s for s in want if not os.path.exists(os.path.join(gen_dir, s, "SKILL.md"))) if miss_sk: report.fail(section, "생성 skill 파일 없음: " + ", ".join(miss_sk)); return on_disk = {os.path.basename(os.path.dirname(p)) for p in _glob.glob(os.path.join(gen_dir, "*", "SKILL.md")) if os.path.basename(os.path.dirname(p)).endswith("-method")} orphan = sorted(on_disk - want) if orphan: report.fail(section, "고아 생성 skill: " + ", ".join(orphan)); return # 3. 카드 skills: 참조 해소 known = known_skill_names(REPO) unresolved = [] for a in _glob.glob(os.path.join(REPO, ".claude/agents/*.md")): try: fm = yaml.safe_load(open(a).read().split("---\n")[1]) or {} except Exception: # noqa: BLE001 continue for s in parse_skills(fm.get("skills")): if s not in known: unresolved.append(f"{os.path.basename(a)}:{s}") if unresolved: report.fail(section, "미해결 skills 참조: " + ", ".join(unresolved)); return # 5. drift rc = _sp.run([sys.executable, os.path.join(HOOKS_DIR, "gen_method_skills.py"), "--check"], capture_output=True, text=True, env={**os.environ, "CLAUDE_PROJECT_DIR": REPO}) if rc.returncode != 0: report.fail(section, "method-skill drift: " + (rc.stdout or rc.stderr).strip()); return report.ok(section, f"method-skill 배선 OK ({len(roles)} roles · {len(on_disk)} skills · 참조·drift 정상)") except Exception as e: # noqa: BLE001 report.fail(section, f"method-skill 배선 점검 오류: {e}") ``` `main()`의 `sections` 리스트에 추가: `"10. method-skill 배선(P3)",`. 그리고 for 루프 tuple에 `check_method_skill_wiring` 추가: ```python for fn in (check_python_deps, check_workspace, check_lint_refs, check_ssot_consumption, check_company_context_lint, check_venture_bootstrap_wiring, check_design_direction_wiring, check_method_skill_wiring): ``` - [ ] **Step 4: 통과 확인** — Run: `python3 /tmp/claude-*/scratchpad/t5.py` + `CLAUDE_PROJECT_DIR="$PWD" python3 .claude/hooks/doctor.py | grep -A1 "method-skill 배선"` → `[ OK ]`. - [ ] **Step 5: Commit** ```bash git add .claude/hooks/doctor.py git commit -m "P3 Task5: doctor check_method_skill_wiring(완전성·실존·참조·고아·drift·키정합) Co-Authored-By: Claude Opus 4.8 " ``` --- ## Task 6: lint_refs.py 에이전트 skills 참조 해소 **Files:** - Modify: `.claude/hooks/lint_refs.py` **Interfaces:** - Consumes: `skill_refs`. Produces: `check_skill_refs(base) -> list[str]`; main이 커맨드 참조 문제와 병합. - [ ] **Step 1: 실패 테스트 (임시)** `/tmp/claude-*/scratchpad/t6.py`: ```python import importlib.util, os, tempfile, shutil spec = importlib.util.spec_from_file_location("lr", ".claude/hooks/lint_refs.py") lr = importlib.util.module_from_spec(spec); spec.loader.exec_module(lr) assert lr.check_skill_refs(os.getcwd()) == [], "clean tree should pass" # 깨진 참조 주입 p = ".claude/agents/_p3probe.md" open(p, "w").write("---\nname: _p3probe\nskills: [no-such-skill-xyz]\n---\nx") try: probs = lr.check_skill_refs(os.getcwd()) assert any("no-such-skill-xyz" in x for x in probs), probs finally: os.remove(p) print("OK task6") ``` - [ ] **Step 2: 실패 확인** — Run → FAIL(`check_skill_refs` 없음). - [ ] **Step 3: lint_refs.py 개정** import에 추가(상단): 없음 — 함수 내부에서 지연 import. `check_refs` 다음에 추가: ```python def check_skill_refs(root=None): """에이전트 카드 skills: frontmatter → 실존 SKILL.md 해소(빈 리스트=통과).""" base = root or ROOT if HOOKS_DIR not in sys.path: sys.path.insert(0, HOOKS_DIR) try: import yaml from skill_refs import known_skill_names, parse_skills except Exception as e: # noqa: BLE001 return [f"skill_refs 로드 실패: {e}"] known = known_skill_names(base) problems = [] for a in sorted(glob.glob(os.path.join(base, ".claude", "agents", "*.md"))): rel = os.path.relpath(a, base) try: fm = yaml.safe_load(open(a, encoding="utf-8").read().split("---\n")[1]) or {} except Exception as e: # noqa: BLE001 problems.append(f"{rel}: frontmatter 파싱 실패 ({e})") continue for s in parse_skills(fm.get("skills")): if s not in known: problems.append(f"{rel}: skill `{s}` 미존재(.claude/skills/**/SKILL.md 없음)") return problems ``` `main()`을 병합형으로 교체: ```python def main(): problems = check_refs() + check_skill_refs() if problems: print("REF-LINT FAIL: 미해결 참조 %d건" % len(problems)) for p in problems: print(f" - {p}") return 1 n = len(glob.glob(COMMANDS_GLOB)) a = len(glob.glob(os.path.join(AGENTS_DIR, "*.md"))) print(f"OK lint_refs: {n} command 참조 + {a} agent skills 참조 모두 해결됨") return 0 ``` (상단에 `import sys`는 이미 존재. `HOOKS_DIR` 이미 정의됨.) - [ ] **Step 4: 통과 확인** — Run: `python3 /tmp/claude-*/scratchpad/t6.py` + `CLAUDE_PROJECT_DIR="$PWD" python3 .claude/hooks/lint_refs.py` → OK. - [ ] **Step 5: Commit** ```bash git add .claude/hooks/lint_refs.py git commit -m "P3 Task6: lint_refs 에이전트 skills: 참조 해소(깨진 참조 검출) Co-Authored-By: Claude Opus 4.8 " ``` --- ## Task 7: test_p3_prompt_skill_separation.py (16 checks) **Files:** - Create: `.claude/tests/test_p3_prompt_skill_separation.py` **Interfaces:** - Consumes: 전 산출물. `run_all.py`가 자동 발견. - [ ] **Step 1: 테스트 작성** ```python #!/usr/bin/env python3 """P3 프롬프트/skill 분리 강제기 — standalone check(pytest 아님). exit 0=통과.""" import glob import importlib.util import os import subprocess import sys import yaml ROOT = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd()) HOOKS = os.path.join(ROOT, ".claude", "hooks") REG = os.path.join(ROOT, "org-os", "00-role-registry") def _load(mod, path): spec = importlib.util.spec_from_file_location(mod, os.path.join(HOOKS, path)) m = importlib.util.module_from_spec(spec) sys.path.insert(0, HOOKS) spec.loader.exec_module(m) return m G = _load("gen_method_skills", "gen_method_skills.py") SR = _load("skill_refs", "skill_refs.py") LR = _load("lint_refs", "lint_refs.py") passed = failed = 0 def check(name, ok): global passed, failed if ok: passed += 1; print(f" ✅ {name}") else: failed += 1; print(f" ❌ {name}") def read(*p): return open(os.path.join(ROOT, *p), encoding="utf-8").read() reg = yaml.safe_load(read("org-os/00-role-registry/method-skill-registry.yaml"))["method-skill-registry"] roles = reg["roles"] gen_dir = os.path.join(ROOT, reg["generated-dir"]) skills_out = G.build_all() # 1. 생성 완전성 check("75 method-skills generated", len(skills_out) == 75) disk = [os.path.basename(os.path.dirname(p)) for p in glob.glob(os.path.join(gen_dir, "*", "SKILL.md")) if os.path.basename(os.path.dirname(p)).endswith("-method")] check("75 method-skills on disk", len(disk) == 75) _c = skills_out["des-prod-method"] check("skill frontmatter valid", _c.startswith("---\nname: des-prod-method\n") and "generated-from: role-working-methods.yaml#DES-PROD" in _c) # 2. 내용 불변(품질 중립) _wm = yaml.safe_load(read("org-os/00-role-registry/role-working-methods.yaml"))["role-working-methods"]["DES-PROD"] check("skill preserves working-method content", all(s in _c for s in _wm["working-method"])) # 3. --check drift _r = subprocess.run([sys.executable, os.path.join(HOOKS, "gen_method_skills.py"), "--check"], capture_output=True, text=True, env={**os.environ, "CLAUDE_PROJECT_DIR": ROOT}) check("gen_method_skills --check clean", _r.returncode == 0) # 4. registry 완전성·키 정합 fams = yaml.safe_load(read("org-os/00-role-registry/capability-families.yaml"))["capability-families"]["families"] bound = set() for f in fams: bound |= set(f["member-role-ids"]) check("registry covers all agent-bound roles", bound == set(roles)) _rtxt = read("org-os/00-role-registry/roles.yaml") check("registry keys ⊆ roles.yaml", all(r in _rtxt for r in roles)) # 5. gen_agents 방출: 워커 spine + skills, no full embed _dp = read(".claude/agents/des-prod.md") check("worker card has spine + skills, no full embed", "## 핵심 작업 방법" in _dp and "des-prod-method" in _dp and "## 일하는 방식" not in _dp) # 6. spine 얇음·공통불변식 재나열 금지·프레임워크 이름만 _spine = _dp.split("## 핵심 작업 방법")[1].split("\n## ")[0] check("spine has no common-invariant restatement", "다른 역할" not in _spine and "반증" not in _spine and "실물" not in _spine) check("spine frameworks names-only (no definition sentence)", "제약>묘사" not in _spine) # 7. skill self-check 정책: 역할 고유만, 없으면 생략 check("skill self-check role-specific, no common invariant", "## 자기검증" in _c and "시각 취향" in _c and "다른 역할" not in _c.split("## 자기검증")[1]) check("self-check optional (arch-app omits)", "## 자기검증" not in skills_out["arch-app-method"]) # 8. router 정책 _fd = read(".claude/agents/fam-design.md") check("router: member pointer table, no member method embed, no member method-skill in skills", "fan-out 멤버 → method-skill" in _fd and "## 일하는 방식" not in _fd and "\nskills: [des-prod-method" not in _fd) # 9. lead 정책 _ld = read(".claude/agents/des-director.md") check("lead: own method-skill only, no member method-skill in skills", "des-director-method" in _ld and "des-prod-method" not in _ld.split("---\n")[1]) # 10. collapse/family 정책(수행능력 보존) _fb = read(".claude/agents/fam-eng-backend.md") check("collapse family loads member method-skill union", "eng-be-method" in _fb) # 11. capability-skill 흡수 check("des-prod skills include design-craft", "design-craft" in _dp.split("---\n")[1]) check("IMPL family includes build-loop", "build-loop" in _fb.split("---\n")[1]) # 12. 하네스 불변식 잔류 check("harness invariants remain in card sections", "## Output contract" in _dp and "primary-artifacts" in _dp and "## When invoked" in _dp) # 13. doctor 게이트 OK _dr = subprocess.run([sys.executable, os.path.join(HOOKS, "doctor.py")], capture_output=True, text=True, env={**os.environ, "CLAUDE_PROJECT_DIR": ROOT}) check("doctor method-skill wiring OK", "method-skill 배선 OK" in _dr.stdout and _dr.returncode == 0) # 14. lint_refs skills 참조 clean check("lint_refs skill refs clean", LR.check_skill_refs(ROOT) == []) # 15. 개수 계약 check("72 agents · 75 method-skills", len(glob.glob(os.path.join(ROOT, ".claude/agents/*.md"))) == 72 and len(skills_out) == 75) # 16. 전 카드 spine + no full embed _bad = [os.path.basename(a) for a in glob.glob(os.path.join(ROOT, ".claude/agents/*.md")) if "## 일하는 방식" in read(".claude/agents", os.path.basename(a))] check("no agent retains full method embed", _bad == []) print(f"\n{passed} passed · {failed} failed") sys.exit(1 if failed else 0) ``` - [ ] **Step 2: 실행** Run: `CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=_sandbox python3 .claude/tests/test_p3_prompt_skill_separation.py` Expected: `16 passed · 0 failed`. (실패 항목이 있으면 해당 산출 태스크로 회귀.) - [ ] **Step 3: Commit** ```bash git add .claude/tests/test_p3_prompt_skill_separation.py git commit -m "P3 Task7: test_p3 통합 강제기 16종 Co-Authored-By: Claude Opus 4.8 " ``` --- ## Task 8: 마이그레이션(기존 테스트 갱신) + 문서 + 최종 green **Files:** - Modify: `.claude/tests/test_enforcement.py` (craft/skills assert) - Modify: `CLAUDE.md` (구조·gen 흐름) **Interfaces:** 없음(정리·검증 태스크). - [ ] **Step 1: test_enforcement.py 갱신** 기존 `skills: [design-craft]` 정확일치 assert(910-915)가 P3에서 깨진다(이제 `[des-prod-method, design-craft]`). 아래로 교체: ```python # P3: skills 는 method-skill + capability-skill 조합. capability-skill 포함 여부로 검증(정확일치 아님). _dpm = open(os.path.join(ROOT, ".claude/agents/des-prod.md")).read() check("des-prod skills include method + design-craft", "des-prod-method" in _dpm and "design-craft" in _dpm.split("---\n")[1]) _dvm = open(os.path.join(ROOT, ".claude/agents/doc-visual.md")).read() check("doc-visual skills include method + design-craft + diagram-craft", all(s in _dvm.split("---\n")[1] for s in ("doc-visual-method", "design-craft", "diagram-craft"))) check("every agent now has skills: frontmatter (arch-app has method)", "arch-app-method" in open(os.path.join(ROOT, ".claude/agents/arch-app.md")).read().split("---\n")[1]) ``` craft 표준 문구 assert(905-908)는 `craft_block` 축소 후에도 `"디자인 craft 표준"`·doc-visual `"D2"`/`"폴백"`을 유지하므로 그대로 통과(Task4 craft_block이 문구 보존). 실행해 확인하고, 만약 문구가 바뀌었으면 새 문구로 갱신. - [ ] **Step 2: 갱신 검증** Run: `CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=_sandbox python3 .claude/tests/test_enforcement.py` Expected: 전 항목 통과(≈500 assert). 실패하면 해당 assert를 P3 구조에 맞게 갱신. - [ ] **Step 3: CLAUDE.md 갱신** `.claude/hooks/*.py` 줄에 `gen_method_skills`(method-skill 생성) 추가. 구조 블록에 `method-skill-registry.yaml`·`.claude/skills/generated/` 언급. "핵심 개념"의 gen 흐름에 "role-profiles/capability-families/role-working-methods 변경 후 `gen_agents`+`gen_method_skills` 재실행" 반영. 검증 섹션에 `gen_method_skills.py --check` 추가: ```bash # 에이전트+method-skill 재생성(role-* 변경 후) CLAUDE_PROJECT_DIR="$PWD" python3 .claude/hooks/gen_method_skills.py CLAUDE_PROJECT_DIR="$PWD" python3 .claude/hooks/gen_agents.py ``` - [ ] **Step 4: render fixture 복구 + 전체 게이트** ```bash git checkout -- .claude/tests/fixtures/ 2>/dev/null || true CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=_sandbox python3 .claude/tests/run_all.py ``` Expected: 전 스위트 green(doctor OK 포함, `test_p3_*` 16 passed, `test_enforcement` 통과). - [ ] **Step 5: Commit** ```bash git add .claude/tests/test_enforcement.py CLAUDE.md git commit -m "P3 Task8: 기존 테스트 마이그레이션(craft/skills assert) + CLAUDE.md gen 흐름 Co-Authored-By: Claude Opus 4.8 " ``` --- ## Self-Review **Spec coverage:** §3 파이프라인(Task1·2·4), §4 registry(Task1), §5 생성 skill(Task2), §6 3정책(Task4), §7 spine(Task4), §8 gen_agents(Task4), §9 무결성(Task5·6), §10 Phase0(Task0), §11 마이그레이션(Task8), §13 테스트 16종(Task7), §12 문서(Task8). 전 섹션 커버. **Placeholder scan:** registry·gen_method_skills·skill_refs·doctor check·lint 확장·test 전부 완전 코드. gen_agents는 함수별 교체 지시 + 완전 새 함수 본문. 임시 테스트 스크립트는 `/tmp/.../scratchpad`(커밋 안 함), 영구 게이트는 test_p3 + doctor + lint. **Type consistency:** `MREG["roles"][rid]["method-skill"]`(gen_agents·gen_method_skills·doctor 일관), `skills_fm_line(kind, *, rid/fid/members)`, `known_skill_names(root)`/`parse_skills(val)`(skill_refs, doctor·lint 동일 소비), `build_all()->{skill:content}`, `generated-dir` registry 필드로 레이아웃 무관. method-skill 이름 규약 `-method` 전 태스크 일치. **Task 경계:** 각 태스크 독립 테스트 가능 — Task1(registry 검증) / Task2(생성+drift) / Task3(헬퍼) / Task4(카드 재생성) / Task5(doctor) / Task6(lint) / Task7(통합) / Task8(마이그레이션+green). Task0는 런타임 probe(게이트, 이후 레이아웃 확정). ## Execution Handoff **Plan complete and saved to `docs/superpowers/plans/2026-07-13-p3-prompt-skill-separation.md`.** **1. Subagent-Driven (recommended)** — 태스크별 fresh subagent + 2단 리뷰(spec 준수 + 품질), 빠른 반복. **2. Inline Execution** — 이 세션에서 executing-plans로 배치 실행. P1·P2와 동일하게 **Subagent-Driven** 권장.