- harness_{banner,icon,social,team}.png(크랩) 삭제 → logo_thumb.png(ZIO 로고) 기반
zioinfo_{banner,icon,social,team}.png 재생성 (SVG 코드 드로잉 → 헤드리스 Chrome 렌더)
· 팀 다이어그램 흐름: 지휘→분석→개발→검증 순환
- 참조 갱신: README(EN/KO/JA)·gen_intro_deck.py·PROJECT_MAP·design.md
(design.md에 크랩 브리프=레거시 주석)
- 사내전용 설치가이드 재구성: 구 pptx 2종 삭제,
docs/gen_install_guide.py(진실원천) 신설 → docs/zioinfo_사내전용_설치가이드.pptx(10슬라이드)
· ythong 마켓플레이스·플러그인 4종·2줄 설치·검증·트러블슈팅
- .gitignore: 설치가이드 pptx만 *.pptx 예외로 추적
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
320 lines
16 KiB
Python
320 lines
16 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Harness 소개 덱 생성 — python-pptx. (스크립트가 진실원천: 내용 수정 후 재생성)
|
|
실행: python docs/gen_intro_deck.py → docs/Harness_소개덱_v1.pptx
|
|
"""
|
|
import os
|
|
from pptx import Presentation
|
|
from pptx.util import Inches, Pt, Emu
|
|
from pptx.dml.color import RGBColor
|
|
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
|
|
from pptx.enum.shapes import MSO_SHAPE
|
|
|
|
# ── 테마(하네스 브랜드: 딥 인디고 + 퍼플/시안 액센트) ─────────────────
|
|
INK = RGBColor(0x0F, 0x14, 0x2E) # 딥 인디고(배경/제목)
|
|
INDIGO = RGBColor(0x31, 0x2E, 0x81) # 인디고
|
|
PURPLE = RGBColor(0x7C, 0x3A, 0xED) # 퍼플 액센트
|
|
CYAN = RGBColor(0x06, 0xB6, 0xD4) # 시안 액센트
|
|
AMBER = RGBColor(0xF5, 0x9E, 0x0B) # 앰버 포인트
|
|
LIGHT = RGBColor(0xEE, 0xEC, 0xFB) # 연보라 배경
|
|
CARD = RGBColor(0xF6, 0xF7, 0xFB)
|
|
GRAY = RGBColor(0x55, 0x5A, 0x6B)
|
|
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
|
|
FONT = "맑은 고딕"
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
ROOT = os.path.dirname(HERE)
|
|
BANNER = os.path.join(ROOT, "zioinfo_banner.png")
|
|
ICON = os.path.join(ROOT, "zioinfo_icon.png")
|
|
|
|
prs = Presentation()
|
|
prs.slide_width = Inches(13.333)
|
|
prs.slide_height = Inches(7.5)
|
|
SW, SH = prs.slide_width, prs.slide_height
|
|
BLANK = prs.slide_layouts[6]
|
|
|
|
|
|
def _set(run, size, bold=False, color=INK, font=FONT):
|
|
run.font.size = Pt(size); run.font.bold = bold
|
|
run.font.color.rgb = color; run.font.name = font
|
|
|
|
|
|
def box(slide, x, y, w, h, fill=None, line=None, line_w=1.0, round_=False):
|
|
shp = slide.shapes.add_shape(
|
|
MSO_SHAPE.ROUNDED_RECTANGLE if round_ else MSO_SHAPE.RECTANGLE,
|
|
Inches(x), Inches(y), Inches(w), Inches(h))
|
|
shp.shadow.inherit = False
|
|
if fill is None: shp.fill.background()
|
|
else: shp.fill.solid(); shp.fill.fore_color.rgb = fill
|
|
if line is None: shp.line.fill.background()
|
|
else: shp.line.color.rgb = line; shp.line.width = Pt(line_w)
|
|
return shp
|
|
|
|
|
|
def text(slide, x, y, w, h, runs, align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP,
|
|
space=4, line_spacing=None):
|
|
"""runs: list of paragraphs; each para = list of (txt, size, bold, color)."""
|
|
tb = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
|
|
tf = tb.text_frame; tf.word_wrap = True; tf.vertical_anchor = anchor
|
|
tf.margin_left = tf.margin_right = Inches(0.05)
|
|
tf.margin_top = tf.margin_bottom = Inches(0.02)
|
|
for i, para in enumerate(runs):
|
|
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
|
|
p.alignment = align; p.space_after = Pt(space)
|
|
if line_spacing: p.line_spacing = line_spacing
|
|
for spec in para:
|
|
txt, size, bold, color = spec[0], spec[1], spec[2], spec[3]
|
|
fnt = spec[4] if len(spec) > 4 else FONT
|
|
r = p.add_run(); r.text = txt; _set(r, size, bold, color, fnt)
|
|
return tb
|
|
|
|
|
|
def header(slide, title, kicker=None, n=None):
|
|
"""상단 헤더 바 + 제목."""
|
|
box(slide, 0, 0, 13.333, 1.15, fill=INK)
|
|
box(slide, 0, 1.15, 13.333, 0.06, fill=PURPLE)
|
|
runs = []
|
|
if kicker:
|
|
runs.append([(kicker, 12, True, CYAN)])
|
|
runs.append([(title, 26, True, WHITE)])
|
|
text(slide, 0.6, 0.12, 11.5, 0.95, runs, anchor=MSO_ANCHOR.MIDDLE, space=2)
|
|
if n is not None:
|
|
text(slide, 12.2, 0.12, 0.9, 0.95, [[(f"{n:02d}", 22, True, AMBER)]],
|
|
align=PP_ALIGN.RIGHT, anchor=MSO_ANCHOR.MIDDLE)
|
|
|
|
|
|
def footer(slide):
|
|
text(slide, 0.6, 7.05, 8, 0.35, [[("Harness — Claude Code Team-Architecture Factory", 9, False, GRAY)]])
|
|
text(slide, 11.0, 7.05, 1.9, 0.35, [[("v1.3.1", 9, False, GRAY)]], align=PP_ALIGN.RIGHT)
|
|
|
|
|
|
def card(slide, x, y, w, h, title, lines, accent=PURPLE, tsize=15):
|
|
box(slide, x, y, w, h, fill=CARD, line=RGBColor(0xE2,0xE3,0xEE), line_w=0.75, round_=True)
|
|
box(slide, x, y, 0.12, h, fill=accent)
|
|
runs = [[(title, tsize, True, INK)]]
|
|
for ln in lines:
|
|
runs.append([("· ", 11, True, accent), (ln, 11.5, False, GRAY)])
|
|
text(slide, x+0.3, y+0.22, w-0.45, h-0.4, runs, space=4)
|
|
|
|
|
|
# ── 슬라이드 1: 타이틀 ───────────────────────────────────────────────
|
|
s = prs.slides.add_slide(BLANK)
|
|
box(s, 0, 0, 13.333, 7.5, fill=INK)
|
|
box(s, 0, 0, 0.25, 7.5, fill=PURPLE)
|
|
box(s, 0.25, 0, 0.1, 7.5, fill=CYAN)
|
|
if os.path.exists(BANNER):
|
|
try: s.shapes.add_picture(BANNER, Inches(8.0), Inches(0.7), height=Inches(2.2))
|
|
except Exception: pass
|
|
text(s, 0.9, 2.0, 9.5, 2.4, [
|
|
[("Harness", 60, True, WHITE)],
|
|
[("Claude Code를 위한 팀 아키텍처 팩토리", 22, True, CYAN)],
|
|
], space=10)
|
|
text(s, 0.95, 4.5, 11, 1.6, [
|
|
[("도메인 한 문장을 ", 16, False, LIGHT), ("에이전트 팀 + 스킬", 16, True, AMBER),
|
|
("로 변환하는 메타 스킬", 16, False, LIGHT)],
|
|
[("\"이 프로젝트용 하네스 구성해줘\" → 전문 에이전트 팀이 자동 생성·협업", 13, False, RGBColor(0xB9,0xBD,0xD6))],
|
|
], space=8)
|
|
text(s, 0.95, 6.6, 11, 0.5, [[("소개 덱 · 2026-06 · Apache-2.0", 11, False, RGBColor(0x8A,0x8F,0xB0))]])
|
|
|
|
# ── 슬라이드 2: 왜 필요한가 ──────────────────────────────────────────
|
|
s = prs.slides.add_slide(BLANK); header(s, "왜 Harness인가?", "THE PROBLEM", 2)
|
|
text(s, 0.6, 1.5, 12.1, 1.0, [
|
|
[("복잡한 작업일수록 ", 16, False, GRAY), ("여러 전문 에이전트의 협업", 16, True, INK),
|
|
("이 필요하지만 —", 16, False, GRAY)],
|
|
], space=4)
|
|
card(s, 0.6, 2.6, 3.95, 3.6, "매번 수작업", [
|
|
"에이전트 역할을 매번 즉석에서 prompt에 작성",
|
|
"다음 세션에서 재사용 불가",
|
|
"팀 통신·협업 프로토콜이 매번 제각각",
|
|
], accent=AMBER)
|
|
card(s, 4.75, 2.6, 3.95, 3.6, "구성 일관성 부재", [
|
|
"누가(에이전트) / 어떻게(스킬) 가 뒤섞임",
|
|
"패턴 없이 임기응변 → 누락·중복",
|
|
"품질이 작성자 역량에 좌우",
|
|
], accent=AMBER)
|
|
card(s, 8.9, 2.6, 3.85, 3.6, "Harness의 답", [
|
|
"한 문장 → 에이전트 팀 + 스킬 자동 생성",
|
|
"파일로 영속화 → 세션 간 재사용",
|
|
"6가지 검증된 아키텍처 패턴 적용",
|
|
], accent=PURPLE)
|
|
footer(s)
|
|
|
|
# ── 슬라이드 3: Harness란 ────────────────────────────────────────────
|
|
s = prs.slides.add_slide(BLANK); header(s, "Harness란 무엇인가", "THE FACTORY", 3)
|
|
text(s, 0.6, 1.5, 12.1, 1.4, [
|
|
[("Harness는 ", 18, False, GRAY), ("메타 스킬(스킬을 만드는 스킬)", 18, True, PURPLE),
|
|
("입니다.", 18, False, GRAY)],
|
|
[("도메인 설명을 입력하면 → 전문 에이전트 팀과 그들이 사용할 스킬 세트를 ",
|
|
14, False, GRAY), ("코드(.md 파일)로 생성", 14, True, INK), ("합니다.", 14, False, GRAY)],
|
|
], space=8)
|
|
# 플로우 4단계
|
|
labels = [("도메인 한 문장", CYAN), ("팩토리(메타 스킬)", PURPLE),
|
|
("에이전트 팀 + 스킬", INDIGO), ("작업 자동 수행", AMBER)]
|
|
x = 0.6
|
|
for i, (lab, col) in enumerate(labels):
|
|
box(s, x, 3.3, 2.7, 1.5, fill=col, round_=True)
|
|
text(s, x+0.15, 3.3, 2.4, 1.5, [[(lab, 14, True, WHITE)]],
|
|
align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
|
if i < 3:
|
|
text(s, x+2.75, 3.3, 0.5, 1.5, [[("→", 26, True, GRAY)]],
|
|
align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
|
x += 3.18
|
|
text(s, 0.6, 5.3, 12.1, 1.4, [
|
|
[("핵심: ", 14, True, INK), ("에이전트(누가) 와 스킬(어떻게) 를 분리", 14, True, PURPLE),
|
|
("한다. 에이전트는 역할·협업을, 스킬은 방법·절차를 담는다.", 14, False, GRAY)],
|
|
[("→ 다른 도메인에서도 스킬을 재사용하고, 에이전트만 갈아끼울 수 있다.", 13, False, GRAY)],
|
|
], space=6)
|
|
footer(s)
|
|
|
|
# ── 슬라이드 4: 핵심 가치 ────────────────────────────────────────────
|
|
s = prs.slides.add_slide(BLANK); header(s, "핵심 가치 세 가지", "WHY IT WORKS", 4)
|
|
card(s, 0.6, 1.6, 3.95, 4.6, "1. 분리(Separation)", [
|
|
"에이전트 = 누가 (역할·원칙·통신)",
|
|
"스킬 = 어떻게 (절차·패턴·스크립트)",
|
|
"오케스트레이터 = 언제·어떤 순서로",
|
|
"→ 책임이 명확, 변경이 국소적",
|
|
], accent=CYAN, tsize=16)
|
|
card(s, 4.75, 1.6, 3.95, 4.6, "2. 재사용(Reuse)", [
|
|
".claude/agents·skills 파일로 영속",
|
|
"세션이 바뀌어도 그대로 재사용",
|
|
"스킬은 여러 에이전트가 공유 가능",
|
|
"→ 빌드할수록 자산이 쌓인다",
|
|
], accent=PURPLE, tsize=16)
|
|
card(s, 8.9, 1.6, 3.85, 4.6, "3. 진화(Evolution)", [
|
|
"고정물이 아니라 살아있는 시스템",
|
|
"실행 후 피드백 → 에이전트·스킬 갱신",
|
|
"CLAUDE.md 변경 이력으로 추적",
|
|
"→ 퇴행 없이 점점 좋아진다",
|
|
], accent=AMBER, tsize=16)
|
|
footer(s)
|
|
|
|
# ── 슬라이드 5: 6가지 아키텍처 패턴 ──────────────────────────────────
|
|
s = prs.slides.add_slide(BLANK); header(s, "6가지 팀 아키텍처 패턴", "PATTERNS", 5)
|
|
pats = [
|
|
("파이프라인", "순차 의존 작업 — A→B→C 단계별 전달"),
|
|
("팬아웃/팬인", "병렬 독립 작업 후 결과 통합"),
|
|
("전문가 풀", "상황별로 필요한 전문가만 선택 호출"),
|
|
("생성-검증", "생성 후 독립 에이전트가 품질 검수"),
|
|
("감독자", "중앙 에이전트가 상태 관리·동적 분배"),
|
|
("계층적 위임", "상위가 하위에 재귀적으로 위임"),
|
|
]
|
|
gx, gy = 0.6, 1.55
|
|
for i, (name, desc) in enumerate(pats):
|
|
r, c = divmod(i, 3)
|
|
x = gx + c*4.18; y = gy + r*2.65
|
|
card(s, x, y, 3.95, 2.4, name, [desc], accent=[CYAN,PURPLE,INDIGO][c], tsize=16)
|
|
footer(s)
|
|
|
|
# ── 슬라이드 6: 7-Phase 워크플로우 ───────────────────────────────────
|
|
s = prs.slides.add_slide(BLANK); header(s, "구성 워크플로우 (7 Phase)", "HOW", 6)
|
|
phases = [
|
|
("0", "현황 감사", "기존 에이전트·스킬·CLAUDE.md 확인, drift 감지"),
|
|
("1", "도메인 분석", "작업 유형·기술스택·데이터모델 파악"),
|
|
("2", "팀 아키텍처 설계", "실행 모드(팀/서브/하이브리드) + 패턴 선택"),
|
|
("3", "에이전트 정의", ".claude/agents/*.md (역할·협업·model:opus)"),
|
|
("4", "스킬 생성", ".claude/skills/*/SKILL.md (방법·references)"),
|
|
("5", "통합·오케스트레이션", "오케스트레이터 스킬 + CLAUDE.md 포인터"),
|
|
("6", "검증·테스트", "구조·트리거·드라이런 검증"),
|
|
]
|
|
y = 1.5
|
|
for num, title, desc in phases:
|
|
box(s, 0.6, y, 0.62, 0.62, fill=PURPLE, round_=True)
|
|
text(s, 0.6, y, 0.62, 0.62, [[(num, 18, True, WHITE)]], align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
|
text(s, 1.45, y, 3.2, 0.62, [[(title, 15, True, INK)]], anchor=MSO_ANCHOR.MIDDLE)
|
|
text(s, 4.7, y, 8.0, 0.62, [[(desc, 12.5, False, GRAY)]], anchor=MSO_ANCHOR.MIDDLE)
|
|
y += 0.72
|
|
text(s, 0.6, 6.55, 12, 0.4, [[("이후 Phase 7(진화): 매 실행 후 피드백을 반영해 지속 갱신", 12, True, CYAN)]])
|
|
footer(s)
|
|
|
|
# ── 슬라이드 7: 설계가 먼저다 ────────────────────────────────────────
|
|
s = prs.slides.add_slide(BLANK); header(s, "설계가 먼저다", "DESIGN FIRST", 7)
|
|
text(s, 0.6, 1.5, 12.1, 1.3, [
|
|
[("플러그인은 하네스를 ", 16, False, GRAY), ("배포·증폭하는 그릇", 16, True, INK),
|
|
("일 뿐, 설계를 대신 잘해주지 않는다.", 16, False, GRAY)],
|
|
[("허술한 설계는 ", 13.5, False, GRAY), ("잘 배포될수록 더 큰 부채", 13.5, True, AMBER),
|
|
("가 된다 — 결함이 여러 사람·세션·프로젝트로 그대로 퍼진다.", 13.5, False, GRAY)],
|
|
], space=6)
|
|
box(s, 0.6, 2.95, 5.85, 1.0, fill=CYAN, round_=True)
|
|
text(s, 0.8, 2.95, 5.5, 1.0, [[("① 제대로 설계하고 동작·검증한 뒤", 15, True, WHITE)]], anchor=MSO_ANCHOR.MIDDLE)
|
|
text(s, 6.5, 2.95, 0.6, 1.0, [[("→", 26, True, GRAY)]], align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
|
box(s, 7.2, 2.95, 5.55, 1.0, fill=PURPLE, round_=True)
|
|
text(s, 7.4, 2.95, 5.2, 1.0, [[("② 비로소 플러그인으로 묶는다", 15, True, WHITE)]], anchor=MSO_ANCHOR.MIDDLE)
|
|
text(s, 0.6, 4.2, 12, 0.4, [[("패키징 전 점검 — 좋은 설계의 4축", 13, True, INK)]])
|
|
axes = [("에이전트 분리", "누가? 역할 겹침·과부하 없게", CYAN),
|
|
("스킬 경계", "어떻게? 절차와 역할 분리", PURPLE),
|
|
("아키텍처 패턴", "6패턴 중 작업에 맞게", INDIGO),
|
|
("트리거 설계", "발동/비발동 정확히", AMBER)]
|
|
x = 0.6
|
|
for name, desc, col in axes:
|
|
card(s, x, 4.65, 2.92, 1.8, name, [desc], accent=col, tsize=14)
|
|
x += 3.07
|
|
footer(s)
|
|
|
|
# ── 슬라이드 8: 플러그인 생태계 ──────────────────────────────────────
|
|
s = prs.slides.add_slide(BLANK); header(s, "플러그인 생태계", "ECOSYSTEM", 8)
|
|
text(s, 0.6, 1.45, 12, 0.7, [
|
|
[("Harness는 ", 15, False, GRAY), ("Claude Code 플러그인 마켓플레이스", 15, True, INK),
|
|
("로 배포됩니다.", 15, False, GRAY)],
|
|
])
|
|
card(s, 0.6, 2.3, 6.0, 3.9, "harness (메타 스킬)", [
|
|
"도메인 → 에이전트 팀 + 스킬 자동 생성",
|
|
"6가지 아키텍처 패턴 내장",
|
|
"설치: harness@ythong",
|
|
"용도: 새 하네스를 처음부터 설계",
|
|
], accent=PURPLE, tsize=17)
|
|
card(s, 6.8, 2.3, 5.95, 3.9, "zio-harness (도메인 플러그인)", [
|
|
"React + Spring Boot + Mobile 풀스택",
|
|
"orchestrator·analyst·bot·agent 팀",
|
|
"PROJECT_MAP.md로 폴더구조 기억",
|
|
"용도: 이미 완성된 풀스택 하네스 즉시 사용",
|
|
], accent=CYAN, tsize=17)
|
|
text(s, 0.6, 6.45, 12, 0.5, [
|
|
[("직접 도메인 플러그인 작성 → ", 12.5, False, GRAY), ("docs/plugin-guide.md", 12.5, True, PURPLE),
|
|
(" 참조", 12.5, False, GRAY)],
|
|
])
|
|
footer(s)
|
|
|
|
# ── 슬라이드 9: 사용법 ───────────────────────────────────────────────
|
|
s = prs.slides.add_slide(BLANK); header(s, "시작하기 — 3단계", "GET STARTED", 9)
|
|
steps = [
|
|
("1. 마켓플레이스 추가", "claude plugin marketplace add\n https://git.zioinfo.co.kr/ythong/harness"),
|
|
("2. 설치 + 플래그", "claude plugin install harness@ythong\nexport CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"),
|
|
("3. 한 문장으로 실행", 'claude "핀테크 리스크 평가 팀용 하네스 구성해줘"'),
|
|
]
|
|
y = 1.6
|
|
for title, code in steps:
|
|
box(s, 0.6, y, 12.1, 1.45, fill=CARD, line=RGBColor(0xE2,0xE3,0xEE), line_w=0.75, round_=True)
|
|
box(s, 0.6, y, 0.12, 1.45, fill=PURPLE)
|
|
text(s, 0.95, y+0.18, 11.5, 0.5, [[(title, 15, True, INK)]])
|
|
text(s, 0.95, y+0.68, 11.5, 0.7, [[(code, 13, False, INDIGO, "Consolas")]])
|
|
y += 1.65
|
|
text(s, 0.6, 6.7, 12, 0.4, [[("요구사항: Claude Code v2.x 이상 · Agent Teams 실험 플래그", 11.5, False, GRAY)]])
|
|
footer(s)
|
|
|
|
# ── 슬라이드 9: 마무리 ───────────────────────────────────────────────
|
|
s = prs.slides.add_slide(BLANK)
|
|
box(s, 0, 0, 13.333, 7.5, fill=INK)
|
|
box(s, 0, 0, 13.333, 0.18, fill=PURPLE)
|
|
box(s, 0, 7.32, 13.333, 0.18, fill=CYAN)
|
|
if os.path.exists(ICON):
|
|
try: s.shapes.add_picture(ICON, Inches(5.9), Inches(0.9), height=Inches(1.7))
|
|
except Exception: pass
|
|
text(s, 0.9, 2.9, 11.5, 2.2, [
|
|
[("한 문장으로, 팀을 짓는다.", 34, True, WHITE)],
|
|
[("Harness — 도메인을 에이전트 팀과 스킬로.", 18, True, CYAN)],
|
|
], align=PP_ALIGN.CENTER, space=12)
|
|
text(s, 0.9, 5.4, 11.5, 1.2, [
|
|
[("\"하네스 구성해줘\" · \"build a harness for this project\"", 15, False, LIGHT)],
|
|
[("git.zioinfo.co.kr/ythong/harness · Apache-2.0", 12, False, RGBColor(0x9A,0x9F,0xC0))],
|
|
], align=PP_ALIGN.CENTER, space=8)
|
|
|
|
out = os.path.join(HERE, "Harness_소개덱_v1.pptx")
|
|
try:
|
|
prs.save(out)
|
|
except PermissionError:
|
|
out = os.path.join(HERE, "Harness_소개덱_v1.1.pptx")
|
|
prs.save(out)
|
|
print("(원본 파일이 열려 있어 v1.1로 저장)")
|
|
print("생성 완료:", out, "(", len(prs.slides._sldIdLst), "슬라이드 )")
|