From 32f58712e51f22b288153b62d4c688c2cee64d69 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Tue, 3 Nov 2026 17:56:54 +0100 Subject: [PATCH] fix(kvb-git-close-issues): drop false references found on a real PR A read-only run of issues.py on infinito-nexus/core#1326 listed #0 and #1 from CI row names like WordPress#0 and docs#1, reported #1324 twice (plain and as infinito-nexus/core#1324 from the linked issue), and offered the pull request's own number. A bare # now needs no word character or slash before it and a number from 1, the required --repo maps the pull request's own repository to the plain form and merges the sources, and the pull request's number is skipped; the skill passes --repo and reads number. On #1326 only #1324 closing remains. Co-Authored-By: Claude Opus 5.5 --- skills/kvb-git-close-issues/SKILL.md | 2 +- skills/kvb-git-close-issues/scripts/issues.py | 28 ++++++++++++------- tests/test_close_issues.py | 10 +++++-- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/skills/kvb-git-close-issues/SKILL.md b/skills/kvb-git-close-issues/SKILL.md index 09c181d..5e8ad90 100644 --- a/skills/kvb-git-close-issues/SKILL.md +++ b/skills/kvb-git-close-issues/SKILL.md @@ -18,7 +18,7 @@ body and the commits, a trailing number in the branch name, and the issues GitHub already links: ```bash -gh pr view --json body,headRefName,commits,closingIssuesReferences | issues.py +gh pr view --json number,body,headRefName,commits,closingIssuesReferences | issues.py --repo ``` ## Step 2: verify diff --git a/skills/kvb-git-close-issues/scripts/issues.py b/skills/kvb-git-close-issues/scripts/issues.py index c102118..74752f6 100755 --- a/skills/kvb-git-close-issues/scripts/issues.py +++ b/skills/kvb-git-close-issues/scripts/issues.py @@ -2,9 +2,11 @@ """List the issues a pull request references, from `gh pr view --json` on stdin. Args: - stdin: JSON with body, headRefName, commits and closingIssuesReferences, - as `gh pr view --json body,headRefName,commits,closingIssuesReferences` - prints it. + stdin: JSON with number, body, headRefName, commits and + closingIssuesReferences, as `gh pr view --json + number,body,headRefName,commits,closingIssuesReferences` prints it. + --repo: owner/name of the pull request's repository; its own references + are reported without a repository. --json: print the candidates as JSON instead of tab-separated lines. Exit codes: @@ -19,7 +21,7 @@ import re import sys KEYWORD = r"(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)" -REF = r"(?:(?P[\w.-]+/[\w.-]+)#|https://github\.com/(?P[\w.-]+/[\w.-]+)/issues/|#)(?P\d+)" +REF = r"(?:(?P[\w.-]+/[\w.-]+)#|https://github\.com/(?P[\w.-]+/[\w.-]+)/issues/|(?[1-9]\d*)" PATTERN = re.compile(rf"(?P\b{KEYWORD}\b:?\s+)?{REF}", re.IGNORECASE) BRANCH = re.compile(r"[/-](\d+)$") @@ -40,7 +42,7 @@ def scan(text: str, source: str, found: dict) -> None: entry["sources"].append(source) -def candidates(pr: dict) -> list[dict]: +def candidates(pr: dict, own: str) -> list[dict]: found: dict = {} scan(pr.get("body", ""), "body", found) for commit in pr.get("commits", []): @@ -57,14 +59,20 @@ def candidates(pr: dict) -> list[dict]: entry = found.setdefault(key, {"closing": False, "sources": []}) entry["closing"] = True entry["sources"].append("linked") - return [ - {"repo": repo, "issue": num, **entry} - for (repo, num), entry in sorted(found.items()) - ] + merged: dict = {} + for (repo, num), entry in found.items(): + key = ("" if repo.lower() == own.lower() else repo, num) + if key == ("", pr.get("number")): + continue + target = merged.setdefault(key, {"closing": False, "sources": []}) + target["closing"] = target["closing"] or entry["closing"] + target["sources"] += [s for s in entry["sources"] if s not in target["sources"]] + return [{"repo": repo, "issue": num, **entry} for (repo, num), entry in sorted(merged.items())] def main() -> int: parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--repo", required=True) parser.add_argument("--json", action="store_true") args = parser.parse_args() try: @@ -73,7 +81,7 @@ def main() -> int: fail("stdin is not the JSON of gh pr view --json") if not isinstance(pr, dict): fail("stdin is not a pull request object") - rows = candidates(pr) + rows = candidates(pr, args.repo) if args.json: print(json.dumps(rows, indent=1)) return 0 diff --git a/tests/test_close_issues.py b/tests/test_close_issues.py index a1de964..af6708e 100644 --- a/tests/test_close_issues.py +++ b/tests/test_close_issues.py @@ -11,7 +11,11 @@ from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] SCRIPT = REPO_ROOT / "skills" / "kvb-git-close-issues" / "scripts" / "issues.py" PR = { - "body": "Fixes #12 and touches #13.\nCloses: other/repo#7\nSee https://github.com/a/b/issues/9", + "number": 40, + "body": ( + "Fixes #12 and touches #13.\nCloses: other/repo#7\nSee https://github.com/a/b/issues/9\n" + "Rows WordPress#0 and docs#1 are not issues; this is #40." + ), "headRefName": "fix/sys-ctl-hlth-runtime/vulnerability-engines/1324", "commits": [ {"oid": "abcdef1234567", "messageHeadline": "fix: guard", "messageBody": "Resolved #13"}, @@ -25,7 +29,7 @@ PR = { def _run(payload: str) -> subprocess.CompletedProcess: return subprocess.run( - [sys.executable, str(SCRIPT), "--json"], + [sys.executable, str(SCRIPT), "--repo", "Owner/repo", "--json"], input=payload, check=False, capture_output=True, @@ -46,7 +50,7 @@ class TestIssues(unittest.TestCase): ("other/repo", 7): (True, ["body"]), ("a/b", 9): (False, ["body"]), ("", 1324): (False, ["branch"]), - ("owner/repo", 21): (True, ["linked"]), + ("", 21): (True, ["linked"]), }, )