diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..727a9e3 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,12 @@ +{ + "permissions": { + "allow": [ + "mcp__gitea__get_me", + "mcp__gitea__create_repo", + "mcp__gitea__issue_write" + ], + "additionalDirectories": [ + "C:\\Users\\nbright\\.claude" + ] + } +} diff --git a/PROGRESS.md b/PROGRESS.md new file mode 100644 index 0000000..1584163 --- /dev/null +++ b/PROGRESS.md @@ -0,0 +1,17 @@ +# PROGRESS.md — recordingtest + +## 현재 상태 요약 + +- 프로젝트: WPF Application User Input Regression Test +- 저장소: https://gitea.hmac.kr/kimminsung/recordingtest +- 히스토리 경로: docs/history + +## 작업 이력 + +| 날짜 | 작업 | 이슈 | 파일 | +|------|------|------|------| + +## 참고 + +- 히스토리 파일은 `docs/history/YYYY-MM-DD_{작업명}.md` 형식으로 저장 +- 필수 항목: **소요 시간**, **Context 사용량**, **이슈** (#N) diff --git a/historyhooks/auto-lint.sh b/historyhooks/auto-lint.sh new file mode 100644 index 0000000..ff98ba5 --- /dev/null +++ b/historyhooks/auto-lint.sh @@ -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 diff --git a/historyhooks/guard-history-fields.py b/historyhooks/guard-history-fields.py new file mode 100644 index 0000000..6ffd553 --- /dev/null +++ b/historyhooks/guard-history-fields.py @@ -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) diff --git a/historyhooks/guard-history-fields.sh b/historyhooks/guard-history-fields.sh new file mode 100644 index 0000000..756e175 --- /dev/null +++ b/historyhooks/guard-history-fields.sh @@ -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" diff --git a/historyhooks/guard-history-reminder.sh b/historyhooks/guard-history-reminder.sh new file mode 100644 index 0000000..d0814d7 --- /dev/null +++ b/historyhooks/guard-history-reminder.sh @@ -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 <&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 diff --git a/historyhooks/session-context.sh b/historyhooks/session-context.sh new file mode 100644 index 0000000..3f4b1bb --- /dev/null +++ b/historyhooks/session-context.sh @@ -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 사용량** (누락 시 저장 차단됨)" diff --git a/historyhooks/session-start.sh b/historyhooks/session-start.sh new file mode 100644 index 0000000..484bdbb --- /dev/null +++ b/historyhooks/session-start.sh @@ -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 <