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