claude-dev-env 1.37.0 → 1.38.0
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/CLAUDE.md +3 -0
- package/_shared/pr-loop/audit-contract.md +4 -3
- package/_shared/pr-loop/fix-protocol.md +2 -0
- package/_shared/pr-loop/gh-payloads.md +38 -37
- package/_shared/pr-loop/scripts/README.md +0 -1
- package/_shared/pr-loop/scripts/preflight.py +2 -1
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +2 -2
- package/_shared/pr-loop/scripts/tests/test_preflight.py +22 -0
- package/_shared/pr-loop/state-schema.md +10 -10
- package/agents/clean-coder.md +4 -0
- package/agents/code-quality-agent.md +23 -85
- package/agents/groq-coder.md +8 -6
- package/hooks/blocking/__init__.py +0 -0
- package/hooks/blocking/hedging_language_blocker.py +2 -2
- package/hooks/blocking/state_description_blocker.py +243 -0
- package/hooks/blocking/tdd_enforcer.py +94 -0
- package/hooks/blocking/test_hedging_language_blocker.py +1 -1
- package/hooks/blocking/test_state_description_blocker.py +618 -0
- package/hooks/blocking/test_tdd_enforcer.py +152 -0
- package/hooks/config/state_description_blocker_constants.py +130 -0
- package/hooks/hooks.json +10 -0
- package/package.json +1 -1
- package/rules/gh-paginate.md +4 -50
- package/rules/no-historical-clutter.md +57 -0
- package/scripts/config/groq_bugteam_config.py +13 -5
- package/skills/bugteam/CONSTRAINTS.md +20 -27
- package/skills/bugteam/EXAMPLES.md +1 -1
- package/skills/bugteam/PROMPTS.md +78 -42
- package/skills/bugteam/SKILL.md +76 -63
- package/skills/bugteam/SKILL_EVALS.md +12 -12
- package/skills/bugteam/reference/audit-and-teammates.md +21 -48
- package/skills/bugteam/reference/audit-contract.md +7 -7
- package/skills/bugteam/reference/github-pr-reviews.md +31 -31
- package/skills/bugteam/reference/team-setup.md +1 -1
- package/skills/bugteam/reference/teardown-publish-permissions.md +4 -4
- package/skills/copilot-review/SKILL.md +7 -14
- package/skills/findbugs/SKILL.md +2 -2
- package/skills/fixbugs/SKILL.md +1 -1
- package/skills/monitor-open-prs/SKILL.md +6 -6
- package/skills/pr-converge/SKILL.md +7 -6
- package/skills/pr-converge/reference/convergence-gates.md +46 -44
- package/skills/pr-converge/reference/examples.md +4 -4
- package/skills/pr-converge/reference/fix-protocol.md +8 -8
- package/skills/pr-converge/reference/multi-pr-orchestration.md +10 -10
- package/skills/pr-converge/reference/per-tick.md +24 -36
- package/skills/pr-converge/reference/stop-conditions.md +7 -7
- package/skills/pr-converge/scripts/README.md +65 -117
- package/skills/pr-review-responder/EXAMPLES.md +2 -2
- package/skills/pr-review-responder/PRINCIPLES.md +2 -8
- package/skills/pr-review-responder/README.md +7 -48
- package/skills/pr-review-responder/SKILL.md +2 -3
- package/skills/pr-review-responder/TESTING.md +8 -65
- package/skills/qbug/SKILL.md +10 -16
- package/_shared/pr-loop/scripts/config/gh_util_constants.py +0 -31
- package/_shared/pr-loop/scripts/gh_util.py +0 -193
- package/_shared/pr-loop/scripts/tests/test_gh_util.py +0 -257
- package/_shared/pr-loop/scripts/tests/test_gh_util_constants.py +0 -61
- package/skills/pr-converge/scripts/check_pr_mergeability.py +0 -78
- package/skills/pr-converge/scripts/config/pr_converge_constants.py +0 -118
- package/skills/pr-converge/scripts/config/test_pr_converge_constants.py +0 -152
- package/skills/pr-converge/scripts/fetch_bugbot_inline_comments.py +0 -70
- package/skills/pr-converge/scripts/fetch_bugbot_reviews.py +0 -57
- package/skills/pr-converge/scripts/fetch_claude_inline_comments.py +0 -70
- package/skills/pr-converge/scripts/fetch_claude_reviews.py +0 -61
- package/skills/pr-converge/scripts/fetch_copilot_inline_comments.py +0 -70
- package/skills/pr-converge/scripts/fetch_copilot_reviews.py +0 -61
- package/skills/pr-converge/scripts/mark_pr_ready.py +0 -54
- package/skills/pr-converge/scripts/post-bugbot-run.helpers.ps1 +0 -49
- package/skills/pr-converge/scripts/post-bugbot-run.ps1 +0 -33
- package/skills/pr-converge/scripts/reply_to_inline_comment.py +0 -84
- package/skills/pr-converge/scripts/request_copilot_review.py +0 -71
- package/skills/pr-converge/scripts/resolve_pr_head.py +0 -58
- package/skills/pr-converge/scripts/review_field_helpers.py +0 -43
- package/skills/pr-converge/scripts/reviewer_fetch_core.py +0 -153
- package/skills/pr-converge/scripts/reviewer_specs.py +0 -98
- package/skills/pr-converge/scripts/test_check_pr_mergeability.py +0 -126
- package/skills/pr-converge/scripts/test_fetch_bugbot_inline_comments.py +0 -443
- package/skills/pr-converge/scripts/test_fetch_bugbot_reviews.py +0 -299
- package/skills/pr-converge/scripts/test_fetch_claude_inline_comments.py +0 -485
- package/skills/pr-converge/scripts/test_fetch_claude_reviews.py +0 -368
- package/skills/pr-converge/scripts/test_fetch_copilot_inline_comments.py +0 -440
- package/skills/pr-converge/scripts/test_fetch_copilot_reviews.py +0 -366
- package/skills/pr-converge/scripts/test_mark_pr_ready.py +0 -69
- package/skills/pr-converge/scripts/test_post_bugbot_run.py +0 -195
- package/skills/pr-converge/scripts/test_reply_to_inline_comment.py +0 -159
- package/skills/pr-converge/scripts/test_request_copilot_review.py +0 -101
- package/skills/pr-converge/scripts/test_resolve_pr_head.py +0 -79
- package/skills/pr-converge/scripts/test_review_field_helpers.py +0 -80
- package/skills/pr-converge/scripts/test_reviewer_fetch_core.py +0 -448
- package/skills/pr-converge/scripts/test_reviewer_specs.py +0 -107
- package/skills/pr-converge/scripts/test_trigger_bugbot.py +0 -139
- package/skills/pr-converge/scripts/test_view_pr_context.py +0 -111
- package/skills/pr-converge/scripts/trigger_bugbot.py +0 -77
- package/skills/pr-converge/scripts/view_pr_context.py +0 -47
- package/skills/pr-review-responder/scripts/respond_to_reviews.py +0 -376
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
"""Tests for check_pr_mergeability.
|
|
2
|
-
|
|
3
|
-
Covers:
|
|
4
|
-
- gh pr view is invoked with the documented mergeability --json field list
|
|
5
|
-
- the parsed JSON object is returned with mergeable/mergeStateStatus/headRefOid keys
|
|
6
|
-
- subprocess errors propagate
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
import importlib.util
|
|
12
|
-
import json
|
|
13
|
-
import subprocess
|
|
14
|
-
from pathlib import Path
|
|
15
|
-
from types import ModuleType
|
|
16
|
-
from unittest.mock import MagicMock, patch
|
|
17
|
-
|
|
18
|
-
import pytest
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _load_module() -> ModuleType:
|
|
22
|
-
module_path = Path(__file__).parent / "check_pr_mergeability.py"
|
|
23
|
-
spec = importlib.util.spec_from_file_location("check_pr_mergeability", module_path)
|
|
24
|
-
assert spec is not None
|
|
25
|
-
assert spec.loader is not None
|
|
26
|
-
module = importlib.util.module_from_spec(spec)
|
|
27
|
-
spec.loader.exec_module(module)
|
|
28
|
-
return module
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
check_pr_mergeability_module = _load_module()
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def _completed(stdout: str) -> subprocess.CompletedProcess:
|
|
35
|
-
process = MagicMock(spec=subprocess.CompletedProcess)
|
|
36
|
-
process.stdout = stdout
|
|
37
|
-
process.returncode = 0
|
|
38
|
-
return process
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def test_should_invoke_gh_pr_view_with_mergeability_field_list() -> None:
|
|
42
|
-
payload = json.dumps(
|
|
43
|
-
{
|
|
44
|
-
"mergeable": "MERGEABLE",
|
|
45
|
-
"mergeStateStatus": "CLEAN",
|
|
46
|
-
"headRefOid": "abc123",
|
|
47
|
-
}
|
|
48
|
-
)
|
|
49
|
-
with patch("subprocess.run") as mock_run:
|
|
50
|
-
mock_run.return_value = _completed(payload)
|
|
51
|
-
check_pr_mergeability_module.check_pr_mergeability(
|
|
52
|
-
owner="acme", repo="widget", number=42
|
|
53
|
-
)
|
|
54
|
-
invoked_argv = mock_run.call_args[0][0]
|
|
55
|
-
assert invoked_argv[0:3] == ["gh", "pr", "view"]
|
|
56
|
-
assert "--json" in invoked_argv
|
|
57
|
-
fields_arg = invoked_argv[invoked_argv.index("--json") + 1]
|
|
58
|
-
for required_field in ("mergeable", "mergeStateStatus", "headRefOid"):
|
|
59
|
-
assert required_field in fields_arg
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def test_should_pass_pr_number_and_repo_arg_for_explicit_targeting() -> None:
|
|
63
|
-
payload = json.dumps(
|
|
64
|
-
{
|
|
65
|
-
"mergeable": "MERGEABLE",
|
|
66
|
-
"mergeStateStatus": "CLEAN",
|
|
67
|
-
"headRefOid": "abc123",
|
|
68
|
-
}
|
|
69
|
-
)
|
|
70
|
-
with patch("subprocess.run") as mock_run:
|
|
71
|
-
mock_run.return_value = _completed(payload)
|
|
72
|
-
check_pr_mergeability_module.check_pr_mergeability(
|
|
73
|
-
owner="acme", repo="widget", number=42
|
|
74
|
-
)
|
|
75
|
-
invoked_argv = mock_run.call_args[0][0]
|
|
76
|
-
assert invoked_argv[3] == "42"
|
|
77
|
-
assert "--repo" in invoked_argv
|
|
78
|
-
repo_arg_value = invoked_argv[invoked_argv.index("--repo") + 1]
|
|
79
|
-
assert repo_arg_value == "acme/widget"
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def test_should_return_parsed_json_object_with_mergeability_keys() -> None:
|
|
83
|
-
payload = {
|
|
84
|
-
"mergeable": "CONFLICTING",
|
|
85
|
-
"mergeStateStatus": "DIRTY",
|
|
86
|
-
"headRefOid": "deadbeef",
|
|
87
|
-
}
|
|
88
|
-
with patch("subprocess.run") as mock_run:
|
|
89
|
-
mock_run.return_value = _completed(json.dumps(payload))
|
|
90
|
-
mergeability_state = check_pr_mergeability_module.check_pr_mergeability(
|
|
91
|
-
owner="acme", repo="widget", number=42
|
|
92
|
-
)
|
|
93
|
-
assert mergeability_state == payload
|
|
94
|
-
assert mergeability_state["mergeable"] == "CONFLICTING"
|
|
95
|
-
assert mergeability_state["mergeStateStatus"] == "DIRTY"
|
|
96
|
-
assert mergeability_state["headRefOid"] == "deadbeef"
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def test_should_raise_when_gh_subprocess_fails() -> None:
|
|
100
|
-
failure = subprocess.CalledProcessError(
|
|
101
|
-
returncode=1, cmd=["gh"], stderr="auth failure"
|
|
102
|
-
)
|
|
103
|
-
with patch("subprocess.run", side_effect=failure):
|
|
104
|
-
with pytest.raises(subprocess.CalledProcessError):
|
|
105
|
-
check_pr_mergeability_module.check_pr_mergeability(
|
|
106
|
-
owner="acme", repo="widget", number=42
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def test_should_pass_imported_constant_directly_without_local_alias() -> None:
|
|
111
|
-
payload = json.dumps(
|
|
112
|
-
{
|
|
113
|
-
"mergeable": "MERGEABLE",
|
|
114
|
-
"mergeStateStatus": "CLEAN",
|
|
115
|
-
"headRefOid": "abc",
|
|
116
|
-
}
|
|
117
|
-
)
|
|
118
|
-
with patch("subprocess.run") as mock_run:
|
|
119
|
-
mock_run.return_value = _completed(payload)
|
|
120
|
-
check_pr_mergeability_module.check_pr_mergeability(
|
|
121
|
-
owner="acme", repo="widget", number=42
|
|
122
|
-
)
|
|
123
|
-
invoked_argv = mock_run.call_args[0][0]
|
|
124
|
-
fields_arg = invoked_argv[invoked_argv.index("--json") + 1]
|
|
125
|
-
expected_fields = check_pr_mergeability_module.MERGEABILITY_FIELDS
|
|
126
|
-
assert fields_arg is expected_fields
|
|
@@ -1,443 +0,0 @@
|
|
|
1
|
-
"""Tests for fetch_bugbot_inline_comments.
|
|
2
|
-
|
|
3
|
-
Covers:
|
|
4
|
-
- gh command uses --paginate --slurp on the comments endpoint
|
|
5
|
-
- only cursor[bot] inline comments are returned
|
|
6
|
-
- comments not anchored to the requested commit are filtered out
|
|
7
|
-
- comments on the same commit but from an older Bugbot review are filtered out
|
|
8
|
-
- multi-page responses are flattened correctly
|
|
9
|
-
- subprocess errors propagate
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
import importlib.util
|
|
15
|
-
import json
|
|
16
|
-
import subprocess
|
|
17
|
-
from pathlib import Path
|
|
18
|
-
from types import ModuleType
|
|
19
|
-
from unittest.mock import MagicMock, patch
|
|
20
|
-
|
|
21
|
-
import pytest
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def _load_module() -> ModuleType:
|
|
25
|
-
module_path = Path(__file__).parent / "fetch_bugbot_inline_comments.py"
|
|
26
|
-
spec = importlib.util.spec_from_file_location(
|
|
27
|
-
"fetch_bugbot_inline_comments", module_path
|
|
28
|
-
)
|
|
29
|
-
assert spec is not None
|
|
30
|
-
assert spec.loader is not None
|
|
31
|
-
module = importlib.util.module_from_spec(spec)
|
|
32
|
-
spec.loader.exec_module(module)
|
|
33
|
-
return module
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
fetch_bugbot_inline_comments_module = _load_module()
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def _completed(stdout: str) -> subprocess.CompletedProcess:
|
|
40
|
-
process = MagicMock(spec=subprocess.CompletedProcess)
|
|
41
|
-
process.stdout = stdout
|
|
42
|
-
process.returncode = 0
|
|
43
|
-
return process
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def _default_review_for_head(*, commit: str, review_id: int) -> list[dict]:
|
|
47
|
-
return [
|
|
48
|
-
{
|
|
49
|
-
"review_id": review_id,
|
|
50
|
-
"commit_id": commit,
|
|
51
|
-
"submitted_at": "2026-01-01T00:00:00Z",
|
|
52
|
-
"body": "Cursor Bugbot has reviewed your changes and found 0 potential issue",
|
|
53
|
-
"classification": "clean",
|
|
54
|
-
}
|
|
55
|
-
]
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def test_should_invoke_gh_with_paginate_slurp_against_comments_endpoint() -> None:
|
|
59
|
-
pages_payload = json.dumps([[]])
|
|
60
|
-
with patch.object(
|
|
61
|
-
fetch_bugbot_inline_comments_module,
|
|
62
|
-
"fetch_bugbot_reviews",
|
|
63
|
-
return_value=_default_review_for_head(commit="abc123", review_id=1),
|
|
64
|
-
), patch("subprocess.run") as mock_run:
|
|
65
|
-
mock_run.return_value = _completed(pages_payload)
|
|
66
|
-
fetch_bugbot_inline_comments_module.fetch_bugbot_inline_comments(
|
|
67
|
-
owner="acme", repo="widget", number=42, current_head="abc123"
|
|
68
|
-
)
|
|
69
|
-
invoked_argv = mock_run.call_args[0][0]
|
|
70
|
-
assert invoked_argv[0] == "gh"
|
|
71
|
-
assert invoked_argv[1] == "api"
|
|
72
|
-
assert "repos/acme/widget/pulls/42/comments?per_page=100" in invoked_argv[2]
|
|
73
|
-
assert "--paginate" in invoked_argv
|
|
74
|
-
assert "--slurp" in invoked_argv
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def test_should_filter_to_cursor_bot_only() -> None:
|
|
78
|
-
pages_payload = json.dumps(
|
|
79
|
-
[
|
|
80
|
-
[
|
|
81
|
-
{
|
|
82
|
-
"id": 100,
|
|
83
|
-
"user": {"login": "copilot-pull-request-reviewer[bot]"},
|
|
84
|
-
"commit_id": "abc123",
|
|
85
|
-
"pull_request_review_id": 1,
|
|
86
|
-
"body": "copilot finding",
|
|
87
|
-
"path": "x.py",
|
|
88
|
-
"line": 5,
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
"id": 101,
|
|
92
|
-
"user": {"login": "cursor[bot]"},
|
|
93
|
-
"commit_id": "abc123",
|
|
94
|
-
"pull_request_review_id": 1,
|
|
95
|
-
"body": "bugbot finding",
|
|
96
|
-
"path": "x.py",
|
|
97
|
-
"line": 6,
|
|
98
|
-
},
|
|
99
|
-
]
|
|
100
|
-
]
|
|
101
|
-
)
|
|
102
|
-
with patch.object(
|
|
103
|
-
fetch_bugbot_inline_comments_module,
|
|
104
|
-
"fetch_bugbot_reviews",
|
|
105
|
-
return_value=_default_review_for_head(commit="abc123", review_id=1),
|
|
106
|
-
), patch("subprocess.run") as mock_run:
|
|
107
|
-
mock_run.return_value = _completed(pages_payload)
|
|
108
|
-
all_inline_comments = (
|
|
109
|
-
fetch_bugbot_inline_comments_module.fetch_bugbot_inline_comments(
|
|
110
|
-
owner="acme", repo="widget", number=42, current_head="abc123"
|
|
111
|
-
)
|
|
112
|
-
)
|
|
113
|
-
assert len(all_inline_comments) == 1
|
|
114
|
-
assert all_inline_comments[0]["comment_id"] == 101
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def test_should_filter_out_comments_not_on_current_head() -> None:
|
|
118
|
-
pages_payload = json.dumps(
|
|
119
|
-
[
|
|
120
|
-
[
|
|
121
|
-
{
|
|
122
|
-
"id": 200,
|
|
123
|
-
"user": {"login": "cursor[bot]"},
|
|
124
|
-
"commit_id": "old_sha",
|
|
125
|
-
"pull_request_review_id": 1,
|
|
126
|
-
"body": "stale finding",
|
|
127
|
-
"path": "x.py",
|
|
128
|
-
"line": 5,
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
"id": 201,
|
|
132
|
-
"user": {"login": "cursor[bot]"},
|
|
133
|
-
"commit_id": "current_sha",
|
|
134
|
-
"pull_request_review_id": 2,
|
|
135
|
-
"body": "fresh finding",
|
|
136
|
-
"path": "x.py",
|
|
137
|
-
"line": 6,
|
|
138
|
-
},
|
|
139
|
-
]
|
|
140
|
-
]
|
|
141
|
-
)
|
|
142
|
-
with patch.object(
|
|
143
|
-
fetch_bugbot_inline_comments_module,
|
|
144
|
-
"fetch_bugbot_reviews",
|
|
145
|
-
return_value=_default_review_for_head(commit="current_sha", review_id=2),
|
|
146
|
-
), patch("subprocess.run") as mock_run:
|
|
147
|
-
mock_run.return_value = _completed(pages_payload)
|
|
148
|
-
all_inline_comments = (
|
|
149
|
-
fetch_bugbot_inline_comments_module.fetch_bugbot_inline_comments(
|
|
150
|
-
owner="acme", repo="widget", number=42, current_head="current_sha"
|
|
151
|
-
)
|
|
152
|
-
)
|
|
153
|
-
assert len(all_inline_comments) == 1
|
|
154
|
-
assert all_inline_comments[0]["comment_id"] == 201
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def test_should_ignore_inline_comments_from_older_bugbot_review_on_same_commit() -> None:
|
|
158
|
-
pages_payload = json.dumps(
|
|
159
|
-
[
|
|
160
|
-
[
|
|
161
|
-
{
|
|
162
|
-
"id": 300,
|
|
163
|
-
"user": {"login": "cursor[bot]"},
|
|
164
|
-
"commit_id": "same_sha",
|
|
165
|
-
"pull_request_review_id": 10,
|
|
166
|
-
"body": "stale dirty thread",
|
|
167
|
-
"path": "x.py",
|
|
168
|
-
"line": 1,
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
"id": 301,
|
|
172
|
-
"user": {"login": "cursor[bot]"},
|
|
173
|
-
"commit_id": "same_sha",
|
|
174
|
-
"pull_request_review_id": 11,
|
|
175
|
-
"body": "current clean thread",
|
|
176
|
-
"path": "x.py",
|
|
177
|
-
"line": 2,
|
|
178
|
-
},
|
|
179
|
-
]
|
|
180
|
-
]
|
|
181
|
-
)
|
|
182
|
-
reviews_newest_first = [
|
|
183
|
-
{
|
|
184
|
-
"review_id": 11,
|
|
185
|
-
"commit_id": "same_sha",
|
|
186
|
-
"submitted_at": "2026-01-02T00:00:00Z",
|
|
187
|
-
"body": "clean",
|
|
188
|
-
"classification": "clean",
|
|
189
|
-
},
|
|
190
|
-
{
|
|
191
|
-
"review_id": 10,
|
|
192
|
-
"commit_id": "same_sha",
|
|
193
|
-
"submitted_at": "2026-01-01T00:00:00Z",
|
|
194
|
-
"body": "Cursor Bugbot has reviewed your changes and found 1 potential issue",
|
|
195
|
-
"classification": "dirty",
|
|
196
|
-
},
|
|
197
|
-
]
|
|
198
|
-
with patch.object(
|
|
199
|
-
fetch_bugbot_inline_comments_module,
|
|
200
|
-
"fetch_bugbot_reviews",
|
|
201
|
-
return_value=reviews_newest_first,
|
|
202
|
-
), patch("subprocess.run") as mock_run:
|
|
203
|
-
mock_run.return_value = _completed(pages_payload)
|
|
204
|
-
all_inline_comments = (
|
|
205
|
-
fetch_bugbot_inline_comments_module.fetch_bugbot_inline_comments(
|
|
206
|
-
owner="acme", repo="widget", number=42, current_head="same_sha"
|
|
207
|
-
)
|
|
208
|
-
)
|
|
209
|
-
assert [each_comment["comment_id"] for each_comment in all_inline_comments] == [301]
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
def test_should_return_empty_when_no_bugbot_review_exists_for_commit() -> None:
|
|
213
|
-
with patch.object(
|
|
214
|
-
fetch_bugbot_inline_comments_module,
|
|
215
|
-
"fetch_bugbot_reviews",
|
|
216
|
-
return_value=[
|
|
217
|
-
{
|
|
218
|
-
"review_id": 1,
|
|
219
|
-
"commit_id": "other_sha",
|
|
220
|
-
"submitted_at": "2026-01-01T00:00:00Z",
|
|
221
|
-
"body": "",
|
|
222
|
-
"classification": "clean",
|
|
223
|
-
}
|
|
224
|
-
],
|
|
225
|
-
), patch("subprocess.run") as mock_run:
|
|
226
|
-
all_inline_comments = (
|
|
227
|
-
fetch_bugbot_inline_comments_module.fetch_bugbot_inline_comments(
|
|
228
|
-
owner="acme", repo="widget", number=42, current_head="missing_sha"
|
|
229
|
-
)
|
|
230
|
-
)
|
|
231
|
-
assert all_inline_comments == []
|
|
232
|
-
mock_run.assert_not_called()
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
def test_should_flatten_across_pages() -> None:
|
|
236
|
-
pages_payload = json.dumps(
|
|
237
|
-
[
|
|
238
|
-
[
|
|
239
|
-
{
|
|
240
|
-
"id": 1,
|
|
241
|
-
"user": {"login": "cursor[bot]"},
|
|
242
|
-
"commit_id": "abc",
|
|
243
|
-
"pull_request_review_id": 9,
|
|
244
|
-
"body": "a",
|
|
245
|
-
"path": "f.py",
|
|
246
|
-
"line": 1,
|
|
247
|
-
}
|
|
248
|
-
],
|
|
249
|
-
[
|
|
250
|
-
{
|
|
251
|
-
"id": 2,
|
|
252
|
-
"user": {"login": "cursor[bot]"},
|
|
253
|
-
"commit_id": "abc",
|
|
254
|
-
"pull_request_review_id": 9,
|
|
255
|
-
"body": "b",
|
|
256
|
-
"path": "f.py",
|
|
257
|
-
"line": 2,
|
|
258
|
-
},
|
|
259
|
-
{
|
|
260
|
-
"id": 3,
|
|
261
|
-
"user": {"login": "cursor[bot]"},
|
|
262
|
-
"commit_id": "abc",
|
|
263
|
-
"pull_request_review_id": 9,
|
|
264
|
-
"body": "c",
|
|
265
|
-
"path": "f.py",
|
|
266
|
-
"line": 3,
|
|
267
|
-
},
|
|
268
|
-
],
|
|
269
|
-
]
|
|
270
|
-
)
|
|
271
|
-
with patch.object(
|
|
272
|
-
fetch_bugbot_inline_comments_module,
|
|
273
|
-
"fetch_bugbot_reviews",
|
|
274
|
-
return_value=_default_review_for_head(commit="abc", review_id=9),
|
|
275
|
-
), patch("subprocess.run") as mock_run:
|
|
276
|
-
mock_run.return_value = _completed(pages_payload)
|
|
277
|
-
all_inline_comments = (
|
|
278
|
-
fetch_bugbot_inline_comments_module.fetch_bugbot_inline_comments(
|
|
279
|
-
owner="acme", repo="widget", number=42, current_head="abc"
|
|
280
|
-
)
|
|
281
|
-
)
|
|
282
|
-
assert [each_comment["comment_id"] for each_comment in all_inline_comments] == [
|
|
283
|
-
1,
|
|
284
|
-
2,
|
|
285
|
-
3,
|
|
286
|
-
]
|
|
287
|
-
|
|
288
|
-
|
|
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 == []
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
def test_should_raise_when_gh_subprocess_fails() -> None:
|
|
399
|
-
failure = subprocess.CalledProcessError(
|
|
400
|
-
returncode=1, cmd=["gh"], stderr="auth failure"
|
|
401
|
-
)
|
|
402
|
-
with patch.object(
|
|
403
|
-
fetch_bugbot_inline_comments_module,
|
|
404
|
-
"fetch_bugbot_reviews",
|
|
405
|
-
return_value=_default_review_for_head(commit="abc", review_id=1),
|
|
406
|
-
), patch("subprocess.run", side_effect=failure):
|
|
407
|
-
with pytest.raises(subprocess.CalledProcessError):
|
|
408
|
-
fetch_bugbot_inline_comments_module.fetch_bugbot_inline_comments(
|
|
409
|
-
owner="acme", repo="widget", number=42, current_head="abc"
|
|
410
|
-
)
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
def test_should_return_entries_whose_keys_are_strings() -> None:
|
|
414
|
-
pages_payload = json.dumps(
|
|
415
|
-
[
|
|
416
|
-
[
|
|
417
|
-
{
|
|
418
|
-
"id": 101,
|
|
419
|
-
"user": {"login": "cursor[bot]"},
|
|
420
|
-
"commit_id": "abc123",
|
|
421
|
-
"pull_request_review_id": 1,
|
|
422
|
-
"body": "bugbot finding",
|
|
423
|
-
"path": "x.py",
|
|
424
|
-
"line": 6,
|
|
425
|
-
}
|
|
426
|
-
]
|
|
427
|
-
]
|
|
428
|
-
)
|
|
429
|
-
with patch.object(
|
|
430
|
-
fetch_bugbot_inline_comments_module,
|
|
431
|
-
"fetch_bugbot_reviews",
|
|
432
|
-
return_value=_default_review_for_head(commit="abc123", review_id=1),
|
|
433
|
-
), patch("subprocess.run") as mock_run:
|
|
434
|
-
mock_run.return_value = _completed(pages_payload)
|
|
435
|
-
all_inline_comments = (
|
|
436
|
-
fetch_bugbot_inline_comments_module.fetch_bugbot_inline_comments(
|
|
437
|
-
owner="acme", repo="widget", number=42, current_head="abc123"
|
|
438
|
-
)
|
|
439
|
-
)
|
|
440
|
-
assert len(all_inline_comments) == 1
|
|
441
|
-
first_comment_entry = all_inline_comments[0]
|
|
442
|
-
assert isinstance(first_comment_entry, dict)
|
|
443
|
-
assert all(isinstance(each_key, str) for each_key in first_comment_entry.keys())
|