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
+17 -17
View File
@@ -46,16 +46,16 @@ from pathlib import Path
import pytest
HOOKS = Path(__file__).resolve().parents[1] / "plugin" / "hooks"
SESSION_START = HOOKS / "scribe_session_context.sh"
DEFS = HOOKS / "scribe_defs.sh"
SESSION_START = HOOKS / "grimoire_session_context.sh"
DEFS = HOOKS / "grimoire_defs.sh"
# Every ledger the hooks write today, and where. Named here so a failure READS
# as "this one survived", but never used to build the hook's own delete list —
# the convention tests below are what keep this roster honest as it grows.
LEDGERS = {
"scribe-priorart": (".ids", ".rules.ids", ".opened.ids", ".sync.ids",
"grimoire-priorart": (".ids", ".rules.ids", ".opened.ids", ".sync.ids",
".derive.ids", ".shapes.ids", ".reportcheck.ids"),
"scribe-autoinject": (".ids",),
"grimoire-autoinject": (".ids",),
}
@@ -66,15 +66,15 @@ def _swept_dirs() -> set[str]:
agree with itself forever, which is precisely the failure that let the
auto-inject ledger sit in a directory nothing cleared.
"""
line = re.search(r'SCRIBE_LEDGER_DIRS="([^"]*)"', DEFS.read_text())
assert line, "SCRIBE_LEDGER_DIRS is gone; the clear has no roster"
line = re.search(r'GRIMOIRE_LEDGER_DIRS="([^"]*)"', DEFS.read_text())
assert line, "GRIMOIRE_LEDGER_DIRS is gone; the clear has no roster"
return set(line.group(1).split())
def _run_session_start(source: str, tmp: Path, extra: list[str] | None = None) -> Path:
"""Run the SessionStart hook for real, with the ledger directories filled.
`extra` names further files to plant in `scribe-priorart` — used to put
`extra` names further files to plant in `grimoire-priorart` — used to put
an evidence twin in front of the sweep.
"""
for tool in ("bash",):
@@ -87,9 +87,9 @@ def _run_session_start(source: str, tmp: Path, extra: list[str] | None = None) -
for suffix in suffixes:
(state / f"s1{suffix}").write_text("42\t1789600000\n")
# Not a ledger: an outage marker that must outlive the clear.
(tmp / "scribe-priorart" / "s1.unreached").write_text("1\n")
(tmp / "grimoire-priorart" / "s1.unreached").write_text("1\n")
for name in extra or ():
(tmp / "scribe-priorart" / name).write_text("42\t1789600000\n")
(tmp / "grimoire-priorart" / name).write_text("42\t1789600000\n")
env = {"PATH": os.environ["PATH"], "HOME": str(tmp), "TMPDIR": str(tmp)}
out = subprocess.run(
@@ -139,7 +139,7 @@ def test_the_outage_marker_is_not_swept_with_them(tmp_path):
the second with the first.
"""
root = _run_session_start("compact", tmp_path)
assert (root / "scribe-priorart" / "s1.unreached").exists()
assert (root / "grimoire-priorart" / "s1.unreached").exists()
# ── the two assumptions the sweep rests on ─────────────────────────────────
@@ -162,9 +162,9 @@ def _session_paths(text: str):
def test_every_directory_holding_a_ledger_is_one_the_clear_visits():
"""The failure that shipped once already, in the same step that fixed it.
The first cut swept `scribe-priorart` alone. Every ledger NAMED in the
hooks was there, so it read as complete — and `scribe_autoinject.sh` keeps
its note ledger in `scribe-autoinject`, which meant the arm that fires most
The first cut swept `grimoire-priorart` alone. Every ledger NAMED in the
hooks was there, so it read as complete — and `grimoire_autoinject.sh` keeps
its note ledger in `grimoire-autoinject`, which meant the arm that fires most
was the only one still carrying the bug. Nothing said so: the clear ran,
found nothing to remove, and exited 0.
"""
@@ -183,7 +183,7 @@ def test_every_directory_holding_a_ledger_is_one_the_clear_visits():
assert not offenders, (
"these ledgers live in directories the compaction clear never visits, "
f"so they outlive the context they describe: {offenders}. Add the "
"directory to SCRIBE_LEDGER_DIRS in scribe_defs.sh."
"directory to GRIMOIRE_LEDGER_DIRS in grimoire_defs.sh."
)
@@ -223,7 +223,7 @@ def test_the_clear_is_derived_and_not_a_list_of_names():
"""
sh = SESSION_START.read_text()
block = sh.split('case "$source" in')[1].split("esac")[0]
assert "scribe_clear_session_ledgers" in block
assert "grimoire_clear_session_ledgers" in block
for suffix in {s for suffixes in LEDGERS.values() for s in suffixes}:
assert suffix not in block, (
f"the clear names {suffix} again — a list, not a convention"
@@ -233,7 +233,7 @@ def test_the_clear_is_derived_and_not_a_list_of_names():
def test_the_evidence_twin_is_not_swept_with_them(tmp_path):
"""The second thing that survives, and for the `.unreached` reason.
`scribe_rules_append` writes each ledger twice: `<sid>.<kind>.ids`, which
`grimoire_rules_append` writes each ledger twice: `<sid>.<kind>.ids`, which
says what this context HOLDS and must be forgotten here, and
`<sid>.<kind>.keep.ids`, which says what HAPPENED and no compaction makes
untrue. The session-end readout is built on the second, and it runs AT the
@@ -249,7 +249,7 @@ def test_the_evidence_twin_is_not_swept_with_them(tmp_path):
either mistake.
"""
root = _run_session_start("compact", tmp_path, extra=["s1.rules.keep.ids"])
assert (root / "scribe-priorart" / "s1.rules.keep.ids").exists(), (
assert (root / "grimoire-priorart" / "s1.rules.keep.ids").exists(), (
"the evidence twin was swept with the exclusion ledgers — the readout "
"at this seam now reports a session it cannot see"
)