chore(harness): Claude Code 하네스 구성 — 안전 가드·규칙 검사·배포 스킬
- .claude/settings.json (공유) 신설, hooks 를 settings.local.json 에서 이동 - bash_guard (PreToolUse): 로컬 스택 기동·운영 DB alembic·_test 아닌 pytest·main 강제 push·위험 rm 차단, main push/deploy.sh 는 장중 경고와 함께 확인 요청, backend 커밋 전 유닛 스위트 게이트 - rules_check (PostToolUse): py_compile + CLAUDE.md 규칙(json.dumps, except 후 logger.debug, except-pass, 실시간 계층 DB I/O) — diff 추가 라인만 - session_context (SessionStart): 브랜치·미커밋·장 상태·alembic head 주입 - stop_check (Stop): frontend 변경 시 tsc - auto-orchestrate: 서브에이전트 보고·시스템 알림·질문형 프롬프트 오발동 제거 - /deploy 스킬 + deploy.sh / prod_smoke.sh: 테스트→dev push→main 병합→재기동 감시→공개 API 스모크 - 팀 에이전트·orchestrate 스킬 추적 시작, settings.local.json 추적 해제(.gitignore) - CLAUDE.md §9 하네스 - 훅 테스트 69 passed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
82
.claude/hooks/auto-orchestrate.py
Executable file
82
.claude/hooks/auto-orchestrate.py
Executable file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
"""UserPromptSubmit hook: 작업성 요청이면 오케스트레이션 모드 지침을 컨텍스트로 주입한다.
|
||||
|
||||
- 화이트리스트(작업 동사) 방식: 확실한 작업 요청에만 반응해 오탐을 줄인다.
|
||||
- 이스케이프: 프롬프트에 #직접 또는 #no-orch 가 있으면 주입하지 않는다.
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
ACTION = re.compile(
|
||||
r"(추가|수정|구현|개발|만들|생성|작성|고쳐|고치|리팩|변경|바꿔|바꾸|적용|삭제|제거|"
|
||||
r"최적화|개선|버그|디버그|테스트|백테스트|검증|배포|마이그|리뷰|점검|분석|튜닝|"
|
||||
r"보완|정리|통합|연동|설계|스캔|리팩터링|해결|처리해|작업|고도화|"
|
||||
r"\badd\b|\bimplement\b|\bfix\b|\brefactor\b|\bcreate\b|\bbuild\b|\bupdate\b|"
|
||||
r"\bremove\b|\bdelete\b|\boptimi[sz]e\b|\bmigrat|\bdebug\b|\btest\b|\bdeploy\b|"
|
||||
r"\breview\b|\banaly[sz]e\b)",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
SKIP = re.compile(r"(#직접|#no-orch)", re.IGNORECASE)
|
||||
|
||||
# 서브에이전트 보고·시스템 알림에는 주입하지 않는다.
|
||||
NOTIFY_PREFIX = "Another Claude session sent a message"
|
||||
NOTIFY_MARKERS = ("<agent-message", "[Subagent hand-back]", "<task-notification>", "[SYSTEM NOTIFICATION")
|
||||
|
||||
# 강한 작업 동사(분석·점검 같은 약한 명사는 제외)
|
||||
STRONG = re.compile(
|
||||
r"(추가|수정|구현|개발|만들|생성|작성|고쳐|고치|리팩|변경|바꿔|바꾸|적용|삭제|제거|최적화|개선|"
|
||||
r"마이그|튜닝|보완|통합|연동|설계|해결|고도화|배포|"
|
||||
r"\badd\b|\bimplement\b|\bfix\b|\brefactor\b|\bcreate\b|\bbuild\b|\bupdate\b|\bremove\b|"
|
||||
r"\bdelete\b|\boptimi[sz]e\b|\bmigrat|\bdeploy\b)",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
# 질문형: 물음표 종결 / 의문 어미 / 정보 요청
|
||||
QUESTION = re.compile(
|
||||
r"(\?\s*$|(했어|됐어|됬어|했나|했니|했죠|했지|되었어|돼\s*있어|있어|없어|인가|일까|인지|나요|어요|까요|죠|니)\??\s*$|"
|
||||
r"(알려줘|알려주세요|알려줄래|보여줘|보여주세요|설명해줘?|설명해주세요|궁금\S*)\s*[.!]?\s*$)",
|
||||
)
|
||||
# 명시적 작업 요청 어미
|
||||
REQUEST = re.compile(r"(해줘|해\s*주세요|해주라|하세요|해라|합시다|부탁|진행해|시작해)")
|
||||
|
||||
CONTEXT = """[자동 오케스트레이션 모드]
|
||||
이 프롬프트는 작업성 요청으로 감지되었습니다. 답변/작업을 시작하기 전에 Skill 툴로 `orchestrate` 스킬을 호출하고, 그 지침에 따라 처리하십시오 (작업 분해 → 팀 에이전트 병렬/순차 분배 → code-verifier 검증 게이트 → 최종 보고). args 에는 사용자 요청 원문을 그대로 전달합니다.
|
||||
단, 스킬 지침 4번대로 하위 작업이 1개뿐이고 단순하면 오케스트레이션 오버헤드 없이 담당 에이전트 하나만 호출하거나 직접 처리해도 됩니다.
|
||||
사용자가 프롬프트에 #직접 을 넣으면 이 주입은 발생하지 않습니다."""
|
||||
|
||||
|
||||
def main() -> int:
|
||||
try:
|
||||
data = json.load(sys.stdin)
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
prompt = (data.get("prompt") or data.get("user_prompt") or "").strip()
|
||||
|
||||
if not prompt or prompt.startswith("/") or len(prompt) < 6:
|
||||
return 0
|
||||
if prompt.startswith(NOTIFY_PREFIX) or any(m in prompt for m in NOTIFY_MARKERS):
|
||||
return 0
|
||||
if SKIP.search(prompt) or not ACTION.search(prompt):
|
||||
return 0
|
||||
if QUESTION.search(prompt) and not REQUEST.search(prompt.rstrip("?? ")) :
|
||||
# 질문형: 강한 작업 동사가 없거나 짧은 상태 확인("수정했어?")이면 주입하지 않는다.
|
||||
if not STRONG.search(prompt) or len(prompt) <= 40:
|
||||
return 0
|
||||
|
||||
json.dump(
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "UserPromptSubmit",
|
||||
"additionalContext": CONTEXT,
|
||||
},
|
||||
"suppressOutput": True,
|
||||
},
|
||||
sys.stdout,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user