feat(instructions): fit the delivery fold — 2k server map, floor Systems reflex, write-time systems_hint
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 7s
CI & Build / integration (push) Successful in 18s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 47s
CI & Build / Build & push image (push) Successful in 28s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 7s
CI & Build / integration (push) Successful in 18s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 47s
CI & Build / Build & push image (push) Successful in 28s
Claude Code injects only the first ~2,048 chars of an MCP server's instructions and silently cuts the rest mid-word (#2562, observed live): _INSTRUCTIONS was 20,002 chars, so ~90% — including all Systems tagging guidance — never reached any session. Rearchitect delivery around what each surface actually delivers: - _INSTRUCTIONS becomes a 1,997-char purpose-sorted map, with a header comment stating the budget and where detail belongs instead. - Tool docstrings keep the per-tool HOW (audit: nearly all displaced topics were already duplicated there); backfill the four gaps — enter_project session scoping + project bootstrap, create_rule entity-vs-rule test, create_design_system not-a-rulebook, create_system two-records test. - The plugin static context (the delivery floor) gains the tag-to-Systems reflex and a surfaces-layering statement; plugin 0.1.25 -> 0.1.26 so the executing cache refreshes (#2209). - create_task / create_note / create_snippet return a systems_hint when a record is created untagged in a project that has Systems — in-band at the exact write it applies to, fail-open like the dedup gate. - Guards: _INSTRUCTIONS length budget, floor-states-the-reflex, and a displaced-topics sweep asserting every cut topic still lives on a delivered surface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,12 @@ async def create_design_system(
|
||||
) -> dict:
|
||||
"""Create a design system, optionally inheriting from another.
|
||||
|
||||
This — not a rulebook — is where visual standards live: a token can be
|
||||
inherited, resolved per mode, rendered to a stylesheet and checked against
|
||||
code, and none of that survives being written as rule prose. Once a
|
||||
project points at one (set_project_design_system), treat it as binding for
|
||||
that project's UI work.
|
||||
|
||||
Args:
|
||||
title: What this system is — a house style, or one app within it
|
||||
(required).
|
||||
|
||||
@@ -14,6 +14,7 @@ Sentinel conventions (inherited from existing fable-mcp tools):
|
||||
from __future__ import annotations
|
||||
|
||||
from scribe.mcp._context import current_user_id
|
||||
from scribe.mcp.tools import systems as systems_tools
|
||||
from scribe.services import access as access_svc
|
||||
from scribe.services import dedup as dedup_svc
|
||||
from scribe.services import notes as notes_svc
|
||||
@@ -149,7 +150,10 @@ async def create_note(
|
||||
|
||||
Returns the created note object including its assigned id, OR — when a
|
||||
near-duplicate is found and force is false — {"duplicate": true,
|
||||
"existing_id": ..., "message": ...} and nothing is created.
|
||||
"existing_id": ..., "message": ...} and nothing is created. Created
|
||||
untagged in a project that has Systems, the response carries a
|
||||
`systems_hint` naming them — answer it: tag the record, create the missing
|
||||
System, or deliberately leave it untagged.
|
||||
"""
|
||||
uid = current_user_id()
|
||||
if not force:
|
||||
@@ -180,6 +184,10 @@ async def create_note(
|
||||
data["systems"] = [
|
||||
s.to_dict() for s in await systems_svc.list_record_systems(uid, note.id)
|
||||
]
|
||||
elif project_id:
|
||||
hint = await systems_tools.untagged_systems_hint(uid, project_id)
|
||||
if hint:
|
||||
data["systems_hint"] = hint
|
||||
await _attach_supersession(uid, note.id, data)
|
||||
return data
|
||||
|
||||
|
||||
@@ -70,6 +70,19 @@ async def enter_project(project_id: int) -> dict:
|
||||
departures from it) plus a summary of the token set — treat it as binding
|
||||
for any UI you write, and pull the values with resolve_design_system or
|
||||
get_design_system_stylesheet before reaching for a literal.
|
||||
|
||||
Entering a project also SCOPES the session: reference and offer work on
|
||||
this project only, and pass its id to search / list_* so results stay
|
||||
inside it. If something clearly belongs to a different project, say so and
|
||||
ask before switching — never silently operate cross-project. The active
|
||||
project does not stick on the server (each call is self-contained);
|
||||
carrying its id forward is on you.
|
||||
|
||||
Don't wait to be told which project is in scope. When work clearly belongs
|
||||
to a project but none is entered, look for a match yourself (list_projects
|
||||
/ search on the repo or subject), propose it, and enter it once the
|
||||
operator confirms; if nothing matches, offer to create one — confirming
|
||||
name and goal first, never guessing a project into existence.
|
||||
"""
|
||||
uid = current_user_id()
|
||||
project = await projects_svc.get_project(uid, project_id)
|
||||
|
||||
@@ -271,6 +271,14 @@ async def create_rule(
|
||||
rulebook+topic ceremony). If it's a standard a CATEGORY of projects shares,
|
||||
put it in a themed subscribed rulebook, not the always-on one.
|
||||
|
||||
Before writing a rule at all, check whether another entity already models
|
||||
the thing. A rule is prose an agent must remember and apply; the others
|
||||
are structure a tool can resolve, render and check. Visual standards are a
|
||||
DESIGN SYSTEM (tokens inherit, resolve per mode, render to a stylesheet —
|
||||
none of that survives being prose). A repeatable procedure is a PROCESS.
|
||||
Reusable code is a SNIPPET. Reach for a rule only when the thing genuinely
|
||||
is a standing instruction about how to work and nothing else can hold it.
|
||||
|
||||
Args:
|
||||
topic_id: The topic to attach the rule to.
|
||||
title: A short imperative title (e.g. "dev is home").
|
||||
|
||||
@@ -11,6 +11,7 @@ on create, System association passthrough).
|
||||
from __future__ import annotations
|
||||
|
||||
from scribe.mcp._context import current_user_id
|
||||
from scribe.mcp.tools import systems as systems_tools
|
||||
from scribe.services import access as access_svc
|
||||
from scribe.services import dedup as dedup_svc
|
||||
from scribe.services import snippets as snippets_svc
|
||||
@@ -132,7 +133,9 @@ async def create_snippet(
|
||||
and it really is the same reusable thing found in another place, prefer
|
||||
merge_snippets(existing_id, [new...]) — or record then merge — to unify them
|
||||
into ONE canonical record (which then carries every call site as a location),
|
||||
rather than forcing a second copy with force=true.
|
||||
rather than forcing a second copy with force=true. Created untagged in a
|
||||
project that has Systems, the response carries a `systems_hint` naming
|
||||
them — answer it: tag, create the missing System, or deliberately skip.
|
||||
|
||||
WHAT THE GATE MATCHES ON. Exact identity first — an existing snippet at the
|
||||
same repo · path · symbol, or holding byte-identical code. Those are certain,
|
||||
@@ -175,6 +178,10 @@ async def create_snippet(
|
||||
data["systems"] = [
|
||||
s.to_dict() for s in await systems_svc.list_record_systems(uid, note.id)
|
||||
]
|
||||
elif project_id:
|
||||
hint = await systems_tools.untagged_systems_hint(uid, project_id)
|
||||
if hint:
|
||||
data["systems_hint"] = hint
|
||||
return data
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,30 @@ from scribe.mcp._context import current_user_id
|
||||
from scribe.services import systems as systems_svc
|
||||
|
||||
|
||||
async def untagged_systems_hint(user_id: int, project_id: int) -> str | None:
|
||||
"""Nudge text for a record created untagged in a project that has Systems.
|
||||
|
||||
Not a tool. create_task / create_note / create_snippet attach this to
|
||||
their responses so the tagging question arrives in-band at the exact write
|
||||
it applies to — instruction prose alone demonstrably doesn't fire at write
|
||||
time, while in-band behavior (the duplicate gate) does (#2562).
|
||||
"""
|
||||
# Fail-open like the dedup gate: a hint must never break a create.
|
||||
try:
|
||||
systems = await systems_svc.list_systems(user_id, project_id)
|
||||
except Exception:
|
||||
return None
|
||||
if not systems:
|
||||
return None
|
||||
names = ", ".join(f"#{s.id} {s.name}" for s in systems)
|
||||
return (
|
||||
f"Created untagged. This project's Systems: {names}. If this record is "
|
||||
"about one of those areas, tag it (update it with system_ids=[...]); if "
|
||||
"its area is missing, create_system it and tag; if it is about no "
|
||||
"particular area, leave it untagged."
|
||||
)
|
||||
|
||||
|
||||
async def create_system(
|
||||
project_id: int,
|
||||
name: str,
|
||||
@@ -27,6 +51,13 @@ async def create_system(
|
||||
Associate records with it via the `system_ids` arg on create/update_task and
|
||||
create/update_note.
|
||||
|
||||
Create one the moment two records would share an area that has no System
|
||||
yet — the same two-or-more test snippets use. Don't wait to be asked to
|
||||
name an area that plainly exists in the code; an unmodelled area means
|
||||
every record about it stays untaggable. Give it a one-paragraph charter,
|
||||
not just a label: the description is what tells a later session whether a
|
||||
record belongs here.
|
||||
|
||||
Args:
|
||||
project_id: The project this system belongs to (required).
|
||||
name: Short label (required).
|
||||
|
||||
@@ -19,6 +19,7 @@ Sentinels (preserved from existing fable-mcp):
|
||||
from __future__ import annotations
|
||||
|
||||
from scribe.mcp._context import current_user_id
|
||||
from scribe.mcp.tools import systems as systems_tools
|
||||
from scribe.services import access as access_svc
|
||||
from scribe.services import dedup as dedup_svc
|
||||
from scribe.services import notes as notes_svc
|
||||
@@ -151,7 +152,9 @@ async def create_task(
|
||||
|
||||
Returns the created task, OR — when a near-duplicate is found and force is
|
||||
false — {"duplicate": true, "existing_id": ..., "message": ...} (nothing
|
||||
created).
|
||||
created). Created untagged in a project that has Systems, the response
|
||||
carries a `systems_hint` naming them — answer it: tag the record, create
|
||||
the missing System, or deliberately leave it untagged.
|
||||
"""
|
||||
uid = current_user_id()
|
||||
if kind == "plan":
|
||||
@@ -188,6 +191,10 @@ async def create_task(
|
||||
data["systems"] = [
|
||||
s.to_dict() for s in await systems_svc.list_record_systems(uid, note.id)
|
||||
]
|
||||
elif project_id:
|
||||
hint = await systems_tools.untagged_systems_hint(uid, project_id)
|
||||
if hint:
|
||||
data["systems_hint"] = hint
|
||||
return data
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user