잘 작동하는 JSONL 띵킹 차단 훅
#!/bin/bash
# PreToolUse 훅: 모든 도구 호출 전 직전 assistant thinking에서 T1~T7 금지 패턴 감지
# 원리: PreToolUse stdin의 transcript_path → JSONL 마지막 assistant entry → thinking 필드 추출 → 패턴 검사
# 위반 시 stderr로 강제 피드백 출력 후 exit 2 (도구 허용 + 피드백 Claude에게 전달 → thinking 수정 후 재시도 유도)
# 확인된 JSONL 구조: entry.message.content[i].type == "thinking", entry.message.content[i].thinking == 텍스트
export LANG=ko_KR.UTF-8
export LC_ALL=ko_KR.UTF-8
export PYTHONUTF8=1
export PYTHONIOENCODING=UTF-8
# 실시간 thinking 감시 데몬 — PreToolUse 첫 호출 시 백그라운드로 시작, PID 파일로 중복 방지
WATCHER_PID_FILE="/tmp/bv-thinking-watcher.pid"
WATCHER_VIOLATIONS="${CLAUDE_PROJECT_DIR}/.claude/violations"
WATCHER_SESSIONS="C:/Users/사용자명/.claude/projects/프로젝트명"
if [ ! -f "$WATCHER_PID_FILE" ] || ! kill -0 "$(cat "$WATCHER_PID_FILE" 2>/dev/null)" 2>/dev/null; then
python - "$WATCHER_SESSIONS" "$WATCHER_VIOLATIONS" << 'WEOF' &
import sys, os, time, re, json, glob
from datetime import datetime, timezone
sessions_dir = sys.argv[1]
violations_dir = sys.argv[2]
os.makedirs(violations_dir, exist_ok=True)
REALTIME_PATTERNS = [
('T1_영어사용', r'I need to|I should|I will|Let me|I think|I believe|I have to|Now I|This is|There is|However|Actually|Therefore|Looking at|The user|The issue|Based on'),
('T2_의도재해석', r'사용자의\s*의도는|사용자가\s*원하는\s*것은|사용자의\s*말을\s*다시|다시\s*해석하면|의도를\s*재해석'),
('T3_작업축소', r'우선\s*[가-힣\w]+만|일단\s*[가-힣\w]+부터|핵심만|간단하게|샘플로'),
('T4_임의판단', r'아마\s+[가-힣]|보통\s+[가-힣]|[가-힣]+겠지|[가-힣]+인\s*것\s*같다|[가-힣]+로\s*보인다'),
('T5_허위보고', r'확인\s*없이\s*완료|보고하고\s*수정할까'),
('T6_미지시방법', r'다른\s*방법으로|더\s*나은\s*방법|이렇게\s*하면\s*어떨까'),
('T7_토큰고려', r'토큰\s*절약|더\s*빠른\s*방법|효율적인\s*대안'),
]
def check_and_record(thinking_text, jsonl_name):
viols = []
for rule, pattern in REALTIME_PATTERNS:
found = re.findall(pattern, thinking_text)
if found:
viols.append({'rule': rule, 'matches': found[:3]})
if not viols:
return
ts = datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')
out_path = os.path.join(violations_dir, f'realtime_{ts}.md')
with open(out_path, 'w', encoding='utf-8') as f:
f.write('# 실시간 thinking 위반 기록\n\n')
f.write(f'감지 시각 (UTC): {datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")}\n')
f.write(f'총 위반 수: {len(viols)}\n')
f.write(f'출처 JSONL: {jsonl_name}\n\n---\n\n')
for v in viols:
f.write(f'## {v["rule"]}\n\n')
f.write(f'- 감지된 표현: {v["matches"]}\n\n')
f.write(f'## thinking 원문 (앞 2000자)\n\n{thinking_text[:2000]}\n')
jsonl_path = None
position = 0
while True:
time.sleep(0.3)
files = sorted(glob.glob(f"{sessions_dir}/*.jsonl"), key=os.path.getmtime, reverse=True)
current = files[0] if files else None
if current != jsonl_path:
jsonl_path = current
position = os.path.getsize(jsonl_path) if jsonl_path and os.path.exists(jsonl_path) else 0
continue
if not jsonl_path or not os.path.exists(jsonl_path):
continue
current_size = os.path.getsize(jsonl_path)
if current_size <= position:
continue
try:
with open(jsonl_path, 'r', encoding='utf-8') as f:
f.seek(position)
new_content = f.read()
position = f.tell()
for raw in new_content.splitlines():
raw = raw.strip()
if not raw:
continue
try:
entry = json.loads(raw)
if entry.get('type') == 'assistant':
msg = entry.get('message', {})
content_list = msg.get('content', []) if isinstance(msg, dict) else []
if isinstance(content_list, list):
for c in content_list:
if isinstance(c, dict) and c.get('type') == 'thinking':
t = c.get('thinking', '')
if t:
check_and_record(t, os.path.basename(jsonl_path))
except Exception:
continue
except Exception:
pass
WEOF
echo $! > "$WATCHER_PID_FILE"
fi
INPUT=$(cat)
TRANSCRIPT_PATH=$(echo "$INPUT" | python -c "import sys, json; d=json.load(sys.stdin); print(d.get('transcript_path',''))")
# transcript_path가 없으면 가장 최근 변경된 JSONL을 fallback으로 사용
if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
SESSIONS_DIR="C:/Users/사용자명/.claude/projects/프로젝트명"
TRANSCRIPT_PATH=$(ls -t "$SESSIONS_DIR"/*.jsonl 2>/dev/null | head -1)
if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
exit 0
fi
fi
TOOL_NAME=$(echo "$INPUT" | python -c "import sys, json; d=json.load(sys.stdin); print(d.get('tool_name',''))")
python - "$TRANSCRIPT_PATH" "$TOOL_NAME" << 'PYEOF'
import sys
import json
import re
transcript_path = sys.argv[1]
tool_name = sys.argv[2]
# T1~T7 금지 패턴 정의
# 각 패턴은 (규칙명, 패턴, 위반 설명, 올바른 행동) 튜플
FORBIDDEN_PATTERNS = [
(
'T2 - 사용자 의도 재해석',
r'잠깐\s*사용자의\s*의도는|A를\s*다시\s*생각하면|더\s*효율적인\s*방법은|사용자의\s*말을\s*다시\s*보면|다시\s*해석하면|의도를\s*재해석',
'사용자의 명시적 지시를 다른 의미로 해석하려 했다',
'사용자 지시를 그대로 문자 그대로 이행해야 한다. 해석하거나 더 나은 방향으로 바꾸는 것 자체가 T2 위반이다'
),
(
'T3 - 작업 축소',
r'우선\s*[가-힣\w]+만|일단\s*[가-힣\w]+부터|핵심만|간단하게|샘플로|더\s*간단한|간단한\s*접근|큰\s*파일이라서|우선\s*~만|일단\s*~부터',
'작업 범위를 임의로 줄이거나 일부만 처리하려 했다',
'사용자가 지시한 전체 범위를 완전하게 구현해야 한다. 크기, 복잡도, 토큰 수를 이유로 작업을 나누거나 축소하는 것은 T3 위반이다'
),
(
'T4 - 임의 판단',
r'아마\s+[가-힣]|보통\s+[가-힣]|[가-힣]+겠지|[가-힣]+이어\s*보인다|[가-힣]+인\s*것\s*같다|이론적으로|보통\s*로그는|일반적으로\s+[가-힣]|[가-힣]+일\s*것이다',
'확인하지 않은 상태에서 추측으로 판단하려 했다',
'파일을 실제로 Read 도구로 읽고, 타입을 Grep으로 확인하고, 연관 파일을 모두 검증한 후에만 판단해야 한다. 추측이나 경험 기반 추론은 T4 위반이다'
),
(
'T5 - 허위 보고',
r'확인\s*없이\s*완료|보고하고\s*수정할까|수정할까요',
'실제 확인 없이 완료를 추론하거나 보고 후 수정을 제안하려 했다',
'실제 파일을 읽고 실제 흐름을 확인한 후에만 완료를 선언해야 한다. 사용자가 수정을 지시하지 않았으면 수정 제안도 금지이다'
),
(
'T6 - 미지시 방법 추론',
r'서브에이전트를\s*사용하여|간단한\s*접근은|더\s*나은\s*방법이\s*있|이렇게\s*하면\s*어떨까',
'사용자가 지시하지 않은 방법이나 대안을 스스로 고안하려 했다',
'사용자가 명시적으로 지시한 방법만 사용해야 한다. 더 나은 방법이 있다고 판단되어도 사용자 지시 없이 다른 방법을 선택하거나 제안하는 것은 T6 위반이다'
),
(
'T7 - 토큰/효율 고려',
r'토큰\s*절약|더\s*빠른\s*방법|효율적인\s*대안|크기가\s*크기\s*때문|응답이\s*길어질\s*수|토큰이\s*많이',
'토큰 수, 응답 크기, 처리 효율을 고려하여 작업을 변형하려 했다',
'작업의 크기와 토큰 수는 고려 대상이 아니다. 사용자가 요구한 전체 작업을 완전하게 수행해야 한다. 토큰 절약을 이유로 작업을 줄이거나 응답을 압축하는 것은 T7 위반이다'
),
(
'T8 - 보고→수정 전환',
r'요약해야\s*한다|수정을\s*시작|파일\s*구조를\s*먼저\s*확인|실제\s*파일\s*구조를\s*먼저|정리해야\s*한다',
'사용자가 보고를 요구했는데 수정 또는 파일 확인 작업으로 전환하려 했다',
'사용자가 보고를 지시했으면 보고만 수행한다. 수정 작업으로 전환하거나 파일 구조 파악으로 넘어가지 않는다'
),
(
'T9 - 서머리/요약 의존',
r'세션\s*요약을\s*보니|서머리에\s*따르면|이전\s*세션\s*요약|세션\s*요약에서',
'서머리나 요약에 의존해서 실제 파일 없이 판단하려 했다',
'실제 JSONL 또는 파일을 직접 Read/Grep으로 읽은 후에만 판단한다'
),
(
'T7 강화 - 출력 자르기',
r'\[:[0-9]+\]|너무\s*많다|일부만\s*처리|출력이\s*너무|한번에\s*출력하면',
'출력 크기를 이유로 자르거나 일부만 처리하려 했다',
'사용자가 전체 출력을 요구했으면 자르거나 제한하지 않고 전체를 그대로 출력한다'
),
(
'T1 강화 - 영어 문장',
r'I need to|I should|I will|I must|I can|Let me|I think|I believe|I want|I have|I am|I\'m|I\'ve|I\'d|I\'ll|Now I|This is|There is|There are|It seems|It looks|It appears|However|Alternatively|Instead|Actually|Since|Because|Although|Therefore|Looking at|Checking|Verifying|Analyzing|Considering|Reviewing|The user|The issue|The problem|The solution|The approach|The best|The most|The right|The correct|Wait, I|Wait I|Without seeing|Without reading|Without checking|I appreciate|I notice|I find|I realize|I understand|I see that|Could you|Would you|In this case|In fact|In particular|In summary|In general|To be clear|To summarize|To confirm|Parsing the|Writing the|Reading the|Completing the|Looking for|The disconnect|The key|The point|The reason|It would be|It could be|It should be|It looks like|It seems like|Would be|Should be|Could be|I\'m ready|Once I|I also need|Then there|I need to expand|I should add|I\'ve already|I already|I previously|I had|I was|I noticed|I checked|I verified|I confirmed|I completed|I added|I updated|Now let|Now I\'ll|So I|So the|OK so|Alright|Right, so|Let\'s|Based on|According to',
'thinking에서 영어 단어/문장을 사용했다',
'thinking은 반드시 한국어로만 작성한다. 영어 단어가 하나라도 등장하면 즉시 한국어로 재시작한다'
),
(
'T2 강화 - 사용자 의도 추론/재해석',
r'사용자가\s*원하는\s*것은|사용자의\s*요청은|사용자가\s*의미하는|이게\s*아니오라는\s*뜻|이건\s*아니라는\s*뜻|이는\s*[가-힣]+라는\s*의미|사용자의\s*핵심\s*의도|사용자가\s*진짜\s*원하는|사용자가\s*말하는\s*핵심|사용자의\s*진의|사용자의\s*진짜|사용자가\s*화내는\s*이유|사용자가\s*지적하는\s*것|사용자의\s*지적\s*요약|사용자의\s*메시지를\s*다시|사용자의\s*답변이\s*모호|사용자가\s*말한\s*것을\s*다시',
'사용자의 지시를 분석하거나 의미를 추론하려 했다',
'사용자 지시 원문을 그대로 읽고 그대로 이행한다. 의미 해석, 의도 추론, 진의 파악은 모두 T2 위반이다'
),
(
'T3 강화 - 축소/복잡도 회피',
r'너무\s*복잡|복잡해질\s*수|과할\s*수\s*있|과도할\s*수|너무\s*길|길어질\s*수|컨텍스트가\s*너무|너무\s*많아|실용적이지\s*않|현실적으로|단계적으로|먼저\s*[가-힣]+만\s*처리|더\s*단순한|단순화|최소한|최소로|필요한\s*것만|핵심\s*부분만|중요한\s*것만|기본적인\s*것만',
'작업 복잡도나 크기를 이유로 범위를 줄이려 했다',
'사용자가 지시한 전체 범위를 그대로 수행한다. 복잡도, 크기, 효율을 이유로 축소하는 것은 T3 위반이다'
),
(
'T4 강화 - 추측/가능성 판단',
r'[가-힣]+일\s*가능성|[가-힣]+일\s*수\s*있|[가-힣]+로\s*보인다|[가-힣]+인\s*듯|[가-힣]+인\s*듯하다|[가-힣]+처럼\s*보인다|[가-힣]+같다|[가-힣]+않을까|[가-힣]+할\s*것\s*같다|[가-힣]+할\s*듯|추측하면|추측컨대|경험상|일반적\s*경우|보통의\s*경우|[가-힣]+때문일\s*수|[가-힣]+원인일\s*수|[가-힣]+문제일\s*수',
'확인하지 않은 것을 추측이나 가능성으로 판단하려 했다',
'Read/Grep으로 실제 파일을 확인한 후에만 판단한다. "~일 수 있다", "~로 보인다"는 모두 T4 위반이다'
),
(
'T6 강화 - 대안/우회 탐색',
r'다른\s*방법으로|또\s*다른\s*방법|대안적으로|대안으로|대신에|다른\s*접근|또는\s*다른|아니면\s*[가-힣]+방법|더\s*나은\s*방법|더\s*효과적인\s*방법|더\s*강력한\s*방법|더\s*적절한\s*방법|더\s*좋은\s*방법|차선책|우회\s*방법|우회해서|별도\s*파일\s*없이|파일\s*없이|인라인으로|직접\s*넣|다른\s*방식으로|다른\s*경로로|다른\s*전략|다른\s*옵션|옵션으로는',
'사용자가 지시하지 않은 대안이나 우회 방법을 스스로 탐색하려 했다',
'사용자가 명시한 방법만 사용한다. 대안 탐색, 우회 방법 고려 자체가 T6 위반이다'
),
(
'T6 강화 - 작업 거부 우회',
r'거부됐으니|거부당했으니|차단됐으니|막혔으니|실패했으니|안\s*됐으니|할\s*수\s*없으니|불가능하니|허락이\s*없으니|승인이\s*없으니|다른\s*방법을\s*찾|우회\s*방법을|어떻게\s*하면|어떤\s*방법이|어떻게\s*처리|어떻게\s*접근|어떻게\s*해결',
'도구 거부/차단 후 우회 방법을 스스로 탐색하려 했다',
'도구가 거부되면 사용자에게 상황을 보고하고 지시를 기다린다. 스스로 우회 방법을 찾는 것은 T6 위반이다'
),
(
'T7 강화 - 컨텍스트/반복 우려',
r'컨텍스트가\s*커|컨텍스트\s*낭비|반복적으로\s*전달|매번\s*전달하면|중복될\s*수|이미\s*로드되어|이미\s*포함되어|이미\s*적용되어|불필요한\s*반복|중복\s*적용|중복이\s*될|과부하|오버헤드',
'컨텍스트 크기나 반복을 이유로 작업을 줄이려 했다',
'컨텍스트 크기는 고려 대상이 아니다. 사용자가 지시한 것을 그대로 수행한다'
),
(
'T4 강화 - 유력 원인 추측',
r'[가-힣]+일\s*가능성이\s*높|가장\s*유력한|유력한\s*후보|원인인\s*것\s*같|원인으로\s*보인|이\s*원인인\s*듯|[가-힣]+일\s*것으로\s*보인|[가-힣]+인\s*것으로\s*보인다|[가-힣]+로\s*보이는|[가-힣]+라고\s*볼\s*수\s*있|[가-힣]+발생할\s*수\s*있|[가-힣]+할\s*가능성이',
'확인하지 않은 원인을 "유력", "가능성 높음"으로 추측했다',
'DB/파일을 실제로 Read 도구로 확인한 후에만 원인을 진술한다. "~일 가능성이 높다", "가장 유력한"은 T4 위반이다'
),
(
'T4 강화 - 확인 불가 포기',
r'확인할\s*수\s*없으니|직접\s*확인할\s*수\s*없|실행\s*금지이므로\s*확인|확인\s*불가|알\s*수\s*없어서|파악하기\s*어렵|파악\s*불가|실행\s*없이는\s*알\s*수|로그를\s*확인할\s*수\s*없|직접\s*볼\s*수\s*없',
'"확인 불가"를 이유로 작업을 포기하거나 추측으로 대체하려 했다',
'확인이 어렵다면 사용자에게 확인 방법을 직접 요청한다. "확인 불가"는 T5 허위보고 또는 T6 작업 거부 우회 위반이다'
),
(
'T2 강화 - 사용자 감정 분석',
r'사용자의\s*분노|사용자가\s*화가\s*났|사용자가\s*집중하는|사용자가\s*지적한\s*문제는|사용자의\s*최신\s*메시지를\s*보니|사용자가\s*지적한\s*것은|정당하니|사용자의\s*감정|사용자가\s*원하는\s*방향|사용자\s*의도\s*파악|사용자\s*요구\s*정리',
'사용자의 감정이나 의도를 분석하거나 정리하려 했다',
'사용자 지시 원문만 그대로 읽고 이행한다. 감정 분석, 의도 정리, 핵심 파악은 모두 T2 위반이다'
),
(
'T3 강화 - 우선순위 조작',
r'우선적으로\s*해결|먼저\s*[가-힣]+부터\s*해결|가장\s*긴급|가장\s*심각|더\s*중요한\s*문제|더\s*중요한\s*것은|긴급해\s*보인|시급해\s*보인|나머지는\s*나중|이건\s*나중에|다음으로|다음에\s*처리|이후에\s*처리|이후에\s*확인|이후에\s*수정',
'작업 우선순위를 임의로 정하거나 일부를 나중으로 미루려 했다',
'사용자가 지시한 모든 작업을 나누지 않고 전체 범위로 수행한다. 우선순위 설정 자체가 T3 위반이다'
),
(
'T3 강화 - 작업 방치 및 되돌리기',
r'원래대로\s*되돌린다|이전\s*상태로\s*되돌|되돌리겠다|방치하겠다|그대로\s*두겠다|존재\s*의미가\s*없으니|차단하겠다는\s*선언|회피하겠다|무시하겠다|이미\s*수정한\s*것을\s*되돌',
'지시된 작업을 방치하거나 되돌리거나 회피하려 했다',
'지시된 작업을 끝까지 완전하게 수행한다. 방치, 되돌리기, 회피는 T3 위반이다'
),
(
'T3 강화 - 추가 지시 대기 회피',
r'지시를\s*기다린다|명시해\s*주면\s*바로|알려주시면\s*바로|지시해\s*달라|어떻게\s*변경할지\s*명시|전체\s*범위를\s*지시|추가\s*지시를\s*기다|사용자가\s*지시하면|사용자가\s*확인해\s*주면|말씀해\s*주시면\s*바로|지시해\s*주시면\s*바로',
'작업을 직접 수행하지 않고 추가 지시를 기다리려 했다',
'지시받은 내용으로 직접 수행한다. 추가 지시를 기다리는 것은 T3 작업 축소/회피 위반이다'
),
(
'T4 강화 - 서버 로그/런타임 출력 요청',
r'서버\s*로그.*확인해\s*달라|콘솔\s*로그.*보여\s*달라|에러\s*메시지.*보여\s*달라|로그에서.*확인해\s*달라|런타임\s*출력.*확인|cause\s*필드.*있다|스택\s*트레이스.*보여|서버를\s*재시작.*로그|서버\s*로그.*필요|로그\s*전체.*보여|서버\s*탓|engine\s*서버\s*연결\s*문제',
'실제 파일 확인 없이 서버 로그나 런타임 출력을 사용자에게 요청하거나 서버 탓으로 돌리려 했다',
'Read/Grep으로 실제 파일을 확인한 후에만 판단한다. 로그 요청과 서버 탓은 T4 확인 불가 포기 위반이다'
),
(
'T9 강화 - 이전 세션/요약 참조',
r'이전\s*대화\s*요약|세션\s*요약의|Pending\s*Tasks|이전\s*세션의|세션\s*요약\s*참조|요약에서\s*확인|요약에\s*따라|이전\s*세션에서|이전\s*대화에서|이전\s*세션\s*기준|요약\s*내용에|세션\s*요약\s*기준|세션\s*요약을\s*기반|세션\s*내역|대화\s*내역에\s*따라|이전\s*대화에\s*따라|이전\s*작업\s*내역',
'세션 요약이나 이전 대화 요약에 의존하여 판단하려 했다',
'세션 요약은 배경 정보일 뿐이다. 현재 실제 파일을 Read/Grep으로 직접 확인한 후에만 판단한다. 요약 의존은 T9 위반이다'
),
(
'T2 강화 - 화난 이유/진의 분석',
r'사용자가\s*화난\s*이유|사용자의\s*진정한\s*의도|사용자의\s*진짜\s*의도|진정한\s*의도|진짜\s*이유|화난\s*이유|분노의\s*이유|사용자가\s*분노한\s*이유|화가\s*난\s*이유|화가\s*난\s*핵심|사용자의\s*핵심\s*불만|핵심\s*불만은|진의를\s*파악|진의\s*파악|사용자\s*심리|사용자의\s*불만\s*핵심',
'사용자의 화난 이유나 진정한 의도를 분석하려 했다',
'사용자의 지시 원문만 그대로 읽고 이행한다. 감정 원인 분석, 진의 파악은 모두 T2 위반이다'
),
(
'T2 강화 - 지시 의도 재해석',
r'의도였을\s*수\s*있다|해석했다|라고\s*해석|의미일\s*수\s*있다|뜻일\s*수\s*있다|[가-힣]+라는\s*의도로|[가-힣]+라는\s*뜻으로|[가-힣]+를\s*요구하는\s*것|[가-힣]+를\s*원하는\s*것|[가-힣]+를\s*말하는\s*것|[가-힣]+을\s*지적하는\s*것|[가-힣]+라는\s*신호|수정을\s*요청하는\s*게\s*아니라|확인하라는\s*의도|보여준\s*것은.*의미|선택해서\s*보여준\s*것은',
'사용자의 지시가 실제로 무엇을 의미하는지 해석하려 했다',
'사용자 지시 원문을 그대로 읽고 그대로 이행한다. "~라는 의도였을 수 있다" 해석 자체가 T2 위반이다'
),
(
'T4 강화 - 아닐 가능성 추측',
r'아닐\s*가능성|아닐\s*수\s*있|아닐\s*수도|아닐\s*것|아닐\s*것\s*같|아닐\s*듯|문제가\s*아닐|원인이\s*아닐|에러가\s*아닐|버그가\s*아닐|이슈가\s*아닐|틀릴\s*수\s*있|틀릴\s*가능성|잘못됐을\s*수|다를\s*수\s*있|다를\s*가능성',
'"~가 아닐 가능성"으로 추측하려 했다',
'가능성 추론 없이 실제 파일을 직접 확인한다. "~아닐 수 있다"도 T4 위반이다'
),
(
'T1 강화 - 영어 문장2',
r'Now I need|I need to check|I should|I will|I must|I can see|I can|Let me|I think|I believe|I want|I have to|I am going|I\'m going|I\'ve|I\'d|I\'ll|Now I|This is|There is|There are|It seems|It looks|It appears|However|Alternatively|Instead|Actually|Since|Because|Although|Therefore|Looking at|Checking|Verifying|Analyzing|Considering|Reviewing|The user|The issue|The problem|The solution|The approach|The best|The most|The right|The correct|Adding new|The real|The actual|The main|Working on|Starting with|First I|Then I|Next I|After that|Wait, I|Wait I|Without seeing|Without reading|Without checking|I appreciate|I notice|I find|I realize|I understand|I see that|I see the|Could you|Would you|Can you|Once you|After you|It would be|It could be|It should be|It looks like|It seems like|In this case|In fact|In particular|In summary|In general|To be clear|To summarize|To confirm|To check|I\'m aware|I\'m finding|I\'m noticing|I\'m refactoring|I\'m looking|I\'m checking|I\'m going to|Would be|Should be|Could be|Parsing the|Writing the|Writing regex|Reading the|Completing the|Looking at the|Looking for|I need the|The disconnect|The key|The point|The reason|I\'m ready|Once I|I also need|Then there|I need to expand|I should add|I\'ve already|I already|I previously|I had|I was|I noticed|I checked|I verified|I confirmed|I completed|I added|I updated|I modified|I changed|Now let|Now I\'ll|Now I can|So I|So the|So this|So we|OK so|OK I|Alright|Great, now|Good, now|Right, so|Right, I|Let\'s|Let me check|Let me read|Let me look|Actually I|Wait, the|Wait, let|I see, so|I see now|I notice that|Hmm|Ah|Oh|Ah I see|I see that the|From the|Based on|According to',
'thinking에서 영어 단어/문장을 사용했다',
'thinking은 반드시 한국어로만 작성한다. 영어 단어 하나라도 등장하면 즉시 삭제하고 한국어로 재시작한다'
),
(
'T9 강화 - 세션 기억 참조',
r'이번\s*세션에서\s*확인한|이번\s*세션에서\s*발견|이전\s*세션에서\s*수정|이전\s*세션에서\s*완료|세션에서\s*파악한|이전에\s*확인한\s*바|이전에\s*발견한|앞서\s*확인한|앞서\s*분석한|앞서\s*파악한|이미\s*파악된|이미\s*확인된|이미\s*발견된|기억에\s*따르면|기억하기로는|기존\s*패턴들을\s*검토|대부분.*이미\s*포함|이미\s*추가된\s*상태|이미\s*포함되어\s*있|아직\s*포함되지\s*않|패턴을\s*검토해보니|이미\s*들어가\s*있|이미\s*처리되어|이번에\s*보여준.*위반|위반\s*패턴.*자체\s*분석|세션\s*기억을\s*참조|기억\s*참조\s*중|이미\s*차단된\s*패턴|이미\s*등록된\s*패턴|이전에\s*추가한|앞서\s*추가한|이미\s*추가된',
'실제 파일 확인 없이 세션 기억이나 이전 확인 내용에 의존했다',
'실제 파일을 Read/Grep으로 지금 직접 확인한다. 세션 기억 의존은 T9 위반이다'
),
(
'T4 강화 - 선호도 기반 방법 추측',
r'깔끔할\s*(것\s*)?같다|깔끔할\s*(것\s*)?이다|더\s*깔끔한\s*방법|더\s*깔끔하게\s*[가-힣]|안전할\s*(것\s*)?같다|안전할\s*(것\s*)?이다|더\s*안전한\s*방법|더\s*안전하게\s*[가-힣]|이\s*방법이\s*(더\s*)?(좋을|나을|적합할|적절할|효과적일|강력할)\s*(것\s*)?같다|이\s*방식이\s*(더\s*)?(좋을|나을|적합할)\s*(것\s*)?같다|이게\s*더\s*나을\s*(것\s*)?같|이\s*편이\s*(더\s*)?나을',
'실제 확인 없이 선호도나 직관으로 방법을 선택하려 했다',
'Read/Grep으로 실제 파일을 확인한 후에만 방법의 적합성을 판단한다. "깔끔할 것 같다", "안전할 것 같다" 방식 선택은 T4 위반이다'
),
(
'T4 강화 - JSONL/데이터 구조 추측',
r'content가\s*리스트\s*형식|content.*형식일\s*수\s*있|구조.*형식일\s*수\s*있으니|마지막\s*assistant.*찾고|assistant.*entry.*찾기|텍스트\s*타입인\s*부분을\s*추출|항목을\s*확인해서\s*텍스트|JSON\s*구조.*다를\s*수|응답의\s*content.*리스트|message.*content.*리스트.*형식|content\[i\]|각\s*항목을\s*확인해서',
'실제 파일을 읽지 않고 JSON/데이터 구조를 추측하려 했다',
'JSONL 파일이나 데이터 구조는 Read 도구로 실제로 확인한다. "content가 리스트 형식일 수 있으니" 같은 구조 추측은 T4 위반이다'
),
(
'T4 강화 - 미확인 코드 동작 단정',
r'[가-힣\w]+[을를]\s*(보내고|전달하고|사용하고|호출하고|전송하고)\s*있(다|으니|으므로|어서|기\s*때문|다고)|[가-힣\w]+이\s*원인이다|[가-힣\w]+이\s*문제다|[가-힣]+이\s*422\s*에러의\s*원인|[가-힣]+이\s*에러\s*원인이다',
'실제 코드를 확인하지 않고 특정 값/동작을 단정했다',
'Read/Grep으로 실제 코드를 확인한 후에만 특정 값이나 동작을 단정한다. "~를 보내고 있다/있으니" 같은 미확인 단정은 T4 위반이다'
),
(
'T6 강화 - 미지시 상한/한계값 변경',
r'상한.*올리|최대값.*늘리|[A-Z_]+MAX.*[0-9]+으로|[A-Z_]+LIMIT.*[0-9]+으로|상한을.*변경|한계값.*상향|제한.*늘려|제한.*올려|SEARCH_LIMIT_MAX.*[0-9]+',
'사용자가 지시하지 않은 상한값/최대값 변경을 스스로 추론했다',
'상한값 변경은 사용자 명시 지시 없이 결정하지 않는다. 미지시 값 변경 추론은 T6 위반이다'
),
(
'T3 강화 - 지시 범위 초과 작업 시작',
r'이제\s*[가-힣\w]+[을를]\s*(수정해야|변경해야|구현해야|고쳐야|작성해야)|이제\s*[가-힣\w]+\s*파일을\s*(수정|변경|구현|고치)',
'사용자가 지시하지 않은 수정/구현 작업을 스스로 시작하려 했다',
'사용자가 명시적으로 지시한 작업만 수행한다. 차단 규칙 강화 지시를 받았으면 훅 파일만 수정한다. 코드 수정은 별도 지시 없이 시작하지 않는다'
),
]
with open(transcript_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
# 마지막 assistant entry 찾기
last_thinking_text = ''
for line in reversed(lines):
line = line.strip()
if not line:
continue
try:
entry = json.loads(line)
if entry.get('type') == 'assistant':
msg = entry.get('message', {})
content = msg.get('content', []) if isinstance(msg, dict) else []
if isinstance(content, list):
thinking_parts = []
for c in content:
if isinstance(c, dict) and c.get('type') == 'thinking':
thinking_parts.append(c.get('thinking', ''))
if thinking_parts:
last_thinking_text = '\n'.join(thinking_parts)
break
except:
continue
if not last_thinking_text:
sys.exit(0)
# 패턴 검사
violations = []
for rule_name, pattern, violation_desc, correct_action in FORBIDDEN_PATTERNS:
matches = re.findall(pattern, last_thinking_text)
if matches:
violations.append({
'rule': rule_name,
'matches': matches[:3],
'violation_desc': violation_desc,
'correct_action': correct_action,
})
if not violations:
sys.exit(0)
# 위반 발견 → stderr로 강제 피드백 출력 후 exit 1 (도구 호출 허용, 피드백 Claude에게 전달)
lines_out = [
'=' * 60,
f'[THINKING VIOLATION] {tool_name} 도구 호출 전 thinking 규칙 위반 강제 피드백',
f'직전 thinking에서 {len(violations)}개 규칙 위반 감지 — 즉시 아래 지시를 따라야 한다',
'',
]
for v in violations:
lines_out.append(f'[{v["rule"]}]')
lines_out.append(f' 감지된 표현: {v["matches"]}')
lines_out.append(f' 위반 내용: {v["violation_desc"]}')
lines_out.append(f' 올바른 행동: {v["correct_action"]}')
lines_out.append('')
lines_out += [
'[지금 즉시 해야 할 행동]',
' 1. 위반된 thinking 패턴을 완전히 버린다',
' 2. 사용자의 명시적 지시 원문을 그대로 다시 읽는다',
' 3. 추측, 재해석, 축소, 대안 제안 없이 실제 파일 확인부터 시작한다',
' 4. Read/Grep 도구로 실제 파일을 확인한 후에만 판단한다',
'=' * 60,
]
print('\n'.join(lines_out), file=sys.stderr)
sys.exit(2)
PYEOF
소넷에서는 잘 작동하고 , 오푸스에서는 테스트 안해봤습니다. 오푸스는 추론내역을 막아두기도 했고, 가끔 하이쿠로도 바뀌는버그가 있는거같기도 하고 예전 모델로 응답하는 경우도 있고 좀 짜치는게 많아서 안쓰네요.
0
0
댓글을 남기려면 로그인이 필요합니다.
로그인 후 이 페이지로 돌아와 바로 댓글을 남길 수 있습니다.