docs(mcp): encode rule-scope model in rulebook tool descriptions #67

Merged
bvandeusen merged 1 commits from dev into main 2026-06-10 10:18:51 -04:00
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
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
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,
communication style) and codebase onboarding pointers, respectively. Before
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
in a project, an existing rulebook+topic, or a new rulebook.
avoid duplicates.
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
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:
"""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:
title: Rulebook name (e.g. "FabledSword family").
@@ -247,7 +258,15 @@ async def create_rule(
topic_id: int, title: str, statement: str,
why: str = "", how_to_apply: str = "", order_index: int = 0,
) -> 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:
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.
how_to_apply: Optional operationalization — when / where it kicks in.
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()
rule = await rulebooks_svc.create_rule(
@@ -275,9 +291,13 @@ async def create_project_rule(
) -> dict:
"""Create a rule scoped to a single project (no rulebook needed).
Use this when a rule only applies to one project — 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=...).
Use this for anything SPECIFIC to one project — its files, paths, layout,
or quirks. This is the correct home for the project-specific detail that
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:
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(
project_id: int, rulebook_id: int,
) -> 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()
await rulebooks_svc.subscribe_project(
project_id=project_id, rulebook_id=rulebook_id, user_id=uid,