diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index 1d30bdb..a33665e 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "scribe", "description": "Scribe for Claude Code: connects the scribe MCP server, adds the hooks that deliver live project state and relevant records at the right moment, ships the shared client-neutral Scribe skills (using-scribe, writing-plans, reporting-back, systematic-debugging, verification, brainstorming, reusing-code, shape-accounting), and syncs your saved Scribe Processes as skills (/scribe:sync).", - "version": "2026.09.24.1358", + "version": "2026.10.01.0326", "author": { "name": "Bryan Van Deusen" }, diff --git a/plugin/skills/shape-accounting/SKILL.md b/plugin/skills/shape-accounting/SKILL.md index c1e7b1c..740ebe7 100644 --- a/plugin/skills/shape-accounting/SKILL.md +++ b/plugin/skills/shape-accounting/SKILL.md @@ -152,6 +152,12 @@ Three questions the ledger answers mechanically (#2793): - **Recheck** — `list_shapes(project_id, flag="recheck")`: judged instances/variants whose body changed since judged. The judgment stands; re-confirm it (classify again with the same status) or re-judge. +- **Weak stamps** — the coverage line's "N weak stamps … to judge — + stamps_to_review": rows the write-path hook asserted, unattended, on a + resemblance below today's floor. They no longer elect a directory's canon + or raise a divergence, but they still claim to be instances. Read each + one and record the verdict with `classify_shapes` — `instance` if it is, + otherwise what it actually is. ## What this buys diff --git a/src/scribe/services/coverage.py b/src/scribe/services/coverage.py index d104d45..49e5914 100644 --- a/src/scribe/services/coverage.py +++ b/src/scribe/services/coverage.py @@ -369,16 +369,62 @@ def extract_definitions(text: str) -> list[Definition]: # the human todo holds only shapes a person should look at, while the bodies # stay in play for the proposer, derive grouping and divergence — the five # auth views' identical rules were found exactly there. Unscoped `\n" + ) + defs = extract_definitions(svelte) + names = {(d.kind, d.name) for d in defs} + assert {("sym", "stageOf"), ("sym", "toggle"), ("css", "card")} <= names + scoped = scoped_definitions("web/src/lib/A.svelte", svelte, defs) + assert ("sym", "toggle") in scoped and ("css", "card") in scoped + assert ("sym", "stageOf") not in scoped, "a module script's export is importable" + assert not any(n == "toast" for _, n in scoped), ":global(...) opts a selector out" + # The legacy spelling of the module block, and a whole global style block. + legacy = ( + "\n" + "\n" + ) + assert scoped_definitions("web/src/lib/B.svelte", legacy, extract_definitions(legacy)) == set() + + +def test_the_line_names_the_review_queue_and_the_tool_that_holds_it(): + """#4608: `stamps_to_review` existed and two projects never called it, + because nothing on arrival said there was anything to review.""" + from scribe.services.coverage import coverage_line + base = {"accounted": 10, "total": 10, "counts": {"exempt": 10}, + "unclassified": 0, "computed_at": "2026-09-30T00:00:00"} + assert "stamps_to_review" not in coverage_line(base) + line = coverage_line({**base, "weak_stamps": 132, "incoherent_canons": 3}) + assert line.endswith("; 132 weak stamps · 3 incoherent canons to judge — stamps_to_review") + assert coverage_line({**base, "weak_stamps": 1}).endswith("; 1 weak stamp to judge — stamps_to_review") + + @pytest.mark.integration async def test_binding_ref_is_the_branch_the_ledger_follows(seeded): """#2873: a binding that names a ref is read at that ref (not the forge's diff --git a/tests/test_stamps_to_review.py b/tests/test_stamps_to_review.py index ac839b6..b4d7066 100644 --- a/tests/test_stamps_to_review.py +++ b/tests/test_stamps_to_review.py @@ -350,3 +350,53 @@ def test_the_coherence_verdict_reads_rows_and_writes_nothing() -> None: canon_coherence(rows) assert [(r.path, r.symbol, r.status, r.snippet_id, r.signature) for r in rows] == before + + +# ── a weak stamp is listed for judgment and counted by nobody (#4608) ───── +# +# The floor guarded new writes; the rows written before it went on electing +# canons. On two projects a YAML CI snippet "dominated" a Svelte directory on +# the strength of 0.68-0.72 stamps, and every write there was told it +# diverged from it. The readers below must agree with the review surface +# about what "weak" means — one predicate, so they cannot drift apart. + +def test_one_predicate_says_what_weak_is() -> None: + from scribe.services.shape_ledger import is_weak_stamp + + assert is_weak_stamp(_stamped(0.72)) + assert not is_weak_stamp(_stamped(_RESEMBLE_MIN)) + # A by-name stamp carries no score: it is not resemblance evidence at all. + assert not is_weak_stamp(_Row(reason="hook: pulled #1; payload references `f`")) + for by in ("agent", "audit", "mechanical"): + assert not is_weak_stamp(_stamped(0.5, classified_by=by)), by + + +def test_weak_stamps_do_not_elect_a_dominant_canon() -> None: + from scribe.services.shape_ledger import dominant_canon + + weak = [_stamped(0.72, path=f"web/src/lib/{i}.ts", symbol=f"f{i}") for i in range(5)] + assert dominant_canon(weak) is None + # The same rows judged by an agent do — the kind of evidence is the point. + judged = [_stamped(0.72, path=f"web/src/lib/{i}.ts", symbol=f"f{i}", classified_by="agent") + for i in range(5)] + assert dominant_canon(judged) == (1, 5, 5) + # And weak rows do not dilute a real majority either: they are not counted + # as judged at all. + mixed = judged[:3] + [_stamped(0.7, snippet_id=2, symbol=f"g{i}") for i in range(4)] + assert dominant_canon(mixed) == (1, 3, 3) + + +def test_weak_stamps_do_not_say_what_form_a_canon_is() -> None: + from scribe.services.shape_ledger import FORM_UNKNOWN, canon_form + + weak = [_stamped(0.7, symbol=f"T{i}", signature=f"class T{i}:") for i in range(5)] + assert canon_form(weak, 1) == FORM_UNKNOWN + judged = [_stamped(0.7, symbol=f"T{i}", signature=f"class T{i}:", classified_by="audit") + for i in range(5)] + assert canon_form(judged + weak, 1) == canon_form(judged, 1) != FORM_UNKNOWN + + +def test_counting_them_out_leaves_them_listed() -> None: + """Not counted is not withdrawn: the review still lists every one.""" + rows = [_stamped(0.7, symbol=f"f{i}") for i in range(3)] + assert stamps_to_review(rows)["weak_count"] == 3