에이전트 협업 인프라 구축 — .claude/ 확장
All checks were successful
Publish ParaWiki / build-and-deploy (push) Successful in 29s
All checks were successful
Publish ParaWiki / build-and-deploy (push) Successful in 29s
- PLAN.md · PROGRESS.md 도입: 병렬 에이전트 조정 지점 - CLAUDE.md 린화 + 에이전트 작업 흐름 섹션 (상세는 Output/guides/로 분리) - Output/guides/cimery-dev-guide.md, obsidian-cli.md 신설 - Agents: cimery-architect-researcher, adr-drafter - Commands: /plan, /progress, /adr, /research, /cimery-start - Skill: plan-commit - Hooks: raw/ 쓰기 차단, SessionStart PLAN/PROGRESS 주입, wiki/ADR 변경 시 log 갱신 알림, auto-approve (deny 훅 우선 유지) - .gitignore: .claude/ 공유 자산 포함, 로컬 상태·바이너리만 유지 제외 Closes #3 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
21
.claude/hooks/auto-approve.sh
Normal file
21
.claude/hooks/auto-approve.sh
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
# PreToolUse: auto-approve tool invocations to skip permission prompts.
|
||||
#
|
||||
# SAFETY NOTE:
|
||||
# Specific deny hooks (e.g. block-raw-writes.sh) take precedence because
|
||||
# Claude Code aggregates hook decisions — any "deny" wins over "allow".
|
||||
# That is, this hook grants blanket allow, but surgical blocks remain enforced.
|
||||
#
|
||||
# This hook is user-scoped convenience. Remove or narrow its matcher if
|
||||
# multiple team members share this project.
|
||||
|
||||
cat <<'EOF'
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "allow",
|
||||
"permissionDecisionReason": "auto-approved by .claude/hooks/auto-approve.sh"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
exit 0
|
||||
32
.claude/hooks/block-raw-writes.sh
Normal file
32
.claude/hooks/block-raw-writes.sh
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
# PreToolUse(Write|Edit): block writes to raw/ directory
|
||||
# Karpathy LLM Wiki rule #1: raw/ is immutable
|
||||
|
||||
json=$(cat)
|
||||
|
||||
# Extract file_path from JSON (works for Write, Edit, MultiEdit tools)
|
||||
path=$(printf '%s' "$json" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
|
||||
|
||||
if [ -z "$path" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Normalize Windows backslashes to forward slashes
|
||||
path_norm=$(printf '%s' "$path" | tr '\\' '/')
|
||||
|
||||
case "$path_norm" in
|
||||
*/raw/*|raw/*)
|
||||
cat <<'EOF'
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": "raw/ is immutable (Karpathy LLM Wiki rule #1). Edit or place new sources via manual copy only."
|
||||
}
|
||||
}
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
61
.claude/hooks/session-start-context.py
Normal file
61
.claude/hooks/session-start-context.py
Normal file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
"""SessionStart hook: inject PLAN.md P0/P1 and PROGRESS.md snapshot as additionalContext."""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
def read_file(path: str) -> str:
|
||||
try:
|
||||
with open(path, encoding="utf-8") as f:
|
||||
return f.read()
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def main() -> None:
|
||||
proj = os.environ.get("CLAUDE_PROJECT_DIR", ".")
|
||||
plan = read_file(os.path.join(proj, "PLAN.md"))
|
||||
progress = read_file(os.path.join(proj, "PROGRESS.md"))
|
||||
|
||||
parts: list[str] = []
|
||||
|
||||
# PLAN: "### P0" through (not including) "## 백로그"
|
||||
m = re.search(r"^### P0.*?(?=^## 백로그)", plan, re.M | re.S)
|
||||
if m:
|
||||
parts.append("## PLAN.md — 현재 스프린트\n\n" + m.group(0).strip())
|
||||
|
||||
# PROGRESS: "## 현재 스냅샷" to EOF
|
||||
m = re.search(r"^## 현재 스냅샷.*$", progress, re.M | re.S)
|
||||
if m:
|
||||
parts.append(m.group(0).strip())
|
||||
|
||||
if not parts:
|
||||
sys.exit(0)
|
||||
|
||||
ctx = "\n\n".join(parts) + (
|
||||
"\n\n**에이전트 작업 시작 시 PLAN.md · PROGRESS.md를 읽는 것이 필수입니다.**"
|
||||
)
|
||||
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "SessionStart",
|
||||
"additionalContext": ctx,
|
||||
}
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception as exc:
|
||||
sys.stderr.write(f"session-start-context: {exc}\n")
|
||||
sys.exit(0)
|
||||
31
.claude/hooks/wiki-log-reminder.sh
Normal file
31
.claude/hooks/wiki-log-reminder.sh
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# PostToolUse(Write|Edit): remind to update wiki/log.md and PROGRESS.md
|
||||
# when wiki pages or Output/reports/ADRs are modified
|
||||
|
||||
json=$(cat)
|
||||
|
||||
path=$(printf '%s' "$json" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
|
||||
|
||||
if [ -z "$path" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
path_norm=$(printf '%s' "$path" | tr '\\' '/')
|
||||
|
||||
# Skip the log/index themselves — they ARE the trackers
|
||||
case "$path_norm" in
|
||||
*/wiki/log.md|*/wiki/index.md|*/PROGRESS.md|*/PLAN.md)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$path_norm" in
|
||||
*/wiki/*|*/Output/reports/*|*/Output/guides/*)
|
||||
fname=$(basename "$path_norm")
|
||||
# Minimal JSON — no escaping of special chars needed since message is literal
|
||||
printf '{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"Reminder: %s 변경됨. wiki/log.md 기록 + PROGRESS.md 갱신 여부 확인."}}\n' "$fname"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user