claude-dev-env 1.93.0 → 1.94.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.
Files changed (220) hide show
  1. package/_shared/pr-loop/CLAUDE.md +1 -0
  2. package/_shared/pr-loop/audit-contract.md +1 -1
  3. package/_shared/pr-loop/gh-payloads.md +3 -3
  4. package/_shared/pr-loop/post-audit-thread-contract.md +51 -0
  5. package/_shared/pr-loop/scripts/README.md +2 -2
  6. package/_shared/pr-loop/scripts/code_rules_gate.py +127 -13
  7. package/_shared/pr-loop/scripts/fix_hookspath.py +47 -12
  8. package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +4 -0
  9. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +146 -69
  10. package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +497 -374
  11. package/_shared/pr-loop/state-schema.md +3 -22
  12. package/bin/CLAUDE.md +1 -1
  13. package/bin/install.mjs +2 -7
  14. package/bin/install.test.mjs +10 -4
  15. package/hooks/blocking/CLAUDE.md +2 -0
  16. package/hooks/blocking/pii_commit_command.py +398 -0
  17. package/hooks/blocking/pii_payload_scan.py +133 -0
  18. package/hooks/blocking/pii_prevention_blocker.py +151 -449
  19. package/hooks/blocking/pii_scanner.py +26 -1
  20. package/hooks/blocking/test_code_rules_enforcer_dead_config_field.py +2 -2
  21. package/hooks/blocking/test_pii_prevention_blocker.py +299 -0
  22. package/hooks/blocking/test_pii_scanner.py +27 -2
  23. package/hooks/blocking/test_verdict_directory_write_blocker.py +42 -7
  24. package/hooks/hooks_constants/CLAUDE.md +2 -2
  25. package/hooks/hooks_constants/code_rules_path_utils_constants.py +0 -2
  26. package/hooks/hooks_constants/local_identity.py +65 -15
  27. package/hooks/hooks_constants/pii_prevention_constants.py +20 -8
  28. package/hooks/hooks_constants/pr_description_enforcer_constants.py +1 -1
  29. package/hooks/hooks_constants/test_local_identity.py +68 -0
  30. package/package.json +1 -1
  31. package/scripts/CLAUDE.md +10 -1
  32. package/scripts/Get-SessionAccount.ps1 +210 -0
  33. package/scripts/tests/CLAUDE.md +15 -1
  34. package/scripts/tests/Get-SessionAccount.Tests.ps1 +350 -0
  35. package/skills/CLAUDE.md +3 -25
  36. package/skills/anthropic-plan/SKILL.md +1 -13
  37. package/skills/autoconverge/CLAUDE.md +5 -2
  38. package/skills/autoconverge/SKILL.md +112 -387
  39. package/skills/autoconverge/reference/CLAUDE.md +4 -1
  40. package/skills/autoconverge/reference/closing-report.md +9 -2
  41. package/skills/autoconverge/reference/convergence.md +26 -22
  42. package/skills/autoconverge/reference/headless-safety.md +44 -0
  43. package/skills/autoconverge/reference/multi-pr.md +88 -0
  44. package/skills/autoconverge/reference/self-closing-loop.md +84 -0
  45. package/skills/autoconverge/reference/stop-conditions.md +21 -13
  46. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +87 -27
  47. package/skills/autoconverge/workflow/converge.contract.test.mjs +40 -10
  48. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +179 -47
  49. package/skills/autoconverge/workflow/converge.fix-progress.test.mjs +1 -1
  50. package/skills/autoconverge/workflow/converge.mjs +284 -94
  51. package/skills/bugteam/CLAUDE.md +1 -1
  52. package/skills/bugteam/CONSTRAINTS.md +3 -3
  53. package/skills/bugteam/PROMPTS.md +8 -19
  54. package/skills/bugteam/SKILL.md +13 -14
  55. package/skills/bugteam/reference/CLAUDE.md +1 -1
  56. package/skills/bugteam/reference/audit-and-teammates.md +2 -2
  57. package/skills/bugteam/reference/copilot-gap-analysis.md +2 -0
  58. package/skills/bugteam/reference/github-pr-reviews.md +56 -38
  59. package/skills/bugteam/reference/team-setup.md +11 -11
  60. package/skills/bugteam/scripts/CLAUDE.md +6 -6
  61. package/skills/bugteam/scripts/README.md +20 -8
  62. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +13 -1463
  63. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +23 -358
  64. package/skills/bugteam/scripts/bugteam_preflight.py +13 -315
  65. package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +2 -3
  66. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +34 -1053
  67. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +30 -347
  68. package/skills/bugteam/scripts/test_bugteam_preflight.py +30 -305
  69. package/skills/copilot-finding-triage/SKILL.md +80 -28
  70. package/skills/copilot-finding-triage/reference/tier-rubric.md +70 -11
  71. package/skills/copilot-finding-triage/templates/notification.md +8 -3
  72. package/skills/everything-search/SKILL.md +1 -2
  73. package/skills/grokify/SKILL.md +58 -0
  74. package/skills/grokify/templates/handoff-template.md +92 -0
  75. package/skills/orchestrator/SKILL.md +12 -8
  76. package/skills/orchestrator-refresh/SKILL.md +3 -3
  77. package/skills/pr-converge/SKILL.md +132 -233
  78. package/skills/pr-converge/reference/convergence-gates.md +102 -66
  79. package/skills/pr-converge/reference/examples.md +37 -25
  80. package/skills/pr-converge/reference/fix-protocol.md +7 -7
  81. package/skills/pr-converge/reference/multi-pr-orchestration.md +10 -4
  82. package/skills/pr-converge/reference/per-tick.md +64 -41
  83. package/skills/pr-converge/reference/state-schema.md +27 -2
  84. package/skills/pr-converge/scripts/CLAUDE.md +8 -0
  85. package/skills/pr-converge/scripts/README.md +4 -3
  86. package/skills/pr-converge/scripts/_pr_converge_path_setup.py +23 -0
  87. package/skills/pr-converge/scripts/check_convergence.py +189 -645
  88. package/skills/pr-converge/scripts/check_convergence_gates.py +235 -0
  89. package/skills/pr-converge/scripts/check_convergence_thread_gates.py +219 -0
  90. package/skills/pr-converge/scripts/conftest.py +8 -0
  91. package/skills/pr-converge/scripts/pr_converge_scripts_constants/CLAUDE.md +1 -0
  92. package/skills/pr-converge/scripts/pr_converge_scripts_constants/convergence_gate_constants.py +36 -0
  93. package/skills/pr-converge/scripts/test__pr_converge_path_setup.py +26 -0
  94. package/skills/pr-converge/scripts/test_check_convergence.py +73 -228
  95. package/skills/pr-converge/scripts/test_check_convergence_contract.py +321 -0
  96. package/skills/pr-converge/scripts/test_check_convergence_gates.py +68 -0
  97. package/skills/pr-converge/scripts/test_check_convergence_thread_gates.py +75 -0
  98. package/skills/privacy-hygiene/SKILL.md +8 -7
  99. package/skills/reviewer-gates/SKILL.md +26 -8
  100. package/skills/session-log/SKILL.md +11 -12
  101. package/skills/session-tidy/SKILL.md +40 -30
  102. package/skills/skill-builder/CLAUDE.md +13 -10
  103. package/skills/skill-builder/SKILL.md +31 -31
  104. package/skills/skill-builder/references/CLAUDE.md +10 -6
  105. package/skills/skill-builder/references/delegation-map.md +13 -4
  106. package/skills/skill-builder/references/description-field.md +113 -0
  107. package/skills/skill-builder/references/self-audit-checklist.md +21 -4
  108. package/skills/skill-builder/references/skill-modularity.md +113 -0
  109. package/skills/skill-builder/references/skill-types.md +4 -0
  110. package/skills/skill-builder/templates/CLAUDE.md +4 -4
  111. package/skills/skill-builder/templates/gap-analysis.md +25 -1
  112. package/skills/skill-builder/workflows/CLAUDE.md +6 -4
  113. package/skills/skill-builder/workflows/improve-skill.md +17 -16
  114. package/skills/skill-builder/workflows/new-skill.md +34 -10
  115. package/skills/skill-builder/workflows/polish-skill.md +59 -43
  116. package/skills/usage-pause/SKILL.md +10 -14
  117. package/skills/bdd-protocol/CLAUDE.md +0 -26
  118. package/skills/bdd-protocol/SKILL.md +0 -30
  119. package/skills/bdd-protocol/references/CLAUDE.md +0 -10
  120. package/skills/bdd-protocol/references/anti-patterns.md +0 -26
  121. package/skills/bdd-protocol/references/example-mapping.md +0 -23
  122. package/skills/bg-agent/CLAUDE.md +0 -17
  123. package/skills/bg-agent/SKILL.md +0 -69
  124. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_code_rules_gate_constants.py +0 -55
  125. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_fix_hookspath_constants.py +0 -26
  126. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_preflight_constants.py +0 -35
  127. package/skills/caveman/CLAUDE.md +0 -15
  128. package/skills/caveman/SKILL.md +0 -38
  129. package/skills/code/CLAUDE.md +0 -17
  130. package/skills/code/SKILL.md +0 -178
  131. package/skills/deep-research/CLAUDE.md +0 -17
  132. package/skills/deep-research/SKILL.md +0 -80
  133. package/skills/findbugs/CLAUDE.md +0 -20
  134. package/skills/findbugs/SKILL.md +0 -241
  135. package/skills/fixbugs/CLAUDE.md +0 -19
  136. package/skills/fixbugs/SKILL.md +0 -142
  137. package/skills/gh-paginate/CLAUDE.md +0 -18
  138. package/skills/gh-paginate/SKILL.md +0 -84
  139. package/skills/gotcha/CLAUDE.md +0 -33
  140. package/skills/gotcha/SKILL.md +0 -73
  141. package/skills/implement/CLAUDE.md +0 -27
  142. package/skills/implement/SKILL.md +0 -66
  143. package/skills/implement/scripts/CLAUDE.md +0 -22
  144. package/skills/implement/scripts/append_note.py +0 -133
  145. package/skills/implement/scripts/implement_scripts_constants/CLAUDE.md +0 -22
  146. package/skills/implement/scripts/implement_scripts_constants/__init__.py +0 -0
  147. package/skills/implement/scripts/implement_scripts_constants/notes_constants.py +0 -12
  148. package/skills/implement/scripts/test_append_note.py +0 -191
  149. package/skills/log-audit/CLAUDE.md +0 -20
  150. package/skills/log-audit/SKILL.md +0 -66
  151. package/skills/log-audit/reference/CLAUDE.md +0 -9
  152. package/skills/log-audit/reference/charter.md +0 -52
  153. package/skills/log-audit/scripts/CLAUDE.md +0 -27
  154. package/skills/log-audit/scripts/cluster_recurrences.py +0 -261
  155. package/skills/log-audit/scripts/collect_log_window.py +0 -199
  156. package/skills/log-audit/scripts/log_audit_constants/CLAUDE.md +0 -12
  157. package/skills/log-audit/scripts/log_audit_constants/__init__.py +0 -0
  158. package/skills/log-audit/scripts/log_audit_constants/cluster_recurrences_constants.py +0 -23
  159. package/skills/log-audit/scripts/log_audit_constants/collect_log_window_constants.py +0 -24
  160. package/skills/log-audit/scripts/log_audit_constants/mine_copilot_findings_constants.py +0 -49
  161. package/skills/log-audit/scripts/mine_copilot_findings.py +0 -302
  162. package/skills/log-audit/scripts/test_cluster_recurrences.py +0 -160
  163. package/skills/log-audit/scripts/test_collect_log_window.py +0 -111
  164. package/skills/log-audit/scripts/test_mine_copilot_findings.py +0 -126
  165. package/skills/logifix/CLAUDE.md +0 -36
  166. package/skills/logifix/SKILL.md +0 -69
  167. package/skills/logifix/scripts/CLAUDE.md +0 -16
  168. package/skills/logifix/scripts/logifix.ps1 +0 -205
  169. package/skills/monitor-open-prs/CLAUDE.md +0 -34
  170. package/skills/monitor-open-prs/SKILL.md +0 -88
  171. package/skills/monitor-open-prs/scripts/CLAUDE.md +0 -17
  172. package/skills/monitor-open-prs/scripts/discover_open_prs.py +0 -69
  173. package/skills/monitor-open-prs/scripts/test_discover_open_prs.py +0 -149
  174. package/skills/monitor-open-prs/test_skill_contract.py +0 -27
  175. package/skills/post-audit-findings/SKILL.md +0 -80
  176. package/skills/pr-consistency-audit/CLAUDE.md +0 -34
  177. package/skills/pr-consistency-audit/SKILL.md +0 -116
  178. package/skills/pr-consistency-audit/reference/CLAUDE.md +0 -16
  179. package/skills/pr-consistency-audit/reference/detection-rules.md +0 -96
  180. package/skills/pr-consistency-audit/reference/illustrations.md +0 -78
  181. package/skills/pr-fix-protocol/SKILL.md +0 -65
  182. package/skills/pr-review-responder/CLAUDE.md +0 -35
  183. package/skills/pr-review-responder/EXAMPLES.md +0 -590
  184. package/skills/pr-review-responder/PRINCIPLES.md +0 -533
  185. package/skills/pr-review-responder/README.md +0 -168
  186. package/skills/pr-review-responder/SKILL.md +0 -203
  187. package/skills/pr-review-responder/TESTING.md +0 -350
  188. package/skills/pr-scope-resolve/SKILL.md +0 -45
  189. package/skills/pre-compact/CLAUDE.md +0 -24
  190. package/skills/pre-compact/SKILL.md +0 -134
  191. package/skills/qbug/CLAUDE.md +0 -40
  192. package/skills/qbug/SKILL.md +0 -387
  193. package/skills/qbug/test_qbug_skill_audit_schema.py +0 -152
  194. package/skills/qbug/test_qbug_skill_post_fix_audit.py +0 -103
  195. package/skills/refine/CLAUDE.md +0 -44
  196. package/skills/refine/SKILL.md +0 -262
  197. package/skills/refine/templates/CLAUDE.md +0 -17
  198. package/skills/refine/templates/implementation-notes-template.html +0 -56
  199. package/skills/refine/templates/plan-template.md +0 -60
  200. package/skills/research-mode/CLAUDE.md +0 -35
  201. package/skills/research-mode/SKILL.md +0 -53
  202. package/skills/structure-prompt/CLAUDE.md +0 -42
  203. package/skills/structure-prompt/SKILL.md +0 -44
  204. package/skills/structure-prompt/reference/CLAUDE.md +0 -28
  205. package/skills/structure-prompt/reference/adversarial-tuning.md +0 -62
  206. package/skills/structure-prompt/reference/block-classification.md +0 -27
  207. package/skills/structure-prompt/reference/canonical-case.md +0 -48
  208. package/skills/structure-prompt/reference/citation-depth.md +0 -70
  209. package/skills/structure-prompt/reference/cleanup.md +0 -33
  210. package/skills/structure-prompt/reference/constraints.md +0 -33
  211. package/skills/structure-prompt/reference/directives.md +0 -37
  212. package/skills/structure-prompt/reference/examples.md +0 -72
  213. package/skills/structure-prompt/reference/instantiation.md +0 -51
  214. package/skills/structure-prompt/reference/output-contract.md +0 -72
  215. package/skills/structure-prompt/reference/per-category.md +0 -23
  216. package/skills/structure-prompt/reference/persona.md +0 -38
  217. package/skills/structure-prompt/reference/research.md +0 -33
  218. package/skills/structure-prompt/reference/structure.md +0 -28
  219. package/skills/verified-build/CLAUDE.md +0 -33
  220. package/skills/verified-build/SKILL.md +0 -33
@@ -1,12 +1,8 @@
1
1
  """Tests for check_convergence.
2
2
 
3
3
  Covers the bugteam audit gate (`_check_bugteam_clean`) which identifies
4
- bugteam reviews by body header signature rather than by the posting user's
5
- GitHub login. Three scenarios are exercised:
6
-
7
- - a clean bugteam review on the current HEAD passes the gate
8
- - a dirty bugteam review on the current HEAD fails the gate
9
- - the absence of any bugteam review on the current HEAD fails the gate
4
+ bugteam reviews by body header signature, the down-flag resolvers, the
5
+ reviewer-bypass paths through ``check_all``, and the ``main`` entry point.
10
6
  """
11
7
 
12
8
  from __future__ import annotations
@@ -20,12 +16,12 @@ from typing import Callable
20
16
 
21
17
  import pytest
22
18
 
19
+ import _pr_converge_path_setup # noqa: F401
20
+ from pr_converge_skill_constants.constants import EXIT_CODE_GH_ERROR
21
+
23
22
  _SCRIPTS_DIRECTORY = Path(__file__).absolute().parent
24
23
  _PR_CONVERGE_DIRECTORY = _SCRIPTS_DIRECTORY.parent
25
24
 
26
- if str(_PR_CONVERGE_DIRECTORY) not in sys.path:
27
- sys.path.insert(0, str(_PR_CONVERGE_DIRECTORY))
28
-
29
25
 
30
26
  def _load_module() -> ModuleType:
31
27
  for each_cached_name in [
@@ -72,6 +68,16 @@ NON_BUGTEAM_BODY = (
72
68
  "Cursor Bugbot has reviewed your changes and found 0 potential issues."
73
69
  )
74
70
 
71
+ _ALL_LEAF_GATE_NAMES = [
72
+ "_check_bugbot",
73
+ "_check_bugbot_not_dirty",
74
+ "_check_bugteam_clean",
75
+ "_check_bot_review",
76
+ "_count_unresolved_bot_threads",
77
+ "_get_mergeable",
78
+ "_check_no_pending_reviews",
79
+ ]
80
+
75
81
 
76
82
  def _make_stub_gh_paginated(
77
83
  all_review_objects: list[dict[str, object]],
@@ -85,6 +91,27 @@ def _make_stub_gh_paginated(
85
91
  return stub_gh_api_paginated
86
92
 
87
93
 
94
+ def _passing_head_sha(**_call_keywords: object) -> str:
95
+ return CURRENT_HEAD_SHA
96
+
97
+
98
+ def _passing_gate(**_call_keywords: object) -> tuple[bool, str]:
99
+ return True, "stub passing"
100
+
101
+
102
+ def _raise_if_called(gate_label: str) -> Callable[..., tuple[bool, str]]:
103
+ def _stub(**_call_keywords: object) -> tuple[bool, str]:
104
+ raise AssertionError(f"{gate_label} must not run when its gate is bypassed")
105
+
106
+ return _stub
107
+
108
+
109
+ def _patch_all_gates_passing(monkeypatch: pytest.MonkeyPatch) -> None:
110
+ monkeypatch.setattr(check_convergence, "_get_pr_head_sha", _passing_head_sha)
111
+ for each_gate_name in _ALL_LEAF_GATE_NAMES:
112
+ monkeypatch.setattr(check_convergence, each_gate_name, _passing_gate)
113
+
114
+
88
115
  def should_pass_when_clean_bugteam_review_present_on_current_head(
89
116
  monkeypatch: pytest.MonkeyPatch,
90
117
  ) -> None:
@@ -96,13 +123,11 @@ def should_pass_when_clean_bugteam_review_present_on_current_head(
96
123
  "submitted_at": "2026-05-17T12:00:00Z",
97
124
  }
98
125
  ]
99
- stub = _make_stub_gh_paginated(reviews_payload)
100
- monkeypatch.setattr(check_convergence, "_gh_api_paginated", stub)
126
+ monkeypatch.setattr(
127
+ check_convergence, "_gh_api_paginated", _make_stub_gh_paginated(reviews_payload)
128
+ )
101
129
  passed, detail = check_convergence._check_bugteam_clean(
102
- owner="JonEcho",
103
- repo="tests",
104
- number=42,
105
- head_sha=CURRENT_HEAD_SHA,
130
+ owner="JonEcho", repo="tests", number=42, head_sha=CURRENT_HEAD_SHA
106
131
  )
107
132
  assert passed is True
108
133
  assert "clean bugteam audit" in detail
@@ -120,13 +145,11 @@ def should_fail_when_dirty_bugteam_review_present_on_current_head(
120
145
  "submitted_at": "2026-05-17T12:00:00Z",
121
146
  }
122
147
  ]
123
- stub = _make_stub_gh_paginated(reviews_payload)
124
- monkeypatch.setattr(check_convergence, "_gh_api_paginated", stub)
148
+ monkeypatch.setattr(
149
+ check_convergence, "_gh_api_paginated", _make_stub_gh_paginated(reviews_payload)
150
+ )
125
151
  passed, detail = check_convergence._check_bugteam_clean(
126
- owner="JonEcho",
127
- repo="tests",
128
- number=42,
129
- head_sha=CURRENT_HEAD_SHA,
152
+ owner="JonEcho", repo="tests", number=42, head_sha=CURRENT_HEAD_SHA
130
153
  )
131
154
  assert passed is False
132
155
  assert "dirty bugteam audit" in detail
@@ -150,13 +173,11 @@ def should_fail_when_no_bugteam_review_present_on_current_head(
150
173
  "submitted_at": "2026-05-17T11:00:00Z",
151
174
  },
152
175
  ]
153
- stub = _make_stub_gh_paginated(reviews_payload)
154
- monkeypatch.setattr(check_convergence, "_gh_api_paginated", stub)
176
+ monkeypatch.setattr(
177
+ check_convergence, "_gh_api_paginated", _make_stub_gh_paginated(reviews_payload)
178
+ )
155
179
  passed, detail = check_convergence._check_bugteam_clean(
156
- owner="JonEcho",
157
- repo="tests",
158
- number=42,
159
- head_sha=CURRENT_HEAD_SHA,
180
+ owner="JonEcho", repo="tests", number=42, head_sha=CURRENT_HEAD_SHA
160
181
  )
161
182
  assert passed is False
162
183
  assert "no bugteam review found" in detail
@@ -175,10 +196,7 @@ def should_fail_with_shape_detail_when_gh_returns_non_list_payload(
175
196
  check_convergence, "_gh_api_paginated", stub_gh_api_paginated_returning_object
176
197
  )
177
198
  passed, detail = check_convergence._check_bugteam_clean(
178
- owner="JonEcho",
179
- repo="tests",
180
- number=42,
181
- head_sha=CURRENT_HEAD_SHA,
199
+ owner="JonEcho", repo="tests", number=42, head_sha=CURRENT_HEAD_SHA
182
200
  )
183
201
  assert passed is False
184
202
  assert "unexpected gh api response shape" in detail
@@ -199,12 +217,8 @@ def test_private_helpers_reject_non_bugteam_body() -> None:
199
217
  assert check_convergence._is_clean_bugteam_review(NON_BUGTEAM_BODY) is False
200
218
 
201
219
 
202
- CLEAN_LEGACY_BUGTEAM_BODY = (
203
- "## /bugteam loop 1 audit: 0 P0 / 0 P1 / 0 P2 → clean"
204
- )
205
- DIRTY_LEGACY_BUGTEAM_BODY = (
206
- "## /bugteam loop 1 audit: 1 P0 / 0 P1 / 0 P2 → dirty"
207
- )
220
+ CLEAN_LEGACY_BUGTEAM_BODY = "## /bugteam loop 1 audit: 0 P0 / 0 P1 / 0 P2 → clean"
221
+ DIRTY_LEGACY_BUGTEAM_BODY = "## /bugteam loop 1 audit: 1 P0 / 0 P1 / 0 P2 → dirty"
208
222
 
209
223
 
210
224
  def test_private_helpers_recognize_clean_legacy_header_body() -> None:
@@ -217,60 +231,44 @@ def test_private_helpers_recognize_dirty_legacy_header_body() -> None:
217
231
  assert check_convergence._is_clean_bugteam_review(DIRTY_LEGACY_BUGTEAM_BODY) is False
218
232
 
219
233
 
220
- def should_resolve_bugbot_down_true_when_flag_set(
221
- monkeypatch: pytest.MonkeyPatch,
222
- ) -> None:
234
+ def should_resolve_bugbot_down_true_when_flag_set(monkeypatch: pytest.MonkeyPatch) -> None:
223
235
  monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
224
236
  assert check_convergence._resolve_bugbot_down(True) is True
225
237
 
226
238
 
227
- def should_resolve_bugbot_down_true_when_env_disables_bugbot(
228
- monkeypatch: pytest.MonkeyPatch,
229
- ) -> None:
239
+ def should_resolve_bugbot_down_true_when_env_disables_bugbot(monkeypatch: pytest.MonkeyPatch) -> None:
230
240
  monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "bugbot")
231
241
  assert check_convergence._resolve_bugbot_down(False) is True
232
242
 
233
243
 
234
- def should_resolve_bugbot_down_true_when_flag_unset_and_env_empty(
235
- monkeypatch: pytest.MonkeyPatch,
236
- ) -> None:
244
+ def should_resolve_bugbot_down_true_when_flag_unset_and_env_empty(monkeypatch: pytest.MonkeyPatch) -> None:
237
245
  monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
238
246
  monkeypatch.delenv("CLAUDE_REVIEWS_ENABLED", raising=False)
239
247
  assert check_convergence._resolve_bugbot_down(False) is True
240
248
 
241
249
 
242
- def should_resolve_bugbot_down_false_when_enabled_lists_bugbot(
243
- monkeypatch: pytest.MonkeyPatch,
244
- ) -> None:
250
+ def should_resolve_bugbot_down_false_when_enabled_lists_bugbot(monkeypatch: pytest.MonkeyPatch) -> None:
245
251
  monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
246
252
  monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "bugbot")
247
253
  assert check_convergence._resolve_bugbot_down(False) is False
248
254
 
249
255
 
250
- def should_resolve_copilot_down_true_when_flag_set(
251
- monkeypatch: pytest.MonkeyPatch,
252
- ) -> None:
256
+ def should_resolve_copilot_down_true_when_flag_set(monkeypatch: pytest.MonkeyPatch) -> None:
253
257
  monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
254
258
  assert check_convergence._resolve_copilot_down(True) is True
255
259
 
256
260
 
257
- def should_resolve_copilot_down_true_when_env_disables_copilot(
258
- monkeypatch: pytest.MonkeyPatch,
259
- ) -> None:
261
+ def should_resolve_copilot_down_true_when_env_disables_copilot(monkeypatch: pytest.MonkeyPatch) -> None:
260
262
  monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "copilot")
261
263
  assert check_convergence._resolve_copilot_down(False) is True
262
264
 
263
265
 
264
- def should_resolve_copilot_down_false_when_flag_unset_and_env_empty(
265
- monkeypatch: pytest.MonkeyPatch,
266
- ) -> None:
266
+ def should_resolve_copilot_down_false_when_flag_unset_and_env_empty(monkeypatch: pytest.MonkeyPatch) -> None:
267
267
  monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
268
268
  assert check_convergence._resolve_copilot_down(False) is False
269
269
 
270
270
 
271
- def should_resolve_copilot_down_false_when_env_disables_only_bugbot(
272
- monkeypatch: pytest.MonkeyPatch,
273
- ) -> None:
271
+ def should_resolve_copilot_down_false_when_env_disables_only_bugbot(monkeypatch: pytest.MonkeyPatch) -> None:
274
272
  monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "bugbot")
275
273
  assert check_convergence._resolve_copilot_down(False) is False
276
274
 
@@ -278,176 +276,30 @@ def should_resolve_copilot_down_false_when_env_disables_only_bugbot(
278
276
  def should_bypass_bugbot_gates_when_bugbot_down_is_true(
279
277
  monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
280
278
  ) -> None:
281
- all_invocation_names: list[str] = []
282
-
283
- def stub_get_pr_head_sha(*, owner: str, repo: str, number: int) -> str:
284
- all_invocation_names.append("_get_pr_head_sha")
285
- return CURRENT_HEAD_SHA
286
-
287
- def stub_check_bugbot_should_not_be_called(
288
- *, owner: str, repo: str, sha: str
289
- ) -> tuple[bool, str]:
290
- all_invocation_names.append("_check_bugbot")
291
- raise AssertionError("_check_bugbot must not be invoked when bugbot_down=True")
292
-
293
- def stub_check_bugbot_not_dirty_should_not_be_called(
294
- *, owner: str, repo: str, number: int, head_sha: str
295
- ) -> tuple[bool, str]:
296
- all_invocation_names.append("_check_bugbot_not_dirty")
297
- raise AssertionError(
298
- "_check_bugbot_not_dirty must not be invoked when bugbot_down=True"
299
- )
300
-
301
- def stub_check_bugteam_clean(
302
- *, owner: str, repo: str, number: int, head_sha: str
303
- ) -> tuple[bool, str]:
304
- all_invocation_names.append("_check_bugteam_clean")
305
- return True, "stub passing"
306
-
307
- def stub_check_bot_review(
308
- *,
309
- owner: str,
310
- repo: str,
311
- number: int,
312
- head_sha: str,
313
- login_substring: str,
314
- clean_states: tuple[str, ...],
315
- dirty_states: tuple[str, ...],
316
- label: str,
317
- ) -> tuple[bool, str]:
318
- all_invocation_names.append("_check_bot_review")
319
- return True, "stub passing"
320
-
321
- def stub_count_unresolved_bot_threads(
322
- *, owner: str, repo: str, number: int
323
- ) -> tuple[bool, str]:
324
- all_invocation_names.append("_count_unresolved_bot_threads")
325
- return True, "stub passing"
326
-
327
- def stub_get_mergeable(
328
- *, owner: str, repo: str, number: int
329
- ) -> tuple[bool, str]:
330
- all_invocation_names.append("_get_mergeable")
331
- return True, "stub passing"
332
-
333
- def stub_check_no_pending_reviews(
334
- *, owner: str, repo: str, number: int
335
- ) -> tuple[bool, str]:
336
- all_invocation_names.append("_check_no_pending_reviews")
337
- return True, "stub passing"
338
-
339
- monkeypatch.setattr(check_convergence, "_get_pr_head_sha", stub_get_pr_head_sha)
340
- monkeypatch.setattr(check_convergence, "_check_bugbot", stub_check_bugbot_should_not_be_called)
279
+ _patch_all_gates_passing(monkeypatch)
280
+ monkeypatch.setattr(check_convergence, "_check_bugbot", _raise_if_called("_check_bugbot"))
341
281
  monkeypatch.setattr(
342
- check_convergence,
343
- "_check_bugbot_not_dirty",
344
- stub_check_bugbot_not_dirty_should_not_be_called,
282
+ check_convergence, "_check_bugbot_not_dirty", _raise_if_called("_check_bugbot_not_dirty")
345
283
  )
346
- monkeypatch.setattr(check_convergence, "_check_bugteam_clean", stub_check_bugteam_clean)
347
- monkeypatch.setattr(check_convergence, "_check_bot_review", stub_check_bot_review)
348
- monkeypatch.setattr(
349
- check_convergence, "_count_unresolved_bot_threads", stub_count_unresolved_bot_threads
350
- )
351
- monkeypatch.setattr(check_convergence, "_get_mergeable", stub_get_mergeable)
352
- monkeypatch.setattr(
353
- check_convergence, "_check_no_pending_reviews", stub_check_no_pending_reviews
354
- )
355
-
356
284
  exit_code = check_convergence.check_all(
357
285
  owner="o", repo="r", number=1, is_bugbot_down=True, is_copilot_down=False
358
286
  )
359
- captured_stdout = capsys.readouterr().out
360
-
361
- assert "_check_bugbot" not in all_invocation_names
362
- assert "_check_bugbot_not_dirty" not in all_invocation_names
363
- assert "bypassed (bugbot_down)" in captured_stdout
287
+ assert "bypassed (bugbot_down)" in capsys.readouterr().out
364
288
  assert exit_code == 0
365
289
 
366
290
 
367
291
  def should_bypass_copilot_gates_when_copilot_down_is_true(
368
292
  monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
369
293
  ) -> None:
370
- all_invocation_names: list[str] = []
371
-
372
- def stub_get_pr_head_sha(*, owner: str, repo: str, number: int) -> str:
373
- return CURRENT_HEAD_SHA
374
-
375
- def stub_check_bugbot(*, owner: str, repo: str, sha: str) -> tuple[bool, str]:
376
- return True, "stub passing"
377
-
378
- def stub_check_bugbot_not_dirty(
379
- *, owner: str, repo: str, number: int, head_sha: str
380
- ) -> tuple[bool, str]:
381
- return True, "stub passing"
382
-
383
- def stub_check_bugteam_clean(
384
- *, owner: str, repo: str, number: int, head_sha: str
385
- ) -> tuple[bool, str]:
386
- return True, "stub passing"
387
-
388
- def stub_check_bot_review_should_not_be_called(
389
- *,
390
- owner: str,
391
- repo: str,
392
- number: int,
393
- head_sha: str,
394
- login_substring: str,
395
- clean_states: tuple[str, ...],
396
- dirty_states: tuple[str, ...],
397
- label: str,
398
- ) -> tuple[bool, str]:
399
- all_invocation_names.append("_check_bot_review")
400
- raise AssertionError(
401
- "_check_bot_review must not be invoked when copilot_down=True"
402
- )
403
-
404
- def stub_count_unresolved_bot_threads(
405
- *, owner: str, repo: str, number: int
406
- ) -> tuple[bool, str]:
407
- return True, "stub passing"
408
-
409
- def stub_get_mergeable(
410
- *, owner: str, repo: str, number: int
411
- ) -> tuple[bool, str]:
412
- return True, "stub passing"
413
-
414
- def stub_check_no_pending_reviews_should_not_be_called(
415
- *, owner: str, repo: str, number: int
416
- ) -> tuple[bool, str]:
417
- all_invocation_names.append("_check_no_pending_reviews")
418
- raise AssertionError(
419
- "_check_no_pending_reviews must not be invoked when copilot_down=True"
420
- )
421
-
422
- monkeypatch.setattr(check_convergence, "_get_pr_head_sha", stub_get_pr_head_sha)
423
- monkeypatch.setattr(check_convergence, "_check_bugbot", stub_check_bugbot)
294
+ _patch_all_gates_passing(monkeypatch)
295
+ monkeypatch.setattr(check_convergence, "_check_bot_review", _raise_if_called("_check_bot_review"))
424
296
  monkeypatch.setattr(
425
- check_convergence, "_check_bugbot_not_dirty", stub_check_bugbot_not_dirty
297
+ check_convergence, "_check_no_pending_reviews", _raise_if_called("_check_no_pending_reviews")
426
298
  )
427
- monkeypatch.setattr(check_convergence, "_check_bugteam_clean", stub_check_bugteam_clean)
428
- monkeypatch.setattr(
429
- check_convergence,
430
- "_check_bot_review",
431
- stub_check_bot_review_should_not_be_called,
432
- )
433
- monkeypatch.setattr(
434
- check_convergence, "_count_unresolved_bot_threads", stub_count_unresolved_bot_threads
435
- )
436
- monkeypatch.setattr(check_convergence, "_get_mergeable", stub_get_mergeable)
437
- monkeypatch.setattr(
438
- check_convergence,
439
- "_check_no_pending_reviews",
440
- stub_check_no_pending_reviews_should_not_be_called,
441
- )
442
-
443
299
  exit_code = check_convergence.check_all(
444
300
  owner="o", repo="r", number=1, is_bugbot_down=False, is_copilot_down=True
445
301
  )
446
- captured_stdout = capsys.readouterr().out
447
-
448
- assert "_check_bot_review" not in all_invocation_names
449
- assert "_check_no_pending_reviews" not in all_invocation_names
450
- assert "bypassed (copilot_down)" in captured_stdout
302
+ assert "bypassed (copilot_down)" in capsys.readouterr().out
451
303
  assert exit_code == 0
452
304
 
453
305
 
@@ -480,24 +332,18 @@ def should_return_one_from_print_conditions_when_any_condition_fails(
480
332
  assert "One or more pre-conditions not met" in captured_stdout
481
333
 
482
334
 
483
- def should_propagate_systemexit_from_get_pr_head_sha(
484
- monkeypatch: pytest.MonkeyPatch,
485
- ) -> None:
486
- def stub_get_pr_head_sha_raising_systemexit(
487
- *, owner: str, repo: str, number: int
488
- ) -> str:
489
- raise SystemExit(check_convergence.EXIT_CODE_GH_ERROR)
335
+ def should_propagate_systemexit_from_get_pr_head_sha(monkeypatch: pytest.MonkeyPatch) -> None:
336
+ def stub_get_pr_head_sha_raising_systemexit(**_call_keywords: object) -> str:
337
+ raise SystemExit(EXIT_CODE_GH_ERROR)
490
338
 
491
339
  monkeypatch.setattr(
492
340
  check_convergence, "_get_pr_head_sha", stub_get_pr_head_sha_raising_systemexit
493
341
  )
494
-
495
342
  with pytest.raises(SystemExit) as exc_info:
496
343
  check_convergence.check_all(
497
344
  owner="o", repo="r", number=1, is_bugbot_down=False, is_copilot_down=False
498
345
  )
499
-
500
- assert exc_info.value.code == check_convergence.EXIT_CODE_GH_ERROR
346
+ assert exc_info.value.code == EXIT_CODE_GH_ERROR
501
347
 
502
348
 
503
349
  def should_derive_copilot_down_from_env_when_main_omits_the_flag(
@@ -513,13 +359,12 @@ def should_derive_copilot_down_from_env_when_main_omits_the_flag(
513
359
  number: int,
514
360
  is_bugbot_down: bool,
515
361
  is_copilot_down: bool,
362
+ is_bugteam_post_blocked: bool = False,
516
363
  ) -> int:
517
364
  captured_copilot_down.append(is_copilot_down)
518
365
  return 0
519
366
 
520
367
  monkeypatch.setattr(check_convergence, "check_all", stub_check_all)
521
- exit_code = check_convergence.main(
522
- ["--owner", "o", "--repo", "r", "--pr-number", "1"]
523
- )
368
+ exit_code = check_convergence.main(["--owner", "o", "--repo", "r", "--pr-number", "1"])
524
369
  assert exit_code == 0
525
370
  assert captured_copilot_down == [True]