claude-dev-env 2.3.0 → 2.5.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 (290) hide show
  1. package/CLAUDE.md +53 -48
  2. package/_shared/pr-loop/scripts/_claude_permissions_common.py +84 -0
  3. package/_shared/pr-loop/scripts/code_rules_gate.py +4 -2
  4. package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +306 -306
  5. package/_shared/pr-loop/scripts/pr_loop_shared_constants/claude_permissions_constants.py +44 -0
  6. package/_shared/pr-loop/scripts/pr_loop_shared_constants/copilot_quota_constants.py +24 -24
  7. package/_shared/pr-loop/scripts/pr_loop_shared_constants/stale_worktree_rule_sweep_constants.py +107 -107
  8. package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +290 -48
  9. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +42 -2
  10. package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +36 -0
  11. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +100 -1
  12. package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +497 -497
  13. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +311 -2
  14. package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep.py +301 -301
  15. package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep_constants.py +85 -85
  16. package/_shared/pr-loop/worker-spawn.md +1 -1
  17. package/agents/CLAUDE.md +3 -1
  18. package/agents/caveman.md +0 -1
  19. package/agents/clasp-deployment-orchestrator.md +0 -1
  20. package/agents/clean-coder.md +0 -1
  21. package/agents/code-advisor.md +0 -1
  22. package/agents/code-quality-agent.md +1 -2
  23. package/agents/code-verifier.md +3 -4
  24. package/agents/deep-research.md +0 -1
  25. package/agents/docs-agent.md +0 -1
  26. package/agents/git-commit-crafter.md +0 -1
  27. package/agents/issue-tracker.md +42 -0
  28. package/agents/plan-packet-validator.md +0 -1
  29. package/agents/pr-description-writer.md +0 -1
  30. package/agents/skill-writer-agent.md +84 -0
  31. package/agents/test_agent_frontmatter.py +67 -18
  32. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +105 -3
  33. package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +29 -13
  34. package/bin/CLAUDE.md +68 -5
  35. package/bin/ever-shipped-skills.mjs +1 -0
  36. package/bin/install-constants.mjs +88 -0
  37. package/bin/install.mjs +1138 -114
  38. package/bin/install.prune.test.mjs +869 -19
  39. package/bin/install.test.mjs +906 -2
  40. package/commands/implement.md +1 -1
  41. package/commands/right-size.md +1 -1
  42. package/docs/CLAUDE.md +2 -0
  43. package/docs/host-pool-health-monitor.md +102 -0
  44. package/docs/references/CLAUDE.md +5 -2
  45. package/docs/references/advisor-tool.md +13 -0
  46. package/docs/references/code-review-enforcement.md +107 -0
  47. package/docs/references/team-advisor-skill.md +14 -0
  48. package/docs/wsl-docker-cowork-starter-matrix.md +89 -0
  49. package/hooks/blocking/CLAUDE.md +9 -1
  50. package/hooks/blocking/code_review_enforcement_config_bootstrap.py +53 -0
  51. package/hooks/blocking/code_review_gate_deny.py +74 -0
  52. package/hooks/blocking/code_review_pr_create_gate.py +198 -0
  53. package/hooks/blocking/code_review_push_gate.py +145 -0
  54. package/hooks/blocking/code_review_stamp_directory_write_blocker.py +348 -0
  55. package/hooks/blocking/code_review_stamp_store.py +233 -0
  56. package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +7 -0
  57. package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +15 -0
  58. package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +212 -0
  59. package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +138 -0
  60. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +49 -0
  61. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +38 -0
  62. package/hooks/blocking/code_verifier_spawn_preflight_gate.py +39 -27
  63. package/hooks/blocking/config/__init__.py +5 -5
  64. package/hooks/blocking/config/code_review_enforcement_constants.py +113 -0
  65. package/hooks/blocking/config/test_code_review_enforcement_constants.py +113 -0
  66. package/hooks/blocking/config/verified_commit_constants.py +160 -155
  67. package/hooks/blocking/conftest.py +2 -0
  68. package/hooks/blocking/convergence_gate_blocker.py +112 -23
  69. package/hooks/blocking/destructive_command_blocker.py +19 -6
  70. package/hooks/blocking/orchestrator_refresh_reschedule_gate.py +256 -0
  71. package/hooks/blocking/pr_description_proof_of_work.py +52 -34
  72. package/hooks/blocking/pre_tool_use_dispatcher.py +24 -24
  73. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +4 -1
  74. package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +62 -0
  75. package/hooks/blocking/test_code_review_gate_deny.py +54 -0
  76. package/hooks/blocking/test_code_review_pr_create_gate.py +199 -0
  77. package/hooks/blocking/test_code_review_push_gate.py +205 -0
  78. package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +199 -0
  79. package/hooks/blocking/test_code_review_stamp_store.py +205 -0
  80. package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +124 -2
  81. package/hooks/blocking/test_convergence_gate_blocker.py +153 -5
  82. package/hooks/blocking/test_destructive_command_blocker.py +1 -1
  83. package/hooks/blocking/test_destructive_command_blocker_deny_mode.py +45 -0
  84. package/hooks/blocking/test_orchestrator_refresh_reschedule_gate.py +231 -0
  85. package/hooks/blocking/test_pr_description_proof_of_work.py +151 -0
  86. package/hooks/blocking/test_pre_tool_use_dispatcher.py +17 -8
  87. package/hooks/blocking/test_verdict_directory_write_blocker.py +808 -808
  88. package/hooks/blocking/test_verification_verdict_store.py +974 -903
  89. package/hooks/blocking/test_verified_commit_gate.py +581 -581
  90. package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +131 -131
  91. package/hooks/blocking/test_volatile_path_in_post_blocker.py +114 -2
  92. package/hooks/blocking/verdict_directory_write_blocker.py +687 -687
  93. package/hooks/blocking/verification_verdict_store.py +1039 -1014
  94. package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +29 -17
  95. package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +35 -0
  96. package/hooks/blocking/verified_commit_message_accuracy_blocker.py +167 -167
  97. package/hooks/blocking/verifier_verdict_minter.py +280 -280
  98. package/hooks/blocking/volatile_path_in_post_blocker.py +69 -8
  99. package/hooks/git-hooks/git_hooks_constants/__init__.py +6 -0
  100. package/hooks/git-hooks/pre_push.py +89 -2
  101. package/hooks/git-hooks/test_pre_push.py +128 -0
  102. package/hooks/hooks.json +26 -1
  103. package/hooks/hooks_constants/CLAUDE.md +3 -1
  104. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +8 -0
  105. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
  106. package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +26 -11
  107. package/hooks/hooks_constants/convergence_gate_blocker_constants.py +20 -3
  108. package/hooks/hooks_constants/destructive_command_segment_constants.py +3 -1
  109. package/hooks/hooks_constants/enter_worktree_prefetch_constants.py +18 -18
  110. package/hooks/hooks_constants/orchestrator_refresh_reschedule_gate_constants.py +48 -0
  111. package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -4
  112. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
  113. package/hooks/hooks_constants/pyproject_config_discovery_constants.py +16 -0
  114. package/hooks/hooks_constants/ruff_integration_constants.py +16 -0
  115. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +24 -0
  116. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +6 -0
  117. package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +8 -1
  118. package/hooks/lifecycle/enter_worktree_origin_prefetch.py +163 -146
  119. package/hooks/lifecycle/test_enter_worktree_origin_prefetch.py +185 -178
  120. package/hooks/pyproject.toml +1 -0
  121. package/hooks/validators/CLAUDE.md +2 -0
  122. package/hooks/validators/config/__init__.py +0 -0
  123. package/hooks/validators/config/directory_exemption_constants.py +183 -0
  124. package/hooks/validators/config/test_directory_exemption_constants.py +21 -0
  125. package/hooks/validators/conftest.py +4 -0
  126. package/hooks/validators/mypy_integration.py +63 -50
  127. package/hooks/validators/pyproject_config_discovery.py +101 -0
  128. package/hooks/validators/ruff_integration.py +257 -25
  129. package/hooks/validators/run_all_validators.py +223 -19
  130. package/hooks/validators/test_directory_exemption_constants.py +185 -0
  131. package/hooks/validators/test_mypy_integration.py +32 -0
  132. package/hooks/validators/test_pyproject_config_discovery.py +94 -0
  133. package/hooks/validators/test_python_antipattern_checks.py +110 -5
  134. package/hooks/validators/test_ruff_integration.py +160 -2
  135. package/hooks/validators/test_run_all_validators.py +140 -68
  136. package/hooks/validators/test_run_all_validators_config_discovery.py +123 -0
  137. package/hooks/validators/test_run_all_validators_pretooluse.py +159 -1
  138. package/package.json +10 -2
  139. package/rules/CLAUDE.md +1 -0
  140. package/rules/docstring-prose-matches-implementation.md +45 -67
  141. package/rules/durable-post-artifacts.md +7 -0
  142. package/rules/state-what-is.md +25 -0
  143. package/rules/verified-commit-gate-skip.md +1 -1
  144. package/scripts/CLAUDE.md +1 -0
  145. package/scripts/Capture-PoolHealth.ps1 +410 -0
  146. package/scripts/_code_review_test_support.py +404 -0
  147. package/scripts/claude_chain_runner.py +141 -1
  148. package/scripts/codec_forwarding_test_support.py +83 -0
  149. package/scripts/conftest.py +23 -0
  150. package/scripts/dev_env_scripts_constants/CLAUDE.md +2 -2
  151. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +53 -1
  152. package/scripts/dev_env_scripts_constants/code_review_constants.py +129 -12
  153. package/scripts/dev_env_scripts_constants/test_code_review_constants.py +55 -0
  154. package/scripts/invoke_code_review.py +550 -38
  155. package/scripts/resolve_worker_spawn.py +626 -619
  156. package/scripts/spawn_grok_batch.py +672 -672
  157. package/scripts/test_claude_chain_runner.py +131 -0
  158. package/scripts/test_invoke_code_review_chain.py +70 -0
  159. package/scripts/test_invoke_code_review_cli.py +192 -0
  160. package/scripts/test_invoke_code_review_codec.py +77 -0
  161. package/scripts/test_invoke_code_review_contract.py +256 -0
  162. package/scripts/test_invoke_code_review_git.py +123 -0
  163. package/scripts/test_invoke_code_review_mode.py +99 -0
  164. package/scripts/test_resolve_worker_spawn.py +1014 -1014
  165. package/scripts/test_resolve_worker_spawn_codec.py +101 -0
  166. package/skills/CLAUDE.md +2 -0
  167. package/skills/auditing-claude-config/SKILL.md +114 -114
  168. package/skills/autoconverge/SKILL.md +427 -421
  169. package/skills/autoconverge/reference/convergence.md +26 -4
  170. package/skills/autoconverge/reference/multi-pr.md +6 -1
  171. package/skills/autoconverge/reference/stop-conditions.md +16 -10
  172. package/skills/autoconverge/workflow/CLAUDE.md +1 -0
  173. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +4 -4
  174. package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +175 -3
  175. package/skills/autoconverge/workflow/converge.contract.test.mjs +1263 -1244
  176. package/skills/autoconverge/workflow/converge.mjs +191 -8
  177. package/skills/autoconverge/workflow/converge.p2-advance.test.mjs +202 -0
  178. package/skills/autoconverge/workflow/converge_multi.mjs +7 -3
  179. package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +5 -0
  180. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +2 -2
  181. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +2 -2
  182. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +2 -2
  183. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +2 -2
  184. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +2 -2
  185. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +2 -2
  186. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +2 -2
  187. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +2 -2
  188. package/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +265 -265
  189. package/skills/closeout/SKILL.md +33 -50
  190. package/skills/codex-review/scripts/codex_review_scripts_constants/run_constants.py +8 -0
  191. package/skills/codex-review/scripts/run_codex_review.py +233 -1
  192. package/skills/codex-review/scripts/test_run_codex_review.py +189 -0
  193. package/skills/condensing-instructions/SKILL.md +81 -0
  194. package/skills/copilot-review/SKILL.md +119 -119
  195. package/skills/e-code-review/SKILL.md +52 -0
  196. package/skills/e-code-review/reference/fix.md +54 -0
  197. package/skills/e-code-review/reference/loop.md +43 -0
  198. package/skills/e-code-review/reference/low.md +57 -0
  199. package/skills/e-code-review/reference/medium.md +153 -0
  200. package/skills/e-code-review/reference/xhigh.md +182 -0
  201. package/skills/e-simplify/SKILL.md +97 -0
  202. package/skills/fresh-branch/CLAUDE.md +2 -0
  203. package/skills/fresh-branch/SKILL.md +2 -0
  204. package/skills/fresh-branch/scripts/create_fresh_branch.py +78 -180
  205. package/skills/fresh-branch/scripts/fresh_branch_git_commands.py +285 -0
  206. package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +1 -0
  207. package/skills/fresh-branch/scripts/pytest.ini +4 -0
  208. package/skills/fresh-branch/scripts/test_create_fresh_branch.py +98 -0
  209. package/skills/fresh-branch/scripts/test_fresh_branch_git_commands.py +310 -0
  210. package/skills/issue-tracker/SKILL.md +92 -0
  211. package/skills/issue-tracker/reference/epic-and-sub-issue-model.md +55 -0
  212. package/skills/issue-tracker/reference/handoff-schema.md +64 -0
  213. package/skills/issue-tracker/reference/operation-matrix.md +41 -0
  214. package/skills/orchestrator/SKILL.md +162 -21
  215. package/skills/orchestrator/scripts/status_gate.py +625 -0
  216. package/skills/orchestrator/scripts/status_gate_constants/__init__.py +1 -0
  217. package/skills/orchestrator/scripts/status_gate_constants/config/__init__.py +1 -0
  218. package/skills/orchestrator/scripts/status_gate_constants/config/constants.py +47 -0
  219. package/skills/orchestrator/scripts/test_status_gate.py +439 -0
  220. package/skills/orchestrator-refresh/SKILL.md +110 -35
  221. package/skills/plan-to-pr/SKILL.md +155 -0
  222. package/skills/plan-to-pr/reference/final-validation-tasks.md +15 -0
  223. package/skills/plan-to-pr/reference/model-routing.md +36 -0
  224. package/skills/plan-to-pr/reference/packet-contract.md +43 -0
  225. package/skills/plan-to-pr/reference/packet-schema.json +57 -0
  226. package/skills/plan-to-pr/reference/process-inventory.md +22 -0
  227. package/skills/plan-to-pr/reference/review-loop.md +33 -0
  228. package/skills/plan-to-pr/reference/run-record.schema.json +27 -0
  229. package/skills/plan-to-pr/reference/self-audit-tasks.md +15 -0
  230. package/skills/plan-to-pr/reference/task-seeds.md +14 -0
  231. package/skills/plan-to-pr/reference/task-ticket.md +38 -0
  232. package/skills/plan-to-pr/scripts/config/__init__.py +1 -0
  233. package/skills/plan-to-pr/scripts/config/constants.py +193 -0
  234. package/skills/plan-to-pr/scripts/create_packet.py +173 -0
  235. package/skills/plan-to-pr/scripts/test_create_packet.py +102 -0
  236. package/skills/plan-to-pr/scripts/test_validate_packet.py +256 -0
  237. package/skills/plan-to-pr/scripts/test_validate_protocol.py +135 -0
  238. package/skills/plan-to-pr/scripts/test_validate_run.py +158 -0
  239. package/skills/plan-to-pr/scripts/validate_packet.py +655 -0
  240. package/skills/plan-to-pr/scripts/validate_protocol.py +622 -0
  241. package/skills/plan-to-pr/scripts/validate_run.py +173 -0
  242. package/skills/plan-to-pr/test_skill_contract.py +207 -0
  243. package/skills/plan-to-pr/test_task_ticket_contract.py +151 -0
  244. package/skills/pr-converge/SKILL.md +472 -469
  245. package/skills/pr-converge/reference/examples.md +3 -3
  246. package/skills/pr-converge/reference/fix-protocol.md +1 -1
  247. package/skills/pr-converge/reference/ground-rules.md +7 -4
  248. package/skills/pr-converge/reference/multi-pr-orchestration.md +4 -1
  249. package/skills/pr-converge/reference/per-tick.md +5 -5
  250. package/skills/pr-converge/reference/progress-checklist.md +1 -1
  251. package/skills/pr-converge/scripts/check_convergence_gates.py +279 -279
  252. package/skills/pr-converge/scripts/test_check_convergence_codex.py +507 -507
  253. package/skills/pr-converge/scripts/test_check_convergence_gates.py +84 -84
  254. package/skills/pr-converge/test_step5_host_branch.py +1 -1
  255. package/skills/pr-fix-protocol/SKILL.md +1 -1
  256. package/skills/privacy-hygiene/SKILL.md +68 -68
  257. package/skills/prototype/SKILL.md +86 -0
  258. package/skills/prototype/reference/honest-limitations.md +23 -0
  259. package/skills/prototype/reference/promotion-tasks.md +23 -0
  260. package/skills/prototype/scripts/build_sandbox_settings.py +249 -0
  261. package/skills/prototype/scripts/conftest.py +15 -0
  262. package/skills/prototype/scripts/launch_sandbox.py +205 -0
  263. package/skills/prototype/scripts/probe_sandbox_safety.py +311 -0
  264. package/skills/prototype/scripts/prototype_scripts_constants/__init__.py +1 -0
  265. package/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
  266. package/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +41 -0
  267. package/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +23 -0
  268. package/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +45 -0
  269. package/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +10 -0
  270. package/skills/prototype/scripts/test_build_sandbox_settings.py +275 -0
  271. package/skills/prototype/scripts/test_launch_sandbox.py +303 -0
  272. package/skills/prototype/scripts/test_probe_sandbox_safety.py +284 -0
  273. package/skills/prototype/workflows/promotion.md +27 -0
  274. package/skills/prototype/workflows/sandbox.md +35 -0
  275. package/skills/release-notes-html/SKILL.md +164 -0
  276. package/skills/skill-builder/CLAUDE.md +3 -3
  277. package/skills/skill-builder/SKILL.md +5 -5
  278. package/skills/skill-builder/references/CLAUDE.md +1 -1
  279. package/skills/skill-builder/references/delegation-map.md +3 -3
  280. package/skills/skill-builder/references/description-field.md +1 -1
  281. package/skills/skill-builder/references/skill-modularity.md +2 -3
  282. package/skills/skill-builder/workflows/CLAUDE.md +1 -1
  283. package/skills/skill-builder/workflows/improve-skill.md +1 -1
  284. package/skills/skill-builder/workflows/new-skill.md +2 -2
  285. package/skills/task-build/CLAUDE.md +8 -7
  286. package/skills/task-build/SKILL.md +16 -8
  287. package/skills/task-build/reference/tool-routing.md +19 -0
  288. package/skills/team-advisor/SKILL.md +2 -2
  289. package/scripts/test_invoke_code_review.py +0 -672
  290. package/skills/closeout/reference/issue-body-templates.md +0 -108
@@ -1,131 +1,131 @@
1
- """Unit tests for the verified-commit-message-accuracy PreToolUse hook."""
2
-
3
- import importlib.util
4
- import pathlib
5
- import sys
6
-
7
- _HOOK_DIR = pathlib.Path(__file__).parent
8
- if str(_HOOK_DIR) not in sys.path:
9
- sys.path.insert(0, str(_HOOK_DIR))
10
-
11
- hook_spec = importlib.util.spec_from_file_location(
12
- "verified_commit_message_accuracy_blocker",
13
- _HOOK_DIR / "verified_commit_message_accuracy_blocker.py",
14
- )
15
- assert hook_spec is not None
16
- assert hook_spec.loader is not None
17
- hook_module = importlib.util.module_from_spec(hook_spec)
18
- hook_spec.loader.exec_module(hook_module)
19
- is_guarded_file = hook_module.is_guarded_file
20
- claims_blanket_comment_exemption = hook_module.claims_blanket_comment_exemption
21
- extract_written_text = hook_module.extract_written_text
22
- build_corrective_message = hook_module.build_corrective_message
23
-
24
- OFFENDING_MESSAGE = (
25
- "CORRECTIVE_MESSAGE = (\n"
26
- ' "BLOCKED: [VERIFIED_COMMIT_GATE] This branch surface has no passing "\n'
27
- ' "verification verdict. Spawn the code-verifier agent (Agent tool, "\n'
28
- " \"subagent_type 'code-verifier') with the task texts, the diff scope, \"\n"
29
- ' "and recorded baselines; when it finishes with a clean verdict the "\n'
30
- ' "SubagentStop hook mints the verdict and this command will pass. Any "\n'
31
- ' "file change after verification invalidates the verdict, so verify "\n'
32
- ' "last. Docs-, docstring-, comment-, and test-only surfaces are exempt "\n'
33
- ' "automatically."\n'
34
- ")\n"
35
- )
36
- ACCURATE_DOCS_EXEMPTION_MENTIONING_COMMENTS = (
37
- "Comments inside Python files are stripped; docs are exempt "
38
- "automatically by extension."
39
- )
40
- ACCURATE_MESSAGE = (
41
- "CORRECTIVE_MESSAGE = (\n"
42
- ' "BLOCKED: [VERIFIED_COMMIT_GATE] ... Docs and images are exempt by "\n'
43
- ' "extension, and Python files whose docstring- and comment-stripped AST "\n'
44
- ' "is unchanged."\n'
45
- ")\n"
46
- )
47
-
48
-
49
- def test_constants_file_is_guarded() -> None:
50
- assert is_guarded_file(
51
- "/repo/.claude/hooks/blocking/config/verified_commit_constants.py"
52
- )
53
-
54
-
55
- def test_unrelated_file_is_not_guarded() -> None:
56
- assert not is_guarded_file("/repo/.claude/hooks/blocking/gh_body_arg_blocker.py")
57
-
58
-
59
- def test_blanket_comment_exemption_claim_is_detected() -> None:
60
- assert claims_blanket_comment_exemption(OFFENDING_MESSAGE)
61
-
62
-
63
- def test_blanket_claim_detected_regardless_of_leading_words() -> None:
64
- assert claims_blanket_comment_exemption(
65
- "Comment-only surfaces are exempt automatically."
66
- )
67
-
68
-
69
- def test_accurate_exemption_wording_passes() -> None:
70
- assert not claims_blanket_comment_exemption(ACCURATE_MESSAGE)
71
-
72
-
73
- def test_accurate_docs_exemption_mentioning_comments_passes() -> None:
74
- assert not claims_blanket_comment_exemption(
75
- ACCURATE_DOCS_EXEMPTION_MENTIONING_COMMENTS
76
- )
77
-
78
-
79
- def test_comma_joined_docs_exemption_mentioning_comments_passes() -> None:
80
- assert not claims_blanket_comment_exemption(
81
- "Comments are handled, and docs are exempt automatically."
82
- )
83
-
84
-
85
- def test_python_ast_clause_mentioning_comments_passes() -> None:
86
- assert not claims_blanket_comment_exemption(
87
- "Python comment-stripped AST changes and docs are exempt automatically "
88
- "by extension"
89
- )
90
-
91
-
92
- def test_single_clause_python_ast_exemption_passes() -> None:
93
- assert not claims_blanket_comment_exemption(
94
- "Python files whose comment-stripped AST is unchanged are exempt "
95
- "automatically."
96
- )
97
-
98
-
99
- def test_commentary_word_stem_passes() -> None:
100
- assert not claims_blanket_comment_exemption(
101
- "Our commentary on the approach is exempt automatically from blame."
102
- )
103
-
104
-
105
- def test_corrective_message_names_only_the_two_real_exemptions() -> None:
106
- corrective_message = build_corrective_message()
107
- assert "exempt by extension" in corrective_message
108
- assert "docstring- and comment-stripped AST" in corrective_message
109
- assert "test file" not in corrective_message
110
- assert "by name convention" not in corrective_message
111
-
112
-
113
- def test_message_without_exemption_claim_passes() -> None:
114
- assert not claims_blanket_comment_exemption(
115
- 'CORRECTIVE_MESSAGE = "Spawn the code-verifier agent to earn a verdict."'
116
- )
117
-
118
-
119
- def test_write_content_is_extracted() -> None:
120
- written_text = extract_written_text({"content": OFFENDING_MESSAGE})
121
- assert claims_blanket_comment_exemption(written_text)
122
-
123
-
124
- def test_edit_new_string_is_extracted() -> None:
125
- written_text = extract_written_text({"new_string": OFFENDING_MESSAGE})
126
- assert claims_blanket_comment_exemption(written_text)
127
-
128
-
129
- def test_edit_new_string_with_accurate_wording_is_clean() -> None:
130
- written_text = extract_written_text({"new_string": ACCURATE_MESSAGE})
131
- assert not claims_blanket_comment_exemption(written_text)
1
+ """Unit tests for the verified-commit-message-accuracy PreToolUse hook."""
2
+
3
+ import importlib.util
4
+ import pathlib
5
+ import sys
6
+
7
+ _HOOK_DIR = pathlib.Path(__file__).parent
8
+ if str(_HOOK_DIR) not in sys.path:
9
+ sys.path.insert(0, str(_HOOK_DIR))
10
+
11
+ hook_spec = importlib.util.spec_from_file_location(
12
+ "verified_commit_message_accuracy_blocker",
13
+ _HOOK_DIR / "verified_commit_message_accuracy_blocker.py",
14
+ )
15
+ assert hook_spec is not None
16
+ assert hook_spec.loader is not None
17
+ hook_module = importlib.util.module_from_spec(hook_spec)
18
+ hook_spec.loader.exec_module(hook_module)
19
+ is_guarded_file = hook_module.is_guarded_file
20
+ claims_blanket_comment_exemption = hook_module.claims_blanket_comment_exemption
21
+ extract_written_text = hook_module.extract_written_text
22
+ build_corrective_message = hook_module.build_corrective_message
23
+
24
+ OFFENDING_MESSAGE = (
25
+ "CORRECTIVE_MESSAGE = (\n"
26
+ ' "BLOCKED: [VERIFIED_COMMIT_GATE] This branch surface has no passing "\n'
27
+ ' "verification verdict. Spawn the code-verifier agent (Agent tool, "\n'
28
+ " \"subagent_type 'code-verifier') with the task texts, the diff scope, \"\n"
29
+ ' "and recorded baselines; when it finishes with a clean verdict the "\n'
30
+ ' "SubagentStop hook mints the verdict and this command will pass. Any "\n'
31
+ ' "file change after verification invalidates the verdict, so verify "\n'
32
+ ' "last. Docs-, docstring-, comment-, and test-only surfaces are exempt "\n'
33
+ ' "automatically."\n'
34
+ ")\n"
35
+ )
36
+ ACCURATE_DOCS_EXEMPTION_MENTIONING_COMMENTS = (
37
+ "Comments inside Python files are stripped; docs are exempt "
38
+ "automatically by extension."
39
+ )
40
+ ACCURATE_MESSAGE = (
41
+ "CORRECTIVE_MESSAGE = (\n"
42
+ ' "BLOCKED: [VERIFIED_COMMIT_GATE] ... Docs and images are exempt by "\n'
43
+ ' "extension, and Python files whose docstring- and comment-stripped AST "\n'
44
+ ' "is unchanged."\n'
45
+ ")\n"
46
+ )
47
+
48
+
49
+ def test_constants_file_is_guarded() -> None:
50
+ assert is_guarded_file(
51
+ "/repo/.claude/hooks/blocking/config/verified_commit_constants.py"
52
+ )
53
+
54
+
55
+ def test_unrelated_file_is_not_guarded() -> None:
56
+ assert not is_guarded_file("/repo/.claude/hooks/blocking/gh_body_arg_blocker.py")
57
+
58
+
59
+ def test_blanket_comment_exemption_claim_is_detected() -> None:
60
+ assert claims_blanket_comment_exemption(OFFENDING_MESSAGE)
61
+
62
+
63
+ def test_blanket_claim_detected_regardless_of_leading_words() -> None:
64
+ assert claims_blanket_comment_exemption(
65
+ "Comment-only surfaces are exempt automatically."
66
+ )
67
+
68
+
69
+ def test_accurate_exemption_wording_passes() -> None:
70
+ assert not claims_blanket_comment_exemption(ACCURATE_MESSAGE)
71
+
72
+
73
+ def test_accurate_docs_exemption_mentioning_comments_passes() -> None:
74
+ assert not claims_blanket_comment_exemption(
75
+ ACCURATE_DOCS_EXEMPTION_MENTIONING_COMMENTS
76
+ )
77
+
78
+
79
+ def test_comma_joined_docs_exemption_mentioning_comments_passes() -> None:
80
+ assert not claims_blanket_comment_exemption(
81
+ "Comments are handled, and docs are exempt automatically."
82
+ )
83
+
84
+
85
+ def test_python_ast_clause_mentioning_comments_passes() -> None:
86
+ assert not claims_blanket_comment_exemption(
87
+ "Python comment-stripped AST changes and docs are exempt automatically "
88
+ "by extension"
89
+ )
90
+
91
+
92
+ def test_single_clause_python_ast_exemption_passes() -> None:
93
+ assert not claims_blanket_comment_exemption(
94
+ "Python files whose comment-stripped AST is unchanged are exempt "
95
+ "automatically."
96
+ )
97
+
98
+
99
+ def test_commentary_word_stem_passes() -> None:
100
+ assert not claims_blanket_comment_exemption(
101
+ "Our commentary on the approach is exempt automatically from blame."
102
+ )
103
+
104
+
105
+ def test_corrective_message_names_only_the_two_real_exemptions() -> None:
106
+ corrective_message = build_corrective_message()
107
+ assert "exempt by extension" in corrective_message
108
+ assert "docstring- and comment-stripped AST" in corrective_message
109
+ assert "test file" not in corrective_message
110
+ assert "by name convention" not in corrective_message
111
+
112
+
113
+ def test_message_without_exemption_claim_passes() -> None:
114
+ assert not claims_blanket_comment_exemption(
115
+ 'CORRECTIVE_MESSAGE = "Spawn the code-verifier agent to earn a verdict."'
116
+ )
117
+
118
+
119
+ def test_write_content_is_extracted() -> None:
120
+ written_text = extract_written_text({"content": OFFENDING_MESSAGE})
121
+ assert claims_blanket_comment_exemption(written_text)
122
+
123
+
124
+ def test_edit_new_string_is_extracted() -> None:
125
+ written_text = extract_written_text({"new_string": OFFENDING_MESSAGE})
126
+ assert claims_blanket_comment_exemption(written_text)
127
+
128
+
129
+ def test_edit_new_string_with_accurate_wording_is_clean() -> None:
130
+ written_text = extract_written_text({"new_string": ACCURATE_MESSAGE})
131
+ assert not claims_blanket_comment_exemption(written_text)
@@ -41,7 +41,7 @@ def test_scan_detects_job_scratch_path_forward_slash() -> None:
41
41
 
42
42
 
43
43
  def test_scan_detects_worktree_path() -> None:
44
- text = r"edited .claude\worktrees\feature\file.py"
44
+ text = r"edited C:\Users\me\.claude\worktrees\feature\file.py"
45
45
  assert scan_text_for_volatile_marker(text) == ".claude/worktrees/"
46
46
 
47
47
 
@@ -71,6 +71,103 @@ def test_scan_clean_body_returns_none() -> None:
71
71
  assert scan_text_for_volatile_marker(text) is None
72
72
 
73
73
 
74
+ def test_scan_bare_backticked_worktree_mention_is_allowed() -> None:
75
+ text = "the `.claude/worktrees/` prefix sits in ALL_TOOLING_STATE_PREFIXES"
76
+ assert scan_text_for_volatile_marker(text) is None
77
+
78
+
79
+ def test_scan_bare_start_of_text_worktree_mention_is_allowed() -> None:
80
+ text = ".claude/worktrees/ entries are skipped by the manifest"
81
+ assert scan_text_for_volatile_marker(text) is None
82
+
83
+
84
+ def test_scan_windows_absolute_worktree_path_is_detected() -> None:
85
+ text = r"C:\Users\me\.claude\worktrees\wt-1\notes.md"
86
+ assert scan_text_for_volatile_marker(text) == ".claude/worktrees/"
87
+
88
+
89
+ def test_scan_home_anchored_worktree_path_is_detected() -> None:
90
+ text = "~/.claude/worktrees/wt-2/file.py"
91
+ assert scan_text_for_volatile_marker(text) == ".claude/worktrees/"
92
+
93
+
94
+ def test_scan_posix_absolute_job_scratch_path_is_detected() -> None:
95
+ text = "/home/me/.claude-editor/jobs/j1/log.txt"
96
+ assert scan_text_for_volatile_marker(text) == ".claude-editor/jobs/"
97
+
98
+
99
+ def test_scan_bare_parenthesized_job_scratch_mention_is_allowed() -> None:
100
+ text = "(.claude-editor/jobs/) as a directory name in prose"
101
+ assert scan_text_for_volatile_marker(text) is None
102
+
103
+
104
+ def test_scan_bare_mention_before_anchored_path_is_detected() -> None:
105
+ text = (
106
+ ".claude/worktrees/ is the manifest key; the live path is "
107
+ r"C:\Users\me\.claude\worktrees\wt-1\notes.md"
108
+ )
109
+ assert scan_text_for_volatile_marker(text) == ".claude/worktrees/"
110
+
111
+
112
+ def test_scan_unchanged_bare_markers_still_detected() -> None:
113
+ assert scan_text_for_volatile_marker(r"saved to %TEMP%\x") == "%temp%"
114
+ assert scan_text_for_volatile_marker("log at /tmp/x") == "/tmp/"
115
+ assert (
116
+ scan_text_for_volatile_marker(r"C:\Users\x\AppData\Local\Temp\y")
117
+ == "appdata/local/temp"
118
+ )
119
+
120
+
121
+ def test_scan_space_prefixed_relative_worktree_path_is_detected() -> None:
122
+ text = "see .claude/worktrees/wt-1/notes.md"
123
+ assert scan_text_for_volatile_marker(text) == ".claude/worktrees/"
124
+
125
+
126
+ def test_scan_cd_relative_worktree_path_is_detected() -> None:
127
+ text = "cd .claude/worktrees/wt-199 && pytest"
128
+ assert scan_text_for_volatile_marker(text) == ".claude/worktrees/"
129
+
130
+
131
+ def test_scan_start_of_text_relative_worktree_path_is_detected() -> None:
132
+ text = ".claude/worktrees/wt-1/notes.md"
133
+ assert scan_text_for_volatile_marker(text) == ".claude/worktrees/"
134
+
135
+
136
+ def test_scan_markdown_link_relative_worktree_target_is_detected() -> None:
137
+ text = "[notes](.claude/worktrees/wt-1/notes.md)"
138
+ assert scan_text_for_volatile_marker(text) == ".claude/worktrees/"
139
+
140
+
141
+ def test_scan_space_prefixed_relative_job_scratch_path_is_detected() -> None:
142
+ text = "see .claude-editor/jobs/j1/log.txt"
143
+ assert scan_text_for_volatile_marker(text) == ".claude-editor/jobs/"
144
+
145
+
146
+ def test_scan_backslash_relative_worktree_path_is_detected() -> None:
147
+ text = r"see .claude\worktrees\wt-1\notes.md"
148
+ assert scan_text_for_volatile_marker(text) == ".claude/worktrees/"
149
+
150
+
151
+ def test_scan_prose_child_token_after_marker_is_detected() -> None:
152
+ text = "the .claude/worktrees/wt-name pattern"
153
+ assert scan_text_for_volatile_marker(text) == ".claude/worktrees/"
154
+
155
+
156
+ def test_scan_relative_worktree_path_with_hyphen_child_is_detected() -> None:
157
+ text = "see .claude/worktrees/-scratch/notes.md"
158
+ assert scan_text_for_volatile_marker(text) == ".claude/worktrees/"
159
+
160
+
161
+ def test_scan_placeholder_child_segment_is_allowed() -> None:
162
+ text = "worktrees live under `.claude/worktrees/<name>`"
163
+ assert scan_text_for_volatile_marker(text) is None
164
+
165
+
166
+ def test_scan_sentence_final_marker_period_is_allowed() -> None:
167
+ text = "the constant is `.claude/worktrees/`. Next."
168
+ assert scan_text_for_volatile_marker(text) is None
169
+
170
+
74
171
  def test_gh_comment_with_job_scratch_path_is_blocked() -> None:
75
172
  command = (
76
173
  'gh pr comment 669 --body "Contact sheet at '
@@ -147,7 +244,7 @@ def test_mcp_issue_comment_body_blocked() -> None:
147
244
 
148
245
 
149
246
  def test_mcp_review_comment_param_blocked() -> None:
150
- tool_input: dict[str, object] = {"comment": r"see .claude\worktrees\x\file"}
247
+ tool_input: dict[str, object] = {"comment": r"see C:\Users\me\.claude\worktrees\x\file"}
151
248
  assert _body_names_volatile_path(
152
249
  "mcp__plugin_github_github__add_reply_to_pull_request_comment", tool_input
153
250
  )
@@ -177,6 +274,21 @@ def test_extract_gh_post_body_texts_returns_inline_and_file(tmp_path: pathlib.Pa
177
274
  assert "file body text" in all_texts
178
275
 
179
276
 
277
+ def test_gh_issue_edit_quoting_bare_constant_is_allowed(tmp_path: pathlib.Path) -> None:
278
+ body_file = tmp_path / "issue_body.md"
279
+ body_file.write_text(
280
+ "The manifest skips these tooling-state prefixes:\n"
281
+ ' ".claude/worktrees/",\n'
282
+ ' ".claude-editor/jobs/",\n'
283
+ "Neither is a machine-local path.",
284
+ encoding="utf-8",
285
+ )
286
+ command = f"gh issue edit 134 --body-file {body_file}"
287
+ all_body_texts = extract_gh_post_body_texts(command)
288
+ assert all_body_texts
289
+ assert hook_module._first_volatile_marker(all_body_texts) is None
290
+
291
+
180
292
  def test_hook_subprocess_denies_volatile_gh_comment() -> None:
181
293
  payload = {
182
294
  "tool_name": "Bash",