feat(plugin): the seam that erases the evidence is where the unresolved rules get named (#4216)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 55s
CI & Build / Python tests (push) Successful in 1m46s
CI & Build / Build & push image (push) Successful in 14s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 55s
CI & Build / Python tests (push) Successful in 1m46s
CI & Build / Build & push image (push) Successful in 14s
Step 5 of milestone 419. The milestone's subject is that a rule read and ignored is arithmetically identical to a rule read and followed, and the compaction is where that identity becomes permanent — the turns holding the evidence are summarised away, and the unjudged thing survives as nothing. WHY THIS IS ASSEMBLED IN THE HOOK. `rule_usage_events` has no session column; it is per user over a window. A session-scoped answer therefore cannot be asked of the server, and has to be built where a session is a thing that exists. Four ledgers four hooks already write: .rules.ids an arm NAMED the rule .opened.ids the session called get_rule (#4100) .acted.ids the session called rule_outcome (new here) .checkpoint.ids the rule HELD an act (#4214) Every one is an observed tool call. Nothing asks the model what it followed — milestone 386 ruled that out, because a model asked "did you apply rule 156?" says yes. Two subtractions: named-minus-opened is the arm talking to nobody, opened-minus-acted is the milestone's whole subject. PreCompact stdout is the compaction's custom instructions (#3680), not a message to the model, so the readout does not say "you slipped" — it says which ids must be carried through, which is the one thing a summary can do about an unjudged finding. SILENT WHEN NOTHING HAPPENED, and the accusations are conditional on having members. "0 rules unresolved" on every compaction is how a readout teaches its reader to skip it. Traffic is still reported, because the static instructions already ask for it in prose; these lines are the measured version. scribe_record_outcome.sh is the third ledger's writer, matched on mcp__.*__rule_outcome and mirroring scribe_record_opened.sh: TMPDIR only, silent, exit 0 on every path. A PostToolUse hook that spoke would put a line after every rule_outcome call and give recording an outcome a cost. Also: check_plugin.py skipped the new hook for want of a smoke event, which would have left the newest of the three ledgers as the only one the plugin lane never runs. Added, mirroring its sibling. tests/test_precompact_hook.py now isolates TMPDIR — the hook reads session ledgers from there, so without isolation a test would see whatever this real session had accumulated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
This commit is contained in:
@@ -46,11 +46,22 @@
|
||||
# text is the receipt. (Auto-compaction suppresses that notification.)
|
||||
set -uo pipefail
|
||||
|
||||
# Drain the event so the caller never sees a broken pipe. Nothing in it changes
|
||||
# what we emit: the instruction is the same whether the operator typed
|
||||
# `/compact` or the session hit its limit, and it composes with any custom
|
||||
# instructions they gave, which are merged ahead of ours.
|
||||
cat >/dev/null 2>&1 || true
|
||||
# shellcheck source=plugin/hooks/scribe_defs.sh
|
||||
. "$(dirname "${BASH_SOURCE[0]}")/scribe_defs.sh"
|
||||
|
||||
# THE EVENT IS NOW READ, where it used to be drained. The old comment here said
|
||||
# nothing in it changes what we emit, and that was true while every line was
|
||||
# static. It stopped being true at #4216: the slippage readout below is
|
||||
# specific to THIS session, and `session_id` is the key to the ledgers that
|
||||
# hold it. The instruction is still the same whether the operator typed
|
||||
# `/compact` or the session hit its limit — what varies is the measurement
|
||||
# appended to it.
|
||||
event=$(cat 2>/dev/null || true)
|
||||
session_id=""
|
||||
if [ -n "$event" ]; then
|
||||
event_flat=$(printf '%s' "$event" | scribe_json_flat 2>/dev/null || true)
|
||||
session_id=$(scribe_json_pick "$event_flat" '.session_id' 2>/dev/null || true)
|
||||
fi
|
||||
|
||||
cat <<'EOF'
|
||||
Preserve the following literally in the summary — copied through, not
|
||||
@@ -71,4 +82,29 @@ paraphrased or counted:
|
||||
Everything else here can be recovered from the repository or from Scribe. These
|
||||
cannot: they are this session's only copy.
|
||||
EOF
|
||||
|
||||
# ── The slippage readout (#4216, milestone 419) ───────────────────────────
|
||||
#
|
||||
# WHY IT BELONGS HERE RATHER THAN IN A REPLY. A rule read and left unresolved
|
||||
# is invisible by construction: it looks exactly like a rule that worked. The
|
||||
# compaction is where that invisibility becomes permanent — the turns holding
|
||||
# the evidence are summarised away, and an unjudged thing that survives as
|
||||
# nothing is how a decision quietly becomes nobody's.
|
||||
#
|
||||
# STDOUT HERE IS THE SUMMARISER'S INSTRUCTIONS, not a message to the model
|
||||
# (the header records how that was established). So this does not say "you
|
||||
# slipped"; it says WHICH ids must be carried through, which is the one thing
|
||||
# the summary can do about it. The next turn then reads them in the summary
|
||||
# with the work still attached.
|
||||
#
|
||||
# SILENT WHEN THERE IS NOTHING TO SAY. A session that opened everything it was
|
||||
# shown gets no extra lines. "0 rules unresolved" on every compaction is how a
|
||||
# readout teaches its reader to skip it.
|
||||
if [ -n "$session_id" ]; then
|
||||
safe_sid=$(printf '%s' "$session_id" | tr -c 'A-Za-z0-9._-' '_')
|
||||
slippage=$(scribe_slippage_lines "${TMPDIR:-/tmp}/scribe-priorart" "$safe_sid" 2>/dev/null || true)
|
||||
if [ -n "$slippage" ]; then
|
||||
printf '\n%s\n' "$slippage"
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user