feat(ledger): the CSS consumer map — code_shape_consumers edges (shape → file whose markup names the class, count), migration 0086, resolve_consumers (own-file row when the template defines the class, else every other definition), sync_repo_consumers rebuilt from the archive on every refresh, consumers_of; derived, so not backed up (milestone 302 step 2, #2935)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 11s
CI & Build / integration (push) Failing after 29s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Failing after 52s
CI & Build / Build & push image (push) Skipped

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-23 13:59:55 -04:00
co-authored by Claude Fable 5
parent dffbf43d84
commit ffbdf19116
8 changed files with 269 additions and 3 deletions
+43
View File
@@ -683,6 +683,49 @@ async def test_derive_new_names_the_copy_that_joined_a_family_since_the_stamp(se
assert derive_new_summary(rows, since=None)["count"] == 0
@pytest.mark.integration
async def test_consumer_map_syncs_edges_from_template_references(seeded):
"""Milestone 302: the consumer edges follow the archive — own-file
resolution for a scoped class, fan-out to the shared sheet for a class a
template does not define, counts refreshed and stale edges removed on
the next sync, and a vanished row's edges gone with it."""
from scribe.services.shape_ledger import consumers_of, live_rows, sync_repo_consumers
pid = seeded["pid"]
defs = _defs(
("v/A.vue", "css", "error-msg", ".error-msg {", ".error-msg { color: red }"),
("v/B.vue", "css", "error-msg", ".error-msg {", ".error-msg { color: blue }"),
("assets/components.css", "css", "btn-primary", ".btn-primary {", ".btn-primary { x: 1 }"),
)
await sync_repo_shapes(pid, REPO, defs, seen_marker="m1")
refs = {
"v/A.vue": {"error-msg": 2, "btn-primary": 1},
"v/B.vue": {"error-msg": 1},
"v/C.vue": {"error-msg": 1, "btn-primary": 4},
}
assert await sync_repo_consumers(pid, REPO, refs) == 5
rows = {(r.path, r.symbol): r.id for r in await live_rows(pid) if r.kind == "css"}
edges = await consumers_of(rows.values())
view = {(p, s): [(e.path, e.count) for e in edges.get(i, [])] for (p, s), i in rows.items()}
assert view[("v/A.vue", "error-msg")] == [("v/A.vue", 2)]
assert view[("v/B.vue", "error-msg")] == [("v/B.vue", 1), ("v/C.vue", 1)]
assert view[("assets/components.css", "btn-primary")] == [("v/A.vue", 1), ("v/C.vue", 4)]
assert view[("web/button.css", "btn")] == [] # the seeded row: no template names it
# The next tree: C stops using error-msg, A uses btn-primary twice now.
refs2 = {"v/A.vue": {"error-msg": 2, "btn-primary": 2}, "v/B.vue": {"error-msg": 1}}
assert await sync_repo_consumers(pid, REPO, refs2) == 3
edges = await consumers_of(rows.values())
assert [(e.path, e.count) for e in edges[rows[("v/B.vue", "error-msg")]]] == [("v/B.vue", 1)]
assert [(e.path, e.count) for e in edges[rows[("assets/components.css", "btn-primary")]]] == [("v/A.vue", 2)]
# B's rule vanishes from the tree → its edges go with the pass.
await sync_repo_shapes(pid, REPO, [d for d in defs if d[0] != "v/B.vue"], seen_marker="m2")
await sync_repo_consumers(pid, REPO, refs2)
edges = await consumers_of(rows.values())
assert rows[("v/B.vue", "error-msg")] not in edges
# --- #2793: the divergence readout against real rows -------------------------