claude-dev-env 2.12.0 → 2.14.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 (399) hide show
  1. package/AGENTS.md +61 -0
  2. package/CLAUDE.md +1 -57
  3. package/_shared/AGENTS.md +15 -0
  4. package/_shared/CLAUDE.md +1 -15
  5. package/_shared/advisor/AGENTS.md +22 -0
  6. package/_shared/advisor/CLAUDE.md +1 -22
  7. package/_shared/pr-loop/AGENTS.md +27 -0
  8. package/_shared/pr-loop/CLAUDE.md +1 -27
  9. package/_shared/pr-loop/scripts/AGENTS.md +35 -0
  10. package/_shared/pr-loop/scripts/CLAUDE.md +1 -35
  11. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
  12. package/_shared/pr-loop/scripts/code_rules_gate_parts/AGENTS.md +42 -0
  13. package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +1 -42
  14. package/_shared/pr-loop/scripts/pr_loop_shared_constants/AGENTS.md +26 -0
  15. package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -26
  16. package/_shared/pr-loop/scripts/test_code_rules_gate.py +0 -26
  17. package/_shared/pr-loop/scripts/tests/AGENTS.md +44 -0
  18. package/_shared/pr-loop/scripts/tests/CLAUDE.md +1 -44
  19. package/_shared/process-tree/AGENTS.md +41 -0
  20. package/_shared/process-tree/CLAUDE.md +1 -41
  21. package/agents/AGENTS.md +31 -0
  22. package/agents/CLAUDE.md +1 -32
  23. package/agents/clean-coder.md +6 -6
  24. package/agents/pr-description-writer.md +11 -81
  25. package/agents/test_agent_frontmatter.py +4 -4
  26. package/audit-rubrics/AGENTS.md +43 -0
  27. package/audit-rubrics/CLAUDE.md +1 -43
  28. package/audit-rubrics/category_rubrics/AGENTS.md +37 -0
  29. package/audit-rubrics/category_rubrics/CLAUDE.md +1 -37
  30. package/audit-rubrics/prompts/AGENTS.md +37 -0
  31. package/audit-rubrics/prompts/CLAUDE.md +1 -37
  32. package/bin/AGENTS.md +112 -0
  33. package/bin/CLAUDE.md +1 -112
  34. package/bin/ever-shipped-skills.mjs +1 -0
  35. package/bin/install-constants.mjs +6 -7
  36. package/bin/install.mjs +71 -13
  37. package/bin/install.prune.test.mjs +26 -0
  38. package/bin/install.settings-defaults.test.mjs +60 -33
  39. package/bin/install.test.mjs +36 -4
  40. package/commands/AGENTS.md +23 -0
  41. package/commands/CLAUDE.md +1 -23
  42. package/commands/hook-log-extract.md +3 -3
  43. package/commands/sr-loop.md +9 -5
  44. package/docs/AGENTS.md +32 -0
  45. package/docs/CLAUDE.md +1 -33
  46. package/docs/CODE_RULES.md +1 -1
  47. package/docs/imagegen.md +22 -0
  48. package/docs/references/AGENTS.md +17 -0
  49. package/docs/references/CLAUDE.md +1 -18
  50. package/hooks/AGENTS.md +31 -0
  51. package/hooks/CLAUDE.md +1 -31
  52. package/hooks/_gh_pr_author_swap_utils.py +14 -0
  53. package/hooks/advisory/AGENTS.md +16 -0
  54. package/hooks/advisory/CLAUDE.md +1 -16
  55. package/hooks/blocking/AGENTS.md +123 -0
  56. package/hooks/blocking/CLAUDE.md +1 -148
  57. package/hooks/blocking/claude_md_orphan_file_blocker_parts/AGENTS.md +28 -0
  58. package/hooks/blocking/claude_md_orphan_file_blocker_parts/CLAUDE.md +1 -28
  59. package/hooks/blocking/code_rules_banned_identifiers.py +1 -1
  60. package/hooks/blocking/code_rules_dead_module_constant.py +149 -37
  61. package/hooks/blocking/code_rules_probe_chains.py +6 -2
  62. package/hooks/blocking/config/AGENTS.md +10 -0
  63. package/hooks/blocking/config/CLAUDE.md +1 -22
  64. package/hooks/blocking/conventional_pr_title_gate.py +1 -2
  65. package/hooks/blocking/gh_pr_author_restore.py +7 -2
  66. package/hooks/blocking/intent_only_ending_blocker.py +11 -16
  67. package/hooks/blocking/inventory_intent_records/AGENTS.md +26 -0
  68. package/hooks/blocking/inventory_intent_records/CLAUDE.md +1 -26
  69. package/hooks/blocking/package_inventory_stale_blocker_parts/AGENTS.md +26 -0
  70. package/hooks/blocking/package_inventory_stale_blocker_parts/CLAUDE.md +1 -26
  71. package/hooks/blocking/pii_prevention_blocker_parts/AGENTS.md +24 -0
  72. package/hooks/blocking/pii_prevention_blocker_parts/CLAUDE.md +1 -24
  73. package/hooks/blocking/send_user_file_open_locally_blocker.py +1 -1
  74. package/hooks/blocking/stop_dispatcher.py +4 -4
  75. package/hooks/blocking/tdd_enforcer_parts/AGENTS.md +30 -0
  76. package/hooks/blocking/tdd_enforcer_parts/CLAUDE.md +1 -30
  77. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -3
  78. package/hooks/blocking/test_code_rules_enforcer_banned_prefixes.py +1 -1
  79. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +171 -0
  80. package/hooks/blocking/test_code_rules_enforcer_magic_allowlist.py +1 -1
  81. package/hooks/blocking/test_gh_pr_author_restore.py +2 -1
  82. package/hooks/blocking/test_intent_only_ending_blocker.py +4 -0
  83. package/hooks/blocking/test_pre_tool_use_dispatcher.py +6 -7
  84. package/hooks/blocking/test_send_user_file_open_locally_blocker.py +4 -3
  85. package/hooks/blocking/test_shared_stdin_adoption.py +0 -2
  86. package/hooks/diagnostic/AGENTS.md +43 -0
  87. package/hooks/diagnostic/CLAUDE.md +1 -43
  88. package/hooks/diagnostic/hook_log_extractor.py +4 -38
  89. package/hooks/diagnostic/hook_log_stop_wrapper.py +6 -155
  90. package/hooks/diagnostic/migrations/AGENTS.md +16 -0
  91. package/hooks/diagnostic/migrations/CLAUDE.md +1 -16
  92. package/hooks/diagnostic/queries/AGENTS.md +19 -0
  93. package/hooks/diagnostic/queries/CLAUDE.md +1 -19
  94. package/hooks/diagnostic/test_hook_log_extractor.py +10 -23
  95. package/hooks/diagnostic/test_hook_log_stop_wrapper.py +3 -331
  96. package/hooks/git-hooks/AGENTS.md +31 -0
  97. package/hooks/git-hooks/CLAUDE.md +1 -31
  98. package/hooks/git-hooks/git_hooks_constants/AGENTS.md +21 -0
  99. package/hooks/git-hooks/git_hooks_constants/CLAUDE.md +1 -21
  100. package/hooks/git-hooks/git_hooks_constants/__init__.py +4 -12
  101. package/hooks/git-hooks/pre_push.py +49 -118
  102. package/hooks/git-hooks/test_pre_push.py +111 -137
  103. package/hooks/hooks.json +0 -35
  104. package/hooks/hooks_constants/AGENTS.md +104 -0
  105. package/hooks/hooks_constants/CLAUDE.md +1 -113
  106. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -11
  107. package/hooks/hooks_constants/convergence_branch_constants.py +0 -1
  108. package/hooks/hooks_constants/fable_spawn_gate_constants.py +3 -4
  109. package/hooks/hooks_constants/hook_log_extractor_constants.py +1 -2
  110. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +0 -8
  111. package/hooks/hooks_constants/send_user_file_open_locally_blocker_constants.py +1 -6
  112. package/hooks/hooks_constants/stop_dispatcher_constants.py +0 -1
  113. package/hooks/hooks_constants/task_list_loop_starter_constants.py +4 -3
  114. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +1 -26
  115. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +0 -5
  116. package/hooks/hooks_constants/test_stop_dispatcher_constants.py +0 -1
  117. package/hooks/hooks_constants/working_style_prompt_constants.py +29 -20
  118. package/hooks/lifecycle/AGENTS.md +18 -0
  119. package/hooks/lifecycle/CLAUDE.md +1 -20
  120. package/hooks/observability/AGENTS.md +20 -0
  121. package/hooks/observability/CLAUDE.md +1 -20
  122. package/hooks/session/AGENTS.md +33 -0
  123. package/hooks/session/CLAUDE.md +1 -33
  124. package/hooks/session/gh_pr_author_session_cleanup.py +7 -2
  125. package/hooks/session/task_list_loop_starter.py +3 -3
  126. package/hooks/session/test_gh_pr_author_session_cleanup.py +2 -1
  127. package/hooks/session/test_task_list_loop_starter.py +10 -2
  128. package/hooks/session/test_working_style_prompt.py +9 -4
  129. package/hooks/validation/AGENTS.md +20 -0
  130. package/hooks/validation/CLAUDE.md +1 -20
  131. package/hooks/validators/AGENTS.md +51 -0
  132. package/hooks/validators/CLAUDE.md +1 -51
  133. package/hooks/validators/health_check.py +52 -19
  134. package/hooks/validators/python_style_checks.py +1 -1
  135. package/hooks/validators/test_health_check.py +112 -27
  136. package/hooks/workflow/AGENTS.md +16 -0
  137. package/hooks/workflow/CLAUDE.md +1 -16
  138. package/installable-surfaces.manifest.json +1 -0
  139. package/output-styles/AGENTS.md +15 -0
  140. package/output-styles/CLAUDE.md +1 -15
  141. package/package.json +2 -1
  142. package/rules/AGENTS.md +61 -0
  143. package/rules/CLAUDE.md +1 -63
  144. package/rules/code-standards.md +5 -9
  145. package/rules/doc-prose-cuts.md +1 -1
  146. package/rules/gh-cli-conventions.md +0 -1
  147. package/rules/git-workflow.md +1 -3
  148. package/scripts/AGENTS.md +62 -0
  149. package/scripts/CLAUDE.md +1 -63
  150. package/scripts/_code_review_test_support.py +0 -95
  151. package/scripts/codex_compat_materializer.py +4 -0
  152. package/scripts/dev_env_scripts_constants/AGENTS.md +21 -0
  153. package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -21
  154. package/scripts/dev_env_scripts_constants/code_review_constants.py +9 -98
  155. package/scripts/dev_env_scripts_constants/test_code_review_constants.py +1 -44
  156. package/scripts/invoke_code_review.py +8 -451
  157. package/scripts/sync_to_cursor/AGENTS.md +23 -0
  158. package/scripts/sync_to_cursor/CLAUDE.md +1 -23
  159. package/scripts/test_invoke_code_review.py +5 -61
  160. package/scripts/test_invoke_code_review_cli.py +1 -45
  161. package/scripts/test_invoke_code_review_contract.py +1 -118
  162. package/scripts/test_validate_instruction_pairs.py +90 -0
  163. package/scripts/test_verify_installable_package.py +5 -1
  164. package/scripts/tests/AGENTS.md +33 -0
  165. package/scripts/tests/CLAUDE.md +1 -33
  166. package/scripts/tests/test_codex_compat_materializer.py +36 -1
  167. package/scripts/validate_instruction_pairs.py +183 -0
  168. package/settings.json +1 -10
  169. package/skills/AGENTS.md +72 -0
  170. package/skills/CLAUDE.md +1 -64
  171. package/skills/_shared/AGENTS.md +44 -0
  172. package/skills/_shared/CLAUDE.md +1 -44
  173. package/skills/_shared/advisor/AGENTS.md +9 -0
  174. package/skills/_shared/advisor/CLAUDE.md +1 -9
  175. package/skills/_shared/pr-loop/AGENTS.md +50 -0
  176. package/skills/_shared/pr-loop/CLAUDE.md +1 -50
  177. package/skills/_shared/pr-loop/prompts/AGENTS.md +9 -0
  178. package/skills/_shared/pr-loop/prompts/CLAUDE.md +1 -9
  179. package/skills/_shared/pr-loop/scripts/AGENTS.md +35 -0
  180. package/skills/_shared/pr-loop/scripts/CLAUDE.md +1 -35
  181. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +24 -0
  182. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -24
  183. package/skills/anthropic-plan/AGENTS.md +34 -0
  184. package/skills/anthropic-plan/CLAUDE.md +1 -34
  185. package/skills/anthropic-plan/scripts/AGENTS.md +11 -0
  186. package/skills/anthropic-plan/scripts/CLAUDE.md +1 -11
  187. package/skills/anthropic-plan/scripts/anthropic_plan_scripts_constants/AGENTS.md +16 -0
  188. package/skills/anthropic-plan/scripts/anthropic_plan_scripts_constants/CLAUDE.md +1 -16
  189. package/skills/anthropic-plan/templates/AGENTS.md +13 -0
  190. package/skills/anthropic-plan/templates/CLAUDE.md +1 -13
  191. package/skills/anthropic-plan/workflow/AGENTS.md +14 -0
  192. package/skills/anthropic-plan/workflow/CLAUDE.md +1 -14
  193. package/skills/auditing-claude-config/AGENTS.md +21 -0
  194. package/skills/auditing-claude-config/CLAUDE.md +1 -21
  195. package/skills/autoconverge/AGENTS.md +36 -0
  196. package/skills/autoconverge/CLAUDE.md +1 -36
  197. package/skills/autoconverge/reference/AGENTS.md +16 -0
  198. package/skills/autoconverge/reference/CLAUDE.md +1 -16
  199. package/skills/autoconverge/reference/convergence.md +7 -7
  200. package/skills/autoconverge/reference/gotchas.md +3 -10
  201. package/skills/autoconverge/workflow/AGENTS.md +26 -0
  202. package/skills/autoconverge/workflow/CLAUDE.md +1 -26
  203. package/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +16 -0
  204. package/skills/autoconverge/workflow/autoconverge_report_constants/CLAUDE.md +1 -16
  205. package/skills/autoconverge/workflow/converge.contract.test.mjs +53 -1388
  206. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +4 -3
  207. package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +33 -493
  208. package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +15 -97
  209. package/skills/autoconverge/workflow/converge.mjs +201 -238
  210. package/skills/autoconverge/workflow/converge.precatch.test.mjs +1 -1
  211. package/skills/bugteam/AGENTS.md +30 -0
  212. package/skills/bugteam/CLAUDE.md +1 -30
  213. package/skills/bugteam/CONSTRAINTS.md +1 -1
  214. package/skills/bugteam/SKILL.md +9 -0
  215. package/skills/bugteam/reference/AGENTS.md +20 -0
  216. package/skills/bugteam/reference/CLAUDE.md +1 -20
  217. package/skills/bugteam/reference/audit-and-teammates.md +10 -6
  218. package/skills/bugteam/reference/obstacles/AGENTS.md +24 -0
  219. package/skills/bugteam/reference/obstacles/CLAUDE.md +1 -24
  220. package/skills/bugteam/scripts/AGENTS.md +30 -0
  221. package/skills/bugteam/scripts/CLAUDE.md +1 -30
  222. package/skills/bugteam/scripts/bugteam_scripts_constants/AGENTS.md +18 -0
  223. package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +1 -18
  224. package/skills/bugteam/test_skill_additions.py +9 -0
  225. package/skills/closeout/reference/handoff-prompt-template.md +1 -1
  226. package/skills/codex-review/AGENTS.md +46 -0
  227. package/skills/codex-review/CLAUDE.md +1 -46
  228. package/skills/codex-review/reference/AGENTS.md +15 -0
  229. package/skills/codex-review/reference/CLAUDE.md +1 -15
  230. package/skills/codex-review/scripts/codex_review_scripts_constants/AGENTS.md +18 -0
  231. package/skills/codex-review/scripts/codex_review_scripts_constants/CLAUDE.md +1 -18
  232. package/skills/codex-review/test_skill_scaffold.py +3 -3
  233. package/skills/comments/SKILL.md +65 -0
  234. package/skills/copilot-review/AGENTS.md +18 -0
  235. package/skills/copilot-review/CLAUDE.md +1 -18
  236. package/skills/descriptions/SKILL.md +51 -0
  237. package/skills/emergencies/SKILL.md +42 -0
  238. package/skills/everything-search/AGENTS.md +17 -0
  239. package/skills/everything-search/CLAUDE.md +1 -17
  240. package/skills/fresh-branch/AGENTS.md +14 -0
  241. package/skills/fresh-branch/CLAUDE.md +1 -14
  242. package/skills/grok-spawn/AGENTS.md +28 -0
  243. package/skills/grok-spawn/CLAUDE.md +1 -28
  244. package/skills/orchestrator/SKILL.md +2 -4
  245. package/skills/plan-to-pr/SKILL.md +6 -7
  246. package/skills/plan-to-pr/reference/final-validation-tasks.md +1 -1
  247. package/skills/plan-to-pr/reference/process-inventory.md +2 -2
  248. package/skills/plan-to-pr/reference/review-loop.md +2 -2
  249. package/skills/plan-to-pr/reference/run-record.schema.json +1 -1
  250. package/skills/plan-to-pr/reference/task-seeds.md +1 -1
  251. package/skills/plan-to-pr/reference/task-ticket.md +3 -4
  252. package/skills/plan-to-pr/scripts/config/constants.py +1 -2
  253. package/skills/plan-to-pr/scripts/test_validate_protocol.py +1 -1
  254. package/skills/plan-to-pr/test_skill_contract.py +2 -2
  255. package/skills/pr-converge/AGENTS.md +32 -0
  256. package/skills/pr-converge/CLAUDE.md +1 -32
  257. package/skills/pr-converge/SKILL.md +13 -16
  258. package/skills/pr-converge/pr_converge_skill_constants/AGENTS.md +26 -0
  259. package/skills/pr-converge/pr_converge_skill_constants/CLAUDE.md +1 -26
  260. package/skills/pr-converge/reference/AGENTS.md +28 -0
  261. package/skills/pr-converge/reference/CLAUDE.md +1 -28
  262. package/skills/pr-converge/reference/obstacles/AGENTS.md +23 -0
  263. package/skills/pr-converge/reference/obstacles/CLAUDE.md +1 -23
  264. package/skills/pr-converge/reference/progress-checklist.md +6 -5
  265. package/skills/pr-converge/reference/state-schema.md +1 -27
  266. package/skills/pr-converge/scripts/AGENTS.md +46 -0
  267. package/skills/pr-converge/scripts/CLAUDE.md +1 -46
  268. package/skills/pr-converge/scripts/pr_converge_scripts_constants/AGENTS.md +18 -0
  269. package/skills/pr-converge/scripts/pr_converge_scripts_constants/CLAUDE.md +1 -18
  270. package/skills/pr-converge/workflows/AGENTS.md +16 -0
  271. package/skills/pr-converge/workflows/CLAUDE.md +1 -16
  272. package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
  273. package/skills/pr-loop-cloud-transport/reference/identity-and-hooks.md +3 -3
  274. package/skills/pr-loop-lifecycle/SKILL.md +1 -1
  275. package/skills/pr-loop-lifecycle/reference/teardown-publish-permissions.md +1 -1
  276. package/skills/prototype/SKILL.md +4 -4
  277. package/skills/prototype/reference/honest-limitations.md +5 -5
  278. package/skills/prototype/reference/promotion-tasks.md +2 -2
  279. package/skills/prototype/workflows/promotion.md +4 -6
  280. package/skills/rebase/AGENTS.md +32 -0
  281. package/skills/rebase/CLAUDE.md +1 -32
  282. package/skills/recall/AGENTS.md +30 -0
  283. package/skills/recall/CLAUDE.md +1 -30
  284. package/skills/remember/AGENTS.md +31 -0
  285. package/skills/remember/CLAUDE.md +1 -31
  286. package/skills/reviews/SKILL.md +85 -0
  287. package/skills/session-log/AGENTS.md +32 -0
  288. package/skills/session-log/CLAUDE.md +1 -32
  289. package/skills/session-tidy/AGENTS.md +36 -0
  290. package/skills/session-tidy/CLAUDE.md +1 -36
  291. package/skills/skill-builder/AGENTS.md +49 -0
  292. package/skills/skill-builder/CLAUDE.md +1 -49
  293. package/skills/skill-builder/references/AGENTS.md +25 -0
  294. package/skills/skill-builder/references/CLAUDE.md +1 -25
  295. package/skills/skill-builder/references/deterministic-elements.md +1 -1
  296. package/skills/skill-builder/templates/AGENTS.md +14 -0
  297. package/skills/skill-builder/templates/CLAUDE.md +1 -14
  298. package/skills/skill-builder/workflows/AGENTS.md +19 -0
  299. package/skills/skill-builder/workflows/CLAUDE.md +1 -19
  300. package/skills/small-cl/SKILL.md +52 -0
  301. package/skills/task-build/AGENTS.md +29 -0
  302. package/skills/task-build/CLAUDE.md +1 -29
  303. package/skills/update/AGENTS.md +38 -0
  304. package/skills/update/CLAUDE.md +1 -38
  305. package/system-prompts/AGENTS.md +17 -0
  306. package/system-prompts/CLAUDE.md +1 -17
  307. package/system-prompts/software-engineer.xml +3 -3
  308. package/agents/code-verifier.md +0 -74
  309. package/docs/PR_DESCRIPTION_GUIDE.md +0 -157
  310. package/docs/references/code-review-enforcement.md +0 -132
  311. package/hooks/blocking/code_review_enforcement_config_bootstrap.py +0 -53
  312. package/hooks/blocking/code_review_gate_deny.py +0 -74
  313. package/hooks/blocking/code_review_pr_create_gate.py +0 -198
  314. package/hooks/blocking/code_review_push_gate.py +0 -145
  315. package/hooks/blocking/code_review_stamp_directory_write_blocker.py +0 -348
  316. package/hooks/blocking/code_review_stamp_store.py +0 -233
  317. package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +0 -7
  318. package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +0 -15
  319. package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +0 -212
  320. package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +0 -138
  321. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +0 -49
  322. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +0 -38
  323. package/hooks/blocking/code_verifier_spawn_preflight_gate.py +0 -483
  324. package/hooks/blocking/config/code_review_enforcement_constants.py +0 -143
  325. package/hooks/blocking/config/test_code_review_enforcement_constants.py +0 -166
  326. package/hooks/blocking/config/verified_commit_constants.py +0 -160
  327. package/hooks/blocking/config/verified_commit_context_constants.py +0 -21
  328. package/hooks/blocking/config/verified_commit_gate_output_constants.py +0 -14
  329. package/hooks/blocking/conftest.py +0 -123
  330. package/hooks/blocking/convergence_gate_blocker.py +0 -310
  331. package/hooks/blocking/pr_converge_bugteam_enforcer.py +0 -170
  332. package/hooks/blocking/pr_description_body_audit.py +0 -148
  333. package/hooks/blocking/pr_description_command_parser.py +0 -237
  334. package/hooks/blocking/pr_description_enforcer.py +0 -227
  335. package/hooks/blocking/pr_description_pr_number.py +0 -155
  336. package/hooks/blocking/pr_description_proof_of_work.py +0 -385
  337. package/hooks/blocking/pr_description_readability.py +0 -364
  338. package/hooks/blocking/reviewer_spawn_gate.py +0 -182
  339. package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +0 -62
  340. package/hooks/blocking/test_code_review_gate_deny.py +0 -54
  341. package/hooks/blocking/test_code_review_pr_create_gate.py +0 -199
  342. package/hooks/blocking/test_code_review_push_gate.py +0 -205
  343. package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +0 -199
  344. package/hooks/blocking/test_code_review_stamp_store.py +0 -205
  345. package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +0 -710
  346. package/hooks/blocking/test_code_verifier_tools_contract.py +0 -28
  347. package/hooks/blocking/test_convergence_gate_blocker.py +0 -428
  348. package/hooks/blocking/test_pr_converge_bugteam_enforcer.py +0 -311
  349. package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +0 -184
  350. package/hooks/blocking/test_pr_description_enforcer.py +0 -221
  351. package/hooks/blocking/test_pr_description_enforcer_body_audit.py +0 -247
  352. package/hooks/blocking/test_pr_description_enforcer_body_rules.py +0 -493
  353. package/hooks/blocking/test_pr_description_enforcer_command_parser.py +0 -366
  354. package/hooks/blocking/test_pr_description_enforcer_pr_number.py +0 -159
  355. package/hooks/blocking/test_pr_description_enforcer_proof_gate.py +0 -175
  356. package/hooks/blocking/test_pr_description_enforcer_readability.py +0 -443
  357. package/hooks/blocking/test_pr_description_proof_of_work.py +0 -313
  358. package/hooks/blocking/test_reviewer_spawn_gate.py +0 -230
  359. package/hooks/blocking/test_verdict_directory_write_blocker.py +0 -804
  360. package/hooks/blocking/test_verification_verdict_store.py +0 -974
  361. package/hooks/blocking/test_verified_commit_config_bootstrap.py +0 -67
  362. package/hooks/blocking/test_verified_commit_docs_delta.py +0 -176
  363. package/hooks/blocking/test_verified_commit_gate.py +0 -581
  364. package/hooks/blocking/test_verified_commit_gate_additional_context.py +0 -134
  365. package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +0 -131
  366. package/hooks/blocking/test_verifier_verdict_minter.py +0 -299
  367. package/hooks/blocking/tests/test_verified_commit_gate.py +0 -41
  368. package/hooks/blocking/verdict_directory_write_blocker.py +0 -687
  369. package/hooks/blocking/verification_verdict_store.py +0 -1039
  370. package/hooks/blocking/verified_commit_config_bootstrap.py +0 -63
  371. package/hooks/blocking/verified_commit_gate.py +0 -113
  372. package/hooks/blocking/verified_commit_gate_parts/CLAUDE.md +0 -28
  373. package/hooks/blocking/verified_commit_gate_parts/__init__.py +0 -1
  374. package/hooks/blocking/verified_commit_gate_parts/command_tokenization.py +0 -174
  375. package/hooks/blocking/verified_commit_gate_parts/deny_payload.py +0 -53
  376. package/hooks/blocking/verified_commit_gate_parts/deny_reason.py +0 -80
  377. package/hooks/blocking/verified_commit_gate_parts/directory_resolution.py +0 -170
  378. package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +0 -217
  379. package/hooks/blocking/verified_commit_gate_parts/tests/conftest.py +0 -10
  380. package/hooks/blocking/verified_commit_gate_parts/tests/test_command_tokenization.py +0 -94
  381. package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_payload.py +0 -17
  382. package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_reason.py +0 -38
  383. package/hooks/blocking/verified_commit_gate_parts/tests/test_directory_resolution.py +0 -71
  384. package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +0 -96
  385. package/hooks/blocking/verified_commit_message_accuracy_blocker.py +0 -167
  386. package/hooks/blocking/verifier_verdict_minter.py +0 -280
  387. package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +0 -64
  388. package/hooks/hooks_constants/convergence_gate_blocker_constants.py +0 -53
  389. package/hooks/hooks_constants/pr_converge_bugteam_enforcer_constants.py +0 -55
  390. package/hooks/hooks_constants/pr_converge_bugteam_enforcer_state.py +0 -67
  391. package/hooks/hooks_constants/pr_description_enforcer_constants.py +0 -167
  392. package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -107
  393. package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +0 -41
  394. package/hooks/hooks_constants/test_pr_description_enforcer_constants.py +0 -292
  395. package/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py +0 -198
  396. package/hooks/lifecycle/test_pr_converge_bugteam_skill_tracker.py +0 -283
  397. package/rules/proof-of-work-pr-comments.md +0 -22
  398. package/rules/verified-commit-gate-skip.md +0 -4
  399. package/scripts/Show-Asset.ps1 +0 -106
@@ -1,221 +0,0 @@
1
- """Unit tests for pr-description-enforcer PreToolUse hook entry flow."""
2
-
3
- import importlib.util
4
- import io
5
- import json
6
- import pathlib
7
- import sys
8
- from unittest.mock import patch
9
-
10
- import pytest
11
-
12
- _HOOK_DIR = pathlib.Path(__file__).parent
13
- _HOOKS_ROOT = _HOOK_DIR.parent
14
- if str(_HOOKS_ROOT) not in sys.path:
15
- sys.path.insert(0, str(_HOOKS_ROOT))
16
- if str(_HOOK_DIR) not in sys.path:
17
- sys.path.insert(0, str(_HOOK_DIR))
18
-
19
- from blocking import pr_description_readability as readability_module
20
- from blocking.pr_description_command_parser import extract_body_from_command
21
-
22
- hook_spec = importlib.util.spec_from_file_location(
23
- "pr_description_enforcer",
24
- _HOOK_DIR / "pr_description_enforcer.py",
25
- )
26
- assert hook_spec is not None
27
- assert hook_spec.loader is not None
28
- hook_module = importlib.util.module_from_spec(hook_spec)
29
- hook_spec.loader.exec_module(hook_module)
30
- validate_pr_body = hook_module.validate_pr_body
31
-
32
-
33
- @pytest.fixture(autouse=True)
34
- def _isolate_readability_state(tmp_path_factory, monkeypatch):
35
- """Redirect the three readability state files to per-test temp paths for every test.
36
-
37
- The enabled file is written with enabled=False so the readability check stays
38
- off for the entry-flow tests, isolating them from readability scoring and the
39
- live state directory.
40
- """
41
- per_test_state_dir = tmp_path_factory.mktemp("readability_state")
42
- strike_path = per_test_state_dir / "strikes.json"
43
- override_path = per_test_state_dir / "overrides.json"
44
- enabled_path = per_test_state_dir / "enabled.json"
45
- enabled_path.write_text(json.dumps({"enabled": False}))
46
- monkeypatch.setattr(readability_module, "READABILITY_STATE_FILE", strike_path)
47
- monkeypatch.setattr(readability_module, "READABILITY_THRESHOLD_OVERRIDE_FILE", override_path)
48
- monkeypatch.setattr(readability_module, "READABILITY_ENABLED_STATE_FILE", enabled_path)
49
-
50
-
51
- VALID_BODY = (
52
- "Allow commas in branch names so PRs whose head branch was generated from "
53
- "a title or external identifier no longer fail validation before any git "
54
- "operation.\n\n"
55
- "Fixes #1300.\n\n"
56
- "## Changes\n\n"
57
- "- `src/github/operations/branch.ts`: add `,` to the whitelist regex\n"
58
- "- `test/branch.test.ts`: 3 new cases covering comma-bearing branch names\n\n"
59
- "## Test plan\n\n"
60
- "- `bun test test/branch.test.ts`\n"
61
- "- `bun run typecheck`\n"
62
- )
63
-
64
-
65
- def test_validate_blocks_literal_empty_body() -> None:
66
- """A literal `gh pr create --body ""` must NOT skip enforcement. Empty-body
67
- extraction returns "" (distinct from shell-var's None), so the validator
68
- runs and blocks via the substantive-prose check. Conflating the two
69
- previously allowed `--body ""` to bypass validation entirely."""
70
- violations = validate_pr_body("")
71
- assert violations, (
72
- "Empty PR body must produce at least one violation (typically substantive "
73
- f"prose); got an empty list, which would let `--body \"\"` bypass enforcement."
74
- )
75
-
76
-
77
- def test_body_file_content_validated(tmp_path: pathlib.Path) -> None:
78
- body_file = tmp_path / "body.md"
79
- body_file.write_text("Too short.")
80
- body = extract_body_from_command(
81
- f'gh pr create --title "T" --body-file {body_file}'
82
- )
83
- assert body == "Too short."
84
- violations = validate_pr_body(body)
85
- assert violations
86
-
87
-
88
- def test_main_does_not_block_when_dash_b_only_appears_in_word() -> None:
89
- hook_input = {
90
- "tool_name": "Bash",
91
- "tool_input": {"command": 'gh pr create --title "fix sub-branch handling"'},
92
- }
93
- captured_stdout = io.StringIO()
94
- with patch("sys.stdin", io.StringIO(json.dumps(hook_input))):
95
- with patch("sys.stdout", captured_stdout):
96
- try:
97
- hook_module.main()
98
- except SystemExit:
99
- pass
100
- assert "deny" not in captured_stdout.getvalue()
101
-
102
-
103
- def test_main_does_not_block_when_no_body_flag_present() -> None:
104
- hook_input = {
105
- "tool_name": "Bash",
106
- "tool_input": {"command": 'gh pr create --title "My PR"'},
107
- }
108
- captured_stdout = io.StringIO()
109
- with patch("sys.stdin", io.StringIO(json.dumps(hook_input))):
110
- with patch("sys.stdout", captured_stdout):
111
- try:
112
- hook_module.main()
113
- except SystemExit:
114
- pass
115
- assert "deny" not in captured_stdout.getvalue()
116
-
117
-
118
- def test_main_allows_through_stdin_sentinel_body_file() -> None:
119
- """--body-file - must not be blocked (stdin body is unauditable)."""
120
- import io
121
- import json
122
- from unittest.mock import patch
123
- hook_input = {
124
- "tool_name": "Bash",
125
- "tool_input": {"command": 'gh pr create --title "T" --body-file -'},
126
- }
127
- captured_stdout = io.StringIO()
128
- with patch("sys.stdin", io.StringIO(json.dumps(hook_input))):
129
- with patch("sys.stdout", captured_stdout):
130
- try:
131
- hook_module.main()
132
- except SystemExit:
133
- pass
134
- assert "deny" not in captured_stdout.getvalue()
135
-
136
-
137
- def _build_main_hook_input(command: str) -> dict[str, object]:
138
- return {"tool_name": "Bash", "tool_input": {"command": command}}
139
-
140
-
141
- def _run_main_and_capture_decision(hook_input: dict[str, object]) -> str:
142
- captured_stdout = io.StringIO()
143
- with patch("sys.stdin", io.StringIO(json.dumps(hook_input))):
144
- with patch("sys.stdout", captured_stdout):
145
- try:
146
- hook_module.main()
147
- except SystemExit:
148
- pass
149
- return captured_stdout.getvalue()
150
-
151
-
152
- def test_main_blocks_gh_pr_edit_short_body_flag() -> None:
153
- """gh pr edit 123 -b "short" must be caught -- the short -b flag is a valid alias for --body."""
154
- command = 'gh pr edit 123 -b "Too short."'
155
- decision_output = _run_main_and_capture_decision(_build_main_hook_input(command))
156
- assert "deny" in decision_output
157
- assert "substantive prose" in decision_output.lower()
158
-
159
-
160
- def test_main_blocks_gh_pr_edit_body_file_short_flag(tmp_path) -> None:
161
- """gh pr edit 123 -F body.md must be caught -- -F is the short alias for --body-file."""
162
- body_file = tmp_path / "body.md"
163
- body_file.write_text("Too short.")
164
- command = f'gh pr edit 123 -F {body_file}'
165
- decision_output = _run_main_and_capture_decision(_build_main_hook_input(command))
166
- assert "deny" in decision_output
167
- assert "substantive prose" in decision_output.lower()
168
-
169
-
170
- def test_main_blocks_gh_pr_edit_body_file_long_flag(tmp_path) -> None:
171
- """gh pr edit 123 --body-file body.md must also be caught (was missing from is_pr_edit detection)."""
172
- body_file = tmp_path / "body.md"
173
- body_file.write_text("Too short.")
174
- command = f'gh pr edit 123 --body-file {body_file}'
175
- decision_output = _run_main_and_capture_decision(_build_main_hook_input(command))
176
- assert "deny" in decision_output
177
-
178
-
179
- def test_main_blocks_gh_pr_create_body_file_short_flag(tmp_path) -> None:
180
- """gh pr create -F body.md must be caught -- -F is the short alias for --body-file."""
181
- body_file = tmp_path / "body.md"
182
- body_file.write_text("Too short.")
183
- command = f'gh pr create --title "T" -F {body_file}'
184
- decision_output = _run_main_and_capture_decision(_build_main_hook_input(command))
185
- assert "deny" in decision_output
186
-
187
-
188
- def test_main_blocks_gh_pr_create_body_file_long_flag(tmp_path) -> None:
189
- """gh pr create --body-file body.md must be caught -- was missing from is_pr_create detection."""
190
- body_file = tmp_path / "body.md"
191
- body_file.write_text("Too short.")
192
- command = f'gh pr create --title "T" --body-file {body_file}'
193
- decision_output = _run_main_and_capture_decision(_build_main_hook_input(command))
194
- assert "deny" in decision_output
195
-
196
-
197
- def test_main_blocks_gh_pr_edit_short_body_equals_form() -> None:
198
- """gh pr edit 123 -b="short" must be caught -- the -b= equals form was bypassing
199
- the pre-filter and silently approving short bodies."""
200
- command = 'gh pr edit 123 -b="Too short."'
201
- decision_output = _run_main_and_capture_decision(_build_main_hook_input(command))
202
- assert "deny" in decision_output
203
- assert "substantive prose" in decision_output.lower()
204
-
205
-
206
- def test_main_blocks_gh_pr_edit_short_body_file_equals_form(tmp_path) -> None:
207
- """gh pr edit 123 -F=body.md must be caught -- the -F= equals form was bypassing the pre-filter."""
208
- body_file = tmp_path / "body.md"
209
- body_file.write_text("Too short.")
210
- command = f'gh pr edit 123 -F={body_file}'
211
- decision_output = _run_main_and_capture_decision(_build_main_hook_input(command))
212
- assert "deny" in decision_output
213
-
214
-
215
- def test_build_short_failing_body_helper_is_removed() -> None:
216
- """The unused test helper `_build_short_failing_body` had zero call sites and
217
- must not be re-introduced."""
218
- test_module = sys.modules[__name__]
219
- assert not hasattr(test_module, "_build_short_failing_body"), (
220
- "_build_short_failing_body was re-introduced; it has no callers in this test file."
221
- )
@@ -1,247 +0,0 @@
1
- """Unit tests for pr-description-enforcer body markdown and shape helpers."""
2
-
3
- import importlib.util
4
- import pathlib
5
- import re as _re
6
- import sys
7
-
8
- _HOOK_DIR = pathlib.Path(__file__).parent
9
- _HOOKS_ROOT = _HOOK_DIR.parent
10
- if str(_HOOKS_ROOT) not in sys.path:
11
- sys.path.insert(0, str(_HOOKS_ROOT))
12
- if str(_HOOK_DIR) not in sys.path:
13
- sys.path.insert(0, str(_HOOK_DIR))
14
-
15
- from hooks_constants.pr_description_enforcer_constants import ALL_HEAVY_OPENING_HEADERS
16
-
17
- body_audit_spec = importlib.util.spec_from_file_location(
18
- "pr_description_body_audit",
19
- _HOOK_DIR / "pr_description_body_audit.py",
20
- )
21
- assert body_audit_spec is not None
22
- assert body_audit_spec.loader is not None
23
- hook_module = importlib.util.module_from_spec(body_audit_spec)
24
- body_audit_spec.loader.exec_module(hook_module)
25
-
26
-
27
- def _build_heavy_body(opening_header: str, testing_header: str) -> str:
28
- intro_text = (
29
- "Adds shape-aware validation across the pr-description-enforcer pipeline. "
30
- "The change unifies the body audit with the Anthropic claude-code style "
31
- "so heavy PRs carry both an opening header and a testing header."
32
- )
33
- return (
34
- f"{intro_text}\n\n"
35
- f"{opening_header}\n\n"
36
- "The earlier flow rejected too many valid bodies on equivalence checks "
37
- "across the three shape categories described in the guide. The fix "
38
- "restructures the path around shape detection and surfaces the missing "
39
- "category in the block message so the agent can correct it on first try.\n\n"
40
- f"{testing_header}\n\n"
41
- "- `pytest packages/claude-dev-env/hooks/blocking/test_pr_description_enforcer.py`\n"
42
- "- Manual smoke test against the implementation PR with a sample heavy body\n"
43
- "- Run the readability check across the full corpus to confirm thresholds hold\n"
44
- )
45
-
46
-
47
- def test_compute_pr_body_shape_trivial() -> None:
48
- """A short single-sentence body with zero headers classifies as Trivial."""
49
- body = "Pin third-party GitHub Actions references to immutable commit SHAs."
50
- assert hook_module._compute_pr_body_shape(body) == "trivial"
51
-
52
-
53
- def test_compute_pr_body_shape_standard() -> None:
54
- """A medium body with one ## header below the Heavy threshold classifies as Standard."""
55
- body = (
56
- "Adds a timestamp check to prevent background data pulls from overwriting "
57
- "recent local edits. The pull engine compares the last-modified marker "
58
- "before deciding whether to apply a remote record.\n\n"
59
- "## Changes\n\n"
60
- "- `pullEngine.ts`: compare lastModified before overwriting\n"
61
- "- `pullEngine.test.ts`: 3 new cases\n"
62
- )
63
- assert hook_module._compute_pr_body_shape(body) == "standard"
64
-
65
-
66
- def test_compute_pr_body_shape_heavy() -> None:
67
- """A long body with two Heavy-detection headers classifies as Heavy."""
68
- body = _build_heavy_body("## Problem", "## Test plan")
69
- assert hook_module._compute_pr_body_shape(body) == "heavy"
70
-
71
-
72
- def test_first_non_empty_line_helper_is_removed() -> None:
73
- """`_first_non_empty_line` was the basis of the prior ceremony-on-Trivial
74
- check, which now uses `_iter_section_headers`. The helper has no remaining
75
- call sites; pin its removal so it cannot drift back as dead code."""
76
- assert not hasattr(hook_module, "_first_non_empty_line"), (
77
- "_first_non_empty_line must be removed; the ceremony-on-Trivial check "
78
- "now reads through _iter_section_headers instead."
79
- )
80
-
81
-
82
- def test_strip_leading_hash_lines_helper_is_removed() -> None:
83
- """The unused leading-hash stripper must not exist as a module attribute."""
84
- assert not hasattr(hook_module, "_strip_leading_hash_lines")
85
-
86
-
87
- def test_strip_markdown_ceremony_returns_stripped_prose() -> None:
88
- """The shared markdown stripper removes fences, inline code, blockquotes,
89
- headings, bullets, bold, emphasis, and Markdown link targets, leaving the
90
- underlying prose intact."""
91
- body = "\n".join(
92
- [
93
- "# Heading text",
94
- "> blockquoted content",
95
- "- bullet content",
96
- "**bold body**",
97
- "*emphasized body*",
98
- "[link label](https://example.com)",
99
- "`inline code body`",
100
- "```",
101
- "fenced code body",
102
- "```",
103
- "plain prose line",
104
- ]
105
- )
106
- stripped = hook_module.strip_markdown_ceremony(body)
107
- assert "Heading text" not in stripped
108
- assert "blockquoted content" in stripped
109
- assert "bullet content" in stripped
110
- assert "bold body" in stripped
111
- assert "emphasized body" in stripped
112
- assert "link label" in stripped
113
- assert "plain prose line" in stripped
114
- assert "inline code body" not in stripped
115
- assert "fenced code body" not in stripped
116
- assert "https://example.com" not in stripped
117
-
118
-
119
- def test_strip_markdown_ceremony_used_by_substantive_prose_count() -> None:
120
- """_count_substantive_prose_chars is consistent with the shared stripper:
121
- its returned count matches len of the whitespace-collapsed stripped body."""
122
- body = "# Heading\n\nA single paragraph of prose with **bold** and `code` words."
123
- stripped = hook_module.strip_markdown_ceremony(body)
124
- collapsed = _re.sub(r"\s+", " ", stripped).strip()
125
- assert hook_module._count_substantive_prose_chars(body) == len(collapsed)
126
-
127
-
128
- def test_shape_classifier_uses_substantive_chars_not_raw_length() -> None:
129
- """Shape classifier and ceremony-on-Trivial check must agree on the metric used
130
- against TRIVIAL_BODY_CHAR_THRESHOLD. A body whose raw length passes the
131
- threshold but whose substantive prose does not (e.g. tiny prose with a large
132
- fenced code block) is genuinely Trivial in shape -- not Standard."""
133
- tiny_prose_with_large_code_fence = "Done.\n\n```\n" + ("x" * 300) + "\n```"
134
- assert len(tiny_prose_with_large_code_fence) >= hook_module.TRIVIAL_BODY_CHAR_THRESHOLD
135
- assert (
136
- hook_module._count_substantive_prose_chars(tiny_prose_with_large_code_fence)
137
- < hook_module.TRIVIAL_BODY_CHAR_THRESHOLD
138
- )
139
- assert hook_module._compute_pr_body_shape(tiny_prose_with_large_code_fence) == "trivial"
140
-
141
-
142
- def test_body_contains_any_header_rejects_plural_extension() -> None:
143
- """`_body_contains_any_header` must enforce a word boundary after the
144
- canonical header text. `## Problems` (plural) extends the canonical
145
- word and must NOT satisfy `## Problem`, otherwise the Heavy
146
- required-header check is weaker than the documented contract."""
147
- body_with_plural_extension = "## Problems\n\nDetails follow."
148
- candidate_set = frozenset({"## Problem"})
149
- assert not hook_module._body_contains_any_header(body_with_plural_extension, candidate_set), (
150
- "`## Problems` must NOT satisfy `## Problem` (different header)"
151
- )
152
-
153
-
154
- def test_body_contains_any_header_accepts_punctuation_suffix() -> None:
155
- """The boundary rule must still accept canonical headers followed by
156
- non-word punctuation: colon, em-dash, parenthesis, trailing whitespace.
157
- Reviewers write `## Problem (context)` and `## Test plan: scope` —
158
- these must continue to satisfy the canonical headers."""
159
- candidate_set = frozenset({"## Problem"})
160
- for each_body in [
161
- "## Problem\n\nDetails.",
162
- "## Problem:\n\nDetails.",
163
- "## Problem (context)\n\nDetails.",
164
- "## Problem — context\n\nDetails.",
165
- ]:
166
- assert hook_module._body_contains_any_header(each_body, candidate_set), (
167
- f"`{each_body!r}` must satisfy `## Problem` (punctuation/space follows)"
168
- )
169
-
170
-
171
- def test_body_contains_any_header_rejects_alphanumeric_suffix() -> None:
172
- """`## Problem2`, `## ProblemX`, `## Problem_one` are different headers
173
- and must not match `## Problem`."""
174
- candidate_set = frozenset({"## Problem"})
175
- for each_body in [
176
- "## Problem2\n\nDetails.",
177
- "## ProblemX\n\nDetails.",
178
- "## Problem_one\n\nDetails.",
179
- ]:
180
- assert not hook_module._body_contains_any_header(each_body, candidate_set), (
181
- f"`{each_body!r}` must NOT satisfy `## Problem` (alphanumeric continuation)"
182
- )
183
-
184
-
185
- def test_iter_section_headers_ignores_headings_inside_fenced_code_blocks() -> None:
186
- """Headings nested inside ``` ... ``` fences are example content, not body headers.
187
- The shape classifier and the Heavy required-header check must agree with the markdown
188
- stripper -- the body of this very test demonstrates the regression."""
189
- body = (
190
- "Intro paragraph that does not classify the body.\n\n```\n## Problem\n## Test plan\n```\n"
191
- )
192
- headers = hook_module._iter_section_headers(body)
193
- assert headers == [], f"Expected zero headers (fenced content), got {headers}"
194
- assert hook_module._compute_pr_body_shape(body) != "heavy", (
195
- "Body with only fenced example headers must not classify as heavy"
196
- )
197
- assert hook_module._body_contains_any_header(body, ALL_HEAVY_OPENING_HEADERS) is False, (
198
- "Heavy opening-header check must not see fenced example content"
199
- )
200
-
201
-
202
- def test_long_body_without_heavy_headers_still_classifies_heavy() -> None:
203
- """The Heavy required-header check in `validate_pr_body` only runs when
204
- `_compute_pr_body_shape` returns HEAVY. Previously the classifier required
205
- BOTH length >= 500 chars AND >= 2 heavy detection headers, which meant a
206
- long body missing the required headers entirely was classified Standard
207
- and silently bypassed the missing-header enforcement. Length alone must
208
- drive the HEAVY classification so the validator can enforce the rule."""
209
- long_body_with_no_heavy_headers = (
210
- "Refactors the request-pipeline batcher to coalesce idempotent calls "
211
- "before the network round-trip. The change touches the dispatcher, the "
212
- "retry loop, the error normalizer, and three downstream consumers. "
213
- "Every test in the integration suite continues to pass without "
214
- "modification because the public contract is unchanged.\n\n"
215
- "The new coalescer reads a per-call digest, looks up an in-flight slot "
216
- "indexed by that digest, and appends the caller's promise to the slot "
217
- "instead of dispatching a duplicate request. Once the network response "
218
- "arrives, every queued promise resolves with the same value. Error "
219
- "responses propagate to every queued promise so retry logic stays "
220
- "consistent with the prior contract.\n"
221
- )
222
- assert (
223
- hook_module._count_substantive_prose_chars(long_body_with_no_heavy_headers)
224
- >= hook_module.HEAVY_MIN_BODY_CHARS_FOR_CLASSIFICATION
225
- )
226
- assert (
227
- hook_module._compute_pr_body_shape(long_body_with_no_heavy_headers)
228
- == hook_module.HEAVY_SHAPE
229
- )
230
-
231
-
232
- def test_compute_pr_body_shape_uses_named_shape_constants() -> None:
233
- """`_compute_pr_body_shape` returns the centralised shape names rather than
234
- inline string literals. Confirm the constants flow through end-to-end."""
235
- trivial_body = "Bump bun to 1.3.14."
236
- assert hook_module._compute_pr_body_shape(trivial_body) == hook_module.TRIVIAL_SHAPE
237
-
238
-
239
- def test_iter_section_headers_docstring_matches_actual_pattern() -> None:
240
- """`_iter_section_headers` uses `HEADING_LINE_PATTERN = ^#+`, so it returns
241
- every ATX heading level (`#`, `##`, `###`...), not just `##`. The docstring
242
- must describe that actual contract so callers cannot be misled."""
243
- docstring = hook_module._iter_section_headers.__doc__ or ""
244
- assert "every ATX heading" in docstring or "any heading level" in docstring, (
245
- f"_iter_section_headers docstring must document that it matches every "
246
- f"heading level (`HEADING_LINE_PATTERN` is `^#+`); got: {docstring!r}"
247
- )