sase-github 0.2.3__tar.gz → 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. sase_github-0.2.4/.release-please-manifest.json +3 -0
  2. {sase_github-0.2.3 → sase_github-0.2.4}/CHANGELOG.md +7 -0
  3. {sase_github-0.2.3 → sase_github-0.2.4}/PKG-INFO +2 -2
  4. {sase_github-0.2.3 → sase_github-0.2.4}/pyproject.toml +1 -1
  5. {sase_github-0.2.3 → sase_github-0.2.4}/src/sase_github/plugin.py +125 -11
  6. {sase_github-0.2.3 → sase_github-0.2.4}/tests/test_github_issues.py +6 -1
  7. sase_github-0.2.4/tests/test_github_pull_requests.py +200 -0
  8. sase_github-0.2.3/.release-please-manifest.json +0 -3
  9. {sase_github-0.2.3 → sase_github-0.2.4}/.github/workflows/ci.yml +0 -0
  10. {sase_github-0.2.3 → sase_github-0.2.4}/.github/workflows/pr-title.yml +0 -0
  11. {sase_github-0.2.3 → sase_github-0.2.4}/.github/workflows/publish.yml +0 -0
  12. {sase_github-0.2.3 → sase_github-0.2.4}/.sase_beads/beads.db +0 -0
  13. {sase_github-0.2.3 → sase_github-0.2.4}/.sase_beads/config.json +0 -0
  14. {sase_github-0.2.3 → sase_github-0.2.4}/.sase_beads/issues.jsonl +0 -0
  15. {sase_github-0.2.3 → sase_github-0.2.4}/CLAUDE.md +0 -0
  16. {sase_github-0.2.3 → sase_github-0.2.4}/Justfile +0 -0
  17. {sase_github-0.2.3 → sase_github-0.2.4}/LICENSE +0 -0
  18. {sase_github-0.2.3 → sase_github-0.2.4}/README.md +0 -0
  19. {sase_github-0.2.3 → sase_github-0.2.4}/docs/architecture.md +0 -0
  20. {sase_github-0.2.3 → sase_github-0.2.4}/docs/configuration.md +0 -0
  21. {sase_github-0.2.3 → sase_github-0.2.4}/docs/xprompts.md +0 -0
  22. {sase_github-0.2.3 → sase_github-0.2.4}/release-please-config.json +0 -0
  23. {sase_github-0.2.3 → sase_github-0.2.4}/src/sase_github/__init__.py +0 -0
  24. {sase_github-0.2.3 → sase_github-0.2.4}/src/sase_github/config.py +0 -0
  25. {sase_github-0.2.3 → sase_github-0.2.4}/src/sase_github/default_config.yml +0 -0
  26. {sase_github-0.2.3 → sase_github-0.2.4}/src/sase_github/errors.py +0 -0
  27. {sase_github-0.2.3 → sase_github-0.2.4}/src/sase_github/scripts/__init__.py +0 -0
  28. {sase_github-0.2.3 → sase_github-0.2.4}/src/sase_github/scripts/gh_setup.py +0 -0
  29. {sase_github-0.2.3 → sase_github-0.2.4}/src/sase_github/scripts/new_pr_desc_get_context.py +0 -0
  30. {sase_github-0.2.3 → sase_github-0.2.4}/src/sase_github/workspace_plugin.py +0 -0
  31. {sase_github-0.2.3 → sase_github-0.2.4}/src/sase_github/xprompts/gh.yml +0 -0
  32. {sase_github-0.2.3 → sase_github-0.2.4}/src/sase_github/xprompts/new_pr_desc.yml +0 -0
  33. {sase_github-0.2.3 → sase_github-0.2.4}/src/sase_github/xprompts/pr_diff.yml +0 -0
  34. {sase_github-0.2.3 → sase_github-0.2.4}/src/sase_github/xprompts/prdd.yml +0 -0
  35. {sase_github-0.2.3 → sase_github-0.2.4}/tests/__init__.py +0 -0
  36. {sase_github-0.2.3 → sase_github-0.2.4}/tests/test_ci_install_contract.py +0 -0
  37. {sase_github-0.2.3 → sase_github-0.2.4}/tests/test_config.py +0 -0
  38. {sase_github-0.2.3 → sase_github-0.2.4}/tests/test_github_plugin.py +0 -0
  39. {sase_github-0.2.3 → sase_github-0.2.4}/tests/test_submit_with_recorded_pr.py +0 -0
  40. {sase_github-0.2.3 → sase_github-0.2.4}/tests/test_workspace_plugin.py +0 -0
  41. {sase_github-0.2.3 → sase_github-0.2.4}/uv.lock +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.2.4"
3
+ }
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.4](https://github.com/sase-org/sase-github/compare/v0.2.3...v0.2.4) (2026-08-11)
4
+
5
+
6
+ ### Features
7
+
8
+ * implement vcs_list_pull_requests over gh pr list ([2b0d6a8](https://github.com/sase-org/sase-github/commit/2b0d6a8d6bc3ecd4cd9e7ebdeb012978cddb1181))
9
+
3
10
  ## [0.2.3](https://github.com/sase-org/sase-github/compare/v0.2.2...v0.2.3) (2026-08-09)
4
11
 
5
12
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: sase-github
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: GitHub VCS plugin for sase
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sase-github"
7
- version = "0.2.3"
7
+ version = "0.2.4"
8
8
  description = "GitHub VCS plugin for sase"
9
9
  requires-python = ">=3.12"
10
10
  license = "MIT"
@@ -10,7 +10,14 @@ import subprocess
10
10
  from collections.abc import Sequence
11
11
  from typing import Any, cast
12
12
 
13
- from sase.vcs_provider import IssueListState, IssueState, IssueWire
13
+ from sase.vcs_provider import (
14
+ IssueListState,
15
+ IssueState,
16
+ IssueWire,
17
+ PullRequestListState,
18
+ PullRequestState,
19
+ PullRequestWire,
20
+ )
14
21
  from sase.vcs_provider._hookspec import hookimpl
15
22
  from sase.vcs_provider._types import CommandOutput
16
23
  from sase.vcs_provider.plugins._git_common import GitCommon
@@ -22,7 +29,12 @@ from sase_github.errors import (
22
29
  )
23
30
 
24
31
  _ISSUE_JSON_FIELDS = (
25
- "number,title,state,body,labels,assignees,author,createdAt,updatedAt,url,comments"
32
+ "id,number,title,state,body,labels,assignees,author,createdAt,updatedAt,url,"
33
+ "comments"
34
+ )
35
+ _PR_JSON_FIELDS = (
36
+ "id,number,title,state,body,isDraft,author,headRefName,baseRefName,createdAt,"
37
+ "updatedAt,closedAt,mergedAt,url"
26
38
  )
27
39
  _UNBOUNDED_ISSUE_LIMIT = 1_000_000
28
40
  _AUTH_ERROR_MARKERS = (
@@ -140,6 +152,56 @@ def _issue_from_json(payload: object) -> IssueWire:
140
152
  updated_at=_string_field(payload, "updatedAt"),
141
153
  url=_string_field(payload, "url"),
142
154
  comment_count=_comment_count(payload.get("comments")),
155
+ provider_id=_string_field(payload, "id"),
156
+ )
157
+
158
+
159
+ def _pull_request_from_json(payload: object) -> PullRequestWire:
160
+ """Normalize one ``gh pr --json`` object into :class:`PullRequestWire`.
161
+
162
+ GitHub reports a merged PR's raw ``state`` as ``MERGED``, distinct from
163
+ ``CLOSED``; that collapses into our two-valued ``state`` as ``"closed"``,
164
+ with ``merged_at`` left populated as the signal that tells the two apart.
165
+ """
166
+ if not isinstance(payload, dict):
167
+ raise ValueError("pull request must be an object")
168
+
169
+ number = payload.get("number")
170
+ title = payload.get("title")
171
+ raw_state = payload.get("state")
172
+ is_draft = payload.get("isDraft")
173
+ if not isinstance(number, int) or isinstance(number, bool):
174
+ raise ValueError("number must be an integer")
175
+ if not isinstance(title, str):
176
+ raise ValueError("title must be a string")
177
+ if not isinstance(raw_state, str) or raw_state.casefold() not in {
178
+ "open",
179
+ "closed",
180
+ "merged",
181
+ }:
182
+ raise ValueError("state must be open, closed, or merged")
183
+ if not isinstance(is_draft, bool):
184
+ raise ValueError("isDraft must be a boolean")
185
+
186
+ state = cast(
187
+ PullRequestState,
188
+ "closed" if raw_state.casefold() == "merged" else raw_state.casefold(),
189
+ )
190
+ return PullRequestWire(
191
+ number=number,
192
+ title=title,
193
+ state=state,
194
+ provider_id=_string_field(payload, "id"),
195
+ url=_string_field(payload, "url"),
196
+ body=_string_field(payload, "body"),
197
+ is_draft=is_draft,
198
+ author=_login(payload.get("author"), "author"),
199
+ head_ref=_string_field(payload, "headRefName"),
200
+ base_ref=_string_field(payload, "baseRefName"),
201
+ created_at=_string_field(payload, "createdAt"),
202
+ updated_at=_string_field(payload, "updatedAt"),
203
+ closed_at=_string_field(payload, "closedAt"),
204
+ merged_at=_string_field(payload, "mergedAt"),
143
205
  )
144
206
 
145
207
 
@@ -182,14 +244,14 @@ class GitHubPlugin(GitCommon):
182
244
 
183
245
  # --- Optional issue-tracker operations ---
184
246
 
185
- def _run_issue_command(self, cmd: list[str], cwd: str, operation: str) -> str:
247
+ def _run_gh_command(self, cmd: list[str], cwd: str, operation: str) -> str:
186
248
  out = self._run(cmd, cwd)
187
249
  if not out.success:
188
250
  raise _command_error(operation, out)
189
251
  return out.stdout
190
252
 
191
- def _run_issue_json(self, cmd: list[str], cwd: str, operation: str) -> object:
192
- raw = self._run_issue_command(cmd, cwd, operation)
253
+ def _run_gh_json(self, cmd: list[str], cwd: str, operation: str) -> object:
254
+ raw = self._run_gh_command(cmd, cwd, operation)
193
255
  try:
194
256
  return json.loads(raw)
195
257
  except json.JSONDecodeError as exc:
@@ -200,7 +262,7 @@ class GitHubPlugin(GitCommon):
200
262
 
201
263
  def _view_issue(self, issue_ref: int | str, cwd: str) -> IssueWire:
202
264
  operation = "gh issue view"
203
- payload = self._run_issue_json(
265
+ payload = self._run_gh_json(
204
266
  [
205
267
  "gh",
206
268
  "issue",
@@ -229,7 +291,7 @@ class GitHubPlugin(GitCommon):
229
291
  ) -> list[IssueWire]:
230
292
  """List GitHub issues and normalize ``gh`` JSON output."""
231
293
  operation = "gh issue list"
232
- payload = self._run_issue_json(
294
+ payload = self._run_gh_json(
233
295
  [
234
296
  "gh",
235
297
  "issue",
@@ -272,7 +334,7 @@ class GitHubPlugin(GitCommon):
272
334
  cmd = ["gh", "issue", "create", "--title", title, "--body", body]
273
335
  for label in _ordered_unique(labels):
274
336
  cmd.extend(("--label", label))
275
- issue_ref = self._run_issue_command(cmd, cwd, operation).strip()
337
+ issue_ref = self._run_gh_command(cmd, cwd, operation).strip()
276
338
  if not issue_ref:
277
339
  raise GitHubIssueError(operation, "gh returned no created issue URL")
278
340
  return self._view_issue(issue_ref, cwd)
@@ -305,11 +367,11 @@ class GitHubPlugin(GitCommon):
305
367
  if label not in current_set:
306
368
  edit_cmd.extend(("--add-label", label))
307
369
  if len(edit_cmd) > 4:
308
- self._run_issue_command(edit_cmd, cwd, "gh issue edit")
370
+ self._run_gh_command(edit_cmd, cwd, "gh issue edit")
309
371
 
310
372
  if state is not None and state != current.state:
311
373
  state_operation = f"gh issue {'close' if state == 'closed' else 'reopen'}"
312
- self._run_issue_command(
374
+ self._run_gh_command(
313
375
  ["gh", "issue", state_operation.rsplit(" ", 1)[-1], str(number)],
314
376
  cwd,
315
377
  state_operation,
@@ -323,7 +385,7 @@ class GitHubPlugin(GitCommon):
323
385
  def vcs_get_issue_url(self, number: int, cwd: str) -> str:
324
386
  """Return the issue URL while requesting no other issue fields."""
325
387
  operation = "gh issue view"
326
- url = self._run_issue_command(
388
+ url = self._run_gh_command(
327
389
  [
328
390
  "gh",
329
391
  "issue",
@@ -341,6 +403,58 @@ class GitHubPlugin(GitCommon):
341
403
  raise GitHubIssueError(operation, "gh returned no issue URL")
342
404
  return url
343
405
 
406
+ # --- Optional pull-request operations ---
407
+
408
+ @hookimpl
409
+ def vcs_list_pull_requests(
410
+ self,
411
+ cwd: str,
412
+ state: PullRequestListState,
413
+ limit: int,
414
+ ) -> list[PullRequestWire]:
415
+ """List GitHub pull requests and normalize ``gh`` JSON output.
416
+
417
+ ``gh pr list --state closed`` is not documented to include merged
418
+ PRs, so any request beyond ``"open"`` fetches ``--state all`` and
419
+ filters locally by normalized (open/closed) state.
420
+ """
421
+ operation = "gh pr list"
422
+ gh_state = "open" if state == "open" else "all"
423
+ needs_local_filter = state == "closed"
424
+ fetch_limit = (
425
+ _UNBOUNDED_ISSUE_LIMIT
426
+ if needs_local_filter
427
+ else (limit if limit > 0 else _UNBOUNDED_ISSUE_LIMIT)
428
+ )
429
+ payload = self._run_gh_json(
430
+ [
431
+ "gh",
432
+ "pr",
433
+ "list",
434
+ "--state",
435
+ gh_state,
436
+ "--limit",
437
+ str(fetch_limit),
438
+ "--json",
439
+ _PR_JSON_FIELDS,
440
+ ],
441
+ cwd,
442
+ operation,
443
+ )
444
+ if not isinstance(payload, list):
445
+ raise GitHubIssueError(operation, "gh returned a non-list JSON value")
446
+ try:
447
+ pull_requests = [_pull_request_from_json(pr) for pr in payload]
448
+ except ValueError as exc:
449
+ raise GitHubIssueError(
450
+ operation,
451
+ f"gh returned invalid pull request JSON: {exc}",
452
+ ) from exc
453
+ if needs_local_filter:
454
+ pull_requests = [pr for pr in pull_requests if pr.state == "closed"]
455
+ return pull_requests if limit <= 0 else pull_requests[:limit]
456
+ return pull_requests
457
+
344
458
  @hookimpl
345
459
  def vcs_abandon_change(
346
460
  self, cl: str, revision: str, cwd: str
@@ -20,7 +20,8 @@ from sase_github.plugin import GitHubPlugin
20
20
 
21
21
  _MOCK_TARGET = "sase.vcs_provider._command_runner.subprocess.run"
22
22
  _JSON_FIELDS = (
23
- "number,title,state,body,labels,assignees,author,createdAt,updatedAt,url,comments"
23
+ "id,number,title,state,body,labels,assignees,author,createdAt,updatedAt,url,"
24
+ "comments"
24
25
  )
25
26
 
26
27
 
@@ -42,8 +43,10 @@ def _json_issue(
42
43
  assignees: tuple[str, ...] = ("octocat",),
43
44
  author: str | None = "hubot",
44
45
  comments: int = 2,
46
+ node_id: str | None = None,
45
47
  ) -> dict[str, Any]:
46
48
  return {
49
+ "id": node_id if node_id is not None else f"I_kwDOissue{number}",
47
50
  "number": number,
48
51
  "title": title,
49
52
  "state": state,
@@ -105,6 +108,7 @@ def test_list_issues_uses_json_and_normalizes_records(
105
108
  updated_at="2026-07-15T11:00:00Z",
106
109
  url="https://github.example/owner/repo/issues/42",
107
110
  comment_count=2,
111
+ provider_id="I_kwDOissue42",
108
112
  ),
109
113
  IssueWire(
110
114
  number=7,
@@ -113,6 +117,7 @@ def test_list_issues_uses_json_and_normalizes_records(
113
117
  created_at="2026-07-14T10:00:00Z",
114
118
  updated_at="2026-07-15T11:00:00Z",
115
119
  url="https://github.example/owner/repo/issues/7",
120
+ provider_id="I_kwDOissue7",
116
121
  ),
117
122
  ]
118
123
  assert _command(mock_run) == [
@@ -0,0 +1,200 @@
1
+ """Tests for the GitHub pull-request listing hook."""
2
+
3
+ import json
4
+ from typing import Any
5
+ from unittest.mock import MagicMock, patch
6
+
7
+ import pluggy
8
+ import pytest
9
+ from sase.vcs_provider import PullRequestWire
10
+ from sase.vcs_provider._hookspec import VCSHookSpec
11
+ from sase.vcs_provider._plugin_manager import VCSPluginManager
12
+
13
+ from sase_github.errors import GitHubIssueError
14
+ from sase_github.plugin import GitHubPlugin
15
+
16
+ _MOCK_TARGET = "sase.vcs_provider._command_runner.subprocess.run"
17
+ _JSON_FIELDS = (
18
+ "id,number,title,state,body,isDraft,author,headRefName,baseRefName,createdAt,"
19
+ "updatedAt,closedAt,mergedAt,url"
20
+ )
21
+
22
+
23
+ @pytest.fixture
24
+ def github_provider() -> VCSPluginManager:
25
+ pm = pluggy.PluginManager("sase_vcs")
26
+ pm.add_hookspecs(VCSHookSpec)
27
+ pm.register(GitHubPlugin())
28
+ return VCSPluginManager(pm)
29
+
30
+
31
+ def _json_pr(
32
+ number: int = 42,
33
+ *,
34
+ state: str = "OPEN",
35
+ title: str = "Add feature",
36
+ body: str | None = "Description",
37
+ is_draft: bool = False,
38
+ author: str | None = "hubot",
39
+ head_ref: str = "feature-branch",
40
+ base_ref: str = "main",
41
+ closed_at: str | None = None,
42
+ merged_at: str | None = None,
43
+ node_id: str | None = None,
44
+ ) -> dict[str, Any]:
45
+ return {
46
+ "id": node_id if node_id is not None else f"PR_kwDOpr{number}",
47
+ "number": number,
48
+ "title": title,
49
+ "state": state,
50
+ "body": body,
51
+ "isDraft": is_draft,
52
+ "author": {"login": author} if author is not None else None,
53
+ "headRefName": head_ref,
54
+ "baseRefName": base_ref,
55
+ "createdAt": "2026-07-14T10:00:00Z",
56
+ "updatedAt": "2026-07-15T11:00:00Z",
57
+ "closedAt": closed_at,
58
+ "mergedAt": merged_at,
59
+ "url": f"https://github.example/owner/repo/pull/{number}",
60
+ }
61
+
62
+
63
+ def _completed(*, returncode: int = 0, stdout: str = "", stderr: str = "") -> MagicMock:
64
+ return MagicMock(returncode=returncode, stdout=stdout, stderr=stderr)
65
+
66
+
67
+ def _command(mock_run: MagicMock, index: int = 0) -> list[str]:
68
+ return mock_run.call_args_list[index].args[0]
69
+
70
+
71
+ def test_github_provider_advertises_pull_request_capability(
72
+ github_provider: VCSPluginManager,
73
+ ) -> None:
74
+ assert github_provider.supports_pull_requests() is True
75
+
76
+
77
+ @patch(_MOCK_TARGET)
78
+ def test_list_pull_requests_open_requests_state_open_and_normalizes_records(
79
+ mock_run: MagicMock, github_provider: VCSPluginManager
80
+ ) -> None:
81
+ mock_run.return_value = _completed(stdout=json.dumps([_json_pr()]))
82
+
83
+ pull_requests = github_provider.list_pull_requests(
84
+ "/workspace", state="open", limit=25
85
+ )
86
+
87
+ assert pull_requests == [
88
+ PullRequestWire(
89
+ number=42,
90
+ title="Add feature",
91
+ state="open",
92
+ provider_id="PR_kwDOpr42",
93
+ url="https://github.example/owner/repo/pull/42",
94
+ body="Description",
95
+ is_draft=False,
96
+ author="hubot",
97
+ head_ref="feature-branch",
98
+ base_ref="main",
99
+ created_at="2026-07-14T10:00:00Z",
100
+ updated_at="2026-07-15T11:00:00Z",
101
+ closed_at="",
102
+ merged_at="",
103
+ )
104
+ ]
105
+ assert _command(mock_run) == [
106
+ "gh",
107
+ "pr",
108
+ "list",
109
+ "--state",
110
+ "open",
111
+ "--limit",
112
+ "25",
113
+ "--json",
114
+ _JSON_FIELDS,
115
+ ]
116
+ assert mock_run.call_args.kwargs["cwd"] == "/workspace"
117
+
118
+
119
+ @patch(_MOCK_TARGET)
120
+ def test_list_pull_requests_all_requests_state_all_directly(
121
+ mock_run: MagicMock, github_provider: VCSPluginManager
122
+ ) -> None:
123
+ mock_run.return_value = _completed(stdout="[]")
124
+
125
+ assert github_provider.list_pull_requests("/workspace", state="all", limit=10) == []
126
+
127
+ command = _command(mock_run)
128
+ assert command[command.index("--state") + 1] == "all"
129
+ assert command[command.index("--limit") + 1] == "10"
130
+
131
+
132
+ @patch(_MOCK_TARGET)
133
+ def test_list_pull_requests_closed_folds_in_merged_and_excludes_open(
134
+ mock_run: MagicMock, github_provider: VCSPluginManager
135
+ ) -> None:
136
+ open_pr = _json_pr(1, state="OPEN")
137
+ closed_pr = _json_pr(2, state="CLOSED", closed_at="2026-07-20T00:00:00Z")
138
+ merged_pr = _json_pr(
139
+ 3,
140
+ state="MERGED",
141
+ closed_at="2026-07-21T00:00:00Z",
142
+ merged_at="2026-07-21T00:00:00Z",
143
+ )
144
+ mock_run.return_value = _completed(
145
+ stdout=json.dumps([open_pr, closed_pr, merged_pr])
146
+ )
147
+
148
+ pull_requests = github_provider.list_pull_requests("/workspace", state="closed")
149
+
150
+ assert [pr.number for pr in pull_requests] == [2, 3]
151
+ assert all(pr.state == "closed" for pr in pull_requests)
152
+ merged = next(pr for pr in pull_requests if pr.number == 3)
153
+ assert merged.merged_at == "2026-07-21T00:00:00Z"
154
+ closed_unmerged = next(pr for pr in pull_requests if pr.number == 2)
155
+ assert closed_unmerged.merged_at == ""
156
+
157
+ command = _command(mock_run)
158
+ assert command[command.index("--state") + 1] == "all"
159
+ assert command[command.index("--limit") + 1] == "1000000"
160
+
161
+
162
+ @patch(_MOCK_TARGET)
163
+ def test_list_pull_requests_non_positive_limit_requests_all_available_results(
164
+ mock_run: MagicMock, github_provider: VCSPluginManager
165
+ ) -> None:
166
+ mock_run.return_value = _completed(stdout="[]")
167
+
168
+ assert github_provider.list_pull_requests("/workspace", limit=0) == []
169
+
170
+ command = _command(mock_run)
171
+ assert command[command.index("--limit") + 1] == "1000000"
172
+
173
+
174
+ @patch(_MOCK_TARGET)
175
+ def test_list_pull_requests_normalizes_draft_flag(
176
+ mock_run: MagicMock, github_provider: VCSPluginManager
177
+ ) -> None:
178
+ mock_run.return_value = _completed(stdout=json.dumps([_json_pr(is_draft=True)]))
179
+
180
+ pull_requests = github_provider.list_pull_requests("/workspace")
181
+
182
+ assert pull_requests[0].is_draft is True
183
+
184
+
185
+ @pytest.mark.parametrize(
186
+ "stdout",
187
+ [
188
+ "not json",
189
+ "{}",
190
+ json.dumps([{"number": 1, "title": "Missing state"}]),
191
+ ],
192
+ )
193
+ @patch(_MOCK_TARGET)
194
+ def test_malformed_pull_request_json_raises_typed_error(
195
+ mock_run: MagicMock, stdout: str, github_provider: VCSPluginManager
196
+ ) -> None:
197
+ mock_run.return_value = _completed(stdout=stdout)
198
+
199
+ with pytest.raises(GitHubIssueError, match="gh pr list"):
200
+ github_provider.list_pull_requests("/workspace")
@@ -1,3 +0,0 @@
1
- {
2
- ".": "0.2.3"
3
- }
File without changes
File without changes
File without changes
File without changes
File without changes