feat(ledger): CSS derive families are names, never bodies — name floor 2 for css, dup: grouping sym-only; derive line says "repeated name" and dismisses scoped-css; plugin 0.1.41 (note #2917)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 10s
CI & Build / integration (push) Successful in 25s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 1m0s
CI & Build / Build & push image (push) Successful in 25s
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 10s
CI & Build / integration (push) Successful in 25s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 1m0s
CI & Build / Build & push image (push) Successful in 25s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -588,21 +588,30 @@ async def test_derive_groups_land_on_rows_and_in_the_summary(seeded):
|
||||
assert summary["derive_groups"][1]["label"] == ".card"
|
||||
assert summary["derive_groups"][0]["size"] == 2 and summary["derive_groups"][1]["size"] == 3
|
||||
|
||||
# One of the css copies gets judged → the group shrinks on the next pass.
|
||||
# One of the css copies gets judged → the group shrinks on the next pass
|
||||
# but stays a family: a class in two files is already a recipe living in
|
||||
# two places (css name floor 2, note 2917). Judge the second and it's gone.
|
||||
await classify_shapes(owner, pid, [
|
||||
{"path": "b/z.css", "symbol": "card", "status": "exempt", "reason": "print sheet"},
|
||||
])
|
||||
await apply_derive_groups(pid)
|
||||
rows, _ = await list_project_shapes(owner, pid, proposal="derive")
|
||||
assert {r.symbol for r in rows} == {"slug"} # 2 files < the name floor
|
||||
assert {r.symbol for r in rows} == {"slug", "card"}
|
||||
assert {r.path for r in rows if r.symbol == "card"} == {"b/x.css", "b/y.css"}
|
||||
await classify_shapes(owner, pid, [
|
||||
{"path": "b/y.css", "symbol": "card", "status": "exempt", "reason": "print sheet"},
|
||||
])
|
||||
await apply_derive_groups(pid)
|
||||
rows, _ = await list_project_shapes(owner, pid, proposal="derive")
|
||||
assert {r.symbol for r in rows} == {"slug"} # 1 file < the css name floor
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_write_time_derive_names_the_family_or_the_canon_for_a_name(seeded):
|
||||
"""#2900: against real rows — a name in a dup family → the family (other
|
||||
files, count); a name whose canonical row lives elsewhere → that canon;
|
||||
a judged row at the path, the canon's own file, or an unknown name →
|
||||
silence."""
|
||||
"""#2900: against real rows — a name in a family → the family (other
|
||||
files, count; for CSS a NAME family, never a body one — note 2917); a
|
||||
name whose canonical row lives elsewhere → that canon; a judged row at
|
||||
the path, the canon's own file, or an unknown name → silence."""
|
||||
from scribe.services.shape_ledger import apply_derive_groups, write_time_derive
|
||||
|
||||
owner, pid, sid = seeded["owner"], seeded["pid"], seeded["snippet"]
|
||||
@@ -622,9 +631,10 @@ async def test_write_time_derive_names_the_family_or_the_canon_for_a_name(seeded
|
||||
out = await write_time_derive(pid, "v/D.vue", [("css", "log-empty"), ("css", "unknown")])
|
||||
assert len(out) == 1 and out[0]["symbol"] == "log-empty" and out[0]["kind"] == "css"
|
||||
fam = out[0]["family"]
|
||||
assert fam["identical"] is True and fam["label"] == ".log-empty"
|
||||
# Three identical bodies, and still a NAME family: CSS never groups by body.
|
||||
assert fam["identical"] is False and fam["label"] == ".log-empty"
|
||||
assert fam["files"] == ["v/A.vue", "v/B.vue", "v/C.vue"] and fam["file_count"] == 3
|
||||
assert out[0]["key"] == fam["group"] and fam["group"].startswith("dup:")
|
||||
assert out[0]["key"] == fam["group"] == "name:css:log-empty"
|
||||
# Editing one existing member still names the OTHER members.
|
||||
out = await write_time_derive(pid, "v/A.vue", [("css", "log-empty")])
|
||||
assert out[0]["family"]["files"] == ["v/B.vue", "v/C.vue"] and out[0]["family"]["size"] == 3
|
||||
|
||||
@@ -308,6 +308,12 @@ def test_derive_groups_copy_before_name_with_floors():
|
||||
("d.css", "css", "btn", "s1"), ("e.css", "css", "btn", "s2"), ("f.css", "css", "btn", "s3"),
|
||||
("g.py", "sym", "main", "s4"), ("h.py", "sym", "main", "s5"), # only 2 files → no name group
|
||||
("i.py", "sym", "one", "s6"),
|
||||
# CSS (note 2917): identical bodies under different names are NOT a
|
||||
# copy — two meanings sharing the style system's look; the same
|
||||
# class in two files IS a family (the name floor is 2 for css).
|
||||
("j.css", "css", "muted", "same"), ("k.css", "css", "pin-auto", "same"),
|
||||
("l.css", "css", "card", "c1"), ("m.css", "css", "card", "c2"),
|
||||
("n.css", "css", "alone", "c3"),
|
||||
]
|
||||
g = derive_groups(rows)
|
||||
assert g[("a.py", "sym", "helper")] == "dup:sha1" == g[("b.py", "sym", "helper")]
|
||||
@@ -315,6 +321,10 @@ def test_derive_groups_copy_before_name_with_floors():
|
||||
assert g[("d.css", "css", "btn")] == "name:css:btn"
|
||||
assert ("g.py", "sym", "main") not in g
|
||||
assert ("i.py", "sym", "one") not in g
|
||||
assert ("j.css", "css", "muted") not in g and ("k.css", "css", "pin-auto") not in g
|
||||
assert g[("l.css", "css", "card")] == "name:css:card" == g[("m.css", "css", "card")]
|
||||
assert ("n.css", "css", "alone") not in g
|
||||
assert not any(v.startswith("dup:") for k, v in g.items() if k[1] == "css")
|
||||
|
||||
|
||||
def test_derive_new_summary_counts_copies_first_seen_since_the_previous_refresh():
|
||||
|
||||
@@ -1250,13 +1250,16 @@ async def test_the_write_time_derive_check_names_a_family_or_a_canon_in_band():
|
||||
channel silences a family already named."""
|
||||
from scribe.services import plugin_context as pc
|
||||
found = [
|
||||
{"symbol": "log-empty", "kind": "css", "key": "dup:483a",
|
||||
"family": {"group": "dup:483a", "label": ".log-empty", "identical": True,
|
||||
{"symbol": "log-empty", "kind": "css", "key": "name:css:log-empty",
|
||||
"family": {"group": "name:css:log-empty", "label": ".log-empty", "identical": False,
|
||||
"files": ["a/TaskLogSection.vue", "a/WorkspaceTaskPanel.vue"],
|
||||
"file_count": 5, "size": 6}},
|
||||
{"symbol": "btn-primary", "kind": "css", "key": "canon:2855",
|
||||
"canon": {"snippet_id": 2855, "path": "frontend/src/assets/components.css",
|
||||
"label": ".btn-primary"}},
|
||||
{"symbol": "slugify", "kind": "sym", "key": "dup:483a",
|
||||
"family": {"group": "dup:483a", "label": "slugify", "identical": True,
|
||||
"files": ["a/x.py", "a/y.py"], "file_count": 2, "size": 3}},
|
||||
{"symbol": "load", "kind": "sym", "key": "name:sym:load",
|
||||
"family": {"group": "name:sym:load", "label": "load", "identical": False,
|
||||
"files": ["a/X.vue", "a/Y.vue", "a/Z.vue"], "file_count": 3, "size": 4}},
|
||||
@@ -1274,20 +1277,27 @@ async def test_the_write_time_derive_check_names_a_family_or_a_canon_in_band():
|
||||
patch.object(pc.shape_ledger_svc, "write_time_derive", check):
|
||||
out = await pc.build_write_path_hint(
|
||||
1, "frontend/src/components/New.vue", code=REAL_CODE, project_id=24,
|
||||
stamp_shapes=[("css", "log-empty"), ("css", "btn-primary"), ("sym", "load")],
|
||||
stamp_shapes=[("css", "log-empty"), ("css", "btn-primary"), ("sym", "slugify"),
|
||||
("sym", "load")],
|
||||
exclude_derive=["name:sym:load"],
|
||||
)
|
||||
check.assert_awaited_once_with(24, "frontend/src/components/New.vue",
|
||||
[("css", "log-empty"), ("css", "btn-primary"), ("sym", "load")])
|
||||
# The excluded family is gone; the other two render and are keyed.
|
||||
assert [d["key"] for d in out["derive"]] == ["dup:483a", "canon:2855"]
|
||||
assert out["derive_keys"] == ["dup:483a", "canon:2855"]
|
||||
[("css", "log-empty"), ("css", "btn-primary"),
|
||||
("sym", "slugify"), ("sym", "load")])
|
||||
# The excluded family is gone; the other three render and are keyed.
|
||||
assert [d["key"] for d in out["derive"]] == ["name:css:log-empty", "canon:2855", "dup:483a"]
|
||||
assert out["derive_keys"] == ["name:css:log-empty", "canon:2855", "dup:483a"]
|
||||
ctx = out["context"]
|
||||
assert "Shape ledger at `frontend/src/components/New.vue`" in ctx
|
||||
assert "`.log-empty` is a duplicate family with no canon — identical body in 5 other file(s): " \
|
||||
# A CSS family is a repeated NAME (note 2917) and its dismissal is scoped-css;
|
||||
# a code dup family is an identical body and dismisses as convention-plumbing.
|
||||
assert "`.log-empty` is a repeated name with no canon — defined in 5 other file(s): " \
|
||||
"`a/TaskLogSection.vue`, `a/WorkspaceTaskPanel.vue` +3 more; derive it now" in ctx
|
||||
assert "`slugify` is a duplicate family with no canon — identical body in 2 other file(s): " \
|
||||
"`a/x.py`, `a/y.py`; derive it now" in ctx
|
||||
assert "`.btn-primary` is canon — snippet #2855 at `frontend/src/assets/components.css`" in ctx
|
||||
assert "convention-plumbing" in ctx
|
||||
assert 'reason_code=\"scoped-css\")` dismisses' in ctx
|
||||
assert 'reason_code=\"convention-plumbing\")` dismisses' in ctx
|
||||
assert "`load`" not in ctx
|
||||
# No project → no check; a failing check never sinks the hint.
|
||||
check.reset_mock()
|
||||
|
||||
Reference in New Issue
Block a user