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>
compute_coverage is now the ledger's sync point: every walk upserts the
extracted shapes (new → unclassified, the todo state; surviving → last-seen
bump; vanished → stamped, kept as history), re-files judgments whose snippet
target went away, and mechanically stamps snippet reference locations as
canonical — the one always-safe rule, self-healing only for its own stamps
(an agent's judgment is never unwound by machinery).
The covering predicate moves to shape_ledger.location_covers as the single
home (match_shapes retired with its consumer); coverage's payload and line
invert from 'N/M shapes recorded' to shape ACCOUNTING per note 2786:
accounted/total with a canonical·instance·variant·exempt breakdown, and
unclassified — THE todo — with its largest directories. Cache key bumps to
v2 so pre-ledger blobs honestly read 'not measured yet' instead of rendering
in a shape no longer spoken.
Readout is deliberately project-wide (all repos' live rows), while the walk
serves whichever repos the owner's keyring reaches this refresh.
Integration tests pin the new contract: rows for every extracted shape,
mechanical canonical stamps carrying snippet ids, idempotent recompute,
agent judgments surviving recompute AND vanish/return, vanished rows leaving
the readout but keeping their history.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A forge token is a user's credential, not an instance's. The single
admin-settings config is replaced by per-user keyring rows (one per forge
host), and every server-side forge read runs on the PROJECT OWNER's keyring:
- forge_connections table + projects.forge_connection_id pin (migration 0078,
which also carries the existing admin config into the first admin's row and
deletes the old setting keys — no legacy dual-read)
- get_forge() replaced by get_forges(owner_id, project_id) -> ForgeSelector;
resolve(repo) picks the connection whose host serves the repo. A pinned
project uses ONLY its pinned connection; a stale pin (ownership moved) is
ignored, never honored across users
- env FORGE_* config survives as an implicit entry for admin owners only;
a stored row for the same host beats it
- consumers threaded: pull-time freshness (owner of the note), coverage
(owner of the project), coverage routes' configured flag
- routes: /api/settings/forge-connections CRUD + per-connection test
(own-rows only, tokens never returned); /api/admin/forge shrinks to
/api/admin/forge-webhook (secret only); PUT /api/projects/<id>/forge pins,
owner-or-admin asking, owner's connections only
- UI: Git Forges card moves to Settings -> Integrations as a connection
list; webhook secret stays in the admin Config tab; owner-only forge
select on the project coverage card
- backups exclude forge_connections (credentials, api_keys precedent) and
the pin, so restores fall back to keyring resolution
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Server-side shape enumeration per bound repo — one archive download via the
forge adapter, definitions extracted with a Python mirror of the write-path
hook's awk rules (shared test vectors pin the two together) — compared
against recorded snippet locations by path+symbol. Summary is cached in the
settings KV with a freshness stamp; recomputed on webhook push (spawned off
the delivery path) or explicit refresh, never in a request path.
Surfaces: GET/POST /api/projects/<id>/coverage[/refresh], a project-page
card (estimate-labeled, largest-gaps chips), and a one-line evidence-carrying
entry in enter_project read from cache only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>