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
+3 -3
View File
@@ -8,7 +8,7 @@ compares them against recorded snippet locations, so "record everything"
becomes a watched number instead of an aspiration.
The definition extractor MIRRORS the write-path hook's awk rules
(plugin/hooks/scribe_prior_art.sh, ARM 1) — one shared notion of "a
(plugin/hooks/grimoire_prior_art.sh, ARM 1) — one shared notion of "a
definition" between the hook and the server, so the metric and the backstop
agree on what counts. The two are pinned together by shared test vectors in
tests/test_pattern_coverage.py; change one, change both.
@@ -69,7 +69,7 @@ _SKIP_DIRS = frozenset({
_MAX_FILE_BYTES = 1_000_000
# --- the definition extractor (mirror of scribe_prior_art.sh ARM 1) ----------
# --- the definition extractor (mirror of grimoire_prior_art.sh ARM 1) ----------
_CSS_RE = re.compile(r"^\s*\.([A-Za-z][A-Za-z0-9_-]*)\s*[,{]")
# Leading declaration modifiers, so the definition keyword is the first word
@@ -105,7 +105,7 @@ _ARROW_RE = re.compile(
#
# The honest tool for .py would be `ast`, which cannot be fooled by prose at
# all. It is not what this uses, because this extractor is mirrored rule for
# rule by an awk program in plugin/hooks/scribe_defs.sh, awk cannot parse
# rule by an awk program in plugin/hooks/grimoire_defs.sh, awk cannot parse
# Python, and a fix only one of the pair can run is the drift the mirror
# exists to prevent. What both can do is blank the SPANS: a triple-quoted
# string or a /* … */ comment is replaced by its own newlines before any
+6 -6
View File
@@ -70,9 +70,9 @@ SKILL_TOOL, SKILL_FIELD = "skill", "skill"
# (`metadata: moments:`), and the declaration ships HERE as defaults, because
# the server never sees the plugin's files. `test_skill_moments` holds the two
# together. The plugin's name qualifies the skill, as the harness names it.
BUNDLED_SKILL_PLUGIN = "scribe"
BUNDLED_SKILL_PLUGIN = "grimoire"
BUNDLED_SKILL_MOMENTS: dict[str, tuple[str, ...]] = {
"using-scribe": ("session.start",),
"using-grimoire": ("session.start",),
"brainstorming": ("work.plan",),
"writing-plans": ("work.plan",),
"reusing-code": ("work.change",),
@@ -83,11 +83,11 @@ BUNDLED_SKILL_MOMENTS: dict[str, tuple[str, ...]] = {
"family-canon": ("work.record",),
}
# A stored process arrives as the skill `scribe-proc-<slug>`
# (scribe_sync_processes.sh). Its moments are the process's own field, read
# A stored process arrives as the skill `grimoire-proc-<slug>`
# (grimoire_sync_processes.sh). Its moments are the process's own field, read
# when it loads — not copied into the stub, which is written once a session
# and would go on firing a moment its process no longer declares.
PROCESS_SKILL_PREFIX = "scribe-proc-"
PROCESS_SKILL_PREFIX = "grimoire-proc-"
PROCESS_MOMENTS_FIELD = "moments"
PROCESS = "process"
@@ -353,7 +353,7 @@ async def moments_for(user_id: int, tool: str, tool_input: dict | None) -> list[
async def process_moments(user_id: int, skill: str) -> list[str]:
"""The moments the stored process behind `scribe-proc-<slug>` declares.
"""The moments the stored process behind `grimoire-proc-<slug>` declares.
The slug is resolved through the same manifest the sync script wrote the
skill from, so the two agree on which process a slug names. Fails open to
+3 -3
View File
@@ -508,8 +508,8 @@ def _slugify(text: str) -> str:
async def build_process_manifest(user_id: int) -> dict:
"""List the user's stored Processes as auto-surfacing skill-stub specs.
The plugin's sync script (scribe_sync_processes.sh) writes one
~/.claude/skills/scribe-proc-<slug>/SKILL.md per entry — `description` is the
The plugin's sync script (grimoire_sync_processes.sh) writes one
~/.claude/skills/grimoire-proc-<slug>/SKILL.md per entry — `description` is the
auto-surface trigger, and the stub body calls get_process(name) for the live
procedure (single source of truth in the DB). Reuses the list_processes query
(note_type='process'). Instance-agnostic: derived from whatever Processes the
@@ -2052,7 +2052,7 @@ async def build_session_context(
Args:
user_id: the operator.
project_id: the resolved active project (0 = none). The endpoint
resolves this from the working repo's remote, or from a `.scribe`
resolves this from the working repo's remote, or from a `.grimoire`
marker file naming the project directly — the only key a session
outside a git repo has. A non-zero id that does not
resolve is reported rather than silently dropped.
+1 -1
View File
@@ -223,7 +223,7 @@ POINTS: dict[str, Point] = dict([
# out would not fail here. What catches that is `assert_registered`, which the
# recorders call as the row is written.
# Keyed by the EXACT string the extractor produces, matched by equality rather
# than by prefix. The paths are relative to `src/` and so begin `scribe/`; a
# than by prefix. The paths are relative to `src/` and so begin `grimoire/`; a
# prefix spelled any other way silently matches nothing, and every site sails
# through a check that looks like it is running.
FAN_OUT_SITES: dict[str, tuple[str, ...]] = {
+1 -1
View File
@@ -953,7 +953,7 @@ async def snippet_consumers(user_id: int, note_id: int) -> dict:
# --- write-path stamping: hook evidence lands as rows -----------------------
#
# The write-path hook (plugin/hooks/scribe_prior_art.sh) fires on every
# The write-path hook (plugin/hooks/grimoire_prior_art.sh) fires on every
# Write/Edit and already carries the two halves of a consumer-map row: the
# file being written and the definitions in (or enclosing) the payload. What
# it could not say on its own is WHICH canon the session is instantiating.