feat(ledger): coverage self-seeds — enter_project background refresh + refresh_pattern_coverage tool + shape-accounting skill (#2802, milestone 294)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 7s
CI & Build / integration (push) Successful in 23s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Failing after 33s
CI & Build / Build & push image (push) Skipped
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 7s
CI & Build / integration (push) Successful in 23s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Failing after 33s
CI & Build / Build & push image (push) Skipped
The UI Refresh button must not be the only seed path (operator directive, hit live: the P7 backfill stalled waiting for a click). Three parts: - enter_project fire-and-forgets refresh_if_stale on the project OWNER — absent or day-old readouts recompute in the background (same spawn the webhook path uses), the enter stays fast, forge-less owners exit quietly (rule #115 baseline), and an in-flight guard keeps concurrent enters from fetching the same tarball N times. - refresh_pattern_coverage(project_id): the synchronous agent-facing form — write-gated, owner-keyring resolution, and ValueError messages that name the fix (add a connection / bind_repo) instead of measuring nothing silently. - plugin 0.1.33 ships the shape-accounting skill: the five statuses, the seed/todo/judge loop, and the derive-first rule, triggered by the coverage line or any proved code-to-canon relationship. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,15 @@ def _no_coverage():
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _no_background_seed():
|
||||
"""enter_project now fire-and-forgets a coverage self-seed (#2802). These
|
||||
are no-database unit tests, so the spawn is stubbed out; the firing shape
|
||||
has its own test below."""
|
||||
with patch("scribe.mcp.tools.projects.spawn") as mock:
|
||||
yield mock
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _no_bootstrap():
|
||||
"""With _no_systems stubbing an empty vocabulary, every test here reaches
|
||||
@@ -319,6 +328,31 @@ async def test_enter_project_never_asks_bootstrap_once_a_vocabulary_exists(
|
||||
))
|
||||
out = await enter_project(project_id=5)
|
||||
assert "systems_bootstrap" not in out
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_enter_project_fires_the_coverage_seed_on_the_owner(
|
||||
_no_background_seed,
|
||||
):
|
||||
"""The arrival self-seed (#2802): entering spawns refresh_if_stale on the
|
||||
project OWNER's id with the cache read the enter already did — fire and
|
||||
forget, so a missing ledger seeds itself without the UI button, and the
|
||||
enter stays fast."""
|
||||
import contextlib
|
||||
|
||||
project = _fake_project(id=5)
|
||||
project.user_id = 42 # explicit: the OWNER, not the caller (ctx uid=7)
|
||||
with contextlib.ExitStack() as stack:
|
||||
for cm in _enter_project_stubs(project):
|
||||
stack.enter_context(cm)
|
||||
seed = stack.enter_context(patch(
|
||||
"scribe.mcp.tools.projects.coverage_svc.refresh_if_stale",
|
||||
MagicMock(return_value=object()),
|
||||
))
|
||||
await enter_project(project_id=5)
|
||||
seed.assert_called_once_with(42, 5, cached=None)
|
||||
_no_background_seed.assert_called_once()
|
||||
assert _no_background_seed.call_args.kwargs["site"] == "enter_project.coverage_seed"
|
||||
_no_bootstrap.assert_not_awaited()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user