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

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:
2026-09-30 23:27:07 -04:00
co-authored by Claude Opus 5.5
parent 3270fe90c1
commit eb5cc6d3a7
7 changed files with 291 additions and 12 deletions
+1 -1
View File
@@ -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"
},
+6
View File
@@ -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
+75 -3
View File
@@ -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 `<style>` in
# a .vue and every non-.vue file stay ordinary.
# a .vue and every file that is neither .vue nor .svelte stay ordinary.
_STYLE_OPEN_RE = re.compile(r"^\s*<style\b[^>]*\bscoped\b", re.IGNORECASE)
_STYLE_CLOSE_RE = re.compile(r"^\s*</style\s*>", re.IGNORECASE)
# Svelte scopes the other way round (#4608): every `<style>` is component-
# scoped unless it opts out — `<style global>` (svelte-preprocess) for a whole
# block, `:global(...)` for one selector. Instance `<script>` code is the
# component's own; `<script context="module">` / `<script module>` is the one
# block other files can import from, so its definitions stay ordinary.
_SVELTE_STYLE_OPEN_RE = re.compile(r"^\s*<style\b(?![^>]*\bglobal\b)[^>]*>", re.IGNORECASE)
_SVELTE_SCRIPT_OPEN_RE = re.compile(r"^\s*<script\b([^>]*)>", re.IGNORECASE)
_SVELTE_MODULE_ATTR_RE = re.compile(r"""\bcontext\s*=\s*["']module["']|\bmodule\b""", re.IGNORECASE)
_SCRIPT_CLOSE_RE = re.compile(r"</script\s*>", re.IGNORECASE)
def _svelte_scoped(text: str, defs: list[Definition]) -> set[tuple[str, str]]:
"""`scoped_definitions` for a .svelte file: syms outside a module script,
css inside a non-global `<style>` that is not itself `:global(...)`."""
lines = text.splitlines()
styles: list[tuple[int, int]] = []
modules: list[tuple[int, int]] = []
style_at: int | None = None
module_at: int | None = None
for i, ln in enumerate(lines):
if style_at is None and _SVELTE_STYLE_OPEN_RE.match(ln):
style_at = i
elif style_at is not None and _STYLE_CLOSE_RE.match(ln):
styles.append((style_at, i))
style_at = None
m = _SVELTE_SCRIPT_OPEN_RE.match(ln)
if module_at is None and m and _SVELTE_MODULE_ATTR_RE.search(m.group(1)):
module_at = i
if module_at is not None and _SCRIPT_CLOSE_RE.search(ln):
modules.append((module_at, i))
module_at = None
out: set[tuple[str, str]] = set()
for d in defs:
if d.kind == "sym":
if not any(a <= d.line <= b for a, b in modules):
out.add((d.kind, d.name))
elif any(a <= d.line <= b for a, b in styles):
line = lines[d.line] if 0 <= d.line < len(lines) else ""
if ":global" not in line:
out.add((d.kind, d.name))
return out
def scoped_definitions(path: str, text: str, defs: list[Definition]) -> set[tuple[str, str]]:
"""The (kind, name) pairs among ``defs`` that are one-offs by
construction in this file: every sym in a .vue, and every css rule
that starts inside a `<style scoped>` block. Empty for other files."""
if not (path or "").lower().endswith(".vue"):
that starts inside a `<style scoped>` block; for a .svelte, see
`_svelte_scoped`. Empty for other files."""
lowered = (path or "").lower()
if lowered.endswith(".svelte"):
return _svelte_scoped(text, defs)
if not lowered.endswith(".vue"):
return set()
ranges: list[tuple[int, int]] = []
open_at: int | None = None
@@ -827,6 +873,15 @@ async def compute_coverage(
proposals = shape_ledger.proposal_summary(rows, consumer_paths=consumer_paths)
divergence = shape_ledger.divergence_summary(rows)
derive_new = shape_ledger.derive_new_summary(rows, since=since)
# What `stamps_to_review` would list (#4608). The review tool existed and
# nobody on two projects knew to call it: 132 and 488 weak stamps sat
# electing canons for weeks. The arrival line is the one place a
# project's own session is sure to look, so the count goes there.
try:
review = shape_ledger.stamps_to_review(rows, top=0)
except Exception:
logger.warning("stamp review read failed", exc_info=True)
review = {}
return {
"total": len(rows),
"accounted": len(rows) - unclassified,
@@ -850,6 +905,11 @@ async def compute_coverage(
"divergent": divergence["divergent"],
"divergence": divergence["divergence"],
"recheck": divergence["recheck"],
# Unattended stamps below today's floor, and canons whose members no
# longer agree what they are — the judgment queue `stamps_to_review`
# holds (#4608).
"weak_stamps": review.get("weak_count", 0),
"incoherent_canons": review.get("incoherent_count", 0),
# Honesty flag, not decoration: every surface that shows the number
# is expected to carry it through.
"estimate": True,
@@ -1042,4 +1102,16 @@ def coverage_line(coverage: dict) -> str:
line += f"; standing: {', '.join(standing)}"
if coverage.get("recheck"):
line += f"; {coverage['recheck']} judged shape{'s' if coverage['recheck'] != 1 else ''} changed since judged — recheck"
# Rows the hook asserted on evidence the ledger no longer accepts (#4608).
# They no longer elect a canon, but they still claim to be instances
# until someone reads them — the tool to call is named, not implied.
review = []
if coverage.get("weak_stamps"):
n_w = coverage["weak_stamps"]
review.append(f"{n_w} weak stamp{'s' if n_w != 1 else ''}")
if coverage.get("incoherent_canons"):
n_i = coverage["incoherent_canons"]
review.append(f"{n_i} incoherent canon{'s' if n_i != 1 else ''}")
if review:
line += f"; {' · '.join(review)} to judge — stamps_to_review"
return line
+38 -8
View File
@@ -1066,6 +1066,8 @@ def canon_form(rows: Iterable, snippet_id: int) -> str:
for r in rows:
if r.snippet_id != snippet_id or r.status not in ("canonical", "instance"):
continue
if is_weak_stamp(r):
continue
f = shape_form(getattr(r, "signature", "") or "", r.kind)
if f:
forms[f] = forms.get(f, 0) + 1
@@ -2190,6 +2192,25 @@ async def confirm_proposals(
# suggestion, not the same.
_RESEMBLE_MIN = 0.80
def is_weak_stamp(row) -> bool:
"""Was this row written by the hook, unattended, on a resemblance below
the floor it would have to clear today?
ONE PREDICATE FOR "WEAK", read by the review surface that lists these rows
and by every reader that would otherwise take them as evidence. The floor
guards new writes only; the rows stamped before it (#4204) are still
stored, and until an agent re-judges them they must not speak. They are
not withdrawn here — `stamps_to_review` still lists them for judgment —
they are simply not counted, which is the module's rule for evidence it
cannot trust: quieter, never more confident (#4608).
"""
if getattr(row, "classified_by", None) not in _UNATTENDED_BY:
return False
score = stamp_score(getattr(row, "reason", None))
return score is not None and score < _RESEMBLE_MIN
_DENSITY_MIN_JUDGED = 3
_DENSITY_SHARE = 0.6
@@ -2229,6 +2250,11 @@ def dominant_canon(rows: Iterable[CodeShape]) -> tuple[int, int, int] | None:
counts: dict[int, int] = {}
judged = 0
for r in rows:
# A weak stamp is not a judgment: counted, it let a YAML CI snippet
# "dominate" a Svelte directory and every write there was told it
# diverged from it (#4608).
if is_weak_stamp(r):
continue
if r.status in ("canonical", "instance") and r.snippet_id is not None:
judged += 1
counts[r.snippet_id] = counts.get(r.snippet_id, 0) + 1
@@ -2488,6 +2514,16 @@ async def flag_divergence(project_id: int, *, since: datetime | None) -> int:
if r.status not in _MECHANICAL_TODO:
continue
if r.diverges_from is not None:
# A flag is a mechanical prompt resting on one premise —
# "this canon dominates here". When the premise no longer
# holds (the rows that elected it were weak stamps, #4608,
# or were re-judged away) the prompt is false, and a false
# prompt on every write is how the flag stopped being
# read. Withdrawing it judges nothing: the row stays
# unclassified, and a judgment is never touched here.
if dom is None or dom[0] != r.diverges_from:
r.diverges_from = None
continue
flagged += 1
continue
if dom is None or r.created_at is None or r.created_at <= since:
@@ -2565,11 +2601,9 @@ def stamps_to_review(rows: Iterable[CodeShape], *, top: int = 10) -> dict:
live = [r for r in rows if r.vanished_at is None]
weak = []
for r in live:
if r.status not in _NEEDS_TARGET or r.classified_by != "hook":
if r.status not in _NEEDS_TARGET or not is_weak_stamp(r):
continue
score = stamp_score(r.reason)
if score is None or score >= _RESEMBLE_MIN:
continue
weak.append({
"path": r.path, "symbol": r.symbol, "kind": r.kind,
"status": r.status, "snippet_id": r.snippet_id,
@@ -2602,11 +2636,7 @@ def stamps_to_review(rows: Iterable[CodeShape], *, top: int = 10) -> dict:
# The listing below is capped; without this you cannot tell a
# canon with twelve strangers from one with three hundred.
"stranger_count": len(coh["strangers"]),
"weak_rows": sum(
1 for r in members
if r.classified_by in _UNATTENDED_BY
and (stamp_score(r.reason) or 1.0) < _RESEMBLE_MIN
),
"weak_rows": sum(1 for r in members if is_weak_stamp(r)),
# The rows that do NOT fit — not the first dozen members. The
# reader's question is "which ones are wrong", and a sample of
# the majority cannot answer it.
+78
View File
@@ -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.
+43
View File
@@ -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
+50
View File
@@ -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