diff --git a/.claude/skills/scan-github-stars/SKILL.md b/.claude/skills/scan-github-stars/SKILL.md index c84b6fe9..8d101059 100644 --- a/.claude/skills/scan-github-stars/SKILL.md +++ b/.claude/skills/scan-github-stars/SKILL.md @@ -16,7 +16,7 @@ Run `uv run python .claude/skills/scan-github-stars/scripts/scan_github_stars.py 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 `repoyyyy-mm-ddreason` 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. +Append each screen-out to `local/starred-scan.tsv` as it is decided, one `https://github.com/owner/repoyyyy-mm-ddreason` row (create the file with header `url 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 @@ -28,8 +28,8 @@ Run `preview-verdicts` over all groups: survivors with kind `candidate` and verd ## 5. Record -- **Added**: follow `audit-the-list` step 5 for the README change, one commit per entry. When the entry links a different repo than the star (a Python binding or client of a starred engine), also append a ledger row `already listed as ()` for the star, since the script only recognizes a star whose own repo is linked. +- **Added**: follow `audit-the-list` step 5 for the README change, one commit per entry. When the entry links a different repo than the star (a Python binding or client of a starred engine), also append a ledger row for the star's URL with reason `already listed as ()`, since the script only recognizes a star whose own repo is linked. - **Rejected**: a candidate with verdict `reject` gets one line under "Rejected Additions": `- owner/repo -- yyyy-mm-dd, /month (): .`, replacing its earlier line. This exact rejection takes precedence over a parked ledger row. -- **Parked**: leave listed entries in README.md. An unlisted repo with no prior decision needs no record. To reopen one screened out or rejected before, remove its exact "Rejected Additions" line and append `owner/repoyyyy-mm-ddparked` to the ledger. Its earlier screen-out rows stay as history. The marker also exempts this repo from an `owner/*` rejection; leave that organization line in place for its other repos. A later screen-out or exact rejection excludes the repo again. A new organization-wide rejection must explicitly resolve any parked exceptions it is intended to cover. +- **Parked**: leave listed entries in README.md. An unlisted repo with no prior decision needs no record. To reopen one screened out or rejected before, remove its exact "Rejected Additions" line and append `https://github.com/owner/repoyyyy-mm-ddparked` to the ledger. Its earlier screen-out rows stay as history. The marker also exempts this repo from an `owner/*` rejection; leave that organization line in place for its other repos. A later screen-out or exact rejection excludes the repo again. A new organization-wide rejection must explicitly resolve any parked exceptions it is intended to cover. Done when every candidate from step 1 is listed in README.md, a ledger row, a Rejected Additions line, or parked by the maintainer. diff --git a/.claude/skills/scan-github-stars/scripts/scan_github_stars.py b/.claude/skills/scan-github-stars/scripts/scan_github_stars.py index faf025cc..4627cd4a 100644 --- a/.claude/skills/scan-github-stars/scripts/scan_github_stars.py +++ b/.claude/skills/scan-github-stars/scripts/scan_github_stars.py @@ -61,16 +61,18 @@ def listed_repos() -> set[str]: return repos +def ledger_repo(row: str) -> str: + """A ledger row starts with the repo's GitHub URL, or a bare owner/repo in older rows.""" + return row.split("\t", 1)[0].removeprefix("https://github.com/").lower() + + def prior_verdicts() -> dict[str, str | None]: """The last ledger row wins; `parked` reopens a repo. An exact registered rejection still wins.""" prior: dict[str, str | None] = {} if LEDGER.exists(): for line in LEDGER.read_text().splitlines()[1:]: - repo, date, reason = line.split("\t", 2) - if reason == "parked": - prior[repo.lower()] = None - else: - prior[repo.lower()] = f"screened out {date}: {reason}" + _, date, reason = line.split("\t", 2) + prior[ledger_repo(line)] = None if reason == "parked" else f"screened out {date}: {reason}" register = REGISTER.read_text().splitlines() start = register.index("## Rejected Additions") for line in register[start + 1 :]: @@ -85,7 +87,7 @@ 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] + kept = [row for row in rows if ledger_repo(row) in starred] if len(kept) < len(rows): LEDGER.write_text("\n".join([header, *kept]) + "\n") return len(rows) - len(kept) diff --git a/website/tests/test_scan_github_stars.py b/website/tests/test_scan_github_stars.py index 15b97534..0ab7641e 100644 --- a/website/tests/test_scan_github_stars.py +++ b/website/tests/test_scan_github_stars.py @@ -111,6 +111,17 @@ def test_unstarred_repos_are_pruned_from_ledger(scan): assert module.LEDGER.read_text() == kept +def test_ledger_rows_keyed_by_github_url(scan): + module, _, run = scan + kept = "url\tdate\treason\nhttps://github.com/Example/Project\t2026-10-16\tno use case\n" + module.LEDGER.write_text(kept + "https://github.com/gone/repo\t2026-10-16\tno use case\n") + + result = run() + assert "Example/Project" not in result.out + assert "pruned 1" in result.err + assert module.LEDGER.read_text() == kept + + def test_parking_keeps_other_exclusions(scan): module, star, run = scan now = datetime.now(UTC)