From 1ab599f4ff83ca67fea3c6dbd65f0584245403f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Chyeonggkim=E2=80=9D?= <“hyeonggkim@smilegate.com”> Date: Mon, 20 Apr 2026 20:22:38 +0900 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20docs:=20=EB=B2=88=EC=97=AD=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EC=97=91=EC=85=80=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EC=9D=98=20MD=20=ED=8C=8C=EC=9D=BC=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EC=A7=80=EC=B9=A8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/requirement-analyzer/SKILL.md | 166 +++++++++ .../references/output-template.md | 159 +++++++++ .../scripts/extract_pptx.py | 314 ++++++++++++++++++ skills/translation-keys/SKILL.md | 22 +- 4 files changed, 651 insertions(+), 10 deletions(-) create mode 100644 skills/requirement-analyzer/SKILL.md create mode 100644 skills/requirement-analyzer/references/output-template.md create mode 100644 skills/requirement-analyzer/scripts/extract_pptx.py diff --git a/skills/requirement-analyzer/SKILL.md b/skills/requirement-analyzer/SKILL.md new file mode 100644 index 0000000..e2b45cb --- /dev/null +++ b/skills/requirement-analyzer/SKILL.md @@ -0,0 +1,166 @@ +--- +name: requirement-analyzer +description: | + PPT 기획서를 분석하여 요구사항 명세서를 자동 생성합니다. + Nuxt pages 라우팅 구조, 컴포넌트 트리, API 엔드포인트 목록, + 화면 전환 플로우(Mermaid)를 구조화된 마크다운 문서로 출력합니다. + + 다음 상황에서 반드시 사용하세요: + - "기획서 분석해줘", "PPT 파싱", "요구사항 정리해줘" + - "라우팅 구조 뽑아줘", "컴포넌트 트리 만들어줘" + - "API 목록 정리", "화면 플로우 그려줘" + - `.pptx` 파일 경로를 제공받고 개발 요구사항 추출을 요청받았을 때 +--- + +# 요구사항 분석기 (Requirement Analyzer) + +이 skill 은 PPT 기획서(`.pptx`)를 파싱하여 팀 전체가 동일한 기반으로 개발에 착수할 수 있는 +구조화된 요구사항 명세서를 자동으로 생성합니다. + +## 언제 사용하는가 + +- 기획팀에서 PPT 기획서를 전달받았을 때 +- 개발 착수 전 화면 목록 / 라우팅 / API 목록을 정리해야 할 때 +- 화면 전환 플로우를 다이어그램으로 시각화해야 할 때 + +--- + +## 작업 순서 + +### Phase 1: 파일 확인 및 추출 + +1. **파일 경로 확인** + - 사용자가 PPTX 파일 경로를 제공했는지 확인한다. + - 미제공 시: 파일 경로를 먼저 요청한다. + +2. **의존성 확인** + 스킬 디렉토리 기준 스크립트를 실행하기 전, `python-pptx` 설치 여부를 확인한다. + ```bash + python3 -c "import pptx" 2>/dev/null && echo "OK" || echo "MISSING" + ``` + - `MISSING` 출력 시: 아래 메시지를 사용자에게 안내한다. + ``` + ⚠️ python-pptx 패키지가 필요합니다. + 설치 명령어: pip3 install python-pptx + 또는 자동 설치: python3 scripts/extract_pptx.py --auto-install <파일경로> + ``` + +3. **PPTX 추출 실행** + ```bash + python3 /scripts/extract_pptx.py --extract-images "" + ``` + - `--extract-images`: 슬라이드 이미지(PNG)를 임시 디렉토리에 추출 → 시각 분석에 활용 + - 출력: JSON 형태의 슬라이드 데이터 (슬라이드별 제목, 텍스트, 노트, 도형, 테이블, 이미지 경로) + +4. **결과 확인** + - JSON 파싱 성공 여부 확인 + - 추출된 이미지가 있으면 각 슬라이드별로 순서대로 열어 와이어프레임/목업 시각 분석 + +--- + +### Phase 2: 내용 분석 + +추출된 JSON 데이터와 슬라이드 이미지를 기반으로 아래 항목을 분석한다. + +#### 화면(페이지) 식별 +- 슬라이드 제목에서 "화면", "페이지", 경로(`/`로 시작), URL 패턴 찾기 +- 와이어프레임 이미지에서 GNB, 레이아웃 영역 확인 +- 중복 레이아웃 슬라이드(상세 설명용) vs 독립 화면 구분 + +#### UI 컴포넌트 패턴 식별 +- 반복 등장하는 도형 레이블 (예: "GNB", "Footer", "Card", "Modal", "Tab") +- 슬라이드 간 동일한 레이아웃 구조 → 공통 컴포넌트 후보 +- 이미지에서 시각적으로 동일한 UI 블록 + +#### 네비게이션 플로우 추론 +- 화살표/커넥터 도형으로 연결된 화면 간 전환 +- "클릭 시", "탭하면", "→" 등의 텍스트 주석 +- 슬라이드 번호 순서 + 제목 키워드로 흐름 유추 + +#### API 엔드포인트 추출 +- 테이블에 "Method", "URL", "API", "Endpoint" 컬럼이 있는 경우 +- 발표자 노트에 `GET /api/...`, `POST /api/...` 형태의 텍스트 +- 텍스트 박스 내 `http`, `/api`, `fetch`, `axios` 언급 + +--- + +### Phase 3: 명세서 생성 + +아래 7개 섹션으로 구성된 마크다운 문서를 작성한다. +추론 기반 정보는 반드시 `(추정)` 또는 `<확인 필요>` 를 표시한다. + +**출력 섹션:** + +``` +1. 화면 목록 (Pages) — 테이블 형식 +2. Nuxt 라우팅 구조 — pages/ 파일 트리 +3. 컴포넌트 트리 — components/ 파일 트리 + Props 상세 테이블 +4. API 엔드포인트 목록 — 테이블 형식 +5. 화면 전환 플로우 — Mermaid flowchart TD +6. 공통 레이아웃 — 레이아웃별 적용 화면 테이블 +7. 추가 참고사항 — 미확정 항목 / 확인 필요 목록 +``` + +출력 형식 상세는 `references/output-template.md` 를 참조한다. + +--- + +### Phase 4: 사용자 확인 및 저장 + +1. **미리보기 제공** + 생성된 명세서 전체를 출력하여 사용자가 검토할 수 있도록 한다. + +2. **저장 경로 확인** + ``` + 📄 아래 경로에 요구사항 명세서를 저장하려고 합니다: + docs/requirements/<기획서파일명>.md + + 다른 경로를 원하시면 알려주세요. 진행할까요? (y/n) + ``` + +3. **파일 저장** + - 사용자 승인 후 파일을 저장한다. + - `docs/requirements/` 디렉토리가 없으면 생성한다. + - 저장 완료 후 아래 메시지를 출력한다: + ``` + ✅ 요구사항 명세서 저장 완료 + docs/requirements/<파일명>.md + + 다음 단계: + 1) <확인 필요> 항목을 기획자에게 확인하세요. + 2) 컴포넌트/API 목록을 개발 태스크로 분해하세요. + 3) git add docs/requirements/ && git commit -m "docs: 요구사항 명세서 추가" + ``` + +--- + +## 출력 형식 + +`references/output-template.md` 에서 전체 마크다운 구조와 예시를 확인하세요. + +### Mermaid 플로우차트 작성 규칙 + +```mermaid +flowchart TD + A[메인] --> B[이벤트 목록] + B --> C[이벤트 상세] + C --> D{사전등록 완료?} + D -- 완료 --> E[완료 모달] + D -- 미완료 --> F[사전등록 폼] +``` + +- 화면 노드: `[화면명]` (사각형) +- 조건 분기: `{조건}` (다이아몬드) +- 모달/팝업: `([모달명])` (타원) +- 화살표 레이블: 클릭 대상 버튼명 또는 조건 + +--- + +## 주의사항 + +- **기존 파일 수정 금지**: 프로젝트의 기존 소스 파일은 절대 수정하지 않는다. 명세서 파일만 새로 생성한다. +- **추측 표시 필수**: 기획서에 명시되지 않은 정보를 추론할 경우 반드시 `(추정)` 을 표시한다. +- **확인 필요 목록**: 확신할 수 없는 항목은 섹션 7에 `<확인 필요>` 로 모아 명시한다. +- **이미지 heavy 기획서**: 텍스트가 적고 이미지 위주인 기획서는 `--extract-images` 옵션이 필수다. +- **대용량 PPTX**: 슬라이드가 50장 이상이면 `--slides 1-20` 으로 범위를 지정하여 순차 처리한다. +- **커밋 금지**: 파일 저장 후 `git commit` 은 사용자 명시 요청이 없으면 실행하지 않는다. diff --git a/skills/requirement-analyzer/references/output-template.md b/skills/requirement-analyzer/references/output-template.md new file mode 100644 index 0000000..4c9393d --- /dev/null +++ b/skills/requirement-analyzer/references/output-template.md @@ -0,0 +1,159 @@ +# 요구사항 명세서 출력 템플릿 + +이 파일은 `requirement-analyzer` 스킬이 생성하는 마크다운 문서의 형식 예시입니다. +실제 출력 시 각 항목을 기획서 내용으로 채우고, 불확실한 항목은 `(추정)` 또는 `<확인 필요>` 로 표시합니다. + +--- + +```markdown +# 요구사항 명세서: <프로젝트명> + +> 기획서: `<파일명.pptx>` | 생성일: | 총 슬라이드: 장 +> 담당 기획자: <확인 필요> | 개발 일정: <확인 필요> + +--- + +## 1. 화면 목록 (Pages) + +| # | 화면명 | 라우팅 경로 | 기획서 슬라이드 | 비고 | +|---|--------|------------|--------------|------| +| 1 | 메인 (랜딩) | `/` | 3-5 | 사전등록 CTA 포함 | +| 2 | 사전등록 | `/register` | 6-9 | 폼 입력 화면 | +| 3 | 등록 완료 | `/register/complete` | 10 | 완료 안내 화면 | +| 4 | 이벤트 소개 | `/about` | 11-13 | 스크롤 단일 페이지 | +| 5 | 공지사항 | `/notice` | 14 | (추정) 목록 페이지 | +| 6 | 공지사항 상세 | `/notice/[id]` | 14 | (추정) 동적 라우트 | + +--- + +## 2. Nuxt 라우팅 구조 + +``` +pages/ +├── index.vue # 메인 (랜딩) +├── register/ +│ ├── index.vue # 사전등록 폼 +│ └── complete.vue # 등록 완료 +├── about.vue # 이벤트 소개 +└── notice/ + ├── index.vue # 공지사항 목록 + └── [id].vue # 공지사항 상세 (추정) +``` + +**레이아웃:** +``` +layouts/ +├── default.vue # GNB + Footer 포함 (전체 화면) +└── blank.vue # GNB/Footer 없음 (모달, 완료 화면 등) +``` + +--- + +## 3. 컴포넌트 트리 + +``` +components/ +├── common/ +│ ├── AppHeader.vue # GNB / 상단 네비게이션 +│ ├── AppFooter.vue # 하단 푸터 +│ └── AppLogo.vue # 로고 (추정) +├── register/ +│ ├── RegisterForm.vue # 사전등록 입력 폼 +│ ├── RegisterComplete.vue # 완료 화면 내용 +│ └── TermsModal.vue # 약관 모달 (추정) +├── notice/ +│ ├── NoticeList.vue # 공지사항 목록 (추정) +│ └── NoticeItem.vue # 공지사항 카드 (추정) +└── ui/ + ├── BaseButton.vue # 공통 버튼 + ├── BaseModal.vue # 공통 모달 래퍼 (추정) + └── BaseInput.vue # 공통 입력 필드 (추정) +``` + +### 컴포넌트 상세 + +| 컴포넌트 | 사용 화면 | Props (추정) | Emits (추정) | 비고 | +|----------|----------|-------------|-------------|------| +| `AppHeader` | 전체 | `transparent?: boolean` | — | 메인에서 투명 배경 | +| `RegisterForm` | 사전등록 | — | `submit` | 폼 상태 내부 관리 | +| `BaseButton` | 전체 | `variant`, `size`, `disabled` | `click` | Tailwind 기반 | +| `TermsModal` | 사전등록 | `visible` | `close`, `agree` | <확인 필요> | + +--- + +## 4. API 엔드포인트 목록 + +| # | Method | Endpoint | 설명 | 사용 화면 | 비고 | +|---|--------|----------|------|----------|------| +| 1 | POST | `/api/register` | 사전등록 제출 | 사전등록 | 폼 데이터 전송 | +| 2 | GET | `/api/notices` | 공지사항 목록 | 공지사항 | 페이지네이션 (추정) | +| 3 | GET | `/api/notices/:id` | 공지사항 상세 | 공지사항 상세 | (추정) | +| 4 | GET | `/api/event/info` | 이벤트 기본 정보 | 메인, 소개 | <확인 필요> | + +> **참고**: API 경로는 기획서 노트/테이블에서 추출했습니다. 백엔드 팀과 확인이 필요합니다. + +--- + +## 5. 화면 전환 플로우 + +```mermaid +flowchart TD + A[메인 랜딩] --> |사전등록 버튼 클릭| B[사전등록 폼] + A --> |공지사항 메뉴| C[공지사항 목록] + A --> |이벤트 소개 메뉴| D[이벤트 소개] + + B --> |약관 보기 클릭| E([약관 모달]) + E --> |닫기| B + B --> |제출 성공| F[등록 완료] + B --> |제출 실패| B + + C --> |항목 클릭| G[공지사항 상세] + G --> |목록으로| C + + F --> |메인으로| A +``` + +--- + +## 6. 공통 레이아웃 + +| 레이아웃 | 적용 화면 | 포함 컴포넌트 | 비고 | +|----------|----------|-------------|------| +| `default` | 메인, 이벤트 소개, 공지사항 | AppHeader, AppFooter | GNB 표시 | +| `blank` | 등록 완료 | — | GNB/Footer 없음 (추정) | + +--- + +## 7. 추가 참고사항 + +### 확인 필요 항목 + +- [ ] 약관 모달 내용 및 동의 필수 항목 (슬라이드 8 — 상세 미기재) +- [ ] 공지사항 페이지네이션 방식 (무한스크롤 vs 페이지 버튼) +- [ ] 사전등록 중복 참여 처리 방식 (동일 이메일 재등록 시) +- [ ] `/api/event/info` 엔드포인트 실제 경로 및 응답 스키마 +- [ ] 다국어 지원 여부 (한국어 전용 vs 영어 병행) + +### 기획서에서 발견된 특이사항 + +- 슬라이드 7: "등록 마감 후 버튼 비활성화" — 마감일 기준 상태 분기 필요 +- 슬라이드 12: 이벤트 소개 섹션 내 동영상 플레이어 포함 — Video 컴포넌트 추가 검토 +- 슬라이드 14: 공지사항 페이지 기획 미완성으로 표시됨 (`<확인 필요>`) + +### 미정 항목 + +| 항목 | 현재 상태 | 담당 | +|------|----------|------| +| OG 태그 / SNS 공유 설정 | 미기재 | <확인 필요> | +| GA / 트래킹 이벤트 정의 | 미기재 | <확인 필요> | +| 브라우저 지원 범위 | 미기재 | <확인 필요> | +``` + +--- + +## 활용 팁 + +- `<확인 필요>` 항목은 기획자와 킥오프 미팅 전 체크리스트로 활용하세요. +- `(추정)` 항목은 개발 착수 전 기획서를 재확인하고 제거하세요. +- API 엔드포인트 목록은 백엔드 팀과 함께 검토하여 최종 확정하세요. +- Mermaid 플로우차트는 PR 설명이나 Confluence 페이지에 바로 붙여 사용할 수 있습니다. diff --git a/skills/requirement-analyzer/scripts/extract_pptx.py b/skills/requirement-analyzer/scripts/extract_pptx.py new file mode 100644 index 0000000..15a47e5 --- /dev/null +++ b/skills/requirement-analyzer/scripts/extract_pptx.py @@ -0,0 +1,314 @@ +""" +PPT 기획서 추출 스크립트 (extract_pptx.py) +======================================== +PPTX 파일을 파싱하여 Claude AI가 분석할 수 있는 JSON 구조로 변환합니다. +의미 분석(화면 매핑, API 추출, 플로우 추론)은 Claude AI가 담당합니다. + +사용법: + python extract_pptx.py [옵션] + +옵션: + --extract-images 슬라이드 이미지를 PNG로 추출 (--output-dir 로 저장 경로 지정) + --output-dir 이미지 추출 디렉토리 (기본: /tmp/pptx_<파일명>/) + --slides <범위> 처리할 슬라이드 범위 (예: 1-10, 5, 3-7,10-12) + --auto-install python-pptx 자동 설치 후 실행 + --pretty JSON 출력 시 들여쓰기 적용 + +출력: + stdout 에 JSON 데이터 출력 +""" +import sys +import os +import json +import argparse +import tempfile +import re + + +# ───────────────────────────────────────────── +# 의존성 확인 및 자동 설치 +# ───────────────────────────────────────────── + +def _ensure_pptx(auto_install: bool) -> None: + """python-pptx 설치 여부 확인. 미설치 시 안내 또는 자동 설치.""" + try: + import pptx # noqa: F401 + except ImportError: + if auto_install: + import subprocess + print("📦 python-pptx 설치 중...", file=sys.stderr) + subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'python-pptx']) + print("✅ python-pptx 설치 완료\n", file=sys.stderr) + else: + print( + "❌ python-pptx 패키지가 설치되어 있지 않습니다.\n" + "\n" + "설치 명령어:\n" + " pip3 install python-pptx\n" + "\n" + "또는 자동 설치 옵션을 사용하세요:\n" + f" python3 {sys.argv[0]} --auto-install <파일경로>", + file=sys.stderr, + ) + sys.exit(1) + + +# ───────────────────────────────────────────── +# 슬라이드 범위 파싱 +# ───────────────────────────────────────────── + +def parse_slide_range(spec: str, total: int) -> set[int]: + """ + "1-5,8,10-12" 형태의 슬라이드 범위 문자열을 슬라이드 번호 set 으로 변환. + 번호는 1-based. + """ + result: set[int] = set() + for part in spec.split(','): + part = part.strip() + if '-' in part: + start, end = part.split('-', 1) + result.update(range(int(start), int(end) + 1)) + else: + result.add(int(part)) + return {n for n in result if 1 <= n <= total} + + +# ───────────────────────────────────────────── +# 도형(Shape) 분류 +# ───────────────────────────────────────────── + +def _shape_type_name(shape) -> str: + """python-pptx MSO_SHAPE_TYPE 값을 사람이 읽기 쉬운 문자열로 변환.""" + from pptx.util import Emu # noqa: F401 — 모듈 로드 확인용 + try: + return shape.shape_type.name.lower() # e.g. 'auto_shape', 'picture', 'line' + except Exception: + return 'unknown' + + +def _is_connector(shape) -> bool: + """화살표/커넥터 도형 여부 확인.""" + try: + # MSO_SHAPE_TYPE.LINE = 9, FREEFORM = 5 + return shape.shape_type in (9,) + except Exception: + return False + + +# ───────────────────────────────────────────── +# 슬라이드 데이터 추출 +# ───────────────────────────────────────────── + +def extract_slide(slide, slide_number: int, extract_images: bool, output_dir: str) -> dict: + """단일 슬라이드에서 모든 관련 데이터를 추출하여 dict 반환.""" + from pptx.enum.shapes import PP_PLACEHOLDER # noqa: F401 + + result: dict = { + 'number': slide_number, + 'title': '', + 'texts': [], + 'notes': '', + 'images': [], + 'shapes': [], + 'tables': [], + } + + # ── 제목 추출 ────────────────────────────── + for shape in slide.shapes: + try: + if shape.is_placeholder: + ph_type = shape.placeholder_format.type + # PP_PLACEHOLDER.TITLE = 1, CENTER_TITLE = 3 + if ph_type in (1, 3): + result['title'] = shape.text.strip() + break + except Exception: + pass + + # ── 모든 도형 순회 ────────────────────────── + for shape in slide.shapes: + shape_info: dict = { + 'type': _shape_type_name(shape), + 'name': getattr(shape, 'name', ''), + 'left': int(shape.left or 0), + 'top': int(shape.top or 0), + 'width': int(shape.width or 0), + 'height': int(shape.height or 0), + 'text': '', + } + + # 텍스트 프레임 + if shape.has_text_frame: + text = shape.text_frame.text.strip() + shape_info['text'] = text + if text: + result['texts'].append({ + 'text': text, + 'left': shape_info['left'], + 'top': shape_info['top'], + 'width': shape_info['width'], + 'height': shape_info['height'], + 'shape_name': shape_info['name'], + }) + + # 테이블 + if shape.has_table: + table = shape.table + headers = [cell.text.strip() for cell in table.rows[0].cells] + rows = [ + [cell.text.strip() for cell in row.cells] + for row in table.rows[1:] + ] + result['tables'].append({'headers': headers, 'rows': rows}) + shape_info['type'] = 'table' + + # 이미지 + if shape.shape_type == 13: # MSO_SHAPE_TYPE.PICTURE = 13 + img_info: dict = { + 'name': shape.name, + 'left': shape_info['left'], + 'top': shape_info['top'], + 'width': shape_info['width'], + 'height': shape_info['height'], + 'path': '', + } + if extract_images: + try: + img_bytes = shape.image.blob + ext = shape.image.ext # e.g. 'png', 'jpeg' + safe_name = re.sub(r'[^\w\-.]', '_', shape.name) + img_filename = f"slide{slide_number:03d}_{safe_name}.{ext}" + img_path = os.path.join(output_dir, img_filename) + os.makedirs(output_dir, exist_ok=True) + with open(img_path, 'wb') as f: + f.write(img_bytes) + img_info['path'] = img_path + except Exception as e: + img_info['error'] = str(e) + result['images'].append(img_info) + shape_info['type'] = 'picture' + + # 커넥터/화살표 처리 + if _is_connector(shape): + shape_info['type'] = 'connector' + + result['shapes'].append(shape_info) + + # ── 발표자 노트 ──────────────────────────── + try: + if slide.has_notes_slide: + notes_text = slide.notes_slide.notes_text_frame.text.strip() + result['notes'] = notes_text + except Exception: + pass + + return result + + +# ───────────────────────────────────────────── +# 메인 추출 함수 +# ───────────────────────────────────────────── + +def extract_pptx( + filepath: str, + extract_images: bool = False, + output_dir: str = '', + slide_range: str = '', + pretty: bool = False, +) -> None: + """ + PPTX 파일 전체를 파싱하여 JSON 구조를 stdout 에 출력합니다. + + Args: + filepath: PPTX 파일 절대/상대 경로 + extract_images: True 이면 슬라이드 이미지를 output_dir 에 PNG로 추출 + output_dir: 이미지 추출 디렉토리 (기본: /tmp/pptx_<파일명>/) + slide_range: 처리할 슬라이드 범위 문자열 (예: "1-10", "" = 전체) + pretty: True 이면 JSON 들여쓰기 출력 + """ + from pptx import Presentation + + if not os.path.exists(filepath): + print(f"❌ 파일을 찾을 수 없습니다: {filepath}", file=sys.stderr) + sys.exit(1) + + prs = Presentation(filepath) + total_slides = len(prs.slides) + filename = os.path.basename(filepath) + + # 출력 디렉토리 결정 + if not output_dir: + stem = re.sub(r'[^\w\-]', '_', os.path.splitext(filename)[0]) + output_dir = os.path.join(tempfile.gettempdir(), f'pptx_{stem}') + + # 처리 대상 슬라이드 번호 결정 + if slide_range: + target_slides = parse_slide_range(slide_range, total_slides) + else: + target_slides = set(range(1, total_slides + 1)) + + print( + f"🔍 파싱 중: {filename} ({total_slides}장 중 {len(target_slides)}장 처리)", + file=sys.stderr, + ) + if extract_images: + print(f"🖼️ 이미지 추출 디렉토리: {output_dir}", file=sys.stderr) + + # 슬라이드 추출 + slides_data = [] + for idx, slide in enumerate(prs.slides, start=1): + if idx not in target_slides: + continue + slide_data = extract_slide(slide, idx, extract_images, output_dir) + slides_data.append(slide_data) + print(f" 슬라이드 {idx}/{total_slides}: {slide_data['title'] or '(제목 없음)'}", file=sys.stderr) + + output = { + 'filename': filename, + 'filepath': os.path.abspath(filepath), + 'total_slides': total_slides, + 'processed_slides': len(slides_data), + 'image_output_dir': output_dir if extract_images else '', + 'slides': slides_data, + } + + indent = 2 if pretty else None + print(json.dumps(output, ensure_ascii=False, indent=indent)) + print(f"\n✅ 추출 완료: {len(slides_data)}개 슬라이드", file=sys.stderr) + + +# ───────────────────────────────────────────── +# CLI 진입점 +# ───────────────────────────────────────────── + +def main() -> None: + parser = argparse.ArgumentParser( + description='PPTX 기획서를 JSON 구조로 추출합니다.', + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument('filepath', nargs='?', help='PPTX 파일 경로') + parser.add_argument('--extract-images', action='store_true', help='슬라이드 이미지를 PNG로 추출') + parser.add_argument('--output-dir', default='', help='이미지 추출 디렉토리') + parser.add_argument('--slides', default='', help='처리할 슬라이드 범위 (예: 1-10, 5, 3-7,10)') + parser.add_argument('--auto-install', action='store_true', help='python-pptx 자동 설치') + parser.add_argument('--pretty', action='store_true', help='JSON 들여쓰기 출력') + args = parser.parse_args() + + _ensure_pptx(args.auto_install) + + if not args.filepath: + parser.print_help() + print("\n❌ PPTX 파일 경로를 입력해 주세요.", file=sys.stderr) + sys.exit(1) + + extract_pptx( + filepath=args.filepath, + extract_images=args.extract_images, + output_dir=args.output_dir, + slide_range=args.slides, + pretty=args.pretty, + ) + + +if __name__ == '__main__': + main() diff --git a/skills/translation-keys/SKILL.md b/skills/translation-keys/SKILL.md index 5cd57aa..6002305 100644 --- a/skills/translation-keys/SKILL.md +++ b/skills/translation-keys/SKILL.md @@ -70,24 +70,26 @@ description: 번역 요청 엑셀 파일의 EN 셀을 기반으로 '번역코드 6. **참조 MD 파일 생성** - 엑셀 저장 완료 후 로컬 참조용 MD 파일을 생성 또는 갱신한다. - - 파일 위치: `doc/translation/<엑셀파일명>.md` (확장자 `.xlsx` → `.md` 치환) - - 예) `smilegate-ax-2026.xlsx` → `doc/translation/smilegate-ax-2026.md` - - `doc/translation/` 디렉토리가 없으면 자동 생성한다. + - 파일 위치: `docs/translation/<엑셀파일명>.md` (확장자 `.xlsx` → `.md` 치환) + - 예) `smilegate-ax-2026.xlsx` → `docs/translation/smilegate-ax-2026.md` + - `docs/translation/` 디렉토리가 없으면 자동 생성한다. - 파일 내용: 엑셀의 **전체** 번역코드(기존 + 신규)를 아래 형식으로 작성 + - 헤더에서 `KO` 컬럼을 탐지하여 함께 기록한다 (개발 시 번역코드 매칭 참조용) ```markdown # 번역 코드 참조 — <엑셀파일명> > 생성일: YYYY-MM-DD > 원본 파일: <엑셀파일 상대경로> - | 번역코드 | EN | - |----------|----| - | BRAND-smilegate-ax | Smilegate x AX | - | NAV-main | Main | - | ... | ... | + | 번역코드 | KO | EN | + |----------|----|----| + | BRAND-smilegate-ax | 스마일게이트 x AX | Smilegate x AX | + | NAV-main | 메인 | Main | + | ... | ... | ... | ``` - - 파일이 이미 존재하면 **덮어쓴다** (엑셀이 단일 소스 오브 트루스). - - 생성 완료 후 경로를 사용자에게 출력한다. + - 파일이 이미 존재하면 **덮어쓰지 않는다**. 새로 생성된 코드 행만 테이블 하단에 추가(append)한다. + - 이미 파일에 존재하는 번역코드는 중복 추가하지 않는다. + - 생성 완료 후 경로와 추가된 행 수를 사용자에게 출력한다. ## 코드 작성 예시 (실제 케이스)