#!/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 = ("", "[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())