@rulemetric/hooks 0.2.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -160,7 +160,7 @@ _rulemetric_ensure_session() {
|
|
|
160
160
|
'{tool: $tool, externalSessionId: $externalSessionId, projectPath: $projectPath, gitBranch: $gitBranch, metadata: $metadata, orgId: $orgId, runId: $runId}')
|
|
161
161
|
|
|
162
162
|
local response
|
|
163
|
-
response=$(curl -sf -X POST "${RULEMETRIC_API_URL}/api/sessions" \
|
|
163
|
+
response=$(curl -sf --noproxy '*' --max-time 4 -X POST "${RULEMETRIC_API_URL}/api/sessions" \
|
|
164
164
|
-H "$AUTH_HEADER" \
|
|
165
165
|
-H "Content-Type: application/json" \
|
|
166
166
|
-d "$payload" \
|
|
@@ -16,7 +16,9 @@ trap 'echo "rulemetric assistant-response failed at line $LINENO: $BASH_COMMAND"
|
|
|
16
16
|
# Load config (env vars, ~/.config/rulemetric/env, or .env.local)
|
|
17
17
|
source "$SCRIPT_DIR/_config.sh" 2>/dev/null || true
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
# Bound the read so a hook runner that leaves stdin open cannot wedge the agent.
|
|
20
|
+
INPUT=""
|
|
21
|
+
IFS= read -r -d '' -t 2 INPUT || true
|
|
20
22
|
source "$SCRIPT_DIR/_normalize.sh"
|
|
21
23
|
|
|
22
24
|
if [ -z "$HOOK_SESSION_ID" ] || [ -z "${RULEMETRIC_API_URL:-}" ]; then
|
|
@@ -74,7 +76,15 @@ TOKEN_USAGE=$(jq -n \
|
|
|
74
76
|
# Increment per-session sequence (same locking pattern as post-tool-use)
|
|
75
77
|
SEQ_FILE="$TEMP_DIR/$HOOK_SESSION_ID.seq"
|
|
76
78
|
LOCK_DIR="$TEMP_DIR/$HOOK_SESSION_ID.seqlock"
|
|
77
|
-
|
|
79
|
+
SEQ_LOCK_TRIES=0
|
|
80
|
+
while ! mkdir "$LOCK_DIR" 2>/dev/null; do
|
|
81
|
+
sleep 0.01
|
|
82
|
+
SEQ_LOCK_TRIES=$((SEQ_LOCK_TRIES + 1))
|
|
83
|
+
if [ "$SEQ_LOCK_TRIES" -ge 500 ]; then
|
|
84
|
+
_rulemetric_log "assistant-response" "skip" "seq_lock_timeout"
|
|
85
|
+
exit 0
|
|
86
|
+
fi
|
|
87
|
+
done
|
|
78
88
|
SEQ=1
|
|
79
89
|
if [ -f "$SEQ_FILE" ]; then
|
|
80
90
|
SEQ=$(( $(cat "$SEQ_FILE") + 1 ))
|
|
@@ -115,7 +125,7 @@ PAYLOAD=$(jq -n \
|
|
|
115
125
|
_rulemetric_log "assistant-response" "success"
|
|
116
126
|
|
|
117
127
|
# Fire-and-forget (same error-logging pattern as post-tool-use)
|
|
118
|
-
(CURL_OUT=$(curl -s -w "\n%{http_code}" -X POST "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/events" \
|
|
128
|
+
(CURL_OUT=$(curl -s --noproxy '*' --max-time 4 -w "\n%{http_code}" -X POST "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/events" \
|
|
119
129
|
-H "$AUTH_HEADER" \
|
|
120
130
|
-H "Content-Type: application/json" \
|
|
121
131
|
-d "$PAYLOAD" 2>&1)
|
package/scripts/post-tool-use.sh
CHANGED
|
@@ -9,8 +9,10 @@ trap 'echo "rulemetric post-tool-use failed at line $LINENO: $BASH_COMMAND" >&2;
|
|
|
9
9
|
# Load config (env vars, ~/.config/rulemetric/env, or .env.local)
|
|
10
10
|
source "$SCRIPT_DIR/_config.sh" 2>/dev/null || true
|
|
11
11
|
|
|
12
|
-
# Read hook input from stdin and normalize across tools
|
|
13
|
-
|
|
12
|
+
# Read hook input from stdin and normalize across tools. Bound the read so a
|
|
13
|
+
# hook runner that leaves stdin open cannot wedge the agent.
|
|
14
|
+
INPUT=""
|
|
15
|
+
IFS= read -r -d '' -t 2 INPUT || true
|
|
14
16
|
source "$SCRIPT_DIR/_normalize.sh"
|
|
15
17
|
|
|
16
18
|
# Session paper-trail: snapshot the full working tree to a shadow git ref for
|
|
@@ -51,7 +53,15 @@ fi
|
|
|
51
53
|
# Increment sequence counter (atomic mkdir lock, works on macOS + Linux)
|
|
52
54
|
SEQ_FILE="$TEMP_DIR/$HOOK_SESSION_ID.seq"
|
|
53
55
|
LOCK_DIR="$TEMP_DIR/$HOOK_SESSION_ID.seqlock"
|
|
54
|
-
|
|
56
|
+
SEQ_LOCK_TRIES=0
|
|
57
|
+
while ! mkdir "$LOCK_DIR" 2>/dev/null; do
|
|
58
|
+
sleep 0.01
|
|
59
|
+
SEQ_LOCK_TRIES=$((SEQ_LOCK_TRIES + 1))
|
|
60
|
+
if [ "$SEQ_LOCK_TRIES" -ge 500 ]; then
|
|
61
|
+
_rulemetric_log "post-tool-use" "skip" "seq_lock_timeout"
|
|
62
|
+
exit 0
|
|
63
|
+
fi
|
|
64
|
+
done
|
|
55
65
|
SEQ=1
|
|
56
66
|
if [ -f "$SEQ_FILE" ]; then
|
|
57
67
|
SEQ=$(( $(cat "$SEQ_FILE") + 1 ))
|
|
@@ -71,7 +81,7 @@ PAYLOAD=$(jq -n \
|
|
|
71
81
|
_rulemetric_log "post-tool-use" "success"
|
|
72
82
|
|
|
73
83
|
# Fire-and-forget (capture HTTP errors for debugging)
|
|
74
|
-
(CURL_OUT=$(curl -s -w "\n%{http_code}" -X POST "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/events" \
|
|
84
|
+
(CURL_OUT=$(curl -s --noproxy '*' --max-time 4 -w "\n%{http_code}" -X POST "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/events" \
|
|
75
85
|
-H "$AUTH_HEADER" \
|
|
76
86
|
-H "Content-Type: application/json" \
|
|
77
87
|
-d "$PAYLOAD" 2>&1)
|
package/scripts/session-end.sh
CHANGED
|
@@ -9,8 +9,10 @@ trap 'echo "rulemetric session-end failed at line $LINENO: $BASH_COMMAND" >&2; _
|
|
|
9
9
|
# Load config (env vars, ~/.config/rulemetric/env, or .env.local)
|
|
10
10
|
source "$SCRIPT_DIR/_config.sh" 2>/dev/null || true
|
|
11
11
|
|
|
12
|
-
# Read hook input from stdin and normalize across tools
|
|
13
|
-
|
|
12
|
+
# Read hook input from stdin and normalize across tools. Bound the read so a
|
|
13
|
+
# hook runner that leaves stdin open cannot wedge the agent.
|
|
14
|
+
INPUT=""
|
|
15
|
+
IFS= read -r -d '' -t 2 INPUT || true
|
|
14
16
|
source "$SCRIPT_DIR/_normalize.sh"
|
|
15
17
|
|
|
16
18
|
if [ -z "$HOOK_SESSION_ID" ] || [ -z "${RULEMETRIC_API_URL:-}" ]; then
|
|
@@ -39,20 +41,20 @@ fi
|
|
|
39
41
|
# Try to reimport with full transcript, fall back to close
|
|
40
42
|
if [ -n "$HOOK_TRANSCRIPT_PATH" ] && [ -f "$HOOK_TRANSCRIPT_PATH" ]; then
|
|
41
43
|
CONTENT=$(cat "$HOOK_TRANSCRIPT_PATH")
|
|
42
|
-
curl -sf -X POST "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/reimport" \
|
|
44
|
+
curl -sf --noproxy '*' --max-time 8 -X POST "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/reimport" \
|
|
43
45
|
-H "$AUTH_HEADER" \
|
|
44
46
|
-H "Content-Type: application/json" \
|
|
45
47
|
-d "{\"content\":$(echo "$CONTENT" | jq -Rs .),\"tool\":\"$HOOK_TOOL\"}" \
|
|
46
48
|
> /dev/null 2>&1 || {
|
|
47
49
|
_rulemetric_log "session-end" "fallback" "reimport_failed"
|
|
48
|
-
curl -sf -X POST "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/close" \
|
|
50
|
+
curl -sf --noproxy '*' --max-time 4 -X POST "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/close" \
|
|
49
51
|
-H "$AUTH_HEADER" \
|
|
50
52
|
> /dev/null 2>&1 || \
|
|
51
53
|
_rulemetric_log "session-end" "error" "close_failed"
|
|
52
54
|
}
|
|
53
55
|
else
|
|
54
56
|
# No transcript available — just close the session
|
|
55
|
-
curl -sf -X POST "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/close" \
|
|
57
|
+
curl -sf --noproxy '*' --max-time 4 -X POST "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/close" \
|
|
56
58
|
-H "$AUTH_HEADER" \
|
|
57
59
|
> /dev/null 2>&1 || \
|
|
58
60
|
_rulemetric_log "session-end" "error" "close_failed"
|
|
@@ -60,9 +62,26 @@ fi
|
|
|
60
62
|
|
|
61
63
|
_rulemetric_log "session-end" "success"
|
|
62
64
|
|
|
65
|
+
# ── Emit a synthetic instruction snapshot (hooks-first, zero-proxy linking) ──
|
|
66
|
+
# The proxy classifies the rendered system prompt into context_items that
|
|
67
|
+
# link-instructions matches on. Hooks-only sessions have no proxy, so build an
|
|
68
|
+
# equivalent snapshot from on-disk CLAUDE.md files and POST it SYNCHRONOUSLY —
|
|
69
|
+
# it must land before the backgrounded link-instructions call below. Best-effort:
|
|
70
|
+
# a failure never breaks session-end (see the ERR trap guard). Claude Code only
|
|
71
|
+
# for now (the snapshot uses claude_md items).
|
|
72
|
+
if [ "$HOOK_TOOL" = "claude_code" ]; then
|
|
73
|
+
INSTR_SNAPSHOT=$(rulemetric hooks emit-instructions --project-dir "${HOOK_CWD:-$(pwd)}" 2>/dev/null || true)
|
|
74
|
+
if [ -n "$INSTR_SNAPSHOT" ]; then
|
|
75
|
+
curl -sf --noproxy '*' --max-time 5 -X POST "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/context-snapshots" \
|
|
76
|
+
-H "$AUTH_HEADER" \
|
|
77
|
+
-H "Content-Type: application/json" \
|
|
78
|
+
-d "$INSTR_SNAPSHOT" > /dev/null 2>&1 || _rulemetric_log "session-end" "warn" "instruction_snapshot_failed"
|
|
79
|
+
fi
|
|
80
|
+
fi
|
|
81
|
+
|
|
63
82
|
# ── Auto-link instructions from context snapshots ──
|
|
64
83
|
# Fire-and-forget: link any captured context to instruction records
|
|
65
|
-
(curl -sf -X POST "${RULEMETRIC_API_URL}/api/sessions/link-instructions" \
|
|
84
|
+
(curl -sf --noproxy '*' --max-time 8 -X POST "${RULEMETRIC_API_URL}/api/sessions/link-instructions" \
|
|
66
85
|
-H "$AUTH_HEADER" \
|
|
67
86
|
-H "Content-Type: application/json" \
|
|
68
87
|
-d '{}' > /dev/null 2>&1) &
|
|
@@ -108,7 +127,7 @@ except:
|
|
|
108
127
|
" 2>/dev/null)
|
|
109
128
|
|
|
110
129
|
if [ -n "$ENRICH_PAYLOAD" ]; then
|
|
111
|
-
(curl -sf -X PATCH "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/enrich" \
|
|
130
|
+
(curl -sf --noproxy '*' --max-time 4 -X PATCH "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/enrich" \
|
|
112
131
|
-H "$AUTH_HEADER" \
|
|
113
132
|
-H "Content-Type: application/json" \
|
|
114
133
|
-d "$ENRICH_PAYLOAD" > /dev/null 2>&1) &
|
|
@@ -116,7 +135,7 @@ except:
|
|
|
116
135
|
fi
|
|
117
136
|
else
|
|
118
137
|
# Non-Claude tools: derive enrichment from stored session events
|
|
119
|
-
(curl -sf -X POST "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/derive-enrichment" \
|
|
138
|
+
(curl -sf --noproxy '*' --max-time 8 -X POST "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/derive-enrichment" \
|
|
120
139
|
-H "$AUTH_HEADER" \
|
|
121
140
|
-H "Content-Type: application/json" \
|
|
122
141
|
-d '{}' > /dev/null 2>&1) &
|
|
@@ -133,11 +152,22 @@ fi
|
|
|
133
152
|
# ── Auto-generate recommendations (daily debounce) ──
|
|
134
153
|
# Queue a recommendation job if one hasn't been generated today.
|
|
135
154
|
# The worker picks it up and runs 7 Opus prompts (~$5-15 per run).
|
|
136
|
-
(curl -sf -X POST "${RULEMETRIC_API_URL}/api/insights-jobs" \
|
|
155
|
+
(curl -sf --noproxy '*' --max-time 4 -X POST "${RULEMETRIC_API_URL}/api/insights-jobs" \
|
|
137
156
|
-H "$AUTH_HEADER" \
|
|
138
157
|
-H "Content-Type: application/json" \
|
|
139
158
|
-d "{\"dedup\":\"daily\",\"projectPath\":\"${HOOK_CWD}\"}" > /dev/null 2>&1) &
|
|
140
159
|
|
|
141
160
|
# Clean up temp files
|
|
161
|
+
for registry_file in "$TEMP_DIR"/*; do
|
|
162
|
+
[ -f "$registry_file" ] || continue
|
|
163
|
+
case "$registry_file" in
|
|
164
|
+
*.log|*.seq|*.ctxhash|*.pid|*.lock) continue ;;
|
|
165
|
+
esac
|
|
166
|
+
if [ "$(sed -n '1p' "$registry_file" 2>/dev/null || true)" = "$RW_SESSION_ID" ]; then
|
|
167
|
+
rm -f "$registry_file" 2>/dev/null || true
|
|
168
|
+
rm -f "$registry_file.seq" 2>/dev/null || true
|
|
169
|
+
rm -f "$registry_file.ctxhash" 2>/dev/null || true
|
|
170
|
+
fi
|
|
171
|
+
done
|
|
142
172
|
rm -f "$TEMP_DIR/$HOOK_SESSION_ID" 2>/dev/null || true
|
|
143
173
|
rm -f "$TEMP_DIR/$HOOK_SESSION_ID.seq" 2>/dev/null || true
|
package/scripts/session-start.sh
CHANGED
|
@@ -10,8 +10,10 @@ trap 'echo "rulemetric session-start failed at line $LINENO: $BASH_COMMAND" >&2;
|
|
|
10
10
|
# Load config (env vars, ~/.config/rulemetric/env, or .env.local)
|
|
11
11
|
source "$SCRIPT_DIR/_config.sh" 2>/dev/null || true
|
|
12
12
|
|
|
13
|
-
# Read hook input from stdin and normalize across tools
|
|
14
|
-
|
|
13
|
+
# Read hook input from stdin and normalize across tools. Bound the read so a
|
|
14
|
+
# hook runner that leaves stdin open cannot wedge the agent.
|
|
15
|
+
INPUT=""
|
|
16
|
+
IFS= read -r -d '' -t 2 INPUT || true
|
|
15
17
|
source "$SCRIPT_DIR/_normalize.sh"
|
|
16
18
|
|
|
17
19
|
if [ -z "$HOOK_SESSION_ID" ]; then
|
|
@@ -38,7 +40,7 @@ _rulemetric_check_auth() {
|
|
|
38
40
|
|
|
39
41
|
# Quick auth check — GET /api/sessions?limit=1 with a 3s timeout
|
|
40
42
|
local http_code
|
|
41
|
-
http_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 \
|
|
43
|
+
http_code=$(curl -s --noproxy '*' -o /dev/null -w "%{http_code}" --max-time 3 \
|
|
42
44
|
"$api_url/api/sessions?limit=1" \
|
|
43
45
|
-H "Authorization: Bearer $auth_token" 2>/dev/null) || http_code="000"
|
|
44
46
|
|
|
@@ -120,7 +122,7 @@ _rulemetric_suggest_instructions() {
|
|
|
120
122
|
# suggestions arrive with the actual rule body. The body is rendered to
|
|
121
123
|
# stdout, which Claude Code surfaces to the model as session context.
|
|
122
124
|
# Low-confidence suggestions still render as name+reason only.
|
|
123
|
-
response="$(curl -s --max-time 4 \
|
|
125
|
+
response="$(curl -s --noproxy '*' --max-time 4 \
|
|
124
126
|
"${api_url}/api/instruction-suggestions?projectPath=${encoded_path}&limit=3&includeContent=true" \
|
|
125
127
|
-H "Authorization: Bearer ${auth_token}" 2>/dev/null)" || return 0
|
|
126
128
|
|
|
@@ -172,7 +174,7 @@ _rulemetric_suggest_instructions() {
|
|
|
172
174
|
else
|
|
173
175
|
event_type="surfaced"
|
|
174
176
|
fi
|
|
175
|
-
curl -s --max-time 3 -X POST \
|
|
177
|
+
curl -s --noproxy '*' --max-time 3 -X POST \
|
|
176
178
|
"${api_url}/api/instruction-suggestions/${sid}/events" \
|
|
177
179
|
-H "Authorization: Bearer ${auth_token}" \
|
|
178
180
|
-H "Content-Type: application/json" \
|
package/scripts/user-prompt.sh
CHANGED
|
@@ -9,8 +9,10 @@ trap 'echo "rulemetric user-prompt failed at line $LINENO: $BASH_COMMAND" >&2; _
|
|
|
9
9
|
# Load config (env vars, ~/.config/rulemetric/env, or .env.local)
|
|
10
10
|
source "$SCRIPT_DIR/_config.sh" 2>/dev/null || true
|
|
11
11
|
|
|
12
|
-
# Read hook input from stdin and normalize across tools
|
|
13
|
-
|
|
12
|
+
# Read hook input from stdin and normalize across tools. Bound the read so a
|
|
13
|
+
# hook runner that leaves stdin open cannot wedge the agent.
|
|
14
|
+
INPUT=""
|
|
15
|
+
IFS= read -r -d '' -t 2 INPUT || true
|
|
14
16
|
source "$SCRIPT_DIR/_normalize.sh"
|
|
15
17
|
|
|
16
18
|
if [ -z "$HOOK_SESSION_ID" ] || [ -z "${RULEMETRIC_API_URL:-}" ]; then
|
|
@@ -39,7 +41,15 @@ fi
|
|
|
39
41
|
# Increment sequence counter (atomic mkdir lock, works on macOS + Linux)
|
|
40
42
|
SEQ_FILE="$TEMP_DIR/$HOOK_SESSION_ID.seq"
|
|
41
43
|
LOCK_DIR="$TEMP_DIR/$HOOK_SESSION_ID.seqlock"
|
|
42
|
-
|
|
44
|
+
SEQ_LOCK_TRIES=0
|
|
45
|
+
while ! mkdir "$LOCK_DIR" 2>/dev/null; do
|
|
46
|
+
sleep 0.01
|
|
47
|
+
SEQ_LOCK_TRIES=$((SEQ_LOCK_TRIES + 1))
|
|
48
|
+
if [ "$SEQ_LOCK_TRIES" -ge 500 ]; then
|
|
49
|
+
_rulemetric_log "user-prompt" "skip" "seq_lock_timeout"
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
done
|
|
43
53
|
SEQ=1
|
|
44
54
|
if [ -f "$SEQ_FILE" ]; then
|
|
45
55
|
SEQ=$(( $(cat "$SEQ_FILE") + 1 ))
|
|
@@ -48,7 +58,7 @@ echo "$SEQ" > "$SEQ_FILE"
|
|
|
48
58
|
rmdir "$LOCK_DIR"
|
|
49
59
|
|
|
50
60
|
# Fire-and-forget: send user message event (capture HTTP errors for debugging)
|
|
51
|
-
(CURL_OUT=$(curl -s -w "\n%{http_code}" -X POST "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/events" \
|
|
61
|
+
(CURL_OUT=$(curl -s --noproxy '*' --max-time 4 -w "\n%{http_code}" -X POST "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/events" \
|
|
52
62
|
-H "$AUTH_HEADER" \
|
|
53
63
|
-H "Content-Type: application/json" \
|
|
54
64
|
-d "{\"sequence\":$SEQ,\"eventType\":\"user_message\",\"content\":$(printf '%s' "$HOOK_PROMPT" | jq -Rs .),\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)\"}" 2>&1)
|