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.
Files changed (76) hide show
  1. package/_shared/pr-loop/scripts/config/preflight_constants.py +29 -8
  2. package/_shared/pr-loop/scripts/preflight.py +242 -20
  3. package/_shared/pr-loop/scripts/tests/test_preflight.py +362 -25
  4. package/_shared/pr-loop/scripts/tests/test_preflight_constants.py +9 -14
  5. package/hooks/blocking/code_rules_enforcer.py +269 -23
  6. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +157 -1
  7. package/hooks/config/test_unused_module_import_constants.py +48 -0
  8. package/hooks/config/unused_module_import_constants.py +41 -0
  9. package/package.json +1 -1
  10. package/rules/gh-paginate.md +4 -50
  11. package/rules/no-historical-clutter.md +36 -0
  12. package/skills/bg-agent/SKILL.md +69 -0
  13. package/skills/bugteam/CONSTRAINTS.md +10 -19
  14. package/skills/bugteam/PROMPTS.md +21 -14
  15. package/skills/bugteam/SKILL.md +122 -208
  16. package/skills/bugteam/SKILL_EVALS.md +75 -114
  17. package/skills/bugteam/reference/README.md +2 -4
  18. package/skills/bugteam/reference/audit-and-teammates.md +21 -48
  19. package/skills/bugteam/reference/audit-contract.md +7 -7
  20. package/skills/bugteam/reference/design-rationale.md +3 -8
  21. package/skills/bugteam/reference/team-setup.md +11 -19
  22. package/skills/bugteam/reference/teardown-publish-permissions.md +2 -14
  23. package/skills/bugteam/scripts/config/__init__.py +0 -0
  24. package/skills/bugteam/scripts/config/reflow_skill_md_constants.py +12 -0
  25. package/skills/bugteam/scripts/reflow_skill_md.py +51 -47
  26. package/skills/bugteam/sources.md +1 -25
  27. package/skills/bugteam/test_skill_additions.py +4 -13
  28. package/skills/fresh-branch/SKILL.md +71 -0
  29. package/skills/gotcha/SKILL.md +73 -0
  30. package/skills/monitor-open-prs/SKILL.md +4 -37
  31. package/skills/monitor-open-prs/test_skill_contract.py +0 -5
  32. package/skills/pr-converge/SKILL.md +60 -1298
  33. package/skills/pr-converge/reference/convergence-gates.md +122 -0
  34. package/skills/pr-converge/reference/examples.md +76 -0
  35. package/skills/pr-converge/reference/fix-protocol.md +56 -0
  36. package/skills/pr-converge/reference/ground-rules.md +13 -0
  37. package/skills/pr-converge/reference/multi-pr-orchestration.md +204 -0
  38. package/skills/pr-converge/reference/per-tick.md +204 -0
  39. package/skills/pr-converge/reference/state-schema.md +19 -0
  40. package/skills/pr-converge/reference/stop-conditions.md +26 -0
  41. package/skills/pr-converge/scripts/README.md +36 -9
  42. package/skills/pr-converge/scripts/check_pr_mergeability.py +1 -2
  43. package/skills/pr-converge/scripts/config/pr_converge_constants.py +74 -5
  44. package/skills/pr-converge/scripts/config/reflow_skill_md_constants.py +13 -0
  45. package/skills/pr-converge/scripts/config/test_pr_converge_constants.py +0 -24
  46. package/skills/pr-converge/scripts/cursor-agents-continue.ahk +22 -2
  47. package/skills/pr-converge/scripts/fetch_bugbot_inline_comments.py +19 -59
  48. package/skills/pr-converge/scripts/fetch_bugbot_reviews.py +15 -61
  49. package/skills/pr-converge/scripts/fetch_claude_inline_comments.py +70 -0
  50. package/skills/pr-converge/scripts/fetch_claude_reviews.py +61 -0
  51. package/skills/pr-converge/scripts/fetch_copilot_inline_comments.py +19 -61
  52. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +14 -74
  53. package/skills/pr-converge/scripts/reflow_skill_md.py +71 -50
  54. package/skills/pr-converge/scripts/reviewer_fetch_core.py +153 -0
  55. package/skills/pr-converge/scripts/reviewer_specs.py +98 -0
  56. package/skills/pr-converge/scripts/test_cursor_agents_continue.py +65 -0
  57. package/skills/pr-converge/scripts/test_fetch_bugbot_inline_comments.py +107 -6
  58. package/skills/pr-converge/scripts/test_fetch_bugbot_reviews.py +85 -6
  59. package/skills/pr-converge/scripts/test_fetch_claude_inline_comments.py +485 -0
  60. package/skills/pr-converge/scripts/test_fetch_claude_reviews.py +368 -0
  61. package/skills/pr-converge/scripts/test_fetch_copilot_inline_comments.py +74 -6
  62. package/skills/pr-converge/scripts/test_fetch_copilot_reviews.py +94 -8
  63. package/skills/pr-converge/scripts/test_reflow_skill_md.py +162 -0
  64. package/skills/pr-converge/scripts/test_reviewer_fetch_core.py +448 -0
  65. package/skills/pr-converge/scripts/test_reviewer_specs.py +107 -0
  66. package/skills/pr-converge/scripts/test_view_pr_context.py +44 -0
  67. package/skills/pr-converge/scripts/view_pr_context.py +35 -4
  68. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +24 -22
  69. package/skills/bugteam/reference/workflow-path-a-orchestrated-teams.md +0 -113
  70. package/skills/bugteam/reference/workflow-path-b-task-harness.md +0 -48
  71. package/skills/bugteam/test_team_lifecycle.py +0 -103
  72. package/skills/monitor-open-prs/test_team_lifecycle.py +0 -46
  73. package/skills/pr-converge/scripts/open_followup_copilot_pr.py +0 -136
  74. package/skills/pr-converge/scripts/test_open_followup_copilot_pr.py +0 -236
  75. package/skills/pr-converge/test_team_lifecycle.py +0 -56
  76. package/skills/pr-converge/workflows/ahk-auto-continue-loop.md +0 -108
@@ -0,0 +1,485 @@
1
+ """Tests for fetch_claude_inline_comments.
2
+
3
+ Covers:
4
+ - gh command uses --paginate --slurp on the comments endpoint
5
+ - only claude reviewer-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 Claude 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_claude_inline_comments.py"
26
+ spec = importlib.util.spec_from_file_location(
27
+ "fetch_claude_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_claude_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
+ "state": "CHANGES_REQUESTED",
53
+ "body": "Please address the inline notes.",
54
+ "classification": "dirty",
55
+ }
56
+ ]
57
+
58
+
59
+ def test_should_invoke_gh_with_paginate_slurp_against_comments_endpoint() -> None:
60
+ pages_payload = json.dumps([[]])
61
+ with (
62
+ patch.object(
63
+ fetch_claude_inline_comments_module,
64
+ "fetch_claude_reviews",
65
+ return_value=_default_review_for_head(commit="abc123", review_id=1),
66
+ ),
67
+ patch("subprocess.run") as mock_run,
68
+ ):
69
+ mock_run.return_value = _completed(pages_payload)
70
+ fetch_claude_inline_comments_module.fetch_claude_inline_comments(
71
+ owner="acme", repo="widget", number=42, current_head="abc123"
72
+ )
73
+ invoked_argv = mock_run.call_args[0][0]
74
+ assert invoked_argv[0] == "gh"
75
+ assert invoked_argv[1] == "api"
76
+ assert "repos/acme/widget/pulls/42/comments?per_page=100" in invoked_argv[2]
77
+ assert "--paginate" in invoked_argv
78
+ assert "--slurp" in invoked_argv
79
+
80
+
81
+ def test_should_filter_to_claude_reviewer_only() -> None:
82
+ pages_payload = json.dumps(
83
+ [
84
+ [
85
+ {
86
+ "id": 100,
87
+ "user": {"login": "cursor[bot]"},
88
+ "commit_id": "abc123",
89
+ "pull_request_review_id": 1,
90
+ "body": "bugbot finding",
91
+ "path": "x.py",
92
+ "line": 5,
93
+ },
94
+ {
95
+ "id": 101,
96
+ "user": {"login": "claude[bot]"},
97
+ "commit_id": "abc123",
98
+ "pull_request_review_id": 1,
99
+ "body": "claude finding",
100
+ "path": "x.py",
101
+ "line": 6,
102
+ },
103
+ ]
104
+ ]
105
+ )
106
+ with (
107
+ patch.object(
108
+ fetch_claude_inline_comments_module,
109
+ "fetch_claude_reviews",
110
+ return_value=_default_review_for_head(commit="abc123", review_id=1),
111
+ ),
112
+ patch("subprocess.run") as mock_run,
113
+ ):
114
+ mock_run.return_value = _completed(pages_payload)
115
+ all_inline_comments = (
116
+ fetch_claude_inline_comments_module.fetch_claude_inline_comments(
117
+ owner="acme", repo="widget", number=42, current_head="abc123"
118
+ )
119
+ )
120
+ assert len(all_inline_comments) == 1
121
+ assert all_inline_comments[0]["comment_id"] == 101
122
+
123
+
124
+ def test_should_filter_out_comments_not_on_current_head() -> None:
125
+ pages_payload = json.dumps(
126
+ [
127
+ [
128
+ {
129
+ "id": 200,
130
+ "user": {"login": "claude[bot]"},
131
+ "commit_id": "old_sha",
132
+ "pull_request_review_id": 1,
133
+ "body": "stale finding",
134
+ "path": "x.py",
135
+ "line": 5,
136
+ },
137
+ {
138
+ "id": 201,
139
+ "user": {"login": "claude[bot]"},
140
+ "commit_id": "current_sha",
141
+ "pull_request_review_id": 2,
142
+ "body": "fresh finding",
143
+ "path": "x.py",
144
+ "line": 6,
145
+ },
146
+ ]
147
+ ]
148
+ )
149
+ with (
150
+ patch.object(
151
+ fetch_claude_inline_comments_module,
152
+ "fetch_claude_reviews",
153
+ return_value=_default_review_for_head(commit="current_sha", review_id=2),
154
+ ),
155
+ patch("subprocess.run") as mock_run,
156
+ ):
157
+ mock_run.return_value = _completed(pages_payload)
158
+ all_inline_comments = (
159
+ fetch_claude_inline_comments_module.fetch_claude_inline_comments(
160
+ owner="acme", repo="widget", number=42, current_head="current_sha"
161
+ )
162
+ )
163
+ assert len(all_inline_comments) == 1
164
+ assert all_inline_comments[0]["comment_id"] == 201
165
+
166
+
167
+ def test_should_ignore_inline_comments_from_older_claude_review_on_same_commit() -> (
168
+ None
169
+ ):
170
+ pages_payload = json.dumps(
171
+ [
172
+ [
173
+ {
174
+ "id": 300,
175
+ "user": {"login": "claude[bot]"},
176
+ "commit_id": "same_sha",
177
+ "pull_request_review_id": 10,
178
+ "body": "stale dirty thread",
179
+ "path": "x.py",
180
+ "line": 1,
181
+ },
182
+ {
183
+ "id": 301,
184
+ "user": {"login": "claude[bot]"},
185
+ "commit_id": "same_sha",
186
+ "pull_request_review_id": 11,
187
+ "body": "current clean thread",
188
+ "path": "x.py",
189
+ "line": 2,
190
+ },
191
+ ]
192
+ ]
193
+ )
194
+ reviews_newest_first = [
195
+ {
196
+ "review_id": 11,
197
+ "commit_id": "same_sha",
198
+ "submitted_at": "2026-01-02T00:00:00Z",
199
+ "state": "APPROVED",
200
+ "body": "lgtm",
201
+ "classification": "clean",
202
+ },
203
+ {
204
+ "review_id": 10,
205
+ "commit_id": "same_sha",
206
+ "submitted_at": "2026-01-01T00:00:00Z",
207
+ "state": "CHANGES_REQUESTED",
208
+ "body": "fix the thing",
209
+ "classification": "dirty",
210
+ },
211
+ ]
212
+ with (
213
+ patch.object(
214
+ fetch_claude_inline_comments_module,
215
+ "fetch_claude_reviews",
216
+ return_value=reviews_newest_first,
217
+ ),
218
+ patch("subprocess.run") as mock_run,
219
+ ):
220
+ mock_run.return_value = _completed(pages_payload)
221
+ all_inline_comments = (
222
+ fetch_claude_inline_comments_module.fetch_claude_inline_comments(
223
+ owner="acme", repo="widget", number=42, current_head="same_sha"
224
+ )
225
+ )
226
+ assert [each_comment["comment_id"] for each_comment in all_inline_comments] == [301]
227
+
228
+
229
+ def test_should_return_empty_when_no_claude_review_exists_for_commit() -> None:
230
+ with (
231
+ patch.object(
232
+ fetch_claude_inline_comments_module,
233
+ "fetch_claude_reviews",
234
+ return_value=[
235
+ {
236
+ "review_id": 1,
237
+ "commit_id": "other_sha",
238
+ "submitted_at": "2026-01-01T00:00:00Z",
239
+ "state": "APPROVED",
240
+ "body": "",
241
+ "classification": "clean",
242
+ }
243
+ ],
244
+ ),
245
+ patch("subprocess.run") as mock_run,
246
+ ):
247
+ all_inline_comments = (
248
+ fetch_claude_inline_comments_module.fetch_claude_inline_comments(
249
+ owner="acme", repo="widget", number=42, current_head="missing_sha"
250
+ )
251
+ )
252
+ assert all_inline_comments == []
253
+ mock_run.assert_not_called()
254
+
255
+
256
+ def test_should_flatten_across_pages() -> None:
257
+ pages_payload = json.dumps(
258
+ [
259
+ [
260
+ {
261
+ "id": 1,
262
+ "user": {"login": "claude[bot]"},
263
+ "commit_id": "abc",
264
+ "pull_request_review_id": 9,
265
+ "body": "a",
266
+ "path": "f.py",
267
+ "line": 1,
268
+ }
269
+ ],
270
+ [
271
+ {
272
+ "id": 2,
273
+ "user": {"login": "claude[bot]"},
274
+ "commit_id": "abc",
275
+ "pull_request_review_id": 9,
276
+ "body": "b",
277
+ "path": "f.py",
278
+ "line": 2,
279
+ },
280
+ {
281
+ "id": 3,
282
+ "user": {"login": "claude[bot]"},
283
+ "commit_id": "abc",
284
+ "pull_request_review_id": 9,
285
+ "body": "c",
286
+ "path": "f.py",
287
+ "line": 3,
288
+ },
289
+ ],
290
+ ]
291
+ )
292
+ with (
293
+ patch.object(
294
+ fetch_claude_inline_comments_module,
295
+ "fetch_claude_reviews",
296
+ return_value=_default_review_for_head(commit="abc", review_id=9),
297
+ ),
298
+ patch("subprocess.run") as mock_run,
299
+ ):
300
+ mock_run.return_value = _completed(pages_payload)
301
+ all_inline_comments = (
302
+ fetch_claude_inline_comments_module.fetch_claude_inline_comments(
303
+ owner="acme", repo="widget", number=42, current_head="abc"
304
+ )
305
+ )
306
+ assert [each_comment["comment_id"] for each_comment in all_inline_comments] == [
307
+ 1,
308
+ 2,
309
+ 3,
310
+ ]
311
+
312
+
313
+ def test_should_match_login_case_insensitively() -> None:
314
+ pages_payload = json.dumps(
315
+ [
316
+ [
317
+ {
318
+ "id": 300,
319
+ "user": {"login": "Claude"},
320
+ "commit_id": "abc123",
321
+ "pull_request_review_id": 1,
322
+ "body": "uppercase login",
323
+ "path": "x.py",
324
+ "line": 5,
325
+ },
326
+ {
327
+ "id": 301,
328
+ "user": {"login": "CLAUDE-CODE[bot]"},
329
+ "commit_id": "abc123",
330
+ "pull_request_review_id": 1,
331
+ "body": "screaming login",
332
+ "path": "x.py",
333
+ "line": 6,
334
+ },
335
+ ]
336
+ ]
337
+ )
338
+ with (
339
+ patch.object(
340
+ fetch_claude_inline_comments_module,
341
+ "fetch_claude_reviews",
342
+ return_value=_default_review_for_head(commit="abc123", review_id=1),
343
+ ),
344
+ patch("subprocess.run") as mock_run,
345
+ ):
346
+ mock_run.return_value = _completed(pages_payload)
347
+ all_inline_comments = (
348
+ fetch_claude_inline_comments_module.fetch_claude_inline_comments(
349
+ owner="acme", repo="widget", number=42, current_head="abc123"
350
+ )
351
+ )
352
+ assert {each_comment["comment_id"] for each_comment in all_inline_comments} == {
353
+ 300,
354
+ 301,
355
+ }
356
+
357
+
358
+ def test_should_match_login_containing_claude_substring() -> None:
359
+ pages_payload = json.dumps(
360
+ [
361
+ [
362
+ {
363
+ "id": 400,
364
+ "user": {"login": "anthropic-claude[bot]"},
365
+ "commit_id": "abc123",
366
+ "pull_request_review_id": 1,
367
+ "body": "non-canonical login still matches",
368
+ "path": "x.py",
369
+ "line": 5,
370
+ }
371
+ ]
372
+ ]
373
+ )
374
+ with (
375
+ patch.object(
376
+ fetch_claude_inline_comments_module,
377
+ "fetch_claude_reviews",
378
+ return_value=_default_review_for_head(commit="abc123", review_id=1),
379
+ ),
380
+ patch("subprocess.run") as mock_run,
381
+ ):
382
+ mock_run.return_value = _completed(pages_payload)
383
+ all_inline_comments = (
384
+ fetch_claude_inline_comments_module.fetch_claude_inline_comments(
385
+ owner="acme", repo="widget", number=42, current_head="abc123"
386
+ )
387
+ )
388
+ assert len(all_inline_comments) == 1
389
+ assert all_inline_comments[0]["comment_id"] == 400
390
+
391
+
392
+ def test_should_exclude_login_without_claude_substring() -> None:
393
+ pages_payload = json.dumps(
394
+ [
395
+ [
396
+ {
397
+ "id": 500,
398
+ "user": {"login": "copilot-pull-request-reviewer[bot]"},
399
+ "commit_id": "abc123",
400
+ "pull_request_review_id": 1,
401
+ "body": "copilot finding",
402
+ "path": "x.py",
403
+ "line": 5,
404
+ },
405
+ {
406
+ "id": 501,
407
+ "user": {"login": "dependabot[bot]"},
408
+ "commit_id": "abc123",
409
+ "pull_request_review_id": 1,
410
+ "body": "dependency bump",
411
+ "path": "x.py",
412
+ "line": 6,
413
+ },
414
+ ]
415
+ ]
416
+ )
417
+ with (
418
+ patch.object(
419
+ fetch_claude_inline_comments_module,
420
+ "fetch_claude_reviews",
421
+ return_value=_default_review_for_head(commit="abc123", review_id=1),
422
+ ),
423
+ patch("subprocess.run") as mock_run,
424
+ ):
425
+ mock_run.return_value = _completed(pages_payload)
426
+ all_inline_comments = (
427
+ fetch_claude_inline_comments_module.fetch_claude_inline_comments(
428
+ owner="acme", repo="widget", number=42, current_head="abc123"
429
+ )
430
+ )
431
+ assert all_inline_comments == []
432
+
433
+
434
+ def test_should_raise_when_gh_subprocess_fails() -> None:
435
+ failure = subprocess.CalledProcessError(
436
+ returncode=1, cmd=["gh"], stderr="auth failure"
437
+ )
438
+ with (
439
+ patch.object(
440
+ fetch_claude_inline_comments_module,
441
+ "fetch_claude_reviews",
442
+ return_value=_default_review_for_head(commit="abc", review_id=1),
443
+ ),
444
+ patch("subprocess.run", side_effect=failure),
445
+ ):
446
+ with pytest.raises(subprocess.CalledProcessError):
447
+ fetch_claude_inline_comments_module.fetch_claude_inline_comments(
448
+ owner="acme", repo="widget", number=42, current_head="abc"
449
+ )
450
+
451
+
452
+ def test_should_return_entries_whose_keys_are_strings() -> None:
453
+ pages_payload = json.dumps(
454
+ [
455
+ [
456
+ {
457
+ "id": 101,
458
+ "user": {"login": "claude[bot]"},
459
+ "commit_id": "abc123",
460
+ "pull_request_review_id": 1,
461
+ "body": "claude finding",
462
+ "path": "x.py",
463
+ "line": 6,
464
+ }
465
+ ]
466
+ ]
467
+ )
468
+ with (
469
+ patch.object(
470
+ fetch_claude_inline_comments_module,
471
+ "fetch_claude_reviews",
472
+ return_value=_default_review_for_head(commit="abc123", review_id=1),
473
+ ),
474
+ patch("subprocess.run") as mock_run,
475
+ ):
476
+ mock_run.return_value = _completed(pages_payload)
477
+ all_inline_comments = (
478
+ fetch_claude_inline_comments_module.fetch_claude_inline_comments(
479
+ owner="acme", repo="widget", number=42, current_head="abc123"
480
+ )
481
+ )
482
+ assert len(all_inline_comments) == 1
483
+ first_comment_entry = all_inline_comments[0]
484
+ assert isinstance(first_comment_entry, dict)
485
+ assert all(isinstance(each_key, str) for each_key in first_comment_entry.keys())