feat(ledger): the scoped bucket — by-construction one-offs are stamped by the sync, not judged by a person (#2869, milestone 294)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / TypeScript typecheck (push) Successful in 23s
CI & Build / integration (push) Failing after 25s
CI & Build / Python tests (push) Canceled after 51s
CI & Build / Build & push image (push) Canceled after 0s

The 2026-08 audit left 77% of Scribe's ledger `exempt`, most of it a Vue
component's scoped <style> rules and <script setup> functions — one-offs by
construction (unreachable from any other file) that add nothing when judged
one by one and bury the rows a person should look at.

- coverage: Definition carries its line; scoped_definitions() names, per
  .vue file, every sym and every css rule inside <style scoped>; ArchiveShape
  carries the flag.
- sync: such rows are stamped status=scoped / classified_by=mechanical with
  the by-construction reason (history event recorded); un-stamped back to
  unclassified if a later tree makes them ordinary; a judgment overrides.
- The machine still sees them: proposer, derive grouping, divergence, hook
  evidence, canonical stamping and classify_shapes_by_rule's default all
  treat unclassified + scoped as the unjudged set (_MECHANICAL_TODO). Only
  the human todo (status=unclassified) and largest_gaps exclude them.
- accounting counts `scoped`; coverage line and the project card legend show
  it; SHAPE_STATUSES gains it (no DB CHECK on status — no migration).
- shape-accounting skill documents the bucket; plugin 0.1.37.

Operator decision on #2869 (2026-08-21): keep extracting everything, stamp
mechanically, keep `exempt` a human judgment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-21 15:06:52 -04:00
co-authored by Claude Fable 5
parent 9abc4443fb
commit 1ab614bfbe
10 changed files with 172 additions and 26 deletions
+32
View File
@@ -169,6 +169,38 @@ async def test_rule_form_sweeps_unclassified_rows_only_and_applies_whole(seeded)
await classify_shapes_where(other, pid, path="src", status="exempt", reason="x")
@pytest.mark.integration
async def test_sync_stamps_scoped_rows_and_unstamps_when_they_become_reachable(seeded):
"""#2869: by-construction one-offs arrive `scoped` (mechanical), count as
accounted, are reached by the sweep's default, and go back to the todo
if a later tree makes them ordinary. A judgment overrides the stamp."""
from scribe.services.coverage import ArchiveShape
owner, pid, sid = seeded["owner"], seeded["pid"], seeded["snippet"]
scoped_shapes = [
ArchiveShape("web/Card.vue", "css", "card", ".card {", "s1", ".card { x: 1 }", True),
ArchiveShape("web/Card.vue", "sym", "load", "function load() {", "s2", "function load() {}", True),
ArchiveShape("src/util.py", "sym", "helper", "def helper():", "s3", "def helper(): pass", False),
]
await sync_repo_shapes(pid, REPO, scoped_shapes, seen_marker="main")
rows, _ = await list_project_shapes(owner, pid, path="web/Card.vue")
assert {r.status for r in rows} == {"scoped"}
assert all(r.classified_by == "mechanical" and "by construction" in (r.reason or "") for r in rows)
# The human todo excludes them; the sweep's default still reaches them.
assert (await list_project_shapes(owner, pid, status="unclassified", path="web/Card.vue"))[1] == 0
out = await classify_shapes_where(
owner, pid, path="web/Card.vue", status="instance", snippet_id=sid, kind="css",
)
assert out["classified"] == 1
# Re-synced as ordinary: the stamped sym returns to the todo; the
# judged css keeps its judgment.
plain = [ArchiveShape(s.path, s.kind, s.name, s.signature, s.body_sha, s.body, False) for s in scoped_shapes]
await sync_repo_shapes(pid, REPO, plain, seen_marker="main")
rows, _ = await list_project_shapes(owner, pid, path="web/Card.vue")
by_symbol = {r.symbol: r for r in rows}
assert by_symbol["load"].status == "unclassified" and by_symbol["load"].classified_by is None
assert by_symbol["card"].status == "instance" and by_symbol["card"].snippet_id == sid
@pytest.mark.integration
async def test_list_filters_compose(seeded):
owner, pid, sid = seeded["owner"], seeded["pid"], seeded["snippet"]
+29 -1
View File
@@ -258,7 +258,7 @@ async def test_coverage_measures_the_tree_exactly_and_caches(seeded):
assert coverage["accounted"] == 2
assert coverage["unclassified"] == 2
assert coverage["counts"] == {
"canonical": 2, "instance": 0, "variant": 0, "exempt": 0,
"canonical": 2, "instance": 0, "variant": 0, "exempt": 0, "scoped": 0,
}
assert coverage["estimate"] is True
assert coverage["repos"] == [{
@@ -500,3 +500,31 @@ def test_coverage_line_names_divergence_and_recheck():
assert line.endswith("; 1 judged shape changed since judged — recheck")
assert "DIVERGENT" not in coverage_line(base)
assert "recheck" not in coverage_line(base)
def test_scoped_definitions_are_vue_script_setup_and_scoped_style_only():
"""#2869: one-offs by construction — every sym in a .vue and every css
rule inside <style scoped>; an unscoped <style> block and non-.vue files
stay ordinary."""
from scribe.services.coverage import extract_definitions, scoped_definitions
vue = (
"<script setup lang=\"ts\">\n"
"function load() {\n return 1;\n}\n"
"const save = async () => {\n return 2;\n};\n"
"</script>\n\n"
"<template><div class=\"card\"/></template>\n\n"
"<style scoped>\n.card {\n padding: 1rem;\n}\n.title {\n margin: 0;\n}\n</style>\n"
"<style>\n.global-toast {\n color: red;\n}\n</style>\n"
)
defs = extract_definitions(vue)
names = {(d.kind, d.name) for d in defs}
assert {("sym", "load"), ("sym", "save"), ("css", "card"), ("css", "title"), ("css", "global-toast")} <= names
scoped = scoped_definitions("frontend/src/views/A.vue", vue, defs)
assert scoped == {("sym", "load"), ("sym", "save"), ("css", "card"), ("css", "title")}
# Definitions know their line, which is what the scoped-style range uses.
assert next(d for d in defs if d.name == "card").line > next(d for d in defs if d.name == "save").line
# Not a .vue: nothing is scoped, whatever it contains.
assert scoped_definitions("frontend/src/assets/components.css", ".card {\n x: 1;\n}\n",
extract_definitions(".card {\n x: 1;\n}\n")) == set()
assert scoped_definitions("src/a.py", "def load():\n pass\n", extract_definitions("def load():\n pass\n")) == set()
+1 -1
View File
@@ -30,7 +30,7 @@ def test_the_todo_state_is_the_default():
assert CodeShape.__table__.c.status.default.arg == "unclassified"
assert "unclassified" in SHAPE_STATUSES
assert set(SHAPE_STATUSES) == {
"canonical", "instance", "variant", "exempt", "unclassified",
"canonical", "instance", "variant", "exempt", "scoped", "unclassified",
}
assert set(SHAPE_CLASSIFIERS) == {
"agent", "audit", "hook", "mechanical", "import",