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

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:
2026-08-20 08:18:36 -04:00
co-authored by Claude Fable 5
parent 9f1a52a035
commit e2a084f1fb
8 changed files with 262 additions and 5 deletions
+30 -1
View File
@@ -9,6 +9,7 @@ refresh; these tools only ever judge what the sync has seen.
from __future__ import annotations
from scribe.mcp._context import current_user_id
from scribe.services import coverage as coverage_svc
from scribe.services import shape_ledger as shape_ledger_svc
@@ -90,6 +91,34 @@ async def list_shapes(
return {"shapes": [r.to_dict() for r in rows], "total": total}
async def refresh_pattern_coverage(project_id: int) -> dict:
"""Seed or refresh the project's shape ledger NOW, and return the readout.
The synchronous form of the arrival-moment background seed (#2802):
downloads the bound repos through the OWNER's forge connections, upserts
every extracted shape into the ledger (new shapes arrive `unclassified`),
re-stamps snippet reference locations as canonical, and recomputes the
accounting. Reach for it when the ledger must be current before you act —
a classification batch about to run, a coverage question asked directly —
rather than waiting on the background seed an enter_project triggers.
Takes seconds, not milliseconds (it moves repo archives). Requires write
access to the project. Errors name the fix: no forge connection → the
owner adds one (Settings → Integrations → Git Forges); no served repo →
bind_repo on a host a connection serves.
Returns the accounting payload — total, accounted, counts by status,
unclassified, repos, largest_gaps — plus `pattern_coverage`, the same
one-line summary enter_project carries.
"""
uid = current_user_id()
coverage = await coverage_svc.refresh_for_caller(uid, project_id)
return {
"pattern_coverage": coverage_svc.coverage_line(coverage),
**coverage,
}
def register(mcp) -> None:
for fn in (classify_shapes, list_shapes):
for fn in (classify_shapes, list_shapes, refresh_pattern_coverage):
mcp.tool(name=fn.__name__)(fn)