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:
@@ -597,6 +597,42 @@ def scannable(path: str) -> bool:
|
||||
return not path.lower().endswith(_SKIP_SUFFIXES)
|
||||
|
||||
|
||||
# --- the file as a unit (milestone 439) ---------------------------------------
|
||||
#
|
||||
# A component is the thing people reuse in a UI, and in a single-file
|
||||
# component nothing inside is named after it: CatalogueBookCard.svelte defines
|
||||
# `.card`, `.title` and a `Props`, never `CatalogueBookCard`. A ledger of
|
||||
# definitions alone therefore could never say "a new card was built where
|
||||
# CatalogueBookCard is canon". So such a file gets a row of its own.
|
||||
#
|
||||
# WHICH FILES, decided by structure rather than by a framework list: a file
|
||||
# that RENDERS — its markup names classes, or it opens with a <script>,
|
||||
# <template> or <style> block — and that DEFINES NOTHING NAMED AFTER ITSELF.
|
||||
# A TSX `function CatalogueBookCard` already has its row; a module of helpers
|
||||
# is accounted for by its helpers. Emitting a row for every file would put
|
||||
# every module in every project into the todo at once, which is a flood, not
|
||||
# a question. No body fingerprint either: every edit would otherwise ask for
|
||||
# the file's judgment to be re-confirmed.
|
||||
_RENDER_BLOCK_RE = re.compile(r"^\s*<(?:script|template|style)\b", re.IGNORECASE | re.MULTILINE)
|
||||
|
||||
|
||||
def file_stem(path: str) -> str:
|
||||
"""`web/src/lib/StatusChip.svelte` → `StatusChip`."""
|
||||
return path.rsplit("/", 1)[-1].rsplit(".", 1)[0]
|
||||
|
||||
|
||||
def is_file_unit(
|
||||
path: str, text: str, defs: list[Definition], refs: dict[str, int] | None = None,
|
||||
) -> bool:
|
||||
"""Is this file a candidate shape in its own right? See the block above."""
|
||||
stem = file_stem(path)
|
||||
if not stem:
|
||||
return False
|
||||
if any(d.name == stem for d in defs if d.kind == "sym"):
|
||||
return False
|
||||
return bool(refs) or _RENDER_BLOCK_RE.search(text) is not None
|
||||
|
||||
|
||||
class ArchiveShape(NamedTuple):
|
||||
"""A definition located in a repo archive — what the sync upserts and
|
||||
the proposer matches. The leading (path, kind, name) triple is the
|
||||
@@ -658,6 +694,11 @@ def scan_archive(blob: bytes) -> ArchiveScan:
|
||||
continue
|
||||
defs = extract_definitions(text)
|
||||
scoped = scoped_definitions(path, text, defs)
|
||||
refs = class_references(path, text)
|
||||
if is_file_unit(path, text, defs, refs):
|
||||
shapes.append(ArchiveShape(
|
||||
path, "file", file_stem(path), f"file {path}", "", "",
|
||||
))
|
||||
shapes.extend(
|
||||
ArchiveShape(
|
||||
path, d.kind, d.name, d.signature, d.body_sha, d.body,
|
||||
@@ -665,7 +706,6 @@ def scan_archive(blob: bytes) -> ArchiveScan:
|
||||
)
|
||||
for d in defs
|
||||
)
|
||||
refs = class_references(path, text)
|
||||
if refs:
|
||||
references[path] = refs
|
||||
return ArchiveScan(shapes, references)
|
||||
|
||||
Reference in New Issue
Block a user