Files
FabledGrimoire/plugin/README.md
T
bvandeusenandClaude Opus 5.5 c5eeec471f
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
rename(547): the plugin is grimoire — manifest, MCP key, hooks, env, skills
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>
2026-10-22 10:52:55 -04:00

145 lines
8.7 KiB
Markdown

# Grimoire plugin for Claude Code
Turns a self-hosted [Grimoire](https://git.fabledsword.com/bvandeusen/FabledScribe)
instance into a first-class Claude Code extension:
- **MCP tools** over your notes, tasks, projects, milestones, systems, and
rulebook (the `grimoire` server).
- **Session-start push channel** — a `SessionStart` hook injects the
active project's live state (from the server) and this adapter's short
Claude Code guidance, so Grimoire surfaces *without being asked*. Rules are
never preloaded; they arrive by retrieval when your work matches one.
- **Prior-art recall on writes** — a `PreToolUse` hook on Write/Edit checks the
file about to be written against your recorded snippets (what's kept at that
path, and what resembles the code) and offers them before the helper is
rewritten. Titles only, never blocks the edit.
- **The shared Grimoire skills** — client-neutral Agent Skills, the same files
any client's package would ship: using-grimoire, writing-plans, reporting-back
(reply to the operator in a shape that says where the work stands),
systematic-debugging, verification, brainstorming, reusing-code (record and
recall reusable code as snippets), shape-accounting and family-canon.
Replaces superpowers.
- **Your Grimoire Processes as skills** — saved Processes are synced into local
`~/.claude/skills/grimoire-proc-*` stubs that auto-surface by relevance; the
stub fetches the live procedure via `get_process`. Refreshed each session and
on demand with `/grimoire:sync`.
It is designed so you can uninstall `superpowers` and depend on Grimoire instead
of auto-memory — leave auto-memory at its default; Grimoire replaces its job by
holding the one copy, not by switching it off.
**How the pieces divide the work**: the Grimoire server orients
every MCP client and serves live state; the skills in `skills/` state every
reflex in full and name no client; this plugin is the Claude Code adapter —
hooks that deliver at the right moment, `/grimoire:sync`, and the few things only
Claude Code needs said (`hooks/grimoire_static_context.md`). Packaging Grimoire for
another client: see [PACKAGING.md](PACKAGING.md).
## Install
The plugin ships inside the Grimoire app repo, so the marketplace *is* that repo —
you always get the plugin version that matches your Grimoire instance.
```
/plugin marketplace add https://git.fabledsword.com/bvandeusen/FabledScribe.git
/plugin install grimoire@grimoire-plugin
```
On install you'll be asked for:
| Setting | What |
|---|---|
| **Grimoire base URL** | e.g. `https://grimoire.example.com` (no trailing slash) |
| **Grimoire API key** | a write-scoped `fmcp_` key from **Settings → MCP Access** (stored in your OS keychain) |
| **Active project id** | optional — numeric project id to scope the session-start context |
## What gets wired
- `plugin.json` `mcpServers` → the `grimoire` MCP server at `<base URL>/mcp` (Bearer auth).
- `hooks/hooks.json` → SessionStart hook (`hooks/grimoire_session_context.sh`),
**fail-open**: if Grimoire is unreachable it injects nothing and never blocks
the session.
- `hooks/hooks.json` → PreToolUse hook on `Write|Edit`
(`hooks/grimoire_prior_art.sh`) → `GET /api/plugin/prior-art`. Returns
`additionalContext` with **no** permission decision, so it can inform the write
but never stop it; silent when nothing is recorded, which is most of the time.
Two framings: a REUSE menu (similar/nearby records), and a SYNC nudge when a
snippet records the exact file being edited — "updating the record is part of
the edit" — each with its own once-per-session dedup. A third, ledger-fed
line names a duplicate family (no canon) or a canon recorded elsewhere for
the names being written (its own dedup channel, `exclude_derive`).
Fail-open but not fail-silent: a configured instance that does not answer
in time is said, once per outage ("Grimoire did not answer … this write went
UNCHECKED"), so a session can tell "checked, nothing there" from "never
checked"; an answer clears the marker. The local by-name arm needs no
server and always runs. Toggle in **Settings → General → Knowledge
auto-inject**.
- `hooks/hooks.json` → UserPromptSubmit hook (`hooks/grimoire_autoinject.sh`) →
`GET /api/plugin/retrieve`: the rules, preferences and records that match the
message, as a short menu of titles and matched passages.
- `hooks/hooks.json` → PreToolUse hook on `Bash` (`hooks/grimoire_tool_rules.sh`)
→ `GET /api/plugin/tool-rules`: the rules that match the command about to run.
- `hooks/hooks.json` → PreToolUse hook on every tool (`hooks/grimoire_moment.sh`)
→ `POST /api/plugin/moment`: the rules mounted on the moments the call
reaches. It skips tools that `GET /api/plugin/moment-tools` says reach
nothing mounted, and Grimoire's own tools, whose responses carry
`moment_rules`.
- `hooks/hooks.json` → PostToolUse hooks on Grimoire's `get_rule`,
`rule_outcome`, `update_task` and `add_task_log`: they keep the session's
ledgers of rules opened and acted on (`get_rule` also reports to
`POST /api/plugin/rule-opened`), and claim the task being worked
(`GET /api/plugin/claim-session`) so it comes back after a compaction.
- `hooks/hooks.json` → PostToolUse hook on `Bash`
(`hooks/grimoire_after_write.sh`): code written through sed/heredocs/scripts
never reaches the PreToolUse hook, so this one diffs the working tree after
every Bash call (per-session path+blob snapshot; one `git status` when
nothing changed) and runs the same arms on the definitions just written,
through the same endpoint and the same dedup channels. `additionalContext`
only; never blocks, and shares the pre-write hook's once-per-outage "did not
answer" line (8 s budget here — it runs after the tool, so it gates
nothing). The extractor, the prose/data skip list, the local by-name
duplicate arm and the outage line are shared in `hooks/grimoire_defs.sh`.
- `hooks/hooks.json` → Stop hook (`hooks/grimoire_reply_check.sh`): sends the
reply that ends the turn to `POST /api/plugin/reply-rules`, which holds it
once for an unopened rule mounted on the reply moments or matching the reply.
When the turn closed a Grimoire task (`update_task`/`create_task` with status
done), the same request has the server check the reply for the completion
sections — where the work sits, what needs you, what comes next. If they are
missing it blocks once with the reason the server returns, and records how
the rewrite came out; it never blocks twice, and never blocks when the
instance did not record the check (unconfigured or unreachable). Outcomes
land in the admin logs under category `plugin`, action `report_check`.
- `hooks/hooks.json` → a second Stop hook (`hooks/grimoire_shape_check.sh`): the
write hooks note every definition a write names (and every new file) in a
session ledger; at the end of the turn this sends them to
`GET /api/plugin/shape-check`, which answers with the ones nobody has judged.
If any are, it blocks once with the server's reason, asking the agent that
wrote them to say what each is — reusable (record a snippet), an instance or
variant of one, or a one-off — in one `classify_shapes` call. Same discipline
as the report check: never twice, never without a recorded check. Outcomes
land under category `plugin`, action `shape_check`.
- `skills/` → the universal process-skills, surfaced by description match.
- `hooks/grimoire_sync_processes.sh` (a 2nd SessionStart hook) + the `/grimoire:sync`
command → generate `~/.claude/skills/grimoire-proc-*` stubs from your Grimoire
Processes (via `GET /api/plugin/processes`); also **fail-open**, and pruned to
match what exists in Grimoire.
- `hooks/hooks.json` → PreCompact hook (`hooks/grimoire_precompact_preserve.sh`):
tells the summarizer which parts of the conversation to keep literally. Local
only, and it never blocks, because a blocked compaction is skipped outright.
- `hooks/hooks.json` → SessionEnd hook (`hooks/grimoire_session_end.sh`) →
`GET /api/plugin/release-session`: releases the session's task claims.
## Notes
- **Do not hand-edit `version` in `.claude-plugin/plugin.json`.** It is minted
from the clock — run `python3 scripts/mint_plugin_version.py` (or `make
mint-plugin`, where `make` is installed) after changing anything under
`plugin/`, and commit the result. The installer decides whether to refresh
the cache it executes from by comparing that string, so content that ships
without a new version reaches the repo and stops there. CI fails the lane if
you forget.
- The plugin uses one key for its MCP tools and its hooks, so give it **write**
scope: the MCP tools create and update records, and the moment, rule-opened
and reply hooks POST. A read-scoped key runs the GET hooks only; the others
fail open and add nothing.