The 2026-08 audit left 77% of Scribe's ledger `exempt`, most of it a Vue component's scoped <style> rules and <script setup> functions — one-offs by construction (unreachable from any other file) that add nothing when judged one by one and bury the rows a person should look at. - coverage: Definition carries its line; scoped_definitions() names, per .vue file, every sym and every css rule inside <style scoped>; ArchiveShape carries the flag. - sync: such rows are stamped status=scoped / classified_by=mechanical with the by-construction reason (history event recorded); un-stamped back to unclassified if a later tree makes them ordinary; a judgment overrides. - The machine still sees them: proposer, derive grouping, divergence, hook evidence, canonical stamping and classify_shapes_by_rule's default all treat unclassified + scoped as the unjudged set (_MECHANICAL_TODO). Only the human todo (status=unclassified) and largest_gaps exclude them. - accounting counts `scoped`; coverage line and the project card legend show it; SHAPE_STATUSES gains it (no DB CHECK on status — no migration). - shape-accounting skill documents the bucket; plugin 0.1.37. Operator decision on #2869 (2026-08-21): keep extracting everything, stamp mechanically, keep `exempt` a human judgment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
6.4 KiB
name, description
| name | description |
|---|---|
| shape-accounting | Use when a project's shape accounting needs attention — the pattern_coverage line from enter_project shows unclassified shapes or is missing on a forge-served project, the operator asks about coverage/accounting/canon, or you just proved a code-to-canon relationship (an audit enumerated call sites, a consolidation repointed consumers, a verify pass confirmed a helper's users). Triggers on "coverage", "accounted", "unclassified", "classify shapes", "what uses this", or finishing any consolidation. |
Shape accounting — every shape classified against canon
The snippet library records canon (small); the shape ledger accounts for every extracted definition in a project's bound repos (total). Each ledger row carries a status:
canonical— IS a snippet's reference (the coverage sync stamps these mechanically; you rarely set it).instanceof snippet N — conforms to recorded canon. Canon in another project counts (a family-level button shape fully accounts for a local use).variantof snippet N — a deliberate, named departure. Reason required — the why IS the record.exempt— judged genuinely one-off. Reason required. A recorded judgment, not silence — it stops the next pass re-litigating it.scoped— one-off by construction, stamped by the coverage sync (a Vue component's scoped<style>rules and its<script setup>functions — unreachable from any other file). Accounted for without a judgment; still proposed against, grouped and flagged; any judgment you make overrides it. Not the todo.unclassified— nobody has judged it yet. This is the todo list.
The loop
- Seed / refresh — the ledger fills from coverage computation. Entering a
project triggers a background seed automatically; when you need it current
now (before a classification batch, or when the line is missing on a
forge-served project), call
refresh_pattern_coverage(project_id)— it returns the fresh accounting line. Takes seconds; it moves repo archives. - Read the todo —
list_shapes(project_id, status="unclassified"), optionally scoped bypathto the directories the coverage line names as largest.snippet_id=Nreads a consumer map. - Judge in batches —
classify_shapes(project_id, [{path, symbol, status, snippet_id?, reason?}]). All-or-nothing: a bad item applies nothing. Rows, never prose — a consumer list in a note or verification detail cannot be sorted, queried, or diffed.
Rows that arrive on their own
Two feeds keep the ledger current between your batches, so most shapes never need a hand judgment:
- The sync stamps a snippet's own reference location
canonical(classified_by: mechanical). - The write path stamps instances as you work: when you
get_snippeta canon and then Write/Edit code that references or resembles it, the definitions being written land asinstancerows (classified_by: hook, the evidence inreason), and the prior-art hook tells you what landed ("Shape accounting: recorded at … → instance of #N"). Offered-but-unopened snippets stamp nothing — so pull the canon you are instantiating; that pull is what turns your reuse into accounting. A hook row is evidence, not judgment: it never overrides a classification you made, and aclassify_shapescall overrides it.
The machine proposes, judgment classifies
Every coverage refresh runs the mechanical proposer over the unclassified rows: same symbol as a canon elsewhere → textual containment → body references a canon → signature resemblance → semantic (capped per refresh). A hit is a proposal on the row, never a classification. Work the queue in bulk:
list_shapes(project_id, proposal="canon", snippet_id=N)orpath="dir"— read the page;proposalcarries snippet_id, basis, score.confirm_shape_proposals(project_id, snippet_id=N)(orpath=,basis=) for the ones that hold — hundreds at a time;symbolandreferenceproposals are near-certain,semanticdeserves a look.classify_shapesthe rest — variant, exempt, or instance of a different snippet. Any judgment retires the proposal.
list_shapes(project_id, proposal="derive") lists the derive-first
candidates — the same body in ≥2 places or the same name defined in ≥3
files, with no canon at all (proposal.group names the family; the coverage
payload's derive_groups ranks the biggest). That is the consolidation
queue, not a classification queue: see below.
The derive-first rule
N same-shaped occurrences matching no recorded canon is never N loose
classifications — it is a consolidation candidate: derive one reference from
the dominant form, create_snippet it, migrate the outliers, then classify
the rest as instances. Canon is determined from the code; consistency comes
from the derivation, not from asking permission.
The divergence readout — button B where button A is canon
Three questions the ledger answers mechanically (#2793):
- Divergence —
list_shapes(project_id, flag="divergence")(and the coverage line's "N DIVERGENT"): a shape new since the previous refresh, in a directory where one canon dominates the judged siblings, that the proposer did not match to that canon.diverges_fromnames the canon. Judge it:instanceif it should be built from the canon (and rebuild it),variantwith the why if the departure is deliberate. The write-path hook asks the same question in-band the moment such a shape is written. - History —
shape_history(project_id, path, symbol?): the current rows plus everyclassified/vanished/reappeared/driftedevent with its commit — "instance of #N from , re-judged variant of #M because R, vanished at C". Rows, not recollection. - Recheck —
list_shapes(project_id, flag="recheck"): judged instances/variants whose body changed since judged. The judgment stands; re-confirm it (classify again with the same status) or re-judge.
What this buys
Divergence becomes mechanical: when button B appears where button A is canon,
the ledger says unintended divergence or justified variant with its
reason — nobody re-derives the history. get_snippet shows each snippet's
instances and variants, so "what uses this?" is answered from rows before
any contract change lands on its consumers.