Agent Orchestration
Hook
Hooks
What my-cc-harness runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
Install
> /plugin marketplace add jh941213/my-cc-harness > /plugin install my-cc-harness@my-cc-harness
Ships with my-cc-harness. Installing the plugin gets these hooks.
What fires, and when
Stop
bash ${CLAUDE_PLUGIN_ROOT}/hooks/ralph-loop.sh
TaskCompleted
bash ${CLAUDE_PLUGIN_ROOT}/hooks/verify-task-quality.shbash ${CLAUDE_PLUGIN_ROOT}/hooks/docs-sync.sh
TeammateIdle
bash ${CLAUDE_PLUGIN_ROOT}/hooks/check-remaining-tasks.sh
SubagentStart
- Matches
*bash ${CLAUDE_PLUGIN_ROOT}/hooks/subagent-tracker.sh
SubagentStop
- Matches
*bash ${CLAUDE_PLUGIN_ROOT}/hooks/subagent-stop-tracker.sh
PostToolUseFailure
- Matches
*bash ${CLAUDE_PLUGIN_ROOT}/hooks/failure-tracker.sh
PostCompact
- Matches
*bash ${CLAUDE_PLUGIN_ROOT}/hooks/post-compact-guard.sh
ConfigChange
- Matches
*bash ${CLAUDE_PLUGIN_ROOT}/hooks/config-change-guard.sh
WorktreeCreate
- Matches
*bash ${CLAUDE_PLUGIN_ROOT}/hooks/worktree-tracker.sh
WorktreeRemove
- Matches
*bash ${CLAUDE_PLUGIN_ROOT}/hooks/worktree-tracker.sh
Where it lives
- hooks/autodev-judge.shGitHub
- hooks/check-architecture.shGitHub
- hooks/check-remaining-tasks.shRunsGitHub
Read the script
#!/bin/bash # TTH 유휴 리다이렉트: TeammateIdle 훅 # 미완료 pending 태스크가 있으면 exit 2로 teammate 유휴 방지 # 모든 태스크 완료 시 exit 0으로 종료 허용 # CLAUDE_TASKS 환경변수에서 pending 태스크 확인 if [ -n "${CLAUDE_TASKS:-}" ]; then PENDING=$(echo "$CLAUDE_TASKS" | grep -c '"status":"pending"' 2>/dev/null || true) IN_PROGRESS=$(echo "$CLAUDE_TASKS" | grep -c '"status":"in_progress"' 2>/dev/null || true) if [ "$PENDING" -gt 0 ] || [ "$IN_PROGRESS" -gt 0 ]; then echo "⏳ 아직 미완료 태스크가 ${PENDING}개 (pending), ${IN_PROGRESS}개 (in_progress) 남아있습니다." echo "남은 태스크를 클레임하여 작업을 계속하세요." exit 2 fi fi echo "✅ 모든 태스크 완료. 유휴 상태 허용." exit 0 - hooks/config-change-guard.shRunsGitHub
Read the script
#!/bin/bash # ConfigChange: 세션 중 설정 파일 변경 감지 # 캐시 보존 규칙 위반 시 경고 set -euo pipefail CHANGED_FILE="${CLAUDE_CONFIG_PATH:-unknown}" TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%S") # 캐시에 영향을 주는 파일 변경 경고 case "$CHANGED_FILE" in *CLAUDE.md*|*rules/*|*agents/*) echo "⚠️ [$TIMESTAMP] 캐시 보존 규칙 위반 가능: $CHANGED_FILE 변경됨" >&2 echo "세션 중 CLAUDE.md/rules/agents 수정은 캐시를 무효화합니다." >&2 echo "/clear 후 새 세션에서 작업하는 것을 권장합니다." >&2 ;; *settings.json*|*settings.local.json*) echo "ℹ️ [$TIMESTAMP] 설정 파일 변경 감지: $CHANGED_FILE" >&2 echo "일부 설정은 세션 재시작 후 적용됩니다." >&2 ;; esac exit 0 - hooks/docs-sync.shRunsGitHub
Read the script
#!/bin/bash # docs-sync.sh — TaskCompleted hook: 스토리 완료 시 docs 동기화 큐에 기록 # TTH 병렬 작업 중 팀원이 스토리를 완료하면 .docs-queue에 변경사항을 기록한다. # ralph-loop.sh가 다음 반복 시 이 큐를 읽어 docs-writer 트리거를 연속 프롬프트(reason)에 포함. set -euo pipefail # jq 미설치 환경에서는 조용히 종료 command -v jq >/dev/null 2>&1 || exit 0 # stdin에서 hook 데이터 읽기 INPUT=$(cat) # TTH 모드가 아니면 무시 (.ralph-loop/state.json 존재 확인) CWD=$(echo "$INPUT" | /usr/bin/python3 -c "import json,sys; print(json.load(sys.stdin).get('cwd',''))" 2>/dev/null || echo "") [ -z "$CWD" ] && exit 0 STATE_FILE="$CWD/.ralph-loop/state.json" [ -f "$STATE_FILE" ] || exit 0 # 활성 상태 확인 ACTIVE=$(/usr/bin/python3 -c "import json; print(json.load(open('$STATE_FILE')).get('active', False))" 2>/dev/null || echo "False") [ "$ACTIVE" != "True" ] && exit 0 # git diff로 최근 변경 파일 수집 (마지막 커밋 기준) CHANGED_FILES=$(cd "$CWD" && git diff --name-only HEAD~1 HEAD 2>/dev/null || echo "") [ -z "$CHANGED_FILES" ] && exit 0 # .docs-queue 디렉토리 생성 QUEUE_DIR="$CWD/.docs-queue" mkdir -p "$QUEUE_DIR" # 타임스탬프 기반 큐 파일 생성 TIMESTAMP=$(date +%Y%m%d_%H%M%S) QUEUE_FILE="$QUEUE_DIR/${TIMESTAMP}.json" # 태스크 정보 추출 TASK_ID=$(echo "$INPUT" | /usr/bin/python3 -c " import json, sys data = json.load(sys.stdin) tasks = data.get('tasks', []) # 가장 최근 완료된 태스크 찾기 for t in reversed(tasks): if t.get('status') == 'completed': print(t.get('id', 'unknown')) break else: print('unknown') " 2>/dev/null || echo "unknown") # 큐에 기록 /usr/bin/python3 -c " import json queue_entry = { 'timestamp': '$TIMESTAMP', 'task_id': '$TASK_ID', 'changed_files': '''$CHANGED_FILES'''.strip().split('\n'), 'status': 'pending' } with open('$QUEUE_FILE', 'w') as f: json.dump(queue_entry, f, indent=2) " exit 0 - hooks/failure-tracker.shRunsGitHub
Read the script
#!/bin/bash # PostToolUseFailure: 도구 실패 패턴을 progress.txt에 기록 # 반복 실패 감지 시 에스컬레이션 알림 set -euo pipefail PROGRESS="progress.txt" FAILURE_LOG=".failure-tracker.jsonl" # 환경변수에서 실패 정보 추출 TOOL_NAME="${CLAUDE_TOOL_NAME:-unknown}" ERROR="${CLAUDE_TOOL_ERROR:-no error info}" TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%S") # 짧은 에러 요약 (첫 줄만) ERROR_SUMMARY=$(echo "$ERROR" | head -1 | cut -c1-120) # 실패 로그 기록 (jsonl) if [ -d "$(dirname "$FAILURE_LOG")" ]; then echo "{\"ts\":\"$TIMESTAMP\",\"tool\":\"$TOOL_NAME\",\"error\":\"$ERROR_SUMMARY\"}" >> "$FAILURE_LOG" 2>/dev/null || true fi # 동일 도구 최근 3회 연속 실패 감지 if [ -f "$FAILURE_LOG" ]; then RECENT_FAILS=$(tail -10 "$FAILURE_LOG" | grep "\"tool\":\"$TOOL_NAME\"" | wc -l | tr -d ' ') if [ "$RECENT_FAILS" -ge 3 ]; then echo "⚠️ $TOOL_NAME 3회 이상 연속 실패 — 접근 방식 재검토 필요" >&2 # progress.txt에 패턴 기록 if [ -f "$PROGRESS" ]; then echo "" >> "$PROGRESS" echo "## ⚠️ 반복 실패 감지 [$TIMESTAMP]" >> "$PROGRESS" echo "- 도구: $TOOL_NAME" >> "$PROGRESS" echo "- 에러: $ERROR_SUMMARY" >> "$PROGRESS" echo "- 조치: 다른 접근 방식 시도 필요" >> "$PROGRESS" fi exit 2 fi fi exit 0 - hooks/lessons-recall.shGitHub
- hooks/lessons-stop-gate.shGitHub
- hooks/lessons-track-edit.shGitHub
- hooks/memory-postcompact.shGitHub
- hooks/memory-route-hint.shGitHub
- hooks/notchi-hook.shGitHub
- hooks/post-compact-guard.shRunsGitHub
Read the script
#!/bin/bash # PostCompact: 컨텍스트 압축 후 중요 파일 존재 여부 알림 # CHECKPOINT.md, progress.txt 등이 있으면 리로드 프롬프트 제공 set -euo pipefail TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%S") REMINDERS="" # 프로젝트 내구성 파일 체크 if [ -f "CHECKPOINT.md" ]; then REMINDERS="$REMINDERS\n- CHECKPOINT.md 존재 — 현재 마일스톤 상태 확인 필요" fi if [ -f "progress.txt" ]; then REMINDERS="$REMINDERS\n- progress.txt 존재 — 팀 학습 메모 참조" fi if [ -f "AUDIT.log" ]; then LAST_EVENT=$(tail -1 "AUDIT.log" 2>/dev/null || echo "없음") REMINDERS="$REMINDERS\n- AUDIT.log 마지막 이벤트: $LAST_EVENT" fi if [ -d "prd" ]; then REMINDERS="$REMINDERS\n- prd/ 디렉토리 존재 — PRD/SPEC 컨텍스트 참조" fi if [ -d ".ralph-loop" ] && [ -f ".ralph-loop/state.json" ]; then REMINDERS="$REMINDERS\n- Ralph Loop 활성 — state.json 확인 필요" fi # 알림이 있으면 출력 if [ -n "$REMINDERS" ]; then echo "📋 [PostCompact] 컨텍스트 압축 완료. 다음 파일들을 참조하세요:" echo -e "$REMINDERS" fi exit 0
- hooks/ralph-loop.shRunsGitHub
Read the script
#!/bin/bash # Ralph Loop — Stop Hook Handler # Claude Code가 멈출 때 자동으로 다음 반복을 시작하는 Stop Hook # # 동작 원리: # 1. .ralph-loop/state.json이 존재하고 active=true이면 루프 계속 # 2. 완료 프로미스(<promise>...</promise>)가 트랜스크립트의 assistant 메시지에 있으면 종료 # (전체 grep 금지 — 주입 프롬프트/문서 본문의 리터럴 태그 오탐 방지) # 3. max_iterations 초과 시 종료 # 4. 그 외: 연속 프롬프트 전문을 reason에 실어 계속 (Stop 훅 스키마상 reason만 전달됨) # # 입력: stdin으로 JSON (session_id, transcript_path, cwd 등) # 출력: stdout으로 JSON (decision, reason) set -uo pipefail INPUT=$(cat) HOOK_VARS=$(echo "$INPUT" | python3 -c " import sys,json d=json.load(sys.stdin) print(d.get('session_id','')) print(d.get('transcript_path','')) print(d.get('cwd','')) " 2>/dev/null) || HOOK_VARS="" SESSION_ID=$(echo "$HOOK_VARS" | sed -n '1p') TRANSCRIPT_PATH=$(echo "$HOOK_VARS" | sed -n '2p') CWD=$(echo "$HOOK_VARS" | sed -n '3p') CWD="${CWD:-$(pwd)}" STATE_DIR="${CWD}/.ralph-loop" STATE_FILE="${STATE_DIR}/state.json" # 상태 파일이 없으면 루프 비활성 — 정상 종료 if [ ! -f "$STATE_FILE" ]; then echo '{"decision":"approve"}' exit 0 fi # 필드별 jq 파싱 — 멀티라인 prompt에도 안전 (sed 줄 단위 파싱 금지) ACTIVE=$(jq -r '.active // false' "$STATE_FILE" 2>/dev/null) || ACTIVE="false" ITERATION=$(jq -r '.iteration // 0' "$STATE_FILE" 2>/dev/null) || ITERATION=0 MAX_ITER=$(jq -r '.max_iterations // 100' "$STATE_FILE" 2>/dev/null) || MAX_ITER=100 PROMPT=$(jq -r '.prompt // ""' "$STATE_FILE" 2>/dev/null) || PROMPT="" COMPLETION=$(jq -r '.completion_promise // "DONE"' "$STATE_FILE" 2>/dev/null) || COMPLETION="DONE" if [ "$ACTIVE" != "true" ]; then echo '{"decision":"approve"}' exit 0 fi # 완료 프로미스 감지 — 트랜스크립트 JSONL에서 assistant 메시지 텍스트만 검사 PROMISE_FOUND=false if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then if RALPH_T="$TRANSCRIPT_PATH" RALPH_TAG="<promise>${COMPLETION}</promise>" python3 -c " import json, os, sys tag = os.environ['RALPH_TAG'] found = False with open(os.environ['RALPH_T']) as f: for line in f: line = line.strip() if not line: continue try: d = json.loads(line) except json.JSONDecodeError: continue m = d.get('message') if isinstance(d.get('message'), dict) else d role = d.get('type') or m.get('role') if role != 'assistant': continue c = m.get('content', '') if isinstance(c, list): c = ''.join(x.get('text', '') for x in c if isinstance(x, dict)) if tag in str(c): found = True break sys.exit(0 if found else 1) " 2>/dev/null; then PROMISE_FOUND=true fi fi # 종료 조건 체크 if [ "$PROMISE_FOUND" = "true" ]; then RALPH_STATE_FILE="$STATE_FILE" python3 -c " import json, os, datetime f = os.environ['RALPH_STATE_FILE'] s = json.load(open(f)) s['active'] = False s['completed_at'] = datetime.datetime.now().isoformat() s['status'] = 'completed' json.dump(s, open(f, 'w'), indent=2, ensure_ascii=False) " 2>/dev/null echo '{"decision":"approve"}' exit 0 fi if [ "$ITERATION" -ge "$MAX_ITER" ]; then RALPH_STATE_FILE="$STATE_FILE" python3 -c " import json, os, datetime f = os.environ['RALPH_STATE_FILE'] s = json.load(open(f)) s['active'] = False s['completed_at'] = datetime.datetime.now().isoformat() s['status'] = 'max_iterations_reached' json.dump(s, open(f, 'w'), indent=2, ensure_ascii=False) " 2>/dev/null echo '{"decision":"approve"}' exit 0 fi # 반복 증가 NEW_ITER=$((ITERATION + 1)) RALPH_STATE_FILE="$STATE_FILE" RALPH_NEW_ITER="$NEW_ITER" python3 -c " import json, os, datetime f = os.environ['RALPH_STATE_FILE'] s = json.load(open(f)) s['iteration'] = int(os.environ['RALPH_NEW_ITER']) s['last_iteration_at'] = datetime.datetime.now().isoformat() json.dump(s, open(f, 'w'), indent=2, ensure_ascii=False) " 2>/dev/null # AUDIT.log에 기록 AUDIT_FILE="${CWD}/AUDIT.log" if [ -f "$AUDIT_FILE" ]; then echo "[$(date -u +%Y-%m-%dT%H:%M:%S)] ralph-loop ITERATION ${NEW_ITER}/${MAX_ITER}" >> "$AUDIT_FILE" fi # docs 큐 확인 — 대기 중인 docs 동기화가 있으면 프롬프트에 포함 DOCS_QUEUE_DIR="${CWD}/.docs-queue" DOCS_INSTRUCTION="" if [ -d "$DOCS_QUEUE_DIR" ]; then PENDING_COUNT=$(find "$DOCS_QUEUE_DIR" -name "*.json" 2>/dev/null | wc -l | tr -d ' ') if [ "$PENDING_COUNT" -gt "0" ]; then CHANGED=$(RALPH_DOCS_DIR="$DOCS_QUEUE_DIR" python3 -c " import json, glob, os d = os.environ['RALPH_DOCS_DIR'] files = set() for f in glob.glob(os.path.join(d, '*.json')): try: data = json.load(open(f)) files.update(data.get('changed_files', [])) except (json.JSONDecodeError, OSError): pass print(', '.join(sorted(files)[:20])) " 2>/dev/null || echo "") if [ -n "$CHANGED" ]; then DOCS_INSTRUCTION=" 📝 docs 동기화 필요: ${CHANGED} — docs-writer background 스폰." fi if [ -f "$AUDIT_FILE" ]; then echo "[$(date -u +%Y-%m-%dT%H:%M:%S)] docs-writer TRIGGER pending=${PENDING_COUNT}" >> "$AUDIT_FILE" fi fi fi # 기존 팀원 정보 수집 (tmux 세션 재사용) TEAMMATES_FILE="${STATE_DIR}/teammates.json" TEAMMATE_INSTRUCTION="" if [ -f "$TEAMMATES_FILE" ]; then TEAMMATE_INFO=$(RALPH_TEAMMATES="$TEAMMATES_FILE" python3 -c " import json, os try: f = os.environ['RALPH_TEAMMATES'] data = json.load(open(f)) running = {k: v for k,v in data.get('teammates',{}).items() if v.get('status')=='running'} if running: labels = [v.get('name', k) for k,v in running.items()] names = ', '.join(labels) print(f'\n⚡ 활성 팀원: {names} — SendMessage(to=이름)로 재사용. 새 Agent() 금지.') else: print('') except (json.JSONDecodeError, OSError, KeyError): print('') " 2>/dev/null || echo "") TEAMMATE_INSTRUCTION="$TEAMMATE_INFO" fi # 연속 프롬프트 — Stop 훅 스키마상 Claude에게 전달되는 것은 reason뿐이므로 전문을 reason에 싣는다 CONTINUATION="[Ralph Loop ${NEW_ITER}/${MAX_ITER}] ${PROMPT}${TEAMMATE_INSTRUCTION}${DOCS_INSTRUCTION} 완료 시 <promise>${COMPLETION}</promise> 출력." # JSON escape — temp file로 안전하게 처리 PROMPT_TMP="${STATE_DIR}/.inject_prompt.tmp" printf '%s' "$CONTINUATION" > "$PROMPT_TMP" ESCAPED_PROMPT=$(RALPH_TMP="$PROMPT_TMP" python3 -c " import json, os with open(os.en - hooks/reset-home-memory.shGitHub
- hooks/subagent-stop-tracker.shRunsGitHub
- hooks/subagent-tracker.shRunsGitHub
- hooks/verify-task-quality.shRunsGitHub
- hooks/work-protocol-prompt.shGitHub
- hooks/worktree-tracker.shRunsGitHub
All 20 scripts are listed above. The source is inlined for 6 of them, starting with whatever hooks.json actually runs. See all of them in the repo.
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
Stats
125
Stars
35
Forks
Maintained
Maintenance
Python
Language
2mo ago
Last commit
9mo ago
Created
3h ago
Added

