55 KiB
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/<role>-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는 생성물. 헤더 경고 +--checkdrift 게이트. 수기 편집 금지. - 협업역할 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 <noreply@anthropic.com>. 커밋 전 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(+--checkdrift)..claude/hooks/skill_refs.py— 공유 헬퍼(.claude/skills/**스캔 → skill 이름 집합,skills:frontmatter 파싱). doctor·lint_refs가 소비..claude/skills/generated/<role>-method/SKILL.md× 75 — 생성물..claude/tests/test_p3_prompt_skill_separation.py— 16 checks.
개정:
org-os/00-role-registry/role-working-methods.yaml— optionalself-check필드 additive + DES-PROD exemplar..claude/hooks/gen_agents.py—wm_block→method_spine,skills_fm_lineregistry 파생, 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 생성
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 생성
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 이름 규약<role>-method(디자인 3종과 flat 동거, 이름으로 그룹). registrygenerated-dir한 줄만 다르다. -
결과를
.superpowers/sdd/progress.md에 기록(이후 태스크가 참조). probe 파일 제거:
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:
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):
# 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
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 <noreply@anthropic.com>"
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에 optionalself-check추가 + field-schema 주석) - Create:
.claude/skills/generated/<role>-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 블록에 한 줄 추가(파일 상단):
self-check: (선택) 역할 고유 자기검증 문항 — 공통 하네스 불변식은 넣지 않는다(카드 계약 섹션이 담당)
DES-PROD 엔트리 끝(sources: 리스트 다음)에 exemplar 추가:
self-check:
- 사용자 행동 근거 없이 시각 취향으로 결정하지 않았나?
- 핵심 흐름과 예외 상태를 모두 설계했나?
- generic한 "modern/clean" 표현으로 방향을 대체하지 않았나?
- Step 2: 실패 테스트 작성 (임시)
/tmp/claude-*/scratchpad/t2.py:
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 작성
#!/usr/bin/env python3
"""Generate .claude/skills/generated/<role>-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 = ("<!-- GENERATED from role-working-methods.yaml — do not edit. "
"Rerun: python3 .claude/hooks/gen_method_skills.py -->")
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 자기확인
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
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 <noreply@anthropic.com>"
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:
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 작성
#!/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 <N>. -
Step 5: Commit
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 <noreply@anthropic.com>"
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:
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 = {} 근처):
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 전체 교체):
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만):
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)도 축소:
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)을 아래로 교체:
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() 다음에:
global MREG
MREG = load_method_registry(fams)
(j) assert 갱신 (605-606):
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: 통과 확인 (재생성 + 테스트)
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
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 <noreply@anthropic.com>"
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:
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 다음에 추가:
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 추가:
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
git add .claude/hooks/doctor.py
git commit -m "P3 Task5: doctor check_method_skill_wiring(완전성·실존·참조·고아·drift·키정합)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
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:
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 다음에 추가:
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()을 병합형으로 교체:
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
git add .claude/hooks/lint_refs.py
git commit -m "P3 Task6: lint_refs 에이전트 skills: 참조 해소(깨진 참조 검출)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
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: 테스트 작성
#!/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
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 <noreply@anthropic.com>"
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]). 아래로 교체:
# 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 추가:
# 에이전트+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 복구 + 전체 게이트
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
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 <noreply@anthropic.com>"
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 이름 규약 <role-lower>-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 권장.