feat(ledger): mechanical proposer — every refresh proposes instances against canon and groups derive-first candidates; agents confirm in batches (#2792, milestone 294 step 6)
CI & Build / Python lint (push) Successful in 5s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / integration (push) Failing after 34s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / Python tests (push) Successful in 1m17s
CI & Build / Build & push image (push) Successful in 48s
CI & Build / Python lint (push) Successful in 5s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / integration (push) Failing after 34s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / Python tests (push) Successful in 1m17s
CI & Build / Build & push image (push) Successful in 48s
Shapes now carry a content fingerprint (signature + whitespace/comment- insensitive body_sha; migration 0080) and the proposer runs inside the coverage refresh, the one moment bodies exist: symbol elsewhere → textual containment → body references the canon → signature resemblance → semantic (capped per refresh, unreached rows stay unexamined for the next). A hit is a proposal on the row (proposed_snippet_id/basis/score), never a classification; rows with no canon hit group by the derive-first rule (identical body in ≥2 places, same name in ≥3 files) as proposal_basis= derive + a group key. list_shapes(proposal=any|canon|derive|<basis>) is the queue; confirm_shape_proposals(project_id, snippet_id|path|basis) confirms in batches as agent instances; any classify_shapes/hook stamp retires the proposal. Readout carries proposed + derive_groups (line, payload, card). Plugin 0.1.35 (skill: the machine proposes, judgment classifies). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -456,3 +456,54 @@ async def test_unservable_binding_measures_nothing(seeded):
|
||||
await set_binding(uid, "https://github.com/somebody/else.git", other_pid)
|
||||
|
||||
assert await compute_coverage(uid, other_pid, selector=_selector(_tarball(TREE))) is None
|
||||
|
||||
|
||||
# --- #2792: fingerprints and the proposer's readout --------------------------
|
||||
|
||||
|
||||
def test_extract_definitions_fingerprints_each_block():
|
||||
"""The block rule across the language families the extractor knows: a
|
||||
Python def ends at the next top-level statement, a braces/CSS block keeps
|
||||
its closer, and comments/decorators don't move the hash."""
|
||||
from scribe.services.coverage import extract_definitions
|
||||
|
||||
text = (
|
||||
"import os\n\n"
|
||||
"def a(x):\n # comment\n return x + 1\n\n\n"
|
||||
"class B:\n def m(self):\n return 2\n\n"
|
||||
".btn {\n color: red;\n}\n"
|
||||
"export const f = (x) => {\n return x;\n};\n"
|
||||
)
|
||||
defs = {d.name: d for d in extract_definitions(text)}
|
||||
assert set(defs) == {"a", "B", "m", "btn", "f"}
|
||||
assert defs["a"].signature == "def a(x):"
|
||||
assert defs["a"].body.startswith("def a(x):\n # comment\n return x + 1")
|
||||
assert "class B" not in defs["a"].body
|
||||
assert defs["B"].body.rstrip().endswith("return 2")
|
||||
assert defs["btn"].body == ".btn {\n color: red;\n}"
|
||||
assert defs["f"].body == "export const f = (x) => {\n return x;\n};"
|
||||
assert all(len(d.body_sha) == 16 for d in defs.values())
|
||||
# Comment changes don't change what the shape IS; code changes do.
|
||||
again = {d.name: d for d in extract_definitions(text.replace("# comment", "# other"))}
|
||||
assert again["a"].body_sha == defs["a"].body_sha
|
||||
changed = {d.name: d for d in extract_definitions(text.replace("x + 1", "x + 2"))}
|
||||
assert changed["a"].body_sha != defs["a"].body_sha
|
||||
# And the identity view is unchanged for the hook mirror.
|
||||
from scribe.services.coverage import extract_shapes
|
||||
assert extract_shapes(text) == [(d.kind, d.name) for d in extract_definitions(text)]
|
||||
|
||||
|
||||
def test_coverage_line_names_the_proposers_standing():
|
||||
from scribe.services.coverage import coverage_line
|
||||
|
||||
base = {
|
||||
"total": 100, "accounted": 10, "unclassified": 90,
|
||||
"counts": {"canonical": 10, "instance": 0, "variant": 0, "exempt": 0},
|
||||
"computed_at": "2026-08-21T00:00:00+00:00",
|
||||
"largest_gaps": [{"dir": "src", "unclassified": 90, "total": 90}],
|
||||
}
|
||||
assert coverage_line(base).endswith("; 90 unclassified, largest: src")
|
||||
line = coverage_line({**base, "proposed": 40, "derive_groups": [{"group": "a"}, {"group": "b"}]})
|
||||
assert "; 90 unclassified (40 proposed, 2 derive groups), largest: src" in line
|
||||
line = coverage_line({**base, "proposed": 0, "derive_groups": [{"group": "a"}]})
|
||||
assert "(1 derive group)" in line
|
||||
|
||||
Reference in New Issue
Block a user