Files
FabledScribe/plugin/skills/shape-accounting/SKILL.md
T
bvandeusenandClaude Fable 5 d74a244b3a
CI & Build / TypeScript typecheck (push) Failing after 2s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 28s
CI & Build / Python tests (push) Failing after 37s
CI & Build / Build & push image (push) Skipped
feat(ledger): divergence readout — button B where button A is canon, shape history, and judged-shape recheck (#2793, milestone 294 step 7)
Every judgment now goes through one helper that remembers the fingerprint
judged (classified_sha) and writes a code_shape_events row; the sync writes
vanished / reappeared / drifted events and flags recheck_at when a body
moves under an instance/variant. The refresh flags diverges_from on shapes
new since the previous computation that sit where one canon dominates the
judged siblings of their directory+kind and were not proposed as that canon
(a first seed flags nothing); the write-path hint asks the same question
in-band for the shapes the hook names. list_shapes(flag=divergence|recheck),
shape_history(project_id, path, symbol) (read-only), coverage line/payload/
card carry divergent + recheck. Backup v8 carries the history. Plugin 0.1.36.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 23:13:39 -04:00

6.0 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).
  • instance of snippet N — conforms to recorded canon. Canon in another project counts (a family-level button shape fully accounts for a local use).
  • variant of 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.
  • unclassified — nobody has judged it yet. This is the todo list.

The loop

  1. 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.
  2. Read the todolist_shapes(project_id, status="unclassified"), optionally scoped by path to the directories the coverage line names as largest. snippet_id=N reads a consumer map.
  3. Judge in batchesclassify_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_snippet a canon and then Write/Edit code that references or resembles it, the definitions being written land as instance rows (classified_by: hook, the evidence in reason), 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 a classify_shapes call 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:

  1. list_shapes(project_id, proposal="canon", snippet_id=N) or path="dir" — read the page; proposal carries snippet_id, basis, score.
  2. confirm_shape_proposals(project_id, snippet_id=N) (or path=, basis=) for the ones that hold — hundreds at a time; symbol and reference proposals are near-certain, semantic deserves a look.
  3. classify_shapes the 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):

  • Divergencelist_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_from names the canon. Judge it: instance if it should be built from the canon (and rebuild it), variant with the why if the departure is deliberate. The write-path hook asks the same question in-band the moment such a shape is written.
  • Historyshape_history(project_id, path, symbol?): the current rows plus every classified / vanished / reappeared / drifted event with its commit — "instance of #N from , re-judged variant of #M because R, vanished at C". Rows, not recollection.
  • Rechecklist_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.