feat(family): the shape ledger judges against family ideas, across languages (milestone 463 step 5, #4991)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 58s
CI & Build / integration (push) Successful in 1m26s
CI & Build / Python tests (push) Successful in 2m9s
CI & Build / Build & push image (push) Successful in 1m27s

- classify_shapes takes idea_id: the shape is judged against the idea's
  reference in its language, or its first.
- A shape classified against a canon idea's reference moves the project's
  adoption row (instance -> adopted, variant -> variant). Withdrawing the
  shapes that gave an answer returns it to unassessed. This runs on
  classify_shapes, the sweep, confirm_proposals and the coverage refresh.
- assess and undo refuse an answer the shapes contradict. A conflict
  resolution re-judges the variant shapes on both sides.
- The proposer's family arm offers a canon idea's reference on a shared
  platform, in any language. It proposes only when the reference is the top
  hit over every readable snippet and scores at least 0.70. The pairs this
  was measured on are recorded beside _FAMILY_FLOOR. The proposer is now v6.
- The matrix cell shows the code that answers it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 13:06:45 -04:00
co-authored by Claude Opus 5.5
parent 233fa3eea9
commit 5f41dbd283
10 changed files with 823 additions and 38 deletions
+6
View File
@@ -280,6 +280,12 @@ async def assess_family_adoption(
variant cancels it, owed again reopens it. The same answer given twice
records nothing the second time.
When the project's code is in the shape ledger, prefer answering THERE:
classify_shapes the shape against the idea (idea_id=…) as an instance or
a variant, and this answer moves with it. An answer the shapes already
give otherwise is refused — the two ledgers never disagree; reclassify
the shapes if they are wrong.
Args:
project_id: the project answering.
idea_id: the canon idea.
+35 -13
View File
@@ -37,10 +37,15 @@ async def classify_shapes(
Args:
project_id: The project whose ledger is being judged.
classifications: Objects of {path, symbol, status, kind?, snippet_id?,
reason?, reason_code?}. path+symbol name the shape exactly as
idea_id?, reason?, reason_code?}. path+symbol name the shape exactly as
list_shapes shows it; kind ("sym"/"css") narrows when one file
defines both. snippet_id is required for canonical/instance/
variant; reason is required for variant/exempt. reason_code is
variant — or idea_id, a family idea: the shape is then judged
against that idea's reference implementation in the shape's
language, or its first reference when none is in that language
(an idea is shared across languages: a Python shape can be an
instance of an idea whose reference is Go). reason is required
for variant/exempt. reason_code is
an OPTIONAL index beside the prose (one of: scoped-css,
one-off-handler, test-helper, convention-plumbing, pure-helper,
generated, script, typed-record) so the ledger can be filtered
@@ -60,11 +65,20 @@ async def classify_shapes(
confirms it. Must be bound to the project. Omit it to judge only
synced rows.
FAMILY CANON FOLLOWS. A shape judged against a canon family idea's
reference IS the project's answer to that idea: an instance answers it
`adopted`, a variant `variant` (with the shape's reason), and
withdrawing the shapes that gave an answer returns it to `unassessed`.
The adoption ledger is moved for you — `family` in the result lists the
answers that moved — and assess_family_adoption refuses an answer the
shapes contradict.
All-or-nothing: a structural error, a missing snippet target, an unbound
repo, or no write access applies NOTHING. Returns {"classified": N,
"unmatched": [...], "provisional": N?} — unmatched names shapes no live
ledger row matches (the tree may have moved since you listed; pass `repo`
for a shape you just wrote, or re-run the project's coverage refresh).
"unmatched": [...], "provisional": N?, "family": [...]?} — unmatched names
shapes no live ledger row matches (the tree may have moved since you
listed; pass `repo` for a shape you just wrote, or re-run the project's
coverage refresh).
"""
uid = current_user_id()
return await shape_ledger_svc.classify_shapes(
@@ -116,7 +130,10 @@ async def list_shapes(
machine thinks are an instance of a snippet: `proposal` carries
snippet_id, basis, score), "derive" (rows that repeat with NO
canon: `proposal.group` names the family), or one basis
(symbol/text/reference/signature/semantic).
(symbol/text/reference/signature/semantic/family). "family" is
a canon family idea's reference — another project's, often
another language's — that the body means: the top match over
every snippet you can read, on a platform the project shares.
flag: the divergence readout (#2793) — "divergence": shapes new
since the previous refresh in a directory where one canon
dominates the judged siblings and NOT proposed as that canon
@@ -145,7 +162,8 @@ async def list_shapes(
THE FAST PATH through a big todo is the proposer's queue: every coverage
refresh matches unclassified shapes against canon (strongest basis
first: same symbol elsewhere → textual containment → body references
the canon → signature resemblance → semantic) and attaches a
the canon → signature resemblance → semantic, and a family idea's
reference in any language) and attaches a
`proposal` to each row it can speak for. Review `proposal="canon"` by
snippet or directory, then confirm_shape_proposals the ones that hold —
hundreds at a time — and classify_shapes the rest (variant/exempt, or
@@ -214,9 +232,11 @@ async def classify_shapes_by_rule(
revise a family you judged earlier).
One transaction: applies whole or not at all. Returns
{"classified": N, "sample": ["path::symbol", ...]} (first 12, sorted)
so you can see what the rule reached; N = 0 means the rule matched
nothing live and unclassified — widen the pattern or refresh coverage.
{"classified": N, "sample": ["path::symbol", ...], "family": [...]?}
(sample: first 12, sorted) so you can see what the rule reached; N = 0
means the rule matched nothing live and unclassified — widen the pattern
or refresh coverage. As with classify_shapes, a family idea's reference
judged here moves the project's answer to that idea (`family`).
"""
uid = current_user_id()
try:
@@ -317,12 +337,14 @@ async def confirm_shape_proposals(
snippet_id (confirm one canon's whole queue after reading its
`list_shapes(proposal="canon", ...)` page), path (a directory you
audited), or basis (e.g. "symbol" and "reference" are near-certain;
"semantic" deserves a look first) is required — a bare confirm-all is
not a judgment. min_score trims a basis's tail.
"semantic" and "family" deserve a look first) is required — a bare
confirm-all is not a judgment. min_score trims a basis's tail.
Proposals you do NOT confirm are judged with classify_shapes (variant,
exempt, or instance of a different snippet) — any judgment retires the
proposal. Requires write access. Returns {"confirmed": N}.
proposal. A confirmed family reference answers that idea `adopted` in
the project (`family` lists the answers that moved). Requires write
access. Returns {"confirmed": N, "family": [...]?}.
"""
uid = current_user_id()
try: