docs(mcp): encode rule-scope model in rulebook tool descriptions
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 48s
CI & Build / Build & push image (push) Successful in 59s

Make the always-on / subscribed / project-rule distinction explicit at the
authoring surface so it can't silently regress (for this operator or other
users). Previously the tools said only 'cross-project rulebook rule' and a
bare 'subscribe a project' — nothing steered project-specific detail away
from shared rulebooks, which is how a Scribe-pinned rule ends up binding
every family project.

Principle encoded in 5 places: a rule's home is chosen by WHO it should bind,
and both rulebook tiers are SHARED so their rules stay general — they differ
in reach (all projects vs opt-in by theme), not generality. Project-specific
detail goes in create_project_rule.

- server.py MCP instructions: add the 3-tier authoring principle
- create_rule / create_rulebook / create_project_rule / subscribe_* docstrings
- using-scribe SKILL.md: a 'Where a new rule goes' note for the pull path

Refs #755

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-10 10:15:01 -04:00
parent 2a5f5fdbe1
commit 50b6902fe2
3 changed files with 73 additions and 11 deletions
+19
View File
@@ -48,6 +48,25 @@ explicit pull. Rules loaded this way are **binding** for the session.
5. **Keep state honest.** Set a task `in_progress` when you start it, `done` the 5. **Keep state honest.** Set a task `in_progress` when you start it, `done` the
moment it's complete; log progress as you go. moment it's complete; log progress as you go.
## Where a new rule goes
When codifying a rule, pick its home by **who it should bind** — and keep
shared homes general:
- **Always-on rulebook** (`create_rule` in an `always_on` rulebook) — universal
norms that bind *every* project. Cross-project standards only.
- **Subscribed rulebook** (`create_rule` + `subscribe_project_to_rulebook`) — a
reusable, *themed* module of general rules that binds only projects that opt
in (e.g. a design system → visual apps). Themed, but still project-agnostic.
- **Project rule** (`create_project_rule`) — anything specific to one project
(its files, paths, quirks).
Both rulebook tiers are shared, so their rules stay general; they differ in
**reach** (all vs opt-in), not generality. Names one project's specifics →
project rule; a standard a category shares → subscribed rulebook; a universal
norm → always-on rulebook. Never put project-specific detail in a shared
rulebook — it leaks to every other project that gets it.
## Other Scribe process-skills ## Other Scribe process-skills
This plugin also ships focused process-skills — brainstorming, systematic This plugin also ships focused process-skills — brainstorming, systematic
+18 -2
View File
@@ -82,8 +82,24 @@ new engineering rules to CLAUDE.md or to ~/.claude/.../memory/feedback_*.md
— those stores are reserved for facts about the user (preferences, role, — those stores are reserved for facts about the user (preferences, role,
communication style) and codebase onboarding pointers, respectively. Before communication style) and codebase onboarding pointers, respectively. Before
creating a rule, call list_always_on_rules and list_rules(project_id=...) to creating a rule, call list_always_on_rules and list_rules(project_id=...) to
avoid duplicates. Coordinate with the operator on whether a new rule belongs avoid duplicates.
in a project, an existing rulebook+topic, or a new rulebook.
Choose a rule's home by WHO it should bind, and keep each home's rules at the
right altitude:
- Always-on rulebook (a rulebook flagged always_on) — universal norms that
bind EVERY one of your projects. Reserve for cross-project standards.
- Subscribed rulebook (always_on off; projects opt in via
subscribe_project_to_rulebook) — a reusable, THEMED module of general
rules that binds only the projects which subscribe. Its rules must make
sense for every project that could subscribe, never one specific project
(e.g. a design-system rulebook: design-specific but project-agnostic — no
rule names a single app).
- Project rule (create_project_rule) — anything specific to ONE project.
Both rulebook tiers are SHARED, so their rules stay general; the difference
between them is REACH (all projects vs opt-in by theme), not generality. Rule
of thumb: names a specific project's files/paths/quirks -> project rule; a
standard a CATEGORY of projects shares -> subscribed rulebook; a universal
norm -> always-on rulebook. Coordinate with the operator on which home fits.
That boundary cuts the other way too. Because rules are pull-only, a fresh That boundary cuts the other way too. Because rules are pull-only, a fresh
session won't reach for them unless its always-loaded context says to. So session won't reach for them unless its always-loaded context says to. So
+36 -9
View File
@@ -39,7 +39,18 @@ async def get_rulebook(rulebook_id: int) -> dict:
async def create_rulebook(title: str, description: str = "") -> dict: async def create_rulebook(title: str, description: str = "") -> dict:
"""Create a new rulebook. """Create a new rulebook (a shared, reusable module of general rules).
Two ways a rulebook reaches projects, set by its always_on flag (toggle via
update_rulebook):
- always_on = true -> binds EVERY one of your projects automatically.
Use for universal cross-project norms (e.g. "FabledSword family").
- always_on = false -> binds only projects that subscribe
(subscribe_project_to_rulebook). Use for a THEMED body of rules a
category of projects shares (e.g. a design system that visual apps
opt into).
Either way a rulebook is SHARED, so its rules must stay general — agnostic
to any single project. Project-specific rules go in create_project_rule.
Args: Args:
title: Rulebook name (e.g. "FabledSword family"). title: Rulebook name (e.g. "FabledSword family").
@@ -247,7 +258,15 @@ async def create_rule(
topic_id: int, title: str, statement: str, topic_id: int, title: str, statement: str,
why: str = "", how_to_apply: str = "", order_index: int = 0, why: str = "", how_to_apply: str = "", order_index: int = 0,
) -> dict: ) -> dict:
"""Create a new rule under a topic (cross-project rulebook rule). """Create a new rule in a rulebook (a SHARED rule — keep it general).
A rulebook rule is shared by every project that gets the rulebook: an
always_on rulebook binds ALL your projects; a subscribed rulebook binds the
projects that opt in. So a rulebook rule must read as a general standard —
never pin it to one project's files, paths, or quirks. For a rule that
applies to a single project only, use create_project_rule instead (no
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.
Args: Args:
topic_id: The topic to attach the rule to. topic_id: The topic to attach the rule to.
@@ -256,9 +275,6 @@ async def create_rule(
why: Optional rationale — the reason the rule exists. why: Optional rationale — the reason the rule exists.
how_to_apply: Optional operationalization — when / where it kicks in. how_to_apply: Optional operationalization — when / where it kicks in.
order_index: Display order within the topic (default 0). order_index: Display order within the topic (default 0).
For a rule that applies to a single project only, use create_project_rule
instead — no rulebook+topic ceremony required.
""" """
uid = current_user_id() uid = current_user_id()
rule = await rulebooks_svc.create_rule( rule = await rulebooks_svc.create_rule(
@@ -275,9 +291,13 @@ async def create_project_rule(
) -> dict: ) -> dict:
"""Create a rule scoped to a single project (no rulebook needed). """Create a rule scoped to a single project (no rulebook needed).
Use this when a rule only applies to one project — it bypasses the Use this for anything SPECIFIC to one project — its files, paths, layout,
Rulebook -> Topic -> Rule ceremony. The rule is returned in get_project's or quirks. This is the correct home for the project-specific detail that
applicable_rules (under project_rules) and in list_rules(project_id=...). must NOT go into a shared rulebook (where it would leak to every other
project that gets the rulebook). General standards belong in a rulebook
instead (create_rule). It bypasses the Rulebook -> Topic -> Rule ceremony;
the rule is returned in get_project's applicable_rules (under
project_rules) and in list_rules(project_id=...).
Args: Args:
project_id: The project to attach the rule to. project_id: The project to attach the rule to.
@@ -345,7 +365,14 @@ async def delete_rule(rule_id: int, confirmed: bool = False) -> dict:
async def subscribe_project_to_rulebook( async def subscribe_project_to_rulebook(
project_id: int, rulebook_id: int, project_id: int, rulebook_id: int,
) -> dict: ) -> dict:
"""Subscribe a project to a rulebook. Its rules will apply to that project.""" """Subscribe a project to a rulebook — its rules then bind that project.
Subscription is the opt-in path for a non-always_on rulebook: a reusable,
themed module of GENERAL rules shared across the projects that subscribe.
Subscribe a project because it fits the rulebook's theme (e.g. a visual app
-> the design-system rulebook), not to host rules about this one project —
those belong in create_project_rule.
"""
uid = current_user_id() uid = current_user_id()
await rulebooks_svc.subscribe_project( await rulebooks_svc.subscribe_project(
project_id=project_id, rulebook_id=rulebook_id, user_id=uid, project_id=project_id, rulebook_id=rulebook_id, user_id=uid,