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,483 +0,0 @@
1
- #!/usr/bin/env python3
2
- """PreToolUse hook: pre-flight gate for the code-verifier subagent spawn.
3
-
4
- The hook fires on an ``Agent`` or ``Task`` tool call whose ``subagent_type`` is
5
- ``code-verifier``. Before that verification spawn runs, the hook checks the
6
- branch for two committability problems against the resolved base ref: a real
7
- merge conflict (a non-mutating trial-merge of HEAD against the base ref) and a
8
- CODE_RULES violation on a line added in the working tree since the merge base
9
- (committed on the branch or uncommitted). When either fires, the hook denies
10
- the spawn with a reason addressed to the spawning agent that names the
11
- conflicting files and the violating file:line, so that agent fixes them and
12
- re-spawns. A CODE_RULES engine import/load failure is fail-closed (deny with a
13
- named load-failure section). Environmental problems — a non-repo cwd, an
14
- absent base ref, a git failure, an unreadable file alone, or a timeout — fail
15
- OPEN because the authoritative fail-closed CODE_RULES enforcement already runs
16
- at Write time and at commit time. The hook never network-fetches and never
17
- mutates the index or working tree.
18
- """
19
-
20
- from __future__ import annotations
21
-
22
- import contextlib
23
- import io
24
- import json
25
- import re
26
- import subprocess
27
- import sys
28
- from pathlib import Path
29
- from typing import TextIO
30
-
31
- _blocking_dir = str(Path(__file__).resolve().parent)
32
- if _blocking_dir not in sys.path:
33
- sys.path.insert(0, _blocking_dir)
34
-
35
- _hooks_dir = str(Path(__file__).resolve().parent.parent)
36
- if _hooks_dir not in sys.path:
37
- sys.path.append(_hooks_dir)
38
-
39
- from verification_verdict_store import ( # noqa: E402
40
- candidate_base_references,
41
- resolve_merge_base,
42
- resolve_repo_root,
43
- run_git,
44
- untracked_file_paths,
45
- )
46
-
47
- from hooks_constants.code_verifier_spawn_preflight_gate_constants import ( # noqa: E402
48
- ALL_CODE_VERIFIER_SPAWN_TOOL_NAMES,
49
- ALL_MERGE_HEAD_PROBE_FLAGS,
50
- ALL_MERGE_TREE_COMMAND_FLAGS,
51
- ALL_NAME_ONLY_WORKTREE_DIFF_FLAGS,
52
- ALL_UNIFIED_ZERO_DIFF_FLAGS,
53
- ALL_UNMERGED_PATHS_DIFF_FLAGS,
54
- CODE_RULES_SECTION_HEADER,
55
- CODE_VERIFIER_SUBAGENT_TYPE,
56
- DENY_REASON_LEAD,
57
- ENGINE_LOAD_FAILURE_SECTION,
58
- GATE_SCRIPTS_RELATIVE_PATH,
59
- MERGE_CONFLICT_SECTION_HEADER,
60
- MERGE_TREE_CLEAN_EXIT_CODE,
61
- MERGE_TREE_CONFLICT_EXIT_CODE,
62
- MERGE_TREE_TIMEOUT_SECONDS,
63
- )
64
- from hooks_constants.hook_block_logger import log_hook_block # noqa: E402
65
-
66
- _scripts_dir = str(Path(__file__).resolve().parents[2] / GATE_SCRIPTS_RELATIVE_PATH)
67
- if _scripts_dir not in sys.path:
68
- sys.path.insert(0, _scripts_dir)
69
-
70
- from code_rules_gate import ( # noqa: E402
71
- ValidateContentCallable,
72
- _collect_partitioned_violations,
73
- _report_partitioned_violations,
74
- is_code_path,
75
- load_validate_content,
76
- parse_added_line_numbers,
77
- whole_file_line_set,
78
- )
79
-
80
-
81
- def _should_run(payload_by_field: dict[str, object]) -> bool:
82
- """Return True only for a code-verifier Agent or Task spawn.
83
-
84
- Args:
85
- payload_by_field: The full PreToolUse hook payload (already
86
- JSON-parsed), keyed by top-level field name.
87
-
88
- Returns:
89
- True when the tool is Agent or Task and ``tool_input.subagent_type``
90
- is ``code-verifier``; False for every other shape.
91
- """
92
- if payload_by_field.get("tool_name", "") not in ALL_CODE_VERIFIER_SPAWN_TOOL_NAMES:
93
- return False
94
- tool_input = payload_by_field.get("tool_input", {})
95
- if not isinstance(tool_input, dict):
96
- return False
97
- return tool_input.get("subagent_type", "") == CODE_VERIFIER_SUBAGENT_TYPE
98
-
99
-
100
- def _resolve_repo_root_and_base(working_directory: str | None) -> tuple[str, str, str] | None:
101
- """Resolve the repo root, merge-base sha, and chosen base ref.
102
-
103
- Args:
104
- working_directory: The spawn's working directory from the payload, or
105
- None when the payload carries no ``cwd``.
106
-
107
- Returns:
108
- A ``(repo_root, merge_base_sha, base_ref)`` triple, or None when the
109
- directory is not a work tree or no base ref resolves on disk — the
110
- caller fails OPEN on None.
111
- """
112
- start_directory = working_directory if working_directory else str(Path.cwd())
113
- repo_root = resolve_repo_root(start_directory)
114
- if repo_root is None:
115
- return None
116
- merge_base_sha = resolve_merge_base(repo_root)
117
- if merge_base_sha is None:
118
- return None
119
- for each_reference in candidate_base_references(repo_root):
120
- if run_git(repo_root, "merge-base", "HEAD", each_reference) is not None:
121
- return repo_root, merge_base_sha, each_reference
122
- return None
123
-
124
-
125
- def _run_trial_merge(repo_root: str, base_ref: str) -> tuple[int, str] | None:
126
- """Run the non-mutating trial-merge and return its exit code and stdout.
127
-
128
- Args:
129
- repo_root: The repository top-level directory.
130
- base_ref: The base ref to trial-merge HEAD against.
131
-
132
- Returns:
133
- A ``(returncode, stdout)`` pair, or None when the command is missing,
134
- times out, or raises an OS-level error — the caller fails OPEN on None.
135
- """
136
- try:
137
- completed_process = subprocess.run(
138
- ["git", "-C", repo_root, *ALL_MERGE_TREE_COMMAND_FLAGS, base_ref, "HEAD"],
139
- capture_output=True,
140
- text=True,
141
- encoding="utf-8",
142
- errors="replace",
143
- timeout=MERGE_TREE_TIMEOUT_SECONDS,
144
- check=False,
145
- )
146
- except (OSError, subprocess.TimeoutExpired):
147
- return None
148
- return completed_process.returncode, completed_process.stdout
149
-
150
-
151
- def _merge_in_progress(repo_root: str) -> bool:
152
- """Decide whether an unfinished merge is recorded in the work tree.
153
-
154
- Args:
155
- repo_root: The repository top-level directory.
156
-
157
- Returns:
158
- True when ``MERGE_HEAD`` resolves — a merge is started but not yet
159
- committed; False when no merge is in progress or git fails.
160
- """
161
- return run_git(repo_root, *ALL_MERGE_HEAD_PROBE_FLAGS) is not None
162
-
163
-
164
- def _staged_merge_conflicts(repo_root: str) -> list[str] | None:
165
- """Return the unmerged paths of an in-progress merge from the index.
166
-
167
- A merge whose conflicts are fully resolved and staged leaves zero unmerged
168
- index entries, so the resolved state is committable and this returns an
169
- empty list. A merge with conflicts still open returns those paths, so the
170
- caller keeps denying until the resolution is staged.
171
-
172
- Args:
173
- repo_root: The repository top-level directory.
174
-
175
- Returns:
176
- The still-unmerged paths (empty when the resolution is fully staged),
177
- or None when git fails — the caller fails OPEN on None.
178
- """
179
- unmerged_text = run_git(repo_root, *ALL_UNMERGED_PATHS_DIFF_FLAGS)
180
- if unmerged_text is None:
181
- return None
182
- return [each_line for each_line in unmerged_text.splitlines() if each_line]
183
-
184
-
185
- def _conflicting_files(repo_root: str, base_ref: str) -> list[str] | None:
186
- """Return the files that conflict when HEAD trial-merges against the base.
187
-
188
- While a merge is in progress the branch tip has not yet recorded the
189
- resolution, so the trial-merge of the base against HEAD would re-report
190
- conflicts the staged index already fixed. In that state the index is the
191
- authority: an empty unmerged set is a committable resolution, and any
192
- remaining unmerged path is a live conflict.
193
-
194
- Args:
195
- repo_root: The repository top-level directory.
196
- base_ref: The base ref to trial-merge HEAD against.
197
-
198
- Returns:
199
- The conflicting file paths on a conflict exit, an empty list on a
200
- clean merge, or None on any infrastructure failure (command missing,
201
- timeout, or an exit code that is neither clean nor conflict) — the
202
- caller fails OPEN on None.
203
- """
204
- if _merge_in_progress(repo_root):
205
- return _staged_merge_conflicts(repo_root)
206
- merge_outcome = _run_trial_merge(repo_root, base_ref)
207
- if merge_outcome is None:
208
- return None
209
- return_code, merge_stdout = merge_outcome
210
- if return_code == MERGE_TREE_CLEAN_EXIT_CODE:
211
- return []
212
- if return_code != MERGE_TREE_CONFLICT_EXIT_CODE:
213
- return None
214
- return _parse_conflicting_paths(merge_stdout)
215
-
216
-
217
- def _parse_conflicting_paths(merge_stdout: str) -> list[str]:
218
- """Extract conflicting paths from trial-merge stdout.
219
-
220
- Args:
221
- merge_stdout: The stdout of a conflict-exit trial-merge: the written
222
- tree-OID line, then one conflicting path per line, then a blank
223
- line and informational text.
224
-
225
- Returns:
226
- The conflicting file paths — the lines after the tree-OID up to the
227
- first blank line.
228
- """
229
- all_lines = merge_stdout.splitlines()
230
- conflicting_paths: list[str] = []
231
- for each_line in all_lines[1:]:
232
- if not each_line.strip():
233
- break
234
- conflicting_paths.append(each_line)
235
- return conflicting_paths
236
-
237
-
238
- def _working_tree_added_lines_by_path(
239
- repo_root: str, merge_base_sha: str
240
- ) -> tuple[list[Path], dict[Path, set[int]]] | None:
241
- """Build the code-file surface and its working-tree-vs-merge-base added lines.
242
-
243
- Args:
244
- repo_root: The repository top-level directory.
245
- merge_base_sha: The merge-base sha the surface diffs against.
246
-
247
- Returns:
248
- A ``(file_paths, added_lines_by_path)`` pair keyed by resolved
249
- absolute path, or None when a surface git query fails — the caller
250
- fails OPEN on None.
251
- """
252
- tracked_changed_text = run_git(repo_root, *ALL_NAME_ONLY_WORKTREE_DIFF_FLAGS, merge_base_sha)
253
- if tracked_changed_text is None:
254
- return None
255
- untracked_paths = untracked_file_paths(repo_root)
256
- if untracked_paths is None:
257
- return None
258
- repo_root_path = Path(repo_root)
259
- file_paths: list[Path] = []
260
- added_lines_by_path: dict[Path, set[int]] = {}
261
- for each_relative in tracked_changed_text.splitlines():
262
- if not each_relative or not is_code_path(Path(each_relative)):
263
- continue
264
- resolved_path = (repo_root_path / each_relative).resolve()
265
- file_paths.append(resolved_path)
266
- added_lines_by_path[resolved_path] = _tracked_file_added_lines(
267
- repo_root, merge_base_sha, each_relative
268
- )
269
- for each_relative in untracked_paths:
270
- if not is_code_path(Path(each_relative)):
271
- continue
272
- resolved_path = (repo_root_path / each_relative).resolve()
273
- file_paths.append(resolved_path)
274
- added_lines_by_path[resolved_path] = whole_file_line_set(resolved_path)
275
- return file_paths, added_lines_by_path
276
-
277
-
278
- def _tracked_file_added_lines(repo_root: str, merge_base_sha: str, relative_path: str) -> set[int]:
279
- """Return the working-tree-added line numbers for one tracked file.
280
-
281
- Args:
282
- repo_root: The repository top-level directory.
283
- merge_base_sha: The merge-base sha the diff runs against.
284
- relative_path: The repo-relative path of the tracked changed file.
285
-
286
- Returns:
287
- The 1-indexed line numbers added vs the merge base in the working
288
- tree, or an empty set when the per-file diff fails.
289
- """
290
- unified_diff_text = run_git(
291
- repo_root, *ALL_UNIFIED_ZERO_DIFF_FLAGS, merge_base_sha, "--", relative_path
292
- )
293
- if unified_diff_text is None:
294
- return set()
295
- return parse_added_line_numbers(unified_diff_text)
296
-
297
-
298
- def _code_rules_report(
299
- repo_root: str, all_file_paths: list[Path], all_added_lines_by_path: dict[Path, set[int]]
300
- ) -> str | None:
301
- """Run the CODE_RULES engine and return a full deny section, or None.
302
-
303
- Args:
304
- repo_root: The repository top-level directory.
305
- all_file_paths: The resolved code-file paths to inspect.
306
- all_added_lines_by_path: Per-file working-tree-added line numbers keyed
307
- by resolved absolute path.
308
-
309
- Returns:
310
- A full deny section when a blocking violation lands on an added line or
311
- the CODE_RULES engine fails to import/load (fail-closed). None when the
312
- surface is clean, only an unreadable changed file caused a non-zero gate
313
- exit, or an environmental engine error arises — those non-block outcomes
314
- fail OPEN. The harness hook timeout in hooks.json is the wall-clock
315
- bound on a runaway engine.
316
- """
317
- if not all_file_paths:
318
- return None
319
- try:
320
- validate_content = load_validate_content()
321
- except SystemExit:
322
- return ENGINE_LOAD_FAILURE_SECTION
323
- except (
324
- ImportError,
325
- SyntaxError,
326
- AttributeError,
327
- RuntimeError,
328
- TypeError,
329
- NameError,
330
- OSError,
331
- re.error,
332
- ):
333
- return ENGINE_LOAD_FAILURE_SECTION
334
- try:
335
- blocking_present, captured_report = _run_gate_capturing_stderr(
336
- validate_content, all_file_paths, Path(repo_root), all_added_lines_by_path
337
- )
338
- except OSError:
339
- return None
340
- if not blocking_present:
341
- return None
342
- return f"{CODE_RULES_SECTION_HEADER}\n{captured_report.strip()}"
343
-
344
-
345
- def _run_gate_capturing_stderr(
346
- validate_content: ValidateContentCallable,
347
- all_file_paths: list[Path],
348
- repository_root: Path,
349
- all_added_lines_by_path: dict[Path, set[int]],
350
- ) -> tuple[bool, str]:
351
- """Run the gate, reporting whether a blocking violation was actually found.
352
-
353
- Args:
354
- validate_content: The enforcer ``validate_content`` callable.
355
- all_file_paths: The resolved code-file paths to inspect.
356
- repository_root: The repository root path the gate resolves against.
357
- all_added_lines_by_path: Per-file working-tree-added line numbers keyed
358
- by resolved absolute path.
359
-
360
- Returns:
361
- A ``(blocking_present, captured_report)`` pair. ``blocking_present`` is
362
- True only when at least one blocking violation landed on an added line;
363
- an unreadable changed file alone (which exits the gate non-zero) leaves
364
- it False, so the caller fails OPEN. ``captured_report`` is the gate's
365
- grouped stderr report.
366
- """
367
- blocking_by_file, advisory_by_file, skipped_unreadable_count = (
368
- _collect_partitioned_violations(
369
- validate_content,
370
- all_file_paths,
371
- repository_root,
372
- all_added_lines_by_path,
373
- False,
374
- )
375
- )
376
- captured_stderr = io.StringIO()
377
- with contextlib.redirect_stderr(captured_stderr):
378
- _report_partitioned_violations(
379
- blocking_by_file,
380
- advisory_by_file,
381
- repository_root,
382
- False,
383
- skipped_unreadable_count,
384
- )
385
- return bool(blocking_by_file), captured_stderr.getvalue()
386
-
387
-
388
- def _build_deny_reason(
389
- all_conflicting_files: list[str] | None, base_ref: str, code_rules_report: str | None
390
- ) -> str | None:
391
- """Assemble the spawner-addressed deny reason from the two check results.
392
-
393
- Args:
394
- all_conflicting_files: The conflicting file paths from the conflict
395
- check, an empty list when clean, or None when that check failed open.
396
- base_ref: The base ref named in the conflict section header.
397
- code_rules_report: A full CODE_RULES deny section (violations or engine
398
- load failure), or None when that check found nothing or failed open.
399
-
400
- Returns:
401
- The full deny reason when either check fired, or None when neither
402
- produced an issue.
403
- """
404
- reason_sections: list[str] = []
405
- if all_conflicting_files:
406
- conflict_lines = "\n".join(f" {each_path}" for each_path in all_conflicting_files)
407
- conflict_header = MERGE_CONFLICT_SECTION_HEADER.format(base_ref=base_ref)
408
- reason_sections.append(f"{conflict_header}\n{conflict_lines}")
409
- if code_rules_report:
410
- reason_sections.append(code_rules_report.strip())
411
- if not reason_sections:
412
- return None
413
- return DENY_REASON_LEAD + "\n\n" + "\n\n".join(reason_sections)
414
-
415
-
416
- def _emit_deny_payload(output_stream: TextIO, reason: str) -> None:
417
- """Write the PreToolUse deny payload to the provided stream.
418
-
419
- Args:
420
- output_stream: Writable text stream — production code passes
421
- ``sys.stdout``; tests pass a ``StringIO`` to capture the JSON.
422
- reason: The ``permissionDecisionReason`` text for the deny payload.
423
- """
424
- deny_payload = {
425
- "hookSpecificOutput": {
426
- "hookEventName": "PreToolUse",
427
- "permissionDecision": "deny",
428
- "permissionDecisionReason": reason,
429
- }
430
- }
431
- log_hook_block(
432
- calling_hook_name="code_verifier_spawn_preflight_gate.py",
433
- hook_event="PreToolUse",
434
- block_reason=reason,
435
- )
436
- output_stream.write(json.dumps(deny_payload) + "\n")
437
- output_stream.flush()
438
-
439
-
440
- def _preflight_deny_reason(payload_by_field: dict[str, object]) -> str | None:
441
- """Run both pre-flight checks and return a deny reason, or None to allow.
442
-
443
- Args:
444
- payload_by_field: The full PreToolUse hook payload, keyed by top-level
445
- field name.
446
-
447
- Returns:
448
- The deny reason when a check fired, or None when both checks pass or
449
- fail open.
450
- """
451
- working_directory = payload_by_field.get("cwd")
452
- resolution = _resolve_repo_root_and_base(
453
- working_directory if isinstance(working_directory, str) else None
454
- )
455
- if resolution is None:
456
- return None
457
- repo_root, merge_base_sha, base_ref = resolution
458
- conflicting_files = _conflicting_files(repo_root, base_ref)
459
- surface = _working_tree_added_lines_by_path(repo_root, merge_base_sha)
460
- code_rules_report = None
461
- if surface is not None:
462
- file_paths, added_lines_by_path = surface
463
- code_rules_report = _code_rules_report(repo_root, file_paths, added_lines_by_path)
464
- return _build_deny_reason(conflicting_files, base_ref, code_rules_report)
465
-
466
-
467
- def main() -> None:
468
- try:
469
- hook_payload = json.load(sys.stdin)
470
- except json.JSONDecodeError:
471
- sys.exit(0)
472
- if not isinstance(hook_payload, dict):
473
- sys.exit(0)
474
- if not _should_run(hook_payload):
475
- sys.exit(0)
476
- deny_reason = _preflight_deny_reason(hook_payload)
477
- if deny_reason is not None:
478
- _emit_deny_payload(sys.stdout, deny_reason)
479
- sys.exit(0)
480
-
481
-
482
- if __name__ == "__main__":
483
- main()
@@ -1,143 +0,0 @@
1
- """Single source of truth for the code-review enforcement gate family.
2
-
3
- ::
4
-
5
- CLAUDE_CODE_REVIEW_ENFORCEMENT=1 -> CODE_REVIEW_ENFORCEMENT_ENABLED True
6
- (unset) -> CODE_REVIEW_ENFORCEMENT_ENABLED False
7
- effort_meets_threshold("xhigh", PUSH_REQUIRED_EFFORT) -> True
8
- effort_meets_threshold("high", PR_CREATE_REQUIRED_EFFORT) -> False
9
-
10
- Every gate reads its thresholds and messages from here, so the effort a push
11
- needs and the effort a pull request needs never drift apart. The master flag
12
- tracks the environment, so a machine opts in through its own environment and
13
- the choice outlives a reinstall that rewrites this shipped file.
14
- """
15
-
16
- from __future__ import annotations
17
-
18
- import os
19
-
20
- CODE_REVIEW_ENFORCEMENT_ENV_VAR = "CLAUDE_CODE_REVIEW_ENFORCEMENT"
21
- ALL_ENFORCEMENT_ENABLED_ENV_VALUES = frozenset({"1", "true", "yes", "on"})
22
-
23
-
24
- def code_review_enforcement_enabled_in_environment() -> bool:
25
- """Read whether this machine turns the code-review gates on.
26
-
27
- ::
28
-
29
- CLAUDE_CODE_REVIEW_ENFORCEMENT=1 -> True
30
- CLAUDE_CODE_REVIEW_ENFORCEMENT=" On " -> True
31
- CLAUDE_CODE_REVIEW_ENFORCEMENT=0 -> False
32
- (variable unset) -> False
33
-
34
- A machine opts in through its own environment, so an install that rewrites
35
- this shipped file leaves the choice standing. Any value outside the enabled
36
- set, and an unset variable, read as off, so the gate family stays quiet
37
- until someone asks for it.
38
-
39
- Returns:
40
- True when the variable holds an enabled value, False otherwise.
41
- """
42
- raw_environment_setting = os.environ.get(CODE_REVIEW_ENFORCEMENT_ENV_VAR, "")
43
- return raw_environment_setting.strip().lower() in ALL_ENFORCEMENT_ENABLED_ENV_VALUES
44
-
45
-
46
- CODE_REVIEW_ENFORCEMENT_ENABLED = code_review_enforcement_enabled_in_environment()
47
- STAMP_DIRECTORY_NAME = "code-review-stamps"
48
- ALL_EFFORT_TOKENS_IN_ASCENDING_ORDER = ("low", "medium", "high", "xhigh", "max")
49
- PUSH_REQUIRED_EFFORT = "low"
50
- PR_CREATE_REQUIRED_EFFORT = "xhigh"
51
- STAMP_KEY_EFFORT = "effort"
52
- STAMP_KEY_MANIFEST_SHA256 = "manifest_sha256"
53
- STAMP_KEY_RECORDED_AT_EPOCH = "recorded_at_epoch"
54
- CODE_REVIEW_BYPASS_MARKER = "# code-review-skip"
55
- GATED_PUSH_SUBCOMMANDS = frozenset({"push"})
56
- ALL_GATED_SHELL_TOOL_NAMES = ("Bash", "PowerShell")
57
- ALL_WRITE_EDIT_TOOL_NAMES = ("Write", "Edit", "MultiEdit")
58
- MCP_CREATE_PULL_REQUEST_TOOL_NAME = "mcp__plugin_github_github__create_pull_request"
59
- HASH_PREVIEW_LENGTH = 16
60
- PRE_TOOL_USE_HOOK_EVENT_NAME = "PreToolUse"
61
- DENY_PERMISSION_DECISION = "deny"
62
- PUSH_GATE_HOOK_MODULE_NAME = "code_review_push_gate.py"
63
- PR_CREATE_GATE_HOOK_MODULE_NAME = "code_review_pr_create_gate.py"
64
- STAMP_WRITE_BLOCKER_HOOK_MODULE_NAME = "code_review_stamp_directory_write_blocker.py"
65
- SANCTIONED_STAMP_MINTER_FLAG = "--record-stamp"
66
- PUSH_GATE_CORRECTIVE_MESSAGE = (
67
- "BLOCKED: [CODE_REVIEW_PUSH_GATE] This branch surface has no clean "
68
- "code-review stamp at effort 'low' or higher. Run "
69
- "`python invoke_code_review.py --record-stamp --cwd <dir> "
70
- "--session-model <alias> low` (or a higher effort) so a clean review "
71
- "mints the stamp before `git push`; any file change after minting "
72
- "invalidates it. Append `# code-review-skip` as a trailing shell comment "
73
- "only when the verified-commit gate's verify-skip rule for a same-surface "
74
- "re-land applies. Exempt automatically: docs/image files, pytest test "
75
- "files, and Python files whose docstring- and comment-stripped AST is "
76
- "unchanged."
77
- )
78
- PR_CREATE_GATE_CORRECTIVE_MESSAGE = (
79
- "BLOCKED: [CODE_REVIEW_PR_CREATE_GATE] This branch surface has no clean "
80
- "code-review stamp at effort 'xhigh' or higher. Run "
81
- "`python invoke_code_review.py --record-stamp --cwd <dir> "
82
- "--session-model <alias> xhigh` (or max) so a clean review mints the "
83
- "stamp before `gh pr create` or the MCP create_pull_request tool. Any "
84
- "file change after minting invalidates the stamp."
85
- )
86
- STAMP_DIRECTORY_GUARD_MESSAGE = (
87
- "BLOCKED: [CODE_REVIEW_STAMP_DIRECTORY_GUARD] Access to the code-review "
88
- "stamp directory (~/.claude/code-review-stamps/) is denied. Only "
89
- "invoke_code_review.py --record-stamp mints stamp files; a shell or "
90
- "Write/Edit forge here would defeat the push and PR-create gates. "
91
- "Earn a stamp by running a clean /code-review through the invoker."
92
- )
93
- RELATIVE_STAMP_DIRECTORY_PATTERN = r"(?:^|(?<=[\s;&|(='\"]))code-review-stamps[\\/]"
94
- STAMP_DIRECTORY_CHANGE_TARGET_PATTERN = r"[ \t]+['\"]?code-review-stamps[\\/]?['\"]?(?=[\s;&|]|$)"
95
- STAMP_FILE_REFERENCE_BOUNDARY_PREFIX_PATTERN = r"(?:^|(?<=[\s;&|(='\"\\/]))"
96
- STAMP_FILE_ROOT_KEY_JSON_SUFFIX_PATTERN = r"[\\/][0-9a-f]{%d}\.json"
97
- GH_PR_CREATE_INVOCATION_PATTERN = (
98
- r"(?:^|[;&|\n`({]|\$\()[ \t]*"
99
- r"(?:(?:if|then|else|elif|while|until|do|!)[ \t]+)*"
100
- r"(?:[A-Za-z_][A-Za-z0-9_]*=\S+[ \t]+)*"
101
- r"gh(?:[ \t]+(?:--[A-Za-z][\w-]*(?:=\S+)?|-[A-Za-z])(?:[ \t]+(?!-)\S+)?)*"
102
- r"[ \t]+pr[ \t]+create\b"
103
- )
104
- ALL_STAMP_PATH_SEGMENT_NAMES = (".claude", "code-review-stamps")
105
- ALL_STAMP_PATH_SEGMENT_BODIES = ("claude", "code-review-stamps")
106
- STAMP_STORE_MODULE_NAME = "code_review_stamp_store"
107
- STAMP_MINT_FUNCTION_NAME = "record_clean_stamp"
108
- ALL_STAMP_STORE_FORGE_PATTERNS = (
109
- rf"\bimport\s+{STAMP_STORE_MODULE_NAME}\b",
110
- rf"\bfrom\s+{STAMP_STORE_MODULE_NAME}\s+import\b",
111
- rf"\b{STAMP_MINT_FUNCTION_NAME}\s*\(",
112
- )
113
-
114
-
115
- def effort_meets_threshold(candidate_effort: str, required_effort: str) -> bool:
116
- """Decide whether a recorded effort satisfies a required threshold.
117
-
118
- ::
119
-
120
- effort_meets_threshold("xhigh", "low") -> True xhigh outranks low
121
- effort_meets_threshold("low", "xhigh") -> False low sits below xhigh
122
- effort_meets_threshold("ultra", "low") -> False ultra is unranked
123
-
124
- A candidate satisfies the threshold when it sits at or above the required
125
- token in the ascending order. A token outside the known order — ``ultra``
126
- among them — satisfies nothing, so an unrecognized stored effort clears no
127
- gate.
128
-
129
- Args:
130
- candidate_effort: The effort a recorded stamp was earned at.
131
- required_effort: The effort the gate demands for the action.
132
-
133
- Returns:
134
- True when both tokens are known and the candidate ranks at or above the
135
- required token; False otherwise.
136
- """
137
- if candidate_effort not in ALL_EFFORT_TOKENS_IN_ASCENDING_ORDER:
138
- return False
139
- if required_effort not in ALL_EFFORT_TOKENS_IN_ASCENDING_ORDER:
140
- return False
141
- candidate_rank = ALL_EFFORT_TOKENS_IN_ASCENDING_ORDER.index(candidate_effort)
142
- required_rank = ALL_EFFORT_TOKENS_IN_ASCENDING_ORDER.index(required_effort)
143
- return candidate_rank >= required_rank