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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user