docs(6205): coverage comments state the reason, not the instance that taught it
Issue, note and milestone citations are removed along with first-deploy counts and the measured phantom-definition story; "button B" jargon becomes the divergence it names. Each block keeps its present-tense reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
"""Pattern-library coverage — what fraction of a bound repo's shapes have a
|
||||
recorded snippet (#2692, forge job 3 of decision #2686).
|
||||
recorded snippet.
|
||||
|
||||
The all-shapes doctrine says every shape gets recorded at first build. This
|
||||
module is the hoping→knowing move: it enumerates the definitions that exist in
|
||||
@@ -43,9 +43,9 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# Cache key in the settings KV, on the project OWNER's user_id — the same
|
||||
# channel the scheduler's last-run summary uses for machine-written state.
|
||||
# v2 suffix with #2788: the payload shape inverted (accounted/unclassified);
|
||||
# pre-ledger blobs under the old key simply stop being found, so the card
|
||||
# honestly reads "not measured yet" until the first ledger-era refresh.
|
||||
# The v2 suffix marks the accounted/unclassified payload shape; a blob under
|
||||
# an older key is simply not found, so the card honestly reads "not measured
|
||||
# yet" until the first refresh writes the current shape.
|
||||
_CACHE_KEY_PREFIX = "pattern_coverage_v2_"
|
||||
|
||||
# Files whose content can't hold definitions — the hook's skip list, verbatim,
|
||||
@@ -93,18 +93,15 @@ _ARROW_RE = re.compile(
|
||||
)
|
||||
|
||||
|
||||
# --- comment and string spans (#4222) ----------------------------------------
|
||||
# --- comment and string spans -----------------------------------------------
|
||||
#
|
||||
# The matchers above are line-oriented and know nothing about what a line is
|
||||
# INSIDE. A wrapped docstring whose line happens to begin "class AND the …"
|
||||
# reads as a definition of a shape called `AND`, and that phantom reaches the
|
||||
# agent mid-edit as a divergence prompt about a symbol that does not exist.
|
||||
# This repo's own source minted twenty-two of them, measured by running the
|
||||
# extractor over every scannable file with and without this scan. Two —
|
||||
# `with` and `nobody`, out of one module docstring in
|
||||
# scripts/check_dangling_styles.py — are persisted, JUDGED `code_shapes`
|
||||
# rows, so the cost was never only noise in the moment. Those clear
|
||||
# themselves: sync_shapes marks a row it no longer extracts as vanished.
|
||||
# agent mid-edit as a divergence prompt about a symbol that does not exist —
|
||||
# and, once judged, persists as a `code_shapes` row. Masking comment and
|
||||
# string spans stops the phantoms; any already stored clear themselves, since
|
||||
# sync_shapes marks a row it no longer extracts as vanished.
|
||||
#
|
||||
# The honest tool for .py would be `ast`, which cannot be fooled by prose at
|
||||
# all. It is not what this uses, because this extractor is mirrored rule for
|
||||
@@ -221,7 +218,7 @@ def _masked_lines(text: str, count: int) -> list[str]:
|
||||
|
||||
|
||||
class Definition(NamedTuple):
|
||||
"""One extracted definition with its content fingerprint (#2792).
|
||||
"""One extracted definition with its content fingerprint.
|
||||
|
||||
`signature` is the definition line itself; `body_sha` hashes the block
|
||||
whitespace- and comment-insensitively; `body` is the block's text, held
|
||||
@@ -234,7 +231,7 @@ class Definition(NamedTuple):
|
||||
signature: str
|
||||
body_sha: str
|
||||
body: str
|
||||
line: int = -1 # 0-based line the definition starts on (#2869)
|
||||
line: int = -1 # 0-based line the definition starts on
|
||||
|
||||
|
||||
def _definition_on(raw: str) -> tuple[str, str] | None:
|
||||
@@ -253,7 +250,7 @@ def _definition_on(raw: str) -> tuple[str, str] | None:
|
||||
# `type` announces a definition only when something is declared after
|
||||
# the name (`type Foo = …`, `type Foo struct {`); an import specifier
|
||||
# (`import { type Foo, bar }`) is the same two words and defines
|
||||
# nothing — it showed up as a two-file "identical body" family (#2904).
|
||||
# nothing, and would read as a two-file "identical body" family.
|
||||
if line.startswith("type") and not re.search(r"[={]", line[m.end():]):
|
||||
return None
|
||||
return ("sym", name)
|
||||
@@ -325,10 +322,10 @@ def extract_definitions(text: str) -> list[Definition]:
|
||||
end = j
|
||||
break
|
||||
block = lines[i:end]
|
||||
# A CSS rule's fingerprint is its DECLARATIONS, not its selector
|
||||
# (#2872): the row's identity already carries the selector. Since
|
||||
# note 2917 the derive grouping no longer reads CSS bodies at all (a
|
||||
# class is grouped by name only), so for CSS the fingerprint is the
|
||||
# A CSS rule's fingerprint is its DECLARATIONS, not its selector:
|
||||
# the row's identity already carries the selector. The derive
|
||||
# grouping does not read CSS bodies at all (a class is grouped by
|
||||
# name only), so for CSS the fingerprint is the
|
||||
# recheck identity — "did this rule's body change since it was
|
||||
# judged?" — and nothing more. The shape of the hash is kept as-is on
|
||||
# purpose: changing it would flip every judged CSS row to recheck on
|
||||
@@ -336,20 +333,20 @@ def extract_definitions(text: str) -> list[Definition]:
|
||||
if kind == "css":
|
||||
# One-line rules (`.x { color: red; }`) carry their declarations on
|
||||
# the selector line itself; a block that is only the selector plus
|
||||
# trailing blanks must not hash to the empty string (which grouped
|
||||
# 68 unrelated one-liners as one "copy" on first deploy, #2872).
|
||||
# trailing blanks must not hash to the empty string, which would
|
||||
# group every unrelated one-liner as one "copy".
|
||||
first = lines[i]
|
||||
brace = first.find("{")
|
||||
head = [first[brace + 1:]] if brace >= 0 and first[brace + 1:].strip() else []
|
||||
hashed = head + block[1:]
|
||||
if not any(x.strip() for x in hashed):
|
||||
hashed = block
|
||||
# A SINGLE declaration is not a shape (#2903): `color: var(--fs-
|
||||
# A SINGLE declaration is not a shape: `color: var(--fs-
|
||||
# text-tertiary)` under .text-muted, .task-mark and .pin-badge-auto
|
||||
# is three meanings sharing one line, not three copies of one
|
||||
# rule. Keep the selector in the hash for one-liners; two
|
||||
# declarations and up stay selector-agnostic. (Moot for grouping
|
||||
# since note 2917, kept for fingerprint stability — see above.)
|
||||
# declarations and up stay selector-agnostic. (Moot for grouping,
|
||||
# kept for fingerprint stability — see above.)
|
||||
elif _declaration_count(hashed) < 2:
|
||||
hashed = block
|
||||
else:
|
||||
@@ -361,19 +358,20 @@ def extract_definitions(text: str) -> list[Definition]:
|
||||
return out
|
||||
|
||||
|
||||
# --- by-construction scope (#2869) -------------------------------------------
|
||||
# --- by-construction scope ---------------------------------------------------
|
||||
#
|
||||
# A Vue single-file component's `<style scoped>` rules and its `<script setup>`
|
||||
# functions cannot be reached from any other file: they are one-offs by
|
||||
# construction, not by judgment. The sync stamps them `scoped` (mechanical) so
|
||||
# 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
|
||||
# stay in play for the proposer, derive grouping and divergence — identical
|
||||
# rules copied across sibling components are found exactly there. Unscoped
|
||||
# `<style>` in
|
||||
# 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-
|
||||
# Svelte scopes the other way round: 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
|
||||
@@ -443,12 +441,12 @@ def scoped_definitions(path: str, text: str, defs: list[Definition]) -> set[tupl
|
||||
return out
|
||||
|
||||
|
||||
# --- template class references: the CSS consumer map (milestone 302) ---------
|
||||
# --- template class references: the CSS consumer map -------------------------
|
||||
|
||||
# Files whose MARKUP can consume a class. Styling consumers are templates —
|
||||
# `querySelector('.x')` / classList in scripts are deliberately not read in
|
||||
# v1 (note 2917: watch CSS by name, by recipe, by token and by what uses it;
|
||||
# "what uses it" is the template).
|
||||
# `querySelector('.x')` / classList in scripts are deliberately not read: CSS
|
||||
# is watched by name, by recipe, by token and by what uses it, and "what uses
|
||||
# it" is the template.
|
||||
_TEMPLATE_SUFFIXES = (
|
||||
".vue", ".html", ".htm", ".jsx", ".tsx", ".js", ".ts", ".svelte", ".astro",
|
||||
)
|
||||
@@ -473,7 +471,7 @@ _SVELTE_CLASS_RE = re.compile(r"(?<![:\w.-])class:([A-Za-z_][A-Za-z0-9_-]*)\s*="
|
||||
# runtime, and React's <CSSTransition classNames="fade"> does the same. A
|
||||
# reader of `class=` attributes alone therefore calls every one of those
|
||||
# rules unused, which is a false positive no amount of care in the
|
||||
# stylesheet can avoid (#2970). A dynamic `:name="…"` stays unknowable.
|
||||
# stylesheet can avoid. A dynamic `:name="…"` stays unknowable.
|
||||
_TRANSITION_NAME_RE = re.compile(
|
||||
r"""<\s*[Tt]ransition(?:-[Gg]roup|Group)?\b[^>]*?(?<![:\w.-])name\s*=\s*"""
|
||||
r"""(?:"([^"]*)"|'([^']*)')"""
|
||||
@@ -552,8 +550,8 @@ def class_references(path: str, text: str) -> dict[str, int]:
|
||||
`class:x` directive; never a CSS selector (`.x {` is a definition, read
|
||||
by extract_definitions) and never a script's `querySelector('.x')`.
|
||||
|
||||
Two forms name classes without spelling them out, and both are read
|
||||
(#2970): a transition `name=` stands for every class the framework
|
||||
Two forms name classes without spelling them out, and both are read:
|
||||
a transition `name=` stands for every class the framework
|
||||
generates from it, and a concatenated name contributes the prefix
|
||||
reference `head-*` — which resolve_consumers matches against every row
|
||||
whose symbol starts with `head-`."""
|
||||
@@ -597,7 +595,7 @@ def scannable(path: str) -> bool:
|
||||
return not path.lower().endswith(_SKIP_SUFFIXES)
|
||||
|
||||
|
||||
# --- the file as a unit (milestone 439) ---------------------------------------
|
||||
# --- the file as a unit ------------------------------------------------------
|
||||
#
|
||||
# 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
|
||||
@@ -644,7 +642,7 @@ class ArchiveShape(NamedTuple):
|
||||
signature: str
|
||||
body_sha: str
|
||||
body: str
|
||||
scoped: bool = False # one-off by construction (#2869)
|
||||
scoped: bool = False # one-off by construction
|
||||
|
||||
|
||||
def shapes_from_archive(blob: bytes) -> list[tuple[str, str, str]]:
|
||||
@@ -656,11 +654,11 @@ def shapes_from_archive(blob: bytes) -> list[tuple[str, str, str]]:
|
||||
class ArchiveScan(NamedTuple):
|
||||
"""One walk of a repo tarball: what each file DEFINES (the ledger rows)
|
||||
and which class names each file's markup REFERENCES (the CSS consumer
|
||||
map, milestone 302) — read together because the bodies are in hand once."""
|
||||
map) — read together because the bodies are in hand once."""
|
||||
|
||||
definitions: list[ArchiveShape]
|
||||
references: dict[str, dict[str, int]] # path → class token → count
|
||||
# EVERY file's repo-relative path, scannable or not (milestone 463). The
|
||||
# EVERY file's repo-relative path, scannable or not. The
|
||||
# platform markers are files this scan otherwise skips — go.mod,
|
||||
# AndroidManifest.xml, a Dockerfile — so detection reads the names here
|
||||
# rather than re-walking the tarball.
|
||||
@@ -722,9 +720,9 @@ def scan_archive(blob: bytes) -> ArchiveScan:
|
||||
# --- matching shapes against recorded locations ------------------------------
|
||||
|
||||
|
||||
# The covering predicate (location_covers) lives in services/shape_ledger.py
|
||||
# since #2788 — the ledger's canonical marking and this module's readout are
|
||||
# two consumers of ONE doctrine, and the ledger is its home.
|
||||
# The covering predicate (location_covers) lives in services/shape_ledger.py:
|
||||
# the ledger's canonical marking and this module's readout are two consumers
|
||||
# of ONE doctrine, and the ledger is its home.
|
||||
|
||||
|
||||
def largest_gaps(
|
||||
@@ -756,7 +754,7 @@ async def _recorded_locations(
|
||||
user_id: int, project_id: int
|
||||
) -> list[tuple[int, str, str]]:
|
||||
"""(snippet_note_id, path, symbol) for every location of every live
|
||||
snippet in a project — the canonical-marking input (#2788): the id is what
|
||||
snippet in a project — the canonical-marking input: the id is what
|
||||
lets a ledger row point back at the snippet it references."""
|
||||
from sqlalchemy import select
|
||||
|
||||
@@ -788,15 +786,14 @@ async def compute_coverage(
|
||||
) -> dict | None:
|
||||
"""Sync the shape ledger from the bound repos and read the accounting.
|
||||
|
||||
Since #2788 this is the ledger's sync point, not just a measurement:
|
||||
every walk upserts the extracted shapes (new → unclassified, vanished →
|
||||
stamped), re-stamps snippet reference locations as canonical, and then
|
||||
reports the ACCOUNTING — how many shapes carry a classification at all —
|
||||
rather than the old "has a snippet" fraction. Unclassified is the todo
|
||||
(note 2786).
|
||||
This is the ledger's sync point, not just a measurement: every walk
|
||||
upserts the extracted shapes (new → unclassified, vanished → stamped),
|
||||
re-stamps snippet reference locations as canonical, and then reports the
|
||||
ACCOUNTING — how many shapes carry a classification at all — rather than
|
||||
a "has a snippet" fraction. Unclassified is the todo.
|
||||
|
||||
None means "nothing to measure" — the owner's keyring serves none of the
|
||||
project's bound repos (#2778). That is the ordinary state for a
|
||||
project's bound repos. That is the ordinary state for a
|
||||
forge-less user and every caller treats it as silence, not failure; the
|
||||
ledger is untouched in that case. Forge errors (unreachable, bad token)
|
||||
RAISE — the two callers are a refresh button and a background task, and
|
||||
@@ -818,7 +815,7 @@ async def compute_coverage(
|
||||
tree_paths: list[str] = []
|
||||
recorded = await _recorded_locations(user_id, project_id)
|
||||
# The proposer's canon catalog, read once per refresh and shared across
|
||||
# the project's repos (#2792).
|
||||
# the project's repos.
|
||||
canons = None
|
||||
proposer_stats = {"examined": 0, "proposed": 0, "semantic_checked": 0}
|
||||
for binding in await bindings_for_project(user_id, project_id):
|
||||
@@ -827,8 +824,8 @@ async def compute_coverage(
|
||||
if hit is None:
|
||||
continue # bound to a host no connection serves
|
||||
forge, api_repo = hit
|
||||
# The binding's own ref when it names one (#2873: a dev-first project
|
||||
# has its ledger follow dev), else the forge's default branch.
|
||||
# The binding's own ref when it names one (a dev-first project has
|
||||
# its ledger follow dev), else the forge's default branch.
|
||||
ref = binding.ref or await forge.default_branch(api_repo)
|
||||
scan = scan_archive(await forge.archive(api_repo, ref))
|
||||
definitions = scan.definitions
|
||||
@@ -844,7 +841,7 @@ async def compute_coverage(
|
||||
project_id, key, definitions, seen_marker=marker
|
||||
)
|
||||
served.append((key, ref))
|
||||
# The CSS consumer map (milestone 302) rides the same archive: which
|
||||
# The CSS consumer map rides the same archive: which
|
||||
# files' markup names each class. Mechanical and recomputable, so it
|
||||
# must not be able to fail the refresh either.
|
||||
try:
|
||||
@@ -868,7 +865,7 @@ async def compute_coverage(
|
||||
if not served:
|
||||
return None
|
||||
|
||||
# Platform detection (milestone 463) rides the same walk: the paths are in
|
||||
# Platform detection rides the same walk: the paths are in
|
||||
# hand once. It only ever ADDS membership the project has no answer for,
|
||||
# and it must not be able to fail the refresh it rides on.
|
||||
try:
|
||||
@@ -879,7 +876,7 @@ async def compute_coverage(
|
||||
logger.warning("platform detection failed for project %s", project_id, exc_info=True)
|
||||
|
||||
await shape_ledger.mark_canonicals(project_id, recorded)
|
||||
# The adoption ledger follows the shapes (milestone 463 step 5). The
|
||||
# The adoption ledger follows the shapes. The
|
||||
# judge paths sync the ideas they touch; the refresh is the one place
|
||||
# that sees canonical stamps land and shapes vanish, so it answers every
|
||||
# canon idea reaching the project. It must not fail the refresh.
|
||||
@@ -895,7 +892,7 @@ async def compute_coverage(
|
||||
logger.warning("derive-first grouping failed", exc_info=True)
|
||||
# "Since the previous computation" — the cache's stamp. A first seed has
|
||||
# none, so nothing is new then. Read once; two passes use it: the
|
||||
# button-B flag (#2793) and the derive-new drift count (#2899).
|
||||
# divergence flag and the derive-new drift count.
|
||||
since = None
|
||||
try:
|
||||
previous = await get_setting(user_id, f"{_CACHE_KEY_PREFIX}{project_id}")
|
||||
@@ -904,7 +901,7 @@ async def compute_coverage(
|
||||
since = datetime.fromisoformat(stamp) if stamp else None
|
||||
except Exception:
|
||||
logger.warning("previous coverage stamp unreadable", exc_info=True)
|
||||
# The button-B pass (#2793): shapes new since the PREVIOUS computation,
|
||||
# The divergence pass: shapes new since the PREVIOUS computation,
|
||||
# where a canon dominates.
|
||||
try:
|
||||
await shape_ledger.flag_divergence(project_id, since=since)
|
||||
@@ -925,7 +922,7 @@ async def compute_coverage(
|
||||
agg["accounted"] += row.status != "unclassified"
|
||||
|
||||
unclassified = counts.pop("unclassified")
|
||||
# The CSS consumer map's readout (milestone 302): which files render each
|
||||
# The CSS consumer map's readout: which files render each
|
||||
# css row — on the derive groups (a shared recipe vs a scoped one is a
|
||||
# count), and the negative space: css rules no template names. "Unused"
|
||||
# is measured only where the map has evidence of templates at all (one
|
||||
@@ -944,16 +941,16 @@ 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.
|
||||
# What `stamps_to_review` would list. A review tool nobody knows to call
|
||||
# leaves weak stamps electing canons indefinitely; 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 = {}
|
||||
# The write-time figure (milestone 439): how often the end-of-turn
|
||||
# The write-time figure: how often the end-of-turn
|
||||
# question was put on this project, and how often it was walked past.
|
||||
# An audit reads the ledger; this reads whether the reflex held.
|
||||
try:
|
||||
@@ -968,27 +965,28 @@ async def compute_coverage(
|
||||
"accounted": len(rows) - unclassified,
|
||||
"unclassified": unclassified,
|
||||
"counts": counts,
|
||||
# The proposer's standing (#2792): canon proposals awaiting a
|
||||
# The proposer's standing: canon proposals awaiting a
|
||||
# confirm, the largest derive-first groups, and what this refresh did.
|
||||
"proposed": proposals["proposed"],
|
||||
"derive_groups": proposals["derive_groups"],
|
||||
"top_canon": proposals.get("top_canon"),
|
||||
# Drift since the previous refresh (#2899): copies that joined a
|
||||
# Drift since the previous refresh: copies that joined a
|
||||
# duplicate family — what the arrival line names so drift is noticed
|
||||
# on entering, not found by an audit.
|
||||
"derive_new": derive_new,
|
||||
# The consumer map's negative space (milestone 302): live css rules
|
||||
# The consumer map's negative space: live css rules
|
||||
# no template names — None when the map has no evidence of templates.
|
||||
"unused_css": unused_css,
|
||||
"proposer": proposer_stats,
|
||||
# The divergence readout (#2793): button B where button A is canon,
|
||||
# and judged shapes whose bodies moved since they were judged.
|
||||
# The divergence readout: new shapes where a different canon
|
||||
# dominates, and judged shapes whose bodies moved since they were
|
||||
# judged.
|
||||
"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).
|
||||
# holds.
|
||||
"weak_stamps": review.get("weak_count", 0),
|
||||
"incoherent_canons": review.get("incoherent_count", 0),
|
||||
# {checked, asked, left, days} from the Stop hook's recorded outcomes.
|
||||
@@ -1021,9 +1019,9 @@ async def refresh_coverage(
|
||||
return coverage
|
||||
|
||||
|
||||
# The arrival-moment self-seed (#2802). A ledger that has never been computed
|
||||
# used to wait for someone to find the UI Refresh button; entering the project
|
||||
# is the moment the number is wanted, so entering is the moment it seeds.
|
||||
# The arrival-moment self-seed. A ledger that has never been computed should
|
||||
# not wait for someone to find the UI Refresh button; entering the project is
|
||||
# the moment the number is wanted, so entering is the moment it seeds.
|
||||
_SEED_MAX_AGE = timedelta(hours=24)
|
||||
|
||||
# Projects with a refresh already running — concurrent enters must not fetch
|
||||
@@ -1056,7 +1054,7 @@ async def refresh_if_stale(
|
||||
return
|
||||
selector = await get_forges(user_id, project_id)
|
||||
if not selector.configured:
|
||||
return # rule #115: forge-less stays exactly as it was
|
||||
return # forge-less stays exactly as it was
|
||||
_inflight_seed.add(project_id)
|
||||
try:
|
||||
await refresh_coverage(user_id, project_id, selector=selector)
|
||||
@@ -1070,7 +1068,7 @@ async def refresh_if_stale(
|
||||
|
||||
|
||||
async def refresh_for_caller(caller_id: int, project_id: int) -> dict:
|
||||
"""The explicit agent-facing refresh (#2802) — synchronous, named errors.
|
||||
"""The explicit agent-facing refresh — synchronous, named errors.
|
||||
|
||||
Write-gated: recomputing spends the owner's forge API budget and rewrites
|
||||
ledger rows' seen-markers, so a read share doesn't grant it. Resolution
|
||||
@@ -1134,10 +1132,10 @@ def coverage_line(coverage: dict) -> str:
|
||||
line += f" — {breakdown}"
|
||||
line += f" (estimate{', computed ' + day if day else ''})"
|
||||
unclassified = coverage.get("unclassified", 0)
|
||||
# The standing work, built whatever the todo count (#2899). Since the
|
||||
# scoped bucket (#2869) a ledger can read 100% accounted and still carry
|
||||
# derive groups, proposals and divergence; gating this block on
|
||||
# `unclassified > 0` is how 439 derive rows went unmentioned.
|
||||
# The standing work, built whatever the todo count. With the scoped
|
||||
# bucket a ledger can read 100% accounted and still carry derive groups,
|
||||
# proposals and divergence; gating this block on `unclassified > 0` would
|
||||
# leave them unmentioned.
|
||||
standing = []
|
||||
if coverage.get("proposed"):
|
||||
standing.append(f"{coverage['proposed']} proposed")
|
||||
@@ -1157,7 +1155,7 @@ def coverage_line(coverage: dict) -> str:
|
||||
standing.append(f"+{n} new cop{'y' if n == 1 else 'ies'} since last refresh{where}")
|
||||
if coverage.get("divergent"):
|
||||
standing.append(f"{coverage['divergent']} DIVERGENT")
|
||||
# The next action, on the line (#2874): the canon with the biggest
|
||||
# The next action, on the line: the canon with the biggest
|
||||
# queue to confirm, and the widest body-identical copy to consolidate.
|
||||
top = coverage.get("top_canon") or {}
|
||||
if top.get("snippet_id"):
|
||||
@@ -1165,7 +1163,7 @@ def coverage_line(coverage: dict) -> str:
|
||||
first = (coverage.get("derive_groups") or [{}])[0]
|
||||
if first.get("label") and first.get("files"):
|
||||
top_copy = f"top copy {first['label']} ×{first['files']} files"
|
||||
# A css family says what renders it (milestone 302): the count that
|
||||
# A css family says what renders it: the count that
|
||||
# tells a shared recipe from a scoped convention.
|
||||
if "consumers" in first:
|
||||
n_t = (first.get("consumers") or {}).get("count", 0)
|
||||
@@ -1185,8 +1183,8 @@ 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
|
||||
# Rows the hook asserted on evidence below the ledger's floor. They do
|
||||
# not 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"):
|
||||
@@ -1197,7 +1195,7 @@ def coverage_line(coverage: dict) -> str:
|
||||
review.append(f"{n_i} incoherent canon{'s' if n_i != 1 else ''}")
|
||||
if review:
|
||||
line += f"; {' · '.join(review)} to judge — stamps_to_review"
|
||||
# Whether the writers judged what they wrote (milestone 439). Silent until
|
||||
# Whether the writers judged what they wrote. Silent until
|
||||
# the question has been put at least once: a project nobody has written to
|
||||
# through the plugin has no figure, not a perfect one.
|
||||
wt = coverage.get("write_time") or {}
|
||||
|
||||
Reference in New Issue
Block a user