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,348 +0,0 @@
1
- #!/usr/bin/env python3
2
- """PreToolUse guard: deny shell and file-tool access to the stamp directory.
3
-
4
- Runs only when ``CODE_REVIEW_ENFORCEMENT_ENABLED`` is on (default off); with
5
- the flag off the guard allows every stamp-directory access.
6
-
7
- The push and PR-create gates trust a single invariant: only
8
- ``invoke_code_review.py --record-stamp`` mints stamp files under
9
- ``~/.claude/code-review-stamps/``. settings.json cannot be the only shipped
10
- protection, so this guard fires on both surfaces:
11
-
12
- - Bash / PowerShell: deny any command that names the stamp directory (absolute
13
- path, cd-then-relative write, cd into the stamp directory, a split directory
14
- change through the Claude home into the stamp directory, stamp-file shape, a
15
- path-join write, or a path assembled by obfuscation that decodes to a stamp
16
- segment), or that imports the stamp store module or calls its mint function.
17
- - Write / Edit / MultiEdit: deny any path under the stamp directory.
18
-
19
- No legitimate workflow reaches that directory through a shell or the file
20
- tools: the invoker writes in-process after a clean review, so the sanctioned
21
- minter command names none of these matchers and passes without an allowlist.
22
- """
23
-
24
- from __future__ import annotations
25
-
26
- import json
27
- import re
28
- import sys
29
- from pathlib import Path
30
-
31
- try:
32
- _blocking_directory = str(Path(__file__).resolve().parent)
33
- if _blocking_directory not in sys.path:
34
- sys.path.insert(0, _blocking_directory)
35
- _hooks_directory = str(Path(__file__).resolve().parent.parent)
36
- if _hooks_directory not in sys.path:
37
- sys.path.append(_hooks_directory)
38
-
39
- from code_review_enforcement_config_bootstrap import (
40
- register_code_review_enforcement_constants,
41
- )
42
- from verified_commit_config_bootstrap import register_verified_commit_constants
43
-
44
- register_code_review_enforcement_constants()
45
- register_verified_commit_constants()
46
-
47
- from code_review_stamp_store import stamp_directory
48
- from code_review_stamp_write_blocker_parts.obfuscated_stamp_path_reference import (
49
- references_obfuscated_stamp_path,
50
- )
51
- from code_review_stamp_write_blocker_parts.split_directory_change_into_stamp import (
52
- changes_through_split_directory_into_stamp,
53
- directory_change_prefix,
54
- )
55
- from config.code_review_enforcement_constants import (
56
- ALL_GATED_SHELL_TOOL_NAMES,
57
- ALL_STAMP_STORE_FORGE_PATTERNS,
58
- ALL_WRITE_EDIT_TOOL_NAMES,
59
- CODE_REVIEW_ENFORCEMENT_ENABLED,
60
- DENY_PERMISSION_DECISION,
61
- PRE_TOOL_USE_HOOK_EVENT_NAME,
62
- RELATIVE_STAMP_DIRECTORY_PATTERN,
63
- STAMP_DIRECTORY_GUARD_MESSAGE,
64
- STAMP_DIRECTORY_NAME,
65
- STAMP_FILE_REFERENCE_BOUNDARY_PREFIX_PATTERN,
66
- STAMP_FILE_ROOT_KEY_JSON_SUFFIX_PATTERN,
67
- STAMP_WRITE_BLOCKER_HOOK_MODULE_NAME,
68
- )
69
- from config.verified_commit_constants import (
70
- CLAUDE_HOME_DIRECTORY_NAME,
71
- CLAUDE_HOME_TARGET_BOUNDARY_PATTERN,
72
- COMMAND_AFTER_DIRECTORY_CHANGE_PATTERN,
73
- DIRECTORY_CHANGE_TARGET_PATTERN,
74
- NON_REDIRECT_FILE_WRITE_PRIMITIVE_PATTERN,
75
- ROOT_KEY_HEX_LENGTH,
76
- VERDICT_DIRECTORY_NAME_SEPARATOR_PATTERN,
77
- VERDICT_DIRECTORY_PATH_BOUNDARY_PATTERN,
78
- VERDICT_DIRECTORY_TARGET_BOUNDARY_PATTERN,
79
- VERDICT_PATH_GLUE_PATTERN,
80
- )
81
-
82
- from hooks_constants.hook_block_logger import log_hook_block
83
- from hooks_constants.pre_tool_use_stdin import (
84
- read_hook_input_dictionary_from_stdin,
85
- )
86
- except ImportError as import_error:
87
- raise ImportError(
88
- "code_review_stamp_directory_write_blocker: cannot import its dependencies; "
89
- "ensure the blocking directory is importable."
90
- ) from import_error
91
-
92
-
93
- def _stamp_file_relative_reference_pattern() -> str:
94
- """Build the ``code-review-stamps/<root-key>.json`` stamp-file shape pattern."""
95
- root_key_json_suffix = STAMP_FILE_ROOT_KEY_JSON_SUFFIX_PATTERN % ROOT_KEY_HEX_LENGTH
96
- return (
97
- f"{STAMP_FILE_REFERENCE_BOUNDARY_PREFIX_PATTERN}"
98
- f"{re.escape(STAMP_DIRECTORY_NAME)}{root_key_json_suffix}"
99
- )
100
-
101
-
102
- def _references_absolute_stamp_path(command_text: str) -> bool:
103
- """Decide whether a command names an absolute stamp-directory path."""
104
- stamp_directory_pattern = re.compile(
105
- re.escape(CLAUDE_HOME_DIRECTORY_NAME)
106
- + VERDICT_DIRECTORY_NAME_SEPARATOR_PATTERN
107
- + re.escape(STAMP_DIRECTORY_NAME)
108
- + VERDICT_DIRECTORY_PATH_BOUNDARY_PATTERN,
109
- re.IGNORECASE,
110
- )
111
- return stamp_directory_pattern.search(command_text) is not None
112
-
113
-
114
- def _changes_into_claude_home_then_writes_relative(command_text: str) -> bool:
115
- """Decide whether a command enters the Claude home then writes the stamp dir."""
116
- directory_change_into_claude_pattern = re.compile(
117
- directory_change_prefix()
118
- + DIRECTORY_CHANGE_TARGET_PATTERN
119
- + re.escape(CLAUDE_HOME_DIRECTORY_NAME)
120
- + CLAUDE_HOME_TARGET_BOUNDARY_PATTERN,
121
- re.IGNORECASE,
122
- )
123
- change_match = directory_change_into_claude_pattern.search(command_text)
124
- if change_match is None:
125
- return False
126
- relative_stamp_pattern = re.compile(RELATIVE_STAMP_DIRECTORY_PATTERN, re.IGNORECASE)
127
- return relative_stamp_pattern.search(command_text, change_match.end()) is not None
128
-
129
-
130
- def _changes_into_stamp_directory_then_writes(command_text: str) -> bool:
131
- """Decide whether a command enters the stamp directory then runs a command."""
132
- directory_change_into_stamp_pattern = re.compile(
133
- directory_change_prefix()
134
- + DIRECTORY_CHANGE_TARGET_PATTERN
135
- + re.escape(CLAUDE_HOME_DIRECTORY_NAME)
136
- + r"[\\/]"
137
- + re.escape(STAMP_DIRECTORY_NAME)
138
- + VERDICT_DIRECTORY_TARGET_BOUNDARY_PATTERN,
139
- re.IGNORECASE,
140
- )
141
- change_match = directory_change_into_stamp_pattern.search(command_text)
142
- if change_match is None:
143
- return False
144
- command_after_change_pattern = re.compile(COMMAND_AFTER_DIRECTORY_CHANGE_PATTERN)
145
- return (
146
- command_after_change_pattern.search(command_text, change_match.end())
147
- is not None
148
- )
149
-
150
-
151
- def _references_stamp_file_shape(command_text: str) -> bool:
152
- """Decide whether a command names a stamp file's ``<root-key>.json`` shape."""
153
- stamp_file_pattern = re.compile(
154
- _stamp_file_relative_reference_pattern(), re.IGNORECASE
155
- )
156
- return stamp_file_pattern.search(command_text) is not None
157
-
158
-
159
- def _segments_join_as_stamp_path(command_text: str) -> bool:
160
- """Decide whether the two name segments sit adjacent in a path-join shape."""
161
- home_name = re.escape(CLAUDE_HOME_DIRECTORY_NAME)
162
- stamp_name = re.escape(STAMP_DIRECTORY_NAME)
163
- path_join_pattern = re.compile(
164
- home_name
165
- + VERDICT_PATH_GLUE_PATTERN
166
- + stamp_name
167
- + "|"
168
- + stamp_name
169
- + VERDICT_PATH_GLUE_PATTERN
170
- + home_name,
171
- re.IGNORECASE,
172
- )
173
- return path_join_pattern.search(command_text) is not None
174
-
175
-
176
- def _writes_with_stamp_path_intent(command_text: str) -> bool:
177
- """Decide whether a write joins the two name segments into a stamp path."""
178
- has_non_redirect_write = (
179
- re.search(NON_REDIRECT_FILE_WRITE_PRIMITIVE_PATTERN, command_text) is not None
180
- )
181
- if not has_non_redirect_write:
182
- return False
183
- return _segments_join_as_stamp_path(command_text)
184
-
185
-
186
- def _references_store_module_or_mint_call(command_text: str) -> bool:
187
- """Decide whether a command imports the stamp store or calls its mint call.
188
-
189
- ::
190
-
191
- python -c "from code_review_stamp_store import record_clean_stamp" -> True
192
- python -c "record_clean_stamp(root, live_hash, 'xhigh')" -> True
193
- python -m pytest test_code_review_stamp_store.py -> False
194
-
195
- A forge that re-implements the mint in-process names no stamp path, so the
196
- path matchers miss it. Matching the store import and the mint call closes
197
- that gap. A pytest run naming the store test file matches none of these.
198
-
199
- Args:
200
- command_text: The raw command string from the tool payload.
201
-
202
- Returns:
203
- True when the command imports ``code_review_stamp_store`` or calls
204
- ``record_clean_stamp``; False otherwise.
205
- """
206
- for each_forge_pattern in ALL_STAMP_STORE_FORGE_PATTERNS:
207
- if re.search(each_forge_pattern, command_text):
208
- return True
209
- return False
210
-
211
-
212
- def references_stamp_directory(command_text: str) -> bool:
213
- """Decide whether a command references the code-review stamp directory.
214
-
215
- Args:
216
- command_text: The raw command string from the tool payload.
217
-
218
- Returns:
219
- True when the command names the stamp directory through any matcher — a
220
- store import or mint call, an absolute path, a cd-then-relative write, a
221
- cd into the directory, a split directory change into the directory, a
222
- stamp-file shape, a path-join write, or an obfuscation-decoded stamp
223
- segment; False otherwise.
224
- """
225
- if _references_store_module_or_mint_call(command_text):
226
- return True
227
- if _references_absolute_stamp_path(command_text):
228
- return True
229
- if _changes_into_claude_home_then_writes_relative(command_text):
230
- return True
231
- if _changes_into_stamp_directory_then_writes(command_text):
232
- return True
233
- if changes_through_split_directory_into_stamp(command_text):
234
- return True
235
- if _references_stamp_file_shape(command_text):
236
- return True
237
- if _writes_with_stamp_path_intent(command_text):
238
- return True
239
- return references_obfuscated_stamp_path(command_text)
240
-
241
-
242
- def path_targets_stamp_directory(file_path: str) -> bool:
243
- """Decide whether a Write/Edit/MultiEdit path sits under the stamp directory.
244
-
245
- Args:
246
- file_path: The path the file tool targets.
247
-
248
- Returns:
249
- True when the resolved path is under ``~/.claude/code-review-stamps/``.
250
- """
251
- if not file_path:
252
- return False
253
- try:
254
- resolved_target = Path(file_path).expanduser().resolve()
255
- resolved_stamp_root = stamp_directory().resolve()
256
- except OSError:
257
- return False
258
- if resolved_target == resolved_stamp_root:
259
- return True
260
- try:
261
- resolved_target.relative_to(resolved_stamp_root)
262
- except ValueError:
263
- return False
264
- return True
265
-
266
-
267
- def build_deny_payload() -> dict[str, dict[str, str]]:
268
- """Build the PreToolUse deny payload for a stamp-directory access."""
269
- return {
270
- "hookSpecificOutput": {
271
- "hookEventName": PRE_TOOL_USE_HOOK_EVENT_NAME,
272
- "permissionDecision": DENY_PERMISSION_DECISION,
273
- "permissionDecisionReason": STAMP_DIRECTORY_GUARD_MESSAGE,
274
- }
275
- }
276
-
277
-
278
- def _shell_decision(all_tool_input: dict[str, object]) -> dict[str, dict[str, str]] | None:
279
- """Return the deny payload when a shell command targets the stamp directory."""
280
- command_text = all_tool_input.get("command", "")
281
- if not isinstance(command_text, str) or not command_text:
282
- return None
283
- if not references_stamp_directory(command_text):
284
- return None
285
- return build_deny_payload()
286
-
287
-
288
- def _file_decision(all_tool_input: dict[str, object]) -> dict[str, dict[str, str]] | None:
289
- """Return the deny payload when a file-tool path targets the stamp directory."""
290
- file_path = all_tool_input.get("file_path", "")
291
- if not isinstance(file_path, str):
292
- return None
293
- if not path_targets_stamp_directory(file_path):
294
- return None
295
- return build_deny_payload()
296
-
297
-
298
- def decision_for_payload(
299
- all_pretooluse_payload: dict[str, object],
300
- ) -> dict[str, dict[str, str]] | None:
301
- """Build the deny decision for stamp-directory shell or file-tool access.
302
-
303
- Returns None immediately when `CODE_REVIEW_ENFORCEMENT_ENABLED` is off.
304
-
305
- Args:
306
- all_pretooluse_payload: The PreToolUse hook payload.
307
-
308
- Returns:
309
- The deny decision mapping when a gated tool targets the stamp
310
- directory; None when the tool call may proceed.
311
- """
312
- if not CODE_REVIEW_ENFORCEMENT_ENABLED:
313
- return None
314
- tool_name = all_pretooluse_payload.get("tool_name", "")
315
- if not isinstance(tool_name, str):
316
- return None
317
- tool_input = all_pretooluse_payload.get("tool_input", {})
318
- if not isinstance(tool_input, dict):
319
- return None
320
- if tool_name in ALL_GATED_SHELL_TOOL_NAMES:
321
- return _shell_decision(tool_input)
322
- if tool_name not in ALL_WRITE_EDIT_TOOL_NAMES:
323
- return None
324
- return _file_decision(tool_input)
325
-
326
-
327
- def main() -> None:
328
- """Read the PreToolUse payload and deny stamp-directory access."""
329
- pretooluse_payload = read_hook_input_dictionary_from_stdin()
330
- if pretooluse_payload is None:
331
- return
332
- deny_decision = decision_for_payload(pretooluse_payload)
333
- if deny_decision is None:
334
- return
335
- raw_tool_name = pretooluse_payload.get("tool_name", "")
336
- tool_name_for_log = raw_tool_name if isinstance(raw_tool_name, str) else ""
337
- log_hook_block(
338
- calling_hook_name=STAMP_WRITE_BLOCKER_HOOK_MODULE_NAME,
339
- hook_event=PRE_TOOL_USE_HOOK_EVENT_NAME,
340
- block_reason=STAMP_DIRECTORY_GUARD_MESSAGE,
341
- tool_name=tool_name_for_log,
342
- )
343
- sys.stdout.write(json.dumps(deny_decision) + "\n")
344
- sys.stdout.flush()
345
-
346
-
347
- if __name__ == "__main__":
348
- main()
@@ -1,233 +0,0 @@
1
- """Read and write the per-work-tree code-review stamp files.
2
-
3
- A stamp records that a clean ``/code-review`` pass ran against an exact branch
4
- surface at a given effort. Each work tree keeps one JSON file under
5
- ``~/.claude/code-review-stamps/`` mapping an effort token to the surface hash it
6
- was earned against, so a later push or pull-request gate can ask whether a
7
- clean review at the needed effort already covers the live surface. This module
8
- only reads and writes those files; it runs no review.
9
-
10
- ::
11
-
12
- record_clean_stamp(root, hash_a, "low")
13
- stamp_covers_surface(root, hash_a, "low") -> True (recorded, matching)
14
- stamp_covers_surface(root, hash_a, "xhigh") -> False (low is below xhigh)
15
- stamp_covers_surface(root, hash_b, "low") -> False (surface hash differs)
16
-
17
- Coverage fails closed: a missing, unreadable, or malformed stamp file, a hash
18
- that does not match, and an effort below the threshold all read as not covered.
19
- """
20
-
21
- from __future__ import annotations
22
-
23
- import json
24
- import sys
25
- import time
26
- from pathlib import Path
27
-
28
- _blocking_directory = str(Path(__file__).resolve().parent)
29
-
30
- try:
31
- from code_review_enforcement_config_bootstrap import (
32
- register_code_review_enforcement_constants,
33
- )
34
- from verified_commit_config_bootstrap import register_verified_commit_constants
35
- except ModuleNotFoundError:
36
- if _blocking_directory not in sys.path:
37
- sys.path.insert(0, _blocking_directory)
38
- from code_review_enforcement_config_bootstrap import (
39
- register_code_review_enforcement_constants,
40
- )
41
- from verified_commit_config_bootstrap import register_verified_commit_constants
42
-
43
- register_code_review_enforcement_constants()
44
- register_verified_commit_constants()
45
-
46
- try:
47
- from config.code_review_enforcement_constants import (
48
- STAMP_DIRECTORY_NAME,
49
- STAMP_KEY_EFFORT,
50
- STAMP_KEY_MANIFEST_SHA256,
51
- STAMP_KEY_RECORDED_AT_EPOCH,
52
- effort_meets_threshold,
53
- )
54
- from config.verified_commit_constants import (
55
- CLAUDE_HOME_DIRECTORY_NAME,
56
- VERDICT_JSON_INDENT,
57
- )
58
- from verification_verdict_store import (
59
- branch_surface_manifest,
60
- empty_surface_hash,
61
- manifest_sha256,
62
- resolve_merge_base,
63
- resolve_repo_root,
64
- root_key_for_repo,
65
- )
66
- except ModuleNotFoundError:
67
- if _blocking_directory not in sys.path:
68
- sys.path.insert(0, _blocking_directory)
69
- from config.code_review_enforcement_constants import (
70
- STAMP_DIRECTORY_NAME,
71
- STAMP_KEY_EFFORT,
72
- STAMP_KEY_MANIFEST_SHA256,
73
- STAMP_KEY_RECORDED_AT_EPOCH,
74
- effort_meets_threshold,
75
- )
76
- from config.verified_commit_constants import (
77
- CLAUDE_HOME_DIRECTORY_NAME,
78
- VERDICT_JSON_INDENT,
79
- )
80
- from verification_verdict_store import (
81
- branch_surface_manifest,
82
- empty_surface_hash,
83
- manifest_sha256,
84
- resolve_merge_base,
85
- resolve_repo_root,
86
- root_key_for_repo,
87
- )
88
-
89
-
90
- def stamp_directory() -> Path:
91
- """Return the shared directory holding every work tree's stamp file.
92
-
93
- Stamps live outside any repository (under the user's Claude home) so no repo
94
- accumulates untracked stamp files; every work tree's stamp shares this one
95
- directory, distinguished by file name.
96
-
97
- Returns:
98
- The stamp directory under the user's Claude home.
99
- """
100
- return Path.home() / CLAUDE_HOME_DIRECTORY_NAME / STAMP_DIRECTORY_NAME
101
-
102
-
103
- def stamp_path_for_repo(repo_root: str) -> Path:
104
- """Derive the stamp file path for a repository work tree.
105
-
106
- Keyed by the shared ``root_key_for_repo`` derivation, so every work tree
107
- gets its own stamp file and a subdirectory of a work tree resolves to the
108
- same key as its root.
109
-
110
- Args:
111
- repo_root: The repository top-level directory.
112
-
113
- Returns:
114
- The stamp file path for this work tree.
115
- """
116
- return stamp_directory() / f"{root_key_for_repo(repo_root)}.json"
117
-
118
-
119
- def _read_stamps_by_effort(stamp_file: Path) -> dict[str, object]:
120
- """Read the per-effort stamp records for a work tree, or an empty map.
121
-
122
- ::
123
-
124
- <root-key>.json holds {"low": {...}, "xhigh": {...}} -> that map
125
- file missing / unreadable / not a JSON object -> {}
126
-
127
- A missing, unreadable, or malformed stamp file yields an empty map, so a
128
- caller reading it sees no coverage and fails closed.
129
-
130
- Args:
131
- stamp_file: The per-work-tree stamp file path.
132
-
133
- Returns:
134
- The mapping of effort token to its stored record, or an empty map when
135
- the file is absent, unreadable, or not a JSON object.
136
- """
137
- try:
138
- parsed_stamps = json.loads(stamp_file.read_text(encoding="utf-8"))
139
- except (OSError, json.JSONDecodeError):
140
- return {}
141
- if not isinstance(parsed_stamps, dict):
142
- return {}
143
- return parsed_stamps
144
-
145
-
146
- def record_clean_stamp(repo_root: str, bound_manifest_sha256: str, effort: str) -> Path:
147
- """Write one effort's clean-review stamp for a work tree.
148
-
149
- Reads any existing per-effort records, sets this effort's entry to the given
150
- surface hash and the current epoch, and writes the map back. Recording a
151
- second effort keeps the first; recording the same effort again overwrites
152
- its bound hash.
153
-
154
- Args:
155
- repo_root: The repository top-level directory.
156
- bound_manifest_sha256: Hash of the branch surface the clean review covered.
157
- effort: The effort token the clean review ran at.
158
-
159
- Returns:
160
- The stamp file path the record was written to.
161
- """
162
- stamp_file = stamp_path_for_repo(repo_root)
163
- stamp_file.parent.mkdir(parents=True, exist_ok=True)
164
- all_stamps_by_effort = _read_stamps_by_effort(stamp_file)
165
- all_stamps_by_effort[effort] = {
166
- STAMP_KEY_EFFORT: effort,
167
- STAMP_KEY_MANIFEST_SHA256: bound_manifest_sha256,
168
- STAMP_KEY_RECORDED_AT_EPOCH: int(time.time()),
169
- }
170
- stamp_file.write_text(
171
- json.dumps(all_stamps_by_effort, indent=VERDICT_JSON_INDENT), encoding="utf-8"
172
- )
173
- return stamp_file
174
-
175
-
176
- def stamp_covers_surface(repo_root: str, live_manifest_sha256: str, required_effort: str) -> bool:
177
- """Decide whether a clean stamp covers the live surface at the needed effort.
178
-
179
- A stored entry covers the surface when its bound hash equals the live hash
180
- exactly and its effort ranks at or above the required effort. Any missing,
181
- unreadable, or malformed stamp reads as not covered.
182
-
183
- Args:
184
- repo_root: The repository top-level directory.
185
- live_manifest_sha256: Hash of the live branch surface the gate checks.
186
- required_effort: The effort the gate demands for the action.
187
-
188
- Returns:
189
- True as soon as one stored entry matches the live hash and meets the
190
- required effort; False when none match.
191
- """
192
- all_stamps_by_effort = _read_stamps_by_effort(stamp_path_for_repo(repo_root))
193
- for each_stamp_record in all_stamps_by_effort.values():
194
- if not isinstance(each_stamp_record, dict):
195
- continue
196
- if each_stamp_record.get(STAMP_KEY_MANIFEST_SHA256) != live_manifest_sha256:
197
- continue
198
- stored_effort = each_stamp_record.get(STAMP_KEY_EFFORT)
199
- if not isinstance(stored_effort, str):
200
- continue
201
- if effort_meets_threshold(stored_effort, required_effort):
202
- return True
203
- return False
204
-
205
-
206
- def live_surface_hash(work_tree_directory: str) -> str | None:
207
- """Compute the live branch-surface manifest hash for a work tree.
208
-
209
- Resolves the work tree's repo root and merge base, then hashes the current
210
- change surface — every changed path and untracked file bound by its content
211
- digest — so the hash tracks the exact bytes under review.
212
-
213
- Args:
214
- work_tree_directory: A directory inside the work tree to hash.
215
-
216
- Returns:
217
- The surface manifest hash, or None when the directory is outside a work
218
- tree, the merge base or manifest cannot be resolved, or the change
219
- surface is empty (HEAD equals the merge base, so nothing is under review).
220
- """
221
- repo_root = resolve_repo_root(work_tree_directory)
222
- if repo_root is None:
223
- return None
224
- merge_base_sha = resolve_merge_base(repo_root)
225
- if merge_base_sha is None:
226
- return None
227
- surface_manifest_text = branch_surface_manifest(repo_root, merge_base_sha)
228
- if surface_manifest_text is None:
229
- return None
230
- surface_hash = manifest_sha256(surface_manifest_text)
231
- if surface_hash == empty_surface_hash():
232
- return None
233
- return surface_hash
@@ -1,7 +0,0 @@
1
- """Concern modules of the code-review stamp write blocker, wired by the entry hook.
2
-
3
- The entry hook ``code_review_stamp_directory_write_blocker.py`` imports two
4
- public matchers from this package to keep its own file short: a split
5
- directory-change matcher and an obfuscated-path-write matcher. Each mirrors the
6
- sibling verdict-directory guard's logic against the code-review stamp segments.
7
- """
@@ -1,15 +0,0 @@
1
- """Put the blocking and hooks directories on sys.path for the parts tests.
2
-
3
- The concern modules import their shared constants as ``from config...`` against
4
- the sibling ``blocking/config/`` package, so the tests need the blocking
5
- directory (which holds that package) and the hooks directory on the path.
6
- """
7
-
8
- import sys
9
- from pathlib import Path
10
-
11
- _blocking_directory = str(Path(__file__).resolve().parents[1])
12
- _hooks_directory = str(Path(__file__).resolve().parents[2])
13
- for each_bootstrap_directory in (_blocking_directory, _hooks_directory):
14
- if each_bootstrap_directory not in sys.path:
15
- sys.path.insert(0, each_bootstrap_directory)