claude-dev-env 1.36.2 → 1.37.1
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.
- package/_shared/pr-loop/scripts/config/preflight_constants.py +29 -8
- package/_shared/pr-loop/scripts/preflight.py +242 -20
- package/_shared/pr-loop/scripts/tests/test_preflight.py +362 -25
- package/_shared/pr-loop/scripts/tests/test_preflight_constants.py +9 -14
- package/hooks/blocking/code_rules_enforcer.py +269 -23
- package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +157 -1
- package/hooks/config/test_unused_module_import_constants.py +48 -0
- package/hooks/config/unused_module_import_constants.py +41 -0
- package/package.json +1 -1
- package/rules/gh-paginate.md +4 -50
- package/rules/no-historical-clutter.md +36 -0
- package/skills/bg-agent/SKILL.md +69 -0
- package/skills/bugteam/CONSTRAINTS.md +10 -19
- package/skills/bugteam/PROMPTS.md +21 -14
- package/skills/bugteam/SKILL.md +122 -208
- package/skills/bugteam/SKILL_EVALS.md +75 -114
- package/skills/bugteam/reference/README.md +2 -4
- package/skills/bugteam/reference/audit-and-teammates.md +21 -48
- package/skills/bugteam/reference/audit-contract.md +7 -7
- package/skills/bugteam/reference/design-rationale.md +3 -8
- package/skills/bugteam/reference/team-setup.md +11 -19
- package/skills/bugteam/reference/teardown-publish-permissions.md +2 -14
- package/skills/bugteam/scripts/config/__init__.py +0 -0
- package/skills/bugteam/scripts/config/reflow_skill_md_constants.py +12 -0
- package/skills/bugteam/scripts/reflow_skill_md.py +51 -47
- package/skills/bugteam/sources.md +1 -25
- package/skills/bugteam/test_skill_additions.py +4 -13
- package/skills/fresh-branch/SKILL.md +71 -0
- package/skills/gotcha/SKILL.md +73 -0
- package/skills/monitor-open-prs/SKILL.md +4 -37
- package/skills/monitor-open-prs/test_skill_contract.py +0 -5
- package/skills/pr-converge/SKILL.md +60 -1298
- package/skills/pr-converge/reference/convergence-gates.md +122 -0
- package/skills/pr-converge/reference/examples.md +76 -0
- package/skills/pr-converge/reference/fix-protocol.md +56 -0
- package/skills/pr-converge/reference/ground-rules.md +13 -0
- package/skills/pr-converge/reference/multi-pr-orchestration.md +204 -0
- package/skills/pr-converge/reference/per-tick.md +204 -0
- package/skills/pr-converge/reference/state-schema.md +19 -0
- package/skills/pr-converge/reference/stop-conditions.md +26 -0
- package/skills/pr-converge/scripts/README.md +36 -9
- package/skills/pr-converge/scripts/check_pr_mergeability.py +1 -2
- package/skills/pr-converge/scripts/config/pr_converge_constants.py +74 -5
- package/skills/pr-converge/scripts/config/reflow_skill_md_constants.py +13 -0
- package/skills/pr-converge/scripts/config/test_pr_converge_constants.py +0 -24
- package/skills/pr-converge/scripts/cursor-agents-continue.ahk +22 -2
- package/skills/pr-converge/scripts/fetch_bugbot_inline_comments.py +19 -59
- package/skills/pr-converge/scripts/fetch_bugbot_reviews.py +15 -61
- package/skills/pr-converge/scripts/fetch_claude_inline_comments.py +70 -0
- package/skills/pr-converge/scripts/fetch_claude_reviews.py +61 -0
- package/skills/pr-converge/scripts/fetch_copilot_inline_comments.py +19 -61
- package/skills/pr-converge/scripts/fetch_copilot_reviews.py +14 -74
- package/skills/pr-converge/scripts/reflow_skill_md.py +71 -50
- package/skills/pr-converge/scripts/reviewer_fetch_core.py +153 -0
- package/skills/pr-converge/scripts/reviewer_specs.py +98 -0
- package/skills/pr-converge/scripts/test_cursor_agents_continue.py +65 -0
- package/skills/pr-converge/scripts/test_fetch_bugbot_inline_comments.py +107 -6
- package/skills/pr-converge/scripts/test_fetch_bugbot_reviews.py +85 -6
- package/skills/pr-converge/scripts/test_fetch_claude_inline_comments.py +485 -0
- package/skills/pr-converge/scripts/test_fetch_claude_reviews.py +368 -0
- package/skills/pr-converge/scripts/test_fetch_copilot_inline_comments.py +74 -6
- package/skills/pr-converge/scripts/test_fetch_copilot_reviews.py +94 -8
- package/skills/pr-converge/scripts/test_reflow_skill_md.py +162 -0
- package/skills/pr-converge/scripts/test_reviewer_fetch_core.py +448 -0
- package/skills/pr-converge/scripts/test_reviewer_specs.py +107 -0
- package/skills/pr-converge/scripts/test_view_pr_context.py +44 -0
- package/skills/pr-converge/scripts/view_pr_context.py +35 -4
- package/skills/pr-converge/workflows/schedule-wakeup-loop.md +24 -22
- package/skills/bugteam/reference/workflow-path-a-orchestrated-teams.md +0 -113
- package/skills/bugteam/reference/workflow-path-b-task-harness.md +0 -48
- package/skills/bugteam/test_team_lifecycle.py +0 -103
- package/skills/monitor-open-prs/test_team_lifecycle.py +0 -46
- package/skills/pr-converge/scripts/open_followup_copilot_pr.py +0 -136
- package/skills/pr-converge/scripts/test_open_followup_copilot_pr.py +0 -236
- package/skills/pr-converge/test_team_lifecycle.py +0 -56
- package/skills/pr-converge/workflows/ahk-auto-continue-loop.md +0 -108
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Reviewer specifications shared by the per-reviewer fetch entry-point scripts.
|
|
2
|
+
|
|
3
|
+
A ReviewerSpec carries the two knobs that vary across the bugbot, copilot, and
|
|
4
|
+
claude reviewers: the case-insensitive substring used to match the reviewer's
|
|
5
|
+
GitHub login, and the callable that classifies a single review payload as
|
|
6
|
+
``"clean"`` or ``"dirty"``. The spec instances declared at module scope are
|
|
7
|
+
imported by the thin entry-point wrappers (``fetch_bugbot_reviews.py`` etc.)
|
|
8
|
+
and by ``reviewer_fetch_core``.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
import sys
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Callable
|
|
18
|
+
|
|
19
|
+
if str(Path(__file__).resolve().parent) not in sys.path:
|
|
20
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
21
|
+
|
|
22
|
+
from evict_cached_config_modules import evict_cached_config_modules
|
|
23
|
+
|
|
24
|
+
evict_cached_config_modules()
|
|
25
|
+
|
|
26
|
+
from config.pr_converge_constants import (
|
|
27
|
+
ALL_CLAUDE_DIRTY_REVIEW_STATES,
|
|
28
|
+
ALL_COPILOT_DIRTY_REVIEW_STATES,
|
|
29
|
+
BUGBOT_DIRTY_BODY_REGEX,
|
|
30
|
+
CLAUDE_CLEAN_REVIEW_STATE,
|
|
31
|
+
CLAUDE_LOGIN_FILTER_SUBSTRING,
|
|
32
|
+
CLAUDE_SOFT_DIRTY_REVIEW_STATE,
|
|
33
|
+
COPILOT_CLEAN_REVIEW_STATE,
|
|
34
|
+
COPILOT_LOGIN_FILTER_SUBSTRING,
|
|
35
|
+
COPILOT_SOFT_DIRTY_REVIEW_STATE,
|
|
36
|
+
CURSOR_LOGIN_FILTER_SUBSTRING,
|
|
37
|
+
)
|
|
38
|
+
from review_field_helpers import body_of, state_of
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class ReviewerSpec:
|
|
43
|
+
"""Per-reviewer configuration: login substring filter plus classify callable."""
|
|
44
|
+
|
|
45
|
+
login_filter_substring: str
|
|
46
|
+
classify_review: Callable[[dict[str, object]], str]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _classify_bugbot_review(field_by_key: dict[str, object]) -> str:
|
|
50
|
+
review_body = body_of(field_by_key)
|
|
51
|
+
if re.search(BUGBOT_DIRTY_BODY_REGEX, review_body):
|
|
52
|
+
return "dirty"
|
|
53
|
+
return "clean"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _make_state_based_classifier(
|
|
57
|
+
*,
|
|
58
|
+
clean_state: str,
|
|
59
|
+
all_dirty_states: tuple[str, ...],
|
|
60
|
+
soft_dirty_state: str,
|
|
61
|
+
) -> Callable[[dict[str, object]], str]:
|
|
62
|
+
def classify_review(field_by_key: dict[str, object]) -> str:
|
|
63
|
+
review_state = state_of(field_by_key)
|
|
64
|
+
if review_state == clean_state:
|
|
65
|
+
return "clean"
|
|
66
|
+
if review_state not in all_dirty_states:
|
|
67
|
+
return "clean"
|
|
68
|
+
if review_state == soft_dirty_state and not body_of(field_by_key):
|
|
69
|
+
return "clean"
|
|
70
|
+
return "dirty"
|
|
71
|
+
|
|
72
|
+
return classify_review
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
bugbot_spec = ReviewerSpec(
|
|
76
|
+
login_filter_substring=CURSOR_LOGIN_FILTER_SUBSTRING,
|
|
77
|
+
classify_review=_classify_bugbot_review,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
copilot_spec = ReviewerSpec(
|
|
82
|
+
login_filter_substring=COPILOT_LOGIN_FILTER_SUBSTRING,
|
|
83
|
+
classify_review=_make_state_based_classifier(
|
|
84
|
+
clean_state=COPILOT_CLEAN_REVIEW_STATE,
|
|
85
|
+
all_dirty_states=ALL_COPILOT_DIRTY_REVIEW_STATES,
|
|
86
|
+
soft_dirty_state=COPILOT_SOFT_DIRTY_REVIEW_STATE,
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
claude_spec = ReviewerSpec(
|
|
92
|
+
login_filter_substring=CLAUDE_LOGIN_FILTER_SUBSTRING,
|
|
93
|
+
classify_review=_make_state_based_classifier(
|
|
94
|
+
clean_state=CLAUDE_CLEAN_REVIEW_STATE,
|
|
95
|
+
all_dirty_states=ALL_CLAUDE_DIRTY_REVIEW_STATES,
|
|
96
|
+
soft_dirty_state=CLAUDE_SOFT_DIRTY_REVIEW_STATE,
|
|
97
|
+
),
|
|
98
|
+
)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Tests for the Cursor Agents AutoHotkey pacer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _script_text() -> str:
|
|
9
|
+
return (Path(__file__).resolve().parent / "cursor-agents-continue.ahk").read_text(
|
|
10
|
+
encoding="utf-8"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _function_body(function_name: str, next_marker: str) -> str:
|
|
15
|
+
script_text = _script_text()
|
|
16
|
+
function_start = script_text.index(function_name)
|
|
17
|
+
function_end = script_text.index(next_marker, function_start)
|
|
18
|
+
return script_text[function_start:function_end]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_should_fallback_when_pwsh_is_unavailable() -> None:
|
|
22
|
+
script_text = _script_text()
|
|
23
|
+
terminate_body = _function_body(
|
|
24
|
+
"terminate_other_script_instances() {",
|
|
25
|
+
"\n}\n\nrun_stop_script_with_shell",
|
|
26
|
+
)
|
|
27
|
+
helper_body = _function_body(
|
|
28
|
+
"run_stop_script_with_shell(shell_name, stop_script) {",
|
|
29
|
+
"\n}\n\nterminate_other_script_instances()",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
first_shell_check = (
|
|
33
|
+
"if run_stop_script_with_shell(POWERSHELL_CORE_SHELL_NAME, stop_script)"
|
|
34
|
+
)
|
|
35
|
+
fallback_shell_check = (
|
|
36
|
+
"if run_stop_script_with_shell(WINDOWS_POWERSHELL_SHELL_NAME, stop_script)"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
assert 'POWERSHELL_CORE_SHELL_NAME := "pwsh"' in script_text
|
|
40
|
+
assert 'WINDOWS_POWERSHELL_SHELL_NAME := "powershell.exe"' in script_text
|
|
41
|
+
assert "STOP_SCRIPT_ARGUMENTS_FORMAT :=" in script_text
|
|
42
|
+
assert 'RUN_WAIT_WINDOW_OPTION := "Hide"' in script_text
|
|
43
|
+
assert first_shell_check in terminate_body
|
|
44
|
+
assert fallback_shell_check in terminate_body
|
|
45
|
+
assert "throw Error(Format(STOP_SCRIPT_FAILURE_MESSAGE_FORMAT" in terminate_body
|
|
46
|
+
assert terminate_body.index(first_shell_check) < terminate_body.index(
|
|
47
|
+
fallback_shell_check
|
|
48
|
+
)
|
|
49
|
+
assert terminate_body.index(fallback_shell_check) < terminate_body.index(
|
|
50
|
+
"throw Error"
|
|
51
|
+
)
|
|
52
|
+
assert '"pwsh"' not in terminate_body
|
|
53
|
+
assert '"powershell.exe"' not in terminate_body
|
|
54
|
+
assert "try {" in helper_body
|
|
55
|
+
assert "catch {" in helper_body
|
|
56
|
+
assert (
|
|
57
|
+
"Format(STOP_SCRIPT_ARGUMENTS_FORMAT, stop_script, ProcessExist())"
|
|
58
|
+
in helper_body
|
|
59
|
+
)
|
|
60
|
+
assert (
|
|
61
|
+
"RunWait(shell_name stop_command_arguments, , RUN_WAIT_WINDOW_OPTION)"
|
|
62
|
+
in helper_body
|
|
63
|
+
)
|
|
64
|
+
assert "-NoProfile" not in helper_body
|
|
65
|
+
assert '"Hide"' not in helper_body
|
|
@@ -286,12 +286,113 @@ def test_should_flatten_across_pages() -> None:
|
|
|
286
286
|
]
|
|
287
287
|
|
|
288
288
|
|
|
289
|
-
def
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
289
|
+
def test_should_match_login_case_insensitively() -> None:
|
|
290
|
+
pages_payload = json.dumps(
|
|
291
|
+
[
|
|
292
|
+
[
|
|
293
|
+
{
|
|
294
|
+
"id": 300,
|
|
295
|
+
"user": {"login": "Cursor"},
|
|
296
|
+
"commit_id": "abc123",
|
|
297
|
+
"pull_request_review_id": 1,
|
|
298
|
+
"body": "uppercase login",
|
|
299
|
+
"path": "x.py",
|
|
300
|
+
"line": 5,
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
"id": 301,
|
|
304
|
+
"user": {"login": "CURSOR[bot]"},
|
|
305
|
+
"commit_id": "abc123",
|
|
306
|
+
"pull_request_review_id": 1,
|
|
307
|
+
"body": "screaming login",
|
|
308
|
+
"path": "x.py",
|
|
309
|
+
"line": 6,
|
|
310
|
+
},
|
|
311
|
+
]
|
|
312
|
+
]
|
|
313
|
+
)
|
|
314
|
+
with patch.object(
|
|
315
|
+
fetch_bugbot_inline_comments_module,
|
|
316
|
+
"fetch_bugbot_reviews",
|
|
317
|
+
return_value=_default_review_for_head(commit="abc123", review_id=1),
|
|
318
|
+
), patch("subprocess.run") as mock_run:
|
|
319
|
+
mock_run.return_value = _completed(pages_payload)
|
|
320
|
+
all_inline_comments = (
|
|
321
|
+
fetch_bugbot_inline_comments_module.fetch_bugbot_inline_comments(
|
|
322
|
+
owner="acme", repo="widget", number=42, current_head="abc123"
|
|
323
|
+
)
|
|
324
|
+
)
|
|
325
|
+
assert {each_comment["comment_id"] for each_comment in all_inline_comments} == {300, 301}
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def test_should_match_login_containing_cursor_substring() -> None:
|
|
329
|
+
pages_payload = json.dumps(
|
|
330
|
+
[
|
|
331
|
+
[
|
|
332
|
+
{
|
|
333
|
+
"id": 400,
|
|
334
|
+
"user": {"login": "internal-cursor-fork[bot]"},
|
|
335
|
+
"commit_id": "abc123",
|
|
336
|
+
"pull_request_review_id": 1,
|
|
337
|
+
"body": "non-canonical login still matches",
|
|
338
|
+
"path": "x.py",
|
|
339
|
+
"line": 5,
|
|
340
|
+
}
|
|
341
|
+
]
|
|
342
|
+
]
|
|
343
|
+
)
|
|
344
|
+
with patch.object(
|
|
345
|
+
fetch_bugbot_inline_comments_module,
|
|
346
|
+
"fetch_bugbot_reviews",
|
|
347
|
+
return_value=_default_review_for_head(commit="abc123", review_id=1),
|
|
348
|
+
), patch("subprocess.run") as mock_run:
|
|
349
|
+
mock_run.return_value = _completed(pages_payload)
|
|
350
|
+
all_inline_comments = (
|
|
351
|
+
fetch_bugbot_inline_comments_module.fetch_bugbot_inline_comments(
|
|
352
|
+
owner="acme", repo="widget", number=42, current_head="abc123"
|
|
353
|
+
)
|
|
354
|
+
)
|
|
355
|
+
assert len(all_inline_comments) == 1
|
|
356
|
+
assert all_inline_comments[0]["comment_id"] == 400
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def test_should_exclude_login_without_cursor_substring() -> None:
|
|
360
|
+
pages_payload = json.dumps(
|
|
361
|
+
[
|
|
362
|
+
[
|
|
363
|
+
{
|
|
364
|
+
"id": 500,
|
|
365
|
+
"user": {"login": "copilot-pull-request-reviewer[bot]"},
|
|
366
|
+
"commit_id": "abc123",
|
|
367
|
+
"pull_request_review_id": 1,
|
|
368
|
+
"body": "copilot finding",
|
|
369
|
+
"path": "x.py",
|
|
370
|
+
"line": 5,
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
"id": 501,
|
|
374
|
+
"user": {"login": "dependabot[bot]"},
|
|
375
|
+
"commit_id": "abc123",
|
|
376
|
+
"pull_request_review_id": 1,
|
|
377
|
+
"body": "dependency bump",
|
|
378
|
+
"path": "x.py",
|
|
379
|
+
"line": 6,
|
|
380
|
+
},
|
|
381
|
+
]
|
|
382
|
+
]
|
|
383
|
+
)
|
|
384
|
+
with patch.object(
|
|
385
|
+
fetch_bugbot_inline_comments_module,
|
|
386
|
+
"fetch_bugbot_reviews",
|
|
387
|
+
return_value=_default_review_for_head(commit="abc123", review_id=1),
|
|
388
|
+
), patch("subprocess.run") as mock_run:
|
|
389
|
+
mock_run.return_value = _completed(pages_payload)
|
|
390
|
+
all_inline_comments = (
|
|
391
|
+
fetch_bugbot_inline_comments_module.fetch_bugbot_inline_comments(
|
|
392
|
+
owner="acme", repo="widget", number=42, current_head="abc123"
|
|
393
|
+
)
|
|
394
|
+
)
|
|
395
|
+
assert all_inline_comments == []
|
|
295
396
|
|
|
296
397
|
|
|
297
398
|
def test_should_raise_when_gh_subprocess_fails() -> None:
|
|
@@ -176,12 +176,91 @@ def test_should_classify_clean_review_when_body_lacks_findings_pattern() -> None
|
|
|
176
176
|
assert all_reviews[0]["classification"] == "clean"
|
|
177
177
|
|
|
178
178
|
|
|
179
|
-
def
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
179
|
+
def test_should_match_login_case_insensitively() -> None:
|
|
180
|
+
pages_payload = json.dumps(
|
|
181
|
+
[
|
|
182
|
+
[
|
|
183
|
+
{
|
|
184
|
+
"id": 1,
|
|
185
|
+
"user": {"login": "Cursor"},
|
|
186
|
+
"commit_id": "abc",
|
|
187
|
+
"submitted_at": "2026-01-01T00:00:00Z",
|
|
188
|
+
"body": "Cursor Bugbot has reviewed your changes and found 1 potential issue.",
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"id": 2,
|
|
192
|
+
"user": {"login": "CURSOR[bot]"},
|
|
193
|
+
"commit_id": "abc",
|
|
194
|
+
"submitted_at": "2026-01-02T00:00:00Z",
|
|
195
|
+
"body": "Bugbot reviewed your changes and found no new issues!",
|
|
196
|
+
},
|
|
197
|
+
]
|
|
198
|
+
]
|
|
199
|
+
)
|
|
200
|
+
with patch("subprocess.run") as mock_run:
|
|
201
|
+
mock_run.return_value = _completed(pages_payload)
|
|
202
|
+
all_reviews = fetch_bugbot_reviews_module.fetch_bugbot_reviews(
|
|
203
|
+
owner="acme", repo="widget", number=42
|
|
204
|
+
)
|
|
205
|
+
assert {each_review["review_id"] for each_review in all_reviews} == {1, 2}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_should_match_login_containing_cursor_substring() -> None:
|
|
209
|
+
pages_payload = json.dumps(
|
|
210
|
+
[
|
|
211
|
+
[
|
|
212
|
+
{
|
|
213
|
+
"id": 1,
|
|
214
|
+
"user": {"login": "cursor[bot]"},
|
|
215
|
+
"commit_id": "abc",
|
|
216
|
+
"submitted_at": "2026-01-01T00:00:00Z",
|
|
217
|
+
"body": "Cursor Bugbot has reviewed your changes and found 1 potential issue.",
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"id": 2,
|
|
221
|
+
"user": {"login": "internal-cursor-fork[bot]"},
|
|
222
|
+
"commit_id": "abc",
|
|
223
|
+
"submitted_at": "2026-01-02T00:00:00Z",
|
|
224
|
+
"body": "Cursor Bugbot has reviewed your changes and found 2 potential issues.",
|
|
225
|
+
},
|
|
226
|
+
]
|
|
227
|
+
]
|
|
228
|
+
)
|
|
229
|
+
with patch("subprocess.run") as mock_run:
|
|
230
|
+
mock_run.return_value = _completed(pages_payload)
|
|
231
|
+
all_reviews = fetch_bugbot_reviews_module.fetch_bugbot_reviews(
|
|
232
|
+
owner="acme", repo="widget", number=42
|
|
233
|
+
)
|
|
234
|
+
assert {each_review["review_id"] for each_review in all_reviews} == {1, 2}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def test_should_exclude_login_without_cursor_substring() -> None:
|
|
238
|
+
pages_payload = json.dumps(
|
|
239
|
+
[
|
|
240
|
+
[
|
|
241
|
+
{
|
|
242
|
+
"id": 1,
|
|
243
|
+
"user": {"login": "copilot-pull-request-reviewer[bot]"},
|
|
244
|
+
"commit_id": "abc",
|
|
245
|
+
"submitted_at": "2026-01-01T00:00:00Z",
|
|
246
|
+
"body": "copilot finding",
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"id": 2,
|
|
250
|
+
"user": {"login": "dependabot[bot]"},
|
|
251
|
+
"commit_id": "abc",
|
|
252
|
+
"submitted_at": "2026-01-02T00:00:00Z",
|
|
253
|
+
"body": "dependency bump",
|
|
254
|
+
},
|
|
255
|
+
]
|
|
256
|
+
]
|
|
257
|
+
)
|
|
258
|
+
with patch("subprocess.run") as mock_run:
|
|
259
|
+
mock_run.return_value = _completed(pages_payload)
|
|
260
|
+
all_reviews = fetch_bugbot_reviews_module.fetch_bugbot_reviews(
|
|
261
|
+
owner="acme", repo="widget", number=42
|
|
262
|
+
)
|
|
263
|
+
assert all_reviews == []
|
|
185
264
|
|
|
186
265
|
|
|
187
266
|
def test_should_raise_when_gh_subprocess_fails() -> None:
|