feat(410): the Claude Code plugin becomes a thin adapter (#4031)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 11s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 56s
CI & Build / Python tests (push) Successful in 1m36s
CI & Build / Build & push image (push) Successful in 12s

Step 4 of milestone 410 "One owner per piece of guidance". The plugin's
static session context was a second copy of using-scribe and the server
index. It now says only what Claude Code needs said: 8,705 -> 1,791 chars.

scribe_static_context.md:
- points at using-scribe for how to work with Scribe, and names the process
  skills
- Claude Code specifics: keep one copy in Scribe rather than CLAUDE.md or
  auto-memory (leave auto-memory at its default); injected lines are
  retrieval, not the whole set; compact at clean seams; stored Processes
  arrive as scribe-proc-* skills with /scribe:sync; say so when the tools
  are unavailable
- retired: the restated reflexes, "how the surfaces divide the work", and
  the "follow the surface that assumes least" precedence (decision #4027)

Hook behaviour is unchanged. The SessionStart hook header says what the
static tier now carries, and the unreachable-instance status points at the
using-scribe skill instead of "the standing guidance above".

README and manifest describe the plugin as the Claude Code adapter over the
shared, client-neutral skills. Fixed along the way: the README said the
SessionStart hook "injects your rules" and suggested disabling auto-memory,
both contradicting the product since milestone 394.

Tests: the session-start rules guards now pin the owner (using-scribe) and
the index (_INSTRUCTIONS) rather than every surface; the Systems-reflex and
snippet-trigger guards pin their owners; the reporting-reflex guard pins
using-scribe. The loss guard and client-neutral guard stay green. Plugin
version minted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-14 12:45:33 -04:00
co-authored by Claude Opus 5
parent 76bfd92c21
commit 106e396b09
5 changed files with 89 additions and 167 deletions
+37 -30
View File
@@ -69,13 +69,19 @@ ABSENCE_CLAIMS = (
"not evidence there is none",
)
# Surfaces a session loads before substantive work. Hand-written because
# "is this a session-start surface?" is an editorial fact, not a derivable one —
# but each entry is asserted to EXIST, so a move or rename fails loudly here
# instead of quietly dropping that surface from the check.
# The surfaces that STATE the rules reflex. Hand-written because "who owns
# this?" is an editorial fact, not a derivable one — but each entry is asserted
# to EXIST, so a move or rename fails loudly here instead of quietly dropping
# that surface from the check.
#
# Since milestone 410 (decision #4027) that is the owner and the index, not
# every surface a session loads: `using-scribe` states the reflex in full and
# the server's `_INSTRUCTIONS` gives it one line for every MCP client. The
# Claude Code adapter's static context used to be a third copy; it now points
# at the skill instead, and tests/test_guidance_ownership.py keeps the topic
# from falling off.
SESSION_START_SURFACES = (
ROOT / "src" / "scribe" / "mcp" / "server.py",
ROOT / "plugin" / "hooks" / "scribe_static_context.md",
ROOT / "plugin" / "skills" / "using-scribe" / "SKILL.md",
)
@@ -174,40 +180,42 @@ def test_instructions_fit_the_fold():
)
def test_floor_states_the_systems_reflex():
"""Write-time tagging guidance must live on the surface that always arrives.
def test_the_systems_reflex_is_stated_by_its_owner():
"""Write-time tagging guidance must be stated as a reflex, not only per tool.
#2562's behavioral finding: with the guidance only in tool descriptions,
sessions filed records untagged. The static context is the delivery floor,
so the tag-as-you-write reflex has to be stated there.
sessions filed records untagged. The tag-as-you-write reflex was pinned on
the static context then; since milestone 410 its owner is using-scribe
(decision #4027), with the in-band `systems_hint` as the half that fires on
its own.
"""
floor = (ROOT / "plugin" / "hooks" / "scribe_static_context.md").read_text()
for needle in ("system_ids", "create_system"):
assert needle in floor, (
f"plugin/hooks/scribe_static_context.md no longer mentions "
f"{needle} — the Systems tagging reflex must be stated on the "
f"floor, not only in tool descriptions (#2562)."
owner = (ROOT / "plugin" / "skills" / "using-scribe" / "SKILL.md").read_text()
for needle in ("system_ids", "create_system", "systems_hint"):
assert needle in owner, (
f"plugin/skills/using-scribe/SKILL.md no longer mentions "
f"{needle} — the Systems tagging reflex must be stated by its "
f"owner, not only in tool descriptions (#2562)."
)
def test_floor_names_the_snippet_recording_triggers():
"""The floor must state the pattern-library recording model, by name.
def test_the_snippet_recording_triggers_are_stated_by_their_owner():
"""The pattern-library recording model must be stated, by name.
#2664's behavioral finding: recording guidance as a trailing clause of the
reuse bullet converted zero times outside snippet-minded sessions. The
2026-08-16 ruling (decision #2686) then replaced the reactive model
entirely: every shape is recorded at FIRST build — no "will it recur?"
judgment — and second-copy consolidation is only the backstop. The floor
is the delivery surface for that reflex, so all three elements must stay
stated: the tool, the first-build trigger, and the backstop.
judgment — and second-copy consolidation is only the backstop. All three
elements must stay stated: the tool, the first-build trigger, and the
backstop. Since milestone 410 the owner is the reusing-code skill.
"""
floor = (ROOT / "plugin" / "hooks" / "scribe_static_context.md").read_text()
owner = " ".join((ROOT / "plugin" / "skills" / "reusing-code" / "SKILL.md").read_text().split())
for needle in ("create_snippet", "first build", "second copy"):
assert needle in floor, (
f"plugin/hooks/scribe_static_context.md no longer states the "
assert needle in owner, (
f"plugin/skills/reusing-code/SKILL.md no longer states the "
f"snippet-recording model ({needle!r}) — record-every-shape-at-"
f"first-build with second-copy consolidation as the backstop must "
f"be stated on the floor (#2664, decision #2686)."
f"be stated by its owner (#2664, decision #2686)."
)
@@ -287,19 +295,18 @@ def test_a_surface_claiming_rules_bind_also_names_what_does_not():
# ── Reporting back (milestone 409 step 3) ──────────────────────────────
#
# The reporting-back skill carries the shapes, but a skill only helps if it
# fires. The reflex that sends a session to it lives on the two plugin
# surfaces a session always reads; the in-band cue on update_task is the half
# that reaches clients with no plugin at all. Since milestone 410 the server's
# index carries a REPORT line too, pointing at `placement`.
# fires. The reflex that sends a session to it is stated in using-scribe; the
# server's index carries a REPORT line pointing at `placement`, and the in-band
# cue on update_task is the half that fires on its own in every client. The
# static context was a second plugin-side copy until milestone 410.
REPORT_REFLEX = "report back in a shape the operator can read"
REPORT_SURFACES = (
ROOT / "plugin" / "hooks" / "scribe_static_context.md",
ROOT / "plugin" / "skills" / "using-scribe" / "SKILL.md",
)
def test_the_reporting_reflex_reaches_every_plugin_surface():
def test_the_reporting_reflex_is_stated_in_using_scribe():
missing = []
for path in REPORT_SURFACES:
text = " ".join(path.read_text().split()).lower()