Skip to content

Commit 78ddb04

Browse files
committed
Update policy_stop_hook.sh to support both v1 and v2 policy formats
The hook now: - Checks for v2 policies in .deepwork/policies/ first - Falls back to v1 policies in .deepwork.policy.yml if no v2 found - Passes JSON input directly to policy_check.py for v2 (via wrapper) - Maintains existing behavior for v1 evaluate_policies.py
1 parent 19a8310 commit 78ddb04

File tree

1 file changed

+45
-32
lines changed

1 file changed

+45
-32
lines changed

src/deepwork/standard_jobs/deepwork_policy/hooks/policy_stop_hook.sh

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,27 @@
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

1010
set -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
1627
fi
1728

@@ -21,36 +32,38 @@ if [ ! -t 0 ]; then
2132
HOOK_INPUT=$(cat)
2233
fi
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)
5669
echo "${result}"

0 commit comments

Comments
 (0)