@rulemetric/hooks 0.10.0 → 0.12.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 +1 -1
- package/scripts/session-end.sh +21 -2
- package/scripts/session-start.sh +58 -0
package/package.json
CHANGED
package/scripts/session-end.sh
CHANGED
|
@@ -121,10 +121,29 @@ fi
|
|
|
121
121
|
"$AUTH_HEADER" "{\"sessionId\":\"$RW_SESSION_ID\"}" "session-end-link" 8 > /dev/null 2>&1) &
|
|
122
122
|
|
|
123
123
|
# ── Auto-enrich from local session-meta ──
|
|
124
|
+
#
|
|
125
|
+
# 🚨 NOTHING BELOW THIS LINE CAN BE RELIED ON TO RUN. Claude Code cancels this
|
|
126
|
+
# hook shortly after it starts ("SessionEnd hook … failed: Hook cancelled",
|
|
127
|
+
# reproduced 2026-08-21 with `claude -p --debug`): the reimport above lands,
|
|
128
|
+
# and the script is killed before it reaches even the `success` log at line 93.
|
|
129
|
+
# Measured the same day: 0 `session-end` entries in $TMPDIR/rulemetric/hook.log
|
|
130
|
+
# across 24 session-starts, while 59 of the last 100 claude_code sessions had
|
|
131
|
+
# been reimported and 100 of 100 had ended_at.
|
|
132
|
+
#
|
|
133
|
+
# So enrichment is NO LONGER this block's responsibility. The API derives it
|
|
134
|
+
# server-side at the finalization points this hook does reach — the reimport
|
|
135
|
+
# handler (finalizeSessionIntelligence) and /close. See
|
|
136
|
+
# apps/api/src/lib/sessions/derive-enrichment.ts. What is left here is the
|
|
137
|
+
# PATCH /enrich path, which carries fields the server cannot derive (real
|
|
138
|
+
# interruption counts, per-message hour histograms, response times) and which
|
|
139
|
+
# the server's derivation refuses to overwrite. Treat it as a best-effort
|
|
140
|
+
# upgrade of already-correct data, never as the thing that makes the dashboard
|
|
141
|
+
# non-zero. Do not move new must-run work below here.
|
|
142
|
+
#
|
|
124
143
|
# For Claude Code: read local session-meta file when present; nothing writes
|
|
125
144
|
# that dir anymore, so when it's missing fall back to deriving enrichment
|
|
126
|
-
# from the reimported events (
|
|
127
|
-
#
|
|
145
|
+
# from the reimported events (redundant with the server-side derive, kept as
|
|
146
|
+
# the path for an API old enough not to have it).
|
|
128
147
|
# For other tools: derive enrichment from stored session events via API
|
|
129
148
|
if [ "$HOOK_TOOL" = "claude_code" ]; then
|
|
130
149
|
SESSION_META_FILE="$HOME/.claude/usage-data/session-meta/$HOOK_SESSION_ID.json"
|
package/scripts/session-start.sh
CHANGED
|
@@ -330,6 +330,60 @@ _rulemetric_suggest_instructions || true
|
|
|
330
330
|
# traffic), so the exposure is BUFFERED by this harness session id and reconciled at
|
|
331
331
|
# session close (reimport). All failures are silent — this block MUST NOT prevent
|
|
332
332
|
# session start. Same 4s read / 3s write timeouts as the suggestion block above.
|
|
333
|
+
|
|
334
|
+
# ── The review expectation ───────────────────────────────────────────────────
|
|
335
|
+
# Human labeling is the only step of the loop no machine can take: an LLM
|
|
336
|
+
# labeling anchors to calibrate an LLM judge is circular. Because every other
|
|
337
|
+
# step runs unattended, this is the step that silently stops happening — and on
|
|
338
|
+
# 2026-08-14..18 it did, while the evolution loop measured nightly and refused
|
|
339
|
+
# to decide because the runs it needed sat unreviewed.
|
|
340
|
+
#
|
|
341
|
+
# A `labels_needed` notification already fires and is correctly deduped. It was
|
|
342
|
+
# not dark, it was DROWNING: 20 of them against 522 usage alerts in one bell.
|
|
343
|
+
# So the ask is placed where it cannot be scrolled past — the top of the session
|
|
344
|
+
# the person is already starting.
|
|
345
|
+
#
|
|
346
|
+
# Prints NOTHING unless the loop is genuinely blocked (`blocking`), which the
|
|
347
|
+
# API decides by requiring an ENROLLED target that is both short of the pilot
|
|
348
|
+
# floor and has runs left to review. A nudge that fires when labeling would
|
|
349
|
+
# change no verdict is the nudge that teaches you to ignore nudges.
|
|
350
|
+
_rulemetric_review_gate() {
|
|
351
|
+
local api_url="${RULEMETRIC_API_URL:-}"
|
|
352
|
+
local auth_token="${RULEMETRIC_API_KEY:-${RULEMETRIC_ACCESS_TOKEN:-}}"
|
|
353
|
+
|
|
354
|
+
[ -n "$api_url" ] && [ -n "$auth_token" ] || return 0
|
|
355
|
+
command -v jq >/dev/null 2>&1 || return 0
|
|
356
|
+
|
|
357
|
+
local response
|
|
358
|
+
# Bounded like every other call here: a slow or absent API must cost a session
|
|
359
|
+
# start nothing it would not already cost.
|
|
360
|
+
response="$(curl -s --noproxy '*' --max-time 3 \
|
|
361
|
+
"${api_url}/api/loop/review-gate" \
|
|
362
|
+
-H "Authorization: Bearer ${auth_token}" 2>/dev/null)" || return 0
|
|
363
|
+
|
|
364
|
+
local blocking total
|
|
365
|
+
blocking="$(printf '%s' "$response" | jq -r '.blocking // false' 2>/dev/null)" || return 0
|
|
366
|
+
[ "$blocking" = "true" ] || return 0
|
|
367
|
+
total="$(printf '%s' "$response" | jq -r '.totalAwaitingReview // 0' 2>/dev/null)" || return 0
|
|
368
|
+
[ "$total" != "0" ] && [ -n "$total" ] || return 0
|
|
369
|
+
|
|
370
|
+
printf '\n---\n'
|
|
371
|
+
printf '[rulemetric] THE LOOP IS WAITING ON YOU — %s run(s) need human review.\n' "$total"
|
|
372
|
+
printf 'Nothing else can do this step, and until it is done the loop keeps measuring and keeps refusing to decide.\n'
|
|
373
|
+
|
|
374
|
+
# One line per target: what is unreviewed, how far the anchor set is from the
|
|
375
|
+
# pilot floor, and the cohort split — the gate is per cohort, so a total alone
|
|
376
|
+
# cannot say which arm is short.
|
|
377
|
+
printf '%s' "$response" | jq -r '
|
|
378
|
+
.targets[]? |
|
|
379
|
+
" - \(.name)\n" +
|
|
380
|
+
" \(.awaitingReview) unreviewed · \(.humanLabeled)/\(.pilotTarget) labels " +
|
|
381
|
+
"(\(.humanLabeledWith) with, \(.humanLabeledWithout) without) · " +
|
|
382
|
+
"\(.labelsToPilotFloor) more to reach the floor\n" +
|
|
383
|
+
" Review: \(env.RULEMETRIC_WEB_URL // "http://localhost:5174")/eval-targets/\(.id)?tab=review"
|
|
384
|
+
' 2>/dev/null || true
|
|
385
|
+
}
|
|
386
|
+
|
|
333
387
|
# Mirrors _rulemetric_suggest_instructions() deliberately.
|
|
334
388
|
_rulemetric_inject_memories() {
|
|
335
389
|
local api_url="${RULEMETRIC_API_URL:-}"
|
|
@@ -433,3 +487,7 @@ _rulemetric_inject_memories() {
|
|
|
433
487
|
}
|
|
434
488
|
|
|
435
489
|
_rulemetric_inject_memories || true
|
|
490
|
+
|
|
491
|
+
# Last, and therefore closest to the prompt: the one item on this block that is
|
|
492
|
+
# a request rather than context.
|
|
493
|
+
_rulemetric_review_gate || true
|