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,710 +0,0 @@
1
- """Behavior tests for the code_verifier_spawn_preflight_gate PreToolUse hook.
2
-
3
- Each test builds a real git repository in a temporary directory, writes real
4
- files (real merge conflicts, real CODE_RULES violations), and runs the hook
5
- script as a subprocess with a real Agent PreToolUse JSON payload on stdin —
6
- the exact production invocation path. No mocks. The harness mirrors
7
- test_precommit_code_rules_gate.py lines 1-70.
8
- """
9
-
10
- import json
11
- import shutil
12
- import subprocess
13
- import sys
14
- from pathlib import Path
15
-
16
- HOOK_PATH = Path(__file__).resolve().parent / "code_verifier_spawn_preflight_gate.py"
17
-
18
- CLEAN_MODULE_SOURCE = '''"""Increment helper used by the preflight gate tests."""
19
-
20
-
21
- def add_one(number: int) -> int:
22
- """Return *number* plus one.
23
-
24
- Args:
25
- number: The integer to increment.
26
-
27
- Returns:
28
- The incremented integer.
29
- """
30
- return number + 1
31
- '''
32
-
33
- CLEAN_MODULE_SOURCE_EDITED = '''"""Increment helper used by the preflight gate tests."""
34
-
35
-
36
- def add_one(number: int) -> int:
37
- """Return *number* plus two.
38
-
39
- Args:
40
- number: The integer to increment.
41
-
42
- Returns:
43
- The incremented integer.
44
- """
45
- return number + 2
46
- '''
47
-
48
- VIOLATING_MODULE_SOURCE = '''"""Module carrying a banned identifier for the preflight gate tests."""
49
-
50
-
51
- def compute_total() -> int:
52
- """Return a fixed total.
53
-
54
- Returns:
55
- The fixed total.
56
- """
57
- result = 1
58
- return result
59
- '''
60
-
61
- CLEAN_TWO_FUNCTION_SOURCE = '''"""Two-function helper used by the preflight gate tests."""
62
-
63
-
64
- def add_one(number: int) -> int:
65
- """Return *number* plus one.
66
-
67
- Args:
68
- number: The integer to increment.
69
-
70
- Returns:
71
- The incremented integer.
72
- """
73
- return number + 1
74
-
75
-
76
- def add_two(number: int) -> int:
77
- """Return *number* plus two.
78
-
79
- Args:
80
- number: The integer to increment.
81
-
82
- Returns:
83
- The integer plus two.
84
- """
85
- return number + 2
86
- '''
87
-
88
- PREEXISTING_VIOLATION_BASE_SOURCE = '''"""Module that already carries a banned identifier at the base commit."""
89
-
90
-
91
- def compute_total() -> int:
92
- """Return a fixed total.
93
-
94
- Returns:
95
- The fixed total.
96
- """
97
- result = 1
98
- return result
99
-
100
-
101
- def add_one(number: int) -> int:
102
- """Return *number* plus one.
103
-
104
- Args:
105
- number: The integer to increment.
106
-
107
- Returns:
108
- The incremented integer.
109
- """
110
- return number + 1
111
- '''
112
-
113
- PREEXISTING_VIOLATION_EDITED_SOURCE = '''"""Module that already carries a banned identifier at the base commit."""
114
-
115
-
116
- def compute_total() -> int:
117
- """Return a fixed total.
118
-
119
- Returns:
120
- The fixed total.
121
- """
122
- result = 1
123
- return result
124
-
125
-
126
- def add_one(number: int) -> int:
127
- """Return *number* plus two.
128
-
129
- Args:
130
- number: The integer to increment.
131
-
132
- Returns:
133
- The integer plus two.
134
- """
135
- return number + 2
136
- '''
137
-
138
- SHARED_BASE_SOURCE = '''"""Shared module the conflict fixture edits on both sides."""
139
-
140
-
141
- def shared_value() -> int:
142
- """Return the shared base value.
143
-
144
- Returns:
145
- The shared integer.
146
- """
147
- return 100
148
- '''
149
-
150
- SHARED_FEATURE_SOURCE = '''"""Shared module the conflict fixture edits on both sides."""
151
-
152
-
153
- def shared_value() -> int:
154
- """Return the shared base value.
155
-
156
- Returns:
157
- The shared integer.
158
- """
159
- return 200
160
- '''
161
-
162
- SHARED_DIVERGENT_SOURCE = '''"""Shared module the conflict fixture edits on both sides."""
163
-
164
-
165
- def shared_value() -> int:
166
- """Return the shared base value.
167
-
168
- Returns:
169
- The shared integer.
170
- """
171
- return 300
172
- '''
173
-
174
- OTHER_DIVERGENT_SOURCE = '''"""Unrelated module the divergent base edits for the behind-but-clean case."""
175
-
176
-
177
- def other_value() -> int:
178
- """Return the unrelated value.
179
-
180
- Returns:
181
- The unrelated integer.
182
- """
183
- return 42
184
- '''
185
-
186
- BROKEN_ENFORCER_SOURCE = (
187
- '"""Enforcer stub that raises a non-import error at load time."""\n'
188
- "\n"
189
- "raise RuntimeError('deliberate enforcer load failure')\n"
190
- )
191
-
192
- RE_ERROR_ENFORCER_SOURCE = (
193
- '"""Enforcer stub whose module-scope regex compile raises re.error."""\n'
194
- "\n"
195
- "import re\n"
196
- "\n"
197
- "re.compile('(')\n"
198
- )
199
-
200
-
201
- def run_git(repository_root: Path, *git_arguments: str) -> str:
202
- completed = subprocess.run(
203
- ["git", "-C", str(repository_root), *git_arguments],
204
- check=True,
205
- capture_output=True,
206
- text=True,
207
- )
208
- return completed.stdout.strip()
209
-
210
-
211
- def disable_native_git_hooks(repository_root: Path) -> None:
212
- empty_hooks_directory = repository_root.parent / "nohooks"
213
- empty_hooks_directory.mkdir(exist_ok=True)
214
- run_git(repository_root, "config", "core.hooksPath", str(empty_hooks_directory))
215
-
216
-
217
- def initialize_repository(repository_root: Path) -> None:
218
- run_git(repository_root, "init")
219
- run_git(repository_root, "config", "user.email", "tests@example.com")
220
- run_git(repository_root, "config", "user.name", "Preflight Tests")
221
- disable_native_git_hooks(repository_root)
222
- run_git(repository_root, "checkout", "-b", "main")
223
- (repository_root / "base.py").write_text(CLEAN_MODULE_SOURCE, encoding="utf-8")
224
- run_git(repository_root, "add", "base.py")
225
- run_git(repository_root, "commit", "-m", "initial")
226
- main_sha = run_git(repository_root, "rev-parse", "HEAD")
227
- run_git(repository_root, "update-ref", "refs/remotes/origin/main", main_sha)
228
- resolved_base = run_git(repository_root, "merge-base", "HEAD", "origin/main")
229
- assert resolved_base, "fixture must resolve a merge base against origin/main"
230
-
231
-
232
- def commit_file(repository_root: Path, relative_name: str, source_text: str, message: str) -> str:
233
- (repository_root / relative_name).write_text(source_text, encoding="utf-8")
234
- run_git(repository_root, "add", relative_name)
235
- run_git(repository_root, "commit", "-m", message)
236
- return run_git(repository_root, "rev-parse", "HEAD")
237
-
238
-
239
- def write_working_tree_file(repository_root: Path, relative_name: str, source_text: str) -> None:
240
- (repository_root / relative_name).write_text(source_text, encoding="utf-8")
241
-
242
-
243
- def write_invalid_utf8_file(repository_root: Path, relative_name: str) -> None:
244
- (repository_root / relative_name).write_bytes(b"\xff\xfe invalid utf-8 bytes\n")
245
-
246
-
247
- def advance_origin_main_divergent(
248
- repository_root: Path, base_sha: str, relative_name: str, source_text: str
249
- ) -> None:
250
- run_git(repository_root, "checkout", "-b", "divergent", base_sha)
251
- commit_file(repository_root, relative_name, source_text, "divergent edit")
252
- divergent_sha = run_git(repository_root, "rev-parse", "HEAD")
253
- run_git(repository_root, "update-ref", "refs/remotes/origin/main", divergent_sha)
254
- run_git(repository_root, "checkout", "feature")
255
-
256
-
257
- def _spawn_payload(tool_name: str, subagent_type: str, prompt: str, cwd: Path) -> str:
258
- return json.dumps(
259
- {
260
- "tool_name": tool_name,
261
- "tool_input": {"subagent_type": subagent_type, "prompt": prompt},
262
- "cwd": str(cwd),
263
- }
264
- )
265
-
266
-
267
- def write_agent_payload(subagent_type: str, prompt: str, cwd: Path) -> str:
268
- return _spawn_payload("Agent", subagent_type, prompt, cwd)
269
-
270
-
271
- def write_task_payload(subagent_type: str, prompt: str, cwd: Path) -> str:
272
- return _spawn_payload("Task", subagent_type, prompt, cwd)
273
-
274
-
275
- def run_hook(payload: str, cwd: Path) -> subprocess.CompletedProcess[str]:
276
- return subprocess.run(
277
- [sys.executable, str(HOOK_PATH)],
278
- check=False,
279
- input=payload,
280
- capture_output=True,
281
- text=True,
282
- cwd=str(cwd),
283
- timeout=120,
284
- )
285
-
286
-
287
- def is_allow(result: subprocess.CompletedProcess[str]) -> bool:
288
- stdout_text = result.stdout.strip()
289
- if not stdout_text:
290
- return True
291
- parsed = json.loads(stdout_text)
292
- hook_output = parsed.get("hookSpecificOutput", {})
293
- return hook_output.get("permissionDecision") != "deny"
294
-
295
-
296
- def deny_reason(result: subprocess.CompletedProcess[str]) -> str:
297
- parsed = json.loads(result.stdout.strip())
298
- return parsed["hookSpecificOutput"]["permissionDecisionReason"]
299
-
300
-
301
- def make_conflict_repository(tmp_path: Path) -> Path:
302
- repository_root = tmp_path / "repo"
303
- repository_root.mkdir()
304
- initialize_repository(repository_root)
305
- base_sha = commit_file(repository_root, "shared.py", SHARED_BASE_SOURCE, "add shared")
306
- run_git(repository_root, "checkout", "-b", "feature")
307
- commit_file(repository_root, "shared.py", SHARED_FEATURE_SOURCE, "feature edit")
308
- advance_origin_main_divergent(repository_root, base_sha, "shared.py", SHARED_DIVERGENT_SOURCE)
309
- return repository_root
310
-
311
-
312
- def make_stale_self_upstream_conflict_repository(tmp_path: Path) -> Path:
313
- repository_root = make_conflict_repository(tmp_path)
314
- feature_sha = run_git(repository_root, "rev-parse", "HEAD")
315
- run_git(repository_root, "remote", "add", "origin", str(repository_root))
316
- run_git(repository_root, "update-ref", "refs/remotes/origin/feature", feature_sha)
317
- run_git(repository_root, "config", "branch.feature.remote", "origin")
318
- run_git(repository_root, "config", "branch.feature.merge", "refs/heads/feature")
319
- return repository_root
320
-
321
-
322
- def make_merge_in_progress_repository(tmp_path: Path) -> Path:
323
- repository_root = tmp_path / "repo"
324
- repository_root.mkdir()
325
- initialize_repository(repository_root)
326
- base_sha = commit_file(repository_root, "shared.py", SHARED_BASE_SOURCE, "add shared")
327
- run_git(repository_root, "update-ref", "refs/remotes/origin/main", base_sha)
328
- run_git(repository_root, "checkout", "-b", "feature")
329
- commit_file(repository_root, "shared.py", SHARED_FEATURE_SOURCE, "feature edit")
330
- advance_origin_main_divergent(repository_root, base_sha, "shared.py", SHARED_DIVERGENT_SOURCE)
331
- return repository_root
332
-
333
-
334
- def start_conflicting_merge(repository_root: Path) -> None:
335
- subprocess.run(
336
- ["git", "-C", str(repository_root), "merge", "--no-edit", "origin/main"],
337
- check=False,
338
- capture_output=True,
339
- text=True,
340
- )
341
-
342
-
343
- def test_non_code_verifier_agent_is_no_op(tmp_path: Path) -> None:
344
- repository_root = make_conflict_repository(tmp_path)
345
- write_working_tree_file(repository_root, "violator.py", VIOLATING_MODULE_SOURCE)
346
- payload = write_agent_payload("clean-coder", "do an audit", repository_root)
347
- result = run_hook(payload, repository_root)
348
- assert is_allow(result)
349
-
350
-
351
- def test_non_agent_tool_is_no_op(tmp_path: Path) -> None:
352
- repository_root = tmp_path / "repo"
353
- repository_root.mkdir()
354
- initialize_repository(repository_root)
355
- payload = json.dumps(
356
- {"tool_name": "Bash", "tool_input": {"command": "ls"}, "cwd": str(repository_root)}
357
- )
358
- result = run_hook(payload, repository_root)
359
- assert is_allow(result)
360
-
361
-
362
- def test_clean_surface_allows(tmp_path: Path) -> None:
363
- repository_root = tmp_path / "repo"
364
- repository_root.mkdir()
365
- initialize_repository(repository_root)
366
- run_git(repository_root, "checkout", "-b", "feature")
367
- write_working_tree_file(repository_root, "feature.py", CLEAN_MODULE_SOURCE)
368
- payload = write_agent_payload("code-verifier", "verify the change", repository_root)
369
- result = run_hook(payload, repository_root)
370
- assert is_allow(result)
371
-
372
-
373
- def test_real_conflict_denies_naming_files(tmp_path: Path) -> None:
374
- repository_root = make_conflict_repository(tmp_path)
375
- payload = write_agent_payload("code-verifier", "verify the change", repository_root)
376
- result = run_hook(payload, repository_root)
377
- assert not is_allow(result)
378
- reason = deny_reason(result)
379
- assert "shared.py" in reason
380
- assert "Merge conflicts vs" in reason
381
-
382
-
383
- def test_real_code_rules_violation_on_added_line_denies(tmp_path: Path) -> None:
384
- repository_root = tmp_path / "repo"
385
- repository_root.mkdir()
386
- initialize_repository(repository_root)
387
- run_git(repository_root, "checkout", "-b", "feature")
388
- commit_file(repository_root, "tracked.py", CLEAN_MODULE_SOURCE, "add tracked")
389
- write_working_tree_file(repository_root, "tracked.py", VIOLATING_MODULE_SOURCE)
390
- payload = write_agent_payload("code-verifier", "verify the change", repository_root)
391
- result = run_hook(payload, repository_root)
392
- assert not is_allow(result)
393
- reason = deny_reason(result)
394
- assert "tracked.py" in reason
395
- assert "Line " in reason
396
-
397
-
398
- def test_committed_on_branch_violation_with_clean_working_tree_denies(tmp_path: Path) -> None:
399
- repository_root = tmp_path / "repo"
400
- repository_root.mkdir()
401
- initialize_repository(repository_root)
402
- run_git(repository_root, "checkout", "-b", "feature")
403
- commit_file(repository_root, "committed.py", VIOLATING_MODULE_SOURCE, "commit violation")
404
- status_output = run_git(repository_root, "status", "--porcelain")
405
- assert status_output == "", "working tree must be clean so the deny comes from the committed line"
406
- payload = write_agent_payload("code-verifier", "verify the change", repository_root)
407
- result = run_hook(payload, repository_root)
408
- assert not is_allow(result)
409
- reason = deny_reason(result)
410
- assert "committed.py" in reason
411
- assert "Line " in reason
412
-
413
-
414
- def test_preexisting_violation_on_untouched_line_allows(tmp_path: Path) -> None:
415
- repository_root = tmp_path / "repo"
416
- repository_root.mkdir()
417
- initialize_repository(repository_root)
418
- carrier_sha = commit_file(
419
- repository_root, "carrier.py", PREEXISTING_VIOLATION_BASE_SOURCE, "add carrier"
420
- )
421
- run_git(repository_root, "update-ref", "refs/remotes/origin/main", carrier_sha)
422
- run_git(repository_root, "checkout", "-b", "feature")
423
- write_working_tree_file(repository_root, "carrier.py", PREEXISTING_VIOLATION_EDITED_SOURCE)
424
- payload = write_agent_payload("code-verifier", "verify the change", repository_root)
425
- result = run_hook(payload, repository_root)
426
- assert is_allow(result)
427
-
428
-
429
- def test_untracked_new_violating_file_denies(tmp_path: Path) -> None:
430
- repository_root = tmp_path / "repo"
431
- repository_root.mkdir()
432
- initialize_repository(repository_root)
433
- run_git(repository_root, "checkout", "-b", "feature")
434
- write_working_tree_file(repository_root, "fresh.py", VIOLATING_MODULE_SOURCE)
435
- payload = write_agent_payload("code-verifier", "verify the change", repository_root)
436
- result = run_hook(payload, repository_root)
437
- assert not is_allow(result)
438
- reason = deny_reason(result)
439
- assert "fresh.py" in reason
440
-
441
-
442
- def test_tooling_scratch_file_is_ignored(tmp_path: Path) -> None:
443
- repository_root = tmp_path / "repo"
444
- repository_root.mkdir()
445
- initialize_repository(repository_root)
446
- run_git(repository_root, "checkout", "-b", "feature")
447
- scratch_directory = repository_root / ".claude" / "verification"
448
- scratch_directory.mkdir(parents=True)
449
- (scratch_directory / "x.py").write_text(VIOLATING_MODULE_SOURCE, encoding="utf-8")
450
- payload = write_agent_payload("code-verifier", "verify the change", repository_root)
451
- result = run_hook(payload, repository_root)
452
- assert is_allow(result)
453
-
454
-
455
- def test_missing_base_ref_fails_open(tmp_path: Path) -> None:
456
- repository_root = tmp_path / "repo"
457
- repository_root.mkdir()
458
- run_git(repository_root, "init")
459
- run_git(repository_root, "config", "user.email", "tests@example.com")
460
- run_git(repository_root, "config", "user.name", "Preflight Tests")
461
- disable_native_git_hooks(repository_root)
462
- (repository_root / "base.py").write_text(CLEAN_MODULE_SOURCE, encoding="utf-8")
463
- run_git(repository_root, "add", "base.py")
464
- run_git(repository_root, "commit", "-m", "initial")
465
- payload = write_agent_payload("code-verifier", "verify the change", repository_root)
466
- result = run_hook(payload, repository_root)
467
- assert is_allow(result)
468
-
469
-
470
- def test_non_repo_cwd_fails_open(tmp_path: Path) -> None:
471
- non_repo_directory = tmp_path / "plain"
472
- non_repo_directory.mkdir()
473
- payload = write_agent_payload("code-verifier", "verify the change", non_repo_directory)
474
- result = run_hook(payload, non_repo_directory)
475
- assert is_allow(result)
476
-
477
-
478
- def test_behind_but_conflict_free_allows(tmp_path: Path) -> None:
479
- repository_root = tmp_path / "repo"
480
- repository_root.mkdir()
481
- initialize_repository(repository_root)
482
- base_sha = commit_file(repository_root, "shared.py", SHARED_BASE_SOURCE, "add shared")
483
- run_git(repository_root, "checkout", "-b", "feature")
484
- commit_file(repository_root, "shared.py", SHARED_FEATURE_SOURCE, "feature edit")
485
- advance_origin_main_divergent(repository_root, base_sha, "other.py", OTHER_DIVERGENT_SOURCE)
486
- payload = write_agent_payload("code-verifier", "verify the change", repository_root)
487
- result = run_hook(payload, repository_root)
488
- assert is_allow(result)
489
-
490
-
491
- def test_unreadable_changed_file_alone_fails_open(tmp_path: Path) -> None:
492
- repository_root = tmp_path / "repo"
493
- repository_root.mkdir()
494
- initialize_repository(repository_root)
495
- run_git(repository_root, "checkout", "-b", "feature")
496
- write_invalid_utf8_file(repository_root, "binary.py")
497
- payload = write_agent_payload("code-verifier", "verify the change", repository_root)
498
- result = run_hook(payload, repository_root)
499
- assert is_allow(result)
500
-
501
-
502
- def test_unreadable_changed_file_does_not_mask_real_violation(tmp_path: Path) -> None:
503
- repository_root = tmp_path / "repo"
504
- repository_root.mkdir()
505
- initialize_repository(repository_root)
506
- run_git(repository_root, "checkout", "-b", "feature")
507
- write_invalid_utf8_file(repository_root, "binary.py")
508
- write_working_tree_file(repository_root, "fresh.py", VIOLATING_MODULE_SOURCE)
509
- payload = write_agent_payload("code-verifier", "verify the change", repository_root)
510
- result = run_hook(payload, repository_root)
511
- assert not is_allow(result)
512
- reason = deny_reason(result)
513
- assert "fresh.py" in reason
514
-
515
-
516
- def test_conflict_and_violation_single_deny_names_both(tmp_path: Path) -> None:
517
- repository_root = make_conflict_repository(tmp_path)
518
- write_working_tree_file(repository_root, "violator.py", VIOLATING_MODULE_SOURCE)
519
- payload = write_agent_payload("code-verifier", "verify the change", repository_root)
520
- result = run_hook(payload, repository_root)
521
- assert not is_allow(result)
522
- reason = deny_reason(result)
523
- assert "Merge conflicts vs" in reason
524
- assert "shared.py" in reason
525
- assert "CODE_RULES violations on changed lines:" in reason
526
- assert "violator.py" in reason
527
-
528
-
529
- def test_stale_self_upstream_still_judges_default_branch(tmp_path: Path) -> None:
530
- repository_root = make_stale_self_upstream_conflict_repository(tmp_path)
531
- tracked_upstream = run_git(
532
- repository_root, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"
533
- )
534
- assert tracked_upstream == "origin/feature", "fixture must track the branch's own remote ref"
535
- payload = write_agent_payload("code-verifier", "verify the change", repository_root)
536
- result = run_hook(payload, repository_root)
537
- assert not is_allow(result)
538
- reason = deny_reason(result)
539
- assert "shared.py" in reason
540
- assert "Merge conflicts vs" in reason
541
-
542
-
543
- def test_staged_resolved_merge_allows(tmp_path: Path) -> None:
544
- repository_root = make_merge_in_progress_repository(tmp_path)
545
- start_conflicting_merge(repository_root)
546
- write_working_tree_file(repository_root, "shared.py", SHARED_FEATURE_SOURCE)
547
- run_git(repository_root, "add", "shared.py")
548
- remaining_unmerged = run_git(repository_root, "diff", "--name-only", "--diff-filter=U")
549
- assert remaining_unmerged == "", "the staged resolution must clear every unmerged index entry"
550
- merge_head_sha = run_git(repository_root, "rev-parse", "--verify", "--quiet", "MERGE_HEAD")
551
- assert merge_head_sha, "a merge must still be in progress so MERGE_HEAD is present"
552
- payload = write_agent_payload("code-verifier", "verify the change", repository_root)
553
- result = run_hook(payload, repository_root)
554
- assert is_allow(result)
555
-
556
-
557
- def test_unresolved_merge_keeps_denying(tmp_path: Path) -> None:
558
- repository_root = make_merge_in_progress_repository(tmp_path)
559
- start_conflicting_merge(repository_root)
560
- remaining_unmerged = run_git(repository_root, "diff", "--name-only", "--diff-filter=U")
561
- assert "shared.py" in remaining_unmerged, "the merge must leave shared.py unmerged"
562
- payload = write_agent_payload("code-verifier", "verify the change", repository_root)
563
- result = run_hook(payload, repository_root)
564
- assert not is_allow(result)
565
- reason = deny_reason(result)
566
- assert "shared.py" in reason
567
-
568
-
569
- def test_hook_imports_real_config_when_parent_holds_shadowing_config(
570
- tmp_path: Path,
571
- ) -> None:
572
- real_hooks_directory = HOOK_PATH.parent.parent
573
- real_package_directory = real_hooks_directory.parent
574
-
575
- staged_package_directory = tmp_path / "claude-dev-env"
576
- shutil.copytree(
577
- real_hooks_directory,
578
- staged_package_directory / "hooks",
579
- )
580
- shutil.copytree(
581
- real_package_directory / "_shared",
582
- staged_package_directory / "_shared",
583
- )
584
-
585
- shadowing_config_directory = staged_package_directory / "hooks" / "config"
586
- shadowing_config_directory.mkdir(parents=True, exist_ok=True)
587
- (shadowing_config_directory / "__init__.py").write_text("", encoding="utf-8")
588
- (shadowing_config_directory / "unrelated_constants.py").write_text(
589
- "UNRELATED_VALUE = 1\n", encoding="utf-8"
590
- )
591
-
592
- staged_hook = (
593
- staged_package_directory
594
- / "hooks"
595
- / "blocking"
596
- / "code_verifier_spawn_preflight_gate.py"
597
- )
598
- payload = write_agent_payload("general-purpose", "unrelated work", tmp_path)
599
- completed = subprocess.run(
600
- [sys.executable, str(staged_hook)],
601
- check=False,
602
- input=payload,
603
- capture_output=True,
604
- text=True,
605
- cwd=str(tmp_path),
606
- timeout=120,
607
- )
608
-
609
- assert "ModuleNotFoundError" not in completed.stderr
610
- assert completed.returncode == 0
611
-
612
-
613
- def test_task_tool_code_verifier_spawn_is_gated_like_agent(tmp_path: Path) -> None:
614
- repository_root = tmp_path / "repo"
615
- repository_root.mkdir()
616
- initialize_repository(repository_root)
617
- run_git(repository_root, "checkout", "-b", "feature")
618
- write_working_tree_file(repository_root, "fresh.py", VIOLATING_MODULE_SOURCE)
619
- task_payload = write_task_payload("code-verifier", "verify the change", repository_root)
620
- task_result = run_hook(task_payload, repository_root)
621
- assert not is_allow(task_result)
622
- task_reason = deny_reason(task_result)
623
- assert "fresh.py" in task_reason
624
- assert "CODE_RULES violations on changed lines:" in task_reason
625
- clean_coder_payload = write_task_payload("clean-coder", "write code", repository_root)
626
- clean_coder_result = run_hook(clean_coder_payload, repository_root)
627
- assert is_allow(clean_coder_result)
628
-
629
-
630
- def stage_package_and_feature_repo(tmp_path: Path) -> tuple[Path, Path]:
631
- """Copy the hook package and build a clean feature repo for load-failure tests.
632
-
633
- Args:
634
- tmp_path: The pytest temporary directory the staged copy lands under.
635
-
636
- Returns:
637
- A ``(staged_enforcer_path, repository_root)`` pair: the copied
638
- ``code_rules_enforcer.py`` a test renders unloadable, and the clean
639
- feature repository the staged hook runs against.
640
- """
641
- real_hooks_directory = HOOK_PATH.parent.parent
642
- real_package_directory = real_hooks_directory.parent
643
- staged_package_directory = tmp_path / "claude-dev-env"
644
- shutil.copytree(real_hooks_directory, staged_package_directory / "hooks")
645
- shutil.copytree(
646
- real_package_directory / "_shared", staged_package_directory / "_shared"
647
- )
648
- enforcer_path = staged_package_directory / "hooks" / "blocking" / "code_rules_enforcer.py"
649
- repository_root = tmp_path / "repo"
650
- repository_root.mkdir()
651
- initialize_repository(repository_root)
652
- run_git(repository_root, "checkout", "-b", "feature")
653
- write_working_tree_file(repository_root, "feature.py", CLEAN_MODULE_SOURCE)
654
- return enforcer_path, repository_root
655
-
656
-
657
- def run_staged_preflight_hook(
658
- enforcer_path: Path, repository_root: Path
659
- ) -> subprocess.CompletedProcess[str]:
660
- """Run the staged preflight hook that sits beside *enforcer_path*.
661
-
662
- Args:
663
- enforcer_path: The staged ``code_rules_enforcer.py`` path; the staged
664
- hook is its sibling in the same ``hooks/blocking`` directory.
665
- repository_root: The feature repository the hook inspects.
666
-
667
- Returns:
668
- The completed hook subprocess run.
669
- """
670
- staged_hook = enforcer_path.parent / "code_verifier_spawn_preflight_gate.py"
671
- payload = write_agent_payload("code-verifier", "verify the change", repository_root)
672
- return subprocess.run(
673
- [sys.executable, str(staged_hook)],
674
- check=False,
675
- input=payload,
676
- capture_output=True,
677
- text=True,
678
- cwd=str(repository_root),
679
- timeout=120,
680
- )
681
-
682
-
683
- def test_engine_load_failure_denies_with_named_reason(tmp_path: Path) -> None:
684
- enforcer_path, repository_root = stage_package_and_feature_repo(tmp_path)
685
- enforcer_path.unlink()
686
- completed = run_staged_preflight_hook(enforcer_path, repository_root)
687
- assert not is_allow(completed)
688
- reason = deny_reason(completed)
689
- assert "CODE_RULES engine failed to load" in reason
690
- assert "load_validate_content" in reason
691
-
692
-
693
- def test_engine_import_error_denies_with_named_reason(tmp_path: Path) -> None:
694
- enforcer_path, repository_root = stage_package_and_feature_repo(tmp_path)
695
- enforcer_path.write_text(BROKEN_ENFORCER_SOURCE, encoding="utf-8")
696
- completed = run_staged_preflight_hook(enforcer_path, repository_root)
697
- assert not is_allow(completed)
698
- reason = deny_reason(completed)
699
- assert "CODE_RULES engine failed to load" in reason
700
- assert "load_validate_content" in reason
701
-
702
-
703
- def test_engine_re_error_denies_with_named_reason(tmp_path: Path) -> None:
704
- enforcer_path, repository_root = stage_package_and_feature_repo(tmp_path)
705
- enforcer_path.write_text(RE_ERROR_ENFORCER_SOURCE, encoding="utf-8")
706
- completed = run_staged_preflight_hook(enforcer_path, repository_root)
707
- assert not is_allow(completed)
708
- reason = deny_reason(completed)
709
- assert "CODE_RULES engine failed to load" in reason
710
- assert "load_validate_content" in reason