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,313 +0,0 @@
1
- """Behavior tests for the proof-of-work comment audit and gh pr ready gate.
2
-
3
- Each test drives the public audit surface of pr_description_proof_of_work
4
- with real comment bodies. The single gh subprocess boundary is patched with
5
- a fake runner, so parsing, part detection, and gate decisions all run the
6
- production code paths.
7
- """
8
-
9
- import json
10
- import pathlib
11
- import sys
12
-
13
- import pytest
14
-
15
- _HOOK_DIR = pathlib.Path(__file__).parent
16
- _HOOKS_ROOT = _HOOK_DIR.parent
17
- if str(_HOOKS_ROOT) not in sys.path:
18
- sys.path.insert(0, str(_HOOKS_ROOT))
19
-
20
- from blocking import convergence_gate_blocker as convergence_module # noqa: E402
21
- from blocking import pr_description_proof_of_work as proof_module # noqa: E402
22
- from blocking.pr_description_proof_of_work import ( # noqa: E402
23
- audit_proof_comment_body,
24
- evaluate_pr_ready_gate,
25
- is_pr_ready_command,
26
- is_proof_shaped_body,
27
- )
28
-
29
- PASSING_PROOF_COMMENT_BODY = (
30
- "## Summary\n\n"
31
- "Recolored the launcher icons and checked every produced asset by hand "
32
- "against the palette the plan names.\n\n"
33
- "## Verification\n\n"
34
- "```\n"
35
- "C:\\Python313\\python.exe scripts/recolor.py --input themes/dawn\n"
36
- "```\n\n"
37
- "| Artifact | Measured |\n"
38
- "| --- | --- |\n"
39
- "| icons.zip | 48 files, 412,993 bytes |\n\n"
40
- "This advances phase 2 of issue #12.\n\n"
41
- "Known gap: the offline proof cannot show the on-device rendering; the "
42
- "store preview covers that.\n"
43
- )
44
-
45
- VISUAL_PROOF_COMMENT_BODY = (
46
- PASSING_PROOF_COMMENT_BODY + "\n![](https://placehold.co/20x20/AABBCC/AABBCC.png)\n"
47
- )
48
-
49
- BARE_PROOF_COMMENT_BODY = "## Verification\n\nLooks good overall to me.\n"
50
-
51
- ORDINARY_COMMENT_BODY = (
52
- "The retry logic here mirrors what the uploader does for chunked "
53
- "transfers, so the two stay in step."
54
- )
55
-
56
-
57
- def _install_fake_gh(monkeypatch, all_comment_bodies, all_diff_names, diff_text):
58
- all_comment_records = [{"body": each_body} for each_body in all_comment_bodies]
59
-
60
- def _fake_run_gh_command(all_gh_arguments):
61
- if all_gh_arguments and all_gh_arguments[0] == "api":
62
- return json.dumps([all_comment_records])
63
- if list(all_gh_arguments[:2]) == ["pr", "view"]:
64
- return json.dumps({"number": 77})
65
- if "--name-only" in all_gh_arguments:
66
- return "\n".join(all_diff_names)
67
- if list(all_gh_arguments[:2]) == ["pr", "diff"]:
68
- return diff_text
69
- return None
70
-
71
- monkeypatch.setattr(proof_module, "_run_gh_command", _fake_run_gh_command)
72
-
73
-
74
- def _install_recording_fake_gh(monkeypatch, all_comment_bodies, all_diff_names, diff_text):
75
- all_comment_records = [{"body": each_body} for each_body in all_comment_bodies]
76
- all_recorded_argument_lists: list[list[str]] = []
77
-
78
- def _fake_run_gh_command(all_gh_arguments):
79
- all_recorded_argument_lists.append(list(all_gh_arguments))
80
- if all_gh_arguments and all_gh_arguments[0] == "api":
81
- return json.dumps([all_comment_records])
82
- if list(all_gh_arguments[:2]) == ["pr", "view"]:
83
- return json.dumps({"number": 77})
84
- if "--name-only" in all_gh_arguments:
85
- return "\n".join(all_diff_names)
86
- if list(all_gh_arguments[:2]) == ["pr", "diff"]:
87
- return diff_text
88
- return None
89
-
90
- monkeypatch.setattr(proof_module, "_run_gh_command", _fake_run_gh_command)
91
- return all_recorded_argument_lists
92
-
93
-
94
- def _argument_lists_with_leading(all_recorded_argument_lists, first_token, second_token):
95
- return [
96
- each_arguments
97
- for each_arguments in all_recorded_argument_lists
98
- if list(each_arguments[:2]) == [first_token, second_token]
99
- ]
100
-
101
-
102
- def test_is_proof_shaped_body_detects_verification_heading() -> None:
103
- assert is_proof_shaped_body(PASSING_PROOF_COMMENT_BODY)
104
-
105
-
106
- def test_is_proof_shaped_body_skips_ordinary_comment() -> None:
107
- assert not is_proof_shaped_body(ORDINARY_COMMENT_BODY)
108
-
109
-
110
- def test_is_pr_ready_command_matches_ready() -> None:
111
- assert is_pr_ready_command("gh pr ready 123")
112
-
113
-
114
- def test_is_pr_ready_command_skips_undo() -> None:
115
- assert not is_pr_ready_command("gh pr ready 123 --undo")
116
-
117
-
118
- def test_is_pr_ready_command_matches_after_shell_operator() -> None:
119
- assert is_pr_ready_command("git fetch && gh pr ready 5")
120
-
121
-
122
- def test_is_pr_ready_command_ignores_phrase_inside_commit_message() -> None:
123
- assert not is_pr_ready_command(
124
- 'git commit -m "block gh pr ready without a proof comment"'
125
- )
126
-
127
-
128
- def test_is_pr_ready_command_ignores_phrase_inside_search_argument() -> None:
129
- assert not is_pr_ready_command('grep -r "gh pr ready" packages/')
130
-
131
-
132
- def test_audit_passes_complete_proof_on_non_visual_change(monkeypatch: pytest.MonkeyPatch) -> None:
133
- _install_fake_gh(monkeypatch, [], ["src/parser.py"], "+ plain code line")
134
- assert audit_proof_comment_body(PASSING_PROOF_COMMENT_BODY, 123) == []
135
-
136
-
137
- def test_audit_flags_missing_visual_on_image_diff(monkeypatch: pytest.MonkeyPatch) -> None:
138
- _install_fake_gh(monkeypatch, [], ["assets/icon.png"], "")
139
- all_missing_parts = audit_proof_comment_body(PASSING_PROOF_COMMENT_BODY, 123)
140
- assert any("visual" in each_part for each_part in all_missing_parts)
141
-
142
-
143
- def test_audit_accepts_image_embed_on_image_diff(monkeypatch: pytest.MonkeyPatch) -> None:
144
- _install_fake_gh(monkeypatch, [], ["assets/icon.png"], "")
145
- assert audit_proof_comment_body(VISUAL_PROOF_COMMENT_BODY, 123) == []
146
-
147
-
148
- def test_audit_detects_visual_change_from_hex_colors(monkeypatch: pytest.MonkeyPatch) -> None:
149
- _install_fake_gh(monkeypatch, [], ["src/theme.py"], '+ accent = "#AABBCC"')
150
- all_missing_parts = audit_proof_comment_body(PASSING_PROOF_COMMENT_BODY, 123)
151
- assert any("visual" in each_part for each_part in all_missing_parts)
152
-
153
-
154
- def test_audit_names_every_missing_part_of_bare_proof(monkeypatch: pytest.MonkeyPatch) -> None:
155
- _install_fake_gh(monkeypatch, [], ["src/parser.py"], "")
156
- all_missing_parts = audit_proof_comment_body(BARE_PROOF_COMMENT_BODY, 123)
157
- assert len(all_missing_parts) == 4
158
-
159
-
160
- def test_ready_gate_blocks_when_no_proof_comment_exists(monkeypatch: pytest.MonkeyPatch) -> None:
161
- _install_fake_gh(monkeypatch, [ORDINARY_COMMENT_BODY], ["src/parser.py"], "")
162
- denial_reason = evaluate_pr_ready_gate("gh pr ready 123")
163
- assert denial_reason is not None
164
- assert "proof" in denial_reason.lower()
165
-
166
-
167
- def test_ready_gate_allows_with_passing_proof_comment(monkeypatch: pytest.MonkeyPatch) -> None:
168
- _install_fake_gh(monkeypatch, [PASSING_PROOF_COMMENT_BODY], ["src/parser.py"], "")
169
- assert evaluate_pr_ready_gate("gh pr ready 123") is None
170
-
171
-
172
- def test_ready_gate_blocks_when_proof_comment_is_incomplete(
173
- monkeypatch: pytest.MonkeyPatch,
174
- ) -> None:
175
- _install_fake_gh(monkeypatch, [BARE_PROOF_COMMENT_BODY], ["src/parser.py"], "")
176
- denial_reason = evaluate_pr_ready_gate("gh pr ready 123")
177
- assert denial_reason is not None
178
- assert "123" in denial_reason
179
- assert "proof" in denial_reason.lower()
180
-
181
-
182
- def test_ready_gate_fails_open_when_gh_is_unavailable(monkeypatch: pytest.MonkeyPatch) -> None:
183
- monkeypatch.setattr(proof_module, "_run_gh_command", lambda all_gh_arguments: None)
184
- assert evaluate_pr_ready_gate("gh pr ready 123") is None
185
-
186
-
187
- def test_ready_gate_resolves_pr_number_from_current_branch(
188
- monkeypatch: pytest.MonkeyPatch,
189
- ) -> None:
190
- _install_fake_gh(monkeypatch, [PASSING_PROOF_COMMENT_BODY], ["src/parser.py"], "")
191
- monkeypatch.setattr(
192
- convergence_module,
193
- "_resolve_current_branch_pr_number",
194
- lambda all_target_repo, cwd: 77,
195
- )
196
- assert evaluate_pr_ready_gate("gh pr ready") is None
197
-
198
-
199
- def test_ready_gate_reads_comments_from_repo_named_by_flag(
200
- monkeypatch: pytest.MonkeyPatch,
201
- ) -> None:
202
- all_recorded = _install_recording_fake_gh(
203
- monkeypatch, [PASSING_PROOF_COMMENT_BODY], ["src/parser.py"], ""
204
- )
205
- evaluate_pr_ready_gate("gh pr ready 42 --repo other-owner/other-repo")
206
- all_api_calls = _argument_lists_with_leading(
207
- all_recorded, "api", "repos/other-owner/other-repo/issues/42/comments"
208
- )
209
- assert all_api_calls
210
- for each_arguments in all_api_calls:
211
- assert "{owner}" not in each_arguments[1]
212
- assert "{repo}" not in each_arguments[1]
213
-
214
-
215
- def test_ready_gate_resolves_pr_number_from_repo_named_by_flag(
216
- monkeypatch: pytest.MonkeyPatch,
217
- ) -> None:
218
- _install_fake_gh(monkeypatch, [PASSING_PROOF_COMMENT_BODY], ["src/parser.py"], "")
219
- all_captured_repo_pairs: list[tuple[str, str] | None] = []
220
-
221
- def fake_resolver(all_target_repo: tuple[str, str] | None, cwd: str | None) -> int:
222
- all_captured_repo_pairs.append(all_target_repo)
223
- return 77
224
-
225
- monkeypatch.setattr(convergence_module, "_resolve_current_branch_pr_number", fake_resolver)
226
- evaluate_pr_ready_gate("gh pr ready --repo other-owner/other-repo")
227
- assert all_captured_repo_pairs == [("other-owner", "other-repo")]
228
-
229
-
230
- def test_ready_gate_reads_comments_from_repo_named_by_url(
231
- monkeypatch: pytest.MonkeyPatch,
232
- ) -> None:
233
- all_recorded = _install_recording_fake_gh(
234
- monkeypatch, [PASSING_PROOF_COMMENT_BODY], ["src/parser.py"], ""
235
- )
236
- evaluate_pr_ready_gate("gh pr ready https://github.com/url-owner/url-repo/pull/99")
237
- all_api_calls = _argument_lists_with_leading(
238
- all_recorded, "api", "repos/url-owner/url-repo/issues/99/comments"
239
- )
240
- assert all_api_calls
241
- all_view_calls = _argument_lists_with_leading(all_recorded, "pr", "view")
242
- assert not all_view_calls
243
-
244
-
245
- def test_ready_gate_leaves_bare_command_bound_to_current_directory(
246
- monkeypatch: pytest.MonkeyPatch,
247
- ) -> None:
248
- all_recorded = _install_recording_fake_gh(
249
- monkeypatch, [PASSING_PROOF_COMMENT_BODY], ["src/parser.py"], ""
250
- )
251
- evaluate_pr_ready_gate("gh pr ready 123")
252
- all_api_calls = _argument_lists_with_leading(
253
- all_recorded, "api", "repos/{owner}/{repo}/issues/123/comments"
254
- )
255
- assert all_api_calls
256
- for each_arguments in all_recorded:
257
- assert "--repo" not in each_arguments
258
-
259
-
260
- def test_ready_gate_ignores_repo_flag_of_chained_command(
261
- monkeypatch: pytest.MonkeyPatch,
262
- ) -> None:
263
- all_recorded = _install_recording_fake_gh(
264
- monkeypatch, [PASSING_PROOF_COMMENT_BODY], ["src/parser.py"], ""
265
- )
266
- evaluate_pr_ready_gate(
267
- "gh pr ready 161 && gh pr comment 999 --repo other-owner/other-repo"
268
- )
269
- all_api_calls = _argument_lists_with_leading(
270
- all_recorded, "api", "repos/{owner}/{repo}/issues/161/comments"
271
- )
272
- assert all_api_calls
273
- for each_arguments in all_recorded:
274
- assert "other-owner/other-repo" not in each_arguments
275
-
276
-
277
- def test_ready_gate_reads_comments_from_repo_named_by_full_url_flag(
278
- monkeypatch: pytest.MonkeyPatch,
279
- ) -> None:
280
- all_recorded = _install_recording_fake_gh(
281
- monkeypatch, [PASSING_PROOF_COMMENT_BODY], ["src/parser.py"], ""
282
- )
283
- evaluate_pr_ready_gate("gh pr ready 42 --repo https://github.com/other-owner/other-repo")
284
- all_api_calls = _argument_lists_with_leading(
285
- all_recorded, "api", "repos/other-owner/other-repo/issues/42/comments"
286
- )
287
- assert all_api_calls
288
-
289
-
290
- def test_ready_gate_reads_comments_from_repo_named_by_short_url_flag(
291
- monkeypatch: pytest.MonkeyPatch,
292
- ) -> None:
293
- all_recorded = _install_recording_fake_gh(
294
- monkeypatch, [PASSING_PROOF_COMMENT_BODY], ["src/parser.py"], ""
295
- )
296
- evaluate_pr_ready_gate("gh pr ready 42 -R https://github.com/other-owner/other-repo")
297
- all_api_calls = _argument_lists_with_leading(
298
- all_recorded, "api", "repos/other-owner/other-repo/issues/42/comments"
299
- )
300
- assert all_api_calls
301
-
302
-
303
- def test_ready_gate_reads_comments_from_repo_named_by_ssh_flag(
304
- monkeypatch: pytest.MonkeyPatch,
305
- ) -> None:
306
- all_recorded = _install_recording_fake_gh(
307
- monkeypatch, [PASSING_PROOF_COMMENT_BODY], ["src/parser.py"], ""
308
- )
309
- evaluate_pr_ready_gate("gh pr ready 42 --repo git@github.com:other-owner/other-repo")
310
- all_api_calls = _argument_lists_with_leading(
311
- all_recorded, "api", "repos/other-owner/other-repo/issues/42/comments"
312
- )
313
- assert all_api_calls
@@ -1,230 +0,0 @@
1
- """Unit tests for reviewer_spawn_gate PreToolUse hook."""
2
-
3
- import importlib.util
4
- import io
5
- import json
6
- import pathlib
7
- import sys
8
- from contextlib import redirect_stderr, redirect_stdout
9
-
10
- import pytest
11
-
12
- _HOOK_DIR = pathlib.Path(__file__).parent
13
- if str(_HOOK_DIR) not in sys.path:
14
- sys.path.insert(0, str(_HOOK_DIR))
15
-
16
- hook_spec = importlib.util.spec_from_file_location(
17
- "reviewer_spawn_gate",
18
- _HOOK_DIR / "reviewer_spawn_gate.py",
19
- )
20
- assert hook_spec is not None
21
- assert hook_spec.loader is not None
22
- hook_module = importlib.util.module_from_spec(hook_spec)
23
- hook_spec.loader.exec_module(hook_module)
24
-
25
- _matches_copilot_trigger = hook_module._matches_copilot_trigger
26
- _matches_bugbot_trigger = hook_module._matches_bugbot_trigger
27
- _reviewer_trigger = hook_module._reviewer_trigger
28
-
29
- _SENTINEL_MARKER = hook_module.GATE_SENTINEL_MARKER
30
- _AVAILABILITY_SCRIPT_PATH_ENV_VAR_NAME = hook_module.AVAILABILITY_SCRIPT_PATH_ENV_VAR_NAME
31
-
32
- _COPILOT_TRIGGER_COMMAND = (
33
- f"{_SENTINEL_MARKER} gh api repos/o/r/pulls/1/requested_reviewers "
34
- f'-f "reviewers[]=copilot-pull-request-reviewer[bot]"'
35
- )
36
- _BUGBOT_TRIGGER_COMMAND = f'{_SENTINEL_MARKER} python post_fix_reply.py --body "bugbot run"'
37
-
38
-
39
- def _write_fake_availability_script(tmp_path: pathlib.Path, exit_code: int) -> pathlib.Path:
40
- fake_script_path = tmp_path / "fake_reviewer_availability.py"
41
- fake_script_path.write_text(f"import sys\nsys.exit({exit_code})\n", encoding="utf-8")
42
- return fake_script_path
43
-
44
-
45
- def _run_hook_with_stdin_text(stdin_text: str) -> tuple[str, str, int]:
46
- captured_stdout = io.StringIO()
47
- captured_stderr = io.StringIO()
48
- exit_code = 0
49
- sys.stdin = io.StringIO(stdin_text)
50
- try:
51
- with redirect_stdout(captured_stdout), redirect_stderr(captured_stderr):
52
- try:
53
- hook_module.main()
54
- except SystemExit as exit_signal:
55
- exit_code = exit_signal.code if isinstance(exit_signal.code, int) else 0
56
- finally:
57
- sys.stdin = sys.__stdin__
58
- return captured_stdout.getvalue(), captured_stderr.getvalue(), exit_code
59
-
60
-
61
- def _run_hook(hook_input: dict) -> tuple[str, int]:
62
- stdout_text, _stderr_text, exit_code = _run_hook_with_stdin_text(json.dumps(hook_input))
63
- return stdout_text, exit_code
64
-
65
-
66
- def test_matches_copilot_trigger_true_for_request_reviewers_command() -> None:
67
- assert _matches_copilot_trigger(_COPILOT_TRIGGER_COMMAND)
68
-
69
-
70
- def test_matches_copilot_trigger_false_for_unrelated_command() -> None:
71
- assert not _matches_copilot_trigger("gh pr list")
72
-
73
-
74
- def test_matches_bugbot_trigger_true_for_rerun_comment_command() -> None:
75
- assert _matches_bugbot_trigger(_BUGBOT_TRIGGER_COMMAND)
76
-
77
-
78
- def test_matches_bugbot_trigger_false_for_unrelated_command() -> None:
79
- assert not _matches_bugbot_trigger("python post_fix_reply.py --body 'looks good'")
80
-
81
-
82
- def test_reviewer_trigger_returns_none_for_unrecognized_command() -> None:
83
- assert _reviewer_trigger("gh pr list") is None
84
-
85
-
86
- def test_main_allows_copilot_shaped_command_with_no_sentinel_marker(
87
- monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
88
- ) -> None:
89
- fake_script_path = _write_fake_availability_script(tmp_path, exit_code=3)
90
- monkeypatch.setenv(_AVAILABILITY_SCRIPT_PATH_ENV_VAR_NAME, str(fake_script_path))
91
- stdout_text, exit_code = _run_hook(
92
- {
93
- "tool_name": "Bash",
94
- "tool_input": {
95
- "command": (
96
- "gh api repos/o/r/pulls/1/requested_reviewers "
97
- '-f "reviewers[]=copilot-pull-request-reviewer[bot]"'
98
- )
99
- },
100
- }
101
- )
102
- assert exit_code == 0
103
- assert stdout_text == ""
104
-
105
-
106
- def test_main_allows_copilot_trigger_when_available(
107
- monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
108
- ) -> None:
109
- fake_script_path = _write_fake_availability_script(tmp_path, exit_code=0)
110
- monkeypatch.setenv(_AVAILABILITY_SCRIPT_PATH_ENV_VAR_NAME, str(fake_script_path))
111
- stdout_text, exit_code = _run_hook(
112
- {"tool_name": "Bash", "tool_input": {"command": _COPILOT_TRIGGER_COMMAND}}
113
- )
114
- assert exit_code == 0
115
- assert stdout_text == ""
116
-
117
-
118
- def test_main_denies_copilot_trigger_when_down(
119
- monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
120
- ) -> None:
121
- fake_script_path = _write_fake_availability_script(tmp_path, exit_code=3)
122
- monkeypatch.setenv(_AVAILABILITY_SCRIPT_PATH_ENV_VAR_NAME, str(fake_script_path))
123
- stdout_text, exit_code = _run_hook(
124
- {"tool_name": "Bash", "tool_input": {"command": _COPILOT_TRIGGER_COMMAND}}
125
- )
126
- assert exit_code == 0
127
- response_payload = json.loads(stdout_text)
128
- decision_block = response_payload["hookSpecificOutput"]
129
- assert decision_block["permissionDecision"] == "deny"
130
- assert "GitHub Copilot" in decision_block["permissionDecisionReason"]
131
-
132
-
133
- def test_main_allows_copilot_trigger_on_unexpected_exit_code(
134
- monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
135
- ) -> None:
136
- fake_script_path = _write_fake_availability_script(tmp_path, exit_code=2)
137
- monkeypatch.setenv(_AVAILABILITY_SCRIPT_PATH_ENV_VAR_NAME, str(fake_script_path))
138
- stdout_text, exit_code = _run_hook(
139
- {"tool_name": "Bash", "tool_input": {"command": _COPILOT_TRIGGER_COMMAND}}
140
- )
141
- assert exit_code == 0
142
- assert stdout_text == ""
143
-
144
-
145
- def test_main_allows_copilot_trigger_when_availability_script_crashes(
146
- monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
147
- ) -> None:
148
- fake_script_path = tmp_path / "fake_reviewer_availability.py"
149
- fake_script_path.write_text(
150
- "import module_that_does_not_exist_anywhere\n", encoding="utf-8"
151
- )
152
- monkeypatch.setenv(_AVAILABILITY_SCRIPT_PATH_ENV_VAR_NAME, str(fake_script_path))
153
- stdout_text, exit_code = _run_hook(
154
- {"tool_name": "Bash", "tool_input": {"command": _COPILOT_TRIGGER_COMMAND}}
155
- )
156
- assert exit_code == 0
157
- assert stdout_text == ""
158
-
159
-
160
- def test_main_allows_bugbot_trigger_when_available(
161
- monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
162
- ) -> None:
163
- fake_script_path = _write_fake_availability_script(tmp_path, exit_code=0)
164
- monkeypatch.setenv(_AVAILABILITY_SCRIPT_PATH_ENV_VAR_NAME, str(fake_script_path))
165
- stdout_text, exit_code = _run_hook(
166
- {"tool_name": "Bash", "tool_input": {"command": _BUGBOT_TRIGGER_COMMAND}}
167
- )
168
- assert exit_code == 0
169
- assert stdout_text == ""
170
-
171
-
172
- def test_main_denies_bugbot_trigger_when_opted_out(
173
- monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
174
- ) -> None:
175
- fake_script_path = _write_fake_availability_script(tmp_path, exit_code=3)
176
- monkeypatch.setenv(_AVAILABILITY_SCRIPT_PATH_ENV_VAR_NAME, str(fake_script_path))
177
- stdout_text, exit_code = _run_hook(
178
- {"tool_name": "Bash", "tool_input": {"command": _BUGBOT_TRIGGER_COMMAND}}
179
- )
180
- assert exit_code == 0
181
- response_payload = json.loads(stdout_text)
182
- decision_block = response_payload["hookSpecificOutput"]
183
- assert decision_block["permissionDecision"] == "deny"
184
- assert "Cursor Bugbot" in decision_block["permissionDecisionReason"]
185
-
186
-
187
- def test_main_allows_marker_command_with_no_recognized_trigger(
188
- monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
189
- ) -> None:
190
- fake_script_path = _write_fake_availability_script(tmp_path, exit_code=3)
191
- monkeypatch.setenv(_AVAILABILITY_SCRIPT_PATH_ENV_VAR_NAME, str(fake_script_path))
192
- stdout_text, exit_code = _run_hook(
193
- {"tool_name": "Bash", "tool_input": {"command": f"{_SENTINEL_MARKER} gh pr list"}}
194
- )
195
- assert exit_code == 0
196
- assert stdout_text == ""
197
-
198
-
199
- def test_main_allows_when_availability_script_missing(
200
- monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
201
- ) -> None:
202
- missing_script_path = tmp_path / "does_not_exist.py"
203
- monkeypatch.setenv(_AVAILABILITY_SCRIPT_PATH_ENV_VAR_NAME, str(missing_script_path))
204
- stdout_text, exit_code = _run_hook(
205
- {"tool_name": "Bash", "tool_input": {"command": _COPILOT_TRIGGER_COMMAND}}
206
- )
207
- assert exit_code == 0
208
- assert stdout_text == ""
209
-
210
-
211
- def test_main_allows_non_bash_tool() -> None:
212
- stdout_text, exit_code = _run_hook(
213
- {"tool_name": "Write", "tool_input": {"content": _COPILOT_TRIGGER_COMMAND}}
214
- )
215
- assert exit_code == 0
216
- assert stdout_text == ""
217
-
218
-
219
- def test_main_with_empty_stdin_exits_silently() -> None:
220
- stdout_text, stderr_text, exit_code = _run_hook_with_stdin_text("")
221
- assert exit_code == 0
222
- assert stdout_text == ""
223
- assert stderr_text == ""
224
-
225
-
226
- def test_main_with_invalid_json_stdin_exits_silently() -> None:
227
- stdout_text, stderr_text, exit_code = _run_hook_with_stdin_text("{broken")
228
- assert exit_code == 0
229
- assert stdout_text == ""
230
- assert stderr_text == ""