claude-dev-env 2.4.0 → 2.7.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 (309) hide show
  1. package/CLAUDE.md +26 -59
  2. package/_shared/pr-loop/scripts/_claude_permissions_common.py +84 -0
  3. package/_shared/pr-loop/scripts/code_rules_gate.py +6 -3
  4. package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +12 -2
  5. package/_shared/pr-loop/scripts/code_rules_gate_parts/baseline_import_isolation.py +309 -0
  6. package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_regression.py +540 -0
  7. package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_running.py +206 -70
  8. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/__init__.py +1 -0
  9. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/_repo_test_helpers.py +76 -0
  10. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_baseline_import_isolation.py +248 -0
  11. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_regression.py +309 -0
  12. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_running.py +91 -58
  13. package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +306 -306
  14. package/_shared/pr-loop/scripts/pr_loop_shared_constants/claude_permissions_constants.py +44 -0
  15. package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +202 -0
  16. package/_shared/pr-loop/scripts/pr_loop_shared_constants/copilot_quota_constants.py +24 -24
  17. package/_shared/pr-loop/scripts/pr_loop_shared_constants/stale_worktree_rule_sweep_constants.py +107 -107
  18. package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +290 -48
  19. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +42 -2
  20. package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +36 -0
  21. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +100 -1
  22. package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +497 -497
  23. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +311 -2
  24. package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep.py +301 -301
  25. package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep_constants.py +85 -85
  26. package/_shared/pr-loop/worker-spawn.md +1 -1
  27. package/agents/CLAUDE.md +3 -2
  28. package/agents/caveman.md +0 -1
  29. package/agents/clasp-deployment-orchestrator.md +0 -1
  30. package/agents/clean-coder.md +0 -1
  31. package/agents/code-advisor.md +0 -1
  32. package/agents/code-quality-agent.md +1 -2
  33. package/agents/code-verifier.md +36 -8
  34. package/agents/deep-research.md +0 -1
  35. package/agents/docs-agent.md +0 -1
  36. package/agents/git-commit-crafter.md +0 -1
  37. package/agents/issue-tracker.md +42 -0
  38. package/agents/plan-packet-validator.md +0 -1
  39. package/agents/pr-description-writer.md +0 -1
  40. package/agents/test_agent_frontmatter.py +67 -18
  41. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +143 -141
  42. package/bin/CLAUDE.md +68 -5
  43. package/bin/codex-compat.mjs +104 -0
  44. package/bin/codex-compat.test.mjs +51 -0
  45. package/bin/ever-shipped-skills.mjs +1 -0
  46. package/bin/install-constants.mjs +88 -0
  47. package/bin/install.mjs +1138 -114
  48. package/bin/install.prune.test.mjs +869 -19
  49. package/bin/install.test.mjs +906 -2
  50. package/codex-capability-map.json +13 -0
  51. package/commands/implement.md +1 -1
  52. package/commands/right-size.md +1 -1
  53. package/docs/CLAUDE.md +1 -0
  54. package/docs/CODE_RULES.md +2 -0
  55. package/docs/codex-compatibility.md +25 -0
  56. package/docs/host-pool-health-monitor.md +102 -0
  57. package/docs/nas-ssh-invocation.md +96 -12
  58. package/docs/references/CLAUDE.md +4 -2
  59. package/docs/references/advisor-tool.md +13 -0
  60. package/docs/references/code-review-enforcement.md +35 -0
  61. package/docs/references/team-advisor-skill.md +14 -0
  62. package/hooks/blocking/CLAUDE.md +4 -0
  63. package/hooks/blocking/code_review_pr_create_gate.py +7 -3
  64. package/hooks/blocking/code_review_push_gate.py +9 -4
  65. package/hooks/blocking/code_review_stamp_directory_write_blocker.py +8 -0
  66. package/hooks/blocking/config/__init__.py +5 -5
  67. package/hooks/blocking/config/code_review_enforcement_constants.py +40 -7
  68. package/hooks/blocking/config/test_code_review_enforcement_constants.py +58 -0
  69. package/hooks/blocking/config/verified_commit_constants.py +160 -159
  70. package/hooks/blocking/eli11_reply_enforcer.py +479 -0
  71. package/hooks/blocking/gh_body_arg_blocker.py +1 -1
  72. package/hooks/blocking/nas_ssh_binary_enforcer.py +8 -46
  73. package/hooks/blocking/orchestrator_refresh_reschedule_gate.py +256 -0
  74. package/hooks/blocking/pre_tool_use_dispatcher.py +24 -24
  75. package/hooks/blocking/shell_substitution_blocker.py +129 -0
  76. package/hooks/blocking/state_description_blocker.py +1 -1
  77. package/hooks/blocking/stop_dispatcher.py +1 -1
  78. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +2 -3
  79. package/hooks/blocking/test_code_review_pr_create_gate.py +14 -0
  80. package/hooks/blocking/test_code_review_push_gate.py +16 -0
  81. package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +19 -0
  82. package/hooks/blocking/test_eli11_reply_enforcer.py +457 -0
  83. package/hooks/blocking/test_orchestrator_refresh_reschedule_gate.py +231 -0
  84. package/hooks/blocking/test_pre_tool_use_dispatcher.py +10 -1
  85. package/hooks/blocking/test_shell_substitution_blocker.py +124 -0
  86. package/hooks/blocking/test_stop_dispatcher.py +23 -0
  87. package/hooks/blocking/test_unscoped_search_blocker.py +102 -0
  88. package/hooks/blocking/test_verdict_directory_write_blocker.py +804 -808
  89. package/hooks/blocking/test_verification_verdict_store.py +54 -0
  90. package/hooks/blocking/test_verified_commit_gate.py +581 -581
  91. package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +131 -131
  92. package/hooks/blocking/unscoped_search_blocker.py +391 -0
  93. package/hooks/blocking/verdict_directory_write_blocker.py +687 -687
  94. package/hooks/blocking/verification_verdict_store.py +1039 -1036
  95. package/hooks/blocking/verified_commit_message_accuracy_blocker.py +167 -167
  96. package/hooks/blocking/verifier_verdict_minter.py +280 -280
  97. package/hooks/git-hooks/CLAUDE.md +3 -0
  98. package/hooks/git-hooks/conftest.py +30 -0
  99. package/hooks/git-hooks/gate_utils.py +2 -2
  100. package/hooks/git-hooks/git_hooks_constants/__init__.py +41 -2
  101. package/hooks/git-hooks/pre_push.py +75 -4
  102. package/hooks/git-hooks/pre_push_base_reference.py +166 -0
  103. package/hooks/git-hooks/test_config.py +0 -15
  104. package/hooks/git-hooks/test_gate_utils.py +3 -15
  105. package/hooks/git-hooks/test_pre_commit.py +1 -15
  106. package/hooks/git-hooks/test_pre_push.py +257 -23
  107. package/hooks/git-hooks/test_pre_push_base_reference.py +339 -0
  108. package/hooks/hooks.json +10 -12
  109. package/hooks/hooks_constants/CLAUDE.md +7 -2
  110. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +4 -4
  111. package/hooks/hooks_constants/eli11_reply_enforcer_constants.py +101 -0
  112. package/hooks/hooks_constants/enter_worktree_prefetch_constants.py +18 -18
  113. package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +2 -8
  114. package/hooks/hooks_constants/orchestrator_refresh_reschedule_gate_constants.py +48 -0
  115. package/hooks/hooks_constants/ruff_integration_constants.py +16 -0
  116. package/hooks/hooks_constants/shell_command_segments.py +82 -0
  117. package/hooks/hooks_constants/shell_substitution_blocker_constants.py +67 -0
  118. package/hooks/hooks_constants/stop_dispatcher_constants.py +1 -0
  119. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +5 -6
  120. package/hooks/hooks_constants/test_stop_dispatcher_constants.py +1 -0
  121. package/hooks/hooks_constants/unscoped_search_blocker_constants.py +153 -0
  122. package/hooks/lifecycle/enter_worktree_origin_prefetch.py +163 -146
  123. package/hooks/lifecycle/test_enter_worktree_origin_prefetch.py +185 -178
  124. package/hooks/pyproject.toml +1 -0
  125. package/hooks/validators/CLAUDE.md +1 -0
  126. package/hooks/validators/config/__init__.py +0 -0
  127. package/hooks/validators/config/directory_exemption_constants.py +183 -0
  128. package/hooks/validators/config/test_directory_exemption_constants.py +21 -0
  129. package/hooks/validators/conftest.py +4 -0
  130. package/hooks/validators/ruff_integration.py +49 -5
  131. package/hooks/validators/run_all_validators.py +206 -9
  132. package/hooks/validators/test_directory_exemption_constants.py +185 -0
  133. package/hooks/validators/test_python_antipattern_checks.py +110 -5
  134. package/hooks/validators/test_ruff_integration.py +92 -1
  135. package/hooks/validators/test_run_all_validators.py +115 -68
  136. package/hooks/validators/test_run_all_validators_pretooluse.py +159 -1
  137. package/package.json +13 -3
  138. package/rules/CLAUDE.md +17 -22
  139. package/rules/agent-spawn-protocol.md +6 -6
  140. package/rules/anti-corollary-tests.md +1 -1
  141. package/rules/bdd.md +1 -1
  142. package/rules/cleanup-temp-files.md +10 -4
  143. package/rules/code-standards.md +7 -0
  144. package/rules/conservative-action.md +1 -5
  145. package/rules/context7.md +0 -4
  146. package/rules/destructive-commands.md +47 -0
  147. package/rules/doc-inventory-integrity.md +48 -0
  148. package/rules/doc-prose-cuts.md +58 -0
  149. package/rules/docstring-prose-matches-implementation.md +53 -44
  150. package/rules/durable-post-artifacts.md +0 -4
  151. package/rules/eli11-replies.md +31 -0
  152. package/rules/explore-thoroughly.md +4 -4
  153. package/rules/falsify-before-green.md +68 -0
  154. package/rules/file-global-constants.md +1 -1
  155. package/rules/filesystem-search.md +51 -0
  156. package/rules/gh-cli-conventions.md +27 -0
  157. package/rules/git-workflow.md +26 -0
  158. package/rules/hedging-claims.md +9 -0
  159. package/rules/long-horizon-autonomy.md +0 -4
  160. package/rules/measurement-denominators.md +48 -0
  161. package/rules/nas-ssh-invocation.md +23 -5
  162. package/rules/parallel-tools.md +2 -2
  163. package/rules/plain-illustrative-docstrings.md +3 -7
  164. package/rules/plain-language.md +2 -0
  165. package/rules/proof-of-work-pr-comments.md +0 -4
  166. package/rules/re-stage-before-commit.md +2 -0
  167. package/rules/research-mode.md +10 -0
  168. package/rules/shell-invocation.md +21 -0
  169. package/rules/testing.md +4 -0
  170. package/rules/verified-commit-gate-skip.md +3 -27
  171. package/rules/verify-before-asking.md +5 -0
  172. package/rules/windows-filesystem-safe.md +1 -1
  173. package/rules/workers-done-before-complete.md +4 -0
  174. package/scripts/CLAUDE.md +1 -0
  175. package/scripts/Capture-PoolHealth.ps1 +410 -0
  176. package/scripts/Migrate-ShellPolicy.ps1 +1 -1
  177. package/scripts/_code_review_test_support.py +404 -0
  178. package/scripts/claude_chain_runner.py +141 -1
  179. package/scripts/codex_capability_bridge.py +171 -0
  180. package/scripts/codex_compat_materializer.py +1087 -0
  181. package/scripts/codex_compat_watcher.py +502 -0
  182. package/scripts/conftest.py +16 -1
  183. package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -1
  184. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +9 -0
  185. package/scripts/dev_env_scripts_constants/code_review_constants.py +37 -0
  186. package/scripts/invoke_code_review.py +11 -4
  187. package/scripts/resolve_worker_spawn.py +626 -626
  188. package/scripts/spawn_grok_batch.py +672 -672
  189. package/scripts/sync_to_cursor/rules.py +0 -10
  190. package/scripts/test_claude_chain_runner.py +131 -0
  191. package/scripts/test_invoke_code_review.py +85 -908
  192. package/scripts/test_invoke_code_review_chain.py +70 -0
  193. package/scripts/test_invoke_code_review_cli.py +192 -0
  194. package/scripts/test_invoke_code_review_contract.py +256 -0
  195. package/scripts/test_invoke_code_review_git.py +123 -0
  196. package/scripts/test_invoke_code_review_mode.py +99 -0
  197. package/scripts/test_resolve_worker_spawn.py +1014 -1014
  198. package/scripts/tests/test_code_review_constants.py +80 -0
  199. package/scripts/tests/test_codex_capability_bridge.py +91 -0
  200. package/scripts/tests/test_codex_compat_materializer.py +632 -0
  201. package/scripts/tests/test_codex_compat_watcher.py +599 -0
  202. package/scripts/tests/test_sync_to_cursor.py +0 -1
  203. package/skills/CLAUDE.md +2 -0
  204. package/skills/auditing-claude-config/SKILL.md +114 -114
  205. package/skills/autoconverge/SKILL.md +427 -427
  206. package/skills/autoconverge/reference/convergence.md +24 -3
  207. package/skills/autoconverge/workflow/CLAUDE.md +1 -0
  208. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +3 -3
  209. package/skills/autoconverge/workflow/converge.contract.test.mjs +1263 -1263
  210. package/skills/autoconverge/workflow/converge.mjs +168 -1
  211. package/skills/autoconverge/workflow/converge.p2-advance.test.mjs +202 -0
  212. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +2 -2
  213. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +2 -2
  214. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +2 -2
  215. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +2 -2
  216. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +2 -2
  217. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +2 -2
  218. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +2 -2
  219. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +2 -2
  220. package/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +265 -265
  221. package/skills/bugteam/reference/copilot-gap-analysis.md +1 -1
  222. package/skills/closeout/SKILL.md +33 -50
  223. package/skills/codex-review/scripts/codex_review_scripts_constants/run_constants.py +8 -0
  224. package/skills/codex-review/scripts/run_codex_review.py +233 -1
  225. package/skills/codex-review/scripts/test_run_codex_review.py +189 -0
  226. package/skills/condensing-instructions/SKILL.md +72 -0
  227. package/skills/copilot-review/SKILL.md +119 -119
  228. package/skills/e-code-review/SKILL.md +52 -0
  229. package/skills/e-code-review/reference/fix.md +54 -0
  230. package/skills/e-code-review/reference/loop.md +43 -0
  231. package/skills/e-code-review/reference/low.md +57 -0
  232. package/skills/e-code-review/reference/medium.md +153 -0
  233. package/skills/e-code-review/reference/xhigh.md +182 -0
  234. package/skills/e-simplify/SKILL.md +97 -0
  235. package/skills/fresh-branch/CLAUDE.md +1 -1
  236. package/skills/fresh-branch/SKILL.md +5 -6
  237. package/skills/fresh-branch/scripts/create_fresh_branch.py +42 -24
  238. package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +1 -3
  239. package/skills/fresh-branch/scripts/test_create_fresh_branch.py +30 -126
  240. package/skills/issue-tracker/SKILL.md +92 -0
  241. package/skills/issue-tracker/reference/epic-and-sub-issue-model.md +55 -0
  242. package/skills/issue-tracker/reference/handoff-schema.md +64 -0
  243. package/skills/issue-tracker/reference/operation-matrix.md +41 -0
  244. package/skills/orchestrator/SKILL.md +177 -22
  245. package/skills/orchestrator/scripts/status_gate.py +625 -0
  246. package/skills/orchestrator/scripts/status_gate_constants/__init__.py +1 -0
  247. package/skills/orchestrator/scripts/status_gate_constants/config/__init__.py +1 -0
  248. package/skills/orchestrator/scripts/status_gate_constants/config/constants.py +47 -0
  249. package/skills/orchestrator/scripts/test_status_gate.py +439 -0
  250. package/skills/orchestrator-refresh/SKILL.md +129 -35
  251. package/skills/plan-to-pr/SKILL.md +155 -0
  252. package/skills/plan-to-pr/reference/final-validation-tasks.md +15 -0
  253. package/skills/plan-to-pr/reference/model-routing.md +36 -0
  254. package/skills/plan-to-pr/reference/packet-contract.md +43 -0
  255. package/skills/plan-to-pr/reference/packet-schema.json +57 -0
  256. package/skills/plan-to-pr/reference/process-inventory.md +22 -0
  257. package/skills/plan-to-pr/reference/review-loop.md +33 -0
  258. package/skills/plan-to-pr/reference/run-record.schema.json +27 -0
  259. package/skills/plan-to-pr/reference/self-audit-tasks.md +15 -0
  260. package/skills/plan-to-pr/reference/task-seeds.md +14 -0
  261. package/skills/plan-to-pr/reference/task-ticket.md +38 -0
  262. package/skills/plan-to-pr/scripts/config/__init__.py +1 -0
  263. package/skills/plan-to-pr/scripts/config/constants.py +193 -0
  264. package/skills/plan-to-pr/scripts/create_packet.py +173 -0
  265. package/skills/plan-to-pr/scripts/test_create_packet.py +102 -0
  266. package/skills/plan-to-pr/scripts/test_validate_packet.py +256 -0
  267. package/skills/plan-to-pr/scripts/test_validate_protocol.py +135 -0
  268. package/skills/plan-to-pr/scripts/test_validate_run.py +158 -0
  269. package/skills/plan-to-pr/scripts/validate_packet.py +655 -0
  270. package/skills/plan-to-pr/scripts/validate_protocol.py +622 -0
  271. package/skills/plan-to-pr/scripts/validate_run.py +173 -0
  272. package/skills/plan-to-pr/test_skill_contract.py +207 -0
  273. package/skills/plan-to-pr/test_task_ticket_contract.py +151 -0
  274. package/skills/pr-converge/SKILL.md +472 -469
  275. package/skills/pr-converge/reference/examples.md +3 -3
  276. package/skills/pr-converge/reference/fix-protocol.md +1 -1
  277. package/skills/pr-converge/reference/ground-rules.md +7 -4
  278. package/skills/pr-converge/reference/multi-pr-orchestration.md +4 -1
  279. package/skills/pr-converge/reference/per-tick.md +5 -5
  280. package/skills/pr-converge/reference/progress-checklist.md +1 -1
  281. package/skills/pr-converge/scripts/check_convergence_gates.py +279 -279
  282. package/skills/pr-converge/scripts/test_check_convergence_codex.py +507 -507
  283. package/skills/pr-converge/scripts/test_check_convergence_gates.py +84 -84
  284. package/skills/pr-converge/test_step5_host_branch.py +1 -1
  285. package/skills/pr-fix-protocol/SKILL.md +1 -1
  286. package/skills/privacy-hygiene/SKILL.md +68 -68
  287. package/skills/privacy-hygiene/reference/sweep-procedure.md +1 -1
  288. package/skills/prototype/workflows/promotion.md +1 -1
  289. package/skills/release-notes-html/SKILL.md +164 -0
  290. package/skills/session-log/SKILL.md +1 -1
  291. package/skills/task-build/CLAUDE.md +8 -7
  292. package/skills/task-build/SKILL.md +16 -8
  293. package/skills/task-build/reference/tool-routing.md +19 -0
  294. package/rules/claude-md-orphan-file.md +0 -28
  295. package/rules/cleanup-command-forms.md +0 -23
  296. package/rules/code-reviews.md +0 -11
  297. package/rules/env-var-table-code-drift.md +0 -10
  298. package/rules/gh-body-file.md +0 -5
  299. package/rules/gh-paginate.md +0 -3
  300. package/rules/hook-prose-matches-detector.md +0 -15
  301. package/rules/no-historical-clutter.md +0 -26
  302. package/rules/no-inline-destructive-literals.md +0 -9
  303. package/rules/no-justification-noise.md +0 -61
  304. package/rules/package-inventory-stale-entry.md +0 -25
  305. package/rules/right-sized-engineering.md +0 -28
  306. package/rules/self-contained-docs.md +0 -17
  307. package/rules/shell-invocation-policy.md +0 -5
  308. package/rules/tdd.md +0 -7
  309. package/skills/closeout/reference/issue-body-templates.md +0 -108
package/hooks/hooks.json CHANGED
@@ -102,6 +102,16 @@
102
102
  }
103
103
  ]
104
104
  },
105
+ {
106
+ "matcher": "ScheduleWakeup|CronCreate",
107
+ "hooks": [
108
+ {
109
+ "type": "command",
110
+ "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/orchestrator_refresh_reschedule_gate.py",
111
+ "timeout": 15
112
+ }
113
+ ]
114
+ },
105
115
  {
106
116
  "matcher": "Skill",
107
117
  "hooks": [
@@ -192,18 +202,6 @@
192
202
  ]
193
203
  }
194
204
  ],
195
- "SubagentStop": [
196
- {
197
- "matcher": "",
198
- "hooks": [
199
- {
200
- "type": "command",
201
- "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/verifier_verdict_minter.py",
202
- "timeout": 30
203
- }
204
- ]
205
- }
206
- ],
207
205
  "SessionEnd": [
208
206
  {
209
207
  "matcher": "",
@@ -31,6 +31,7 @@ Shared constant modules imported by hooks throughout the `hooks/` tree. Each fil
31
31
  | `duplicate_function_body_constants.py` | Hashing and comparison config for the duplicate-body check |
32
32
  | `duplicate_rmtree_helper_blocker_constants.py` | Sanctioned Windows-safe rmtree helper names, the definition pattern, and the exempt-path set for the duplicate-rmtree-helper blocker |
33
33
  | `dynamic_stderr_handler.py` | `DynamicStderrHandler` — a logging handler that resolves `sys.stderr` at emit time (for testability) |
34
+ | `eli11_reply_enforcer_constants.py` | Reply-shape thresholds (word cap, enforced word floor, bullet cap, lead-line window, per-line word cap, over-packed line cap), the `Long form:` escape prefix, the bullet / numbered-step / table-row / link-target / word patterns, the imperative instruction verbs, and the user-facing notice for the ELI11 reply-shape Stop hook |
34
35
  | `gh_pr_author_swap_constants.py` | Constants for the PR-author swap enforcement hooks |
35
36
  | `hardcoded_user_path_constants.py` | Patterns for detecting hardcoded home-directory paths |
36
37
  | `harness_scratchpad_constants.py` | Fixed path components (`claude` / `claude-` user-directory name and prefix, `scratchpad` leaf name), the PreToolUse session-id payload key, and the `CLAUDE_CODE_SESSION_ID` environment variable name that the code-rules and TDD gates use to recognize an existing harness session scratchpad directory |
@@ -45,7 +46,7 @@ Shared constant modules imported by hooks throughout the `hooks/` tree. Each fil
45
46
  | `multi_edit_reconstruction.py` | `apply_edits()` / `edits_for_tool()` — shared helpers that reconstruct the post-edit content of an Edit or MultiEdit, imported by the blockers that judge post-edit content |
46
47
  | `mypy_integration_constants.py` | Path markers (``.git``, ``.py``, ``pyproject.toml``) for mypy project-root resolution |
47
48
  | `mypy_validator_cache_constants.py` | Cache paths and tunables for the mypy_validator per-session caches |
48
- | `nas_ssh_binary_enforcer_constants.py` | Bash tool name, ssh-family basenames, OpenSSH binary path suffixes, launcher-wrapper set, shell control-operator tokens and split pattern, leading-assignment and duration patterns, and the batch-mode pattern for the NAS ssh binary enforcer |
49
+ | `nas_ssh_binary_enforcer_constants.py` | Bash tool name, ssh-family basenames, OpenSSH binary path suffixes, and the batch-mode pattern for the NAS ssh binary enforcer; segment helpers come from `shell_command_segments.py` |
49
50
  | `open_questions_in_plans_blocker_constants.py` | Patterns for detecting unresolved open questions in plan documents |
50
51
  | `orphan_css_class_constants.py` | Scan radius and selector patterns for the orphan-CSS-class check |
51
52
  | `package_inventory_stale_blocker_constants.py` | Inventory document names, production code extensions, backtick token pattern, smallest inventory size, exempt names, scan budget, and block-message text for the package-inventory stale-entry blocker |
@@ -64,12 +65,15 @@ Shared constant modules imported by hooks throughout the `hooks/` tree. Each fil
64
65
  | `pytest_testpaths_orphan_blocker_constants.py` | Marker filename, section and key names, test-file pattern, search budget, and block-message text for the pytest unregistered-test-directory blocker |
65
66
  | `python_style_checks_constants.py` | Command-line argument count and blank-line count between top-level functions for the style validator |
66
67
  | `reviewer_spawn_gate_constants.py` | Bash tool name, the sentinel marker, the Copilot and Bugbot trigger markers, the availability-script relative path and override env-var name, and the deny-message template for the reviewer-spawn gate |
68
+ | `ruff_integration_constants.py` | ``NO_COLOR`` / ``FORCE_COLOR`` environment variable names for plain ruff diagnostics |
67
69
  | `send_user_file_open_locally_blocker_constants.py` | Tool name, proactive status, and the block message for the open-locally attach blocker |
68
70
  | `sensitive_file_protector_constants.py` | Sensitive filename patterns, the committed-template suffixes that earn an exemption, the write/edit tool names, and the deny decision and message template for `sensitive_file_protector` |
69
71
  | `session_edit_stage_gate_constants.py` | Tracker filename prefix/suffix, JSON payload key, edit tool name set, session-id sanitize pattern, lock filename suffix and lock-acquire timing, git diff command, commit flag escapes, and deny-message template shared by the session edit stage gate trio |
70
72
  | `session_env_cleanup_constants.py` | Stale-age threshold and directory names for the session-env cleanup hook |
71
73
  | `session_handoff_blocker_constants.py` | Trigger phrases for the session-handoff blocker |
72
74
  | `setup_project_paths_constants.py` | Encoding policy, BOM marker, and registry meta-key used across multiple hooks |
75
+ | `shell_command_segments.py` | Shell tokenization helpers shared by the segment-walking blockers: segment splitting on control operators, a segment's effective leading program, and a token's basename |
76
+ | `shell_substitution_blocker_constants.py` | Tool name, payload keys, the four substitution-detection patterns, the deny decision shape, and the corrective message for the shell-substitution blocker |
73
77
  | `stale_comment_reference_blocker_constants.py` | Identifier pattern, comment stopwords, and denial text for the stale-comment-reference blocker |
74
78
  | `state_description_blocker_constants.py` | The set of historical/comparative phrases the state-description blocker rejects, plus the docstring-extraction and mention-span patterns for its Python docstring scan |
75
79
  | `stop_dispatcher_constants.py` | Ordered hosted-hook roster and Stop block payload field names for the Stop-chain dispatcher |
@@ -80,6 +84,7 @@ Shared constant modules imported by hooks throughout the `hooks/` tree. Each fil
80
84
  | `task_list_loop_starter_constants.py` | The one-line task-list instruction and the full session-start directive text for the task-list loop starter hook |
81
85
  | `test_layout_constants.py` | The named constants for the test-layout write-time checks |
82
86
  | `text_stripping.py` | `strip_code_and_quotes()` — shared helper that removes fenced code blocks, inline code, and blockquotes from prose, imported by the Stop-hook prose blockers |
87
+ | `unscoped_search_blocker_constants.py` | Tool names, `find` and listing program basenames, Get-ChildItem option sets, unscoped-root patterns, the flag/value token stride, and the deny message for the unscoped-search blocker |
83
88
  | `unused_module_import_constants.py` | Patterns for detecting unused module-level imports |
84
89
  | `volatile_path_in_post_blocker_constants.py` | Volatile path markers, affected `gh` post subcommands, MCP body param names, and the corrective message for the volatile-path post blocker |
85
90
  | `windows_rmtree_blocker_constants.py` | The unsafe `shutil.rmtree` pattern and the safe replacement pattern |
@@ -90,6 +95,6 @@ Shared constant modules imported by hooks throughout the `hooks/` tree. Each fil
90
95
  - Most files in this package are pure constants modules — no side effects, no I/O.
91
96
  - Hooks import from this package with `from hooks_constants.<module> import <CONSTANT>`.
92
97
  - Tests for these modules live beside them as `test_<module>.py`. Run with `python -m pytest hooks_constants/test_<name>.py`.
93
- - `dynamic_stderr_handler.py`, `pre_tool_use_stdin.py`, `multi_edit_reconstruction.py`, `hosted_hook_runner.py`, and `text_stripping.py` are utility modules (not pure constants) but live here because they are shared across many hooks.
98
+ - `dynamic_stderr_handler.py`, `pre_tool_use_stdin.py`, `multi_edit_reconstruction.py`, `hosted_hook_runner.py`, and `text_stripping.py` are utility modules (not pure constants) but live here because they hold logic shared across hook surfaces.
94
99
  - `local_identity.py` is a loader: it reads the environment or `~/.claude/local-identity.json` (its path overridable via `CLAUDE_LOCAL_IDENTITY_PATH`) to resolve private NAS values (with committed placeholder defaults), the PII commit-scan exempt-repo slug set (`CLAUDE_PII_EXEMPT_REPOS` / `pii_exempt_repositories`), and the per-repository allowlisted-values mapping (`pii_allowlisted_values`).
95
100
 
@@ -56,6 +56,10 @@ ALL_BASH_HOSTED_HOOK_ENTRIES: tuple[BashHostedHookEntry, ...] = (
56
56
  BashHostedHookEntry("blocking/es_exe_path_rewriter.py", ALL_BASH_ONLY_TOOL_NAMES),
57
57
  BashHostedHookEntry("blocking/destructive_command_blocker.py", ALL_BASH_ONLY_TOOL_NAMES),
58
58
  BashHostedHookEntry("blocking/gh_body_arg_blocker.py", ALL_BASH_ONLY_TOOL_NAMES),
59
+ BashHostedHookEntry("blocking/shell_substitution_blocker.py", ALL_BASH_ONLY_TOOL_NAMES),
60
+ BashHostedHookEntry(
61
+ "blocking/unscoped_search_blocker.py", ALL_BASH_AND_POWERSHELL_TOOL_NAMES
62
+ ),
59
63
  BashHostedHookEntry("blocking/nas_ssh_binary_enforcer.py", ALL_BASH_ONLY_TOOL_NAMES),
60
64
  BashHostedHookEntry("blocking/volatile_path_in_post_blocker.py", ALL_BASH_ONLY_TOOL_NAMES),
61
65
  BashHostedHookEntry(
@@ -71,10 +75,6 @@ ALL_BASH_HOSTED_HOOK_ENTRIES: tuple[BashHostedHookEntry, ...] = (
71
75
  BashHostedHookEntry("blocking/convergence_gate_blocker.py", ALL_BASH_ONLY_TOOL_NAMES),
72
76
  BashHostedHookEntry("blocking/windows_rmtree_blocker.py", ALL_BASH_ONLY_TOOL_NAMES),
73
77
  BashHostedHookEntry("blocking/gh_pr_author_enforcer.py", ALL_BASH_ONLY_TOOL_NAMES),
74
- BashHostedHookEntry("blocking/verified_commit_gate.py", ALL_BASH_AND_POWERSHELL_TOOL_NAMES),
75
- BashHostedHookEntry(
76
- "blocking/verdict_directory_write_blocker.py", ALL_BASH_AND_POWERSHELL_TOOL_NAMES
77
- ),
78
78
  BashHostedHookEntry("blocking/code_review_push_gate.py", ALL_BASH_AND_POWERSHELL_TOOL_NAMES),
79
79
  BashHostedHookEntry(
80
80
  "blocking/code_review_pr_create_gate.py", ALL_BASH_AND_POWERSHELL_TOOL_NAMES
@@ -0,0 +1,101 @@
1
+ """Tunables for the eli11_reply_enforcer Stop hook.
2
+
3
+ ::
4
+
5
+ NUMBERED_STEP_PATTERN
6
+ ok: "1. Run the script" flag: "1.5% of hosts still fail."
7
+ ALL_IMPERATIVE_OBJECT_LEAD_WORDS
8
+ ok: "Run the migration" flag: "Run time stays under a second"
9
+
10
+ Holds the reply-shape thresholds the `eli11-replies` rule names, the compiled
11
+ patterns that read a reply's structure, the closed sets naming an instruction
12
+ verb and its object, and the notice the hook shows when it blocks.
13
+ """
14
+
15
+ import re
16
+
17
+ __all__ = [
18
+ "ACTION_FIRST_LEAD_LINE_COUNT",
19
+ "ALL_IMPERATIVE_INSTRUCTION_VERBS",
20
+ "ALL_IMPERATIVE_OBJECT_LEAD_WORDS",
21
+ "ALPHABETIC_WORD_PATTERN",
22
+ "BULLET_LINE_PATTERN",
23
+ "COUNTABLE_WORD_PATTERN",
24
+ "IMPERATIVE_OBJECT_TOKEN_PATTERN",
25
+ "LINK_TARGET_PATTERN",
26
+ "LIST_MARKER_PREFIX_PATTERN",
27
+ "LONG_FORM_ESCAPE_PREFIX",
28
+ "MARKDOWN_LEAD_MARKER_PATTERN",
29
+ "MAXIMUM_BULLET_LINE_COUNT",
30
+ "MAXIMUM_OVERPACKED_LINE_COUNT",
31
+ "MAXIMUM_REPLY_WORD_COUNT",
32
+ "MAXIMUM_WORDS_PER_LINE",
33
+ "MINIMUM_ENFORCED_WORD_COUNT",
34
+ "NUMBERED_STEP_PATTERN",
35
+ "TABLE_ROW_PATTERN",
36
+ "TARGET_BULLET_LINE_COUNT",
37
+ "USER_FACING_ELI11_NOTICE",
38
+ "VIOLATION_SEPARATOR",
39
+ ]
40
+
41
+ MAXIMUM_REPLY_WORD_COUNT = 120
42
+ MINIMUM_ENFORCED_WORD_COUNT = 60
43
+ MAXIMUM_BULLET_LINE_COUNT = 6
44
+ TARGET_BULLET_LINE_COUNT = 3
45
+ ACTION_FIRST_LEAD_LINE_COUNT = 6
46
+ MAXIMUM_WORDS_PER_LINE = 20
47
+ MAXIMUM_OVERPACKED_LINE_COUNT = 2
48
+
49
+ LONG_FORM_ESCAPE_PREFIX = "long form:"
50
+ VIOLATION_SEPARATOR = "; "
51
+ USER_FACING_ELI11_NOTICE = "Agent wrote a long reply - rewriting it short..."
52
+
53
+ TABLE_ROW_PATTERN = re.compile(r"^[ \t]*\|.*\|[ \t]*$", re.MULTILINE)
54
+ LINK_TARGET_PATTERN = re.compile(r"(?:https?://|www\.)\S+", re.IGNORECASE)
55
+ COUNTABLE_WORD_PATTERN = re.compile(r"\S*[A-Za-z0-9]\S*")
56
+ ALPHABETIC_WORD_PATTERN = re.compile(r"[A-Za-z][A-Za-z'’-]*")
57
+ BULLET_LINE_PATTERN = re.compile(r"^[ \t]*[-*+][ \t]+\S")
58
+ NUMBERED_STEP_PATTERN = re.compile(r"^[ \t]*(?:\*\*)?\d{1,2}[.)](?:\*\*)?[ \t]+\S")
59
+ LIST_MARKER_PREFIX_PATTERN = re.compile(
60
+ r"^[ \t]*(?:[-*+][ \t]+|(?:\*\*)?\d{1,2}[.)][ \t]*)?(?:\*\*)?"
61
+ )
62
+ MARKDOWN_LEAD_MARKER_PATTERN = re.compile(r"^(?:[ \t]*(?:>+|#{1,6}|\*\*|__|\*|_))+[ \t]*")
63
+
64
+ ALL_IMPERATIVE_INSTRUCTION_VERBS = (
65
+ "click",
66
+ "copy",
67
+ "delete",
68
+ "do",
69
+ "download",
70
+ "install",
71
+ "merge",
72
+ "open",
73
+ "paste",
74
+ "restart",
75
+ "run",
76
+ "save",
77
+ )
78
+
79
+ ALL_IMPERATIVE_OBJECT_LEAD_WORDS = (
80
+ "a",
81
+ "all",
82
+ "an",
83
+ "any",
84
+ "both",
85
+ "each",
86
+ "every",
87
+ "into",
88
+ "it",
89
+ "my",
90
+ "our",
91
+ "that",
92
+ "the",
93
+ "them",
94
+ "these",
95
+ "this",
96
+ "those",
97
+ "to",
98
+ "your",
99
+ )
100
+
101
+ IMPERATIVE_OBJECT_TOKEN_PATTERN = re.compile(r"^\d|[`/\\]|\.[A-Za-z]{1,4}$")
@@ -1,18 +1,18 @@
1
- """Configuration constants for the ``enter_worktree_origin_prefetch`` hook.
2
-
3
- The hook keeps a freshly created worktree from starting on a stale cached
4
- ``refs/remotes/origin/<default-branch>`` value: it fetches that ref right
5
- before ``EnterWorktree`` resolves its base.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- ENTER_WORKTREE_TOOL_NAME: str = "EnterWorktree"
11
- ENTER_WORKTREE_PATH_INPUT_KEY: str = "path"
12
-
13
- ORIGIN_REMOTE_NAME: str = "origin"
14
- ORIGIN_HEAD_SYMBOLIC_REF: str = "refs/remotes/origin/HEAD"
15
- ORIGIN_REMOTE_REF_PREFIX: str = "refs/remotes/origin/"
16
-
17
- GIT_SYMBOLIC_REF_TIMEOUT_SECONDS: int = 5
18
- GIT_FETCH_TIMEOUT_SECONDS: int = 15
1
+ """Configuration constants for the ``enter_worktree_origin_prefetch`` hook.
2
+
3
+ The hook keeps a freshly created worktree from starting on a stale cached
4
+ ``refs/remotes/origin/<default-branch>`` value: it fetches that ref right
5
+ before ``EnterWorktree`` resolves its base.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ ENTER_WORKTREE_TOOL_NAME: str = "EnterWorktree"
11
+ ENTER_WORKTREE_PATH_INPUT_KEY: str = "path"
12
+
13
+ ORIGIN_REMOTE_NAME: str = "origin"
14
+ ORIGIN_HEAD_SYMBOLIC_REF: str = "refs/remotes/origin/HEAD"
15
+ ORIGIN_REMOTE_REF_PREFIX: str = "refs/remotes/origin/"
16
+
17
+ GIT_SYMBOLIC_REF_TIMEOUT_SECONDS: int = 5
18
+ GIT_FETCH_TIMEOUT_SECONDS: int = 15
@@ -1,9 +1,8 @@
1
1
  """Constants for the NAS ssh binary enforcer hook.
2
2
 
3
3
  Holds the Bash tool name, the ssh-family command basenames, the Windows OpenSSH
4
- binary path suffixes, the launcher-wrapper set, the shell control-operator tokens
5
- and their split pattern, the leading-assignment and launcher-duration patterns,
6
- and the batch-mode pattern.
4
+ binary path suffixes, and the batch-mode pattern. Segment splitting and
5
+ leading-program resolution come from ``shell_command_segments``.
7
6
  """
8
7
 
9
8
  import re
@@ -18,9 +17,4 @@ ALL_OPENSSH_BINARY_PATH_SUFFIXES = (
18
17
  "/openssh/scp.exe",
19
18
  "/openssh/sftp.exe",
20
19
  )
21
- ALL_LAUNCHER_WRAPPER_COMMANDS = frozenset({"timeout", "nohup", "nice", "stdbuf", "setsid", "env"})
22
- ALL_SHELL_CONTROL_OPERATOR_TOKENS = frozenset({"&&", "||", ";", "|", "&", "|&"})
23
- CONTROL_OPERATOR_SPLIT_PATTERN = re.compile(r"(&&|\|\||;|\|&|\||(?<!>)&(?!>))")
24
- LEADING_ASSIGNMENT_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*=")
25
- LAUNCHER_DURATION_PATTERN = re.compile(r"^\d+[a-z]*$", re.IGNORECASE)
26
20
  BATCH_MODE_PATTERN = re.compile(r"batchmode\s*=\s*yes", re.IGNORECASE)
@@ -0,0 +1,48 @@
1
+ """Constants for the orchestrator-refresh ScheduleWakeup reschedule gate."""
2
+
3
+ from __future__ import annotations
4
+
5
+ SCHEDULE_WAKEUP_TOOL_NAME = "ScheduleWakeup"
6
+ CRON_CREATE_TOOL_NAME = "CronCreate"
7
+ ALL_SCHEDULE_TOOL_NAMES: frozenset[str] = frozenset(
8
+ {
9
+ SCHEDULE_WAKEUP_TOOL_NAME,
10
+ CRON_CREATE_TOOL_NAME,
11
+ }
12
+ )
13
+
14
+ ORCHESTRATOR_REFRESH_PROMPT_TOKEN = "/orchestrator-refresh"
15
+ RUN_SLUG_PROMPT_FLAG = "--run-slug"
16
+ RUN_SLUG_PROMPT_VALUE_PATTERN = r"(?:=\s*|\s+)([^\s]+)"
17
+ PROMPT_FIELD_NAME = "prompt"
18
+ TOOL_NAME_FIELD_NAME = "tool_name"
19
+ TOOL_INPUT_FIELD_NAME = "tool_input"
20
+ CWD_FIELD_NAME = "cwd"
21
+
22
+ REASON_CRON_CREATE_FORBIDDEN = "cron_create_forbidden"
23
+
24
+ DENY_REASON_TEMPLATE = (
25
+ "BLOCKED: orchestrator-refresh re-arm denied ({reason}). "
26
+ "Use one-shot delayed wake only (never CronCreate / never recurring). "
27
+ "At most one re-arm slot: begin-firing at refresh start; "
28
+ "should-reschedule then create then claim-rearm (never claim before "
29
+ "ScheduleWakeup). Status must be active: "
30
+ "python skills/orchestrator/scripts/status_gate.py set "
31
+ "--status active. When finished: --status done. Never re-arm an idle loop."
32
+ )
33
+
34
+ CALLING_HOOK_NAME = "orchestrator_refresh_reschedule_gate.py"
35
+ HOOK_EVENT_NAME = "PreToolUse"
36
+
37
+ ALL_SKILL_SCRIPTS_RELATIVE_PARTS: tuple[str, ...] = (
38
+ "skills",
39
+ "orchestrator",
40
+ "scripts",
41
+ )
42
+ PLUGIN_ROOT_ENV_VAR = "CLAUDE_PLUGIN_ROOT"
43
+ ALL_HOME_SKILLS_RELATIVE_PARTS: tuple[str, ...] = (
44
+ ".claude",
45
+ "skills",
46
+ "orchestrator",
47
+ "scripts",
48
+ )
@@ -0,0 +1,16 @@
1
+ """Environment variable names used when ruff emits plain diagnostics.
2
+
3
+ The PreToolUse gate parses ``path:line:col:`` prefixes. Forcing ``NO_COLOR`` and
4
+ clearing ``FORCE_COLOR`` keeps ANSI codes out of those fields so baseline
5
+ scoping can locate each finding.
6
+ """
7
+
8
+ __all__ = [
9
+ "FORCE_COLOR_ENVIRONMENT_VARIABLE_NAME",
10
+ "NO_COLOR_ENABLED_VALUE",
11
+ "NO_COLOR_ENVIRONMENT_VARIABLE_NAME",
12
+ ]
13
+
14
+ NO_COLOR_ENVIRONMENT_VARIABLE_NAME: str = "NO_COLOR"
15
+ FORCE_COLOR_ENVIRONMENT_VARIABLE_NAME: str = "FORCE_COLOR"
16
+ NO_COLOR_ENABLED_VALUE: str = "1"
@@ -0,0 +1,82 @@
1
+ """Shared shell-command segment helpers for Bash PreToolUse blockers.
2
+
3
+ Tokenizes a command into simple-command segments on control operators and finds
4
+ each segment's effective leading program after env assignments and launcher
5
+ wrappers. NAS ssh enforcement and unscoped-search blocking both need this
6
+ shape, so one module owns it.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import re
12
+
13
+ __all__ = [
14
+ "ALL_LAUNCHER_WRAPPER_COMMANDS",
15
+ "ALL_SHELL_CONTROL_OPERATOR_TOKENS",
16
+ "CONTROL_OPERATOR_SPLIT_PATTERN",
17
+ "LEADING_ASSIGNMENT_PATTERN",
18
+ "LAUNCHER_DURATION_PATTERN",
19
+ "token_basename",
20
+ "split_into_segments",
21
+ "effective_leading_program",
22
+ ]
23
+
24
+ ALL_LAUNCHER_WRAPPER_COMMANDS: frozenset[str] = frozenset(
25
+ {"timeout", "nohup", "nice", "stdbuf", "setsid", "env"}
26
+ )
27
+ ALL_SHELL_CONTROL_OPERATOR_TOKENS: frozenset[str] = frozenset(
28
+ {"&&", "||", ";", "|", "&", "|&"}
29
+ )
30
+ CONTROL_OPERATOR_SPLIT_PATTERN = re.compile(r"(&&|\|\||;|\|&|\||(?<!>)&(?!>))")
31
+ LEADING_ASSIGNMENT_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*=")
32
+ LAUNCHER_DURATION_PATTERN = re.compile(r"^\d+[a-z]*$", re.IGNORECASE)
33
+
34
+
35
+ def token_basename(token: str) -> str:
36
+ """Return the lowercased basename of a path-or-command token."""
37
+ return token.replace("\\", "/").rsplit("/", 1)[-1].lower()
38
+
39
+
40
+ def split_into_segments(all_command_tokens: list[str]) -> list[list[str]]:
41
+ """Split tokens into simple-command segments on shell control operators.
42
+
43
+ Glued operators on a single token are exploded first, then the stream is
44
+ cut on ``&&`` / ``||`` / ``;`` / ``|`` / ``&`` / ``|&``.
45
+ """
46
+ all_exploded_tokens: list[str] = []
47
+ for each_token in all_command_tokens:
48
+ for each_fragment in CONTROL_OPERATOR_SPLIT_PATTERN.split(each_token):
49
+ if each_fragment:
50
+ all_exploded_tokens.append(each_fragment)
51
+ all_segments: list[list[str]] = []
52
+ current_segment: list[str] = []
53
+ for each_token in all_exploded_tokens:
54
+ if each_token in ALL_SHELL_CONTROL_OPERATOR_TOKENS:
55
+ all_segments.append(current_segment)
56
+ current_segment = []
57
+ continue
58
+ current_segment.append(each_token)
59
+ all_segments.append(current_segment)
60
+ return all_segments
61
+
62
+
63
+ def effective_leading_program(all_segment_tokens: list[str]) -> str | None:
64
+ """Return the effective program token after assignments and launcher wrappers.
65
+
66
+ Skips ``VAR=value`` prefixes and known launchers (``timeout``, ``env``, …)
67
+ plus their flags and duration arguments. Returns None when no program token
68
+ remains.
69
+ """
70
+ has_seen_launcher_wrapper = False
71
+ for each_token in all_segment_tokens:
72
+ if LEADING_ASSIGNMENT_PATTERN.match(each_token):
73
+ continue
74
+ if token_basename(each_token) in ALL_LAUNCHER_WRAPPER_COMMANDS:
75
+ has_seen_launcher_wrapper = True
76
+ continue
77
+ if has_seen_launcher_wrapper and (
78
+ each_token.startswith("-") or LAUNCHER_DURATION_PATTERN.match(each_token)
79
+ ):
80
+ continue
81
+ return each_token
82
+ return None
@@ -0,0 +1,67 @@
1
+ """Constants for the shell-substitution PreToolUse Bash blocker.
2
+
3
+ Holds the tool name, the payload keys, the four detection patterns, the deny
4
+ decision shape, and the corrective message. The hook imports these so no
5
+ single-use constant sits at its file scope.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+
12
+ __all__ = [
13
+ "BASH_TOOL_NAME",
14
+ "TOOL_NAME_KEY",
15
+ "TOOL_INPUT_KEY",
16
+ "COMMAND_KEY",
17
+ "HOOK_SPECIFIC_OUTPUT_KEY",
18
+ "HOOK_EVENT_NAME_KEY",
19
+ "HOOK_EVENT_NAME",
20
+ "PERMISSION_DECISION_KEY",
21
+ "DENY_DECISION",
22
+ "PERMISSION_DECISION_REASON_KEY",
23
+ "DOLLAR_PAREN_PATTERN",
24
+ "EVEN_BACKSLASH_BACKTICK_PATTERN",
25
+ "PROCESS_SUBSTITUTION_PATTERN",
26
+ "SINGLE_QUOTED_RUN_PATTERN",
27
+ "STRIPPED_RUN_REPLACEMENT",
28
+ "CORRECTIVE_MESSAGE",
29
+ ]
30
+
31
+ BASH_TOOL_NAME = "Bash"
32
+
33
+ TOOL_NAME_KEY = "tool_name"
34
+ TOOL_INPUT_KEY = "tool_input"
35
+ COMMAND_KEY = "command"
36
+
37
+ HOOK_SPECIFIC_OUTPUT_KEY = "hookSpecificOutput"
38
+ HOOK_EVENT_NAME_KEY = "hookEventName"
39
+ HOOK_EVENT_NAME = "PreToolUse"
40
+ PERMISSION_DECISION_KEY = "permissionDecision"
41
+ DENY_DECISION = "deny"
42
+ PERMISSION_DECISION_REASON_KEY = "permissionDecisionReason"
43
+
44
+ DOLLAR_PAREN_PATTERN = re.compile(r"\$\((?!\()")
45
+ EVEN_BACKSLASH_BACKTICK_PATTERN = re.compile(r"(?<!\\)(?:\\\\)*`")
46
+ PROCESS_SUBSTITUTION_PATTERN = re.compile(r"[<>]\(")
47
+ SINGLE_QUOTED_RUN_PATTERN = re.compile(r"'[^']*'")
48
+ STRIPPED_RUN_REPLACEMENT = ""
49
+
50
+ CORRECTIVE_MESSAGE = (
51
+ "BLOCKED [shell-substitution]: command contains $(...), backtick, or "
52
+ "<(...)/>(...) process substitution, which forces a permission prompt "
53
+ "because the Claude Code allowlist matcher does not descend into "
54
+ "substitutions.\n\n"
55
+ "Fix: split into two Bash tool calls, or rewrite without substitution.\n"
56
+ ' cd X && echo "$(git rev-parse HEAD)"\n'
57
+ "becomes either two calls:\n"
58
+ " 1) cd X\n"
59
+ " 2) git rev-parse HEAD\n"
60
+ "or one substitution-free call:\n"
61
+ " git -C X rev-parse HEAD\n\n"
62
+ "Process substitution example:\n"
63
+ " diff <(cat a) <(cat b)\n"
64
+ "becomes two separate Bash calls to `cat a` and `cat b`, with the diff "
65
+ "performed on the captured outputs.\n\n"
66
+ "See `shell-invocation.md` in the rules directory for full guidance."
67
+ )
@@ -22,5 +22,6 @@ ALL_STOP_HOSTED_HOOK_PATHS: tuple[str, ...] = (
22
22
  "blocking/question_to_user_enforcer.py",
23
23
  "blocking/intent_only_ending_blocker.py",
24
24
  "blocking/session_handoff_blocker.py",
25
+ "blocking/eli11_reply_enforcer.py",
25
26
  "diagnostic/hook_log_stop_wrapper.py",
26
27
  )
@@ -25,6 +25,8 @@ _EXPECTED_BASH_ORDER = (
25
25
  "blocking/es_exe_path_rewriter.py",
26
26
  "blocking/destructive_command_blocker.py",
27
27
  "blocking/gh_body_arg_blocker.py",
28
+ "blocking/shell_substitution_blocker.py",
29
+ "blocking/unscoped_search_blocker.py",
28
30
  "blocking/nas_ssh_binary_enforcer.py",
29
31
  "blocking/volatile_path_in_post_blocker.py",
30
32
  "blocking/pii_prevention_blocker.py",
@@ -38,17 +40,14 @@ _EXPECTED_BASH_ORDER = (
38
40
  "blocking/convergence_gate_blocker.py",
39
41
  "blocking/windows_rmtree_blocker.py",
40
42
  "blocking/gh_pr_author_enforcer.py",
41
- "blocking/verified_commit_gate.py",
42
- "blocking/verdict_directory_write_blocker.py",
43
43
  "blocking/code_review_push_gate.py",
44
44
  "blocking/code_review_pr_create_gate.py",
45
45
  "blocking/code_review_stamp_directory_write_blocker.py",
46
46
  )
47
47
 
48
48
  _POWERSHELL_APPLICABLE = (
49
+ "blocking/unscoped_search_blocker.py",
49
50
  "blocking/pii_prevention_blocker.py",
50
- "blocking/verified_commit_gate.py",
51
- "blocking/verdict_directory_write_blocker.py",
52
51
  "blocking/code_review_push_gate.py",
53
52
  "blocking/code_review_pr_create_gate.py",
54
53
  "blocking/code_review_stamp_directory_write_blocker.py",
@@ -69,8 +68,8 @@ def test_every_hook_applies_to_the_bash_tool() -> None:
69
68
  assert BASH_TOOL_NAME in each_entry.applicable_tool_names
70
69
 
71
70
 
72
- def test_powershell_applicable_hooks_include_pii_and_verified_commit_pair() -> None:
73
- """PowerShell runs the PII gate plus the verified-commit pair."""
71
+ def test_powershell_applicable_hooks_include_the_shared_gates() -> None:
72
+ """PowerShell runs the unscoped-search and PII gates plus the review family."""
74
73
  powershell_hooks = tuple(
75
74
  each_entry.script_relative_path
76
75
  for each_entry in ALL_BASH_HOSTED_HOOK_ENTRIES
@@ -16,6 +16,7 @@ _EXPECTED_STOP_ORDER = (
16
16
  "blocking/question_to_user_enforcer.py",
17
17
  "blocking/intent_only_ending_blocker.py",
18
18
  "blocking/session_handoff_blocker.py",
19
+ "blocking/eli11_reply_enforcer.py",
19
20
  "diagnostic/hook_log_stop_wrapper.py",
20
21
  )
21
22