feat(coverage): the line names standing work with 0 unclassified + derive_new, copies that joined a family since the previous refresh (#2899, milestone 299 step 1)
CI & Build / TypeScript typecheck (push) Successful in 40s
CI & Build / Python lint (push) Successful in 6s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Successful in 33s
CI & Build / Python tests (push) Successful in 1m7s
CI & Build / Build & push image (push) Successful in 24s
CI & Build / TypeScript typecheck (push) Successful in 40s
CI & Build / Python lint (push) Successful in 6s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Successful in 33s
CI & Build / Python tests (push) Successful in 1m7s
CI & Build / Build & push image (push) Successful in 24s
Since the scoped bucket (#2869) the ledger reads 100% accounted while 439 derive rows stand; the standing block was gated on unclassified > 0 and so went silent. Build it whatever the todo count ("; standing: ..."), and add derive_new — derive-grouped rows first seen after the previous refresh stamp — so entering a project names the drift ("+2 new copies since last refresh: .error-msg in InceptionCard.vue") instead of waiting for an audit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -462,15 +462,20 @@ async def compute_coverage(
|
||||
await shape_ledger.apply_derive_groups(project_id)
|
||||
except Exception:
|
||||
logger.warning("derive-first grouping failed", exc_info=True)
|
||||
# The button-B pass (#2793): shapes new since the PREVIOUS computation,
|
||||
# where a canon dominates. The previous computation's stamp is the cache;
|
||||
# a first seed has none, so it flags nothing (everything is new then).
|
||||
# "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).
|
||||
since = None
|
||||
try:
|
||||
previous = await get_setting(user_id, f"{_CACHE_KEY_PREFIX}{project_id}")
|
||||
since = None
|
||||
if previous:
|
||||
stamp = (json.loads(previous) or {}).get("computed_at")
|
||||
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,
|
||||
# where a canon dominates.
|
||||
try:
|
||||
await shape_ledger.flag_divergence(project_id, since=since)
|
||||
except Exception:
|
||||
logger.warning("divergence pass failed", exc_info=True)
|
||||
@@ -491,6 +496,7 @@ async def compute_coverage(
|
||||
unclassified = counts.pop("unclassified")
|
||||
proposals = shape_ledger.proposal_summary(rows)
|
||||
divergence = shape_ledger.divergence_summary(rows)
|
||||
derive_new = shape_ledger.derive_new_summary(rows, since=since)
|
||||
return {
|
||||
"total": len(rows),
|
||||
"accounted": len(rows) - unclassified,
|
||||
@@ -501,6 +507,10 @@ async def compute_coverage(
|
||||
"proposed": proposals["proposed"],
|
||||
"derive_groups": proposals["derive_groups"],
|
||||
"top_canon": proposals.get("top_canon"),
|
||||
# Drift since the previous refresh (#2899): 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,
|
||||
"proposer": proposer_stats,
|
||||
# The divergence readout (#2793): button B where button A is canon,
|
||||
# and judged shapes whose bodies moved since they were judged.
|
||||
@@ -648,29 +658,46 @@ 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.
|
||||
standing = []
|
||||
if coverage.get("proposed"):
|
||||
standing.append(f"{coverage['proposed']} proposed")
|
||||
n_groups = len(coverage.get("derive_groups") or [])
|
||||
if n_groups:
|
||||
standing.append(f"{n_groups} derive group{'s' if n_groups != 1 else ''}")
|
||||
# Drift since the previous refresh: copies that joined a family, the
|
||||
# first one named — the sentence the arrival moment exists to say.
|
||||
new = coverage.get("derive_new") or {}
|
||||
if new.get("count"):
|
||||
n = new["count"]
|
||||
first_new = (new.get("examples") or [{}])[0]
|
||||
where = (
|
||||
f": {first_new['label']} in {first_new['path']}"
|
||||
if first_new.get("label") and first_new.get("path") else ""
|
||||
)
|
||||
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
|
||||
# queue to confirm, and the widest body-identical copy to consolidate.
|
||||
top = coverage.get("top_canon") or {}
|
||||
if top.get("snippet_id"):
|
||||
standing.append(f"top canon #{top['snippet_id']} ×{top.get('count', 0)}")
|
||||
first = (coverage.get("derive_groups") or [{}])[0]
|
||||
if first.get("label") and first.get("files"):
|
||||
standing.append(f"top copy {first['label']} ×{first['files']} files")
|
||||
if unclassified:
|
||||
line += f"; {unclassified} unclassified"
|
||||
standing = []
|
||||
if coverage.get("proposed"):
|
||||
standing.append(f"{coverage['proposed']} proposed")
|
||||
n_groups = len(coverage.get("derive_groups") or [])
|
||||
if n_groups:
|
||||
standing.append(f"{n_groups} derive group{'s' if n_groups != 1 else ''}")
|
||||
if coverage.get("divergent"):
|
||||
standing.append(f"{coverage['divergent']} DIVERGENT")
|
||||
# The next action, on the line (#2874): 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"):
|
||||
standing.append(f"top canon #{top['snippet_id']} ×{top.get('count', 0)}")
|
||||
first = (coverage.get("derive_groups") or [{}])[0]
|
||||
if first.get("label") and first.get("files"):
|
||||
standing.append(f"top copy {first['label']} ×{first['files']} files")
|
||||
if standing:
|
||||
line += f" ({', '.join(standing)})"
|
||||
gaps = [g["dir"] for g in coverage.get("largest_gaps") or []]
|
||||
if gaps:
|
||||
line += ", largest: " + ", ".join(gaps)
|
||||
elif standing:
|
||||
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"
|
||||
return line
|
||||
|
||||
@@ -1404,6 +1404,34 @@ def proposal_summary(rows: Iterable[CodeShape], *, top: int = 8) -> dict:
|
||||
return {"proposed": proposed, "derive_groups": ranked[:top], "top_canon": top_canon}
|
||||
|
||||
|
||||
def derive_new_summary(
|
||||
rows: Iterable[CodeShape], *, since: datetime | None, top: int = 3
|
||||
) -> dict:
|
||||
"""The arrival-moment drift signal (#2899): derive-grouped rows FIRST
|
||||
SEEN after ``since`` — the previous refresh's stamp, the same one
|
||||
flag_divergence uses. "Since the last refresh, N more copies joined a
|
||||
duplicate family" is the sentence that makes the derive queue a thing
|
||||
you notice on entering, not a thing an audit finds. ``since`` None (a
|
||||
first seed) means nothing is new. Judged rows never count."""
|
||||
if since is None:
|
||||
return {"count": 0, "examples": []}
|
||||
fresh = [
|
||||
r for r in rows
|
||||
if r.proposal_basis == "derive" and r.proposal_group
|
||||
and r.status in _MECHANICAL_TODO and r.vanished_at is None
|
||||
and r.created_at is not None and r.created_at > since
|
||||
]
|
||||
fresh.sort(key=lambda r: r.created_at, reverse=True)
|
||||
return {
|
||||
"count": len(fresh),
|
||||
"examples": [
|
||||
{"label": ("." if r.kind == "css" else "") + r.symbol,
|
||||
"path": r.path, "group": r.proposal_group}
|
||||
for r in fresh[:top]
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
async def confirm_proposals(
|
||||
user_id: int,
|
||||
project_id: int,
|
||||
|
||||
Reference in New Issue
Block a user