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,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 test_should_reference_cursor_bot_login_constant_directly_without_local_alias() -> None:
290
- source_text = (
291
- Path(__file__).resolve().parent / "fetch_bugbot_inline_comments.py"
292
- ).read_text(encoding="utf-8")
293
- assert "cursor_bot_login = CURSOR_BOT_LOGIN" not in source_text
294
- assert "CURSOR_BOT_LOGIN" in source_text
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 test_should_reference_cursor_bot_login_constant_directly_without_local_alias() -> None:
180
- source_text = (
181
- Path(__file__).resolve().parent / "fetch_bugbot_reviews.py"
182
- ).read_text(encoding="utf-8")
183
- assert "cursor_bot_login = CURSOR_BOT_LOGIN" not in source_text
184
- assert "CURSOR_BOT_LOGIN" in source_text
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: