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,311 +0,0 @@
1
- """Unit tests for the pr_converge_bugteam_enforcer PreToolUse hook.
2
-
3
- Covers the Step 5 BUGTEAM contract: Agent({subagent_type: "clean-coder"})
4
- calls that look like audit substitutes are blocked when
5
- $CLAUDE_JOB_DIR/pr-converge-state.json (named by PR_CONVERGE_STATE_FILENAME)
6
- shows phase=BUGTEAM and the formal Skill({skill: "bugteam"}) has not
7
- registered at the current HEAD and tick. qbug is explicitly NOT a substitute.
8
- """
9
-
10
- from __future__ import annotations
11
-
12
- import importlib.util
13
- import io
14
- import json
15
- import pathlib
16
- import sys
17
- from typing import Any
18
- from unittest import mock
19
-
20
- import pytest
21
-
22
- _HOOK_DIR = pathlib.Path(__file__).parent
23
- _HOOKS_TREE = _HOOK_DIR.parent
24
- for each_path in (str(_HOOK_DIR), str(_HOOKS_TREE)):
25
- if each_path not in sys.path:
26
- sys.path.insert(0, each_path)
27
-
28
- hook_spec = importlib.util.spec_from_file_location(
29
- "pr_converge_bugteam_enforcer",
30
- _HOOK_DIR / "pr_converge_bugteam_enforcer.py",
31
- )
32
- assert hook_spec is not None
33
- assert hook_spec.loader is not None
34
- hook_module = importlib.util.module_from_spec(hook_spec)
35
- hook_spec.loader.exec_module(hook_module)
36
-
37
- from hooks_constants.pr_converge_bugteam_enforcer_constants import (
38
- BUGTEAM_PHASE,
39
- CLAUDE_JOB_DIR_ENV_VAR,
40
- PR_CONVERGE_STATE_FILENAME,
41
- )
42
-
43
- _HEAD_SHA_CURRENT = "abc123def456abc123def456abc123def456abcd"
44
- _HEAD_SHA_STALE = "deadbeef0000deadbeef0000deadbeef0000dead"
45
- _TICK_CURRENT = 7
46
- _AUDIT_PROMPT = "Run the bugteam audit and report findings against the A-J categories."
47
- _FIX_ONLY_PROMPT = "Fix the failing test in tests/test_widget.py by adding a guard clause."
48
-
49
-
50
- def _write_state(state_directory: pathlib.Path, state: dict[str, Any]) -> pathlib.Path:
51
- state_path = state_directory / PR_CONVERGE_STATE_FILENAME
52
- state_path.write_text(json.dumps(state), encoding="utf-8")
53
- return state_path
54
-
55
-
56
- def _bugteam_phase_state(**overrides: Any) -> dict[str, Any]:
57
- baseline_state: dict[str, Any] = {
58
- "phase": BUGTEAM_PHASE,
59
- "current_head": _HEAD_SHA_CURRENT,
60
- "tick_count": _TICK_CURRENT,
61
- "bugteam_skill_invoked_at_head": None,
62
- "bugteam_skill_invoked_at_tick": None,
63
- }
64
- baseline_state.update(overrides)
65
- return baseline_state
66
-
67
-
68
- def _clean_coder_audit_payload(prompt: str = _AUDIT_PROMPT) -> dict[str, Any]:
69
- return {
70
- "tool_name": "Agent",
71
- "tool_input": {
72
- "subagent_type": "clean-coder",
73
- "prompt": prompt,
74
- "description": "Audit the PR",
75
- },
76
- }
77
-
78
-
79
- def _run_main_with_io(input_text: str) -> str:
80
- with mock.patch("sys.stdin", io.StringIO(input_text)):
81
- with mock.patch("sys.stdout", new_callable=io.StringIO) as captured_stdout:
82
- try:
83
- hook_module.main()
84
- except SystemExit:
85
- pass
86
- return captured_stdout.getvalue()
87
-
88
-
89
- @pytest.fixture()
90
- def claude_job_directory(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path:
91
- monkeypatch.setenv(CLAUDE_JOB_DIR_ENV_VAR, str(tmp_path))
92
- return tmp_path
93
-
94
-
95
- def test_should_allow_when_state_file_absent(claude_job_directory: pathlib.Path) -> None:
96
- payload_text = json.dumps(_clean_coder_audit_payload())
97
- captured_output = _run_main_with_io(payload_text)
98
- assert captured_output == ""
99
-
100
-
101
- def test_should_allow_when_phase_not_bugteam(claude_job_directory: pathlib.Path) -> None:
102
- _write_state(claude_job_directory, _bugteam_phase_state(phase="BUGBOT"))
103
- payload_text = json.dumps(_clean_coder_audit_payload())
104
- captured_output = _run_main_with_io(payload_text)
105
- assert captured_output == ""
106
-
107
-
108
- def test_should_allow_when_subagent_type_not_clean_coder(
109
- claude_job_directory: pathlib.Path,
110
- ) -> None:
111
- _write_state(claude_job_directory, _bugteam_phase_state())
112
- explore_payload: dict[str, Any] = {
113
- "tool_name": "Agent",
114
- "tool_input": {"subagent_type": "Explore", "prompt": _AUDIT_PROMPT},
115
- }
116
- captured_output = _run_main_with_io(json.dumps(explore_payload))
117
- assert captured_output == ""
118
-
119
-
120
- def test_should_allow_when_prompt_lacks_audit_keywords(
121
- claude_job_directory: pathlib.Path,
122
- ) -> None:
123
- _write_state(claude_job_directory, _bugteam_phase_state())
124
- payload_text = json.dumps(_clean_coder_audit_payload(prompt=_FIX_ONLY_PROMPT))
125
- captured_output = _run_main_with_io(payload_text)
126
- assert captured_output == ""
127
-
128
-
129
- def test_should_block_when_clean_coder_audit_without_bugteam_skill_invocation(
130
- claude_job_directory: pathlib.Path,
131
- ) -> None:
132
- _write_state(claude_job_directory, _bugteam_phase_state())
133
- captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
134
- deny_payload = json.loads(captured_output)
135
- assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
136
- assert "bugteam-enforcer" in deny_payload["hookSpecificOutput"]["permissionDecisionReason"]
137
-
138
-
139
- def test_should_allow_after_bugteam_skill_invoked_at_current_head_and_tick(
140
- claude_job_directory: pathlib.Path,
141
- ) -> None:
142
- _write_state(
143
- claude_job_directory,
144
- _bugteam_phase_state(
145
- bugteam_skill_invoked_at_head=_HEAD_SHA_CURRENT,
146
- bugteam_skill_invoked_at_tick=_TICK_CURRENT,
147
- ),
148
- )
149
- captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
150
- assert captured_output == ""
151
-
152
-
153
- def test_should_block_when_bugteam_skill_invocation_is_stale_head(
154
- claude_job_directory: pathlib.Path,
155
- ) -> None:
156
- _write_state(
157
- claude_job_directory,
158
- _bugteam_phase_state(
159
- bugteam_skill_invoked_at_head=_HEAD_SHA_STALE,
160
- bugteam_skill_invoked_at_tick=_TICK_CURRENT,
161
- ),
162
- )
163
- captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
164
- deny_payload = json.loads(captured_output)
165
- assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
166
-
167
-
168
- def test_should_block_when_qbug_was_invoked_but_not_bugteam(
169
- claude_job_directory: pathlib.Path,
170
- ) -> None:
171
- _write_state(
172
- claude_job_directory,
173
- _bugteam_phase_state(
174
- bugteam_skill_invoked_at_head=None,
175
- bugteam_skill_invoked_at_tick=None,
176
- ),
177
- )
178
- captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
179
- deny_payload = json.loads(captured_output)
180
- assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
181
-
182
-
183
- def test_should_block_when_bugteam_invoked_at_current_head_but_previous_tick(
184
- claude_job_directory: pathlib.Path,
185
- ) -> None:
186
- _write_state(
187
- claude_job_directory,
188
- _bugteam_phase_state(
189
- bugteam_skill_invoked_at_head=_HEAD_SHA_CURRENT,
190
- bugteam_skill_invoked_at_tick=_TICK_CURRENT - 1,
191
- ),
192
- )
193
- captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
194
- deny_payload = json.loads(captured_output)
195
- assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
196
-
197
-
198
- def test_should_block_when_invoked_head_is_non_string_type(
199
- claude_job_directory: pathlib.Path,
200
- ) -> None:
201
- _write_state(
202
- claude_job_directory,
203
- _bugteam_phase_state(
204
- bugteam_skill_invoked_at_head=42,
205
- bugteam_skill_invoked_at_tick=_TICK_CURRENT,
206
- ),
207
- )
208
- captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
209
- deny_payload = json.loads(captured_output)
210
- assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
211
-
212
-
213
- def test_should_block_when_current_head_is_non_string_type(
214
- claude_job_directory: pathlib.Path,
215
- ) -> None:
216
- _write_state(
217
- claude_job_directory,
218
- _bugteam_phase_state(
219
- current_head=42,
220
- bugteam_skill_invoked_at_head=_HEAD_SHA_CURRENT,
221
- bugteam_skill_invoked_at_tick=_TICK_CURRENT,
222
- ),
223
- )
224
- captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
225
- deny_payload = json.loads(captured_output)
226
- assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
227
-
228
-
229
- def test_should_block_when_invoked_tick_is_string_type(
230
- claude_job_directory: pathlib.Path,
231
- ) -> None:
232
- _write_state(
233
- claude_job_directory,
234
- _bugteam_phase_state(
235
- bugteam_skill_invoked_at_head=_HEAD_SHA_CURRENT,
236
- bugteam_skill_invoked_at_tick="7",
237
- ),
238
- )
239
- captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
240
- deny_payload = json.loads(captured_output)
241
- assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
242
-
243
-
244
- def test_should_block_when_current_tick_is_string_type(
245
- claude_job_directory: pathlib.Path,
246
- ) -> None:
247
- _write_state(
248
- claude_job_directory,
249
- _bugteam_phase_state(
250
- tick_count="7",
251
- bugteam_skill_invoked_at_head=_HEAD_SHA_CURRENT,
252
- bugteam_skill_invoked_at_tick=_TICK_CURRENT,
253
- ),
254
- )
255
- captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
256
- deny_payload = json.loads(captured_output)
257
- assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
258
-
259
-
260
- def test_should_block_when_invoked_tick_is_bool_type(
261
- claude_job_directory: pathlib.Path,
262
- ) -> None:
263
- _write_state(
264
- claude_job_directory,
265
- _bugteam_phase_state(
266
- tick_count=1,
267
- bugteam_skill_invoked_at_head=_HEAD_SHA_CURRENT,
268
- bugteam_skill_invoked_at_tick=True,
269
- ),
270
- )
271
- captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
272
- deny_payload = json.loads(captured_output)
273
- assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
274
-
275
-
276
- def test_should_block_when_current_tick_is_bool_type(
277
- claude_job_directory: pathlib.Path,
278
- ) -> None:
279
- _write_state(
280
- claude_job_directory,
281
- _bugteam_phase_state(
282
- tick_count=True,
283
- bugteam_skill_invoked_at_head=_HEAD_SHA_CURRENT,
284
- bugteam_skill_invoked_at_tick=1,
285
- ),
286
- )
287
- captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
288
- deny_payload = json.loads(captured_output)
289
- assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
290
-
291
-
292
- def test_should_allow_when_claude_job_dir_env_var_absent(
293
- monkeypatch: pytest.MonkeyPatch,
294
- ) -> None:
295
- monkeypatch.delenv(CLAUDE_JOB_DIR_ENV_VAR, raising=False)
296
- captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
297
- assert captured_output == ""
298
-
299
-
300
- def test_should_allow_when_state_json_is_malformed(
301
- claude_job_directory: pathlib.Path,
302
- ) -> None:
303
- state_path = claude_job_directory / PR_CONVERGE_STATE_FILENAME
304
- state_path.write_text("{not valid json", encoding="utf-8")
305
- captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
306
- assert captured_output == ""
307
-
308
-
309
- def test_should_allow_when_payload_is_malformed_json() -> None:
310
- captured_output = _run_main_with_io("not valid json {{{")
311
- assert captured_output == ""
@@ -1,184 +0,0 @@
1
- """Unit tests: persistent-agent state keys leave both pr-converge hooks unchanged.
2
-
3
- pr-converge-state.json carries `agents_session_id` and `persistent_agents`
4
- (the persistent per-step agent map with a `fix_executor` entry). Neither the
5
- enforcer (`pr_converge_bugteam_enforcer`) nor the tracker
6
- (`pr_converge_bugteam_skill_tracker`) reads these keys, so their allow/block
7
- decisions and stamped fields match a state file without them, and the tracker
8
- preserves the keys unchanged on its stamp write.
9
- """
10
-
11
- from __future__ import annotations
12
-
13
- import importlib.util
14
- import io
15
- import json
16
- import pathlib
17
- import sys
18
- from types import ModuleType
19
- from typing import Any
20
- from unittest import mock
21
-
22
- import pytest
23
-
24
- _BLOCKING_DIR = pathlib.Path(__file__).parent
25
- _HOOKS_TREE = _BLOCKING_DIR.parent
26
- _LIFECYCLE_DIR = _HOOKS_TREE / "lifecycle"
27
- for each_path in (str(_BLOCKING_DIR), str(_HOOKS_TREE)):
28
- if each_path not in sys.path:
29
- sys.path.insert(0, each_path)
30
-
31
-
32
- def _load_hook_module(module_name: str, module_path: pathlib.Path) -> ModuleType:
33
- hook_spec = importlib.util.spec_from_file_location(module_name, module_path)
34
- assert hook_spec is not None
35
- assert hook_spec.loader is not None
36
- loaded_module = importlib.util.module_from_spec(hook_spec)
37
- hook_spec.loader.exec_module(loaded_module)
38
- return loaded_module
39
-
40
-
41
- enforcer_module = _load_hook_module(
42
- "pr_converge_bugteam_enforcer_state_tolerance_target",
43
- _BLOCKING_DIR / "pr_converge_bugteam_enforcer.py",
44
- )
45
- tracker_module = _load_hook_module(
46
- "pr_converge_bugteam_skill_tracker_state_tolerance_target",
47
- _LIFECYCLE_DIR / "pr_converge_bugteam_skill_tracker.py",
48
- )
49
-
50
- from hooks_constants.pr_converge_bugteam_enforcer_constants import (
51
- BUGTEAM_PHASE,
52
- CLAUDE_JOB_DIR_ENV_VAR,
53
- PR_CONVERGE_STATE_FILENAME,
54
- )
55
-
56
- _HEAD_SHA_CURRENT = "abc123def456abc123def456abc123def456abcd"
57
- _TICK_CURRENT = 7
58
- _AUDIT_PROMPT = "Run the bugteam audit and report findings against the A-J categories."
59
- _AGENTS_SESSION_ID = "session-0123456789abcdef"
60
- _FIX_EXECUTOR_AGENT_ID = "prc-fix-42"
61
-
62
-
63
- def _persistent_agent_state_keys() -> dict[str, Any]:
64
- return {
65
- "agents_session_id": _AGENTS_SESSION_ID,
66
- "persistent_agents": {
67
- "fix_executor": {
68
- "agent_id": _FIX_EXECUTOR_AGENT_ID,
69
- "created_tick": 2,
70
- "last_used_tick": 6,
71
- }
72
- },
73
- }
74
-
75
-
76
- def _bugteam_phase_state(**overrides: Any) -> dict[str, Any]:
77
- baseline_state: dict[str, Any] = {
78
- "phase": BUGTEAM_PHASE,
79
- "current_head": _HEAD_SHA_CURRENT,
80
- "tick_count": _TICK_CURRENT,
81
- "bugteam_skill_invoked_at_head": None,
82
- "bugteam_skill_invoked_at_tick": None,
83
- }
84
- baseline_state.update(_persistent_agent_state_keys())
85
- baseline_state.update(overrides)
86
- return baseline_state
87
-
88
-
89
- def _write_state(state_directory: pathlib.Path, state: dict[str, Any]) -> pathlib.Path:
90
- state_path = state_directory / PR_CONVERGE_STATE_FILENAME
91
- state_path.write_text(json.dumps(state), encoding="utf-8")
92
- return state_path
93
-
94
-
95
- def _read_state(state_directory: pathlib.Path) -> dict[str, Any]:
96
- state_path = state_directory / PR_CONVERGE_STATE_FILENAME
97
- return json.loads(state_path.read_text(encoding="utf-8"))
98
-
99
-
100
- def _clean_coder_audit_payload() -> dict[str, Any]:
101
- return {
102
- "tool_name": "Agent",
103
- "tool_input": {
104
- "subagent_type": "clean-coder",
105
- "prompt": _AUDIT_PROMPT,
106
- "description": "Audit the PR",
107
- },
108
- }
109
-
110
-
111
- def _bugteam_skill_payload() -> dict[str, Any]:
112
- return {
113
- "tool_name": "Skill",
114
- "tool_input": {"skill": "bugteam", "args": "https://github.com/o/r/pull/1"},
115
- }
116
-
117
-
118
- def _run_enforcer_main_with_io(input_text: str) -> str:
119
- with mock.patch("sys.stdin", io.StringIO(input_text)):
120
- with mock.patch("sys.stdout", new_callable=io.StringIO) as captured_stdout:
121
- try:
122
- enforcer_module.main()
123
- except SystemExit:
124
- pass
125
- return captured_stdout.getvalue()
126
-
127
-
128
- def _run_tracker_main_with_io(input_text: str) -> None:
129
- with mock.patch("sys.stdin", io.StringIO(input_text)):
130
- with mock.patch("sys.stdout", new_callable=io.StringIO):
131
- with mock.patch("sys.stderr", new_callable=io.StringIO):
132
- try:
133
- tracker_module.main()
134
- except SystemExit:
135
- pass
136
-
137
-
138
- @pytest.fixture()
139
- def claude_job_directory(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path:
140
- monkeypatch.setenv(CLAUDE_JOB_DIR_ENV_VAR, str(tmp_path))
141
- return tmp_path
142
-
143
-
144
- def test_should_block_enforcer_when_persistent_agent_keys_present_without_skill(
145
- claude_job_directory: pathlib.Path,
146
- ) -> None:
147
- _write_state(claude_job_directory, _bugteam_phase_state())
148
- captured_output = _run_enforcer_main_with_io(json.dumps(_clean_coder_audit_payload()))
149
- deny_payload = json.loads(captured_output)
150
- assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
151
-
152
-
153
- def test_should_allow_enforcer_when_persistent_agent_keys_present_with_skill_recorded(
154
- claude_job_directory: pathlib.Path,
155
- ) -> None:
156
- _write_state(
157
- claude_job_directory,
158
- _bugteam_phase_state(
159
- bugteam_skill_invoked_at_head=_HEAD_SHA_CURRENT,
160
- bugteam_skill_invoked_at_tick=_TICK_CURRENT,
161
- ),
162
- )
163
- captured_output = _run_enforcer_main_with_io(json.dumps(_clean_coder_audit_payload()))
164
- assert captured_output == ""
165
-
166
-
167
- def test_should_stamp_tracker_fields_when_persistent_agent_keys_present(
168
- claude_job_directory: pathlib.Path,
169
- ) -> None:
170
- _write_state(claude_job_directory, _bugteam_phase_state())
171
- _run_tracker_main_with_io(json.dumps(_bugteam_skill_payload()))
172
- updated_state = _read_state(claude_job_directory)
173
- assert updated_state["bugteam_skill_invoked_at_head"] == _HEAD_SHA_CURRENT
174
- assert updated_state["bugteam_skill_invoked_at_tick"] == _TICK_CURRENT
175
-
176
-
177
- def test_should_preserve_persistent_agent_keys_on_tracker_stamp_write(
178
- claude_job_directory: pathlib.Path,
179
- ) -> None:
180
- _write_state(claude_job_directory, _bugteam_phase_state())
181
- _run_tracker_main_with_io(json.dumps(_bugteam_skill_payload()))
182
- updated_state = _read_state(claude_job_directory)
183
- assert updated_state["agents_session_id"] == _AGENTS_SESSION_ID
184
- assert updated_state["persistent_agents"] == _persistent_agent_state_keys()["persistent_agents"]