fix(systems): sweeps are the discovery moment — zero-Systems hint, create_system dedup gate, prose inverted
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 7s
CI & Build / TypeScript typecheck (push) Successful in 11s
CI & Build / integration (push) Successful in 16s
CI & Build / Python tests (push) Failing after 29s
CI & Build / Build & push image (push) Skipped

First real-world test of the #2562 fixes (Scribe issue #2569): a Forge
session ran a whole-codebase audit and created zero Systems — endorsed
by the shipped guidance, whose "no particular area takes none" clause
read as an exemption for exactly the record type that enumerates the
subsystem vocabulary. And the systems_hint was silent for a zero-Systems
project, the one state nothing else nudges out of.

- systems_hint gains a zero-Systems branch: prompt the FIRST
  create_system instead of going quiet.
- create_system is duplicate-gated like the other creates (normalized
  name, archived included, fail-open) — liberal creation becomes safe by
  construction, so the guidance can stop preaching restraint.
- Prose inverted on every surface (hint text, create_system docstring,
  floor bullet, using-scribe step 7): audits/sweeps take several tags
  and mint the Systems they name; the gate is the guardrail against
  sprawl, not holding back; only a record genuinely about no particular
  area goes untagged.
- Plugin 0.1.26 -> 0.1.27.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-09 15:35:59 -04:00
co-authored by Claude Fable 5
parent 3ff8803593
commit 56b1952de5
5 changed files with 97 additions and 16 deletions
+33 -2
View File
@@ -89,17 +89,48 @@ async def test_untagged_hint_names_the_projects_systems():
@pytest.mark.asyncio
async def test_untagged_hint_absent_without_systems_and_fails_open():
async def test_untagged_hint_zero_systems_prompts_first_create_and_fails_open():
from scribe.mcp.tools.systems import untagged_systems_hint
with patch("scribe.mcp.tools.systems.systems_svc") as svc:
# Zero Systems is the state nothing else nudges out of — the hint must
# prompt the FIRST create_system, not go silent.
svc.list_systems = AsyncMock(return_value=[])
assert await untagged_systems_hint(1, 5) is None
hint = await untagged_systems_hint(1, 5)
assert "no Systems yet" in hint and "create_system" in hint
with patch("scribe.mcp.tools.systems.systems_svc") as svc:
# A hint must never break a create — DB failure degrades to no hint.
svc.list_systems = AsyncMock(side_effect=RuntimeError("db down"))
assert await untagged_systems_hint(1, 5) is None
@pytest.mark.asyncio
async def test_create_system_same_normalized_name_is_duplicate_gated():
existing = _fake_system(sid=7, name="Scrape Pipeline")
existing.name = "Scrape Pipeline"
with patch("scribe.mcp.tools.systems.current_user_id", return_value=1), \
patch("scribe.mcp.tools.systems.systems_svc") as svc:
svc.list_systems = AsyncMock(return_value=[existing])
svc.create_system = AsyncMock()
from scribe.mcp.tools.systems import create_system
result = await create_system(project_id=5, name=" scrape pipeline ")
assert result["duplicate"] is True
assert result["existing_id"] == 7
svc.create_system.assert_not_awaited()
@pytest.mark.asyncio
async def test_create_system_distinct_name_passes_the_gate():
other = _fake_system(sid=7, name="Workers")
other.name = "Workers"
with patch("scribe.mcp.tools.systems.current_user_id", return_value=1), \
patch("scribe.mcp.tools.systems.systems_svc") as svc:
svc.list_systems = AsyncMock(return_value=[other])
svc.create_system = AsyncMock(return_value=_fake_system(sid=8, name="Exporter"))
from scribe.mcp.tools.systems import create_system
result = await create_system(project_id=5, name="Exporter")
assert result["name"] == "Exporter"
@pytest.mark.asyncio
async def test_create_task_untagged_in_project_with_systems_carries_hint():
note = MagicMock(); note.id = 60; note.to_dict.return_value = {"id": 60}