mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 22:07:15 +01:00
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzxdWWcbzoov1KFZJSFcSs
151 lines
5.5 KiB
Python
151 lines
5.5 KiB
Python
"""`leetcode due`, `leetcode --cluster <name>` and `leetcode --retest <slug>`.
|
|
|
|
DOCS-coach-spec.md decisions 10 and 12, on top of `progress` (which derives
|
|
everything from the session logs):
|
|
|
|
* ``due`` lists the cold re-tests that are due (+3 and +10 days after a solve).
|
|
* ``--cluster <name>`` serves that cluster's due re-test first, else its next
|
|
problem with no ``code`` event, skipping problems LeetCode already reports
|
|
Accepted. The live check fails OPEN: if LeetCode cannot be asked, the
|
|
problem is served rather than skipped.
|
|
* ``--retest <slug>`` starts a cold session: a fresh ``claude -p`` session
|
|
with no history, whose opening tells the coach it is a re-test judged in
|
|
``node`` and that kuhy must NOT submit -- a resubmission is a new
|
|
submission id, and the harvester mints a gate credit for every one.
|
|
A ``retest`` event is logged only if the session reached a post-mortem, so
|
|
quitting early does not burn the re-test.
|
|
|
|
``--dry-run`` says what would be served without starting a session. The
|
|
cluster name reaches the session writer through ``$LEETCODE_COACH_CLUSTER``.
|
|
Its prints are its interface (T201 exemption in pyproject.toml).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
import logging
|
|
import os
|
|
import sys
|
|
|
|
from leetcode_coach import how_to, progress
|
|
from leetcode_guard._live_solved import check_solved
|
|
from leetcode_guard._settings import build_client
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
COMMANDS = frozenset({"due", "--cluster", "--retest"})
|
|
USAGE = (
|
|
"usage: leetcode due | --cluster <name> [--dry-run] | --retest <slug> [--dry-run]"
|
|
)
|
|
STAGES = len(progress.RETEST_OFFSETS)
|
|
COLD_RETEST_TAG = "[cold re-test]"
|
|
COLD_RETEST = (
|
|
f"{COLD_RETEST_TAG} kuhy solved this problem before. This is a cold "
|
|
"re-test: fresh session, no hints or code from last time, requirement "
|
|
"list first as usual. Judge the solution by running the statement's "
|
|
"examples locally with node, not on LeetCode. Tell kuhy: do NOT submit "
|
|
"on LeetCode, a resubmission mints a gate credit."
|
|
)
|
|
|
|
Start = Callable[[list[str]], int]
|
|
|
|
|
|
def accepted_on_leetcode(slug: str) -> bool:
|
|
"""Whether LeetCode confirms an Accepted submission; False if it cannot say."""
|
|
try:
|
|
evidence = check_solved(build_client().post, [slug])
|
|
except Exception: # pylint: disable=broad-exception-caught
|
|
_logger.exception("live solved-check for %s raised; serving it", slug)
|
|
return False
|
|
return slug in evidence.solved
|
|
|
|
|
|
def _due() -> int:
|
|
rows = progress.due_retests()
|
|
if not rows:
|
|
print("no re-tests due")
|
|
return 0
|
|
for row in rows:
|
|
command = how_to.retest_command(row["slug"])
|
|
print(
|
|
f"{row['due']} {row['slug']} re-test {row['stage']}/{STAGES} "
|
|
f"({row['cluster'] or 'no cluster'}) -> {command}"
|
|
)
|
|
return 0
|
|
|
|
|
|
def _retest(slug: str, start: Start, *, dry: bool) -> int:
|
|
past = progress.history(slug)
|
|
if past.solved is None:
|
|
print(
|
|
f"{slug} is not solved yet (no code + post-mortem logged)", file=sys.stderr
|
|
)
|
|
return 1
|
|
if past.retests >= STAGES:
|
|
print(f"both re-tests of {slug} are done; practise it with `leetcode {slug}`")
|
|
return 0
|
|
stage = past.retests + 1
|
|
print(f"cold re-test {stage}/{STAGES}: {slug} (solved {past.solved})")
|
|
if dry:
|
|
return 0
|
|
cluster = progress.cluster_of(slug)
|
|
if cluster is None:
|
|
os.environ.pop(progress.CLUSTER_ENV, None)
|
|
else:
|
|
os.environ[progress.CLUSTER_ENV] = cluster
|
|
before = progress.count_events(slug, "postmortem")
|
|
code = start([slug, COLD_RETEST])
|
|
if progress.count_events(slug, "postmortem") > before:
|
|
event = {"event": "retest", "stage": stage, "cluster": cluster}
|
|
progress.append_event(slug, event)
|
|
print(f"re-test {stage}/{STAGES} logged for {slug}")
|
|
else:
|
|
print(
|
|
f"re-test not logged: the session ended without a post-mortem; "
|
|
f"rerun `leetcode --retest {slug}`"
|
|
)
|
|
return code
|
|
|
|
|
|
def _cluster(
|
|
name: str, start: Start, accepted: Callable[[str], bool], *, dry: bool
|
|
) -> int:
|
|
clusters = progress.load_clusters()
|
|
if name not in clusters:
|
|
known = ", ".join(clusters)
|
|
print(f"unknown cluster {name!r}; known: {known}", file=sys.stderr)
|
|
return 1
|
|
due = [row for row in progress.due_retests() if row["cluster"] == name]
|
|
if due:
|
|
print(f"re-test due: {due[0]['slug']} (due {due[0]['due']})")
|
|
return _retest(due[0]["slug"], start, dry=dry)
|
|
slug = progress.next_new(name, None, accepted)
|
|
if slug is None:
|
|
print(f"{name}: every problem is started or accepted on LeetCode")
|
|
return 0
|
|
number = next(p["id"] for p in clusters[name] if p["slug"] == slug)
|
|
print(f"next in {name}: {slug} (#{number})")
|
|
if dry:
|
|
return 0
|
|
os.environ[progress.CLUSTER_ENV] = name
|
|
return start([slug])
|
|
|
|
|
|
def main(
|
|
argv: list[str], start: Start, accepted: Callable[[str], bool] | None = None
|
|
) -> int:
|
|
"""Dispatch `due`, `--cluster <name>` and `--retest <slug>`."""
|
|
dry = "--dry-run" in argv
|
|
args = [a for a in argv if a != "--dry-run"]
|
|
match args:
|
|
case ["due"]:
|
|
return _due()
|
|
case ["--cluster", name]:
|
|
check = accepted_on_leetcode if accepted is None else accepted
|
|
return _cluster(name, start, check, dry=dry)
|
|
case ["--retest", slug]:
|
|
return _retest(slug, start, dry=dry)
|
|
case _:
|
|
print(USAGE, file=sys.stderr)
|
|
return 1
|