@ranger1/dx 0.1.48 → 0.1.50
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/@opencode/agents/__pycache__/gh_review_harvest.cpython-314.pyc +0 -0
- package/@opencode/agents/__pycache__/pr_context.cpython-314.pyc +0 -0
- package/@opencode/agents/__pycache__/pr_precheck.cpython-314.pyc +0 -0
- package/@opencode/agents/__pycache__/pr_review_aggregate.cpython-314.pyc +0 -0
- package/@opencode/agents/__pycache__/test_pr_review_aggregate.cpython-314-pytest-9.0.2.pyc +0 -0
- package/@opencode/agents/__pycache__/test_pr_review_aggregate.cpython-314.pyc +0 -0
- package/@opencode/agents/claude-reviewer.md +6 -2
- package/@opencode/agents/codex-reviewer.md +6 -2
- package/@opencode/agents/gemini-reviewer.md +6 -2
- package/@opencode/agents/gh-thread-reviewer.md +6 -1
- package/@opencode/agents/pr-context.md +12 -0
- package/@opencode/agents/pr-fix.md +6 -1
- package/@opencode/agents/pr-precheck.md +4 -2
- package/@opencode/agents/pr-review-aggregate.md +18 -10
- package/@opencode/agents/pr_context.py +35 -19
- package/@opencode/agents/pr_precheck.py +210 -42
- package/@opencode/agents/test_pr_review_aggregate.py +234 -33
- package/@opencode/commands/oh_attach.json +12 -12
- package/@opencode/commands/opencode_attach.json +3 -3
- package/@opencode/commands/pr-review-loop.md +61 -11
- package/package.json +1 -1
|
@@ -8,21 +8,47 @@ Tests cover:
|
|
|
8
8
|
3. Edge cases: empty input, malformed data, cross-reviewer matching
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
import
|
|
11
|
+
import importlib.util
|
|
12
|
+
import json
|
|
13
|
+
import subprocess
|
|
14
|
+
from collections.abc import Mapping, Sequence
|
|
14
15
|
from pathlib import Path
|
|
16
|
+
from types import SimpleNamespace
|
|
17
|
+
from typing import Callable, cast
|
|
18
|
+
|
|
19
|
+
import pytest
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _load_pr_review_aggregate_module():
|
|
23
|
+
module_path = Path(__file__).with_name("pr_review_aggregate.py")
|
|
24
|
+
spec = importlib.util.spec_from_file_location("pr_review_aggregate", module_path)
|
|
25
|
+
if spec is None or spec.loader is None:
|
|
26
|
+
raise RuntimeError(f"Failed to load module spec: {module_path}")
|
|
27
|
+
module = importlib.util.module_from_spec(spec)
|
|
28
|
+
spec.loader.exec_module(module)
|
|
29
|
+
return module
|
|
15
30
|
|
|
16
|
-
# Add parent directory to path for importing pr_review_aggregate
|
|
17
|
-
sys.path.insert(0, str(Path(__file__).parent))
|
|
18
31
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
_pr_review_aggregate = _load_pr_review_aggregate_module()
|
|
33
|
+
|
|
34
|
+
_parse_decision_log = cast(Callable[[str], list[dict[str, object]]], getattr(_pr_review_aggregate, "_parse_decision_log"))
|
|
35
|
+
_filter_by_decision_log = cast(
|
|
36
|
+
Callable[[Sequence[Mapping[str, object]], Sequence[Mapping[str, object]], list[list[str]]], list[dict[str, object]]],
|
|
37
|
+
getattr(_pr_review_aggregate, "_filter_by_decision_log"),
|
|
38
|
+
)
|
|
39
|
+
_parse_escalation_groups_json = cast(
|
|
40
|
+
Callable[[str], list[list[str]]],
|
|
41
|
+
getattr(_pr_review_aggregate, "_parse_escalation_groups_json"),
|
|
42
|
+
)
|
|
43
|
+
_parse_escalation_groups_b64 = cast(
|
|
44
|
+
Callable[[str], list[list[str]]],
|
|
45
|
+
getattr(_pr_review_aggregate, "_parse_escalation_groups_b64"),
|
|
46
|
+
)
|
|
47
|
+
_check_existing_comment = cast(
|
|
48
|
+
Callable[[int, str, int, str], bool],
|
|
49
|
+
getattr(_pr_review_aggregate, "_check_existing_comment"),
|
|
25
50
|
)
|
|
51
|
+
_MARKER = cast(str, getattr(_pr_review_aggregate, "MARKER"))
|
|
26
52
|
|
|
27
53
|
|
|
28
54
|
# ============================================================
|
|
@@ -30,13 +56,13 @@ from pr_review_aggregate import (
|
|
|
30
56
|
# ============================================================
|
|
31
57
|
|
|
32
58
|
@pytest.fixture
|
|
33
|
-
def empty_decision_log():
|
|
59
|
+
def empty_decision_log() -> str:
|
|
34
60
|
"""Empty decision log markdown."""
|
|
35
61
|
return ""
|
|
36
62
|
|
|
37
63
|
|
|
38
64
|
@pytest.fixture
|
|
39
|
-
def valid_decision_log():
|
|
65
|
+
def valid_decision_log() -> str:
|
|
40
66
|
"""Valid decision log with Fixed and Rejected entries."""
|
|
41
67
|
return """# Decision Log
|
|
42
68
|
|
|
@@ -46,20 +72,24 @@ PR: 123
|
|
|
46
72
|
|
|
47
73
|
### Fixed
|
|
48
74
|
- id: CDX-001
|
|
75
|
+
file: apps/backend/src/api.ts
|
|
49
76
|
commit: abc123
|
|
50
77
|
essence: JSON.parse 未捕获异常
|
|
51
78
|
|
|
52
79
|
- id: GMN-002
|
|
80
|
+
file: apps/front/src/ErrorBoundary.tsx
|
|
53
81
|
commit: def456
|
|
54
82
|
essence: 缺少错误边界处理
|
|
55
83
|
|
|
56
84
|
### Rejected
|
|
57
85
|
- id: GMN-004
|
|
86
|
+
file: apps/front/src/Component.tsx
|
|
58
87
|
priority: P2
|
|
59
88
|
reason: 需要产品决策,超出 PR 范围
|
|
60
89
|
essence: 组件拆分建议
|
|
61
90
|
|
|
62
91
|
- id: CLD-003
|
|
92
|
+
file: apps/backend/src/db.ts
|
|
63
93
|
priority: P3
|
|
64
94
|
reason: 性能优化非当前优先级
|
|
65
95
|
essence: 批量查询优化
|
|
@@ -67,7 +97,38 @@ PR: 123
|
|
|
67
97
|
|
|
68
98
|
|
|
69
99
|
@pytest.fixture
|
|
70
|
-
def
|
|
100
|
+
def valid_decision_log_legacy_no_file() -> str:
|
|
101
|
+
"""Legacy decision log fixture without the file: field (backward compat)."""
|
|
102
|
+
return """# Decision Log
|
|
103
|
+
|
|
104
|
+
PR: 123
|
|
105
|
+
|
|
106
|
+
## Round 1
|
|
107
|
+
|
|
108
|
+
### Fixed
|
|
109
|
+
- id: CDX-001
|
|
110
|
+
commit: abc123
|
|
111
|
+
essence: JSON.parse 未捕获异常
|
|
112
|
+
|
|
113
|
+
- id: GMN-002
|
|
114
|
+
commit: def456
|
|
115
|
+
essence: 缺少错误边界处理
|
|
116
|
+
|
|
117
|
+
### Rejected
|
|
118
|
+
- id: GMN-004
|
|
119
|
+
priority: P2
|
|
120
|
+
reason: 需要产品决策,超出 PR 范围
|
|
121
|
+
essence: 组件拆分建议
|
|
122
|
+
|
|
123
|
+
- id: CLD-003
|
|
124
|
+
priority: P3
|
|
125
|
+
reason: 性能优化非当前优先级
|
|
126
|
+
essence: 批量查询优化
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@pytest.fixture
|
|
131
|
+
def malformed_decision_log() -> str:
|
|
71
132
|
"""Malformed decision log with missing fields and bad formatting."""
|
|
72
133
|
return """# Decision Log
|
|
73
134
|
|
|
@@ -90,7 +151,7 @@ Some random text that should be ignored
|
|
|
90
151
|
|
|
91
152
|
|
|
92
153
|
@pytest.fixture
|
|
93
|
-
def sample_findings():
|
|
154
|
+
def sample_findings() -> list[dict[str, object]]:
|
|
94
155
|
"""Sample findings list for filter tests."""
|
|
95
156
|
return [
|
|
96
157
|
{
|
|
@@ -137,7 +198,7 @@ def sample_findings():
|
|
|
137
198
|
|
|
138
199
|
|
|
139
200
|
@pytest.fixture
|
|
140
|
-
def prior_decisions():
|
|
201
|
+
def prior_decisions() -> list[dict[str, object]]:
|
|
141
202
|
"""Sample prior decisions from _parse_decision_log."""
|
|
142
203
|
return [
|
|
143
204
|
{
|
|
@@ -160,7 +221,7 @@ def prior_decisions():
|
|
|
160
221
|
# Test: _parse_decision_log() - Empty Input
|
|
161
222
|
# ============================================================
|
|
162
223
|
|
|
163
|
-
def test_parse_decision_log_empty(empty_decision_log):
|
|
224
|
+
def test_parse_decision_log_empty(empty_decision_log: str) -> None:
|
|
164
225
|
"""
|
|
165
226
|
Test that empty decision log returns empty list.
|
|
166
227
|
|
|
@@ -177,7 +238,7 @@ def test_parse_decision_log_empty(empty_decision_log):
|
|
|
177
238
|
# Test: _parse_decision_log() - Valid Input
|
|
178
239
|
# ============================================================
|
|
179
240
|
|
|
180
|
-
def test_parse_decision_log_valid(valid_decision_log):
|
|
241
|
+
def test_parse_decision_log_valid(valid_decision_log: str) -> None:
|
|
181
242
|
"""
|
|
182
243
|
Test that valid decision log is parsed into structured data.
|
|
183
244
|
|
|
@@ -194,6 +255,7 @@ def test_parse_decision_log_valid(valid_decision_log):
|
|
|
194
255
|
fixed_1 = result[0]
|
|
195
256
|
assert fixed_1["id"] == "CDX-001"
|
|
196
257
|
assert fixed_1["status"] == "fixed"
|
|
258
|
+
assert fixed_1["file"] == "apps/backend/src/api.ts"
|
|
197
259
|
assert fixed_1["commit"] == "abc123"
|
|
198
260
|
assert fixed_1["essence"] == "JSON.parse 未捕获异常"
|
|
199
261
|
|
|
@@ -201,6 +263,7 @@ def test_parse_decision_log_valid(valid_decision_log):
|
|
|
201
263
|
fixed_2 = result[1]
|
|
202
264
|
assert fixed_2["id"] == "GMN-002"
|
|
203
265
|
assert fixed_2["status"] == "fixed"
|
|
266
|
+
assert fixed_2["file"] == "apps/front/src/ErrorBoundary.tsx"
|
|
204
267
|
assert fixed_2["commit"] == "def456"
|
|
205
268
|
assert fixed_2["essence"] == "缺少错误边界处理"
|
|
206
269
|
|
|
@@ -208,6 +271,7 @@ def test_parse_decision_log_valid(valid_decision_log):
|
|
|
208
271
|
rejected_1 = result[2]
|
|
209
272
|
assert rejected_1["id"] == "GMN-004"
|
|
210
273
|
assert rejected_1["status"] == "rejected"
|
|
274
|
+
assert rejected_1["file"] == "apps/front/src/Component.tsx"
|
|
211
275
|
assert rejected_1["priority"] == "P2"
|
|
212
276
|
assert rejected_1["reason"] == "需要产品决策,超出 PR 范围"
|
|
213
277
|
assert rejected_1["essence"] == "组件拆分建议"
|
|
@@ -216,14 +280,31 @@ def test_parse_decision_log_valid(valid_decision_log):
|
|
|
216
280
|
rejected_2 = result[3]
|
|
217
281
|
assert rejected_2["id"] == "CLD-003"
|
|
218
282
|
assert rejected_2["status"] == "rejected"
|
|
283
|
+
assert rejected_2["file"] == "apps/backend/src/db.ts"
|
|
219
284
|
assert rejected_2["priority"] == "P3"
|
|
220
285
|
|
|
221
286
|
|
|
287
|
+
def test_parse_decision_log_legacy_without_file(valid_decision_log_legacy_no_file: str) -> None:
|
|
288
|
+
"""Decision log entries without file: should still parse (backward compat)."""
|
|
289
|
+
result = _parse_decision_log(valid_decision_log_legacy_no_file)
|
|
290
|
+
|
|
291
|
+
# Should have 4 entries (2 Fixed, 2 Rejected)
|
|
292
|
+
assert len(result) == 4
|
|
293
|
+
|
|
294
|
+
# Basic shape should still be present
|
|
295
|
+
for entry in result:
|
|
296
|
+
assert "id" in entry
|
|
297
|
+
assert "status" in entry
|
|
298
|
+
|
|
299
|
+
# And file should be optional
|
|
300
|
+
assert all(("file" not in e) or (e["file"] in (None, "")) for e in result)
|
|
301
|
+
|
|
302
|
+
|
|
222
303
|
# ============================================================
|
|
223
304
|
# Test: _parse_decision_log() - Malformed Input
|
|
224
305
|
# ============================================================
|
|
225
306
|
|
|
226
|
-
def test_parse_decision_log_malformed(malformed_decision_log):
|
|
307
|
+
def test_parse_decision_log_malformed(malformed_decision_log: str) -> None:
|
|
227
308
|
"""
|
|
228
309
|
Test that malformed decision log degrades gracefully.
|
|
229
310
|
|
|
@@ -247,7 +328,7 @@ def test_parse_decision_log_malformed(malformed_decision_log):
|
|
|
247
328
|
# Test: _filter_by_decision_log() - Fixed Issues
|
|
248
329
|
# ============================================================
|
|
249
330
|
|
|
250
|
-
def test_filter_fixed_issues(sample_findings, prior_decisions):
|
|
331
|
+
def test_filter_fixed_issues(sample_findings: list[dict[str, object]], prior_decisions: list[dict[str, object]]) -> None:
|
|
251
332
|
"""
|
|
252
333
|
Test that findings matching Fixed decisions are filtered out.
|
|
253
334
|
|
|
@@ -255,7 +336,7 @@ def test_filter_fixed_issues(sample_findings, prior_decisions):
|
|
|
255
336
|
When: _filter_by_decision_log() is called with empty escalation_groups
|
|
256
337
|
Then: CDX-001 is filtered out
|
|
257
338
|
"""
|
|
258
|
-
escalation_groups = []
|
|
339
|
+
escalation_groups: list[list[str]] = []
|
|
259
340
|
|
|
260
341
|
result = _filter_by_decision_log(sample_findings, prior_decisions, escalation_groups)
|
|
261
342
|
|
|
@@ -271,7 +352,7 @@ def test_filter_fixed_issues(sample_findings, prior_decisions):
|
|
|
271
352
|
# Test: _filter_by_decision_log() - Rejected Without Escalation
|
|
272
353
|
# ============================================================
|
|
273
354
|
|
|
274
|
-
def test_filter_rejected_without_escalation(sample_findings, prior_decisions):
|
|
355
|
+
def test_filter_rejected_without_escalation(sample_findings: list[dict[str, object]], prior_decisions: list[dict[str, object]]) -> None:
|
|
275
356
|
"""
|
|
276
357
|
Test that findings matching Rejected decisions are filtered out when NOT in escalation_groups.
|
|
277
358
|
|
|
@@ -280,7 +361,7 @@ def test_filter_rejected_without_escalation(sample_findings, prior_decisions):
|
|
|
280
361
|
When: _filter_by_decision_log() is called
|
|
281
362
|
Then: GMN-004 is filtered out
|
|
282
363
|
"""
|
|
283
|
-
escalation_groups = []
|
|
364
|
+
escalation_groups: list[list[str]] = []
|
|
284
365
|
|
|
285
366
|
result = _filter_by_decision_log(sample_findings, prior_decisions, escalation_groups)
|
|
286
367
|
|
|
@@ -296,7 +377,7 @@ def test_filter_rejected_without_escalation(sample_findings, prior_decisions):
|
|
|
296
377
|
# Test: _filter_by_decision_log() - Rejected With Escalation
|
|
297
378
|
# ============================================================
|
|
298
379
|
|
|
299
|
-
def test_filter_rejected_with_escalation(sample_findings, prior_decisions):
|
|
380
|
+
def test_filter_rejected_with_escalation(sample_findings: list[dict[str, object]], prior_decisions: list[dict[str, object]]) -> None:
|
|
300
381
|
"""
|
|
301
382
|
Test that findings matching Rejected decisions are kept when in escalation_groups.
|
|
302
383
|
|
|
@@ -322,7 +403,7 @@ def test_filter_rejected_with_escalation(sample_findings, prior_decisions):
|
|
|
322
403
|
# Test: _filter_by_decision_log() - Cross-Reviewer Match
|
|
323
404
|
# ============================================================
|
|
324
405
|
|
|
325
|
-
def test_filter_cross_reviewer_match():
|
|
406
|
+
def test_filter_cross_reviewer_match() -> None:
|
|
326
407
|
"""
|
|
327
408
|
Test that findings with different reviewer IDs but same essence are filtered.
|
|
328
409
|
|
|
@@ -381,7 +462,7 @@ def test_filter_cross_reviewer_match():
|
|
|
381
462
|
# Test: _parse_escalation_groups_json()
|
|
382
463
|
# ============================================================
|
|
383
464
|
|
|
384
|
-
def test_parse_escalation_groups_json_valid():
|
|
465
|
+
def test_parse_escalation_groups_json_valid() -> None:
|
|
385
466
|
"""Test parsing valid escalation groups JSON."""
|
|
386
467
|
json_str = '{"escalationGroups": [["GMN-004", "CLD-007"], ["CDX-001", "GMN-005"]]}'
|
|
387
468
|
result = _parse_escalation_groups_json(json_str)
|
|
@@ -391,13 +472,13 @@ def test_parse_escalation_groups_json_valid():
|
|
|
391
472
|
assert ["CDX-001", "GMN-005"] in result
|
|
392
473
|
|
|
393
474
|
|
|
394
|
-
def test_parse_escalation_groups_json_empty():
|
|
475
|
+
def test_parse_escalation_groups_json_empty() -> None:
|
|
395
476
|
"""Test parsing empty escalation groups JSON."""
|
|
396
477
|
result = _parse_escalation_groups_json("")
|
|
397
478
|
assert result == []
|
|
398
479
|
|
|
399
480
|
|
|
400
|
-
def test_parse_escalation_groups_json_malformed():
|
|
481
|
+
def test_parse_escalation_groups_json_malformed() -> None:
|
|
401
482
|
"""Test parsing malformed JSON returns empty list."""
|
|
402
483
|
result = _parse_escalation_groups_json("not valid json {{{")
|
|
403
484
|
assert result == []
|
|
@@ -407,7 +488,7 @@ def test_parse_escalation_groups_json_malformed():
|
|
|
407
488
|
# Test: _parse_escalation_groups_b64()
|
|
408
489
|
# ============================================================
|
|
409
490
|
|
|
410
|
-
def test_parse_escalation_groups_b64_valid():
|
|
491
|
+
def test_parse_escalation_groups_b64_valid() -> None:
|
|
411
492
|
"""Test parsing valid base64-encoded escalation groups."""
|
|
412
493
|
import base64
|
|
413
494
|
json_str = '{"escalationGroups": [["GMN-004", "CLD-007"]]}'
|
|
@@ -419,13 +500,13 @@ def test_parse_escalation_groups_b64_valid():
|
|
|
419
500
|
assert ["GMN-004", "CLD-007"] in result
|
|
420
501
|
|
|
421
502
|
|
|
422
|
-
def test_parse_escalation_groups_b64_empty():
|
|
503
|
+
def test_parse_escalation_groups_b64_empty() -> None:
|
|
423
504
|
"""Test parsing empty base64 string."""
|
|
424
505
|
result = _parse_escalation_groups_b64("")
|
|
425
506
|
assert result == []
|
|
426
507
|
|
|
427
508
|
|
|
428
|
-
def test_parse_escalation_groups_b64_invalid():
|
|
509
|
+
def test_parse_escalation_groups_b64_invalid() -> None:
|
|
429
510
|
"""Test parsing invalid base64 returns empty list."""
|
|
430
511
|
result = _parse_escalation_groups_b64("not-valid-base64!!!")
|
|
431
512
|
assert result == []
|
|
@@ -435,7 +516,7 @@ def test_parse_escalation_groups_b64_invalid():
|
|
|
435
516
|
# Test: Integration - Full Workflow
|
|
436
517
|
# ============================================================
|
|
437
518
|
|
|
438
|
-
def test_integration_full_filter_workflow():
|
|
519
|
+
def test_integration_full_filter_workflow() -> None:
|
|
439
520
|
"""
|
|
440
521
|
Integration test: parse decision log and filter findings.
|
|
441
522
|
|
|
@@ -496,5 +577,125 @@ PR: 456
|
|
|
496
577
|
assert len(result) == 2
|
|
497
578
|
|
|
498
579
|
|
|
580
|
+
# ============================================================
|
|
581
|
+
# Test: _check_existing_comment() - PR comment idempotency
|
|
582
|
+
# ============================================================
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def _patch_subprocess_run_for_gh_comments(monkeypatch: pytest.MonkeyPatch, comments: list[dict[str, object]], returncode: int = 0) -> None:
|
|
586
|
+
stdout = json.dumps(comments, ensure_ascii=True)
|
|
587
|
+
|
|
588
|
+
def _fake_run(*_args: object, **_kwargs: object) -> SimpleNamespace:
|
|
589
|
+
return SimpleNamespace(returncode=returncode, stdout=stdout)
|
|
590
|
+
|
|
591
|
+
monkeypatch.setattr(subprocess, "run", _fake_run)
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
@pytest.mark.parametrize(
|
|
595
|
+
"comment_type,round_num,expected_header",
|
|
596
|
+
[
|
|
597
|
+
("review-summary", 2, "## Review Summary (Round 2)"),
|
|
598
|
+
("fix-report", 2, "## Fix Report (Round 2)"),
|
|
599
|
+
("final-report", 2, "## Final Report"),
|
|
600
|
+
],
|
|
601
|
+
)
|
|
602
|
+
def test_check_existing_comment_true_when_marker_header_and_runid_match(
|
|
603
|
+
monkeypatch: pytest.MonkeyPatch, comment_type: str, round_num: int, expected_header: str
|
|
604
|
+
) -> None:
|
|
605
|
+
pr_number = 123
|
|
606
|
+
run_id = "run-abc"
|
|
607
|
+
body = "\n".join([_MARKER, "", expected_header, "", f"RunId: {run_id}"])
|
|
608
|
+
_patch_subprocess_run_for_gh_comments(monkeypatch, [{"body": body}])
|
|
609
|
+
|
|
610
|
+
assert _check_existing_comment(pr_number, run_id, round_num, comment_type) is True
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
@pytest.mark.parametrize(
|
|
614
|
+
"comment_type,round_num,expected_header",
|
|
615
|
+
[
|
|
616
|
+
("review-summary", 3, "## Review Summary (Round 3)"),
|
|
617
|
+
("fix-report", 3, "## Fix Report (Round 3)"),
|
|
618
|
+
("final-report", 3, "## Final Report"),
|
|
619
|
+
],
|
|
620
|
+
)
|
|
621
|
+
def test_check_existing_comment_false_when_marker_missing(
|
|
622
|
+
monkeypatch: pytest.MonkeyPatch, comment_type: str, round_num: int, expected_header: str
|
|
623
|
+
) -> None:
|
|
624
|
+
pr_number = 456
|
|
625
|
+
run_id = "run-xyz"
|
|
626
|
+
body = "\n".join(["", expected_header, "", f"RunId: {run_id}"])
|
|
627
|
+
_patch_subprocess_run_for_gh_comments(monkeypatch, [{"body": body}])
|
|
628
|
+
|
|
629
|
+
assert _check_existing_comment(pr_number, run_id, round_num, comment_type) is False
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
@pytest.mark.parametrize(
|
|
633
|
+
"comment_type,round_num,expected_header,wrong_header",
|
|
634
|
+
[
|
|
635
|
+
(
|
|
636
|
+
"review-summary",
|
|
637
|
+
2,
|
|
638
|
+
"## Review Summary (Round 2)",
|
|
639
|
+
"## Fix Report (Round 2)",
|
|
640
|
+
),
|
|
641
|
+
(
|
|
642
|
+
"fix-report",
|
|
643
|
+
2,
|
|
644
|
+
"## Fix Report (Round 2)",
|
|
645
|
+
"## Review Summary (Round 2)",
|
|
646
|
+
),
|
|
647
|
+
(
|
|
648
|
+
"final-report",
|
|
649
|
+
2,
|
|
650
|
+
"## Final Report",
|
|
651
|
+
"## Review Summary (Round 2)",
|
|
652
|
+
),
|
|
653
|
+
],
|
|
654
|
+
)
|
|
655
|
+
def test_check_existing_comment_false_when_header_mismatched(
|
|
656
|
+
monkeypatch: pytest.MonkeyPatch, comment_type: str, round_num: int, expected_header: str, wrong_header: str
|
|
657
|
+
) -> None:
|
|
658
|
+
pr_number = 789
|
|
659
|
+
run_id = "run-123"
|
|
660
|
+
|
|
661
|
+
body = "\n".join([_MARKER, "", wrong_header, "", f"RunId: {run_id}"])
|
|
662
|
+
_patch_subprocess_run_for_gh_comments(monkeypatch, [{"body": body}])
|
|
663
|
+
|
|
664
|
+
assert expected_header not in body
|
|
665
|
+
assert _check_existing_comment(pr_number, run_id, round_num, comment_type) is False
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
@pytest.mark.parametrize(
|
|
669
|
+
"comment_type,round_num,expected_header",
|
|
670
|
+
[
|
|
671
|
+
("review-summary", 1, "## Review Summary (Round 1)"),
|
|
672
|
+
("fix-report", 1, "## Fix Report (Round 1)"),
|
|
673
|
+
("final-report", 1, "## Final Report"),
|
|
674
|
+
],
|
|
675
|
+
)
|
|
676
|
+
def test_check_existing_comment_false_when_runid_mismatched(
|
|
677
|
+
monkeypatch: pytest.MonkeyPatch, comment_type: str, round_num: int, expected_header: str
|
|
678
|
+
) -> None:
|
|
679
|
+
pr_number = 101
|
|
680
|
+
run_id = "run-a"
|
|
681
|
+
other_run_id = "run-b"
|
|
682
|
+
|
|
683
|
+
body = "\n".join([_MARKER, "", expected_header, "", f"RunId: {other_run_id}"])
|
|
684
|
+
_patch_subprocess_run_for_gh_comments(monkeypatch, [{"body": body}])
|
|
685
|
+
|
|
686
|
+
assert _check_existing_comment(pr_number, run_id, round_num, comment_type) is False
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
def test_check_existing_comment_false_when_subprocess_run_nonzero(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
690
|
+
pr_number = 999
|
|
691
|
+
run_id = "run-nonzero"
|
|
692
|
+
round_num = 2
|
|
693
|
+
comment_type = "review-summary"
|
|
694
|
+
body = "\n".join([_MARKER, "", "## Review Summary (Round 2)", "", f"RunId: {run_id}"])
|
|
695
|
+
|
|
696
|
+
_patch_subprocess_run_for_gh_comments(monkeypatch, [{"body": body}], returncode=1)
|
|
697
|
+
assert _check_existing_comment(pr_number, run_id, round_num, comment_type) is False
|
|
698
|
+
|
|
699
|
+
|
|
499
700
|
if __name__ == "__main__":
|
|
500
|
-
pytest.main([__file__, "-v"])
|
|
701
|
+
_ = pytest.main([__file__, "-v"])
|
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
"variant": "high"
|
|
16
16
|
},
|
|
17
17
|
"librarian": {
|
|
18
|
-
"model": "
|
|
18
|
+
"model": "openai/gpt-5.3-codex-spark"
|
|
19
19
|
},
|
|
20
20
|
"explore": {
|
|
21
|
-
"model": "
|
|
21
|
+
"model": "openai/gpt-5.3-codex-spark"
|
|
22
22
|
},
|
|
23
23
|
"multimodal-looker": {
|
|
24
|
-
"model": "
|
|
24
|
+
"model": "openai/gpt-5.3-codex-spark"
|
|
25
25
|
},
|
|
26
26
|
"prometheus": {
|
|
27
27
|
"model": "openai/gpt-5.2",
|
|
@@ -44,15 +44,15 @@
|
|
|
44
44
|
"temperature": 0.1
|
|
45
45
|
},
|
|
46
46
|
"gemini-reviewer": {
|
|
47
|
-
"model": "
|
|
47
|
+
"model": "openai/gpt-5.3-codex-spark",
|
|
48
48
|
"variant": "max"
|
|
49
49
|
},
|
|
50
50
|
"claude-reviewer": {
|
|
51
|
-
"model": "
|
|
51
|
+
"model": "openai/gpt-5.3-codex-spark",
|
|
52
52
|
"variant": "high"
|
|
53
53
|
},
|
|
54
54
|
"pr-fixer": {
|
|
55
|
-
"model": "openai/gpt-5.
|
|
55
|
+
"model": "openai/gpt-5.3-codex",
|
|
56
56
|
"variant": "xhigh",
|
|
57
57
|
"temperature": 0.1
|
|
58
58
|
}
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"concurrency": 5,
|
|
61
61
|
"categories": {
|
|
62
62
|
"visual-engineering": {
|
|
63
|
-
"model": "
|
|
63
|
+
"model": "openai/gpt-5.3-codex",
|
|
64
64
|
"variant": "high"
|
|
65
65
|
},
|
|
66
66
|
"ultrabrain": {
|
|
@@ -68,17 +68,17 @@
|
|
|
68
68
|
"variant": "xhigh"
|
|
69
69
|
},
|
|
70
70
|
"artistry": {
|
|
71
|
-
"model": "
|
|
71
|
+
"model": "openai/gpt-5.3-codex",
|
|
72
72
|
"variant": "max"
|
|
73
73
|
},
|
|
74
74
|
"quick": {
|
|
75
|
-
"model": "openai/gpt-5.
|
|
75
|
+
"model": "openai/gpt-5.3-codex-spark"
|
|
76
76
|
},
|
|
77
77
|
"middle": {
|
|
78
|
-
"model": "
|
|
78
|
+
"model": "openai/gpt-5.3-codex-spark"
|
|
79
79
|
},
|
|
80
80
|
"unspecified-low": {
|
|
81
|
-
"model": "
|
|
81
|
+
"model": "openai/gpt-5.3-codex-spark",
|
|
82
82
|
"variant": "medium"
|
|
83
83
|
},
|
|
84
84
|
"unspecified-high": {
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"variant": "medium"
|
|
87
87
|
},
|
|
88
88
|
"writing": {
|
|
89
|
-
"model": "
|
|
89
|
+
"model": "openai/gpt-5.3-codex-spark"
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
}
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
],
|
|
11
11
|
"agent": {
|
|
12
12
|
"quick": {
|
|
13
|
-
"model": "openai/gpt-5.
|
|
13
|
+
"model": "openai/gpt-5.3-codex-spark"
|
|
14
14
|
},
|
|
15
15
|
"middle": {
|
|
16
|
-
"model": "
|
|
16
|
+
"model": "openai/gpt-5.3-codex"
|
|
17
17
|
},
|
|
18
18
|
"documenter": {
|
|
19
|
-
"model": "
|
|
19
|
+
"model": "openai/gpt-5.3-codex-spark"
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"permission": {
|