@rulemetric/hooks 0.11.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulemetric/hooks",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -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 (claude_code enrichment was silently dead
127
- # without this with_meta dropped to ~0 from April on).
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"
@@ -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