feat(ledger): coverage refresh feeds the shape ledger; the readout inverts to accounting (#2788, milestone 294 step 2)
CI & Build / Python lint (push) Successful in 5s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Failing after 27s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 1m4s
CI & Build / Build & push image (push) Successful in 44s

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>
This commit is contained in:
2026-08-19 19:37:18 -04:00
co-authored by Claude Fable 5
parent 19fdc9aa89
commit 9b1597a3c9
6 changed files with 427 additions and 129 deletions
+24 -12
View File
@@ -424,15 +424,17 @@ async function loadNotes() {
interface CoverageGap {
dir: string;
uncovered: number;
unclassified: number;
total: number;
}
interface Coverage {
total: number;
recorded: number;
accounted: number;
unclassified: number;
counts: Record<string, number>;
estimate: boolean;
computed_at: string;
repos: { repo: string; ref: string; total: number; recorded: number }[];
repos: { repo: string; ref: string; total: number; accounted: number }[];
largest_gaps: CoverageGap[];
}
@@ -712,32 +714,42 @@ async function confirmDelete() {
</div>
<template v-if="coverage">
<div class="coverage-numbers">
<span class="coverage-count">{{ coverage.recorded }}/{{ coverage.total }}</span>
<span class="coverage-label">shapes recorded</span>
<span class="coverage-count">{{ coverage.accounted }}/{{ coverage.total }}</span>
<span class="coverage-label">shapes accounted for</span>
</div>
<div
class="coverage-bar"
role="progressbar"
:aria-valuenow="coverage.recorded"
:aria-valuenow="coverage.accounted"
:aria-valuemin="0"
:aria-valuemax="coverage.total"
aria-label="Shapes with a recorded snippet"
aria-label="Shapes classified against canon"
>
<div
class="coverage-bar-fill"
:style="{ width: (coverage.total ? (coverage.recorded / coverage.total) * 100 : 0) + '%' }"
:style="{ width: (coverage.total ? (coverage.accounted / coverage.total) * 100 : 0) + '%' }"
></div>
</div>
<div v-if="coverage.counts" class="coverage-gaps">
<span
v-for="k in ['canonical', 'instance', 'variant', 'exempt']"
:key="k"
>
<span v-if="coverage.counts[k]" class="coverage-gap-chip">
{{ coverage.counts[k] }} {{ k }}
</span>
</span>
</div>
<div v-if="coverage.largest_gaps?.length" class="coverage-gaps">
<span class="coverage-gaps-label">Largest gaps:</span>
<span class="coverage-gaps-label">Most unclassified:</span>
<span v-for="gap in coverage.largest_gaps" :key="gap.dir" class="coverage-gap-chip">
{{ gap.dir }} <span class="coverage-gap-count">{{ gap.uncovered }}</span>
{{ gap.dir }} <span class="coverage-gap-count">{{ gap.unclassified }}</span>
</span>
</div>
</template>
<p v-else class="coverage-empty">
Not measured yet — Refresh compares the bound repo's definitions
against recorded snippets.
Not measured yet — Refresh reads the bound repo's definitions into
the shape ledger and reports how many are classified against canon.
</p>
<p v-if="coverageError" class="coverage-error">{{ coverageError }}</p>
<!-- Forge pin (#2778): owner-only, because the eligible set is the