diff --git a/.claude/hookify.block-packages.local.md b/.claude/hookify.block-packages.local.md new file mode 100644 index 0000000..85e7ba8 --- /dev/null +++ b/.claude/hookify.block-packages.local.md @@ -0,0 +1,20 @@ +--- +name: block-packages +enabled: true +event: file +action: block +conditions: + - field: file_path + operator: regex_match + pattern: (^|/)(node_modules|vendor|\.venv|venv|__pycache__|\.git)/ +--- + +πŸ›‘ **Dependency folder edit blocked** + +이 ν΄λ”μ˜ νŒŒμΌμ€ 직접 νŽΈμ§‘ν•  수 μ—†μŠ΅λ‹ˆλ‹€: + +- `node_modules/`, `vendor/` - νŒ¨ν‚€μ§€ λ§€λ‹ˆμ € 관리 폴더 +- `.venv/`, `venv/`, `__pycache__/` - Python ν™˜κ²½/μΊμ‹œ +- `.git/` - Git λ‚΄λΆ€ 데이터 + +변경사항은 νŒ¨ν‚€μ§€ μž¬μ„€μΉ˜ μ‹œ μ†μ‹€λ˜λ©°, 버전 관리에 ν¬ν•¨λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. diff --git a/.claude/hookify.require-tests.local.md b/.claude/hookify.require-tests.local.md new file mode 100644 index 0000000..6d0f439 --- /dev/null +++ b/.claude/hookify.require-tests.local.md @@ -0,0 +1,24 @@ +--- +name: require-tests +enabled: false +event: stop +action: block +conditions: + - field: transcript + operator: not_contains + pattern: npm test|yarn test|pnpm test|pytest|phpunit|pest|cargo test|go test +--- + +# TODO : make test, make check λ“±μœΌλ‘œ λ³€κ²½ν•΄μ„œ μ‚¬μš© + +⚠️ **Tests not detected in transcript** + +μž‘μ—… μ™„λ£Œ μ „ ν…ŒμŠ€νŠΈ 싀행이 κ°μ§€λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. + +변경사항이 정상 λ™μž‘ν•˜λŠ”μ§€ ν™•μΈν•˜κΈ° μœ„ν•΄, ν”„λ‘œμ νŠΈμ— λ§žλŠ” ν…ŒμŠ€νŠΈ λͺ…λ Ήμ–΄ 쀑 **ν•˜λ‚˜λ₯Ό μ‹€ν–‰**ν•˜μ„Έμš”: + +- JavaScript/TypeScript: `npm test`, `yarn test`, `pnpm test` +- PHP: `phpunit`, `pest` +- Python: `pytest` +- Go: `go test` +- Rust: `cargo test` diff --git a/.claude/hookify.sync-docs.local.md b/.claude/hookify.sync-docs.local.md new file mode 100644 index 0000000..4090784 --- /dev/null +++ b/.claude/hookify.sync-docs.local.md @@ -0,0 +1,22 @@ +--- +name: sync-docs +enabled: true +event: file +action: warn +conditions: + - field: file_path + operator: regex_match + pattern: \.(php|ts|tsx|js|jsx|py|svelte|cs|rs)$ +--- + +πŸ“ **Documentation sync required** + +μ½”λ“œκ°€ λ³€κ²½λ˜μ—ˆμŠ΅λ‹ˆλ‹€. `app/Domains/{domain}/docs/`의 κ΄€λ ¨ λ¬Έμ„œλ₯Ό **μΆ”κ°€ν•˜κ±°λ‚˜ κ°±μ‹ **ν•΄μ•Ό ν•˜λŠ”μ§€ ν™•μΈν•˜μ„Έμš”: + +- μƒˆ κΈ°λŠ₯ μΆ”κ°€ μ‹œ β†’ λ¬Έμ„œ **생성** ν•„μš” +- κΈ°μ‘΄ κΈ°λŠ₯ μˆ˜μ •/μ‚­μ œ μ‹œ β†’ λ¬Έμ„œ **κ°±μ‹ ** ν•„μš” + +[ ] `adr/*.md` - μ•„ν‚€ν…μ²˜ 결정사항 λ³€κ²½ μ‹œ +[ ] `bpmn.md` - λΉ„μ¦ˆλ‹ˆμŠ€ ν”„λ‘œμ„ΈμŠ€ 흐름 λ³€κ²½ μ‹œ +[ ] `planning.md` - κΈ°λŠ₯ κ³„νš/μš”κ΅¬μ‚¬ν•­ λ³€κ²½ μ‹œ +[ ] `tech-spec.md` - 기술 λͺ…μ„Έ/API μŠ€νŽ™ λ³€κ²½ μ‹œ diff --git a/.claude/hookify.warn-security.local.md b/.claude/hookify.warn-security.local.md new file mode 100644 index 0000000..91a87b6 --- /dev/null +++ b/.claude/hookify.warn-security.local.md @@ -0,0 +1,19 @@ +--- +name: warn-security +enabled: true +event: file +action: warn +conditions: + - field: new_text + operator: regex_match + pattern: console\.log\(|debugger;|var_dump\(|dd\(|print_r\(|(?i)(api[_-]?key|secret[_-]?key|password|token|auth[_-]?token)\s*[=:]\s*("[^"]{8,}"|'[^']{8,}') +--- + +⚠️ **Debug code or hardcoded secret detected** + +컀밋 μ „ 확인이 ν•„μš”ν•œ νŒ¨ν„΄μ΄ κ°μ§€λ˜μ—ˆμŠ΅λ‹ˆλ‹€: + +- 디버그 μ½”λ“œ: `console.log`, `debugger`, `var_dump`, `dd`, `print_r` +- ν•˜λ“œμ½”λ”©λœ μ‹œν¬λ¦Ώ: `api_key`, `password`, `token` λ“± + +κ°μ§€λœ 파일 κ²½λ‘œμ™€ 라인 번호λ₯Ό μ‚¬μš©μžμ—κ²Œ μ•Œλ €μ£Όμ„Έμš”. μ˜λ„λœ μ½”λ“œμΌ 수 μžˆμœΌλ―€λ‘œ 직접 μ œκ±°ν•˜μ§€ λ§ˆμ„Έμš”. diff --git a/.claude/hooks/notify.sh b/.claude/hooks/notify.sh new file mode 100755 index 0000000..36aad3f --- /dev/null +++ b/.claude/hooks/notify.sh @@ -0,0 +1,87 @@ +#!/bin/bash +# Claude Code task completion notification +# Enhanced version with prompt preview from history + +set -euo pipefail + +# Read stdin JSON input +INPUT=$(cat) + +# Configuration +NOTIFY_SOUND="${CLAUDE_NOTIFY_SOUND:-Glass}" +NOTIFY_TITLE="${CLAUDE_NOTIFY_TITLE:-Claude Code}" +HISTORY_FILE="${HOME}/.claude/history.jsonl" +PROMPT_LENGTH=30 + +# Extract session_id from hook input +SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null) + +# Get last prompt for this session from history.jsonl +get_prompt_preview() { + if [[ -z "$SESSION_ID" ]] || [[ ! -f "$HISTORY_FILE" ]]; then + echo "" + return + fi + + # Find the last entry for this session and get display field + local prompt + prompt=$(grep "\"sessionId\":\"$SESSION_ID\"" "$HISTORY_FILE" 2>/dev/null | tail -1 | jq -r '.display // empty' 2>/dev/null) + + if [[ -n "$prompt" ]]; then + # Remove newlines and truncate + local cleaned + cleaned=$(echo "$prompt" | tr '\n' ' ' | sed 's/ */ /g') + if [[ ${#cleaned} -gt $PROMPT_LENGTH ]]; then + echo "${cleaned:0:$PROMPT_LENGTH}..." + else + echo "$cleaned" + fi + else + echo "" + fi +} + +# Determine status based on stop reason +get_status() { + local reason="$1" + + case "$reason" in + end_turn) echo "βœ“" ;; + max_tokens) echo "⚠" ;; + stop_sequence) echo "β– " ;; + tool_use) echo "βš™" ;; + *) echo "●" ;; + esac +} + +# Get sound based on context +get_sound() { + local reason="$1" + + case "$reason" in + end_turn) echo "Glass" ;; + max_tokens) echo "Basso" ;; + *) echo "$NOTIFY_SOUND" ;; + esac +} + +# Main +REASON=$(echo "$INPUT" | jq -r '.stop_reason // empty' 2>/dev/null) +STATUS=$(get_status "$REASON") +PROMPT=$(get_prompt_preview) +SOUND=$(get_sound "$REASON") + +# Build message +if [[ -n "$PROMPT" ]]; then + MESSAGE="${STATUS} ${PROMPT}" +else + MESSAGE="${STATUS} Task completed" +fi + +# Send notification +escaped_message=${MESSAGE//\"/\\\"} +escaped_title=${NOTIFY_TITLE//\"/\\\"} +escaped_sound=${SOUND//\"/\\\"} +osascript -e "display notification \"${escaped_message}\" with title \"${escaped_title}\" sound name \"${escaped_sound}\"" + +exit 0 diff --git a/.claude/settings.json b/.claude/settings.json index b53aa67..b667581 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -11,7 +11,7 @@ "hooks": [ { "type": "command", - "command": "osascript -e 'display notification \"Task completed\" with title \"Claude Code\" sound name \"Glass\"'" + "command": ".claude/hooks/notify.sh" } ] } @@ -53,7 +53,7 @@ "gitlab@claude-plugins-official": false, "gopls-lsp@claude-plugins-official": false, "greptile@claude-plugins-official": false, - "hookify@claude-plugins-official": false, + "hookify@claude-plugins-official": true, "huggingface-skills@claude-plugins-official": false, "jdtls-lsp@claude-plugins-official": false, "kotlin-lsp@claude-plugins-official": false, diff --git a/.gitignore b/.gitignore index d7668e6..42097ae 100644 --- a/.gitignore +++ b/.gitignore @@ -139,8 +139,8 @@ Icon? .windsurf/* # Claude -.claude/*.local.* .claude/memory +.claude/settings.local.json # GitHub .github/agents/*