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:
@@ -198,11 +198,13 @@ def extract_definitions(text: str) -> list[Definition]:
|
||||
break
|
||||
block = lines[i:end]
|
||||
# A CSS rule's fingerprint is its DECLARATIONS, not its selector
|
||||
# (#2872): the row's identity already carries the selector, and the
|
||||
# question the fingerprint answers for derive grouping is "is this the
|
||||
# same rule under another name?" — .closed-msg / .error-block /
|
||||
# .success-msg with identical bodies are one dup group, not three
|
||||
# lonely rows. Sym blocks keep their signature line in the hash.
|
||||
# (#2872): the row's identity already carries the selector. Since
|
||||
# note 2917 the derive grouping no longer reads CSS bodies at all (a
|
||||
# class is grouped by name only), so for CSS the fingerprint is the
|
||||
# recheck identity — "did this rule's body change since it was
|
||||
# judged?" — and nothing more. The shape of the hash is kept as-is on
|
||||
# purpose: changing it would flip every judged CSS row to recheck on
|
||||
# the next sync. Sym blocks keep their signature line in the hash.
|
||||
if kind == "css":
|
||||
# One-line rules (`.x { color: red; }`) carry their declarations on
|
||||
# the selector line itself; a block that is only the selector plus
|
||||
@@ -217,10 +219,9 @@ def extract_definitions(text: str) -> list[Definition]:
|
||||
# A SINGLE declaration is not a shape (#2903): `color: var(--fs-
|
||||
# text-tertiary)` under .text-muted, .task-mark and .pin-badge-auto
|
||||
# is three meanings sharing one line, not three copies of one
|
||||
# rule — the first pay-down found 5-file "families" of exactly
|
||||
# this and nobody would consolidate them. Keep the selector in the
|
||||
# hash for one-liners, so they group only with same-name copies;
|
||||
# two declarations and up stay selector-agnostic.
|
||||
# rule. Keep the selector in the hash for one-liners; two
|
||||
# declarations and up stay selector-agnostic. (Moot for grouping
|
||||
# since note 2917, kept for fingerprint stability — see above.)
|
||||
elif _declaration_count(hashed) < 2:
|
||||
hashed = block
|
||||
else:
|
||||
|
||||
@@ -1061,17 +1061,27 @@ def _derive_line(path: str, derive: list[dict]) -> str:
|
||||
)
|
||||
continue
|
||||
f = d["family"]
|
||||
how = "identical body" if f.get("identical") else "same name defined"
|
||||
files = ", ".join(f"`{x}`" for x in f.get("files") or [])
|
||||
more = f.get("file_count", 0) - len(f.get("files") or [])
|
||||
if more > 0:
|
||||
files += f" +{more} more"
|
||||
n = f.get("file_count", 0)
|
||||
if f.get("identical"):
|
||||
what = f"is a duplicate family with no canon — identical body in {n} other file(s)"
|
||||
else:
|
||||
# A name family: the same definition name living in several
|
||||
# files. CSS is only ever grouped this way (note 2917) — a class
|
||||
# is a recipe, and the recipe is what gets derived or dismissed.
|
||||
what = f"is a repeated name with no canon — defined in {n} other file(s)"
|
||||
# The dismissal reason the family most likely earns: a class name
|
||||
# reused for different purposes is scoped styling; a code name reused
|
||||
# across modules is convention plumbing.
|
||||
dismiss = "scoped-css" if d.get("kind") == "css" else "convention-plumbing"
|
||||
parts.append(
|
||||
f"`{f['label']}` is a duplicate family with no canon — {how} in "
|
||||
f"{f.get('file_count', 0)} other file(s): {files}; derive it now: "
|
||||
f"`{f['label']}` {what}: {files}; derive it now: "
|
||||
"record the canon (create_snippet) and make the copies instances "
|
||||
"(classify_shapes) — or, if these are convention not copies, "
|
||||
"`classify_shapes(..., status=\"exempt\", reason_code=\"convention-plumbing\")` "
|
||||
f"`classify_shapes(..., status=\"exempt\", reason_code=\"{dismiss}\")` "
|
||||
"dismisses the family — rather than adding another copy"
|
||||
)
|
||||
return f"> Shape ledger at `{path}`: " + "; ".join(parts) + "."
|
||||
|
||||
@@ -923,6 +923,13 @@ async def stamp_write_path_instances(
|
||||
# recur by convention, not by duplication).
|
||||
_DERIVE_MIN_DUP = 2
|
||||
_DERIVE_MIN_NAME = 3
|
||||
# CSS is never grouped by body (note 2917): classes for different purposes
|
||||
# share declarations because the style system makes them alike — `.text-muted`
|
||||
# and `.pin-badge-auto` carrying the same `color: var(--fs-text-tertiary)` are
|
||||
# two meanings, not two copies. A CSS family is a NAME defined in more than
|
||||
# one file: that is a recipe living in several places, and two is already
|
||||
# the signal (a class name is deliberate in a way `setup`/`load` are not).
|
||||
_DERIVE_MIN_NAME_CSS = 2
|
||||
# Semantic checks per repo per refresh — an embedding each (local fastembed),
|
||||
# bounded so a 4,000-row ledger is worked through over refreshes, not in one.
|
||||
_SEMANTIC_CAP = 150
|
||||
@@ -1298,15 +1305,16 @@ def derive_groups(
|
||||
rows: Iterable[tuple[str, str, str, str]]
|
||||
) -> dict[tuple[str, str, str], str]:
|
||||
"""The derive-first grouping over (path, kind, symbol, body_sha) rows
|
||||
that matched no canon: {(path, kind, symbol): group_key}. Identical
|
||||
bodies in ≥2 places group as `dup:<sha>`; the same name defined in ≥3
|
||||
files groups as `name:<kind>:<symbol>`; a row joins at most one group,
|
||||
the copy before the name."""
|
||||
that matched no canon: {(path, kind, symbol): group_key}. For code
|
||||
(kind `sym`) identical bodies in ≥2 places group as `dup:<sha>` and the
|
||||
same name defined in ≥3 files groups as `name:sym:<symbol>`, the copy
|
||||
before the name. CSS groups by name only — the same class defined in
|
||||
≥2 files is `name:css:<symbol>`; its body never groups it (note 2917)."""
|
||||
by_sha: dict[str, list[tuple[str, str, str]]] = {}
|
||||
by_name: dict[tuple[str, str], list[tuple[str, str, str]]] = {}
|
||||
for path, kind, symbol, sha in rows:
|
||||
key = (path, kind, symbol)
|
||||
if sha:
|
||||
if sha and kind != "css":
|
||||
by_sha.setdefault(sha, []).append(key)
|
||||
by_name.setdefault((kind, _norm_symbol(symbol)), []).append(key)
|
||||
out: dict[tuple[str, str, str], str] = {}
|
||||
@@ -1315,7 +1323,8 @@ def derive_groups(
|
||||
for key in keys:
|
||||
out.setdefault(key, f"dup:{sha}")
|
||||
for (kind, symbol), keys in by_name.items():
|
||||
if len({k[0] for k in keys}) >= _DERIVE_MIN_NAME:
|
||||
floor = _DERIVE_MIN_NAME_CSS if kind == "css" else _DERIVE_MIN_NAME
|
||||
if len({k[0] for k in keys}) >= floor:
|
||||
for key in keys:
|
||||
out.setdefault(key, f"name:{kind}:{symbol}")
|
||||
return out
|
||||
@@ -1606,9 +1615,10 @@ async def write_time_derive(
|
||||
named at ``path``, what the ledger already knows about that name
|
||||
elsewhere in the project —
|
||||
|
||||
family the name sits in a derive-first group (identical body in N
|
||||
files, or the same name in ≥3): "this is a known duplicate
|
||||
family with no canon — derive it now, don't add a copy";
|
||||
family the name sits in a derive-first group (code: identical body
|
||||
in N files or the same name in ≥3; CSS: the same class in
|
||||
≥2 files, note 2917): "this is a known family with no canon
|
||||
— derive it now, don't add a copy";
|
||||
canon a `canonical` row of that name at another path: "this is
|
||||
canon #N at <path> — reuse, don't redefine".
|
||||
|
||||
|
||||
Reference in New Issue
Block a user