154 lines
11 KiB
Markdown
154 lines
11 KiB
Markdown
# CLAUDE.md — Vignette 프로젝트 작업 지침
|
||
|
||
> Vignette = AI 심리상담 시뮬레이션 훈련 플랫폼 (한신대 산학협력).
|
||
> 모노레포: `apps/api`(FastAPI/Python), `apps/web`(React 19/Vite/Playwright), `docs`, `infra`, `scripts`.
|
||
|
||
## 📚 문서 맵 — 작업 전 해당 가이드를 먼저 읽어라
|
||
|
||
| 목적 | 문서 |
|
||
|---|---|
|
||
| 저장소 개요·빠른 시작 | [`README.md`](./README.md) |
|
||
| **로컬 서버 띄우기·테스트** | [`docs/guides/local-development.md`](./docs/guides/local-development.md) |
|
||
| 시스템 아키텍처·데이터 흐름 | [`docs/guides/architecture.md`](./docs/guides/architecture.md) |
|
||
| 테스트·검증 실행 | [`docs/guides/testing.md`](./docs/guides/testing.md) |
|
||
| 원천문서·갭 로드맵 | [`docs/guides/source-docs-and-gaps.md`](./docs/guides/source-docs-and-gaps.md) |
|
||
| **SSOT 상태판** | [`docs/dev_dashboard.html`](./docs/dev_dashboard.html) |
|
||
| 백로그 | [`docs/ops/backlog-2026-06-26.md`](./docs/ops/backlog-2026-06-26.md) |
|
||
| **서연 아바타 핸드오프** | [`docs/ops/handoff-avatar-seoyeon-2026-06-27.md`](./docs/ops/handoff-avatar-seoyeon-2026-06-27.md) |
|
||
|
||
작업 결과로 동작/구조가 바뀌면 해당 가이드와 SSOT 대시보드를 함께 갱신한다.
|
||
|
||
---
|
||
|
||
## ⚠️ 0. 무조건 OS를 먼저 파악하고 시작한다 (최우선·필수)
|
||
|
||
**어떤 작업이든 명령을 실행하기 전에 OS와 셸을 먼저 확정하라.** 이걸 건너뛰면
|
||
경로/인코딩/도구 차이로 시간을 크게 낭비한다(실제로 그랬다).
|
||
|
||
작업 시작 시 반드시 확인할 것:
|
||
|
||
1. **OS / 셸**: 이 저장소의 주 개발 환경은 **Windows 11 + PowerShell**이다.
|
||
POSIX를 가정하지 마라. Bash 도구도 쓸 수 있으나 셸마다 문법이 다르다.
|
||
2. **경로 규칙**: Windows 절대경로(`D:\...`, `C:\...`). 한글·공백 포함 경로가 흔하다
|
||
(예: OneDrive `문서\카카오톡 받은 파일`). `-LiteralPath`로 다루고, 외부 도구에
|
||
넘기기 전에 **ASCII 이름으로 로컬 복사**해 인코딩/공백 문제를 차단하라.
|
||
3. **PowerShell 판(5.1) 주의**: 인라인 `if(){}else{}`를 식으로 못 쓴다(삼항 없음).
|
||
네이티브 exe stderr를 `2>&1`로 합치지 마라(ErrorRecord로 감싸짐).
|
||
기본 출력 인코딩은 UTF-16 — 다른 도구가 읽을 파일은 `-Encoding utf8`.
|
||
4. **외부 CLI는 실제로 블로킹되는지 확인**: GUI 런처(`soffice.exe` 등)는 즉시
|
||
detach되어 `Start-Process -Wait`가 변환을 안 기다린다. 실제 작업 프로세스
|
||
(`soffice.bin`)를 직접 호출하라. 좀비 프로세스가 락을 잡으면 정리부터 한다.
|
||
5. **도구 가용성 먼저 탐지**: 변환/처리 전에 LibreOffice·pandoc·python 라이브러리·
|
||
Playwright 브라우저 등 무엇이 설치돼 있는지 먼저 확인하고 경로를 잡아라.
|
||
|
||
> 한 줄 요약: **"먼저 OS·셸·경로·도구를 확정한 뒤 실행한다."** 추정 금지.
|
||
|
||
### 0.1 PowerShell 5.1 실행 규칙 (Windows 기본 셸)
|
||
|
||
이 프로젝트의 기본 셸은 **Windows PowerShell 5.1 Desktop**이다. PowerShell 7 문법이나
|
||
Bash 문법을 섞으면 바로 지연된다. 명령을 작성할 때 아래 규칙을 기본값으로 삼아라.
|
||
|
||
- **세션 시작 프리루드**: 한글/UTF-8 출력이 필요한 명령 전에는 아래를 먼저 둔다.
|
||
```powershell
|
||
$ErrorActionPreference = 'Stop'
|
||
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
||
$OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
||
```
|
||
단, `$ErrorActionPreference='Stop'`은 PowerShell cmdlet용 안전장치다. 네이티브 exe의
|
||
실패는 자동으로 예외가 되지 않으므로 실행 후 `$LASTEXITCODE`를 반드시 확인한다.
|
||
- **5.1 미지원 문법 금지**: `? :` 삼항, `??`, `??=`, `&&`, `||`,
|
||
`ForEach-Object -Parallel`, Bash heredoc(`<<EOF`), Bash식 환경변수 주입
|
||
(`FOO=bar command`)을 쓰지 않는다. 값 선택은 명시적 `if/else`와 변수 대입으로 쓴다.
|
||
- **제어문은 파이프라인 값이 아니다**: `foreach (...) { ... } | Format-Table`처럼 쓰면
|
||
5.1에서 파서 오류가 난다. 필요하면 `& { foreach (...) { ... } } | Format-Table`처럼
|
||
스크립트블록을 파이프라인 입력으로 감싼다.
|
||
- **경로는 PowerShell 방식으로 다룬다**: 파일/폴더에는 `-LiteralPath`,
|
||
`Resolve-Path -LiteralPath`, `Join-Path`를 우선 사용한다. 한글·공백·괄호가 있는 경로를
|
||
외부 CLI에 직접 넘기기 전에는 ASCII 임시 경로로 복사하는 편이 낫다.
|
||
- **파일 인코딩을 명시한다**: `Get-Content`/`Set-Content`/`Out-File`에는 필요한 경우
|
||
`-Encoding UTF8`을 붙인다. 단, Windows PowerShell 5.1의 `-Encoding UTF8`은 BOM을 쓴다.
|
||
Node/Python/TS 도구가 읽을 **UTF-8 no BOM** 파일은 .NET API로 쓴다.
|
||
```powershell
|
||
[IO.File]::WriteAllText($path, $text, [Text.UTF8Encoding]::new($false))
|
||
```
|
||
- **한글 출력 깨짐은 파일 손상으로 단정하지 않는다**: 먼저 `OutputEncoding`을 UTF-8로
|
||
맞추고, 필요하면 `Format-Hex`, Node/Python 읽기, 실제 빌드/타입체크로 확인한다.
|
||
- **네이티브 exe stderr를 `2>&1`로 합치지 않는다**: 5.1은 네이티브 stderr를
|
||
`ErrorRecord`로 감싸 파이프라인/문자열 처리와 순서를 흐릴 수 있다. 로그가 필요하면
|
||
stdout/stderr를 별도 파일로 리디렉션하거나 `System.Diagnostics.Process`로 분리 캡처한다.
|
||
- **네이티브 명령은 문자열 조립보다 인자 배열로 호출한다**:
|
||
```powershell
|
||
$exe = 'C:\path\tool.exe'
|
||
$args = @('--flag', $value, '--out', $outPath)
|
||
& $exe @args
|
||
if ($LASTEXITCODE -ne 0) { throw "tool failed: $LASTEXITCODE" }
|
||
```
|
||
PowerShell 파싱이 외부 도구 인자를 망가뜨릴 때만 네이티브 명령 뒤에 `--%`를 검토한다.
|
||
- **HTTP/JSON은 `curl` 별칭을 피한다**: PowerShell의 `curl`은 별칭일 수 있다.
|
||
JSON API는 `Invoke-RestMethod`/`Invoke-WebRequest`와 `ConvertTo-Json`을 우선 사용하고,
|
||
진짜 curl이 필요하면 `curl.exe`를 명시한다.
|
||
- **인라인 Python/Node는 짧고 결정적으로 실행한다**: 여러 줄 코드를 stdin으로 밀어 넣다
|
||
BOM/인용 문제가 나면 `python -c`, UTF-8 no BOM 임시 파일, 또는 base64 전달을 쓴다.
|
||
Python 검증에는 필요 시 `$env:PYTHONUTF8='1'`와 `python -X utf8`을 사용한다.
|
||
- **`powershell.exe -EncodedCommand`는 UTF-16LE base64**다. UTF-8로 인코딩하면 깨진다.
|
||
- **프로세스 대기는 검증한다**: 단순 CLI는 직접 실행하고 `$LASTEXITCODE`를 본다.
|
||
`Start-Process`가 필요하면 `-Wait -PassThru`로 ExitCode를 확인한다. GUI 런처가 즉시
|
||
detach되는 도구(예: LibreOffice `soffice.exe`)는 실제 작업 프로세스와 산출물 생성을
|
||
따로 검증한다.
|
||
|
||
---
|
||
|
||
## 1. 운영 원칙
|
||
|
||
- **가짜 증거로 DONE 표기 금지.** 실증/외부 의존/소유자 결정이 필요한 항목은
|
||
`docs/ops/backlog-*.md`에 분류해 추적한다(B1 코스메틱 · B2 환경제약 · B3 소유자결정 · B4 외부거버넌스).
|
||
- **`docs/dev_dashboard.html`이 SSOT(단일 진실 공급원)다.** 상태·검증 증거·결정 필요·로드맵의 권위 기준이며, 새 발견·작업 결과·상태 변경은 별도 문서로만 남기지 말고 대시보드에 반영/동기화한다. 백로그(`docs/ops/backlog-*.md`)는 대시보드와 일치시킨다(어긋나면 대시보드 기준).
|
||
- 소유자(윤찬) 단독 결정 사안을 임의로 정하지 않는다(월권 금지).
|
||
|
||
## 2. 검증 기준 (프론트 변경 시)
|
||
|
||
- `cd apps/web && npm run typecheck`
|
||
- 레이아웃 변경은 `e2e/layout-visual-gate.spec.ts`(7/7) + 레이아웃 포커스 E2E +
|
||
`e2e/session-layout.spec.ts`(8/8) 무회귀. E2E는 web+api(+DB) 스택이 떠 있어야 한다.
|
||
|
||
## 3. 커뮤니케이션
|
||
|
||
- 모든 대화·주석·커밋 메시지는 한글.
|
||
- git 커밋 메시지에 Co-Authored-By / Claude 관련 문구 추가 금지.
|
||
|
||
---
|
||
|
||
## 4. 이미지 생성(gpt-image-2 = imagegen2) · Live2D식 아바타
|
||
|
||
> "이미지 생성해/만들어/그려줘" 요청 → `~/.claude/skills/codex-image` 스킬이 아래 래퍼를 자동 사용.
|
||
|
||
### 4.1 gpt-image-2 호출 (반드시 래퍼)
|
||
```bash
|
||
bash ~/.codex/imagegen-headless/codex_imagegen.sh \
|
||
--out <경로.png> [--size WxH] [--quality low|medium|high|auto] \
|
||
[-i <참조이미지> ...] [--all] "<프롬프트>"
|
||
```
|
||
- 인증: ChatGPT 구독 OAuth(`~/.codex/auth.json`의 `auth_mode=="chatgpt"`). **API 키 사용 금지**(과금).
|
||
- codex 0.140+는 생성 이미지를 세션 rollout JSONL에 **base64로 인라인 반환** → 래퍼의 `extract_imagegen.py` 추출만 결정적. stdout의 "저장 경로"는 **환각**(직접 codex exec 금지).
|
||
- 프롬프트는 **stdin 파이프**로(인자 전달 시 멈춤). 변주 생성 시 base를 `-i` 참조로 넘겨 아이덴티티·프레이밍 고정.
|
||
- 투명배경 미지원 → 단색 평면 배경으로 생성 후 누끼. 한글 텍스트 렌더 가능(stdin 파이프라 인코딩 문제 없음).
|
||
|
||
### 4.2 누끼(컷아웃)
|
||
- `object-separation` 스킬(BiRefNet): `~/.venvs/object-separation/Scripts/python.exe ~/.agents/skills/object-separation/scripts/separate_object.py <in> <out> --model birefnet-general`
|
||
- 알파 정제(잔류 헤이즈 제거): 임계치 `<35→0, >205→255` + 페더(`docs/avatar-art/seoyeon/publish.py` 참조).
|
||
|
||
### 4.3 Live2D식 아바타(파츠 분리 리깅)
|
||
아바타는 기본 **SVG 파라미터 리그** 또는 **래스터 파츠 분리 리깅**(`apps/web/src/components/avatar/RasterBust.tsx`)으로 렌더. 후자는 `persona.rasterArtSet` 지정 시 활성.
|
||
- 레이어(각각 독립 opacity/교체 → 표정 중에도 깜빡임·입술싱크가 따로 움직임):
|
||
`base(neutral 전신)` + `upperface-<표정>(눈썹+눈)` + `eyelid-closed(깜빡임, 표정 무관)` + `mouth-<표정>` + `mouth-open(립싱크)`.
|
||
- 파이프라인(재현 스크립트는 `docs/avatar-art/seoyeon/`):
|
||
1. `codex_imagegen.sh`로 base + 표정 변주(sad/tired/anxious/warm/startled/eyes-closed/speaking) 생성. 변주는 base를 `-i` 참조로, 동일 평면 배경.
|
||
2. BiRefNet 누끼 → `publish.py`(표준 캔버스 900×1125 정규화 + 알파 정제)로 `apps/web/public/avatar/<artSet>/` 게시.
|
||
3. `make-parts.py`로 특징 영역(upperface/eyelid/mouth) 크롭+페더 파츠를 `parts/` 생성(영역 상수 튜너 블럭).
|
||
- 연결: `persona.ts`의 `AvatarPersona.rasterArtSet` / `Session.tsx`의 `PERSONA_AVATAR_LOOKS[<code>].rasterArtSet` / `RasterBust.tsx`(28표정→클러스터 매핑 포함).
|
||
- dev 미리보기(인증 없음): `/dev/avatar-preview`(`AvatarPreview.tsx`). 스크린샷: `node apps/web/scripts/avatar-shot.mjs`(BASE_URL 환경변수로 포트 지정).
|
||
- 실제 Live2D Cubism(`.moc3`)은 편집기 저작이 필요해 자동화 불가 → 위 레이어 합성이 실용적 대안.
|
||
|
||
### 4.4 진행 중인 아바타 작업 핸드오프
|
||
서연(P1) 아바타 작업은 **별도 세션에서 진행**. 현재 상태·남은 작업(Image #2=짧은 보브 기준 재생성 등)은
|
||
[`docs/ops/handoff-avatar-seoyeon-2026-06-27.md`](./docs/ops/handoff-avatar-seoyeon-2026-06-27.md) 참조.
|