fix(shapes): weak stamps elect no canon; the arrival line names the review; Svelte scopes by default (#4608)
CI & Build / Python lint (push) Successful in 8s
CI & Build / Plugin hooks (push) Successful in 16s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 1m3s
CI & Build / Python tests (push) Successful in 1m44s
CI & Build / Build & push image (push) Successful in 1m5s
CI & Build / Python lint (push) Successful in 8s
CI & Build / Plugin hooks (push) Successful in 16s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 1m3s
CI & Build / Python tests (push) Successful in 1m44s
CI & Build / Build & push image (push) Successful in 1m5s
Librarian and Stash had 132 and 488 write-path stamps written at 0.68-0.72, before the 0.80 floor (#4204). Nothing re-judged them, and dominant_canon counted them, so a YAML CI snippet (#3410) "dominated" Librarian's web directory and every write there was told it diverged from it — 1408 flags on one project, 902 on the other, all skimmed past. - is_weak_stamp: one predicate for "hook-stamped below today's floor". dominant_canon and canon_form skip such rows; stamps_to_review lists them by the same predicate. No stored row changes — they stay for judgment. - flag_divergence withdraws a standing flag whose canon no longer dominates its directory. A flag is a mechanical prompt, not a judgment. - The coverage line ends "N weak stamps · M incoherent canons to judge — stamps_to_review" when either is nonzero, so a project's own session sees the queue on arrival. The shape-accounting skill says what to do with it. - scoped_definitions handles .svelte: every <style> is component-scoped unless <style global> or :global(...); instance-script syms are scoped, module-script ones stay ordinary. Plugin version minted for the skill change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -889,6 +889,84 @@ async def test_a_second_confirm_dialog_is_detected_and_named(seeded):
|
||||
assert total == 0
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_weak_stamps_elect_no_canon_and_their_flags_are_withdrawn(seeded):
|
||||
"""#4608, the poisoned-ledger case as it stood on two projects: a
|
||||
directory's "dominant canon" was elected entirely by hook stamps written
|
||||
at 0.68-0.72, before the 0.80 floor, so every new shape there was told it
|
||||
diverged from a YAML CI snippet. The same fixture as the acceptance case
|
||||
above, with one difference — the four instances are weak hook stamps
|
||||
rather than an audit — and the prompt must fall silent: at refresh (the
|
||||
standing flag is withdrawn) and at write time. The stamps themselves are
|
||||
left exactly as they were, for `stamps_to_review` to put before a judge."""
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from sqlalchemy import update
|
||||
|
||||
from scribe.services import snippets as snippets_svc
|
||||
from scribe.services.shape_ledger import (
|
||||
_RESEMBLE_REASON, flag_divergence, propose_for_repo, write_time_divergence,
|
||||
)
|
||||
|
||||
owner, pid = seeded["owner"], seeded["pid"]
|
||||
canon = await snippets_svc.create_snippet(
|
||||
owner, name="cls_confirm_factory",
|
||||
code="export async function factory(): Promise<boolean> {\n return true;\n}\n",
|
||||
language="typescript", repo="Widget",
|
||||
path="frontend/src/composables/useConfirm.ts", symbol="factory",
|
||||
project_id=pid,
|
||||
)
|
||||
sid = int(canon.id)
|
||||
comp = "frontend/src/components"
|
||||
names = ("Trash", "Delete", "Remove", "Restore")
|
||||
base = _defs(
|
||||
*[(f"{comp}/{n}.vue", "sym", f"on{n}", f"async function on{n}() {{",
|
||||
f"async function on{n}() {{\n const ok = await factory();\n if (!ok) return;\n}}")
|
||||
for n in names],
|
||||
)
|
||||
await sync_repo_shapes(pid, REPO, base, seen_marker="aaa111")
|
||||
await classify_shapes(owner, pid, [
|
||||
{"path": f"{comp}/{n}.vue", "symbol": f"on{n}", "status": "instance", "snippet_id": sid}
|
||||
for n in names
|
||||
], via="audit")
|
||||
previous = datetime.now(timezone.utc)
|
||||
later = base + _defs(
|
||||
(f"{comp}/Danger.vue", "sym", "confirmDanger", "function confirmDanger() {",
|
||||
"function confirmDanger() {\n return window.confirm('Really?');\n}"),
|
||||
)
|
||||
await sync_repo_shapes(pid, REPO, later, seen_marker="bbb222")
|
||||
with _quiet_semantic():
|
||||
await propose_for_repo(owner, pid, REPO, later)
|
||||
since = previous - timedelta(seconds=1)
|
||||
# Judged by an audit, the canon dominates and the new shape is flagged.
|
||||
assert await flag_divergence(pid, since=since) == 1
|
||||
|
||||
# Now make the four what the poisoned ledgers held: unattended stamps
|
||||
# below today's floor.
|
||||
weak = _RESEMBLE_REASON.format(sid=sid, score=0.72)
|
||||
async with async_session() as session:
|
||||
await session.execute(
|
||||
update(CodeShape)
|
||||
.where(CodeShape.project_id == pid, CodeShape.snippet_id == sid)
|
||||
.values(classified_by="hook", reason=weak)
|
||||
)
|
||||
await session.commit()
|
||||
|
||||
assert await flag_divergence(pid, since=since) == 0
|
||||
rows, total = await list_project_shapes(owner, pid, flag="divergence")
|
||||
assert total == 0, "a flag whose canon no longer dominates is withdrawn"
|
||||
assert await write_time_divergence(
|
||||
pid, f"{comp}/Danger.vue", [("sym", "confirmDanger")], stamped=[]
|
||||
) == []
|
||||
# Nothing was un-judged: the stamps stand until someone reads them.
|
||||
async with async_session() as session:
|
||||
kept = (await session.execute(
|
||||
select(CodeShape).where(CodeShape.project_id == pid, CodeShape.snippet_id == sid)
|
||||
)).scalars().all()
|
||||
assert len(kept) == 4
|
||||
assert all(r.status == "instance" and r.classified_by == "hook" for r in kept)
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_a_semantic_miss_does_not_silence_the_prompt(seeded):
|
||||
"""#4208, reversed on measurement: a semantic miss is not evidence.
|
||||
|
||||
@@ -823,6 +823,49 @@ def test_scoped_definitions_are_vue_script_setup_and_scoped_style_only():
|
||||
assert scoped_definitions("src/a.py", "def load():\n pass\n", extract_definitions("def load():\n pass\n")) == set()
|
||||
|
||||
|
||||
def test_svelte_scopes_by_default_and_its_module_script_stays_importable():
|
||||
"""#4608: Svelte scopes the other way round from Vue — every <style> is
|
||||
the component's own unless it opts out, and only <script module> /
|
||||
<script context="module"> can be imported elsewhere. Without this a
|
||||
Svelte project's every `.card` and `.title` sat in the human todo."""
|
||||
from scribe.services.coverage import extract_definitions, scoped_definitions
|
||||
svelte = (
|
||||
"<script module lang=\"ts\">\n"
|
||||
"export function stageOf(x: number) {\n return x;\n}\n"
|
||||
"</script>\n\n"
|
||||
"<script lang=\"ts\">\n"
|
||||
"function toggle() {\n open = !open;\n}\n"
|
||||
"</script>\n\n"
|
||||
"<div class=\"card\"></div>\n\n"
|
||||
"<style>\n.card {\n padding: 1rem;\n}\n:global(.toast) {\n color: red;\n}\n</style>\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 = (
|
||||
"<script context=\"module\">\nexport function shared() {\n return 1;\n}\n</script>\n"
|
||||
"<style global>\n.page {\n margin: 0;\n}\n</style>\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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user