diff --git a/plugin/skills/using-scribe/SKILL.md b/plugin/skills/using-scribe/SKILL.md index cd9aff8..6932a72 100644 --- a/plugin/skills/using-scribe/SKILL.md +++ b/plugin/skills/using-scribe/SKILL.md @@ -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 diff --git a/src/scribe/mcp/server.py b/src/scribe/mcp/server.py index 5766704..0c2ba81 100644 --- a/src/scribe/mcp/server.py +++ b/src/scribe/mcp/server.py @@ -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 diff --git a/src/scribe/mcp/tools/rulebooks.py b/src/scribe/mcp/tools/rulebooks.py index a9dd673..a2af6ae 100644 --- a/src/scribe/mcp/tools/rulebooks.py +++ b/src/scribe/mcp/tools/rulebooks.py @@ -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,