Files
FabledScribe/plugin/PACKAGING.md
T
bvandeusenandClaude Opus 5.5 c6cdfc2172
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m1s
CI & Build / Python tests (push) Failing after 1m25s
CI & Build / Build & push image (push) Skipped
feat(moments): the reply moment holds a finished reply for one read (milestone 458 step 4b, #4922)
The reply is the one act no tool call marks, and it is where "let me
know if it works" gets said. A new Stop hook (scribe_reply_check.sh)
sends the finished reply to POST /api/plugin/reply-rules, which checks
it twice:

- mounted: every unopened RULE on reply.report, plus reply.ask when the
  reply asks a question. Deterministic.
- semantic: the reply's head and tail against every rule's trigger, on a
  new ranked surface, reply_rule. It is the backstop for whatever the
  earlier arms missed. Its floor is its stop bar (default 0.80, budget
  1), with its own Settings dials. The new stop_only stage records
  surfacing for the rule that holds and nothing else, because nothing
  else reached anyone.

Following the operator's ruling from 456 step 8, a rule that holds blocks
once, in the server's words. The hook blocks only on a reason it was
given, so an unreachable instance never stops a session, and it never
holds the rewrite. The ledger is the act checkpoint's own, so a rule
holds a session once across both doors and the per-session cap counts
both.

The turn reader moved from the report check into scribe_defs.sh
(scribe_turn_facts / scribe_turn_fact), so the two Stop hooks read a
turn the same way. The output was checked identical on a real transcript.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:30:07 -04:00

7.0 KiB

Packaging Scribe for an agent client

This is the maintainer contract for shipping Scribe into an agent client: what every client package shares, what each one adds, and where a new client's files go. It follows from decision #4027 (milestone 410): the server orients, the skills hold the depth, and a client adapter only times delivery and says what that one client needs said.

Claude Code is the only client built today. The layout below is kept so adding another one means adding files, not moving or rewriting any.

Shared by every client package

Piece Where What it is
The skills plugin/skills/*/SKILL.md Agent Skills (the open SKILL.md format). They state every Scribe reflex in full and name no client. tests/test_guidance_ownership.py fails if a skill names a particular client, or references anything outside its own folder. Every client package ships this folder verbatim.
The MCP server <base URL>/mcp HTTP, Authorization: Bearer <fmcp_ key>. Its _INSTRUCTIONS is a client-neutral orientation — the workflow across tools (≤1,600 chars, #4389); each tool's description carries its contract; in-band responses (placement, report_back, systems_hint, the duplicate gate, the guessed-id refusal) fire in every client.
The adapter API <base URL>/api/plugin/* Plain GET endpoints any client's hooks can call with the same key (read scope is enough): context (live session state), retrieve (rules, preferences and notes for a message), prior-art (records and shape-ledger hints for code being written), tool-rules (rules for a command about to run), report-check (records a completion-report check and returns the reason for a block), processes (stored Processes to expose as skills).
The API key Scribe → Settings → API Keys One fmcp_ key per install. Read scope for hooks; write scope for the MCP tools.

Added by each client

Piece What it holds Rule of thumb
A manifest Name, version, how to reach the MCP server with the key, where the skills and hooks are Only that client's format.
Hooks Scripts in that client's hook format that call the adapter API at the right moment and print what comes back Timing and transport only. A hook may print status lines (unconfigured, unreachable, running version) and pointers; guidance text comes from the server or the adapter's own static text.
Commands That client's command wrappers, e.g. a process sync Optional.
Adapter static text What only that client needs said: its local memory files, its compaction command, its commands Never a copy of a skill or of _INSTRUCTIONS. The loss guard and ownership registry in tests/test_guidance_ownership.py cover it.

The worked example: Claude Code

File Role
.claude-plugin/plugin.json Manifest: mcpServers.scribe (HTTP, Authorization: Bearer ${user_config.api_token}), userConfig for the base URL and key, version (minted, never hand-edited; see README).
../.claude-plugin/marketplace.json (repo root) The marketplace entry pointing at ./plugin.
hooks/hooks.json Wires the scripts to Claude Code events.
hooks/scribe_session_context.sh SessionStart: adapter static text + running version + GET /api/plugin/context; a reload banner after compaction.
hooks/scribe_autoinject.sh UserPromptSubmit: GET /api/plugin/retrieve for the message.
hooks/scribe_prior_art.sh PreToolUse on editor writes: GET /api/plugin/prior-art.
hooks/scribe_after_write.sh PostToolUse on shell commands: the same check for code written through the shell.
hooks/scribe_tool_rules.sh PreToolUse on shell commands: GET /api/plugin/tool-rules.
hooks/scribe_moment.sh PreToolUse on every tool: the rules mounted on the moments the call reaches, POST /api/plugin/moment; skips tools GET /api/plugin/moment-tools says reach nothing mounted, and Scribe's own (their responses carry moment_rules).
hooks/scribe_report_check.sh Stop: when the turn closed a task, checks the reply for the completion sections and reports to GET /api/plugin/report-check; blocks once, with the reason the server returns.
hooks/scribe_reply_check.sh Stop: sends the finished reply to POST /api/plugin/reply-rules — the rules mounted on the reply moments, and the reply against every rule's trigger; holds once per rule, with the reason the server returns, and never holds the rewrite.
hooks/scribe_shape_check.sh Stop: sends the definitions the turn wrote (the write hooks' <sid>.written.ids ledger) to GET /api/plugin/shape-check; blocks once, with the reason the server returns, so the agent judges what it built.
hooks/scribe_sync_processes.sh + commands/sync.md GET /api/plugin/processes → ~/.claude/skills/scribe-proc-* stubs; /scribe:sync on demand.
hooks/scribe_defs.sh Shared shell helpers: config, dedup ledgers, outage line.
hooks/scribe_static_context.md The adapter static text.

Hook config arrives as CLAUDE_PLUGIN_OPTION_API_ENDPOINT / CLAUDE_PLUGIN_OPTION_API_TOKEN (uppercased by Claude Code, #2198), with SCRIBE_URL / SCRIBE_TOKEN as an override.

How other clients would map

Researched, not tested (spike #4023, September 2026). Re-check each client's current docs before building.

  • Agent Plugins 1.0 (Codex, Cursor, GitHub Copilot / VS Code, Kiro, ChatGPT): a root plugin.json, a skills/ folder, and an mcp.json. That is the same plugin/skills/ plus two small files. Hooks, commands and rules are not part of v1; each client adds its own under a reverse-domain directory (e.g. com.<client>/) that other clients ignore.
  • Gemini CLI: gemini-extension.json configuring the MCP server, a context file, and bundled skills.
  • Client-native formats also exist (.codex-plugin/, .cursor-plugin/) where a client wants more than the shared standard carries.
  • Claude Code does not read Agent Plugins: only .claude-plugin/plugin.json.

Open questions for whoever builds the second package

  1. Passing the key to the MCP server. Claude Code substitutes ${user_config.api_token} into the header. Agent Plugins' mcp.json and Gemini's extension config have their own variable and secret handling. Decide per client, and keep the key out of files that get committed.
  2. Coexistence in one folder. Whether a root plugin.json (Agent Plugins) beside .claude-plugin/plugin.json changes how Claude Code loads the plugin is untested. Test it before shipping both from plugin/, or give the second client its own package directory that reuses plugin/skills/.
  3. Hook parity. Other clients' hook events differ and are partly implemented. Map each Scribe hook to the nearest event, and where none exists, leave that timing to the in-band server responses rather than writing a copy of the guidance.
  4. Process skills. The Claude Code sync writes ~/.claude/skills. Another client needs its own skills location, or the MCP skills extension (SEP-2640) once a client supports it.