22# policy_stop_hook.sh - Evaluates policies when the agent stops
33#
44# This script is called as a Claude Code Stop hook. It:
5- # 1. Evaluates policies from .deepwork. policy.yml
5+ # 1. Evaluates policies from .deepwork/policies/ (v2) or .deepwork. policy.yml (v1)
66# 2. Computes changed files based on each policy's compare_to setting
77# 3. Checks for <promise> tags in the conversation transcript
88# 4. Returns JSON to block stop if policies need attention
99
1010set -e
1111
12- # Check if policy file exists
13- if [ ! -f .deepwork.policy.yml ]; then
14- # No policies defined, nothing to do
12+ # Determine which policy system to use
13+ USE_V2=false
14+ V1_POLICY_FILE=" .deepwork.policy.yml"
15+ V2_POLICY_DIR=" .deepwork/policies"
16+
17+ if [ -d " ${V2_POLICY_DIR} " ]; then
18+ # Check if there are any .md files in the v2 directory
19+ if ls " ${V2_POLICY_DIR} " /* .md 1> /dev/null 2>&1 ; then
20+ USE_V2=true
21+ fi
22+ fi
23+
24+ # If no v2 policies and no v1 policy file, nothing to do
25+ if [ " ${USE_V2} " = false ] && [ ! -f " ${V1_POLICY_FILE} " ]; then
1526 exit 0
1627fi
1728
@@ -21,36 +32,38 @@ if [ ! -t 0 ]; then
2132 HOOK_INPUT=$( cat)
2233fi
2334
24- # Extract transcript_path from the hook input JSON using jq
25- # Claude Code passes: {"session_id": "...", "transcript_path": "...", ...}
26- TRANSCRIPT_PATH= " "
27- if [ -n " ${HOOK_INPUT} " ] ; then
28- TRANSCRIPT_PATH= $( echo " ${HOOK_INPUT} " | jq -r ' .transcript_path // empty ' 2> /dev/null || echo " " )
29- fi
35+ if [ " ${USE_V2} " = true ] ; then
36+ # Use v2 policy system via cross-platform wrapper
37+ # The wrapper reads JSON input and handles transcript extraction
38+ result= $( echo " ${HOOK_INPUT} " | DEEPWORK_HOOK_PLATFORM=claude DEEPWORK_HOOK_EVENT=Stop python -m deepwork.hooks.policy_check 2> /dev/null || echo ' {} ' )
39+ else
40+ # Use v1 policy system - extract conversation context for evaluate_policies
3041
31- # Extract conversation text from the JSONL transcript
32- # The transcript is JSONL format - each line is a JSON object
33- # We need to extract the text content from assistant messages
34- conversation_context=" "
35- if [ -n " ${TRANSCRIPT_PATH} " ] && [ -f " ${TRANSCRIPT_PATH} " ]; then
36- # Extract text content from all assistant messages in the transcript
37- # Each line is a JSON object; we extract .message.content[].text for assistant messages
38- conversation_context=$( cat " ${TRANSCRIPT_PATH} " | \
39- grep -E ' "role"\s*:\s*"assistant"' | \
40- jq -r ' .message.content // [] | map(select(.type == "text")) | map(.text) | join("\n")' 2> /dev/null | \
41- tr -d ' \0' || echo " " )
42- fi
42+ # Extract transcript_path from the hook input JSON using jq
43+ # Claude Code passes: {"session_id": "...", "transcript_path": "...", ...}
44+ TRANSCRIPT_PATH=" "
45+ if [ -n " ${HOOK_INPUT} " ]; then
46+ TRANSCRIPT_PATH=$( echo " ${HOOK_INPUT} " | jq -r ' .transcript_path // empty' 2> /dev/null || echo " " )
47+ fi
48+
49+ # Extract conversation text from the JSONL transcript
50+ # The transcript is JSONL format - each line is a JSON object
51+ # We need to extract the text content from assistant messages
52+ conversation_context=" "
53+ if [ -n " ${TRANSCRIPT_PATH} " ] && [ -f " ${TRANSCRIPT_PATH} " ]; then
54+ # Extract text content from all assistant messages in the transcript
55+ # Each line is a JSON object; we extract .message.content[].text for assistant messages
56+ conversation_context=$( cat " ${TRANSCRIPT_PATH} " | \
57+ grep -E ' "role"\s*:\s*"assistant"' | \
58+ jq -r ' .message.content // [] | map(select(.type == "text")) | map(.text) | join("\n")' 2> /dev/null | \
59+ tr -d ' \0' || echo " " )
60+ fi
4361
44- # Call the Python evaluator
45- # The Python module handles:
46- # - Parsing the policy file
47- # - Computing changed files based on each policy's compare_to setting
48- # - Matching changed files against triggers/safety patterns
49- # - Checking for promise tags in the conversation context
50- # - Generating appropriate JSON output
51- result=$( echo " ${conversation_context} " | python -m deepwork.hooks.evaluate_policies \
52- --policy-file .deepwork.policy.yml \
53- 2> /dev/null || echo ' {}' )
62+ # Call the Python v1 evaluator
63+ result=$( echo " ${conversation_context} " | python -m deepwork.hooks.evaluate_policies \
64+ --policy-file " ${V1_POLICY_FILE} " \
65+ 2> /dev/null || echo ' {}' )
66+ fi
5467
5568# Output the result (JSON for Claude Code hooks)
5669echo " ${result} "
0 commit comments