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

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:
2026-10-22 10:52:55 -04:00
co-authored by Claude Opus 5.5
parent 0f5ef0be63
commit c5eeec471f
87 changed files with 1027 additions and 989 deletions
+13 -13
View File
@@ -22,7 +22,7 @@ while proving nothing) or the wording of the reason (prose, and it will be
rewritten). The cases below express scores relative to the constant.
THE ONE BOUNDARY THAT IS A STANDING DECISION, NOT A DESIGN CHOICE. Only the
ACTION arm can hold. `scribe_prior_art.sh` carries a tested property that it
ACTION arm can hold. `grimoire_prior_art.sh` carries a tested property that it
never returns a permissionDecision — a recall aid may not stand in the way of
a write — and a checkpoint does not get to quietly overturn that. The write-path arm computes the same block and returns
it, so the decision can be revisited with evidence; the hook ignores it. The
@@ -46,9 +46,9 @@ from tests.helpers import fake_rule
ROOT = Path(__file__).resolve().parents[1]
HOOKS = ROOT / "plugin" / "hooks"
DEFS = HOOKS / "scribe_defs.sh"
ACTION_HOOK = HOOKS / "scribe_tool_rules.sh"
WRITE_HOOK = HOOKS / "scribe_prior_art.sh"
DEFS = HOOKS / "grimoire_defs.sh"
ACTION_HOOK = HOOKS / "grimoire_tool_rules.sh"
WRITE_HOOK = HOOKS / "grimoire_prior_art.sh"
WHERE = "this Bash call"
TRIGGER = "about to assert in a commit message what CI said"
@@ -187,7 +187,7 @@ def ledger(tmp_path):
def allowed(ledger, rule_id) -> bool:
r = sh(f'scribe_checkpoint_allowed "{ledger}" "{rule_id}" && echo YES || echo NO')
r = sh(f'grimoire_checkpoint_allowed "{ledger}" "{rule_id}" && echo YES || echo NO')
assert r.returncode == 0, r.stderr
return r.stdout.strip().endswith("YES")
@@ -233,7 +233,7 @@ def test_no_ledger_file_refuses_rather_than_holding(tmp_path):
def test_the_deny_envelope_is_valid_json_carrying_the_reason():
r = sh('scribe_json_deny PreToolUse "read \\"rule 9\\" first — then re-submit"')
r = sh('grimoire_json_deny PreToolUse "read \\"rule 9\\" first — then re-submit"')
assert r.returncode == 0, r.stderr
out = json.loads(r.stdout)["hookSpecificOutput"]
assert out["hookEventName"] == "PreToolUse"
@@ -250,7 +250,7 @@ def test_only_the_action_hook_can_hold_an_act():
"""THE GUARD ON THE RECORDED DECISION, and the reason it is here rather
than left to memory.
`scribe_prior_art.sh` runs before every Write and Edit and has never been
`grimoire_prior_art.sh` runs before every Write and Edit and has never been
able to stop one. That is a standing decision — a recall aid may not stand
in the way of the work — and `test_hook_never_returns_a_permission_
decision` in test_write_path_trigger.py holds the other half of it.
@@ -261,12 +261,12 @@ def test_only_the_action_hook_can_hold_an_act():
A feature with a good reason to spread is the kind that spreads without
anyone deciding to, and the decision here is the operator's to revisit.
"""
assert "scribe_json_deny" in ACTION_HOOK.read_text()
assert "grimoire_json_deny" in ACTION_HOOK.read_text()
write_code = [
ln for ln in WRITE_HOOK.read_text().splitlines()
if not ln.lstrip().startswith("#")
]
assert not any("scribe_json_deny" in ln for ln in write_code)
assert not any("grimoire_json_deny" in ln for ln in write_code)
assert not any("permissionDecision" in ln for ln in write_code)
@@ -277,11 +277,11 @@ def test_the_action_hook_caps_every_hold_it_emits():
both guards exist to prevent."""
text = ACTION_HOOK.read_text()
code = [ln for ln in text.splitlines() if not ln.lstrip().startswith("#")]
denies = [i for i, ln in enumerate(code) if "scribe_json_deny" in ln]
denies = [i for i, ln in enumerate(code) if "grimoire_json_deny" in ln]
assert denies, "the action arm no longer emits a hold at all"
for i in denies:
window = "\n".join(code[max(0, i - 6):i])
assert "scribe_checkpoint_allowed" in window, (
assert "grimoire_checkpoint_allowed" in window, (
"a hold is emitted without passing the per-rule and per-session "
"guards first"
)
@@ -296,8 +296,8 @@ def test_the_action_hook_records_what_was_surfaced_whichever_way_it_renders():
ln for ln in ACTION_HOOK.read_text().splitlines()
if not ln.lstrip().startswith("#")
]
append = next(i for i, ln in enumerate(code) if "scribe_rules_append" in ln)
deny = next(i for i, ln in enumerate(code) if "scribe_json_deny" in ln)
append = next(i for i, ln in enumerate(code) if "grimoire_rules_append" in ln)
deny = next(i for i, ln in enumerate(code) if "grimoire_json_deny" in ln)
assert append < deny