From 658348f208b858633f4da694d9bdcce99b149c6f Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Mon, 1 Jun 2026 00:56:08 -0400 Subject: [PATCH 1/3] feat(rules): always_on rulebook flag + Scribe-first prompt Adds rulebooks.always_on (migration 0058) and a new list_always_on_rules MCP tool so a session-start eager pull can fetch standing rules without needing an active-project notion. Updates _INSTRUCTIONS so Claude calls the new tool at session start and codifies engineering rules in Scribe rather than CLAUDE.md / auto-memory. Seeds FabledSword family rulebook to always_on=true on migrate, matching its design role as the cross-project standards rulebook. Frontend: badge in RulebookListPane for always-on rulebooks; toggle in RulebookDetailPane header bound to a new toggleAlwaysOn store action. This is S1+S2 of the rules-consolidation plan (Scribe task #508). S3 (project-scoped rules) and S4 (enter_project handshake) follow. Co-Authored-By: Claude Opus 4.7 --- alembic/versions/0058_rulebook_always_on.py | 39 ++++++++++++++ frontend/src/api/rulebooks.ts | 3 +- .../components/rules/RulebookDetailPane.vue | 25 ++++++++- .../src/components/rules/RulebookListPane.vue | 13 ++++- frontend/src/stores/rulebooks.ts | 10 +++- src/fabledassistant/mcp/server.py | 23 +++++--- src/fabledassistant/mcp/tools/rulebooks.py | 38 ++++++++++++- src/fabledassistant/models/rulebook.py | 6 ++- src/fabledassistant/routes/rulebooks.py | 2 +- src/fabledassistant/services/rulebooks.py | 29 +++++++++- tests/test_mcp_tool_rulebooks.py | 53 ++++++++++++++++++- tests/test_routes_rulebooks.py | 23 +++++++- 12 files changed, 243 insertions(+), 21 deletions(-) create mode 100644 alembic/versions/0058_rulebook_always_on.py diff --git a/alembic/versions/0058_rulebook_always_on.py b/alembic/versions/0058_rulebook_always_on.py new file mode 100644 index 0000000..0607aae --- /dev/null +++ b/alembic/versions/0058_rulebook_always_on.py @@ -0,0 +1,39 @@ +"""rulebook always_on flag + +Revision ID: 0058 +Revises: 0057 +Create Date: 2026-06-01 + +Adds a boolean `always_on` to the `rulebooks` table. Rules from rulebooks +flagged always_on are loaded at session start by the new +`list_always_on_rules` MCP tool — they apply regardless of which project +(if any) is in scope. Seeds the FabledSword family rulebook to always_on +because that's the cross-project standards rulebook by design. +""" +from alembic import op +import sqlalchemy as sa + + +revision = "0058" +down_revision = "0057" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.add_column( + "rulebooks", + sa.Column( + "always_on", + sa.Boolean(), + nullable=False, + server_default=sa.text("false"), + ), + ) + op.execute( + "UPDATE rulebooks SET always_on = TRUE WHERE title = 'FabledSword family'" + ) + + +def downgrade() -> None: + op.drop_column("rulebooks", "always_on") diff --git a/frontend/src/api/rulebooks.ts b/frontend/src/api/rulebooks.ts index 77685a6..bb4bbfa 100644 --- a/frontend/src/api/rulebooks.ts +++ b/frontend/src/api/rulebooks.ts @@ -5,6 +5,7 @@ export interface Rulebook { owner_user_id: number; title: string; description: string; + always_on: boolean; created_at: string | null; updated_at: string | null; } @@ -65,7 +66,7 @@ export async function createRulebook(data: { title: string; description?: string return apiPost("/api/rulebooks", data); } -export async function updateRulebook(id: number, data: Partial<{ title: string; description: string }>): Promise { +export async function updateRulebook(id: number, data: Partial<{ title: string; description: string; always_on: boolean }>): Promise { return apiPatch(`/api/rulebooks/${id}`, data); } diff --git a/frontend/src/components/rules/RulebookDetailPane.vue b/frontend/src/components/rules/RulebookDetailPane.vue index 56be664..ab69d29 100644 --- a/frontend/src/components/rules/RulebookDetailPane.vue +++ b/frontend/src/components/rules/RulebookDetailPane.vue @@ -1,5 +1,5 @@