rename(547): the plugin is grimoire — manifest, MCP key, hooks, env, skills
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m2s
CI & Build / Python tests (push) Failing after 1m26s
CI & Build / Build & push image (push) Skipped
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m2s
CI & Build / Python tests (push) Failing after 1m26s
CI & Build / Build & push image (push) Skipped
Step 3 of the Scribe -> Grimoire rename. - Plugin `grimoire` in marketplace `grimoire-plugin`, MCP server key `grimoire`, so tools reach a session as mcp__plugin_grimoire_grimoire__*. - hooks/scribe_* -> hooks/grimoire_*, their shell functions and the TMPDIR ledgers with them; SCRIBE_URL / SCRIBE_TOKEN / SCRIBE_HOOK_DIR / SCRIBE_LEDGER_DIRS -> GRIMOIRE_*; the project marker is `.grimoire`. - skills/using-scribe -> using-grimoire, /grimoire:sync, stored processes arrive as grimoire-proc-<slug>; moment_actions names them the same way. - Migration 0124 rewrites stored skill names (rule_moments, rule_moment_judgments, moment_mappings) from scribe:/scribe-proc- to grimoire:/grimoire-proc-, so a rule mounted on a skill keeps firing. - Settings' install commands and MCP-only defaults, the docs and the tests follow. - scripts/check_plugin.py drops the smoke event for a report-check hook that has no file in plugin/hooks. Kept on purpose: the secret-sealing label, and the names that let backups written as Scribe restore and be counted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env bash
|
||||
# Grimoire plugin — Stop hook: the turn's new shapes are judged by the agent
|
||||
# that wrote them.
|
||||
#
|
||||
# The write hooks (grimoire_prior_art.sh, grimoire_after_write.sh) append every
|
||||
# definition a write names to a session ledger, `<sid>.written.ids`
|
||||
# (`path<TAB>kind<TAB>name`, see grimoire_written_append). At the end of the
|
||||
# turn this hook sends that ledger to the instance, which answers with what
|
||||
# nobody has judged yet. If anything is, the hook blocks ONCE, in the server's
|
||||
# words: the agent that built the code is the one participant who knows what
|
||||
# it is, and the end of the turn is the last moment that is still true.
|
||||
#
|
||||
# THE SAME DISCIPLINE AS the completion-section check in grimoire_reply_check.sh:
|
||||
# - it blocks only on a `reason` the instance returned, which it returns
|
||||
# only for a block it RECORDED — an unconfigured or unreachable instance
|
||||
# never stops a session, and every intervention is one the numbers see;
|
||||
# - never twice for one turn: the stop that follows its own block
|
||||
# (`stop_hook_active` with this hook's marker present) is recorded as
|
||||
# judged_after_block / left_after_block and let through;
|
||||
# - another plugin's block loop is left alone (active, no marker).
|
||||
#
|
||||
# THE LEDGER IS CONSUMED, NOT TURN-STAMPED. Everything written since the last
|
||||
# stop this hook answered is "this turn". It is emptied once the instance has
|
||||
# answered — after a pass, or after the post-block stop — and KEPT when the
|
||||
# instance could not be reached, so the question is asked at the next stop
|
||||
# that can be answered rather than silently dropped.
|
||||
#
|
||||
# Config (same as the other hooks):
|
||||
# CLAUDE_PLUGIN_OPTION_API_ENDPOINT base URL, no trailing slash
|
||||
# CLAUDE_PLUGIN_OPTION_API_TOKEN fmcp_ API key (sensitive)
|
||||
# GRIMOIRE_URL / GRIMOIRE_TOKEN override for the settings.json dogfooding path.
|
||||
set -uo pipefail
|
||||
|
||||
command -v curl >/dev/null 2>&1 || exit 0
|
||||
|
||||
# shellcheck source=plugin/hooks/grimoire_defs.sh
|
||||
. "$(dirname "${BASH_SOURCE[0]}")/grimoire_defs.sh"
|
||||
|
||||
# Stop delivers { session_id, transcript_path, cwd, hook_event_name, stop_hook_active }.
|
||||
event=$(cat 2>/dev/null || true)
|
||||
event_flat=$(printf '%s' "$event" | grimoire_json_flat)
|
||||
session_id=$(grimoire_json_pick "$event_flat" '.session_id')
|
||||
active=$(grimoire_json_pick "$event_flat" '.stop_hook_active')
|
||||
event_cwd=$(grimoire_json_pick "$event_flat" '.cwd')
|
||||
[ -n "$session_id" ] || exit 0
|
||||
|
||||
safe_sid=$(printf '%s' "$session_id" | tr -c 'A-Za-z0-9._-' '_')
|
||||
ledger="${TMPDIR:-/tmp}/grimoire-priorart/${safe_sid}.written.ids"
|
||||
state_dir="${TMPDIR:-/tmp}/grimoire-shapecheck"
|
||||
mkdir -p "$state_dir" 2>/dev/null || true
|
||||
marker="$state_dir/${safe_sid}.blocked"
|
||||
|
||||
if [ "$active" = "true" ]; then
|
||||
# A Stop hook already blocked this stop. Another plugin's → nothing to add.
|
||||
[ -f "$marker" ] || exit 0
|
||||
phase=after
|
||||
else
|
||||
rm -f "$marker" 2>/dev/null || true
|
||||
phase=check
|
||||
fi
|
||||
|
||||
if [ ! -s "$ledger" ]; then
|
||||
rm -f "$marker" 2>/dev/null || true
|
||||
exit 0
|
||||
fi
|
||||
grimoire_config || exit 0
|
||||
|
||||
# Unique lines, oldest first, capped: the request stays a GET (a read-scoped
|
||||
# key runs the plugin), and a turn that wrote more than this generated code.
|
||||
written=$(awk '!seen[$0]++' "$ledger" 2>/dev/null | head -n 80)
|
||||
written_enc=$(printf '%s' "$written" | grimoire_urlenc) || exit 0
|
||||
dir="${event_cwd:-${CLAUDE_PROJECT_DIR:-$PWD}}"
|
||||
q="phase=${phase}&written=${written_enc}"
|
||||
scope=$(grimoire_scope_query "$dir")
|
||||
[ -n "$scope" ] && q="${q}&${scope}"
|
||||
# The repo rides along even when a marker names the project: the instance
|
||||
# needs it to tell the agent which repo a just-written shape belongs to.
|
||||
case "$scope" in
|
||||
repo=*) ;;
|
||||
*)
|
||||
remote=$(git -C "$dir" remote get-url origin 2>/dev/null || true)
|
||||
if [ -n "$remote" ]; then
|
||||
remote_enc=$(printf '%s' "$remote" | grimoire_urlenc) || remote_enc=""
|
||||
[ -n "$remote_enc" ] && q="${q}&repo=${remote_enc}"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
answer=$(curl -fsS --max-time 4 \
|
||||
-H "Authorization: Bearer ${token}" \
|
||||
"${url%/}/api/plugin/shape-check?${q}" 2>/dev/null) || exit 0 # unreachable: keep the ledger
|
||||
|
||||
if [ "$phase" = "after" ]; then
|
||||
rm -f "$marker" "$ledger" 2>/dev/null || true
|
||||
exit 0
|
||||
fi
|
||||
|
||||
reason=$(grimoire_json_pick "$(printf '%s' "$answer" | grimoire_json_flat)" '.reason')
|
||||
if [ -z "$reason" ]; then
|
||||
rm -f "$ledger" 2>/dev/null || true
|
||||
exit 0
|
||||
fi
|
||||
: > "$marker" 2>/dev/null || true
|
||||
printf '{"decision":"block","reason":"%s"}\n' "$(printf '%s' "$reason" | grimoire_json_escape)"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user