claude-dev-env 2.9.0 → 2.10.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 (289) hide show
  1. package/CLAUDE.md +1 -1
  2. package/_shared/pr-loop/CLAUDE.md +1 -1
  3. package/_shared/pr-loop/audit-contract.md +17 -6
  4. package/_shared/pr-loop/audit-reply-template.md +4 -4
  5. package/_shared/pr-loop/code-rules-gate.md +3 -5
  6. package/_shared/pr-loop/fix-protocol.md +2 -3
  7. package/_shared/pr-loop/gh-payloads.md +1 -1
  8. package/_shared/pr-loop/scripts/CLAUDE.md +1 -1
  9. package/_shared/pr-loop/scripts/README.md +1 -1
  10. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -0
  11. package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_running.py +16 -1
  12. package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +11 -5
  13. package/_shared/pr-loop/scripts/preflight.py +9 -4
  14. package/_shared/pr-loop/scripts/reviews_disabled.py +50 -22
  15. package/_shared/pr-loop/scripts/tests/conftest.py +20 -0
  16. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +6 -6
  17. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +50 -6
  18. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +1 -1
  19. package/_shared/pr-loop/state-schema.md +5 -14
  20. package/agents/CLAUDE.md +2 -2
  21. package/agents/clean-coder.md +58 -548
  22. package/agents/code-quality-agent.md +10 -2
  23. package/agents/code-verifier.md +1 -1
  24. package/agents/test_agent_frontmatter.py +32 -40
  25. package/audit-rubrics/CLAUDE.md +2 -1
  26. package/audit-rubrics/audit-categories.json +704 -0
  27. package/audit-rubrics/prompts/category-i-concurrency.md +1 -1
  28. package/bin/CLAUDE.md +16 -5
  29. package/bin/ever-shipped-skills.mjs +2 -0
  30. package/bin/install-plan.mjs +402 -0
  31. package/bin/install-transaction.mjs +455 -0
  32. package/bin/install.mjs +593 -147
  33. package/bin/install.plan.test.mjs +194 -0
  34. package/bin/install.profile-root.test.mjs +154 -0
  35. package/bin/install.profiles.test.mjs +253 -0
  36. package/bin/install.settings-defaults.test.mjs +200 -0
  37. package/bin/install.transaction.test.mjs +400 -0
  38. package/bin/install.uninstall-transaction.test.mjs +418 -0
  39. package/bin/merge_managed_permissions.mjs +130 -0
  40. package/bin/resolve-install-root.mjs +181 -0
  41. package/bin/select-install-targets.mjs +401 -0
  42. package/commands/CLAUDE.md +0 -2
  43. package/docs/references/CLAUDE.md +1 -0
  44. package/docs/references/advisor-tool.md +1 -1
  45. package/docs/references/prose-style-enforcement.md +25 -0
  46. package/hooks/blocking/CLAUDE.md +6 -6
  47. package/hooks/blocking/_path_setup.py +9 -5
  48. package/hooks/blocking/code_rules_docstrings.py +124 -30
  49. package/hooks/blocking/code_rules_enforcer.py +161 -16
  50. package/hooks/blocking/code_rules_shared.py +40 -23
  51. package/hooks/blocking/config/CLAUDE.md +3 -5
  52. package/hooks/blocking/config/prose_style_enforcement_constants.py +38 -0
  53. package/hooks/blocking/config/test_prose_style_enforcement_constants.py +45 -0
  54. package/hooks/blocking/eli11_reply_enforcer.py +70 -113
  55. package/hooks/blocking/hedging_language_blocker.py +103 -20
  56. package/hooks/blocking/hook_prose_detector_consistency.py +6 -0
  57. package/hooks/blocking/intent_only_ending_blocker.py +6 -0
  58. package/hooks/blocking/plain_language_blocker.py +139 -20
  59. package/hooks/blocking/pre_tool_use_dispatcher.py +102 -20
  60. package/hooks/blocking/state_description_blocker.py +7 -1
  61. package/hooks/blocking/tdd_enforcer.py +8 -0
  62. package/hooks/blocking/test__path_setup.py +28 -0
  63. package/hooks/blocking/test_code_rules_enforcer_agent_home_tooling.py +99 -0
  64. package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +232 -10
  65. package/hooks/blocking/test_code_rules_enforcer_ephemeral.py +1 -1
  66. package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +41 -0
  67. package/hooks/blocking/test_code_rules_enforcer_string_magic.py +98 -0
  68. package/hooks/blocking/test_eli11_reply_enforcer.py +98 -165
  69. package/hooks/blocking/test_hedging_language_blocker.py +120 -1
  70. package/hooks/blocking/test_hook_prose_detector_consistency.py +28 -8
  71. package/hooks/blocking/test_intent_only_ending_blocker.py +27 -2
  72. package/hooks/blocking/test_package_inventory_stale_blocker.py +11 -4
  73. package/hooks/blocking/test_plain_language_blocker.py +129 -19
  74. package/hooks/blocking/test_plain_language_blocker_allowlist.py +70 -26
  75. package/hooks/blocking/test_pre_tool_use_dispatcher.py +99 -26
  76. package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +87 -50
  77. package/hooks/blocking/test_state_description_blocker.py +45 -2
  78. package/hooks/blocking/test_stop_dispatcher.py +11 -7
  79. package/hooks/blocking/test_volatile_path_in_post_blocker.py +12 -12
  80. package/hooks/blocking/volatile_path_in_post_blocker.py +2 -2
  81. package/hooks/hooks.json +15 -0
  82. package/hooks/hooks_constants/CLAUDE.md +14 -3
  83. package/hooks/hooks_constants/ask_user_question_shape.py +281 -0
  84. package/hooks/hooks_constants/code_rules_enforcer_constants.py +2 -1
  85. package/hooks/hooks_constants/eli11_reply_enforcer_constants.py +5 -12
  86. package/hooks/hooks_constants/hedging_uncertainty_constants.py +42 -0
  87. package/hooks/hooks_constants/issue_tracker_session_starter_constants.py +23 -0
  88. package/hooks/hooks_constants/orchestrator_auto_starter_constants.py +23 -0
  89. package/hooks/hooks_constants/piped_pytest_blocker_constants.py +4 -1
  90. package/hooks/hooks_constants/plain_language_blocker_constants.py +4 -1
  91. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +6 -0
  92. package/hooks/hooks_constants/project_paths_reader.py +31 -4
  93. package/hooks/hooks_constants/prose_matcher_precision_constants.py +40 -0
  94. package/hooks/hooks_constants/pytest_invocation.py +354 -0
  95. package/hooks/hooks_constants/session_start_injector.py +163 -0
  96. package/hooks/hooks_constants/session_start_injector_constants.py +46 -0
  97. package/hooks/hooks_constants/shell_command_pipeline.py +397 -0
  98. package/hooks/hooks_constants/shell_command_segments.py +5 -0
  99. package/hooks/hooks_constants/test_ask_user_question_shape.py +167 -0
  100. package/hooks/hooks_constants/test_project_paths_reader.py +29 -0
  101. package/hooks/hooks_constants/test_prose_metrics_parity.py +8 -0
  102. package/hooks/hooks_constants/test_pytest_invocation.py +130 -0
  103. package/hooks/hooks_constants/test_session_start_injector.py +168 -0
  104. package/hooks/hooks_constants/test_shell_command_pipeline.py +135 -0
  105. package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +1 -1
  106. package/hooks/hooks_constants/working_style_prompt_constants.py +30 -0
  107. package/hooks/observability/CLAUDE.md +2 -0
  108. package/hooks/observability/prose_matcher_advisory.py +237 -0
  109. package/hooks/observability/test_prose_matcher_advisory.py +143 -0
  110. package/hooks/session/CLAUDE.md +9 -1
  111. package/hooks/session/_path_setup.py +13 -0
  112. package/hooks/session/issue_tracker_session_starter.py +135 -0
  113. package/hooks/session/orchestrator_auto_starter.py +100 -0
  114. package/hooks/session/test__path_setup.py +28 -0
  115. package/hooks/session/test_issue_tracker_session_starter.py +104 -0
  116. package/hooks/session/test_orchestrator_auto_starter.py +99 -0
  117. package/hooks/session/test_working_style_prompt.py +47 -0
  118. package/hooks/session/untracked_repo_detector.py +1 -24
  119. package/hooks/session/working_style_prompt.py +36 -0
  120. package/hooks/validators/_path_setup.py +19 -0
  121. package/hooks/validators/run_all_validators.py +8 -13
  122. package/installable-surfaces.manifest.json +21 -0
  123. package/output-styles/CLAUDE.md +1 -3
  124. package/package.json +4 -2
  125. package/rules/CLAUDE.md +1 -0
  126. package/rules/durable-post-artifacts.md +2 -2
  127. package/rules/eli11-replies.md +6 -1
  128. package/rules/hedging-claims.md +4 -2
  129. package/rules/long-horizon-autonomy.md +3 -1
  130. package/rules/opus5-communication-contract.md +45 -0
  131. package/rules/plain-language.md +2 -2
  132. package/rules/research-mode.md +1 -1
  133. package/scripts/CLAUDE.md +11 -0
  134. package/scripts/Sync-RepoMain.ps1 +215 -0
  135. package/scripts/active_capability_references.py +218 -0
  136. package/scripts/ci/windows-installer-lifecycle.ps1 +78 -0
  137. package/scripts/claude_chain_runner.py +394 -6
  138. package/scripts/claude_chain_usage.py +1 -1
  139. package/scripts/codex_compat_materializer.py +105 -85
  140. package/scripts/dev_env_scripts_constants/CLAUDE.md +2 -0
  141. package/scripts/dev_env_scripts_constants/active_capability_constants.py +46 -0
  142. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +74 -0
  143. package/scripts/dev_env_scripts_constants/verify_installable_package_constants.py +116 -0
  144. package/scripts/profile-isolation-launchers/config/mcp-bundles.json +25 -0
  145. package/scripts/profile-isolation-launchers/config/profile-isolation-constants.mjs +60 -0
  146. package/scripts/profile-isolation-launchers/config/profiles.manifest.json +54 -0
  147. package/scripts/profile-isolation-launchers/config/shared-allowlist.json +64 -0
  148. package/scripts/profile-isolation-launchers/launcher-runtime.mjs +180 -0
  149. package/scripts/profile-isolation-launchers/lib/profile-manifest.mjs +288 -0
  150. package/scripts/profile-isolation-launchers/mcp-bundles.mjs +275 -0
  151. package/scripts/profile-isolation-launchers/profile-isolation-contract.test.mjs +221 -0
  152. package/scripts/profile-isolation-launchers/tests/launcher-runtime.test.mjs +108 -0
  153. package/scripts/profile-isolation-launchers/tests/mcp-bundles.test.mjs +147 -0
  154. package/scripts/profile-isolation-launchers/tests/shortcut-contract.test.ps1 +102 -0
  155. package/scripts/profile-isolation-launchers/tests/version-compatibility.test.mjs +210 -0
  156. package/scripts/profile-isolation-launchers/version-compatibility.mjs +299 -0
  157. package/scripts/profile-isolation-launchers/windows/shortcut-inventory.ps1 +127 -0
  158. package/scripts/profile-isolation-launchers/windows/shortcut-manifest.json +51 -0
  159. package/scripts/profile-isolation-launchers/windows/shortcut-reconcile.ps1 +77 -0
  160. package/scripts/spawn_grok_batch.py +3 -0
  161. package/scripts/test_active_capability_references.py +108 -0
  162. package/scripts/test_claude_chain_runner.py +414 -82
  163. package/scripts/test_claude_chain_usage.py +12 -12
  164. package/scripts/test_resolve_worker_spawn.py +2 -2
  165. package/scripts/test_verify_installable_package.py +208 -0
  166. package/scripts/tests/test_codex_compat_materializer.py +33 -0
  167. package/scripts/verify_installable_package.py +612 -0
  168. package/settings.json +10 -0
  169. package/skills/CLAUDE.md +2 -0
  170. package/skills/_shared/pr-loop/scripts/CLAUDE.md +1 -0
  171. package/skills/_shared/pr-loop/scripts/audit_category_schema.py +355 -0
  172. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -0
  173. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +32 -0
  174. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +7 -19
  175. package/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +94 -0
  176. package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +21 -0
  177. package/skills/autoconverge/reference/convergence.md +2 -1
  178. package/skills/autoconverge/reference/stop-conditions.md +5 -3
  179. package/skills/beat-sheet/SKILL.md +54 -0
  180. package/skills/beat-sheet/reference/visual-beats.md +29 -0
  181. package/skills/bugteam/CONSTRAINTS.md +4 -4
  182. package/skills/bugteam/EXAMPLES.md +1 -1
  183. package/skills/bugteam/reference/README.md +1 -1
  184. package/skills/e-code-review/SKILL.md +26 -5
  185. package/skills/e-code-review/reference/effort-evaluation.md +35 -0
  186. package/skills/e-code-review/reference/medium.md +15 -4
  187. package/skills/e-code-review/scripts/config/e_code_review_effort_constants/__init__.py +41 -0
  188. package/skills/e-code-review/scripts/config/e_code_review_effort_constants/effort_constants.py +40 -0
  189. package/skills/e-code-review/scripts/e_code_review_scripts_constants/finding_pipeline_constants.py +49 -0
  190. package/skills/e-code-review/scripts/effort_defaults_evidence.json +186 -0
  191. package/skills/e-code-review/scripts/effort_evaluation.py +362 -0
  192. package/skills/e-code-review/scripts/finding_pipeline.py +140 -0
  193. package/skills/e-code-review/scripts/fixtures/demanding.json +26 -0
  194. package/skills/e-code-review/scripts/fixtures/easy.json +14 -0
  195. package/skills/e-code-review/scripts/fixtures/medium.json +20 -0
  196. package/skills/e-code-review/scripts/grok_code_review.py +16 -7
  197. package/skills/e-code-review/scripts/test_effort_evaluation.py +180 -0
  198. package/skills/e-code-review/scripts/test_finding_pipeline.py +197 -0
  199. package/skills/e-code-review/scripts/test_grok_code_review.py +77 -0
  200. package/skills/plan-to-pr/scripts/create_packet.py +4 -4
  201. package/skills/plan-to-pr/scripts/load_skill_constants.py +41 -0
  202. package/skills/plan-to-pr/scripts/validate_packet.py +4 -4
  203. package/skills/plan-to-pr/scripts/validate_protocol.py +4 -1
  204. package/skills/plan-to-pr/scripts/validate_run.py +4 -1
  205. package/skills/pr-converge/scripts/check_convergence.py +21 -19
  206. package/skills/pr-converge/scripts/check_convergence_availability.py +50 -7
  207. package/skills/pr-converge/scripts/conftest.py +35 -0
  208. package/skills/pr-converge/scripts/test_check_convergence_availability.py +65 -0
  209. package/skills/pr-converge/scripts/test_check_convergence_codex.py +11 -1
  210. package/skills/pr-converge/scripts/test_check_convergence_contract.py +9 -2
  211. package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
  212. package/skills/rebase/SKILL.md +15 -3
  213. package/skills/reviewer-gates/SKILL.md +2 -2
  214. package/skills/show/SKILL.md +51 -0
  215. package/skills/show/references/accessibility.md +7 -0
  216. package/skills/show/references/art.md +3 -0
  217. package/skills/show/references/charts.md +3 -0
  218. package/skills/show/references/core-design.md +14 -0
  219. package/skills/show/references/erds.md +3 -0
  220. package/skills/show/references/flowcharts.md +3 -0
  221. package/skills/show/references/host-and-html.md +3 -0
  222. package/skills/show/references/illustrative-diagrams.md +10 -0
  223. package/skills/show/references/interaction.md +3 -0
  224. package/skills/show/references/mockups.md +3 -0
  225. package/skills/show/references/quality-gates.md +7 -0
  226. package/skills/show/references/structural-diagrams.md +3 -0
  227. package/skills/show/references/subject-inventory.md +21 -0
  228. package/skills/show/references/svg-contract.md +22 -0
  229. package/skills/show/routing.yaml +30 -0
  230. package/skills/show/samples/pr1262-v2.svg +222 -0
  231. package/skills/show/scripts/README.md +6 -0
  232. package/skills/show/scripts/validate-artifact.py +91 -0
  233. package/skills/show/scripts/validate-package.py +18 -0
  234. package/skills/show/templates/html-widget.html +4 -0
  235. package/skills/show/templates/svg-base.svg +19 -0
  236. package/skills/show/tests/fixtures/css-var.svg +6 -0
  237. package/skills/show/tests/fixtures/dead-ref.svg +7 -0
  238. package/skills/show/tests/fixtures/filled-glyph.svg +8 -0
  239. package/skills/show/tests/fixtures/inherited-fill.svg +18 -0
  240. package/skills/show/tests/fixtures/invalid.svg +1 -0
  241. package/skills/show/tests/fixtures/large-canvas.svg +21 -0
  242. package/skills/show/tests/fixtures/unfilled-connector.svg +15 -0
  243. package/skills/show/tests/fixtures/valid.html +1 -0
  244. package/skills/show/tests/test_validate-artifact.py +74 -0
  245. package/skills/show/tests/test_validators.py +59 -0
  246. package/skills/show/workflows/create-visual.md +13 -0
  247. package/skills/show/workflows/review-visual.md +20 -0
  248. package/skills/split-pr/SKILL.md +85 -0
  249. package/skills/split-pr/reference/path-layers.md +16 -0
  250. package/skills/split-pr/reference/proposal-format.md +15 -0
  251. package/skills/split-pr/reference/split-further-loop.md +10 -0
  252. package/skills/split-pr/reference/splitting-principles.md +26 -0
  253. package/skills/split-pr/scripts/analyze_pr.py +279 -0
  254. package/skills/split-pr/scripts/categorize_files.py +106 -0
  255. package/skills/split-pr/scripts/config/__init__.py +1 -0
  256. package/skills/split-pr/scripts/config/dependency_constants.py +14 -0
  257. package/skills/split-pr/scripts/config/git_operations_constants.py +36 -0
  258. package/skills/split-pr/scripts/config/packing_constants.py +61 -0
  259. package/skills/split-pr/scripts/config/plan_constants.py +49 -0
  260. package/skills/split-pr/scripts/config/split_pr_constants.py +110 -0
  261. package/skills/split-pr/scripts/execute_split_slices.py +82 -0
  262. package/skills/split-pr/scripts/pack_files_into_slices.py +212 -0
  263. package/skills/split-pr/scripts/split_pr_dependency_graph.py +70 -0
  264. package/skills/split-pr/scripts/split_pr_git_operations.py +184 -0
  265. package/skills/split-pr/scripts/split_pr_layer_order.py +58 -0
  266. package/skills/split-pr/scripts/split_pr_paginate.py +119 -0
  267. package/skills/split-pr/scripts/split_pr_process_runner.py +52 -0
  268. package/skills/split-pr/scripts/split_pr_script_types.py +126 -0
  269. package/skills/split-pr/scripts/split_pr_title.py +41 -0
  270. package/skills/split-pr/scripts/test_analyze_pr.py +228 -0
  271. package/skills/split-pr/scripts/test_categorize_files.py +55 -0
  272. package/skills/split-pr/scripts/test_categorize_files_packing.py +59 -0
  273. package/skills/split-pr/scripts/test_execute_split_slices.py +99 -0
  274. package/skills/split-pr/scripts/test_split_pr_dependency_graph.py +47 -0
  275. package/skills/split-pr/scripts/test_split_pr_git_operations.py +125 -0
  276. package/skills/split-pr/scripts/test_split_pr_layer_order.py +36 -0
  277. package/skills/split-pr/scripts/test_split_pr_paginate.py +65 -0
  278. package/skills/split-pr/scripts/test_split_pr_script_types.py +73 -0
  279. package/skills/split-pr/scripts/test_split_pr_title.py +28 -0
  280. package/skills/split-pr/scripts/test_verify_dependency_graph.py +46 -0
  281. package/skills/split-pr/scripts/test_verify_plan.py +56 -0
  282. package/skills/split-pr/scripts/test_verify_plan_contract.py +50 -0
  283. package/skills/split-pr/scripts/test_verify_plan_path_normalization.py +45 -0
  284. package/skills/split-pr/scripts/verify_dependency_graph.py +111 -0
  285. package/skills/split-pr/scripts/verify_plan.py +139 -0
  286. package/system-prompts/software-engineer.xml +11 -2
  287. package/commands/initialize.md +0 -90
  288. package/commands/stubcheck.md +0 -88
  289. package/output-styles/caveman-agent.md +0 -37
package/CLAUDE.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Communication
4
4
 
5
- Reply shape and length: follow `~/.claude/rules/eli11-replies.md`. Word choice: follow `~/.claude/rules/plain-language.md`. State claims affirmatively.
5
+ Reply shape and length: follow `~/.claude/rules/eli11-replies.md`. Word choice: follow `~/.claude/rules/plain-language.md`. Progress and finals: follow `~/.claude/rules/opus5-communication-contract.md` (`opus5-communication-contract-v1`). State claims affirmatively.
6
6
 
7
7
  ## Security
8
8
 
@@ -1,6 +1,6 @@
1
1
  # _shared/pr-loop
2
2
 
3
- Runtime documents and scripts shared by every PR-loop skill. Changes here affect `bugteam`, `pr-converge`, `findbugs`, `fixbugs`, and `qbug` simultaneously — treat this as a breaking-change surface.
3
+ Runtime documents and scripts shared by every PR-loop skill. Changes here affect `bugteam`, `pr-converge`, `clean-room audit`, and `pr-fix-protocol` simultaneously — treat this as a breaking-change surface.
4
4
 
5
5
  ## Key documents
6
6
 
@@ -1,6 +1,6 @@
1
1
  # Audit contract
2
2
 
3
- Shared output schema and audit-loop contract used by `/bugteam`, `/qbug`, `/findbugs`, and `/fixbugs`. Changing a shape here is a breaking change for every consuming skill.
3
+ Shared output schema and audit-loop contract used by `/bugteam`, `clean-room audit (code-quality-agent)`, and `/pr-fix-protocol`. Changing a shape here is a breaking change for every consuming skill.
4
4
 
5
5
  ## Contents
6
6
 
@@ -11,6 +11,17 @@ Shared output schema and audit-loop contract used by `/bugteam`, `/qbug`, `/find
11
11
  - De-dup and merge
12
12
  - Persistence (loop-<L>-audit.json, loop-<L>-diagnostics.json)
13
13
 
14
+ ## Collection before severity filtering
15
+
16
+ Audit collection reports every real finding. The collection record retains all
17
+ seeded severities (`P0`, `P1`, `P2`) and every Shape A field (`file`, `line`,
18
+ `category`, `severity`, `excerpt`, `failure_mode`, `evidence_files`). Do not drop a real finding because its severity is low.
19
+
20
+ Severity or action filtering is a **separate consumer stage** after collection
21
+ is complete. A consumer may select a subset for fix or posting; it must not
22
+ rewrite the collection record or strip file, line, evidence, or category from
23
+ retained entries.
24
+
14
25
  ## Finding schema
15
26
 
16
27
  Each finding an audit produces MUST be one of exactly two shapes.
@@ -30,7 +41,7 @@ Each finding an audit produces MUST be one of exactly two shapes.
30
41
  }
31
42
  ```
32
43
 
33
- `id` is `loop<L>-<K>` where `L` is the loop counter (1-based) and `K` is the 1-based index within the loop. For `/findbugs` which runs once, use `find<K>`.
44
+ `id` is `loop<L>-<K>` where `L` is the loop counter (1-based) and `K` is the 1-based index within the loop. For `clean-room audit (code-quality-agent)` which runs once, use `find<K>`.
34
45
 
35
46
  ### Shape B — structured proof-of-absence
36
47
 
@@ -92,10 +103,10 @@ The audit must either produce new Shape A findings citing new file:line referenc
92
103
 
93
104
  ## Haiku secondary auditor
94
105
 
95
- For single-subagent skills (`/qbug`, `/findbugs`) the LEAD spawns two `Agent()` calls in one message:
106
+ For single-subagent skills (`/bugteam`, `clean-room audit (code-quality-agent)`) the LEAD spawns two `Agent()` calls in one message:
96
107
 
97
- - **Primary** — `subagent_type=clean-coder`, `model=sonnet` (for qbug cycle) or `subagent_type=code-quality-agent`, `model=sonnet` (for findbugs clean-room).
98
- - **Secondary (Haiku)** — `subagent_type=code-quality-agent`, `model=haiku`, same self-contained clean-room prompt shape used by `/findbugs`.
108
+ - **Primary** — `subagent_type=clean-coder`, `model=sonnet` (for `/bugteam` cycle) or `subagent_type=code-quality-agent`, `model=sonnet` (for clean-room audit).
109
+ - **Secondary (Haiku)** — `subagent_type=code-quality-agent`, `model=haiku`, same self-contained clean-room prompt shape used by `clean-room audit (code-quality-agent)`.
99
110
 
100
111
  Both audit the same diff. The secondary returns findings to the LEAD only — never posted to the PR.
101
112
 
@@ -112,7 +123,7 @@ For `/bugteam`, the single audit agent provides per-category coverage by walking
112
123
 
113
124
  ## Post-fix self-audit
114
125
 
115
- Audit-and-fix skills (`/qbug`, `/bugteam`) MUST re-audit modified files between `py_compile` and `git add`. This catches fix-induced regressions in the same loop that introduced them rather than on loop N+1.
126
+ Audit-and-fix skills (`/bugteam`) MUST re-audit modified files between `py_compile` and `git add`. This catches fix-induced regressions in the same loop that introduced them rather than on loop N+1.
116
127
 
117
128
  Sequence:
118
129
 
@@ -1,10 +1,10 @@
1
1
  # Audit reply template
2
2
 
3
- Canonical reply shape for every orchestrator-to-thread reply posted by the PR-loop suite (`pr-converge`, `bugteam`, `qbug`, and the `clean-coder` agent). Every reply Claude posts to an unresolved review thread on a draft PR uses this template.
3
+ Canonical reply shape for every orchestrator-to-thread reply posted by the PR-loop suite (`pr-converge`, `bugteam`, and the `clean-coder` agent). Every reply Claude posts to an unresolved review thread on a draft PR uses this template.
4
4
 
5
- Read this doc before authoring any code that posts a reply. The template is the contract emitted by every `clean-coder` reply path; the audit-skill wiring (`bugteam`, `qbug`, `pr-converge`) reads it before authoring a reply.
5
+ Read this doc before authoring any code that posts a reply. The template is the contract emitted by every `clean-coder` reply path; the audit-skill wiring (`bugteam`, `pr-converge`) reads it before authoring a reply.
6
6
 
7
- This doc has two surfaces, called out separately so consumers do not conflate them: the **reply template** (this section and below) shapes the body Claude posts back to a single review thread; the **audit body skeleton** in [Relationship to the audit review body](#relationship-to-the-audit-review-body) shapes the parent review `post_audit_thread.py` posts at audit time. The reply-template consumers are the four skills/agents listed above (read-only `findbugs` is excluded — it posts an audit review via `post_audit_thread.py` but never authors thread replies). The audit-body-skeleton consumers include `findbugs` alongside `bugteam` and `qbug` because all three skills post audit reviews via the same script.
7
+ This doc has two surfaces, called out separately so consumers do not conflate them: the **reply template** (this section and below) shapes the body Claude posts back to a single review thread; the **audit body skeleton** in [Relationship to the audit review body](#relationship-to-the-audit-review-body) shapes the parent review `post_audit_thread.py` posts at audit time. The reply-template consumers are the skills/agents listed above (read-only `clean-room audit` is excluded — it posts an audit review via `post_audit_thread.py` but never authors thread replies). The audit-body-skeleton consumers include `clean-room audit` alongside `bugteam` because both post audit reviews via the same script.
8
8
 
9
9
  ## Provenance
10
10
 
@@ -121,7 +121,7 @@ The thread can be resolved without further action; the reviewer's underlying con
121
121
 
122
122
  ## Relationship to the audit review body
123
123
 
124
- The audit review body posted by `post_audit_thread.py` (consumers: `bugteam`, `findbugs`, `qbug`) shares the same header anchor pattern:
124
+ The audit review body posted by `post_audit_thread.py` (consumers: `bugteam`, `clean-room audit`) shares the same header anchor pattern:
125
125
 
126
126
  <!-- audit-body-skeleton:start -->
127
127
  ```
@@ -7,7 +7,7 @@ Pre-audit validator run before each AUDIT (and pre-commit when applicable). Wrap
7
7
  Canonical: `~/.claude/_shared/pr-loop/scripts/code_rules_gate.py` after install.
8
8
 
9
9
  Workflows reference it via:
10
- - all skills (bugteam, qbug, pr-converge, monitor-many): `${CLAUDE_SKILL_DIR}/../../_shared/pr-loop/scripts/code_rules_gate.py`
10
+ - all skills (bugteam, pr-converge): `${CLAUDE_SKILL_DIR}/../../_shared/pr-loop/scripts/code_rules_gate.py`
11
11
 
12
12
  Cross-skill path traversal works because every skill installs as a flat top-level dir under `~/.claude/skills/` and `_shared/` lives alongside `skills/` under `~/.claude/`.
13
13
 
@@ -37,9 +37,7 @@ python "<gate_script>" --base origin/<base_branch> --only-under <prefix> [--only
37
37
  ## Workflow integration
38
38
 
39
39
  - **bugteam:** before every AUDIT in Step 3 of the loop. Non-zero → spawn clean-coder for standards fix; loop until exit 0 or 5 consecutive gate failures → `error: code rules gate failed pre-audit`.
40
- - **qbug:** before every AUDIT inside the subagent's loop. Three consecutive failures `error: code rules gate failed pre-audit`.
41
- - **pr-converge:** before every push in the Fix protocol. Halt on non-zero, fix violations, re-run.
42
- - **monitor-many:** before every commit during the fix loop. Halt on non-zero, fix violations, re-run.
40
+ - **pr-converge:** before every push in the Fix protocol and before every commit during the fix loop. Halt on non-zero, fix violations, re-run.
43
41
 
44
42
  ## Coverage
45
43
 
@@ -61,4 +59,4 @@ When the gate exits non-zero:
61
59
  2. Group violations by file
62
60
  3. Apply fixes in the smallest change set (extract constants to `config/`, rename collection params with `all_` prefix, replace literals with named constants, etc.)
63
61
  4. Re-run the gate with the same arguments
64
- 5. Cap at 5 consecutive failures (bugteam, monitor-many) or 3 (qbug); on cap exceeded, exit `error`
62
+ 5. Cap at 5 consecutive failures (bugteam, pr-converge); on cap exceeded, exit `error`
@@ -42,9 +42,8 @@
42
42
 
43
43
  Transport: post the reply via [`gh-payloads.md`](gh-payloads.md), then call `pull_request_review_write(method="resolve_thread", threadId=<thread_node_id>, ...)` for the same thread before moving to the next finding (this is the PR review thread node ID — `PRRT_kwDOxxx` — distinct from the numeric comment ID; harvest it at audit time when calling `get_review_comments`, see [`skills/bugteam/reference/obstacles/fix-resolve-thread.md`](../../skills/bugteam/reference/obstacles/fix-resolve-thread.md)).
44
44
  13. **Re-trigger reviewer** when the calling workflow specifies. Workflow-specific:
45
- - `pr-converge`: post `bugbot run` issue comment after every push (Cursor Bugbot)
46
- - `monitor-many`: post `bugbot run` issue comment AND call `requested_reviewers` API for Copilot
47
- - `bugteam` / `qbug`: skip — Claude itself is the reviewer; the next loop iteration audits
45
+ - `pr-converge`: post `bugbot run` issue comment after every push (Cursor Bugbot); when the multi-PR path also needs Copilot, call `requested_reviewers` for Copilot as well
46
+ - `bugteam`: skip Claude itself is the reviewer; the next loop iteration audits
48
47
 
49
48
  ## Stuck detection
50
49
 
@@ -1,6 +1,6 @@
1
1
  # MCP-based payloads
2
2
 
3
- Shared payload shapes for posting PR reviews and replies. Used by `bugteam`, `qbug`, `pr-converge`, `monitor-many`.
3
+ Shared payload shapes for posting PR reviews and replies. Used by `bugteam` and `pr-converge`.
4
4
 
5
5
  ## Build payloads with MCP tools
6
6
 
@@ -14,7 +14,7 @@ Python scripts invoked at runtime by the PR-loop skills. Each script is a standa
14
14
  | `stale_worktree_rule_sweep.py` | Drops allow/deny rules pointing at deleted `~/.claude/worktrees/<repo>/<worktree>` directories and deduplicates the rule arrays; run by the grant and revoke flows before they write |
15
15
  | `code_rules_gate.py` | Pre-commit gate that runs `code_rules_enforcer` checks on staged Python files before a fix commit lands, and the terminology sweep over the staged diff |
16
16
  | `terminology_sweep.py` | Flags a prose term that near-misses an identifier introduced on added code lines of a unified diff (shared leading word, divergent tail) |
17
- | `reviews_disabled.py` | Shared helper for the reviewer opt-out and opt-in gates; parses `CLAUDE_REVIEWS_DISABLED` tokens `bugteam`, `bugbot`, `copilot`, and `codex`, plus `CLAUDE_REVIEWS_ENABLED` for bugbot opt-in (bugbot is off by default and runs only when `CLAUDE_REVIEWS_ENABLED` lists it) |
17
+ | `reviews_disabled.py` | Shared helper for the reviewer opt-out and opt-in gates; parses `CLAUDE_REVIEWS_DISABLED` tokens `bugteam`, `bugbot`, `copilot`, and `codex`, plus `CLAUDE_REVIEWS_ENABLED` for opt-in. `bugbot`, `bugteam`, and `codex` are off by default and each runs only when `CLAUDE_REVIEWS_ENABLED` lists it; `copilot` runs by default and stops only when `CLAUDE_REVIEWS_DISABLED` lists it |
18
18
  | `copilot_quota.py` | Copilot premium-request quota pre-check: resolves a configured GitHub account, reads its remaining `premium_interactions` quota via `gh api copilot_internal/user`, and exits 0 (run Copilot) or non-zero (skip: out of quota, API down, or no account configured) |
19
19
  | `reviewer_availability.py` | Unified reviewer-availability entry point for Copilot and Bugbot: reuses `copilot_quota.py` and `reviews_disabled.py` and exits 0 when the named `--reviewer` may be spawned, non-zero when it is opted out or (for Copilot) out of quota |
20
20
  | `fix_hookspath.py` | Repairs a malformed `core.hooksPath` global git config entry |
@@ -1,6 +1,6 @@
1
1
  # Shared PR-loop scripts
2
2
 
3
- Runnable helpers used by **bugteam**, **qbug**, **pr-converge**, and related skills. These files are **executed** from the repository (or a `~/.claude` install); they are not meant as primary model-reading context.
3
+ Runnable helpers used by **bugteam**, **pr-converge**, and related skills. These files are **executed** from the repository (or a `~/.claude` install); they are not meant as primary model-reading context.
4
4
 
5
5
  ## Inventory
6
6
 
@@ -314,11 +314,13 @@ def _run_diff_mode(
314
314
  scoped_added_lines = (
315
315
  added_lines_by_file(repository_root, arguments.base, file_paths) if file_paths else {}
316
316
  )
317
+ merge_base_ref = resolve_merge_base(repository_root, arguments.base)
317
318
  return run_gate(
318
319
  validate_content,
319
320
  file_paths,
320
321
  repository_root,
321
322
  all_added_lines_by_path=scoped_added_lines,
323
+ prior_ref=merge_base_ref,
322
324
  )
323
325
 
324
326
 
@@ -96,6 +96,7 @@ def _scoped_violations_for_file(
96
96
  repository_root: Path,
97
97
  all_added_lines_for_file: set[int] | None,
98
98
  should_read_staged_content: bool = False,
99
+ prior_ref: str = "HEAD",
99
100
  ) -> tuple[list[str], list[str]] | None:
100
101
  """Validate one resolved file and partition its violations by diff scope."""
101
102
  resolved_root = repository_root.resolve()
@@ -106,7 +107,7 @@ def _scoped_violations_for_file(
106
107
  if content is None:
107
108
  sys.stderr.write(f"code_rules_gate: skip unreadable {resolved_path}\n")
108
109
  return None
109
- prior_content = read_prior_committed_content(resolved_root, relative_posix)
110
+ prior_content = read_prior_committed_content(resolved_root, relative_posix, prior_ref)
110
111
  issues = validate_content(
111
112
  content,
112
113
  relative_posix,
@@ -135,6 +136,7 @@ def _partition_over_eligible_paths(
135
136
  repository_root: Path,
136
137
  all_added_lines_by_path: dict[Path, set[int]] | None,
137
138
  should_read_staged_content: bool,
139
+ prior_ref: str = "HEAD",
138
140
  ) -> PartitionedViolations:
139
141
  """Validate each already-resolved eligible file and partition the results."""
140
142
  blocking_by_file: dict[Path, list[str]] = {}
@@ -147,6 +149,7 @@ def _partition_over_eligible_paths(
147
149
  repository_root,
148
150
  _added_lines_for(all_added_lines_by_path, each_resolved),
149
151
  should_read_staged_content,
152
+ prior_ref,
150
153
  )
151
154
  if scoped_violations is None:
152
155
  skipped_unreadable_count += 1
@@ -165,6 +168,7 @@ def _collect_partitioned_violations(
165
168
  repository_root: Path,
166
169
  all_added_lines_by_path: dict[Path, set[int]] | None,
167
170
  should_read_staged_content: bool = False,
171
+ prior_ref: str = "HEAD",
168
172
  ) -> PartitionedViolations:
169
173
  """Validate every eligible file and partition results, counting read skips."""
170
174
  all_eligible_paths = _eligible_resolved_paths(
@@ -176,6 +180,7 @@ def _collect_partitioned_violations(
176
180
  repository_root,
177
181
  all_added_lines_by_path,
178
182
  should_read_staged_content,
183
+ prior_ref,
179
184
  )
180
185
 
181
186
 
@@ -266,6 +271,7 @@ def _validate_and_count(
266
271
  repository_root: Path,
267
272
  all_added_lines_by_path: dict[Path, set[int]] | None,
268
273
  should_read_staged_content: bool,
274
+ prior_ref: str = "HEAD",
269
275
  ) -> PartitionedViolations:
270
276
  """Validate the eligible files, report the inspected count, return partitions."""
271
277
  all_eligible_paths = _eligible_resolved_paths(
@@ -277,6 +283,7 @@ def _validate_and_count(
277
283
  repository_root,
278
284
  all_added_lines_by_path,
279
285
  should_read_staged_content,
286
+ prior_ref,
280
287
  )
281
288
  _report_inspected_count(len(all_eligible_paths) - skipped_count)
282
289
  return blocking_by_file, advisory_by_file, skipped_count
@@ -288,6 +295,7 @@ def run_gate(
288
295
  repository_root: Path,
289
296
  all_added_lines_by_path: dict[Path, set[int]] | None = None,
290
297
  should_read_staged_content: bool = False,
298
+ prior_ref: str = "HEAD",
291
299
  ) -> int:
292
300
  """Run the gate over *all_file_paths* and emit a partitioned report.
293
301
 
@@ -297,6 +305,12 @@ def run_gate(
297
305
  repository_root: Repository root for resolving relative paths.
298
306
  all_added_lines_by_path: Per-file added-line maps, or None.
299
307
  should_read_staged_content: When True, validate staged blobs.
308
+ prior_ref: The git ref each file's grandfathering baseline is read
309
+ from. Defaults to ``HEAD`` (staged and explicit-paths mode); diff
310
+ mode passes the merge-base with the diff's base branch so a
311
+ fragment-baselined finding (such as ``check_comment_changes``)
312
+ grandfathers against the branch's starting point, not its own
313
+ latest commit.
300
314
 
301
315
  Returns:
302
316
  Zero when clean; non-zero on a blocking violation or a skipped file.
@@ -307,6 +321,7 @@ def run_gate(
307
321
  repository_root,
308
322
  all_added_lines_by_path,
309
323
  should_read_staged_content,
324
+ prior_ref,
310
325
  )
311
326
  return _report_partitioned_violations(
312
327
  partitions, repository_root, all_added_lines_by_path is None
@@ -12,19 +12,25 @@ from pathlib import Path
12
12
  from terminology_sweep import repository_environment
13
13
 
14
14
 
15
- def read_prior_committed_content(repository_root: Path, relative_path_posix: str) -> str:
16
- """Return the HEAD-committed content for *relative_path_posix*.
15
+ def read_prior_committed_content(
16
+ repository_root: Path, relative_path_posix: str, ref: str = "HEAD"
17
+ ) -> str:
18
+ """Return *ref*'s committed content for *relative_path_posix*.
17
19
 
18
20
  Args:
19
21
  repository_root: Repository root used as the ``git -C`` target.
20
22
  relative_path_posix: Repository-relative POSIX path to read.
23
+ ref: The git ref to read the blob from. Defaults to ``HEAD`` for
24
+ staged and explicit-paths mode; diff mode passes the merge-base
25
+ with the diff's base branch so grandfathering compares against
26
+ the branch's starting point rather than its own latest commit.
21
27
 
22
28
  Returns:
23
- The committed content at HEAD, or an empty string when the path is not
24
- tracked or ``git show`` returns non-zero.
29
+ The committed content at *ref*, or an empty string when the path is
30
+ not tracked there or ``git show`` returns non-zero.
25
31
  """
26
32
  completed = subprocess.run(
27
- ["git", "show", f"HEAD:{relative_path_posix}"],
33
+ ["git", "show", f"{ref}:{relative_path_posix}"],
28
34
  cwd=str(repository_root),
29
35
  capture_output=True,
30
36
  text=True,
@@ -43,6 +43,7 @@ from preflight_self_heal import silently_clear_stale_local_hooks_path_override
43
43
  from reviews_disabled import (
44
44
  CLAUDE_REVIEWS_DISABLED_BUGTEAM_TOKEN,
45
45
  CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME,
46
+ CLAUDE_REVIEWS_ENABLED_ENV_VAR_NAME,
46
47
  EXIT_CODE_BUGTEAM_DISABLED_VIA_ENV,
47
48
  is_bugteam_disabled_via_env,
48
49
  )
@@ -467,8 +468,10 @@ def main(all_arguments: list[str]) -> int:
467
468
 
468
469
  Returns:
469
470
  Zero on success. Non-zero exit code on the first failing check.
470
- Returns :data:`EXIT_CODE_BUGTEAM_DISABLED_VIA_ENV` when
471
- ``CLAUDE_REVIEWS_DISABLED`` lists the ``bugteam`` token.
471
+ Returns :data:`EXIT_CODE_BUGTEAM_DISABLED_VIA_ENV` when bugteam is off
472
+ for this run. Bugteam is off by default, so that happens when
473
+ ``CLAUDE_REVIEWS_ENABLED`` omits the ``bugteam`` token, and also when
474
+ ``CLAUDE_REVIEWS_DISABLED`` lists it.
472
475
  """
473
476
  arguments = parse_arguments(all_arguments)
474
477
  skip_env_var_name = BUGTEAM_PREFLIGHT_SKIP_ENV_VAR_NAME
@@ -480,13 +483,15 @@ def main(all_arguments: list[str]) -> int:
480
483
  )
481
484
  return 0
482
485
  reviews_disabled_env_var_name = CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME
486
+ reviews_enabled_env_var_name = CLAUDE_REVIEWS_ENABLED_ENV_VAR_NAME
483
487
  reviews_disabled_bugteam_token = CLAUDE_REVIEWS_DISABLED_BUGTEAM_TOKEN
484
488
  disabled_via_env_exit_code = EXIT_CODE_BUGTEAM_DISABLED_VIA_ENV
485
489
  if is_bugteam_disabled_via_env():
486
490
  print(
487
491
  f"bugteam_preflight: halted "
488
- f"({reviews_disabled_env_var_name} contains "
489
- f"'{reviews_disabled_bugteam_token}').",
492
+ f"({reviews_enabled_env_var_name} omits "
493
+ f"'{reviews_disabled_bugteam_token}', or "
494
+ f"{reviews_disabled_env_var_name} lists it).",
490
495
  file=sys.stderr,
491
496
  )
492
497
  return disabled_via_env_exit_code
@@ -5,13 +5,17 @@
5
5
  enabled lists bugbot, disabled empty -> bugbot ok: runs
6
6
  no lists set -> bugbot flag: off (default)
7
7
  enabled lists bugbot, disabled bugbot -> bugbot flag: off (opt-out wins)
8
- disabled empty -> bugteam ok: runs
8
+ enabled lists bugteam, disabled empty -> bugteam ok: runs
9
+ no lists set -> bugteam flag: off (default)
10
+ no lists set -> codex flag: off (default)
11
+ disabled empty -> copilot ok: runs
9
12
  disabled lists copilot -> copilot flag: off
10
- disabled lists codex -> codex flag: off
11
13
 
12
- Bugbot is off by default and runs only when the enabled list names it.
13
- Bugteam, copilot, and codex run by default and stop only when the disabled
14
- list names them; both lists parse case-insensitively and tolerate whitespace.
14
+ Bugbot, bugteam, and codex are off by default. Each runs only when the
15
+ enabled list names it. A token in the disabled list forces one off even
16
+ when the enabled list names it too. Copilot runs by default and stops only
17
+ when the disabled list names it. Both lists parse case-insensitively and
18
+ tolerate whitespace.
15
19
  """
16
20
 
17
21
  from __future__ import annotations
@@ -75,15 +79,42 @@ def _is_reviewer_listed_in_env(
75
79
  return reviewer_token in all_listed_tokens
76
80
 
77
81
 
78
- def is_bugteam_disabled_via_env() -> bool:
79
- """Check whether CLAUDE_REVIEWS_DISABLED opts the bug-audit family out.
82
+ def _is_off_by_default_reviewer_disabled(reviewer_token: str) -> bool:
83
+ """Decide whether an opt-in reviewer stays off for this run.
84
+
85
+ ::
86
+
87
+ enabled lists the token, disabled empty -> False (runs)
88
+ neither list names the token -> True (off by default)
89
+ both lists name the token -> True (opt-out wins)
90
+
91
+ Args:
92
+ reviewer_token: The lowercase reviewer token to look up in both lists.
80
93
 
81
94
  Returns:
82
- True when the env var lists the ``bugteam`` token.
95
+ True when the reviewer stays off for this run.
83
96
  """
84
- return _is_reviewer_listed_in_env(
85
- CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME, CLAUDE_REVIEWS_DISABLED_BUGTEAM_TOKEN
97
+ is_opted_out = _is_reviewer_listed_in_env(
98
+ CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME, reviewer_token
86
99
  )
100
+ is_opted_in = _is_reviewer_listed_in_env(
101
+ CLAUDE_REVIEWS_ENABLED_ENV_VAR_NAME, reviewer_token
102
+ )
103
+ return is_opted_out or not is_opted_in
104
+
105
+
106
+ def is_bugteam_disabled_via_env() -> bool:
107
+ """Check whether the bug-audit family stays off for this run.
108
+
109
+ The bug-audit family is off by default. It runs only when
110
+ ``CLAUDE_REVIEWS_ENABLED`` lists ``bugteam``, and a ``bugteam`` token in
111
+ ``CLAUDE_REVIEWS_DISABLED`` forces it off even when the opt-in lists it.
112
+
113
+ Returns:
114
+ True when ``CLAUDE_REVIEWS_DISABLED`` lists ``bugteam`` or when
115
+ ``CLAUDE_REVIEWS_ENABLED`` does not list ``bugteam``.
116
+ """
117
+ return _is_off_by_default_reviewer_disabled(CLAUDE_REVIEWS_DISABLED_BUGTEAM_TOKEN)
87
118
 
88
119
 
89
120
  def is_bugbot_disabled_via_env() -> bool:
@@ -97,13 +128,7 @@ def is_bugbot_disabled_via_env() -> bool:
97
128
  True when ``CLAUDE_REVIEWS_DISABLED`` lists ``bugbot`` or when
98
129
  ``CLAUDE_REVIEWS_ENABLED`` does not list ``bugbot``.
99
130
  """
100
- is_opted_out = _is_reviewer_listed_in_env(
101
- CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME, CLAUDE_REVIEWS_DISABLED_BUGBOT_TOKEN
102
- )
103
- is_opted_in = _is_reviewer_listed_in_env(
104
- CLAUDE_REVIEWS_ENABLED_ENV_VAR_NAME, CLAUDE_REVIEWS_DISABLED_BUGBOT_TOKEN
105
- )
106
- return is_opted_out or not is_opted_in
131
+ return _is_off_by_default_reviewer_disabled(CLAUDE_REVIEWS_DISABLED_BUGBOT_TOKEN)
107
132
 
108
133
 
109
134
  def is_bugbot_opted_out_via_env() -> bool:
@@ -134,14 +159,17 @@ def is_copilot_disabled_via_env() -> bool:
134
159
 
135
160
 
136
161
  def is_codex_disabled_via_env() -> bool:
137
- """Check whether CLAUDE_REVIEWS_DISABLED opts the Codex reviewer out.
162
+ """Check whether the Codex reviewer stays off for this run.
163
+
164
+ Codex is off by default. It runs only when ``CLAUDE_REVIEWS_ENABLED``
165
+ lists ``codex``, and a ``codex`` token in ``CLAUDE_REVIEWS_DISABLED``
166
+ forces it off even when the opt-in lists it.
138
167
 
139
168
  Returns:
140
- True when the env var lists the ``codex`` token.
169
+ True when ``CLAUDE_REVIEWS_DISABLED`` lists ``codex`` or when
170
+ ``CLAUDE_REVIEWS_ENABLED`` does not list ``codex``.
141
171
  """
142
- return _is_reviewer_listed_in_env(
143
- CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME, CLAUDE_REVIEWS_DISABLED_CODEX_TOKEN
144
- )
172
+ return _is_off_by_default_reviewer_disabled(CLAUDE_REVIEWS_DISABLED_CODEX_TOKEN)
145
173
 
146
174
 
147
175
  def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
@@ -1 +1,21 @@
1
1
  """Test fixtures for _shared/pr-loop/scripts/tests/."""
2
+
3
+ import pytest
4
+ from pr_loop_shared_constants.reviews_disabled_constants import (
5
+ CLAUDE_REVIEWS_DISABLED_BUGTEAM_TOKEN,
6
+ CLAUDE_REVIEWS_ENABLED_ENV_VAR_NAME,
7
+ )
8
+
9
+
10
+ @pytest.fixture(autouse=True)
11
+ def opt_bugteam_in(monkeypatch: pytest.MonkeyPatch) -> None:
12
+ """Opt bugteam in so each case exercises its own check, not the default off.
13
+
14
+ Bugteam is off by default, so a preflight case that never mentions the
15
+ reviewer lists halts on the opt-in check before reaching the behavior it
16
+ was written for. A case that tests those lists overrides this from its own
17
+ body, where monkeypatch runs after the fixture.
18
+ """
19
+ monkeypatch.setenv(
20
+ CLAUDE_REVIEWS_ENABLED_ENV_VAR_NAME, CLAUDE_REVIEWS_DISABLED_BUGTEAM_TOKEN
21
+ )
@@ -200,9 +200,9 @@ def test_is_valid_project_root_uses_extracted_directory_marker_constants() -> No
200
200
 
201
201
  def test_permission_rule_tool_name_extracts_tool_prefix() -> None:
202
202
  assert common.permission_rule_tool_name("Edit(/repo/.claude/**)") == "Edit"
203
- assert common.permission_rule_tool_name("Write(c:/Users/jon/.claude/worktrees/x/**)") == (
204
- "Write"
205
- )
203
+ assert common.permission_rule_tool_name(
204
+ "Write(c:/Users/example/.claude/worktrees/x/**)"
205
+ ) == ("Write")
206
206
  assert common.permission_rule_tool_name(None) is None
207
207
  assert common.permission_rule_tool_name("not-a-rule") is None
208
208
 
@@ -217,13 +217,13 @@ def test_is_inert_file_permission_rule_for_write_glob_notebookedit() -> None:
217
217
 
218
218
  def test_all_project_path_aliases_for_reap_adds_home_alias_when_project_is_home() -> None:
219
219
  all_aliases = common.all_project_path_aliases_for_reap(
220
- "C:/Users/jon", home_directory_path="C:/Users/jon"
220
+ "C:/Users/example", home_directory_path="C:/Users/example"
221
221
  )
222
- assert all_aliases == ("C:/Users/jon", "$HOME")
222
+ assert all_aliases == ("C:/Users/example", "$HOME")
223
223
 
224
224
 
225
225
  def test_all_project_path_aliases_for_reap_skips_home_alias_for_other_projects() -> None:
226
226
  all_aliases = common.all_project_path_aliases_for_reap(
227
- "C:/dev/other", home_directory_path="C:/Users/jon"
227
+ "C:/dev/other", home_directory_path="C:/Users/example"
228
228
  )
229
229
  assert all_aliases == ("C:/dev/other",)
@@ -29,13 +29,30 @@ def test_is_bugteam_disabled_via_env_returns_true_when_env_lists_bugteam(
29
29
  assert reviews_disabled.is_bugteam_disabled_via_env() is True
30
30
 
31
31
 
32
- def test_is_bugteam_disabled_via_env_returns_false_when_env_is_empty(
32
+ def test_is_bugteam_disabled_via_env_returns_true_when_no_env_vars_set(
33
33
  monkeypatch: pytest.MonkeyPatch,
34
34
  ) -> None:
35
35
  monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
36
+ monkeypatch.delenv("CLAUDE_REVIEWS_ENABLED", raising=False)
37
+ assert reviews_disabled.is_bugteam_disabled_via_env() is True
38
+
39
+
40
+ def test_is_bugteam_disabled_via_env_returns_false_when_enabled_lists_bugteam(
41
+ monkeypatch: pytest.MonkeyPatch,
42
+ ) -> None:
43
+ monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
44
+ monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "bugteam")
36
45
  assert reviews_disabled.is_bugteam_disabled_via_env() is False
37
46
 
38
47
 
48
+ def test_is_bugteam_disabled_via_env_true_when_disabled_overrides_enabled(
49
+ monkeypatch: pytest.MonkeyPatch,
50
+ ) -> None:
51
+ monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "bugteam")
52
+ monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "bugteam")
53
+ assert reviews_disabled.is_bugteam_disabled_via_env() is True
54
+
55
+
39
56
  def test_is_bugbot_disabled_via_env_returns_true_when_env_lists_bugbot(
40
57
  monkeypatch: pytest.MonkeyPatch,
41
58
  ) -> None:
@@ -150,20 +167,30 @@ def test_is_codex_disabled_via_env_returns_true_when_env_lists_codex(
150
167
  assert reviews_disabled.is_codex_disabled_via_env() is True
151
168
 
152
169
 
153
- def test_is_codex_disabled_via_env_returns_false_when_env_is_empty(
170
+ def test_is_codex_disabled_via_env_returns_true_when_no_env_vars_set(
154
171
  monkeypatch: pytest.MonkeyPatch,
155
172
  ) -> None:
156
173
  monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
157
- assert reviews_disabled.is_codex_disabled_via_env() is False
174
+ monkeypatch.delenv("CLAUDE_REVIEWS_ENABLED", raising=False)
175
+ assert reviews_disabled.is_codex_disabled_via_env() is True
158
176
 
159
177
 
160
- def test_is_codex_disabled_via_env_returns_false_when_only_bugbot_listed(
178
+ def test_is_codex_disabled_via_env_returns_false_when_enabled_lists_codex(
161
179
  monkeypatch: pytest.MonkeyPatch,
162
180
  ) -> None:
163
- monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "bugbot")
181
+ monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
182
+ monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "codex")
164
183
  assert reviews_disabled.is_codex_disabled_via_env() is False
165
184
 
166
185
 
186
+ def test_is_codex_disabled_via_env_true_when_only_bugbot_enabled(
187
+ monkeypatch: pytest.MonkeyPatch,
188
+ ) -> None:
189
+ monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
190
+ monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "bugbot")
191
+ assert reviews_disabled.is_codex_disabled_via_env() is True
192
+
193
+
167
194
  def test_is_codex_disabled_via_env_true_when_listed_among_other_tokens(
168
195
  monkeypatch: pytest.MonkeyPatch,
169
196
  ) -> None:
@@ -223,8 +250,25 @@ def test_cli_main_supports_codex_reviewer(
223
250
  assert reviews_disabled.main(["--reviewer", "codex"]) == 0
224
251
 
225
252
 
226
- def test_cli_main_returns_one_for_codex_when_only_bugbot_disabled(
253
+ def test_cli_main_returns_one_for_codex_when_enabled_via_env(
227
254
  monkeypatch: pytest.MonkeyPatch,
228
255
  ) -> None:
229
256
  monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "bugbot")
257
+ monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "codex")
230
258
  assert reviews_disabled.main(["--reviewer", "codex"]) == 1
259
+
260
+
261
+ def test_cli_main_returns_one_for_bugteam_when_enabled_via_env(
262
+ monkeypatch: pytest.MonkeyPatch,
263
+ ) -> None:
264
+ monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
265
+ monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "bugteam")
266
+ assert reviews_disabled.main(["--reviewer", "bugteam"]) == 1
267
+
268
+
269
+ def test_cli_main_returns_zero_for_bugteam_when_no_env_vars_set(
270
+ monkeypatch: pytest.MonkeyPatch,
271
+ ) -> None:
272
+ monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
273
+ monkeypatch.delenv("CLAUDE_REVIEWS_ENABLED", raising=False)
274
+ assert reviews_disabled.main(["--reviewer", "bugteam"]) == 0
@@ -114,7 +114,7 @@ def test_revoke_strips_inert_write_glob_notebookedit_on_foreign_worktree_paths(
114
114
  (project_directory / ".git").mkdir()
115
115
  settings_path = tmp_path / "home" / ".claude" / "settings.json"
116
116
  foreign_worktree = (
117
- "c:/Users/jon/.claude/worktrees/claude-dev-env-702a0c2055d2/other-wt"
117
+ "c:/Users/example/.claude/worktrees/claude-dev-env-702a0c2055d2/other-wt"
118
118
  )
119
119
  live_other_project = "C:/dev/live-other-project"
120
120
  settings_path.parent.mkdir(parents=True, exist_ok=True)