Project rule + topic suppressions #54
Reference in New Issue
Block a user
Delete Branch "dev"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Lets a project mute individual rules or whole topics from rulebooks it subscribes to, without unsubscribing the rulebook. Companion to the prior rules-consolidation work (S1–S4 in PR #53) and implements Scribe task #187.
Use case
A project subscribes to a broad rulebook (e.g. FabledSword family, or a future "FabledSword design system") but wants to opt out of one or two specific rules — or a whole topic — that doesn't apply. Without this, the operator's only options were "don't subscribe at all" (loses every rule in the rulebook) or "fork the rulebook" (maintenance nightmare).
Changes
Schema (migration 0060): two new pure many-to-many association tables —
project_rule_suppressions(project_id, rule_id)andproject_topic_suppressions(project_id, topic_id). PKs on both columns; FKs CASCADE. Nodeleted_at— pure associations, hard-deleted on project trash.MCP tools (4):
suppress_rule_for_project,unsuppress_rule_for_project,suppress_topic_for_project,unsuppress_topic_for_project. All idempotent; verify project ownership AND that the rule/topic belongs to a rulebook the user owns. Project-scoped rules (Rule.project_id) are deliberately not suppressible — delete them instead.REST:
POST/DELETE /api/projects/<pid>/suppressions/rules/<rid>andPOST/DELETE /api/projects/<pid>/suppressions/topics/<tid>. 204 on success.Service-layer projection:
get_applicable_rulesruns two extra queries (cheap — bounded by suppression count) and now emits:suppressed_rules— detail objects{id, title, topic_id, topic_title, rulebook_id, rulebook_title}(deviation from the plan's bare ID-array spec; UI needed titles, bundling avoids a follow-up lookup)suppressed_topics— detail objects{id, title, rulebook_id, rulebook_title}ruleslist is filtered SQL-side, sotruncatedreflects the post-suppression countrulesnow also carriestopic_idandrulebook_idcolumns for the UI's per-row affordanceThese two new fields propagate through
get_project,enter_project,get_task(plan), andstart_planningso Claude sees what's muted on a project from any of the standard handshakes.Trash cascade: project deletion hard-deletes suppression rows for that project. FK CASCADE would only fire on a final purge; the soft-delete path keeps the project row alive, so we clean up explicitly here to ensure restore doesn't bring stale mutes back.
Frontend (
ProjectRulesTab.vue):groupByRulebookAndTopicrefactored to return id-bearing structs (was string-keyed records — needed for the per-topic suppress click)Tests
get_applicable_rulesshape tests rewritten: execute count went 3 → 5, rules tuple grew 5 → 7 columns, new test coverssuppressed_rules/suppressed_topicscontentDeploy
Migration 0060 runs automatically on container start. Tested on dev: ✅ migration applied cleanly; ✅ MCP suppression tools work end-to-end; ✅ UI affordance reveals on hover and the Suppressed section toggles correctly.
What this does NOT do
Rule.project_idrules can't be suppressed (delete them instead). Asymmetric on purpose: suppression is meant for rules you don't own.🤖 Generated with Claude Code