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:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "scribe",
|
"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).",
|
"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": {
|
"author": {
|
||||||
"name": "Bryan Van Deusen"
|
"name": "Bryan Van Deusen"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -152,6 +152,12 @@ Three questions the ledger answers mechanically (#2793):
|
|||||||
- **Recheck** — `list_shapes(project_id, flag="recheck")`: judged
|
- **Recheck** — `list_shapes(project_id, flag="recheck")`: judged
|
||||||
instances/variants whose body changed since judged. The judgment stands;
|
instances/variants whose body changed since judged. The judgment stands;
|
||||||
re-confirm it (classify again with the same status) or re-judge.
|
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
|
## What this buys
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
# 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
|
# stay in play for the proposer, derive grouping and divergence — the five
|
||||||
# auth views' identical rules were found exactly there. Unscoped `<style>` in
|
# 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_OPEN_RE = re.compile(r"^\s*<style\b[^>]*\bscoped\b", re.IGNORECASE)
|
||||||
_STYLE_CLOSE_RE = re.compile(r"^\s*</style\s*>", 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]]:
|
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
|
"""The (kind, name) pairs among ``defs`` that are one-offs by
|
||||||
construction in this file: every sym in a .vue, and every css rule
|
construction in this file: every sym in a .vue, and every css rule
|
||||||
that starts inside a `<style scoped>` block. Empty for other files."""
|
that starts inside a `<style scoped>` block; for a .svelte, see
|
||||||
if not (path or "").lower().endswith(".vue"):
|
`_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()
|
return set()
|
||||||
ranges: list[tuple[int, int]] = []
|
ranges: list[tuple[int, int]] = []
|
||||||
open_at: int | None = None
|
open_at: int | None = None
|
||||||
@@ -827,6 +873,15 @@ async def compute_coverage(
|
|||||||
proposals = shape_ledger.proposal_summary(rows, consumer_paths=consumer_paths)
|
proposals = shape_ledger.proposal_summary(rows, consumer_paths=consumer_paths)
|
||||||
divergence = shape_ledger.divergence_summary(rows)
|
divergence = shape_ledger.divergence_summary(rows)
|
||||||
derive_new = shape_ledger.derive_new_summary(rows, since=since)
|
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 {
|
return {
|
||||||
"total": len(rows),
|
"total": len(rows),
|
||||||
"accounted": len(rows) - unclassified,
|
"accounted": len(rows) - unclassified,
|
||||||
@@ -850,6 +905,11 @@ async def compute_coverage(
|
|||||||
"divergent": divergence["divergent"],
|
"divergent": divergence["divergent"],
|
||||||
"divergence": divergence["divergence"],
|
"divergence": divergence["divergence"],
|
||||||
"recheck": divergence["recheck"],
|
"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
|
# Honesty flag, not decoration: every surface that shows the number
|
||||||
# is expected to carry it through.
|
# is expected to carry it through.
|
||||||
"estimate": True,
|
"estimate": True,
|
||||||
@@ -1042,4 +1102,16 @@ def coverage_line(coverage: dict) -> str:
|
|||||||
line += f"; standing: {', '.join(standing)}"
|
line += f"; standing: {', '.join(standing)}"
|
||||||
if coverage.get("recheck"):
|
if coverage.get("recheck"):
|
||||||
line += f"; {coverage['recheck']} judged shape{'s' if coverage['recheck'] != 1 else ''} changed since judged — 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
|
return line
|
||||||
|
|||||||
@@ -1066,6 +1066,8 @@ def canon_form(rows: Iterable, snippet_id: int) -> str:
|
|||||||
for r in rows:
|
for r in rows:
|
||||||
if r.snippet_id != snippet_id or r.status not in ("canonical", "instance"):
|
if r.snippet_id != snippet_id or r.status not in ("canonical", "instance"):
|
||||||
continue
|
continue
|
||||||
|
if is_weak_stamp(r):
|
||||||
|
continue
|
||||||
f = shape_form(getattr(r, "signature", "") or "", r.kind)
|
f = shape_form(getattr(r, "signature", "") or "", r.kind)
|
||||||
if f:
|
if f:
|
||||||
forms[f] = forms.get(f, 0) + 1
|
forms[f] = forms.get(f, 0) + 1
|
||||||
@@ -2190,6 +2192,25 @@ async def confirm_proposals(
|
|||||||
# suggestion, not the same.
|
# suggestion, not the same.
|
||||||
_RESEMBLE_MIN = 0.80
|
_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_MIN_JUDGED = 3
|
||||||
_DENSITY_SHARE = 0.6
|
_DENSITY_SHARE = 0.6
|
||||||
|
|
||||||
@@ -2229,6 +2250,11 @@ def dominant_canon(rows: Iterable[CodeShape]) -> tuple[int, int, int] | None:
|
|||||||
counts: dict[int, int] = {}
|
counts: dict[int, int] = {}
|
||||||
judged = 0
|
judged = 0
|
||||||
for r in rows:
|
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:
|
if r.status in ("canonical", "instance") and r.snippet_id is not None:
|
||||||
judged += 1
|
judged += 1
|
||||||
counts[r.snippet_id] = counts.get(r.snippet_id, 0) + 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:
|
if r.status not in _MECHANICAL_TODO:
|
||||||
continue
|
continue
|
||||||
if r.diverges_from is not None:
|
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
|
flagged += 1
|
||||||
continue
|
continue
|
||||||
if dom is None or r.created_at is None or r.created_at <= since:
|
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]
|
live = [r for r in rows if r.vanished_at is None]
|
||||||
weak = []
|
weak = []
|
||||||
for r in live:
|
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
|
continue
|
||||||
score = stamp_score(r.reason)
|
score = stamp_score(r.reason)
|
||||||
if score is None or score >= _RESEMBLE_MIN:
|
|
||||||
continue
|
|
||||||
weak.append({
|
weak.append({
|
||||||
"path": r.path, "symbol": r.symbol, "kind": r.kind,
|
"path": r.path, "symbol": r.symbol, "kind": r.kind,
|
||||||
"status": r.status, "snippet_id": r.snippet_id,
|
"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
|
# The listing below is capped; without this you cannot tell a
|
||||||
# canon with twelve strangers from one with three hundred.
|
# canon with twelve strangers from one with three hundred.
|
||||||
"stranger_count": len(coh["strangers"]),
|
"stranger_count": len(coh["strangers"]),
|
||||||
"weak_rows": sum(
|
"weak_rows": sum(1 for r in members if is_weak_stamp(r)),
|
||||||
1 for r in members
|
|
||||||
if r.classified_by in _UNATTENDED_BY
|
|
||||||
and (stamp_score(r.reason) or 1.0) < _RESEMBLE_MIN
|
|
||||||
),
|
|
||||||
# The rows that do NOT fit — not the first dozen members. The
|
# The rows that do NOT fit — not the first dozen members. The
|
||||||
# reader's question is "which ones are wrong", and a sample of
|
# reader's question is "which ones are wrong", and a sample of
|
||||||
# the majority cannot answer it.
|
# the majority cannot answer it.
|
||||||
|
|||||||
@@ -889,6 +889,84 @@ async def test_a_second_confirm_dialog_is_detected_and_named(seeded):
|
|||||||
assert total == 0
|
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
|
@pytest.mark.integration
|
||||||
async def test_a_semantic_miss_does_not_silence_the_prompt(seeded):
|
async def test_a_semantic_miss_does_not_silence_the_prompt(seeded):
|
||||||
"""#4208, reversed on measurement: a semantic miss is not evidence.
|
"""#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()
|
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
|
@pytest.mark.integration
|
||||||
async def test_binding_ref_is_the_branch_the_ledger_follows(seeded):
|
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
|
"""#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)
|
canon_coherence(rows)
|
||||||
assert [(r.path, r.symbol, r.status, r.snippet_id, r.signature)
|
assert [(r.path, r.symbol, r.status, r.snippet_id, r.signature)
|
||||||
for r in rows] == before
|
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