diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index f8ac872..794ecf9 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "scribe", "description": "Scribe system-of-record for Claude Code: MCP tools over your notes/tasks/projects/rules, a session-start push channel that surfaces your always-on rules + active-project context, process-skills (writing-plans, systematic-debugging, verification, brainstorming, reusing-code), and your saved Scribe Processes auto-surfaced as skills (/scribe:sync). Replaces superpowers + file-memory with one app-backed plugin.", - "version": "0.1.44", + "version": "0.1.45", "author": { "name": "Bryan Van Deusen" }, "mcpServers": { "scribe": { diff --git a/tests/test_integration_shape_classify.py b/tests/test_integration_shape_classify.py index 12f06db..034cfdc 100644 --- a/tests/test_integration_shape_classify.py +++ b/tests/test_integration_shape_classify.py @@ -683,6 +683,58 @@ 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_transition_and_prefix_references_clear_the_unused_css_flag(seeded): + """#2970 end to end: the two class forms a template never spells out — + a transition `name=` and a concatenated name — travel from the real + extractor through resolution into the flag, so rules they reach stop + being reported as unused. Only a rule nothing can reach stays listed.""" + from scribe.services.coverage import class_references + from scribe.services.shape_ledger import sync_repo_consumers, used_by_map, live_rows + + pid, owner = seeded["pid"], seeded["owner"] + defs = _defs( + ("assets/anim.css", "css", "toast-enter-active", + ".toast-enter-active {", ".toast-enter-active { opacity: 0 }"), + ("assets/anim.css", "css", "toast-leave-to", + ".toast-leave-to {", ".toast-leave-to { opacity: 0 }"), + ("assets/state.css", "css", "status-done", + ".status-done {", ".status-done { color: green }"), + ("assets/state.css", "css", "status-todo", + ".status-todo {", ".status-todo { color: grey }"), + ("assets/state.css", "css", "really-dead", + ".really-dead {", ".really-dead { color: red }"), + ) + await sync_repo_shapes(pid, REPO, defs, seen_marker="t1") + + refs = { + # Vue applies the toast-* classes itself; the markup names none of them. + "v/Toast.vue": class_references( + "v/Toast.vue", '
  • ' + ), + # The board builds its status class; which one is unknowable. + "v/Board.vue": class_references("v/Board.vue", ''), + } + assert "toast-enter-active" in refs["v/Toast.vue"] + assert refs["v/Board.vue"] == {"status-*": 1} + + await sync_repo_consumers(pid, REPO, refs) + live = [r for r in await live_rows(pid) if r.kind == "css"] + used = await used_by_map(live) + by = {(r.path, r.symbol): used[r.id]["count"] for r in live} + assert by[("assets/anim.css", "toast-enter-active")] == 1 + assert by[("assets/anim.css", "toast-leave-to")] == 1 + # the prefix credits BOTH candidates — the template does not say which + assert by[("assets/state.css", "status-done")] == 1 + assert by[("assets/state.css", "status-todo")] == 1 + assert by[("assets/state.css", "really-dead")] == 0 + + unused, n = await list_project_shapes(owner, pid, flag="unused-css") + assert n == 1 and [(r.path, r.symbol) for r in unused] == [ + ("assets/state.css", "really-dead") + ] + + @pytest.mark.integration async def test_consumer_map_syncs_edges_from_template_references(seeded): """Milestone 302: the consumer edges follow the archive — own-file