Add history tracking hooks and project structure
- Add docs/history/ directory for work history files - Add PROGRESS.md for project status tracking - Add .claude/settings.json with Gitea MCP permissions - Gitea issue #1: 히스토리 및 AI 사용량 추적 시스템 설정 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
36
historyhooks/auto-lint.sh
Normal file
36
historyhooks/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
historyhooks/guard-history-fields.py
Normal file
80
historyhooks/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
historyhooks/guard-history-fields.sh
Normal file
8
historyhooks/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
historyhooks/guard-history-reminder.sh
Normal file
38
historyhooks/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
historyhooks/path.json
Normal file
4
historyhooks/path.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"_comment": "각 프로젝트에서 이 파일을 복사해 history_path 만 재정의하세요.",
|
||||
"history_path": "docs/history"
|
||||
}
|
||||
23
historyhooks/progress-reminder.sh
Normal file
23
historyhooks/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
historyhooks/protect-files.sh
Normal file
45
historyhooks/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
historyhooks/session-context.sh
Normal file
29
historyhooks/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
historyhooks/session-start.sh
Normal file
25
historyhooks/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
|
||||
Reference in New Issue
Block a user