feat(systems): a System names its files — path patterns stored, validated and matched (milestone 444 step 3, #4756)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 15s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 1m6s
CI & Build / Python tests (push) Successful in 1m50s
CI & Build / Build & push image (push) Successful in 43s

A System gains path_patterns: globs relative to the repo root (* within one
directory, ** across any depth, a plain directory covering everything under
it). One service validates them for every door, so the web UI and MCP refuse
the same bad pattern with the same message. systems_for_paths resolves paths
to every active System that covers them, which step 4 (#4757) uses to deliver
an area's rulings when its files are touched.

- schema: systems.path_patterns JSONB NOT NULL default [] (migration 0113)
- service: normalize_path_patterns, path_matches, systems_for_paths
- routes + MCP create_system/update_system accept it; [] clears
- web UI: a Files field in the create and edit forms, patterns on the card
- backup carries it through export and restore
- using-scribe reflex 7: tagging work keeps a System's files current

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 22:55:26 -04:00
co-authored by Claude Opus 5.5
parent 01d8a0b9f1
commit a113c72b4f
14 changed files with 435 additions and 17 deletions
+17
View File
@@ -101,6 +101,23 @@ async def test_get_system_splits_records_by_kind():
assert [r["id"] for r in result["notes"]] == [12]
@pytest.mark.asyncio
async def test_path_patterns_reach_the_service_from_both_tools():
"""Omitted means unchanged; an empty list is a value — it clears them."""
with patch("scribe.mcp.tools.systems.current_user_id", return_value=1), \
patch("scribe.mcp.tools.systems.systems_svc") as svc:
svc.assess_system_name = AsyncMock(return_value=_NO_MATCH)
svc.create_system = AsyncMock(return_value=fake_system(name="Billing"))
svc.update_system = AsyncMock(return_value=fake_system(name="Billing"))
from scribe.mcp.tools.systems import create_system, update_system
await create_system(project_id=5, name="Billing", path_patterns=["src/billing"])
assert svc.create_system.await_args.kwargs["path_patterns"] == ["src/billing"]
await update_system(system_id=1, name="Billing")
assert "path_patterns" not in svc.update_system.await_args.kwargs
await update_system(system_id=1, path_patterns=[])
assert svc.update_system.await_args.kwargs["path_patterns"] == []
@pytest.mark.asyncio
async def test_update_system_not_found_raises():
with patch("scribe.mcp.tools.systems.current_user_id", return_value=1), \