claude-dev-env 1.94.0 → 2.0.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 (280) hide show
  1. package/_shared/advisor/CLAUDE.md +2 -2
  2. package/_shared/advisor/advisor-protocol.md +41 -33
  3. package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +17 -13
  4. package/_shared/advisor/scripts/model_tier_run_validator.py +27 -18
  5. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +95 -25
  6. package/_shared/advisor/scripts/tests/test_tier_model_ids.py +17 -17
  7. package/_shared/advisor/scripts/tier_model_ids.py +18 -18
  8. package/_shared/pr-loop/CLAUDE.md +1 -0
  9. package/_shared/pr-loop/scripts/CLAUDE.md +2 -1
  10. package/_shared/pr-loop/scripts/README.md +1 -0
  11. package/_shared/pr-loop/scripts/code_rules_gate.py +253 -1980
  12. package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +32 -0
  13. package/_shared/pr-loop/scripts/code_rules_gate_parts/__init__.py +7 -0
  14. package/_shared/pr-loop/scripts/code_rules_gate_parts/added_line_maps.py +268 -0
  15. package/_shared/pr-loop/scripts/code_rules_gate_parts/enforcer_loading.py +172 -0
  16. package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_arguments.py +70 -0
  17. package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_running.py +326 -0
  18. package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +85 -0
  19. package/_shared/pr-loop/scripts/code_rules_gate_parts/git_file_sets.py +331 -0
  20. package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_running.py +369 -0
  21. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/conftest.py +14 -0
  22. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_added_line_maps.py +118 -0
  23. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_enforcer_loading.py +17 -0
  24. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_arguments.py +29 -0
  25. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_running.py +99 -0
  26. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_git_blob_readers.py +69 -0
  27. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_git_file_sets.py +137 -0
  28. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_running.py +116 -0
  29. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_violation_scoping.py +75 -0
  30. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_wrapper_plumb_check.py +49 -0
  31. package/_shared/pr-loop/scripts/code_rules_gate_parts/violation_scoping.py +328 -0
  32. package/_shared/pr-loop/scripts/code_rules_gate_parts/wrapper_plumb_check.py +206 -0
  33. package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +24 -17
  34. package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +1 -0
  35. package/_shared/pr-loop/scripts/reviews_disabled.py +19 -2
  36. package/_shared/pr-loop/scripts/test_code_rules_gate.py +278 -0
  37. package/_shared/pr-loop/scripts/tests/test_code_rules_gate_constants.py +6 -39
  38. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +43 -0
  39. package/_shared/pr-loop/worker-spawn.md +186 -0
  40. package/agents/code-verifier.md +1 -1
  41. package/bin/CLAUDE.md +10 -1
  42. package/bin/ever-shipped-skills.mjs +73 -0
  43. package/bin/expand_home_directory_tokens.mjs +1 -1
  44. package/bin/install.mjs +140 -7
  45. package/bin/install.prune.test.mjs +457 -0
  46. package/docs/CODE_RULES.md +1 -1
  47. package/hooks/advisory/refactor_guard.py +3 -4
  48. package/hooks/blocking/CLAUDE.md +7 -1
  49. package/hooks/blocking/block_main_commit.py +2 -2
  50. package/hooks/blocking/claude_md_orphan_file_blocker.py +75 -699
  51. package/hooks/blocking/claude_md_orphan_file_blocker_parts/CLAUDE.md +28 -0
  52. package/hooks/blocking/claude_md_orphan_file_blocker_parts/__init__.py +1 -0
  53. package/hooks/blocking/claude_md_orphan_file_blocker_parts/config/__init__.py +1 -0
  54. package/hooks/blocking/claude_md_orphan_file_blocker_parts/config/orphan_blocker_constants.py +18 -0
  55. package/hooks/blocking/claude_md_orphan_file_blocker_parts/decision.py +81 -0
  56. package/hooks/blocking/claude_md_orphan_file_blocker_parts/references.py +307 -0
  57. package/hooks/blocking/claude_md_orphan_file_blocker_parts/scan_plan.py +124 -0
  58. package/hooks/blocking/claude_md_orphan_file_blocker_parts/subtree_scan.py +179 -0
  59. package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/conftest.py +10 -0
  60. package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_decision.py +34 -0
  61. package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_references.py +42 -0
  62. package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_scan_plan.py +27 -0
  63. package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_subtree_scan.py +30 -0
  64. package/hooks/blocking/code_rules_boolean_mustcheck.py +1 -1
  65. package/hooks/blocking/code_rules_enforcer.py +4 -0
  66. package/hooks/blocking/code_rules_mock_completeness.py +1 -1
  67. package/hooks/blocking/code_rules_optional_params.py +2 -2
  68. package/hooks/blocking/code_rules_shared.py +83 -1
  69. package/hooks/blocking/code_rules_test_assertions.py +1 -1
  70. package/hooks/blocking/code_rules_test_layout.py +9 -3
  71. package/hooks/blocking/code_rules_typeddict_stub.py +1 -1
  72. package/hooks/blocking/gh_pr_author_enforcer.py +1 -1
  73. package/hooks/blocking/inventory_intent_records/CLAUDE.md +26 -0
  74. package/hooks/blocking/inventory_intent_records/__init__.py +1 -0
  75. package/hooks/blocking/inventory_intent_records/config/__init__.py +1 -0
  76. package/hooks/blocking/inventory_intent_records/config/intent_records_constants.py +20 -0
  77. package/hooks/blocking/inventory_intent_records/records.py +271 -0
  78. package/hooks/blocking/inventory_intent_records/tests/conftest.py +10 -0
  79. package/hooks/blocking/inventory_intent_records/tests/test_records.py +80 -0
  80. package/hooks/blocking/package_inventory_stale_blocker.py +54 -384
  81. package/hooks/blocking/package_inventory_stale_blocker_parts/CLAUDE.md +26 -0
  82. package/hooks/blocking/package_inventory_stale_blocker_parts/__init__.py +1 -0
  83. package/hooks/blocking/package_inventory_stale_blocker_parts/config/__init__.py +1 -0
  84. package/hooks/blocking/package_inventory_stale_blocker_parts/config/inventory_blocker_constants.py +16 -0
  85. package/hooks/blocking/package_inventory_stale_blocker_parts/decision.py +84 -0
  86. package/hooks/blocking/package_inventory_stale_blocker_parts/inventory_detection.py +307 -0
  87. package/hooks/blocking/package_inventory_stale_blocker_parts/tests/conftest.py +10 -0
  88. package/hooks/blocking/package_inventory_stale_blocker_parts/tests/test_decision.py +38 -0
  89. package/hooks/blocking/package_inventory_stale_blocker_parts/tests/test_inventory_detection.py +61 -0
  90. package/hooks/blocking/pii_payload_scan.py +138 -42
  91. package/hooks/blocking/pii_prevention_blocker.py +185 -291
  92. package/hooks/blocking/pii_prevention_blocker_parts/CLAUDE.md +24 -0
  93. package/hooks/blocking/pii_prevention_blocker_parts/__init__.py +1 -0
  94. package/hooks/blocking/pii_prevention_blocker_parts/config/__init__.py +1 -0
  95. package/hooks/blocking/pii_prevention_blocker_parts/config/repository_resolution_constants.py +28 -0
  96. package/hooks/blocking/pii_prevention_blocker_parts/repository_exemption.py +214 -0
  97. package/hooks/blocking/pii_prevention_blocker_parts/repository_resolution.py +208 -0
  98. package/hooks/blocking/plain_language_blocker.py +138 -4
  99. package/hooks/blocking/pr_description_command_parser.py +8 -4
  100. package/hooks/blocking/precommit_code_rules_gate.py +3 -3
  101. package/hooks/blocking/sensitive_file_protector.py +114 -48
  102. package/hooks/blocking/tdd_enforcer.py +97 -601
  103. package/hooks/blocking/tdd_enforcer_parts/CLAUDE.md +30 -0
  104. package/hooks/blocking/tdd_enforcer_parts/__init__.py +1 -0
  105. package/hooks/blocking/tdd_enforcer_parts/candidate_paths.py +142 -0
  106. package/hooks/blocking/tdd_enforcer_parts/config/__init__.py +1 -0
  107. package/hooks/blocking/tdd_enforcer_parts/config/tdd_enforcer_constants.py +32 -0
  108. package/hooks/blocking/tdd_enforcer_parts/content_analysis.py +268 -0
  109. package/hooks/blocking/tdd_enforcer_parts/decisions.py +92 -0
  110. package/hooks/blocking/tdd_enforcer_parts/freshness.py +80 -0
  111. package/hooks/blocking/tdd_enforcer_parts/git_tracking.py +63 -0
  112. package/hooks/blocking/tdd_enforcer_parts/path_classification.py +119 -0
  113. package/hooks/blocking/tdd_enforcer_parts/tests/conftest.py +10 -0
  114. package/hooks/blocking/tdd_enforcer_parts/tests/test_candidate_paths.py +31 -0
  115. package/hooks/blocking/tdd_enforcer_parts/tests/test_content_analysis.py +30 -0
  116. package/hooks/blocking/tdd_enforcer_parts/tests/test_decisions.py +34 -0
  117. package/hooks/blocking/tdd_enforcer_parts/tests/test_freshness.py +28 -0
  118. package/hooks/blocking/tdd_enforcer_parts/tests/test_git_tracking.py +48 -0
  119. package/hooks/blocking/tdd_enforcer_parts/tests/test_path_classification.py +36 -0
  120. package/hooks/blocking/test_code_rules_enforcer_scratchpad.py +105 -0
  121. package/hooks/blocking/test_code_rules_shared.py +181 -0
  122. package/hooks/blocking/test_inventory_deadlock_resolution.py +154 -0
  123. package/hooks/blocking/test_pii_payload_scan.py +168 -0
  124. package/hooks/blocking/test_plain_language_blocker_allowlist.py +184 -0
  125. package/hooks/blocking/test_sensitive_file_protector.py +185 -0
  126. package/hooks/blocking/test_tdd_enforcer_restore.py +108 -0
  127. package/hooks/blocking/test_tdd_enforcer_scratchpad.py +105 -0
  128. package/hooks/blocking/tests/conftest.py +10 -0
  129. package/hooks/blocking/tests/test_pii_prevention_blocker.py +260 -0
  130. package/hooks/blocking/tests/test_repository_exemption.py +105 -0
  131. package/hooks/blocking/tests/test_repository_resolution.py +108 -0
  132. package/hooks/diagnostic/hook_log_extractor.py +12 -10
  133. package/hooks/git-hooks/post_commit.py +3 -4
  134. package/hooks/hooks_constants/CLAUDE.md +4 -2
  135. package/hooks/hooks_constants/banned_identifiers_constants.py +0 -1
  136. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
  137. package/hooks/hooks_constants/harness_scratchpad_constants.py +17 -0
  138. package/hooks/hooks_constants/local_identity.py +59 -8
  139. package/hooks/hooks_constants/pii_prevention_constants.py +0 -6
  140. package/hooks/hooks_constants/plain_language_blocker_constants.py +5 -0
  141. package/hooks/hooks_constants/sensitive_file_protector_constants.py +42 -0
  142. package/hooks/hooks_constants/test_local_identity.py +105 -3
  143. package/hooks/pyproject.toml +52 -4
  144. package/hooks/session/plugin_data_dir_cleanup.py +0 -1
  145. package/hooks/validation/mypy_validator.py +2 -2
  146. package/hooks/validators/CLAUDE.md +1 -1
  147. package/hooks/validators/README.md +2 -0
  148. package/hooks/validators/health_check.py +1 -0
  149. package/hooks/validators/mypy_integration.py +2 -0
  150. package/hooks/validators/python_style_checks.py +114 -136
  151. package/hooks/validators/python_style_helpers.py +95 -0
  152. package/hooks/validators/ruff_integration.py +3 -0
  153. package/hooks/validators/test_python_style_checks.py +0 -164
  154. package/hooks/validators/test_python_style_checks_decorator_gap.py +119 -0
  155. package/hooks/validators/test_python_style_fixes.py +251 -0
  156. package/hooks/validators/test_python_style_helpers.py +125 -0
  157. package/hooks/workflow/auto_formatter.py +5 -4
  158. package/package.json +1 -1
  159. package/rules/CLAUDE.md +1 -0
  160. package/rules/anti-corollary-tests.md +69 -0
  161. package/rules/bdd.md +1 -3
  162. package/rules/code-reviews.md +1 -1
  163. package/rules/gh-paginate.md +1 -1
  164. package/rules/plain-language.md +2 -0
  165. package/scripts/CLAUDE.md +4 -0
  166. package/scripts/dev_env_scripts_constants/CLAUDE.md +6 -4
  167. package/scripts/dev_env_scripts_constants/code_review_constants.py +71 -0
  168. package/scripts/dev_env_scripts_constants/grok_worker_constants.py +435 -0
  169. package/scripts/dev_env_scripts_constants/timing.py +7 -1
  170. package/scripts/grok_headless_runner.py +294 -0
  171. package/scripts/grok_worker_preflight.py +410 -0
  172. package/scripts/invoke_code_review.py +463 -0
  173. package/scripts/resolve_worker_spawn.py +619 -0
  174. package/scripts/spawn_grok_batch.py +672 -0
  175. package/scripts/test_grok_headless_runner.py +626 -0
  176. package/scripts/test_grok_worker_preflight.py +1054 -0
  177. package/scripts/test_invoke_code_review.py +672 -0
  178. package/scripts/test_resolve_worker_spawn.py +1014 -0
  179. package/scripts/test_spawn_grok_batch.py +1017 -0
  180. package/skills/CLAUDE.md +6 -3
  181. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +72 -13
  182. package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +121 -14
  183. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +78 -0
  184. package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +121 -0
  185. package/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +196 -6
  186. package/skills/autoconverge/CLAUDE.md +3 -3
  187. package/skills/autoconverge/SKILL.md +9 -3
  188. package/skills/autoconverge/reference/CLAUDE.md +2 -2
  189. package/skills/autoconverge/reference/convergence.md +33 -11
  190. package/skills/autoconverge/reference/stop-conditions.md +16 -5
  191. package/skills/autoconverge/workflow/CLAUDE.md +2 -1
  192. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +7 -2
  193. package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +300 -0
  194. package/skills/autoconverge/workflow/converge.contract.test.mjs +5 -5
  195. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +29 -29
  196. package/skills/autoconverge/workflow/converge.fix-progress.test.mjs +1 -1
  197. package/skills/autoconverge/workflow/converge.mjs +202 -18
  198. package/skills/bugteam/CLAUDE.md +2 -2
  199. package/skills/bugteam/CONSTRAINTS.md +3 -2
  200. package/skills/bugteam/PROMPTS.md +7 -6
  201. package/skills/bugteam/SKILL.md +18 -13
  202. package/skills/bugteam/reference/README.md +2 -3
  203. package/skills/bugteam/reference/audit-and-teammates.md +215 -35
  204. package/skills/bugteam/reference/design-rationale.md +1 -1
  205. package/skills/bugteam/reference/obstacles/CLAUDE.md +1 -1
  206. package/skills/bugteam/reference/team-setup.md +8 -2
  207. package/skills/closeout/SKILL.md +153 -0
  208. package/skills/closeout/reference/handoff-prompt-template.md +72 -0
  209. package/skills/closeout/reference/issue-body-templates.md +108 -0
  210. package/skills/closeout/reference/pii-redaction-checklist.md +36 -0
  211. package/skills/codex-review/CLAUDE.md +46 -0
  212. package/skills/codex-review/SKILL.md +181 -0
  213. package/skills/codex-review/reference/CLAUDE.md +15 -0
  214. package/skills/codex-review/reference/cli-contract.md +253 -0
  215. package/skills/codex-review/reference/loop-integration.md +118 -0
  216. package/skills/codex-review/scripts/codex_down_classifier.py +98 -0
  217. package/skills/codex-review/scripts/codex_review_scripts_constants/CLAUDE.md +18 -0
  218. package/skills/codex-review/scripts/codex_review_scripts_constants/__init__.py +1 -0
  219. package/skills/codex-review/scripts/codex_review_scripts_constants/classifier_constants.py +35 -0
  220. package/skills/codex-review/scripts/codex_review_scripts_constants/codex_usage_probe_constants.py +86 -0
  221. package/skills/codex-review/scripts/codex_review_scripts_constants/findings_constants.py +18 -0
  222. package/skills/codex-review/scripts/codex_review_scripts_constants/run_constants.py +45 -0
  223. package/skills/codex-review/scripts/codex_usage_probe.py +573 -0
  224. package/skills/codex-review/scripts/fixtures/auth_failure_synthetic.txt +1 -0
  225. package/skills/codex-review/scripts/fixtures/config_load_failure_v0.125.0.txt +1 -0
  226. package/skills/codex-review/scripts/fixtures/freeform_findings_v0.144.3.txt +6 -0
  227. package/skills/codex-review/scripts/fixtures/model_rejection_v0.125.0.jsonl +5 -0
  228. package/skills/codex-review/scripts/fixtures/structured_findings.txt +13 -0
  229. package/skills/codex-review/scripts/fixtures/success_stream_v0.144.3.jsonl +6 -0
  230. package/skills/codex-review/scripts/fixtures/unknown_failure_synthetic.txt +1 -0
  231. package/skills/codex-review/scripts/fixtures/usage_limit_synthetic.txt +1 -0
  232. package/skills/codex-review/scripts/parse_codex_findings.py +207 -0
  233. package/skills/codex-review/scripts/run_codex_review.py +415 -0
  234. package/skills/codex-review/scripts/test_codex_down_classifier.py +143 -0
  235. package/skills/codex-review/scripts/test_codex_usage_probe.py +678 -0
  236. package/skills/codex-review/scripts/test_parse_codex_findings.py +130 -0
  237. package/skills/codex-review/scripts/test_run_codex_review.py +812 -0
  238. package/skills/codex-review/test_skill_scaffold.py +192 -0
  239. package/skills/grok-spawn/CLAUDE.md +28 -0
  240. package/skills/grok-spawn/SKILL.md +226 -0
  241. package/skills/grok-spawn/reference/flag-profiles.md +132 -0
  242. package/skills/grok-spawn/reference/worker-briefs.md +152 -0
  243. package/skills/grokify/SKILL.md +9 -1
  244. package/skills/grokify/capability-claims.test.mjs +28 -0
  245. package/skills/grokify/evals/README.md +72 -0
  246. package/skills/grokify/evals/parse-payload.test.mjs +171 -0
  247. package/skills/grokify/evals/run-capability-evals.mjs +545 -0
  248. package/skills/orchestrator/SKILL.md +32 -22
  249. package/skills/orchestrator-refresh/SKILL.md +12 -8
  250. package/skills/pr-converge/CLAUDE.md +1 -1
  251. package/skills/pr-converge/SKILL.md +34 -13
  252. package/skills/pr-converge/reference/convergence-gates.md +42 -15
  253. package/skills/pr-converge/reference/fix-protocol.md +1 -1
  254. package/skills/pr-converge/reference/ground-rules.md +1 -1
  255. package/skills/pr-converge/reference/per-tick.md +130 -42
  256. package/skills/pr-converge/reference/state-schema.md +10 -0
  257. package/skills/pr-converge/scripts/CLAUDE.md +2 -0
  258. package/skills/pr-converge/scripts/_pr_converge_path_setup.py +5 -1
  259. package/skills/pr-converge/scripts/check_convergence.py +605 -29
  260. package/skills/pr-converge/scripts/check_convergence_availability.py +232 -0
  261. package/skills/pr-converge/scripts/check_convergence_gates.py +279 -235
  262. package/skills/pr-converge/scripts/check_convergence_thread_gates.py +1 -1
  263. package/skills/pr-converge/scripts/pr_converge_scripts_constants/convergence_gate_constants.py +36 -2
  264. package/skills/pr-converge/scripts/test__pr_converge_path_setup.py +4 -0
  265. package/skills/pr-converge/scripts/test_check_convergence.py +71 -3
  266. package/skills/pr-converge/scripts/test_check_convergence_availability.py +326 -0
  267. package/skills/pr-converge/scripts/test_check_convergence_codex.py +507 -0
  268. package/skills/pr-converge/scripts/test_check_convergence_contract.py +89 -17
  269. package/skills/pr-converge/scripts/test_check_convergence_fixture.py +179 -0
  270. package/skills/pr-converge/scripts/test_check_convergence_gates.py +84 -68
  271. package/skills/pr-converge/scripts/test_check_convergence_thread_gates.py +24 -0
  272. package/skills/pr-converge/test_step5_host_branch.py +106 -0
  273. package/skills/pr-fix-protocol/SKILL.md +65 -0
  274. package/skills/pr-loop-cloud-transport/SKILL.md +2 -0
  275. package/skills/reviewer-gates/SKILL.md +7 -5
  276. package/skills/skill-builder/references/skill-modularity.md +1 -1
  277. package/skills/team-advisor/SKILL.md +16 -12
  278. package/system-prompts/software-engineer.xml +7 -6
  279. package/hooks/validators/test_verify_paths.py +0 -32
  280. package/hooks/validators/verify_paths.py +0 -57
@@ -0,0 +1,232 @@
1
+ """Resolve whether a reviewer gate is waived, and why, for the convergence check.
2
+
3
+ ::
4
+
5
+ flag / resolved settings disable the reviewer -> waived, note "copilot_down"
6
+ copilot out of quota -> waived, "copilot unavailable: <reason>"
7
+ bugbot settings-disabled (opt-out / no opt-in) -> waived, "bugbot_down"
8
+ copilot quota available -> not waived (live gates run)
9
+ copilot probe error / indeterminate -> not waived; probe_error_reason set;
10
+ live gates still run (no hard-fail)
11
+
12
+ When ``~/.claude/settings.json`` is readable it is the sole source for both
13
+ ``CLAUDE_REVIEWS_DISABLED`` and ``CLAUDE_REVIEWS_ENABLED``. The process env is
14
+ the fallback only when that file is missing, unreadable, or not a JSON object.
15
+
16
+ Bugbot has no quota or outage API. Confirmed-down for Bugbot means the resolved
17
+ settings disable it (opt-out or missing opt-in). A runtime Bugbot outage is
18
+ detected later as a poll timeout, not at this gate.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import functools
24
+ import json
25
+ import logging
26
+ import os
27
+ import subprocess
28
+ from typing import NamedTuple
29
+
30
+ import _pr_converge_path_setup # noqa: F401
31
+ from copilot_quota import evaluate_copilot_quota
32
+ from pr_converge_scripts_constants.convergence_gate_constants import (
33
+ BUGBOT_DOWN_BYPASS_NOTE,
34
+ COPILOT_DOWN_BYPASS_NOTE,
35
+ REVIEWER_UNAVAILABLE_NOTE_TEMPLATE,
36
+ SETTINGS_DISK_FALLBACK_LOG_TEMPLATE,
37
+ )
38
+ from pr_loop_shared_constants.claude_permissions_constants import (
39
+ get_claude_user_settings_path,
40
+ )
41
+ from pr_loop_shared_constants.copilot_quota_constants import (
42
+ COPILOT_QUOTA_DEFAULT_ENV_FILE_PATH,
43
+ EXIT_CODE_OUT_OF_QUOTA,
44
+ EXIT_CODE_QUOTA_AVAILABLE,
45
+ )
46
+ from pr_loop_shared_constants.reviews_disabled_constants import (
47
+ CLAUDE_REVIEWS_DISABLED_BUGBOT_TOKEN,
48
+ CLAUDE_REVIEWS_DISABLED_COPILOT_TOKEN,
49
+ CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME,
50
+ CLAUDE_REVIEWS_DISABLED_TOKEN_SEPARATOR,
51
+ CLAUDE_REVIEWS_ENABLED_ENV_VAR_NAME,
52
+ )
53
+
54
+ _logger = logging.getLogger(__name__)
55
+
56
+
57
+ class ReviewerWaiver(NamedTuple):
58
+ """Whether a reviewer gate is waived, and the note printed on the bypassed line."""
59
+
60
+ is_waived: bool
61
+ bypass_note: str
62
+ probe_error_reason: str = ""
63
+
64
+
65
+ class _ReviewsSettings(NamedTuple):
66
+ """Resolved disabled and enabled reviewer tokens for one settings source."""
67
+
68
+ disabled_tokens: frozenset[str]
69
+ enabled_tokens: frozenset[str]
70
+
71
+
72
+ def _tokens_from_text(reviews_token_text: str) -> frozenset[str]:
73
+ """Split a comma-separated reviews token list into lowercase tokens."""
74
+ return frozenset(
75
+ each_token.strip().lower()
76
+ for each_token in reviews_token_text.split(CLAUDE_REVIEWS_DISABLED_TOKEN_SEPARATOR)
77
+ if each_token.strip()
78
+ )
79
+
80
+
81
+ def _tokens_from_env(environment_variable_name: str) -> frozenset[str]:
82
+ """Return the tokens listed in a process environment variable."""
83
+ return _tokens_from_text(os.environ.get(environment_variable_name, ""))
84
+
85
+
86
+ def _tokens_from_env_block(
87
+ all_env_entries: dict[str, object], environment_variable_name: str
88
+ ) -> frozenset[str]:
89
+ """Return the tokens listed under one key of a settings.json env block."""
90
+ reviews_token_text = all_env_entries.get(environment_variable_name, "")
91
+ if not isinstance(reviews_token_text, str):
92
+ return frozenset()
93
+ return _tokens_from_text(reviews_token_text)
94
+
95
+
96
+ def _try_read_disk_env_block() -> dict[str, object] | None:
97
+ """Return the settings.json env block, or None when disk is not authoritative."""
98
+ settings_path = get_claude_user_settings_path()
99
+ if not settings_path.is_file():
100
+ return None
101
+ try:
102
+ settings_payload = json.loads(settings_path.read_text(encoding="utf-8"))
103
+ except (OSError, ValueError):
104
+ return None
105
+ if not isinstance(settings_payload, dict):
106
+ return None
107
+ all_env_entries = settings_payload.get("env", {})
108
+ if not isinstance(all_env_entries, dict):
109
+ return {}
110
+ return all_env_entries
111
+
112
+
113
+ @functools.lru_cache(maxsize=1)
114
+ def _log_disk_settings_fallback_once() -> None:
115
+ """Log once per process that settings.json fell back to the process env."""
116
+ _logger.warning(
117
+ SETTINGS_DISK_FALLBACK_LOG_TEMPLATE,
118
+ CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME,
119
+ CLAUDE_REVIEWS_ENABLED_ENV_VAR_NAME,
120
+ )
121
+
122
+
123
+ def _resolve_reviews_settings() -> _ReviewsSettings:
124
+ """Return disabled and enabled tokens from disk, or env when disk is unreadable."""
125
+ all_disk_env_entries = _try_read_disk_env_block()
126
+ if all_disk_env_entries is not None:
127
+ return _ReviewsSettings(
128
+ disabled_tokens=_tokens_from_env_block(
129
+ all_disk_env_entries, CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME
130
+ ),
131
+ enabled_tokens=_tokens_from_env_block(
132
+ all_disk_env_entries, CLAUDE_REVIEWS_ENABLED_ENV_VAR_NAME
133
+ ),
134
+ )
135
+ _log_disk_settings_fallback_once()
136
+ return _ReviewsSettings(
137
+ disabled_tokens=_tokens_from_env(CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME),
138
+ enabled_tokens=_tokens_from_env(CLAUDE_REVIEWS_ENABLED_ENV_VAR_NAME),
139
+ )
140
+
141
+
142
+ def _is_copilot_disabled_via_resolved_settings() -> bool:
143
+ """Return True when the resolved settings disable the copilot reviewer."""
144
+ return (
145
+ CLAUDE_REVIEWS_DISABLED_COPILOT_TOKEN
146
+ in _resolve_reviews_settings().disabled_tokens
147
+ )
148
+
149
+
150
+ def _is_bugbot_disabled_via_resolved_settings() -> bool:
151
+ """Return True when the resolved settings disable the bugbot reviewer.
152
+
153
+ Bugbot is off by default. It is disabled when the disabled list names it, or
154
+ when the enabled list does not name it.
155
+ """
156
+ reviews_settings = _resolve_reviews_settings()
157
+ is_opted_out = (
158
+ CLAUDE_REVIEWS_DISABLED_BUGBOT_TOKEN in reviews_settings.disabled_tokens
159
+ )
160
+ is_opted_in = CLAUDE_REVIEWS_DISABLED_BUGBOT_TOKEN in reviews_settings.enabled_tokens
161
+ return is_opted_out or not is_opted_in
162
+
163
+
164
+ def _unavailable_note(reviewer_token: str, message: str) -> str:
165
+ """Format the bypass note for a confirmed-down reviewer."""
166
+ return REVIEWER_UNAVAILABLE_NOTE_TEMPLATE.format(
167
+ token=reviewer_token, message=message
168
+ )
169
+
170
+
171
+ @functools.lru_cache(maxsize=None)
172
+ def _probe_copilot_quota() -> ReviewerWaiver:
173
+ """Probe Copilot quota once per run; waive only on confirmed out-of-quota.
174
+
175
+ ::
176
+
177
+ exit == out-of-quota (1) -> waived, "copilot unavailable: <msg>"
178
+ exit == available (0) -> not waived (live gates run)
179
+ exit == API down / no account / other non-zero
180
+ -> not waived; probe_error_reason set (live gates run)
181
+ probe raises -> not waived; probe_error_reason set (live gates run)
182
+ """
183
+ try:
184
+ quota_decision = evaluate_copilot_quota(
185
+ cli_account=None,
186
+ env_file_path=COPILOT_QUOTA_DEFAULT_ENV_FILE_PATH,
187
+ )
188
+ except (OSError, ValueError, RuntimeError, subprocess.SubprocessError) as probe_error:
189
+ _logger.warning(
190
+ "reviewer-availability: probe error for %s: %s",
191
+ CLAUDE_REVIEWS_DISABLED_COPILOT_TOKEN,
192
+ probe_error,
193
+ )
194
+ return ReviewerWaiver(False, "", str(probe_error))
195
+ if quota_decision.exit_code == EXIT_CODE_OUT_OF_QUOTA:
196
+ return ReviewerWaiver(
197
+ True,
198
+ _unavailable_note(
199
+ CLAUDE_REVIEWS_DISABLED_COPILOT_TOKEN, quota_decision.message
200
+ ),
201
+ "",
202
+ )
203
+ if quota_decision.exit_code == EXIT_CODE_QUOTA_AVAILABLE:
204
+ return ReviewerWaiver(False, "", "")
205
+ return ReviewerWaiver(False, "", quota_decision.message)
206
+
207
+
208
+ def _waiver_from_cli_flag(is_down_flag: bool, bypass_note: str) -> ReviewerWaiver:
209
+ """Build a waiver from an explicit CLI flag alone (fixture offline path)."""
210
+ if is_down_flag:
211
+ return ReviewerWaiver(True, bypass_note, "")
212
+ return ReviewerWaiver(False, "", "")
213
+
214
+
215
+ def _resolve_copilot_waiver(is_copilot_down_flag: bool) -> ReviewerWaiver:
216
+ """Resolve the Copilot waiver from flag, resolved settings, then the quota probe."""
217
+ if is_copilot_down_flag or _is_copilot_disabled_via_resolved_settings():
218
+ return ReviewerWaiver(True, COPILOT_DOWN_BYPASS_NOTE, "")
219
+ return _probe_copilot_quota()
220
+
221
+
222
+ def _resolve_bugbot_waiver(is_bugbot_down_flag: bool) -> ReviewerWaiver:
223
+ """Resolve the Bugbot waiver from flag, then resolved settings.
224
+
225
+ Bugbot has no quota or outage API. Once the resolved settings (disk when
226
+ readable, else env) confirm the opt-in, the gate is enforced. Re-probing
227
+ via ``evaluate_reviewer_availability`` would re-read the process env and
228
+ undo a mid-session disk enable of Bugbot.
229
+ """
230
+ if is_bugbot_down_flag or _is_bugbot_disabled_via_resolved_settings():
231
+ return ReviewerWaiver(True, BUGBOT_DOWN_BYPASS_NOTE, "")
232
+ return ReviewerWaiver(False, "", "")