Skip to content

Commit 628fe52

Browse files
authored
✨ feat: unify file-based inputs for comment edits and suggestions (#35)
1 parent 5bea943 commit 628fe52

6 files changed

Lines changed: 309 additions & 25 deletions

File tree

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,9 @@ gh-llm issue view 77924 --repo PaddlePaddle/Paddle --show meta,description
141141
```bash
142142
# Edit comment
143143
gh-llm pr comment-edit IC_xxx --body '<new_body>' --pr 77900 --repo PaddlePaddle/Paddle
144+
gh-llm pr comment-edit IC_xxx --body-file edit.md --pr 77900 --repo PaddlePaddle/Paddle
144145
gh-llm issue comment-edit IC_xxx --body '<new_body>' --issue 77924 --repo PaddlePaddle/Paddle
146+
gh-llm issue comment-edit IC_xxx --body-file edit.md --issue 77924 --repo PaddlePaddle/Paddle
145147

146148
# Reply / resolve / unresolve review thread
147149
gh-llm pr thread-reply PRRT_xxx --body '<reply>' --pr 77900 --repo PaddlePaddle/Paddle
@@ -231,6 +233,14 @@ gh-llm pr review-suggest \
231233
--body-file suggestion-reason.md \
232234
--suggestion 'replacement_code_here' \
233235
--pr 77938 --repo PaddlePaddle/Paddle
236+
237+
gh-llm pr review-suggest \
238+
--path 'path/to/file' \
239+
--line 123 \
240+
--side RIGHT \
241+
--body-file suggestion-reason.md \
242+
--suggestion-file replacement.txt \
243+
--pr 77938 --repo PaddlePaddle/Paddle
234244
```
235245

236246
### 4) Submit review
@@ -247,7 +257,9 @@ gh-llm pr review-submit \
247257
--pr 77938 --repo PaddlePaddle/Paddle
248258
```
249259

250-
`thread-reply`, `review-comment`, `review-suggest`, and `review-submit` all support `--body-file -` to read multi-line text from standard input.
260+
`pr comment-edit`, `issue comment-edit`, `thread-reply`, `review-comment`, `review-suggest`, and `review-submit` all support `--body-file -` to read multi-line text from standard input. `review-suggest` also supports `--suggestion-file -` for the suggestion block itself.
261+
262+
> Note: `review-suggest --body-file - --suggestion-file -` is intentionally rejected because standard input can only be consumed once. Use separate files when both the reason text and suggestion block need external input.
251263
252264
Submit behavior:
253265

skills/github-conversation/SKILL.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ gh-llm issue timeline-expand <page> --issue <issue> --repo <owner/repo>
103103
```bash
104104
gh pr comment <pr> --repo <owner/repo> --body '<comment>'
105105
gh issue comment <issue> --repo <owner/repo> --body '<comment>'
106+
gh-llm pr comment-edit <comment_id> --body-file edit.md --pr <pr> --repo <owner/repo>
107+
gh-llm issue comment-edit <comment_id> --body-file edit.md --issue <issue> --repo <owner/repo>
108+
cat edit.md | gh-llm issue comment-edit <comment_id> --body-file - --issue <issue> --repo <owner/repo>
106109
gh pr edit <pr> --repo <owner/repo> --add-label '<label1>,<label2>'
107110
gh pr edit <pr> --repo <owner/repo> --remove-label '<label1>,<label2>'
108111
gh pr edit <pr> --repo <owner/repo> --add-reviewer '<reviewer1>,<reviewer2>'
@@ -218,6 +221,14 @@ gh-llm pr review-suggest \
218221
--body '<why>' \
219222
--suggestion '<replacement>' \
220223
--pr <pr> --repo <owner/repo>
224+
225+
gh-llm pr review-suggest \
226+
--path 'path/to/file' \
227+
--line <line> \
228+
--side RIGHT \
229+
--body-file reason.md \
230+
--suggestion-file replacement.txt \
231+
--pr <pr> --repo <owner/repo>
221232
```
222233

223234
Then submit one review:

src/gh_llm/commands/issue.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from dataclasses import dataclass
44
from typing import TYPE_CHECKING, Any
55

6-
from gh_llm.commands.options import raise_unknown_option_value
6+
from gh_llm.commands.options import raise_unknown_option_value, resolve_file_or_inline_text
77
from gh_llm.github_api import GitHubClient
88
from gh_llm.pager import DEFAULT_PAGE_SIZE, TimelinePager
99
from gh_llm.render import (
@@ -86,7 +86,13 @@ def register_issue_parser(subparsers: Any) -> None:
8686

8787
comment_edit_parser = issue_subparsers.add_parser("comment-edit", help="edit one issue comment by node id")
8888
comment_edit_parser.add_argument("comment_id", help="comment id, e.g. IC_xxx")
89-
comment_edit_parser.add_argument("--body", required=True, help="new comment body")
89+
comment_edit_body_group = comment_edit_parser.add_mutually_exclusive_group(required=True)
90+
comment_edit_body_group.add_argument("--body", help="new comment body")
91+
comment_edit_body_group.add_argument(
92+
"-F",
93+
"--body-file",
94+
help="read new comment body from file (use `-` to read from standard input)",
95+
)
9096
comment_edit_parser.add_argument("--issue", help="Issue number/url")
9197
comment_edit_parser.add_argument("--repo", help="repository in OWNER/REPO format")
9298
comment_edit_parser.set_defaults(handler=cmd_issue_comment_edit)
@@ -230,7 +236,10 @@ def cmd_issue_comment_edit(args: Any) -> int:
230236
raise RuntimeError("`--issue` is required when `--repo` is provided")
231237
if args.issue is not None:
232238
client.resolve_issue(selector=args.issue, repo=args.repo)
233-
updated_comment_id = client.edit_comment(comment_id=str(args.comment_id), body=str(args.body))
239+
updated_comment_id = client.edit_comment(
240+
comment_id=str(args.comment_id),
241+
body=resolve_file_or_inline_text(args, text_attr="body", file_attr="body_file"),
242+
)
234243
print(f"comment: {updated_comment_id}")
235244
print("status: edited")
236245
return 0

src/gh_llm/commands/options.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,31 @@
11
from __future__ import annotations
22

3+
import sys
34
from difflib import get_close_matches
4-
from typing import NoReturn
5+
from pathlib import Path
6+
from typing import Any, NoReturn
7+
8+
9+
def read_text_from_path_or_stdin(path: str) -> str:
10+
if path == "-":
11+
return sys.stdin.read()
12+
return Path(path).read_text(encoding="utf-8")
13+
14+
15+
def resolve_file_or_inline_text(
16+
args: Any,
17+
*,
18+
text_attr: str,
19+
file_attr: str,
20+
default: str = "",
21+
) -> str:
22+
file_path = getattr(args, file_attr, None)
23+
if file_path is not None:
24+
return read_text_from_path_or_stdin(str(file_path))
25+
text = getattr(args, text_attr, None)
26+
if text is None:
27+
return default
28+
return str(text)
529

630

731
def raise_unknown_option_value(

src/gh_llm/commands/pr.py

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@
44
import os
55
import re
66
import shlex
7-
import sys
87
import tempfile
98
from dataclasses import dataclass
109
from pathlib import Path
1110
from typing import TYPE_CHECKING, Any
1211

13-
from gh_llm.commands.options import raise_unknown_option_value
12+
from gh_llm.commands.options import raise_unknown_option_value, resolve_file_or_inline_text
1413
from gh_llm.github_api import GitHubClient
1514
from gh_llm.invocation import display_command_with
1615
from gh_llm.models import PullRequestDiffPage
@@ -221,7 +220,13 @@ def register_pr_parser(subparsers: Any) -> None:
221220

222221
comment_edit_parser = pr_subparsers.add_parser("comment-edit", help="edit one issue/review comment by node id")
223222
comment_edit_parser.add_argument("comment_id", help="comment id, e.g. IC_xxx or PRRC_xxx")
224-
comment_edit_parser.add_argument("--body", required=True, help="new comment body")
223+
comment_edit_body_group = comment_edit_parser.add_mutually_exclusive_group(required=True)
224+
comment_edit_body_group.add_argument("--body", help="new comment body")
225+
comment_edit_body_group.add_argument(
226+
"-F",
227+
"--body-file",
228+
help="read new comment body from file (use `-` to read from standard input)",
229+
)
225230
comment_edit_parser.add_argument("--pr", help="PR number/url/branch")
226231
comment_edit_parser.add_argument("--repo", help="repository in OWNER/REPO format")
227232
comment_edit_parser.set_defaults(handler=cmd_pr_comment_edit)
@@ -313,11 +318,15 @@ def register_pr_parser(subparsers: Any) -> None:
313318
"--body-file",
314319
help="read review comment body from file (use `-` to read from standard input)",
315320
)
316-
review_suggest_parser.add_argument(
321+
review_suggest_suggestion_group = review_suggest_parser.add_mutually_exclusive_group(required=True)
322+
review_suggest_suggestion_group.add_argument(
317323
"--suggestion",
318-
required=True,
319324
help="replacement content inserted inside ```suggestion block",
320325
)
326+
review_suggest_suggestion_group.add_argument(
327+
"--suggestion-file",
328+
help="read replacement content from file (use `-` to read from standard input)",
329+
)
321330
review_suggest_parser.add_argument("--head", help="expected PR head sha for stale-snapshot protection")
322331
review_suggest_parser.add_argument("--pr", help="PR number/url/branch")
323332
review_suggest_parser.add_argument("--repo", help="repository in OWNER/REPO format")
@@ -344,20 +353,19 @@ def register_pr_parser(subparsers: Any) -> None:
344353
review_submit_parser.set_defaults(handler=cmd_pr_review_submit)
345354

346355

347-
def _read_body_file(path: str) -> str:
348-
if path == "-":
349-
return sys.stdin.read()
350-
return Path(path).read_text(encoding="utf-8")
356+
def _resolve_body_argument(args: Any, *, default: str = "") -> str:
357+
return resolve_file_or_inline_text(args, text_attr="body", file_attr="body_file", default=default)
358+
351359

360+
def _resolve_suggestion_argument(args: Any) -> str:
361+
return resolve_file_or_inline_text(args, text_attr="suggestion", file_attr="suggestion_file")
352362

353-
def _resolve_body_argument(args: Any, *, default: str = "") -> str:
354-
body_file = getattr(args, "body_file", None)
355-
if body_file:
356-
return _read_body_file(str(body_file))
357-
body = getattr(args, "body", None)
358-
if body is None:
359-
return default
360-
return str(body)
363+
364+
def _validate_review_suggest_stdin_sources(args: Any) -> None:
365+
if getattr(args, "body_file", None) == "-" and getattr(args, "suggestion_file", None) == "-":
366+
raise RuntimeError(
367+
"`--body-file -` cannot be combined with `--suggestion-file -`; standard input can only be consumed once"
368+
)
361369

362370

363371
def _resolve_review_submit_body(args: Any) -> str:
@@ -746,7 +754,7 @@ def cmd_pr_comment_edit(args: Any) -> int:
746754
raise RuntimeError("`--pr` is required when `--repo` is provided")
747755
if args.pr is not None:
748756
client.resolve_pull_request(selector=args.pr, repo=args.repo)
749-
updated_comment_id = client.edit_comment(comment_id=str(args.comment_id), body=str(args.body))
757+
updated_comment_id = client.edit_comment(comment_id=str(args.comment_id), body=_resolve_body_argument(args))
750758
print(f"comment: {updated_comment_id}")
751759
print("status: edited")
752760
return 0
@@ -1082,6 +1090,7 @@ def cmd_pr_review_comment(args: Any) -> int:
10821090

10831091

10841092
def cmd_pr_review_suggest(args: Any) -> int:
1093+
_validate_review_suggest_stdin_sources(args)
10851094
client = GitHubClient()
10861095
meta = _resolve_pr_meta(client=client, args=args)
10871096
_validate_pr_head_snapshot(meta=meta, requested_head=_resolve_requested_head(args))
@@ -1096,7 +1105,7 @@ def cmd_pr_review_suggest(args: Any) -> int:
10961105
start_line=start_line,
10971106
start_side=start_side,
10981107
)
1099-
suggestion = str(args.suggestion).rstrip("\n")
1108+
suggestion = _resolve_suggestion_argument(args).rstrip("\n")
11001109
body = _resolve_body_argument(args, default="Suggested change")
11011110
full_body = f"{body.rstrip()}\n\n```suggestion\n{suggestion}\n```"
11021111
thread_id, comment_id = client.add_pull_request_review_thread_comment(

0 commit comments

Comments
 (0)