fix: prune star-scan ledger rows for repos no longer starred

The ledger kept screen-out rows for unstarred repos because the scan script only read it and the skill called it append-only, so dead rows piled up.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Vinta Chen
2026-11-08 21:42:57 +08:00
co-authored by Claude
parent 40d1ddebe8
commit 66e177d983
3 changed files with 26 additions and 3 deletions
+2 -2
View File
@@ -10,13 +10,13 @@ A star scan turns the maintainer's GitHub stars into proposed additions. Every s
## 1. Scan
Run `uv run python .claude/skills/scan-github-stars/scripts/scan_github_stars.py`, adding `--full` when the argument is `full`. With no ledger yet, a plain run already covers every star. Print the drop counts from stderr. Done when the candidate TSV is in hand.
Run `uv run python .claude/skills/scan-github-stars/scripts/scan_github_stars.py`, adding `--full` when the argument is `full`. With no ledger yet, a plain run already covers every star. Print the drop and prune counts from stderr. Done when the candidate TSV is in hand.
## 2. Screen
Judge each candidate from its name, description, topics, and language against CONTRIBUTING Quality Requirement 1 (Serves Python Developers), and name the current use case it would compete in. The screen is loose: drop only what plainly fails, and pass anything uncertain on, since `audit-the-list` is the real judge. In a full scan, read the `prior` column as context and overturn a rejection only on new evidence.
Append each screen-out to `local/starred-scan.tsv` as it is decided, one `repo<TAB>yyyy-mm-dd<TAB>reason` row (create the file with header `repo date reason`), so an interrupted run keeps its work. The file is append-only; the script reads the last row per repo. A survivor no current use case fits is a structure question: carry it to review with its evidence. Done when every candidate is a ledger row, holds a target use case, or is a structure question.
Append each screen-out to `local/starred-scan.tsv` as it is decided, one `repo<TAB>yyyy-mm-dd<TAB>reason` row (create the file with header `repo date reason`), so an interrupted run keeps its work. Only append rows: the script reads the last row per repo, and it prunes rows for repos the maintainer no longer stars. A survivor no current use case fits is a structure question: carry it to review with its evidence. Done when every candidate is a ledger row, holds a target use case, or is a structure question.
## 3. Judge
@@ -6,7 +6,8 @@ recomputed every run, so a repo that becomes active again resurfaces. An
incremental run also drops stars already judged: model screen-outs in
local/starred-scan.tsv and maintainer rejections under docs/audit-logs.md
"Rejected Additions". `--full` keeps them and shows the earlier verdict in the
`prior` column. Prints survivors as TSV on stdout, drop counts on stderr.
`prior` column. Ledger rows for repos no longer starred are pruned each run.
Prints survivors as TSV on stdout, drop and prune counts on stderr.
"""
import argparse
@@ -80,6 +81,16 @@ def prior_verdicts() -> dict[str, str | None]:
return prior
def prune_ledger(starred: set[str]) -> int:
if not LEDGER.exists():
return 0
header, *rows = LEDGER.read_text().splitlines()
kept = [row for row in rows if row.split("\t", 1)[0].lower() in starred]
if len(kept) < len(rows):
LEDGER.write_text("\n".join([header, *kept]) + "\n")
return len(rows) - len(kept)
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--full", action="store_true", help="rejudge stars already screened out or rejected")
@@ -91,6 +102,7 @@ def main() -> None:
prior = prior_verdicts()
stars: list[Star] = [json.loads(line) for line in gh("api", "--paginate", "user/starred?per_page=100", "--jq", ".[]").splitlines()]
pruned = prune_ledger({star["full_name"].lower() for star in stars})
dropped = Counter()
print("repo\tlanguage\tstars\tpushed\ttopics\tdescription\tprior")
for star in stars:
@@ -115,6 +127,8 @@ def main() -> None:
print(f"{len(stars)} stars; {len(stars) - dropped.total()} candidates", file=sys.stderr)
for reason, count in dropped.most_common():
print(f" dropped {count}: {reason}", file=sys.stderr)
if pruned:
print(f" pruned {pruned} ledger rows for repos no longer starred", file=sys.stderr)
if __name__ == "__main__":
+9
View File
@@ -102,6 +102,15 @@ def test_parking_reopens_one_repo_from_rejected_organization(scan):
assert "rejected again" in run("--full").out
def test_unstarred_repos_are_pruned_from_ledger(scan):
module, _, run = scan
kept = "repo\tdate\treason\nexample/project\t2026-10-16\tno use case\n"
module.LEDGER.write_text(kept + "gone/repo\t2026-10-16\tno use case\ngone/repo\t2026-10-17\tparked\n")
assert "pruned 2" in run().err
assert module.LEDGER.read_text() == kept
def test_parking_keeps_other_exclusions(scan):
module, star, run = scan
now = datetime.now(UTC)