feat(shapes): a component file is a candidate shape in its own right (milestone 439 step 5)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / integration (push) Successful in 54s
CI & Build / Build & push image (push) Canceled after 0s
CI & Build / Python tests (push) Canceled after 1m42s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / integration (push) Successful in 54s
CI & Build / Build & push image (push) Canceled after 0s
CI & Build / Python tests (push) Canceled after 1m42s
A single-file component defines `.card`, `.title` and a `Props`, never anything named after itself — so the ledger could not hold "StatusChip is canon" and could not say a new card was built where one already existed. - coverage.is_file_unit: a file that RENDERS (its markup names classes, or it opens a <script>/<template>/<style> block) and defines nothing named after its stem gets one `file` row. Structural, not a framework list: Svelte and Vue components qualify, a TSX component already has its function's row, a module is accounted for by its definitions. Emitting every file would have put every module of every project in the todo at once. - No body fingerprint on file rows, so an edit never re-asks a judgment. - `file` is its own form and family; derive grouping skips it (`index`, `+page` repeat by convention). Divergence buckets it on its own, so a new component where a component canon dominates is a fair question. - mark_canonicals: a snippet recorded at a path with no symbol makes that file row canonical; it still covers no definition inside. - The write hooks apply the same test before noting a new file for the end-of-turn question. Plugin version minted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1231,3 +1231,31 @@ async def test_unjudged_is_what_nobody_read(seeded):
|
||||
assert got[1]["evidence"]["stamped"] == {"snippet_id": sid, "score": 0.7}
|
||||
assert got[2]["evidence"] == {}
|
||||
assert await unjudged_shapes(pid, []) == []
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_a_whole_file_snippet_makes_its_file_row_canonical(seeded):
|
||||
"""Milestone 439: a snippet recorded at a component's path, with no
|
||||
symbol, is the canon for that file row — the claim a whole-file record
|
||||
makes and a definition row never could. It still covers no definition
|
||||
inside the file."""
|
||||
from scribe.services import snippets as snippets_svc
|
||||
from scribe.services.coverage import _recorded_locations
|
||||
from scribe.services.shape_ledger import mark_canonicals
|
||||
|
||||
owner, pid = seeded["owner"], seeded["pid"]
|
||||
await sync_repo_shapes(pid, REPO, SHAPES + [
|
||||
("web/lib/StatusChip.svelte", "file", "StatusChip"),
|
||||
("web/lib/StatusChip.svelte", "css", "chip"),
|
||||
], seen_marker="c1")
|
||||
chip = await snippets_svc.create_snippet(
|
||||
owner, name="StatusChip — the one status pill",
|
||||
code="<span class=\"chip\">{label}</span>", language="svelte",
|
||||
repo="Widget", path="web/lib/StatusChip.svelte", project_id=pid,
|
||||
)
|
||||
await mark_canonicals(pid, await _recorded_locations(owner, pid))
|
||||
rows, _ = await list_project_shapes(owner, pid, path="web/lib/StatusChip.svelte")
|
||||
by = {(r.kind, r.symbol): r for r in rows}
|
||||
assert by[("file", "StatusChip")].status == "canonical"
|
||||
assert by[("file", "StatusChip")].snippet_id == int(chip.id)
|
||||
assert by[("css", "chip")].status == "unclassified"
|
||||
|
||||
@@ -310,6 +310,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", "css", "card"),
|
||||
]
|
||||
# Only files whose markup names a class appear; the .py/.css files don't.
|
||||
@@ -886,3 +887,23 @@ async def test_binding_ref_is_the_branch_the_ledger_follows(seeded):
|
||||
coverage = await compute_coverage(uid, pid, selector=_selector(_tarball(TREE)))
|
||||
assert coverage["repos"][0]["ref"] == "main"
|
||||
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path,text,unit", [
|
||||
# A single-file component: nothing inside carries its name.
|
||||
("web/lib/StatusChip.svelte", "<script>\nfunction tone() {\n return 1;\n}\n</script>\n<span class=\"chip\"></span>\n", True),
|
||||
("web/views/Card.vue", "<template><div class=\"card\"/></template>\n", True),
|
||||
# A TSX component already has its row: the function named after the file.
|
||||
("web/Card.tsx", "export function Card() {\n return <div className=\"card\" />;\n}\n", False),
|
||||
# A JSX file whose markup names classes but whose component is named otherwise.
|
||||
("web/index.jsx", "function App() {\n return <div className=\"app\" />;\n}\n", True),
|
||||
# Modules render nothing: their definitions account for them.
|
||||
("internal/api/upnext.go", "func stageOf(x int) int {\n\treturn x\n}\n", False),
|
||||
("src/app/util.py", "def helper():\n pass\n", False),
|
||||
("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."""
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user