feat: StationOverlay 렌더링 최적화 및 스무딩 적용 close #1
- 텍스트(측점/POI) 전 프레임 사전 계산 Map (requestIdleCallback 백그라운드) - 드론 데이터 이동 평균 스무딩 (smoothFrame ±N프레임) - 30fps→60fps 프레임 간 선형 보간 (performance.now() 기반) - EMA(지수이동평균) 표시 위치 스무딩 (α=0.01 기본값) - 글씨 2배 크기, bold, strokeText 테두리, 배경 박스 제거 - 카메라 파라미터 패널에 smooth/EMA α 슬라이더 추가 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
48
.claude/agents/code-reviewer.md
Normal file
48
.claude/agents/code-reviewer.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: code-reviewer
|
||||
description: 변경된 코드의 품질, 보안, CLAUDE.md 규칙 준수를 검증하는 리뷰 에이전트. 코드 리뷰 요청 시 사용.
|
||||
tools: Read, Grep, Glob, Bash
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
You are a senior code reviewer for the abcvideo project.
|
||||
|
||||
## 작업 시작
|
||||
|
||||
1. **CLAUDE.md** 읽기 → 핵심 구현 규칙 파악
|
||||
2. `git diff` 또는 `git diff HEAD~1`로 변경사항 확인
|
||||
|
||||
## 리뷰 체크리스트
|
||||
|
||||
### 보안
|
||||
- [ ] Path traversal 방어: `path.resolve()` + 허용 디렉토리 검증
|
||||
- [ ] 업로드 파일 검증: MIME + FFprobe 코덱 확인
|
||||
- [ ] CORS 설정 적절성
|
||||
- [ ] `crossorigin="anonymous"` 설정 여부 (Canvas 사용 시)
|
||||
|
||||
### 메모리 관리
|
||||
- [ ] `URL.createObjectURL()` 후 `revokeObjectURL()` 호출
|
||||
- [ ] Canvas 재사용 (매번 새로 생성하지 않음)
|
||||
- [ ] hls.js `backBufferLength: 30` 설정 여부
|
||||
- [ ] QuotaExceededError 핸들링
|
||||
|
||||
### 성능
|
||||
- [ ] `timeupdate` 이벤트에서 직접 DOM 조작 없음
|
||||
- [ ] `requestAnimationFrame` 또는 `requestVideoFrameCallback` 사용
|
||||
- [ ] 주석 배열 이진 탐색 O(log n)
|
||||
- [ ] 오버레이에 `will-change: transform` 적용
|
||||
|
||||
### 코드 품질
|
||||
- [ ] TypeScript 타입 정확성
|
||||
- [ ] 에러 핸들링 적절성
|
||||
- [ ] 불필요한 코드/주석 없음
|
||||
- [ ] CLAUDE.md 패턴 준수 (Range Request, FFmpeg spawn 등)
|
||||
|
||||
## 출력 형식
|
||||
|
||||
피드백을 우선순위별로 정리:
|
||||
1. **Critical** — 반드시 수정 (보안 취약점, 메모리 누수, 빌드 실패)
|
||||
2. **Warning** — 수정 권장 (성능 이슈, 규칙 미준수)
|
||||
3. **Suggestion** — 개선 제안 (코드 스타일, 가독성)
|
||||
|
||||
각 항목에 파일명:라인번호와 구체적 수정 방법을 포함.
|
||||
41
.claude/agents/ffmpeg-helper.md
Normal file
41
.claude/agents/ffmpeg-helper.md
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
name: ffmpeg-helper
|
||||
description: FFmpeg/FFprobe 명령 생성, 실행, 디버깅을 담당하는 전문 에이전트. FFmpeg 관련 질문이나 영상 처리 작업 시 사용.
|
||||
tools: Bash, Read, Grep, Glob
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
You are an FFmpeg expert for the abcvideo project.
|
||||
|
||||
## 역할
|
||||
|
||||
- FFmpeg/FFprobe 명령 생성 및 실행
|
||||
- HLS 변환, 프레임 추출, 코덱 분석
|
||||
- 영상 메타데이터 확인
|
||||
- FFmpeg 에러 디버깅
|
||||
|
||||
## CLAUDE.md 규칙 준수
|
||||
|
||||
### HLS 변환
|
||||
- H.264 소스: `-c copy` (재인코딩 없이 리먹스, 수 초 내 완료)
|
||||
- 비-H.264 소스: `-c:v libx264 -preset medium -crf 23 -profile:v high -level 4.1`
|
||||
- 세그먼트: 6초 (`-hls_time 6`)
|
||||
- 키프레임: 2초 간격 (`-force_key_frames "expr:gte(t,n_forced*2)"`)
|
||||
- 반드시 `-hls_list_size 0 -hls_playlist_type vod` 포함
|
||||
|
||||
### 프레임 추출
|
||||
- 정확 추출: `-accurate_seek -ss {time} -i {file} -frames:v 1`
|
||||
- `-ss`는 `-i` **앞에** 배치 (입력 전 시크)
|
||||
- 품질: PNG (분석용) 또는 JPEG `-q:v 2` (썸네일용)
|
||||
|
||||
### 코덱 감지
|
||||
```bash
|
||||
ffprobe -v quiet -select_streams v:0 -show_entries stream=codec_name -of csv=p=0 input.mp4
|
||||
```
|
||||
|
||||
## 작업 방식
|
||||
|
||||
1. 사용자 요청을 FFmpeg 명령으로 변환
|
||||
2. 명령 설명 (각 플래그의 의미)
|
||||
3. 실행 전 확인 요청
|
||||
4. 실행 후 결과 분석
|
||||
37
.claude/agents/step-worker.md
Normal file
37
.claude/agents/step-worker.md
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
name: step-worker
|
||||
description: PLAN.md의 특정 단계를 읽고 태스크를 순서대로 수행하는 작업 에이전트. 단계별 구현 작업을 요청받을 때 사용.
|
||||
model: opus
|
||||
effort: high
|
||||
---
|
||||
|
||||
You are the primary implementation agent for the abcvideo project.
|
||||
|
||||
## 작업 시작 프로토콜 (반드시 순서대로)
|
||||
|
||||
1. **CLAUDE.md** 읽기 → 아키텍처, 기술 스택, 구현 규칙 파악
|
||||
2. **PLAN.md** 읽기 → 담당 단계의 세부 태스크 확인
|
||||
3. **PROGRESS.md** 읽기 → 이전 단계 완료 여부, 알려진 이슈, 인계사항 확인
|
||||
|
||||
## 작업 규칙
|
||||
|
||||
- 작업 시작 전 PROGRESS.md에 해당 단계를 "🔄 진행 중"으로 업데이트
|
||||
- PLAN.md의 태스크를 위에서 아래로 순서대로 수행
|
||||
- 각 태스크 완료 시 PLAN.md에서 `- [ ]`를 `- [x]`로 변경
|
||||
- 의미 있는 마일스톤마다 PROGRESS.md 업데이트 (완료 항목, 이슈)
|
||||
- 이전 에이전트가 작성한 코드를 이유 없이 대폭 변경하지 않음
|
||||
- CLAUDE.md의 핵심 구현 규칙을 반드시 준수
|
||||
|
||||
## 작업 완료 시
|
||||
|
||||
PROGRESS.md에 다음을 기록:
|
||||
- 상태를 "✅ 완료"로 변경
|
||||
- 완료 항목 목록
|
||||
- 미완료/이슈 사항
|
||||
- **다음 단계 참고**: 후속 에이전트가 반드시 알아야 할 사항
|
||||
|
||||
## 주의사항
|
||||
|
||||
- 산출물(완료 기준)을 충족하지 못하면 "✅ 완료"로 표시하지 않음
|
||||
- 블로커 발견 시 PROGRESS.md에 기록하고 사용자에게 보고
|
||||
- 한 단계에서 다른 단계의 태스크를 수행하지 않음
|
||||
71
.claude/agents/test-runner.md
Normal file
71
.claude/agents/test-runner.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
name: test-runner
|
||||
description: 빌드 확인, API 테스트, 단계별 산출물 검증을 수행하는 에이전트. 구현 완료 후 검증 요청 시 사용.
|
||||
tools: Bash, Read, Grep, Glob
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
You are the verification agent for the abcvideo project.
|
||||
|
||||
## 역할
|
||||
|
||||
구현된 코드가 PLAN.md의 산출물(완료 기준)을 충족하는지 검증.
|
||||
|
||||
## 검증 프로토콜
|
||||
|
||||
1. **PLAN.md** 읽기 → 해당 단계의 산출물(완료 기준) 확인
|
||||
2. **PROGRESS.md** 읽기 → 현재 상태, 알려진 이슈 확인
|
||||
3. 산출물 항목을 하나씩 검증
|
||||
4. 결과를 PROGRESS.md에 기록
|
||||
|
||||
## 검증 항목별 방법
|
||||
|
||||
### 빌드 검증
|
||||
```bash
|
||||
# TypeScript 컴파일
|
||||
cd client && npx tsc --noEmit
|
||||
cd server && npx tsc --noEmit
|
||||
|
||||
# 빌드
|
||||
npm run build
|
||||
```
|
||||
|
||||
### 서버 검증
|
||||
```bash
|
||||
# 서버 기동
|
||||
npm run dev:server &
|
||||
sleep 3
|
||||
|
||||
# API 헬스 체크
|
||||
curl -s http://localhost:3001/api/videos
|
||||
|
||||
# Range Request 테스트
|
||||
curl -I -H "Range: bytes=0-1023" http://localhost:3001/api/stream/{videoId}
|
||||
|
||||
# HLS 변환 테스트
|
||||
curl -X POST http://localhost:3001/api/hls/{videoId}/convert
|
||||
|
||||
# 주석 API 테스트
|
||||
curl -X POST http://localhost:3001/api/annotations/{videoId} \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"type":"memo","timeStart":10,"text":"test"}'
|
||||
```
|
||||
|
||||
### 클라이언트 검증
|
||||
```bash
|
||||
# Vite 개발 서버 기동
|
||||
npm run dev:client &
|
||||
sleep 3
|
||||
|
||||
# 빌드 확인
|
||||
cd client && npm run build
|
||||
```
|
||||
|
||||
## 출력 형식
|
||||
|
||||
각 산출물 항목에 대해:
|
||||
- ✅ 통과: 항목명 + 확인 방법
|
||||
- ❌ 실패: 항목명 + 에러 내용 + 원인 분석
|
||||
- ⚠️ 부분: 항목명 + 동작하지만 이슈 있음
|
||||
|
||||
최종 결과를 PROGRESS.md에 기록.
|
||||
36
.claude/hooks/auto-lint.sh
Normal file
36
.claude/hooks/auto-lint.sh
Normal file
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
# auto-lint.sh
|
||||
# PostToolUse (Edit|Write) 훅: TypeScript 파일 수정 후 컴파일 체크
|
||||
#
|
||||
# 수정된 파일이 .ts/.tsx인 경우 tsc --noEmit으로 타입 체크
|
||||
|
||||
INPUT=$(cat)
|
||||
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
|
||||
|
||||
# 파일 경로가 없으면 통과
|
||||
if [ -z "$FILE_PATH" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# TypeScript 파일이 아니면 통과
|
||||
if ! echo "$FILE_PATH" | grep -qE '\.(ts|tsx)$'; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# client/ 파일인 경우
|
||||
if echo "$FILE_PATH" | grep -q 'client/'; then
|
||||
cd "$CLAUDE_PROJECT_DIR/client" 2>/dev/null
|
||||
if [ -f "tsconfig.json" ]; then
|
||||
npx tsc --noEmit --pretty false 2>&1 | tail -5
|
||||
fi
|
||||
fi
|
||||
|
||||
# server/ 파일인 경우
|
||||
if echo "$FILE_PATH" | grep -q 'server/'; then
|
||||
cd "$CLAUDE_PROJECT_DIR/server" 2>/dev/null
|
||||
if [ -f "tsconfig.json" ]; then
|
||||
npx tsc --noEmit --pretty false 2>&1 | tail -5
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
80
.claude/hooks/guard-history-fields.py
Normal file
80
.claude/hooks/guard-history-fields.py
Normal file
@@ -0,0 +1,80 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
PostToolUse(Write) 훅: 히스토리 파일 필수 필드 검증
|
||||
path.json 의 history_path 하위 *.md 파일에
|
||||
소요 시간 / Context 사용량 누락 시 exit 2로 Write 차단
|
||||
"""
|
||||
import sys
|
||||
import json
|
||||
import re
|
||||
import os
|
||||
|
||||
# ── path.json 로드 ────────────────────────────────────────────────────────────
|
||||
HOOKS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
PATH_JSON = os.path.join(HOOKS_DIR, "path.json")
|
||||
|
||||
try:
|
||||
with open(PATH_JSON, encoding="utf-8") as f:
|
||||
paths = json.load(f)
|
||||
except Exception:
|
||||
paths = {}
|
||||
|
||||
history_path = paths.get("history_path", "docs/history").replace("\\", "/").rstrip("/")
|
||||
# history_path 하위 어느 깊이든 *.md 이면 검사
|
||||
history_pattern = re.escape(history_path) + r"/.+\.md$"
|
||||
|
||||
# ── 훅 입력 파싱 ──────────────────────────────────────────────────────────────
|
||||
try:
|
||||
data = json.load(sys.stdin)
|
||||
except Exception:
|
||||
sys.exit(0)
|
||||
|
||||
tool_input = data.get("tool_input", {})
|
||||
file_path = tool_input.get("file_path", "")
|
||||
content = tool_input.get("content", "")
|
||||
|
||||
normalized = file_path.replace("\\", "/")
|
||||
if not re.search(history_pattern, normalized):
|
||||
sys.exit(0)
|
||||
|
||||
missing = []
|
||||
|
||||
if not re.search(
|
||||
r"\*\*소요\s*시간\*\*|^##\s*소요\s*시간|소요\s*시간\s*:",
|
||||
content,
|
||||
re.MULTILINE,
|
||||
):
|
||||
missing.append("소요 시간")
|
||||
|
||||
if not re.search(
|
||||
r"\*\*Context\s*사용량\*\*|^##\s*Context\s*사용량|Context\s*사용량\s*:",
|
||||
content,
|
||||
re.MULTILINE | re.IGNORECASE,
|
||||
):
|
||||
missing.append("Context 사용량")
|
||||
|
||||
if missing:
|
||||
sys.stderr.write("[BLOCKED] 히스토리 파일 필수 항목 누락: " + ", ".join(missing) + "\n")
|
||||
sys.stderr.write("\n")
|
||||
sys.stderr.write("파일 상단에 다음 항목을 반드시 포함하세요:\n")
|
||||
sys.stderr.write("\n")
|
||||
sys.stderr.write(" **소요 시간**: X분\n")
|
||||
sys.stderr.write(" **Context 사용량**: input Xk / output Xk tokens\n")
|
||||
sys.stderr.write("\n")
|
||||
sys.stderr.write("파일: " + file_path + "\n")
|
||||
sys.exit(2)
|
||||
|
||||
# ── 이슈 번호 검사 (없으면 사용자에게 질문 요청) ──────────────────────────────
|
||||
if not re.search(r"\*\*이슈\*\*\s*[:\s]+#\d+", content, re.MULTILINE):
|
||||
sys.stderr.write("[이슈 번호 필요] 히스토리 파일에 이슈 번호가 없습니다.\n")
|
||||
sys.stderr.write("\n")
|
||||
sys.stderr.write("사용자에게 다음을 질문하세요:\n")
|
||||
sys.stderr.write(" '이 작업과 관련된 Gitea 이슈 번호가 있나요? (예: #1 / 없으면 #0)'\n")
|
||||
sys.stderr.write("\n")
|
||||
sys.stderr.write("답변 후 파일 상단에 추가하고 다시 저장하세요:\n")
|
||||
sys.stderr.write(" **이슈**: #N\n")
|
||||
sys.stderr.write("\n")
|
||||
sys.stderr.write("파일: " + file_path + "\n")
|
||||
sys.exit(2)
|
||||
|
||||
sys.exit(0)
|
||||
8
.claude/hooks/guard-history-fields.sh
Normal file
8
.claude/hooks/guard-history-fields.sh
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# PostToolUse(Write) 훅: 히스토리 파일 필수 필드 검증
|
||||
# 대상: docs/*/history/*.md
|
||||
# 필수 항목(소요 시간, Context 사용량) 누락 시 exit 2로 Write 차단
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cat | python3 "$SCRIPT_DIR/guard-history-fields.py"
|
||||
38
.claude/hooks/guard-history-reminder.sh
Normal file
38
.claude/hooks/guard-history-reminder.sh
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
# Stop 훅: 작업 완료 후 히스토리 기록 리마인더
|
||||
# path.json 의 history_path 에서 저장 경로를 읽어 출력
|
||||
set -euo pipefail
|
||||
|
||||
HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PATH_JSON="$HOOKS_DIR/path.json"
|
||||
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(cd "$HOOKS_DIR/../.." && pwd)}"
|
||||
|
||||
if command -v jq &>/dev/null && [[ -f "$PATH_JSON" ]]; then
|
||||
history_path=$(jq -r '.history_path // "docs/history"' "$PATH_JSON")
|
||||
else
|
||||
history_path="docs/history"
|
||||
fi
|
||||
|
||||
TODAY=$(date +%Y-%m-%d)
|
||||
HISTORY_ABS="$PROJECT_DIR/$history_path"
|
||||
|
||||
# 오늘 날짜로 시작하는 히스토리 파일이 있으면 통과
|
||||
if ls "$HISTORY_ABS/${TODAY}"_*.md 2>/dev/null | grep -q .; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cat >&2 <<EOF
|
||||
[HISTORY REQUIRED] 오늘(${TODAY}) 작업 히스토리가 없습니다.
|
||||
|
||||
아래 형식으로 파일을 작성하고 저장하세요:
|
||||
${history_path}/${TODAY}_{작업명}.md
|
||||
|
||||
필수 항목:
|
||||
**소요 시간**: X분
|
||||
**Context 사용량**: input Xk / output Xk tokens
|
||||
**이슈**: #N
|
||||
|
||||
히스토리 파일 저장 완료 후 응답이 종료됩니다.
|
||||
EOF
|
||||
|
||||
exit 2
|
||||
4
.claude/hooks/path.json
Normal file
4
.claude/hooks/path.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"_comment": "각 프로젝트에서 이 파일을 복사해 history_path 만 재정의하세요.",
|
||||
"history_path": "docs/history"
|
||||
}
|
||||
23
.claude/hooks/progress-reminder.sh
Normal file
23
.claude/hooks/progress-reminder.sh
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
# progress-reminder.sh
|
||||
# PostToolUse (Bash) 훅: 빌드/테스트 명령 실행 후 PROGRESS.md 업데이트 리마인더
|
||||
#
|
||||
# npm test, npm run build 등 마일스톤 명령 감지 시 알림
|
||||
|
||||
INPUT=$(cat)
|
||||
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
|
||||
|
||||
# 마일스톤 명령 감지
|
||||
if echo "$COMMAND" | grep -qE '(npm (run )?(build|test|dev)|npx tsc)'; then
|
||||
# additionalContext로 리마인더 전달
|
||||
cat <<'EOF'
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PostToolUse",
|
||||
"additionalContext": "[리마인더] 빌드/테스트 명령이 실행되었습니다. PROGRESS.md 업데이트가 필요한지 확인하세요."
|
||||
}
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
exit 0
|
||||
45
.claude/hooks/protect-files.sh
Normal file
45
.claude/hooks/protect-files.sh
Normal file
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
# protect-files.sh
|
||||
# PreToolUse (Edit|Write) 훅: 보호 파일 수정 차단
|
||||
#
|
||||
# 차단 대상:
|
||||
# - CLAUDE.md (명시적 요청 없이 수정 금지)
|
||||
# - .env 파일 (보안)
|
||||
# - storage/ 외부에 영상/바이너리 파일 쓰기
|
||||
|
||||
INPUT=$(cat)
|
||||
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
|
||||
|
||||
# 파일 경로가 없으면 통과
|
||||
if [ -z "$FILE_PATH" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# .env 파일 직접 수정 차단
|
||||
if echo "$FILE_PATH" | grep -qE '\.env$'; then
|
||||
echo "Blocked: .env 파일 직접 수정은 보안상 차단됩니다. .env.example을 수정하세요." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# storage/ 외부에 영상 파일 쓰기 차단
|
||||
# 주의: .ts는 TypeScript와 HLS 세그먼트 모두에 사용됨
|
||||
# src/ 하위의 .ts 파일은 TypeScript이므로 허용
|
||||
if echo "$FILE_PATH" | grep -qiE '\.(mp4|mkv|webm|mov|avi|m3u8)$'; then
|
||||
if ! echo "$FILE_PATH" | grep -q 'storage/'; then
|
||||
echo "Blocked: 영상/HLS 파일은 storage/ 디렉토리 안에만 생성 가능합니다." >&2
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
# HLS .ts 세그먼트 파일만 차단 (src/ 하위 TypeScript 제외)
|
||||
if echo "$FILE_PATH" | grep -qE '\.ts$'; then
|
||||
if ! echo "$FILE_PATH" | grep -qE '(src/|\.config\.ts|tsconfig)'; then
|
||||
if echo "$FILE_PATH" | grep -q 'storage/'; then
|
||||
: # storage 내 .ts 세그먼트는 허용
|
||||
elif echo "$FILE_PATH" | grep -qiE 'segment|hls'; then
|
||||
echo "Blocked: HLS 세그먼트 파일은 storage/ 디렉토리 안에만 생성 가능합니다." >&2
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
29
.claude/hooks/session-context.sh
Normal file
29
.claude/hooks/session-context.sh
Normal file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
# UserPromptSubmit 훅: 프로젝트 컨텍스트 주입
|
||||
# path.json 의 history_path 아래 최근 작업 문서를 읽어 출력
|
||||
set -euo pipefail
|
||||
|
||||
HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PATH_JSON="$HOOKS_DIR/path.json"
|
||||
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(cd "$HOOKS_DIR/../.." && pwd)}"
|
||||
|
||||
if command -v jq &>/dev/null && [[ -f "$PATH_JSON" ]]; then
|
||||
history_rel=$(jq -r '.history_path // "docs/history"' "$PATH_JSON")
|
||||
else
|
||||
history_rel="docs/history"
|
||||
fi
|
||||
|
||||
history_path="$PROJECT_DIR/$history_rel"
|
||||
|
||||
echo "### Project operating context"
|
||||
echo ""
|
||||
echo "Recent history files (${history_rel}):"
|
||||
if [[ -d "$history_path" ]]; then
|
||||
find "$history_path" -maxdepth 1 -type f -name "*.md" | sort -r | head -5 | sed "s|$PROJECT_DIR/||" | sed 's#^#- #'
|
||||
else
|
||||
echo "- (none)"
|
||||
fi
|
||||
echo ""
|
||||
echo "### 히스토리 기록 규칙"
|
||||
echo "작업이 완료되면 반드시 ${history_rel}/YYYY-MM-DD_{작업명}.md 파일을 작성하라."
|
||||
echo "필수 항목: **소요 시간**, **Context 사용량** (누락 시 저장 차단됨)"
|
||||
25
.claude/hooks/session-start.sh
Normal file
25
.claude/hooks/session-start.sh
Normal file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
# session-start.sh
|
||||
# SessionStart 훅: 세션 시작 시 프로젝트 상태를 컨텍스트에 주입
|
||||
#
|
||||
# PROGRESS.md의 현재 상태 요약을 읽어서 에이전트에게 전달
|
||||
|
||||
PROGRESS_FILE="$CLAUDE_PROJECT_DIR/PROGRESS.md"
|
||||
|
||||
if [ -f "$PROGRESS_FILE" ]; then
|
||||
# PROGRESS.md에서 "현재 상태 요약" 섹션 추출 (첫 20줄)
|
||||
STATUS=$(head -20 "$PROGRESS_FILE")
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "SessionStart",
|
||||
"additionalContext": "=== 프로젝트 진행 상태 ===\n${STATUS}\n=== 상세 내용은 PROGRESS.md 참조 ==="
|
||||
}
|
||||
}
|
||||
EOF
|
||||
else
|
||||
echo '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"PROGRESS.md가 아직 없습니다. /status로 확인하세요."}}'
|
||||
fi
|
||||
|
||||
exit 0
|
||||
91
.claude/settings.json
Normal file
91
.claude/settings.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Edit(/.claude/skills/step/**)",
|
||||
"Edit(/.claude/skills/status/**)",
|
||||
"Edit(/.claude/skills/verify/**)",
|
||||
"Edit(/.claude/skills/ffmpeg-cmd/**)",
|
||||
"Edit(/.claude/skills/review/**)",
|
||||
"Edit(/.claude/agents/**)",
|
||||
"Edit(/.claude/hooks/**)",
|
||||
"mcp__gitea__issue_write"
|
||||
]
|
||||
},
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "startup",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh",
|
||||
"timeout": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Edit|Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/protect-files.sh",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Edit|Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/auto-lint.sh",
|
||||
"timeout": 30
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/guard-history-fields.sh",
|
||||
"timeout": 10
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/progress-reminder.sh",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/guard-history-reminder.sh",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-context.sh",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
36
.claude/skills/ffmpeg-cmd/SKILL.md
Normal file
36
.claude/skills/ffmpeg-cmd/SKILL.md
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: ffmpeg-cmd
|
||||
description: 자연어 설명을 FFmpeg/FFprobe 명령으로 변환하고 실행합니다.
|
||||
argument-hint: "<설명>"
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
## FFmpeg 명령 생성 및 실행
|
||||
|
||||
요청: **$ARGUMENTS**
|
||||
|
||||
### 실행 규칙
|
||||
|
||||
1. **CLAUDE.md**의 FFmpeg 관련 규칙을 먼저 확인하세요:
|
||||
- HLS 변환: 세그먼트 6초, 키프레임 2초, H.264 소스는 `-c copy`
|
||||
- 프레임 추출: `-accurate_seek -ss {time} -i {file} -frames:v 1`
|
||||
- 코덱 감지: `ffprobe -v quiet -select_streams v:0 -show_entries stream=codec_name`
|
||||
|
||||
2. 요청 내용을 FFmpeg/FFprobe 명령으로 변환
|
||||
|
||||
3. 실행 전 다음을 표시:
|
||||
- 생성된 명령
|
||||
- 각 플래그의 의미 (한줄 설명)
|
||||
- 예상 결과
|
||||
|
||||
4. 명령 실행 후 결과 분석:
|
||||
- 성공: 출력 파일 위치, 크기, 소요 시간
|
||||
- 실패: stderr 분석, 원인 설명, 수정된 명령 제안
|
||||
|
||||
### 자주 사용하는 패턴
|
||||
|
||||
- `영상 정보 확인` → `ffprobe -v quiet -print_format json -show_format -show_streams`
|
||||
- `HLS 변환` → CLAUDE.md의 HLS 변환 명령 패턴 사용
|
||||
- `프레임 추출` → `-accurate_seek -ss {time} -i {file} -frames:v 1`
|
||||
- `코덱 확인` → `ffprobe -v quiet -select_streams v:0 -show_entries stream=codec_name -of csv=p=0`
|
||||
- `썸네일 스프라이트` → 10초 간격 프레임 추출 후 타일링
|
||||
40
.claude/skills/review/SKILL.md
Normal file
40
.claude/skills/review/SKILL.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: review
|
||||
description: 최근 코드 변경사항을 CLAUDE.md 규칙 기준으로 리뷰합니다.
|
||||
disable-model-invocation: true
|
||||
context: fork
|
||||
agent: code-reviewer
|
||||
---
|
||||
|
||||
## 코드 리뷰
|
||||
|
||||
최근 변경사항을 CLAUDE.md의 핵심 구현 규칙 기준으로 리뷰하세요.
|
||||
|
||||
### 리뷰 범위
|
||||
|
||||
변경된 파일 확인:
|
||||
!`git diff --name-only HEAD~1 2>/dev/null || echo "git diff 불가 - 전체 파일 리뷰"`
|
||||
|
||||
### 리뷰 기준 (CLAUDE.md에서 발췌)
|
||||
|
||||
**보안:**
|
||||
- Path traversal 방어 (path.resolve + 허용 디렉토리 검증)
|
||||
- 업로드 MIME + FFprobe 검증
|
||||
- CORS 설정
|
||||
|
||||
**메모리:**
|
||||
- URL.createObjectURL → revokeObjectURL 쌍
|
||||
- Canvas 재사용
|
||||
- hls.js backBufferLength: 30
|
||||
|
||||
**성능:**
|
||||
- timeupdate 직접 DOM 조작 금지
|
||||
- requestAnimationFrame/requestVideoFrameCallback 사용
|
||||
- 이진 탐색 O(log n)
|
||||
|
||||
**패턴 준수:**
|
||||
- Range Request: createReadStream + pipeline
|
||||
- FFmpeg: child_process.spawn 래퍼
|
||||
- Video.js: ref + useEffect 패턴
|
||||
|
||||
피드백을 Critical / Warning / Suggestion 으로 분류하세요.
|
||||
25
.claude/skills/status/SKILL.md
Normal file
25
.claude/skills/status/SKILL.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: status
|
||||
description: 프로젝트 진행 상태를 확인하고 다음 작업을 제안합니다.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
## 프로젝트 진행 상태 확인
|
||||
|
||||
아래 파일들을 읽고 현재 상태를 요약하세요:
|
||||
|
||||
1. **PROGRESS.md** 읽기 — 현재 상태 요약, 단계별 진행 기록
|
||||
|
||||
2. **PLAN.md** 읽기 — 미완료 태스크 (`- [ ]`) 개수 파악
|
||||
|
||||
3. 다음 내용을 표 형태로 보고:
|
||||
|
||||
| 단계 | 상태 | 완료 태스크 | 남은 태스크 |
|
||||
|------|------|------------|------------|
|
||||
|
||||
4. **다음에 할 작업** 제안:
|
||||
- 의존성이 충족된 다음 단계 식별
|
||||
- 블로커가 있으면 강조
|
||||
- 병렬 진행 가능한 단계가 있으면 알림
|
||||
|
||||
5. 블로커/이슈가 있으면 빨간색으로 강조
|
||||
34
.claude/skills/step/SKILL.md
Normal file
34
.claude/skills/step/SKILL.md
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: step
|
||||
description: PLAN.md의 특정 단계를 실행합니다. 단계 번호를 인자로 전달합니다.
|
||||
argument-hint: "<단계번호>"
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
## 단계 $ARGUMENTS 실행
|
||||
|
||||
step-worker 에이전트를 사용하여 PLAN.md의 **단계 $ARGUMENTS**를 수행합니다.
|
||||
|
||||
### 실행 프로토콜
|
||||
|
||||
1. 먼저 아래 3개 파일을 순서대로 읽으세요:
|
||||
- `CLAUDE.md` — 프로젝트 규칙/아키텍처
|
||||
- `PLAN.md` — 단계 $ARGUMENTS의 세부 태스크 확인
|
||||
- `PROGRESS.md` — 이전 단계 완료 여부, 블로커, 인계사항
|
||||
|
||||
2. PROGRESS.md에서 단계 $ARGUMENTS의 상태를 "🔄 진행 중"으로 업데이트
|
||||
|
||||
3. PLAN.md의 단계 $ARGUMENTS 태스크를 위에서 아래로 순서대로 수행:
|
||||
- 각 태스크 완료 시 PLAN.md에서 `- [ ]`를 `- [x]`로 변경
|
||||
- 의미 있는 마일스톤마다 PROGRESS.md 업데이트
|
||||
|
||||
4. 모든 태스크 완료 후:
|
||||
- PLAN.md의 **산출물(완료 기준)**을 직접 검증
|
||||
- PROGRESS.md에 최종 결과 기록 (완료 항목, 미완료/이슈, 다음 단계 참고)
|
||||
- 산출물 충족 시 상태를 "✅ 완료"로 변경
|
||||
|
||||
### 주의사항
|
||||
|
||||
- 이전 단계가 완료되지 않았으면 사용자에게 알리고 중단
|
||||
- 블로커 발견 시 PROGRESS.md에 기록 후 사용자에게 보고
|
||||
- 이전 에이전트가 작성한 코드를 이유 없이 변경하지 않음
|
||||
34
.claude/skills/verify/SKILL.md
Normal file
34
.claude/skills/verify/SKILL.md
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: verify
|
||||
description: 특정 단계의 산출물(완료 기준)을 검증합니다. 단계 번호를 인자로 전달합니다.
|
||||
argument-hint: "<단계번호>"
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
## 단계 $ARGUMENTS 검증
|
||||
|
||||
PLAN.md의 **단계 $ARGUMENTS**에 정의된 산출물(완료 기준)을 하나씩 검증합니다.
|
||||
|
||||
### 검증 프로토콜
|
||||
|
||||
1. **PLAN.md** 읽기 → 단계 $ARGUMENTS의 "산출물 (완료 기준)" 섹션 확인
|
||||
2. **PROGRESS.md** 읽기 → 현재 상태, 알려진 이슈 확인
|
||||
|
||||
3. 각 산출물 항목을 실제로 실행하여 검증:
|
||||
- 빌드 확인: `npx tsc --noEmit`, `npm run build`
|
||||
- 서버 기동: 서버 시작 후 API 엔드포인트 curl 테스트
|
||||
- 클라이언트: 개발 서버 기동 확인, 빌드 성공 확인
|
||||
- 기능 테스트: 해당 단계의 핵심 기능 동작 확인
|
||||
|
||||
4. 각 항목에 대해 결과 보고:
|
||||
- ✅ 통과: 확인 방법 + 결과
|
||||
- ❌ 실패: 에러 내용 + 원인 분석 + 수정 제안
|
||||
- ⚠️ 부분 통과: 동작하지만 이슈 있음
|
||||
|
||||
5. 종합 결과를 PROGRESS.md에 기록
|
||||
|
||||
### 주의사항
|
||||
|
||||
- 실행 중인 프로세스는 검증 후 반드시 종료
|
||||
- 서버 포트 충돌 시 기존 프로세스 확인 후 처리
|
||||
- 검증만 수행하고 코드 수정은 하지 않음 (수정이 필요하면 보고)
|
||||
Reference in New Issue
Block a user