docs(6209): coverage, guidance-ownership, shape, warnings tests and shared fixtures
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / integration (push) Successful in 1m12s
CI & Build / Python tests (push) Successful in 1m56s
CI & Build / Build & push image (push) Successful in 13s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / integration (push) Successful in 1m12s
CI & Build / Python tests (push) Successful in 1m56s
CI & Build / Build & push image (push) Successful in 13s
test_pattern_coverage, test_guidance_ownership, test_integration_shape_classify, test_instruction_surfaces_agree, test_retrieval_warnings, helpers.py and conftest.py state the property each test or fixture holds. The issue and milestone numbers, consolidation dates and one-install measurements that recorded how each was found are dropped; fixture dates and ids stay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""Pattern-library coverage (#2692) — the extractor that mirrors the hook,
|
||||
"""Pattern-library coverage — the extractor that mirrors the hook,
|
||||
the shape/record matcher, and the end-to-end measurement on real Postgres.
|
||||
|
||||
The extractor here and the hook's awk program (scribe_prior_art.sh ARM 1)
|
||||
@@ -66,11 +66,9 @@ EXTRACTION_VECTORS = [
|
||||
[]),
|
||||
("dedup-within-file", "def f():\n pass\ndef f():\n pass\n",
|
||||
[("sym", "f")]),
|
||||
# --- comment and string spans (#4222) ------------------------------------
|
||||
# --- comment and string spans --------------------------------------------
|
||||
# Prose is not code. A wrapped docstring line beginning "class AND the"
|
||||
# announced a shape called `AND` to a live session; `with` and `nobody`
|
||||
# out of one module docstring in scripts/check_dangling_styles.py reached
|
||||
# persisted, judged `code_shapes` rows.
|
||||
# would otherwise announce a shape called `AND`.
|
||||
("docstring-prose",
|
||||
'def real_one():\n """Its own text is about the\n'
|
||||
' class AND the to_dict, and is a def bar():\n'
|
||||
@@ -81,18 +79,15 @@ EXTRACTION_VECTORS = [
|
||||
("unterminated-docstring",
|
||||
'def before():\n """oops, never closed\n\ndef after():\n pass\n',
|
||||
[("sym", "before"), ("sym", "after")]),
|
||||
# The CSS half of the same defect (#2990): a wrapped comment line that
|
||||
# The CSS half of the same rule: a wrapped comment line that
|
||||
# happens to begin with a dotted token reads as a selector.
|
||||
("css-comment-selector",
|
||||
"/* A real base rule, not just descendants: the check reads a\n"
|
||||
" .ghost, class that only ever appears as an ancestor */\n.check { }\n",
|
||||
[("css", "check")]),
|
||||
# The rest of #2990's verify list, which this scan satisfies as written:
|
||||
# a comment holding a WHOLE rule defines nothing, and an unterminated
|
||||
# comment does not swallow the file. The phantom it was filed for —
|
||||
# `.editor-body` out of a wrapped comment in TaskEditorView.vue, a
|
||||
# persisted row with a `used_by` count of zero inflating unused_css — is
|
||||
# among the twenty-two this removes.
|
||||
# A comment holding a WHOLE rule defines nothing, and an unterminated
|
||||
# comment does not swallow the file. A phantom class out of a comment
|
||||
# would persist with a `used_by` count of zero and inflate unused_css.
|
||||
("comment-containing-a-whole-rule",
|
||||
"/* .ghost {\n color: red;\n } */\n.real { color: blue; }\n",
|
||||
[("css", "real")]),
|
||||
@@ -195,7 +190,7 @@ def test_shapes_from_archive_strips_the_wrapper_and_gates_files():
|
||||
assert shapes_from_archive(_tarball(TREE)) == TREE_SHAPES
|
||||
|
||||
|
||||
# --- unit: template class references — the CSS consumer map (milestone 302) --
|
||||
# --- unit: template class references — the CSS consumer map ----------------
|
||||
|
||||
|
||||
def test_class_references_reads_vue_static_and_dynamic_forms_only():
|
||||
@@ -229,7 +224,7 @@ def test_class_references_reads_vue_static_and_dynamic_forms_only():
|
||||
def test_class_references_reads_framework_transition_names():
|
||||
"""A transition `name=` is a class reference: the framework applies
|
||||
`.toast-enter-active` and friends at runtime, so a stylesheet that
|
||||
defines them is consumed even though no template spells one out (#2970).
|
||||
defines them is consumed even though no template spells one out.
|
||||
Every spelling of the tag counts; a bound `:name` stays unknowable."""
|
||||
refs = class_references(
|
||||
"a/T.vue",
|
||||
@@ -251,7 +246,7 @@ def test_class_references_reads_framework_transition_names():
|
||||
def test_class_references_reads_a_concatenated_name_as_a_prefix():
|
||||
"""`status-${s}` cannot be resolved to one class, but its static head is
|
||||
real information: it is emitted as the prefix reference `status-*` so
|
||||
the rows it could have built are not reported unused (#2970). A head too
|
||||
the rows it could have built are not reported unused. A head too
|
||||
short to mean anything, or a bare separator, says nothing."""
|
||||
vue = (
|
||||
'<div :class="`status-${task.status}`" />'
|
||||
@@ -301,7 +296,7 @@ def test_scan_archive_returns_definitions_and_references_from_one_walk():
|
||||
scan = scan_archive(_tarball(tree))
|
||||
assert isinstance(scan, ArchiveScan)
|
||||
assert [(d.path, d.kind, d.name) for d in scan.definitions] == TREE_SHAPES + [
|
||||
("web/Card.vue", "file", "Card"), # a component is a unit (milestone 439)
|
||||
("web/Card.vue", "file", "Card"), # a component is a unit
|
||||
("web/Card.vue", "css", "card"),
|
||||
]
|
||||
# Only files whose markup names a class appear; the .py/.css files don't.
|
||||
@@ -309,7 +304,7 @@ def test_scan_archive_returns_definitions_and_references_from_one_walk():
|
||||
assert shapes_from_archive(_tarball(tree)) == [(d.path, d.kind, d.name) for d in scan.definitions]
|
||||
|
||||
|
||||
# --- unit: the covering predicate (lives with the ledger since #2788) --------
|
||||
# --- unit: the covering predicate (lives with the ledger) --------------------
|
||||
|
||||
|
||||
def test_location_covers_by_exact_path_dir_prefix_and_css_dot():
|
||||
@@ -341,7 +336,7 @@ def test_largest_gaps_ranks_by_unclassified_and_drops_clean_dirs():
|
||||
|
||||
|
||||
def test_type_import_specifiers_are_not_definitions():
|
||||
"""#2904: `import { type Foo, bar }` is the same two words as `type Foo =`
|
||||
"""`import { type Foo, bar }` is the same two words as `type Foo =`
|
||||
and defines nothing; only a `type` line with a declaration after the
|
||||
name counts (TS alias, Go/Rust type)."""
|
||||
from scribe.services.coverage import extract_shapes
|
||||
@@ -375,7 +370,7 @@ def test_coverage_line_is_evidence_carrying_and_labeled_estimate():
|
||||
|
||||
|
||||
def test_bind_repo_tool_takes_a_ref():
|
||||
"""#2873: the binding names the branch the ledger follows."""
|
||||
"""The binding names the branch the ledger follows."""
|
||||
from scribe.mcp.server import build_mcp_server
|
||||
tool = build_mcp_server()._tool_manager.get_tool("bind_repo")
|
||||
assert "ref" in tool.parameters.get("properties", {})
|
||||
@@ -412,7 +407,7 @@ def _forge(tar_bytes: bytes):
|
||||
|
||||
|
||||
def _selector(tar_bytes: bytes):
|
||||
"""The keyring shape compute_coverage consumes since #2778 — one owner
|
||||
"""The keyring shape compute_coverage consumes — one owner
|
||||
keyring holding the mocked Gitea adapter."""
|
||||
from scribe.services.forge import ForgeSelector
|
||||
|
||||
@@ -482,10 +477,10 @@ async def test_coverage_measures_the_tree_exactly_and_caches(seeded):
|
||||
assert coverage["largest_gaps"] == [
|
||||
{"dir": "src", "unclassified": 2, "total": 3}
|
||||
]
|
||||
# #2899: a first computation has no previous stamp — nothing is "new".
|
||||
# A first computation has no previous stamp — nothing is "new".
|
||||
assert coverage["derive_new"] == {"count": 0, "examples": []}
|
||||
|
||||
# The walk fed the LEDGER (#2788): every extracted shape has a row, the
|
||||
# The walk fed the LEDGER: every extracted shape has a row, the
|
||||
# snippet reference locations are mechanically stamped canonical WITH
|
||||
# their snippet id, and the rest sit in the todo state.
|
||||
async with async_session() as s:
|
||||
@@ -516,7 +511,7 @@ async def test_coverage_measures_the_tree_exactly_and_caches(seeded):
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_ledger_keeps_judgments_and_stamps_vanished_shapes(seeded):
|
||||
"""The two survival rules (#2788): an agent's classification outlives
|
||||
"""The two survival rules: an agent's classification outlives
|
||||
recompute, and a shape that leaves the tree is stamped vanished — kept
|
||||
for history, dropped from the readout."""
|
||||
from datetime import datetime, timezone
|
||||
@@ -593,9 +588,8 @@ async def test_enter_project_surfaces_the_line_only_once_computed(seeded):
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_explicit_refresh_names_its_failures(seeded):
|
||||
"""refresh_for_caller (#2802) raises fixable errors instead of silence:
|
||||
an agent mid-task must learn WHY nothing measured — 'None' is exactly the
|
||||
stranding the button-only path caused."""
|
||||
"""refresh_for_caller raises fixable errors instead of silence: an agent
|
||||
mid-task must learn WHY nothing measured, and 'None' would strand it."""
|
||||
from scribe.models import async_session
|
||||
from scribe.services.coverage import refresh_for_caller
|
||||
|
||||
@@ -617,8 +611,8 @@ async def test_explicit_refresh_names_its_failures(seeded):
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_background_seed_is_quiet_without_a_forge(seeded):
|
||||
"""refresh_if_stale (#2802) must exit silently for a forge-less owner —
|
||||
rule #115's baseline — and treat a fresh cache as nothing-to-do."""
|
||||
"""refresh_if_stale must exit silently for a forge-less owner — the
|
||||
baseline install — and treat a fresh cache as nothing-to-do."""
|
||||
from scribe.services.coverage import refresh_coverage, refresh_if_stale
|
||||
|
||||
uid, pid = seeded["uid"], seeded["pid"]
|
||||
@@ -651,7 +645,7 @@ async def test_unservable_binding_measures_nothing(seeded):
|
||||
assert await compute_coverage(uid, other_pid, selector=_selector(_tarball(TREE))) is None
|
||||
|
||||
|
||||
# --- #2792: fingerprints and the proposer's readout --------------------------
|
||||
# --- fingerprints and the proposer's readout --------------------------------
|
||||
|
||||
|
||||
def test_extract_definitions_fingerprints_each_block():
|
||||
@@ -684,14 +678,14 @@ def test_extract_definitions_fingerprints_each_block():
|
||||
# And the identity view is unchanged for the hook mirror.
|
||||
from scribe.services.coverage import extract_shapes
|
||||
assert extract_shapes(text) == [(d.kind, d.name) for d in extract_definitions(text)]
|
||||
# A CSS rule's fingerprint is its declarations (#2872): the same body
|
||||
# A CSS rule's fingerprint is its declarations: the same body
|
||||
# under another selector is the same shape to the derive grouping.
|
||||
css = ".closed-msg {\n text-align: center;\n padding: 0.5rem 0;\n}\n.error-block {\n text-align: center;\n padding: 0.5rem 0;\n}\n.other {\n text-align: left;\n}\n"
|
||||
d = {x.name: x for x in extract_definitions(css)}
|
||||
assert d["closed-msg"].body_sha == d["error-block"].body_sha != d["other"].body_sha
|
||||
# One-line rules hash their own declarations — never the empty string
|
||||
# (first deploy grouped 68 unrelated one-liners as one copy) — and a
|
||||
# SINGLE declaration is not a shape (#2903): it keeps its selector in the
|
||||
# One-line rules hash their own declarations — never the empty string,
|
||||
# which would group every unrelated one-liner as one copy — and a
|
||||
# SINGLE declaration is not a shape: it keeps its selector in the
|
||||
# hash, so `.a { color: red }` groups only with another `.a`, never with
|
||||
# `.b { color: red }`. Two declarations and up stay selector-agnostic.
|
||||
one = ".a { color: red; }\n\n.b { color: red; }\n\n.c { color: blue; }\n\n.a {\n color: red;\n}\n"
|
||||
@@ -718,7 +712,7 @@ def test_coverage_line_names_the_proposers_standing():
|
||||
assert "; 90 unclassified (40 proposed, 2 derive groups), largest: src" in line
|
||||
line = coverage_line({**base, "proposed": 0, "derive_groups": [{"group": "a"}]})
|
||||
assert "(1 derive group)" in line
|
||||
# Milestone 302: a css top copy says what renders it; unused classes
|
||||
# A css top copy says what renders it; unused classes
|
||||
# join the standing block only when measured (None = no evidence).
|
||||
line = coverage_line({**base, "unclassified": 0, "proposed": 0, "derive_groups": [
|
||||
{"group": "name:css:error-msg", "label": ".error-msg", "files": 6,
|
||||
@@ -729,7 +723,7 @@ def test_coverage_line_names_the_proposers_standing():
|
||||
{"group": "name:css:x", "label": ".x", "files": 2,
|
||||
"consumers": {"count": 1, "paths": ["a.vue"]}}], "unused_css": None})
|
||||
assert "top copy .x ×2 files · used by 1 template" in line and "unused" not in line
|
||||
# #2874: the next action on the line — biggest canon queue, widest copy.
|
||||
# The next action on the line — biggest canon queue, widest copy.
|
||||
line = coverage_line({
|
||||
**base, "proposed": 40, "top_canon": {"snippet_id": 2844, "count": 78},
|
||||
"derive_groups": [{"group": "dup:abc", "label": "closed-msg (identical body)", "files": 3}],
|
||||
@@ -738,7 +732,7 @@ def test_coverage_line_names_the_proposers_standing():
|
||||
|
||||
|
||||
def test_coverage_line_shows_standing_work_even_with_nothing_unclassified():
|
||||
"""#2899: since the scoped bucket a ledger can be fully accounted and
|
||||
"""A ledger can be fully accounted and
|
||||
still carry derive groups / proposals / divergence — the line names
|
||||
them as `standing:` instead of hiding them behind the todo count, and
|
||||
names the drift since the previous refresh first-copy-first."""
|
||||
@@ -789,7 +783,7 @@ def test_coverage_line_names_divergence_and_recheck():
|
||||
|
||||
|
||||
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
|
||||
"""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
|
||||
@@ -816,10 +810,10 @@ def test_scoped_definitions_are_vue_script_setup_and_scoped_style_only():
|
||||
|
||||
|
||||
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> /
|
||||
"""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."""
|
||||
Svelte project's every `.card` and `.title` sits in the human todo."""
|
||||
from scribe.services.coverage import extract_definitions, scoped_definitions
|
||||
svelte = (
|
||||
"<script module lang=\"ts\">\n"
|
||||
@@ -847,8 +841,8 @@ def test_svelte_scopes_by_default_and_its_module_script_stays_importable():
|
||||
|
||||
|
||||
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."""
|
||||
"""`stamps_to_review` goes uncalled unless something on arrival says
|
||||
there is 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"}
|
||||
@@ -860,7 +854,7 @@ def test_the_line_names_the_review_queue_and_the_tool_that_holds_it():
|
||||
|
||||
@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
|
||||
"""A binding that names a ref is read at that ref (not the forge's
|
||||
default branch); "" clears it; None on a re-bind leaves it standing."""
|
||||
from scribe.services.coverage import compute_coverage
|
||||
from scribe.services.repo_bindings import bindings_for_project, set_binding
|
||||
@@ -894,14 +888,14 @@ async def test_binding_ref_is_the_branch_the_ledger_follows(seeded):
|
||||
("web/button.css", ".btn {\n color: red;\n}\n", False),
|
||||
])
|
||||
def test_a_file_is_a_unit_when_it_renders_and_nothing_inside_carries_its_name(path, text, unit):
|
||||
"""Milestone 439: one structural test, not a framework list."""
|
||||
"""One structural test, not a framework list."""
|
||||
from scribe.services.coverage import class_references, extract_definitions, is_file_unit
|
||||
|
||||
assert is_file_unit(path, text, extract_definitions(text), class_references(path, text)) is unit
|
||||
|
||||
|
||||
def test_the_line_says_whether_writers_judged_what_they_wrote():
|
||||
"""Milestone 439: the write-time figure — silent until the question has
|
||||
"""The write-time figure — silent until the question has
|
||||
been put, then turns checked, asked, and asks walked past."""
|
||||
from scribe.services.coverage import coverage_line
|
||||
base = {"accounted": 10, "total": 10, "counts": {"exempt": 10},
|
||||
|
||||
Reference in New Issue
Block a user