From dca368270bdb24e44efa57144eec2ce6c3cd4235 Mon Sep 17 00:00:00 2001 From: Eli Fine <5436635+ejfine@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:23:47 +0000 Subject: [PATCH 1/7] feat: add a script to post inline review comments on a PR diff post-review-comment.py posts a new review comment on one line or a range of lines in a PR's diff, with the body read from a file and the commit defaulting to the PR's head. Like post-reply.py, it runs gh api from a skills script so the call is covered by the skills allow-list instead of needing approval each time. Also adds it to the copier tasks that set the executable bit on the skill's scripts. --- .../post-review-comment.py | 103 ++++++++++++++++++ copier.yaml | 2 +- template/copier.yml.jinja-base | 2 +- 3 files changed, 105 insertions(+), 2 deletions(-) create mode 100755 .claude/skills/address-pr-comments/post-review-comment.py diff --git a/.claude/skills/address-pr-comments/post-review-comment.py b/.claude/skills/address-pr-comments/post-review-comment.py new file mode 100755 index 00000000..88387062 --- /dev/null +++ b/.claude/skills/address-pr-comments/post-review-comment.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +"""Post a new inline review comment on a PR diff via the GitHub API. + +Usage: + # Comment on one line of the new file: + post-review-comment.py --pr --path --line --body-file + + # Comment spanning a range of lines (start and end must be in the same diff hunk): + post-review-comment.py --pr --path --start-line --line --body-file + + # Comment on a removed line (the old file's numbering): + post-review-comment.py --pr --path --line --side LEFT --body-file + +Line numbers are those of the file at --commit-id (default: the PR's current head) for --side RIGHT, or of the base +file for --side LEFT. Owner and repo are derived from the git remote automatically. + +This script exists so that gh api calls are covered by the skills allow-list +rather than requiring manual approval each time. +""" + +import argparse +import json +import subprocess +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +from utils import owner_repo_from_remote +from utils import run_cmd + + +def pr_head_sha(*, owner: str, repo: str, pr: int) -> str: + try: + result = run_cmd( + ["gh", "api", f"repos/{owner}/{repo}/pulls/{pr}", "--jq", ".head.sha"], + timeout=30, + timeout_msg="gh api timed out while reading the PR head commit.", + ) + except subprocess.CalledProcessError as e: + _ = sys.stderr.write(f"gh api error reading PR #{pr}: {e.stderr}\n") + sys.exit(1) + return result.stdout.strip() + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + _ = parser.add_argument("--pr", type=int, required=True, help="PR number") + _ = parser.add_argument("--path", required=True, help="File path relative to the repo root") + _ = parser.add_argument("--line", type=int, required=True, help="Line the comment ends on") + _ = parser.add_argument("--start-line", type=int, help="First line of a multi-line comment") + _ = parser.add_argument("--side", choices=["RIGHT", "LEFT"], default="RIGHT", help="RIGHT = new file, LEFT = old") + _ = parser.add_argument("--start-side", choices=["RIGHT", "LEFT"], help="Defaults to --side") + _ = parser.add_argument("--commit-id", help="Commit the line numbers refer to (default: the PR's head)") + _ = parser.add_argument("--body-file", required=True, type=Path) + args = parser.parse_args() + + if args.start_line is not None and args.start_line >= args.line: + _ = sys.stderr.write(f"--start-line ({args.start_line}) must be less than --line ({args.line})\n") + sys.exit(1) + + owner, repo = owner_repo_from_remote() + try: + body = args.body_file.read_text(encoding="utf-8") + except OSError as e: + _ = sys.stderr.write(f"Unable to read body file {args.body_file}: {e}\n") + sys.exit(1) + + commit_id = args.commit_id + if commit_id is None: + commit_id = pr_head_sha(owner=owner, repo=repo, pr=args.pr) + + fields = { + "body": body, + "commit_id": commit_id, + "path": args.path, + "side": args.side, + } + if args.start_line is not None: + fields["start_side"] = args.side if args.start_side is None else args.start_side + cmd = ["gh", "api", f"repos/{owner}/{repo}/pulls/{args.pr}/comments", "--method", "POST"] + for key, value in fields.items(): + cmd += ["--raw-field", f"{key}={value}"] + cmd += ["--field", f"line={args.line}"] + if args.start_line is not None: + cmd += ["--field", f"start_line={args.start_line}"] + + try: + result = run_cmd(cmd, timeout=30, timeout_msg="gh api timed out while posting the review comment.") + except subprocess.CalledProcessError as e: + _ = sys.stderr.write(f"gh api error: {e.stderr}\n") + sys.exit(1) + + response = json.loads(result.stdout) + _ = sys.stdout.write(f"Posted: {response.get('html_url', '(no URL in response)')}\n") + + try: + args.body_file.unlink() + except OSError as e: + _ = sys.stderr.write(f"Warning: post succeeded but could not delete body file {args.body_file}: {e}\n") + + +if __name__ == "__main__": + main() diff --git a/copier.yaml b/copier.yaml index 2eddb8c3..fc792156 100644 --- a/copier.yaml +++ b/copier.yaml @@ -106,7 +106,7 @@ _tasks: fi fi - command: | - for script in .claude/skills/address-pr-comments/check-footer.py .claude/skills/address-pr-comments/fetch-pr-comments.py .claude/skills/address-pr-comments/post-reply.py; do + for script in .claude/skills/address-pr-comments/check-footer.py .claude/skills/address-pr-comments/fetch-pr-comments.py .claude/skills/address-pr-comments/post-reply.py .claude/skills/address-pr-comments/post-review-comment.py; do if [ -f "$script" ]; then chmod +x "$script" if git ls-files --error-unmatch "$script" > /dev/null 2>&1; then diff --git a/template/copier.yml.jinja-base b/template/copier.yml.jinja-base index e953102e..d24ca076 100644 --- a/template/copier.yml.jinja-base +++ b/template/copier.yml.jinja-base @@ -199,7 +199,7 @@ pnpm_minimum_release_age_exclude: fi fi - command: | - for script in .claude/skills/address-pr-comments/check-footer.py .claude/skills/address-pr-comments/commit-link.py .claude/skills/address-pr-comments/fetch-pr-comments.py .claude/skills/address-pr-comments/post-reply.py .claude/skills/address-pr-comments/verify-env.py; do + for script in .claude/skills/address-pr-comments/check-footer.py .claude/skills/address-pr-comments/commit-link.py .claude/skills/address-pr-comments/fetch-pr-comments.py .claude/skills/address-pr-comments/post-reply.py .claude/skills/address-pr-comments/post-review-comment.py .claude/skills/address-pr-comments/verify-env.py; do if [ -f "$script" ]; then chmod +x "$script" if git ls-files --error-unmatch "$script" > /dev/null 2>&1; then From 74014d03a4ad6601ab3c5a10178ac4bedbb53867 Mon Sep 17 00:00:00 2001 From: Eli Fine <5436635+ejfine@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:27:48 +0000 Subject: [PATCH 2/7] refactor: share body-file reading and gh api posting between the comment scripts post-reply.py and post-review-comment.py both read the comment body from a file, post it with gh api, print the new comment's URL and delete the body file. pylint's duplicate-code check flagged the copies, so both now call read_body_file and post_via_gh_api in utils.py. post-reply.py's output and exit codes are unchanged. --- .../skills/address-pr-comments/post-reply.py | 41 ++++--------------- .../post-review-comment.py | 23 ++--------- .claude/skills/address-pr-comments/utils.py | 30 ++++++++++++++ 3 files changed, 42 insertions(+), 52 deletions(-) diff --git a/.claude/skills/address-pr-comments/post-reply.py b/.claude/skills/address-pr-comments/post-reply.py index 4500e3c2..c8562b03 100755 --- a/.claude/skills/address-pr-comments/post-reply.py +++ b/.claude/skills/address-pr-comments/post-reply.py @@ -15,14 +15,13 @@ """ import argparse -import json -import subprocess import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent)) from utils import owner_repo_from_remote -from utils import run_cmd +from utils import post_via_gh_api +from utils import read_body_file def main() -> None: @@ -38,11 +37,7 @@ def main() -> None: args = parser.parse_args() owner, repo = owner_repo_from_remote() - try: - body = args.body_file.read_text(encoding="utf-8") - except OSError as e: - _ = sys.stderr.write(f"Unable to read body file {args.body_file}: {e}\n") - sys.exit(1) + body = read_body_file(args.body_file) if args.comment_type == "pulls/comments": # Reply directly into the inline review comment thread. @@ -51,31 +46,11 @@ def main() -> None: # GitHub has no "reply" concept for issue comments — post a new top-level comment. endpoint = f"repos/{owner}/{repo}/issues/{args.pr}/comments" - try: - result = run_cmd( - [ - "gh", - "api", - endpoint, - "--method", - "POST", - "--raw-field", - f"body={body}", - ], - timeout=30, - timeout_msg="gh api timed out while posting reply.", - ) - except subprocess.CalledProcessError as e: - _ = sys.stderr.write(f"gh api error: {e.stderr}\n") - sys.exit(1) - - response = json.loads(result.stdout) - _ = sys.stdout.write(f"Posted: {response.get('html_url', '(no URL in response)')}\n") - - try: - args.body_file.unlink() - except OSError as e: - _ = sys.stderr.write(f"Warning: post succeeded but could not delete body file {args.body_file}: {e}\n") + post_via_gh_api( + cmd=["gh", "api", endpoint, "--method", "POST", "--raw-field", f"body={body}"], + body_file=args.body_file, + timeout_msg="gh api timed out while posting reply.", + ) if __name__ == "__main__": diff --git a/.claude/skills/address-pr-comments/post-review-comment.py b/.claude/skills/address-pr-comments/post-review-comment.py index 88387062..b79353d6 100755 --- a/.claude/skills/address-pr-comments/post-review-comment.py +++ b/.claude/skills/address-pr-comments/post-review-comment.py @@ -19,13 +19,14 @@ """ import argparse -import json import subprocess import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent)) from utils import owner_repo_from_remote +from utils import post_via_gh_api +from utils import read_body_file from utils import run_cmd @@ -59,11 +60,7 @@ def main() -> None: sys.exit(1) owner, repo = owner_repo_from_remote() - try: - body = args.body_file.read_text(encoding="utf-8") - except OSError as e: - _ = sys.stderr.write(f"Unable to read body file {args.body_file}: {e}\n") - sys.exit(1) + body = read_body_file(args.body_file) commit_id = args.commit_id if commit_id is None: @@ -84,19 +81,7 @@ def main() -> None: if args.start_line is not None: cmd += ["--field", f"start_line={args.start_line}"] - try: - result = run_cmd(cmd, timeout=30, timeout_msg="gh api timed out while posting the review comment.") - except subprocess.CalledProcessError as e: - _ = sys.stderr.write(f"gh api error: {e.stderr}\n") - sys.exit(1) - - response = json.loads(result.stdout) - _ = sys.stdout.write(f"Posted: {response.get('html_url', '(no URL in response)')}\n") - - try: - args.body_file.unlink() - except OSError as e: - _ = sys.stderr.write(f"Warning: post succeeded but could not delete body file {args.body_file}: {e}\n") + post_via_gh_api(cmd=cmd, body_file=args.body_file, timeout_msg="gh api timed out while posting the review comment.") if __name__ == "__main__": diff --git a/.claude/skills/address-pr-comments/utils.py b/.claude/skills/address-pr-comments/utils.py index d68f10fc..f21aaa05 100644 --- a/.claude/skills/address-pr-comments/utils.py +++ b/.claude/skills/address-pr-comments/utils.py @@ -1,6 +1,8 @@ +import json import re import subprocess import sys +from pathlib import Path def run_cmd( @@ -41,3 +43,31 @@ def owner_repo_from_remote() -> tuple[str, str]: _ = sys.stderr.write(f"Cannot parse GitHub owner/repo from remote: {url}\n") sys.exit(1) return match.group(1), match.group(2) + + +def read_body_file(body_file: Path) -> str: + try: + return body_file.read_text(encoding="utf-8") + except OSError as e: + _ = sys.stderr.write(f"Unable to read body file {body_file}: {e}\n") + sys.exit(1) + + +def post_via_gh_api(*, cmd: list[str], body_file: Path, timeout_msg: str) -> None: + """Run a gh api POST, print the new comment's URL, then delete the body file it was posted from. + + Exits with status 1 if gh api fails. A failure to delete the body file only warns, since the post went through. + """ + try: + result = run_cmd(cmd, timeout=30, timeout_msg=timeout_msg) + except subprocess.CalledProcessError as e: + _ = sys.stderr.write(f"gh api error: {e.stderr}\n") + sys.exit(1) + + response = json.loads(result.stdout) + _ = sys.stdout.write(f"Posted: {response.get('html_url', '(no URL in response)')}\n") + + try: + body_file.unlink() + except OSError as e: + _ = sys.stderr.write(f"Warning: post succeeded but could not delete body file {body_file}: {e}\n") From 5e2d6b779d5e0b8f1023fb0a6614b3de23785c03 Mon Sep 17 00:00:00 2001 From: Eli Fine <5436635+ejfine@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:27:48 +0000 Subject: [PATCH 3/7] fix: set the executable bit on every address-pr-comments script in generated templates The copier task in copier.yaml listed only check-footer.py, fetch-pr-comments.py and post-reply.py. When commit-link.py and verify-env.py were added (#176), only the list in template/copier.yml.jinja-base was updated, so templates generated from this repo could lose the executable bit on those two. Both lists now name the same scripts. --- copier.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/copier.yaml b/copier.yaml index fc792156..6e552cc3 100644 --- a/copier.yaml +++ b/copier.yaml @@ -106,7 +106,7 @@ _tasks: fi fi - command: | - for script in .claude/skills/address-pr-comments/check-footer.py .claude/skills/address-pr-comments/fetch-pr-comments.py .claude/skills/address-pr-comments/post-reply.py .claude/skills/address-pr-comments/post-review-comment.py; do + for script in .claude/skills/address-pr-comments/check-footer.py .claude/skills/address-pr-comments/commit-link.py .claude/skills/address-pr-comments/fetch-pr-comments.py .claude/skills/address-pr-comments/post-reply.py .claude/skills/address-pr-comments/post-review-comment.py .claude/skills/address-pr-comments/verify-env.py; do if [ -f "$script" ]; then chmod +x "$script" if git ls-files --error-unmatch "$script" > /dev/null 2>&1; then From 7adfd1edac656f3ae994eb6cea46f19afa50b24f Mon Sep 17 00:00:00 2001 From: Eli Fine <5436635+ejfine@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:29:39 +0000 Subject: [PATCH 4/7] fix: count an unparseable gh api response as posted and still delete the body file Once gh api exits successfully the comment exists. If its output was empty or not JSON, json.loads raised before the body file was deleted, and a retry would post the comment again. post_via_gh_api now prints a placeholder URL in that case and carries on with the cleanup, for both post-reply.py and post-review-comment.py. --- .claude/skills/address-pr-comments/utils.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.claude/skills/address-pr-comments/utils.py b/.claude/skills/address-pr-comments/utils.py index f21aaa05..1543c90b 100644 --- a/.claude/skills/address-pr-comments/utils.py +++ b/.claude/skills/address-pr-comments/utils.py @@ -56,7 +56,9 @@ def read_body_file(body_file: Path) -> str: def post_via_gh_api(*, cmd: list[str], body_file: Path, timeout_msg: str) -> None: """Run a gh api POST, print the new comment's URL, then delete the body file it was posted from. - Exits with status 1 if gh api fails. A failure to delete the body file only warns, since the post went through. + Exits with status 1 if gh api fails. Once gh api succeeds the comment exists, so an unparseable response still counts + as posted and the body file is still deleted (otherwise a retry would post a duplicate). A failure to delete the body + file only warns. """ try: result = run_cmd(cmd, timeout=30, timeout_msg=timeout_msg) @@ -64,8 +66,11 @@ def post_via_gh_api(*, cmd: list[str], body_file: Path, timeout_msg: str) -> Non _ = sys.stderr.write(f"gh api error: {e.stderr}\n") sys.exit(1) - response = json.loads(result.stdout) - _ = sys.stdout.write(f"Posted: {response.get('html_url', '(no URL in response)')}\n") + try: + url = json.loads(result.stdout).get("html_url", "(no URL in response)") + except json.JSONDecodeError: + url = "(unparseable response)" + _ = sys.stdout.write(f"Posted: {url}\n") try: body_file.unlink() From d847d04d2c94421c71c0d5b7e44e63152ef24df0 Mon Sep 17 00:00:00 2001 From: Eli Fine <5436635+ejfine@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:40:04 +0000 Subject: [PATCH 5/7] refactor: have post_via_gh_api build the gh api POST itself post_via_gh_api took a ready-made command, so each caller repeated gh api --method POST and the helper could have been handed any command at all. It now takes the endpoint and the fields to send, reads the body file itself, and builds the POST, so it can only post through gh api. String fields go as --raw-field and integer fields (line numbers) as --field, which sends them as JSON numbers. --- .../skills/address-pr-comments/post-reply.py | 8 +---- .../post-review-comment.py | 29 ++++++++----------- .claude/skills/address-pr-comments/utils.py | 26 +++++++++++++++-- 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/.claude/skills/address-pr-comments/post-reply.py b/.claude/skills/address-pr-comments/post-reply.py index c8562b03..588981f9 100755 --- a/.claude/skills/address-pr-comments/post-reply.py +++ b/.claude/skills/address-pr-comments/post-reply.py @@ -21,7 +21,6 @@ sys.path.insert(0, str(Path(__file__).parent)) from utils import owner_repo_from_remote from utils import post_via_gh_api -from utils import read_body_file def main() -> None: @@ -37,7 +36,6 @@ def main() -> None: args = parser.parse_args() owner, repo = owner_repo_from_remote() - body = read_body_file(args.body_file) if args.comment_type == "pulls/comments": # Reply directly into the inline review comment thread. @@ -46,11 +44,7 @@ def main() -> None: # GitHub has no "reply" concept for issue comments — post a new top-level comment. endpoint = f"repos/{owner}/{repo}/issues/{args.pr}/comments" - post_via_gh_api( - cmd=["gh", "api", endpoint, "--method", "POST", "--raw-field", f"body={body}"], - body_file=args.body_file, - timeout_msg="gh api timed out while posting reply.", - ) + post_via_gh_api(endpoint=endpoint, body_file=args.body_file, timeout_msg="gh api timed out while posting reply.") if __name__ == "__main__": diff --git a/.claude/skills/address-pr-comments/post-review-comment.py b/.claude/skills/address-pr-comments/post-review-comment.py index b79353d6..638dd2d6 100755 --- a/.claude/skills/address-pr-comments/post-review-comment.py +++ b/.claude/skills/address-pr-comments/post-review-comment.py @@ -26,7 +26,6 @@ sys.path.insert(0, str(Path(__file__).parent)) from utils import owner_repo_from_remote from utils import post_via_gh_api -from utils import read_body_file from utils import run_cmd @@ -60,28 +59,24 @@ def main() -> None: sys.exit(1) owner, repo = owner_repo_from_remote() - body = read_body_file(args.body_file) commit_id = args.commit_id if commit_id is None: commit_id = pr_head_sha(owner=owner, repo=repo, pr=args.pr) - fields = { - "body": body, - "commit_id": commit_id, - "path": args.path, - "side": args.side, - } + string_fields = {"commit_id": commit_id, "path": args.path, "side": args.side} + int_fields = {"line": args.line} if args.start_line is not None: - fields["start_side"] = args.side if args.start_side is None else args.start_side - cmd = ["gh", "api", f"repos/{owner}/{repo}/pulls/{args.pr}/comments", "--method", "POST"] - for key, value in fields.items(): - cmd += ["--raw-field", f"{key}={value}"] - cmd += ["--field", f"line={args.line}"] - if args.start_line is not None: - cmd += ["--field", f"start_line={args.start_line}"] - - post_via_gh_api(cmd=cmd, body_file=args.body_file, timeout_msg="gh api timed out while posting the review comment.") + string_fields["start_side"] = args.side if args.start_side is None else args.start_side + int_fields["start_line"] = args.start_line + + post_via_gh_api( + endpoint=f"repos/{owner}/{repo}/pulls/{args.pr}/comments", + body_file=args.body_file, + string_fields=string_fields, + int_fields=int_fields, + timeout_msg="gh api timed out while posting the review comment.", + ) if __name__ == "__main__": diff --git a/.claude/skills/address-pr-comments/utils.py b/.claude/skills/address-pr-comments/utils.py index 1543c90b..dbd628a8 100644 --- a/.claude/skills/address-pr-comments/utils.py +++ b/.claude/skills/address-pr-comments/utils.py @@ -2,6 +2,7 @@ import re import subprocess import sys +from collections.abc import Mapping from pathlib import Path @@ -45,7 +46,7 @@ def owner_repo_from_remote() -> tuple[str, str]: return match.group(1), match.group(2) -def read_body_file(body_file: Path) -> str: +def _read_body_file(body_file: Path) -> str: try: return body_file.read_text(encoding="utf-8") except OSError as e: @@ -53,13 +54,32 @@ def read_body_file(body_file: Path) -> str: sys.exit(1) -def post_via_gh_api(*, cmd: list[str], body_file: Path, timeout_msg: str) -> None: - """Run a gh api POST, print the new comment's URL, then delete the body file it was posted from. +def post_via_gh_api( + *, + endpoint: str, + body_file: Path, + string_fields: Mapping[str, str] | None = None, + int_fields: Mapping[str, int] | None = None, + timeout_msg: str, +) -> None: + """POST a comment to a GitHub API endpoint with gh api, print its URL, then delete the body file it came from. + + The comment's `body` field is read from body_file. string_fields are sent as strings (--raw-field) and int_fields as + JSON numbers (--field), which is what GitHub expects for values like line numbers. Exits with status 1 if gh api fails. Once gh api succeeds the comment exists, so an unparseable response still counts as posted and the body file is still deleted (otherwise a retry would post a duplicate). A failure to delete the body file only warns. """ + body = _read_body_file(body_file) + cmd = ["gh", "api", endpoint, "--method", "POST", "--raw-field", f"body={body}"] + if string_fields is not None: + for key, value in string_fields.items(): + cmd += ["--raw-field", f"{key}={value}"] + if int_fields is not None: + for key, value in int_fields.items(): + cmd += ["--field", f"{key}={value}"] + try: result = run_cmd(cmd, timeout=30, timeout_msg=timeout_msg) except subprocess.CalledProcessError as e: From f3d21d9ce5f513f38e5567fbcd8c8538d48d5be6 Mon Sep 17 00:00:00 2001 From: Eli Fine <5436635+ejfine@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:50:26 +0000 Subject: [PATCH 6/7] fix: spell "unparsable" the way the typos check expects --- .claude/skills/address-pr-comments/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.claude/skills/address-pr-comments/utils.py b/.claude/skills/address-pr-comments/utils.py index dbd628a8..c169fe39 100644 --- a/.claude/skills/address-pr-comments/utils.py +++ b/.claude/skills/address-pr-comments/utils.py @@ -67,7 +67,7 @@ def post_via_gh_api( The comment's `body` field is read from body_file. string_fields are sent as strings (--raw-field) and int_fields as JSON numbers (--field), which is what GitHub expects for values like line numbers. - Exits with status 1 if gh api fails. Once gh api succeeds the comment exists, so an unparseable response still counts + Exits with status 1 if gh api fails. Once gh api succeeds the comment exists, so an unparsable response still counts as posted and the body file is still deleted (otherwise a retry would post a duplicate). A failure to delete the body file only warns. """ @@ -89,7 +89,7 @@ def post_via_gh_api( try: url = json.loads(result.stdout).get("html_url", "(no URL in response)") except json.JSONDecodeError: - url = "(unparseable response)" + url = "(unparsable response)" _ = sys.stdout.write(f"Posted: {url}\n") try: From 98e355f646704b163361fee0969d77d926ddabe1 Mon Sep 17 00:00:00 2001 From: Eli Fine <5436635+ejfine@users.noreply.github.com> Date: Thu, 15 Oct 2026 08:53:53 -0400 Subject: [PATCH 7/7] Update pull_request_template.md --- .github/pull_request_template.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b90b48a7..5ab1e5d9 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -14,13 +14,13 @@ -## Test plan +## How do we know this works? - + ## Other