CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 21s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Failing after 42s
CI & Build / Build & push image (push) Skipped
- components/InceptionCard.vue: the one form, two homes — mode="create" in the New-project modal's second step (emits the choices; the create carries `inception`), mode="decide" on ProjectView for the owner of an undecided project (loads that project's defaults, records the decision). Always-on rulebooks listed checked (uncheck = exclude), others unchecked (check = subscribe), design system select, seed-Systems toggle (disabled once the project has Systems). Tokens only; modal canon (#2855); .btn-* canon. - ProjectView: the card while undecided, one "Inheritance decided <date> via … · …" line after; onDecided refreshes the project. - ProjectRulesTab: "Excluded always-on rulebooks" section with include-back. - api/inception.ts (types, fetchInceptionDefaults, decideInception); api/rulebooks.ts: ApplicableRules.excluded_always_on, exclude/include wrappers; REST POST/DELETE /api/projects/<id>/exclusions/rulebooks/<rb>. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
67 lines
3.3 KiB
Python
67 lines
3.3 KiB
Python
"""Milestone 297 step 2 — always-on exclusions reach every rule surface.
|
|
|
|
The SQL is the integration lane's; here the contracts: rules_payload carries
|
|
the seventh key, list_always_on_rules takes project_id, the session-start
|
|
block names the excluded rulebooks, and the MCP tools mount.
|
|
"""
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
|
|
from scribe.services.rulebooks import rules_payload
|
|
|
|
|
|
def test_rules_payload_carries_excluded_always_on_as_the_seventh_key():
|
|
out = rules_payload({
|
|
"rules": [], "truncated": False, "subscribed_rulebooks": [],
|
|
"excluded_always_on": [{"id": 1, "title": "Family"}],
|
|
})
|
|
assert set(out) == {
|
|
"applicable_rules", "applicable_rules_truncated", "subscribed_rulebooks",
|
|
"project_rules", "suppressed_rules", "suppressed_topics", "excluded_always_on",
|
|
}
|
|
assert out["excluded_always_on"] == [{"id": 1, "title": "Family"}]
|
|
# An older applicable dict without the key still renders (empty list).
|
|
assert rules_payload({"rules": [], "truncated": False, "subscribed_rulebooks": []})["excluded_always_on"] == []
|
|
|
|
|
|
def test_list_always_on_rules_service_and_tool_take_a_project_id():
|
|
import inspect
|
|
|
|
from scribe.mcp.tools import rulebooks as tools
|
|
from scribe.services import rulebooks as svc
|
|
assert "project_id" in inspect.signature(svc.list_always_on_rules).parameters
|
|
assert "project_id" in inspect.signature(tools.list_always_on_rules).parameters
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_session_context_names_the_excluded_always_on_rulebooks():
|
|
from types import SimpleNamespace as NS
|
|
|
|
from scribe.services.plugin_context import build_session_context
|
|
rules = [NS(id=1, title="`dev` is home", topic_id=1, statement="x")]
|
|
project = NS(id=9, title="Widget", goal="", design_system_id=None)
|
|
with patch("scribe.services.plugin_context.rulebooks_svc.list_always_on_rules",
|
|
AsyncMock(return_value=rules)) as lao, \
|
|
patch("scribe.services.plugin_context.rulebooks_svc.excluded_always_on_rulebooks",
|
|
AsyncMock(return_value=[{"id": 5, "title": "Design standards"}])), \
|
|
patch("scribe.services.plugin_context._topic_titles", AsyncMock(return_value={1: "git"})), \
|
|
patch("scribe.services.plugin_context.projects_svc.get_project", AsyncMock(return_value=project)), \
|
|
patch("scribe.services.plugin_context.notes_svc.list_notes", AsyncMock(return_value=([], 0))), \
|
|
patch("scribe.services.plugin_context.rulebooks_svc.get_applicable_rules",
|
|
AsyncMock(return_value={"rules": [], "truncated": False, "subscribed_rulebooks": [],
|
|
"project_rules": [], "suppressed_rules": [],
|
|
"suppressed_topics": [], "excluded_always_on": []})):
|
|
out = await build_session_context(user_id=7, project_id=9)
|
|
# The always-on set was asked FOR THIS PROJECT, and the departure is named.
|
|
assert lao.await_args.kwargs.get("project_id") == 9
|
|
assert "Excluded for this project by its inception decision" in out["context"]
|
|
assert "Design standards (#5)" in out["context"]
|
|
|
|
|
|
def test_exclusion_routes_are_registered():
|
|
from scribe.app import create_app
|
|
rules = {r.rule for r in create_app().url_map.iter_rules()}
|
|
assert "/api/projects/<int:project_id>/exclusions/rulebooks/<int:rulebook_id>" in rules
|
|
|