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
@@ -0,0 +1,153 @@
1
+ """Constants for the unscoped-search PreToolUse Bash/PowerShell blocker.
2
+
3
+ Holds tool names, program basenames, Get-ChildItem option sets, the clustered
4
+ short-option pattern, unscoped root patterns (drive root, posix root alias, UNC
5
+ share root), the shared flag/value token stride, and the deny message. Find
6
+ global option flags reuse ``destructive_command_segment_constants`` so both find
7
+ parsers stay on one set.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import re
13
+
14
+ from hooks_constants.destructive_command_segment_constants import (
15
+ ALL_FIND_GLOBAL_OPTION_FLAGS_TAKING_A_VALUE,
16
+ ALL_FIND_GLOBAL_OPTION_FLAGS_WITHOUT_VALUE,
17
+ FIND_OPTIMIZATION_LEVEL_OPTION_PREFIX,
18
+ )
19
+
20
+ __all__ = [
21
+ "BASH_TOOL_NAME",
22
+ "POWERSHELL_TOOL_NAME",
23
+ "ALL_SUPPORTED_TOOL_NAMES",
24
+ "ALL_FIND_PROGRAM_BASENAMES",
25
+ "ALL_POWERSHELL_LISTING_PROGRAM_BASENAMES",
26
+ "ALL_UNIX_LS_PROGRAM_BASENAMES",
27
+ "ALL_LISTING_PROGRAM_BASENAMES",
28
+ "ALL_FIND_GLOBAL_OPTION_FLAGS_WITHOUT_VALUE",
29
+ "ALL_FIND_GLOBAL_OPTION_FLAGS_TAKING_A_VALUE",
30
+ "FIND_OPTIMIZATION_LEVEL_OPTION_PREFIX",
31
+ "FLAG_AND_VALUE_TOKEN_STRIDE",
32
+ "ALL_FIND_EXPRESSION_INTRODUCER_TOKENS",
33
+ "ALL_POWERSHELL_RECURSE_FLAGS",
34
+ "ALL_UNIX_LS_RECURSE_FLAGS",
35
+ "UNIX_LS_RECURSE_SHORT_OPTION_LETTER",
36
+ "CLUSTERED_SHORT_OPTION_PATTERN",
37
+ "ALL_POWERSHELL_RECURSE_FLAG_PREFIXES",
38
+ "ALL_POWERSHELL_PATH_FLAGS",
39
+ "ALL_POWERSHELL_PATH_FLAG_PREFIXES",
40
+ "ALL_TRUTHY_RECURSE_COLON_VALUES",
41
+ "ALL_STRING_EXECUTING_SHELL_BASENAMES",
42
+ "ALL_STRING_EXEC_COMMAND_FLAGS",
43
+ "ALL_UNSCOPED_HOME_LITERALS_CASEFOLD",
44
+ "GIT_BASH_DRIVE_ROOT_PATTERN",
45
+ "WINDOWS_DRIVE_ROOT_PATTERN",
46
+ "POSIX_ROOT_ALIAS_PATTERN",
47
+ "UNC_SHARE_ROOT_PATTERN",
48
+ "HOOK_EVENT_NAME",
49
+ "DENY_DECISION",
50
+ "CALLING_HOOK_NAME",
51
+ "CORRECTIVE_MESSAGE",
52
+ ]
53
+
54
+ BASH_TOOL_NAME = "Bash"
55
+ POWERSHELL_TOOL_NAME = "PowerShell"
56
+ ALL_SUPPORTED_TOOL_NAMES: frozenset[str] = frozenset(
57
+ {BASH_TOOL_NAME, POWERSHELL_TOOL_NAME}
58
+ )
59
+
60
+ ALL_FIND_PROGRAM_BASENAMES: frozenset[str] = frozenset({"find", "find.exe"})
61
+ ALL_POWERSHELL_LISTING_PROGRAM_BASENAMES: frozenset[str] = frozenset(
62
+ {"get-childitem", "gci", "dir"}
63
+ )
64
+ ALL_UNIX_LS_PROGRAM_BASENAMES: frozenset[str] = frozenset({"ls", "ls.exe"})
65
+ ALL_LISTING_PROGRAM_BASENAMES: frozenset[str] = (
66
+ ALL_POWERSHELL_LISTING_PROGRAM_BASENAMES | ALL_UNIX_LS_PROGRAM_BASENAMES
67
+ )
68
+
69
+ FLAG_AND_VALUE_TOKEN_STRIDE = 2
70
+ ALL_FIND_EXPRESSION_INTRODUCER_TOKENS: frozenset[str] = frozenset({"!", "(", ")"})
71
+
72
+ ALL_POWERSHELL_RECURSE_FLAGS: frozenset[str] = frozenset(
73
+ {"-recurse", "-r", "-rec", "/s"}
74
+ )
75
+ ALL_UNIX_LS_RECURSE_FLAGS: frozenset[str] = frozenset({"-R", "--recursive"})
76
+ UNIX_LS_RECURSE_SHORT_OPTION_LETTER = "R"
77
+ CLUSTERED_SHORT_OPTION_PATTERN = re.compile(r"^-[A-Za-z]+$")
78
+ ALL_POWERSHELL_RECURSE_FLAG_PREFIXES: tuple[str, ...] = (
79
+ "-recurse:",
80
+ "-r:",
81
+ )
82
+ ALL_POWERSHELL_PATH_FLAGS: frozenset[str] = frozenset(
83
+ {"-path", "-literalpath", "-lp"}
84
+ )
85
+ ALL_POWERSHELL_PATH_FLAG_PREFIXES: tuple[str, ...] = (
86
+ "-path:",
87
+ "-literalpath:",
88
+ "-lp:",
89
+ )
90
+ ALL_TRUTHY_RECURSE_COLON_VALUES: frozenset[str] = frozenset(
91
+ {"$true", "true", "1", "yes", "on"}
92
+ )
93
+
94
+ ALL_STRING_EXECUTING_SHELL_BASENAMES: frozenset[str] = frozenset(
95
+ {
96
+ "bash",
97
+ "bash.exe",
98
+ "sh",
99
+ "sh.exe",
100
+ "pwsh",
101
+ "pwsh.exe",
102
+ "powershell",
103
+ "powershell.exe",
104
+ }
105
+ )
106
+ ALL_STRING_EXEC_COMMAND_FLAGS: frozenset[str] = frozenset(
107
+ {"-c", "-lc", "-command", "-encodedcommand"}
108
+ )
109
+
110
+ _ALL_UNSCOPED_HOME_LITERALS: frozenset[str] = frozenset(
111
+ {
112
+ "~",
113
+ "~/",
114
+ "~\\",
115
+ "$HOME",
116
+ "$HOME/",
117
+ "$HOME\\",
118
+ "${HOME}",
119
+ "${HOME}/",
120
+ "${HOME}\\",
121
+ "%USERPROFILE%",
122
+ "%USERPROFILE%\\",
123
+ "%USERPROFILE%/",
124
+ "$env:USERPROFILE",
125
+ "$env:USERPROFILE\\",
126
+ "$env:USERPROFILE/",
127
+ "$env:HOME",
128
+ "$env:HOME\\",
129
+ "$env:HOME/",
130
+ }
131
+ )
132
+ ALL_UNSCOPED_HOME_LITERALS_CASEFOLD: frozenset[str] = frozenset(
133
+ each_literal.casefold() for each_literal in _ALL_UNSCOPED_HOME_LITERALS
134
+ )
135
+
136
+ GIT_BASH_DRIVE_ROOT_PATTERN = re.compile(r"^/[a-zA-Z]/?$")
137
+ WINDOWS_DRIVE_ROOT_PATTERN = re.compile(r"^[a-zA-Z]:[\\/]*$")
138
+ POSIX_ROOT_ALIAS_PATTERN = re.compile(r"^/(?:\.|/)*$")
139
+ UNC_SHARE_ROOT_PATTERN = re.compile(r"^//[^/]+/[^/]+/*$")
140
+
141
+ HOOK_EVENT_NAME = "PreToolUse"
142
+ DENY_DECISION = "deny"
143
+ CALLING_HOOK_NAME = "unscoped_search_blocker.py"
144
+
145
+ CORRECTIVE_MESSAGE = (
146
+ "Unscoped filesystem search blocked. Never start find/Get-ChildItem at `/`, "
147
+ "a drive root (`/c`, `C:\\`), bare home (`~`, `$HOME`), or a network share "
148
+ "root (`\\\\server\\share`). Scope the walk to a project or worktree path "
149
+ "(for example `find . -name '*.py'` or "
150
+ "`find packages/claude-dev-env -iname code_rules_gate.py`). On Windows prefer "
151
+ "es.exe with a path scope, or the Grep/Glob tools. Batch shell work; avoid "
152
+ "parallel full-tree searches that contend for the shell and lock the host."
153
+ )
@@ -1,146 +1,163 @@
1
- #!/usr/bin/env python3
2
- """PreToolUse hook: fetch origin's default branch before EnterWorktree creates one.
3
-
4
- ``EnterWorktree`` in its default ``fresh`` mode bases a new worktree on the
5
- locally cached ``refs/remotes/origin/<default-branch>`` ref and only runs
6
- ``git fetch`` when that ref is missing entirely -- so a worktree created
7
- without a recent fetch silently starts behind the remote. This hook fetches
8
- that ref immediately before the tool resolves its base, so the ref it reads
9
- is current.
10
-
11
- ::
12
-
13
- EnterWorktree({}) # tool_input has no "path"
14
- |
15
- v
16
- this hook: git fetch origin <default-branch> # best-effort, never blocks
17
- |
18
- v
19
- EnterWorktree resolves refs/remotes/origin/<default-branch> # now fresh
20
-
21
- The hook is a no-op when ``tool_input`` carries a ``path`` (switching into an
22
- already-existing worktree needs no fresh base) and always exits 0 -- a failed
23
- or slow fetch never blocks worktree creation.
24
- """
25
-
26
- from __future__ import annotations
27
-
28
- import json
29
- import subprocess
30
- import sys
31
- from pathlib import Path
32
-
33
- _hooks_dir = str(Path(__file__).resolve().parent.parent)
34
- if _hooks_dir not in sys.path:
35
- sys.path.insert(0, _hooks_dir)
36
-
37
- from hooks_constants.enter_worktree_prefetch_constants import ( # noqa: E402
38
- ENTER_WORKTREE_PATH_INPUT_KEY,
39
- ENTER_WORKTREE_TOOL_NAME,
40
- GIT_FETCH_TIMEOUT_SECONDS,
41
- GIT_SYMBOLIC_REF_TIMEOUT_SECONDS,
42
- ORIGIN_HEAD_SYMBOLIC_REF,
43
- ORIGIN_REMOTE_NAME,
44
- ORIGIN_REMOTE_REF_PREFIX,
45
- )
46
-
47
-
48
- def is_enter_worktree_creation(payload_by_field: dict[str, object]) -> bool:
49
- """Return True when this hook invocation is an EnterWorktree creation call.
50
-
51
- Args:
52
- payload_by_field: The full PreToolUse hook payload (already JSON-parsed),
53
- keyed by top-level field name.
54
-
55
- Returns:
56
- True when ``tool_name == "EnterWorktree"`` and ``tool_input`` carries
57
- no ``path`` key. A ``path`` value means the call switches into an
58
- already-existing worktree rather than creating one, so it is False
59
- for that case and for every other tool.
60
- """
61
- if payload_by_field.get("tool_name", "") != ENTER_WORKTREE_TOOL_NAME:
62
- return False
63
- tool_input = payload_by_field.get("tool_input", {})
64
- if not isinstance(tool_input, dict):
65
- return True
66
- return not tool_input.get(ENTER_WORKTREE_PATH_INPUT_KEY)
67
-
68
-
69
- def resolve_origin_default_branch(repo_directory: str) -> str | None:
70
- """Return the branch name origin's HEAD points at, or None if unresolved.
71
-
72
- Args:
73
- repo_directory: Working directory to run ``git`` in.
74
-
75
- Returns:
76
- The branch name (for example ``"main"``) parsed from
77
- ``refs/remotes/origin/HEAD``, or None when the symbolic ref is
78
- unset, git is unavailable, or the command times out.
79
- """
80
- try:
81
- completed_process = subprocess.run(
82
- ["git", "symbolic-ref", ORIGIN_HEAD_SYMBOLIC_REF],
83
- capture_output=True,
84
- text=True,
85
- timeout=GIT_SYMBOLIC_REF_TIMEOUT_SECONDS,
86
- cwd=repo_directory,
87
- check=False,
88
- )
89
- except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
90
- return None
91
- if completed_process.returncode != 0:
92
- return None
93
- resolved_ref = completed_process.stdout.strip()
94
- if not resolved_ref.startswith(ORIGIN_REMOTE_REF_PREFIX):
95
- return None
96
- return resolved_ref[len(ORIGIN_REMOTE_REF_PREFIX):]
97
-
98
-
99
- def fetch_origin_branch(repo_directory: str, branch_name: str) -> None:
100
- """Best-effort ``git fetch origin <branch_name>``; never raises.
101
-
102
- Args:
103
- repo_directory: Working directory to run ``git`` in.
104
- branch_name: Branch to fetch from the ``origin`` remote.
105
- """
106
- try:
107
- subprocess.run(
108
- ["git", "fetch", ORIGIN_REMOTE_NAME, branch_name],
109
- capture_output=True,
110
- text=True,
111
- timeout=GIT_FETCH_TIMEOUT_SECONDS,
112
- cwd=repo_directory,
113
- check=False,
114
- )
115
- except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
116
- return
117
-
118
-
119
- def main() -> None:
120
- """Entry point for the PreToolUse:EnterWorktree hook.
121
-
122
- Reads the PreToolUse payload from stdin, and when it is an EnterWorktree
123
- creation call, fetches origin's default branch in the session's ``cwd``
124
- before returning. Always exits 0: a fetch failure self-heals on the next
125
- fetch rather than blocking worktree creation.
126
- """
127
- try:
128
- hook_payload = json.load(sys.stdin)
129
- except json.JSONDecodeError:
130
- sys.exit(0)
131
- if not isinstance(hook_payload, dict):
132
- sys.exit(0)
133
- if not is_enter_worktree_creation(hook_payload):
134
- sys.exit(0)
135
- repo_directory = hook_payload.get("cwd")
136
- if not isinstance(repo_directory, str) or not repo_directory:
137
- sys.exit(0)
138
- default_branch = resolve_origin_default_branch(repo_directory)
139
- if default_branch is None:
140
- sys.exit(0)
141
- fetch_origin_branch(repo_directory, default_branch)
142
- sys.exit(0)
143
-
144
-
145
- if __name__ == "__main__":
146
- main()
1
+ #!/usr/bin/env python3
2
+ """PreToolUse hook: fetch origin's default branch before EnterWorktree creates one.
3
+
4
+ ``EnterWorktree`` in its default ``fresh`` mode bases a new worktree on the
5
+ locally cached ``refs/remotes/origin/<default-branch>`` ref and only runs
6
+ ``git fetch`` when that ref is missing entirely -- so a worktree created
7
+ without a recent fetch silently starts behind the remote. This hook fetches
8
+ that ref immediately before the tool resolves its base, so the ref it reads
9
+ is current.
10
+
11
+ ::
12
+
13
+ EnterWorktree({}) # tool_input has no "path"
14
+ |
15
+ v
16
+ this hook: git fetch origin <default-branch> # best-effort, never blocks
17
+ |
18
+ v
19
+ EnterWorktree resolves refs/remotes/origin/<default-branch> # now fresh
20
+
21
+ The hook is a no-op when ``tool_input`` carries a ``path`` (switching into an
22
+ already-existing worktree needs no fresh base) and always exits 0 -- a failed
23
+ or slow fetch never blocks worktree creation.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import subprocess
29
+ import sys
30
+ from pathlib import Path
31
+
32
+ _hooks_dir = str(Path(__file__).resolve().parent.parent)
33
+ if _hooks_dir not in sys.path:
34
+ sys.path.insert(0, _hooks_dir)
35
+
36
+ from hooks_constants.enter_worktree_prefetch_constants import ( # noqa: E402
37
+ ENTER_WORKTREE_PATH_INPUT_KEY,
38
+ ENTER_WORKTREE_TOOL_NAME,
39
+ GIT_FETCH_TIMEOUT_SECONDS,
40
+ GIT_SYMBOLIC_REF_TIMEOUT_SECONDS,
41
+ ORIGIN_HEAD_SYMBOLIC_REF,
42
+ ORIGIN_REMOTE_NAME,
43
+ ORIGIN_REMOTE_REF_PREFIX,
44
+ )
45
+ from hooks_constants.pre_tool_use_stdin import read_hook_input_dictionary_from_stdin # noqa: E402
46
+
47
+
48
+ def is_enter_worktree_creation(payload_by_field: dict[str, object]) -> bool:
49
+ """Return True when this hook invocation is an EnterWorktree creation call.
50
+
51
+ Args:
52
+ payload_by_field: The full PreToolUse hook payload (already JSON-parsed),
53
+ keyed by top-level field name.
54
+
55
+ Returns:
56
+ True when ``tool_name == "EnterWorktree"`` and ``tool_input`` carries
57
+ no ``path`` key. A ``path`` value means the call switches into an
58
+ already-existing worktree rather than creating one, so it is False
59
+ for that case and for every other tool.
60
+ """
61
+ if payload_by_field.get("tool_name", "") != ENTER_WORKTREE_TOOL_NAME:
62
+ return False
63
+ tool_input = payload_by_field.get("tool_input", {})
64
+ if not isinstance(tool_input, dict):
65
+ return True
66
+ return not tool_input.get(ENTER_WORKTREE_PATH_INPUT_KEY)
67
+
68
+
69
+ def _run_git_quietly(
70
+ repo_directory: str,
71
+ all_git_arguments: list[str],
72
+ timeout_seconds: int,
73
+ ) -> subprocess.CompletedProcess[str] | None:
74
+ """Run ``git`` with the given arguments, or return None when it cannot run.
75
+
76
+ Args:
77
+ repo_directory: Working directory to run ``git`` in.
78
+ all_git_arguments: The git subcommand and its arguments, without the
79
+ leading ``"git"`` (for example ``["fetch", "origin", "main"]``).
80
+ timeout_seconds: Seconds to wait before abandoning the command.
81
+
82
+ Returns:
83
+ The completed process, or None when git is unavailable, the OS refuses
84
+ to start it, or the command exceeds ``timeout_seconds``.
85
+ """
86
+ try:
87
+ return subprocess.run(
88
+ ["git", *all_git_arguments],
89
+ capture_output=True,
90
+ text=True,
91
+ timeout=timeout_seconds,
92
+ cwd=repo_directory,
93
+ check=False,
94
+ )
95
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
96
+ return None
97
+
98
+
99
+ def resolve_origin_default_branch(repo_directory: str) -> str | None:
100
+ """Return the branch name origin's HEAD points at, or None if unresolved.
101
+
102
+ Args:
103
+ repo_directory: Working directory to run ``git`` in.
104
+
105
+ Returns:
106
+ The branch name (for example ``"main"``) parsed from
107
+ ``refs/remotes/origin/HEAD``, or None when the symbolic ref is
108
+ unset, git is unavailable, or the command times out.
109
+ """
110
+ completed_process = _run_git_quietly(
111
+ repo_directory,
112
+ ["symbolic-ref", ORIGIN_HEAD_SYMBOLIC_REF],
113
+ GIT_SYMBOLIC_REF_TIMEOUT_SECONDS,
114
+ )
115
+ if completed_process is None:
116
+ return None
117
+ if completed_process.returncode != 0:
118
+ return None
119
+ resolved_ref = completed_process.stdout.strip()
120
+ if not resolved_ref.startswith(ORIGIN_REMOTE_REF_PREFIX):
121
+ return None
122
+ return resolved_ref[len(ORIGIN_REMOTE_REF_PREFIX):]
123
+
124
+
125
+ def fetch_origin_branch(repo_directory: str, branch_name: str) -> None:
126
+ """Best-effort ``git fetch origin <branch_name>``; never raises.
127
+
128
+ Args:
129
+ repo_directory: Working directory to run ``git`` in.
130
+ branch_name: Branch to fetch from the ``origin`` remote.
131
+ """
132
+ _run_git_quietly(
133
+ repo_directory,
134
+ ["fetch", ORIGIN_REMOTE_NAME, branch_name],
135
+ GIT_FETCH_TIMEOUT_SECONDS,
136
+ )
137
+
138
+
139
+ def main() -> None:
140
+ """Entry point for the PreToolUse:EnterWorktree hook.
141
+
142
+ Reads the PreToolUse payload from stdin, and when it is an EnterWorktree
143
+ creation call, fetches origin's default branch in the session's ``cwd``
144
+ before returning. Always exits 0: a fetch failure self-heals on the next
145
+ fetch rather than blocking worktree creation.
146
+ """
147
+ hook_payload = read_hook_input_dictionary_from_stdin()
148
+ if hook_payload is None:
149
+ sys.exit(0)
150
+ if not is_enter_worktree_creation(hook_payload):
151
+ sys.exit(0)
152
+ repo_directory = hook_payload.get("cwd")
153
+ if not isinstance(repo_directory, str) or not repo_directory:
154
+ sys.exit(0)
155
+ default_branch = resolve_origin_default_branch(repo_directory)
156
+ if default_branch is None:
157
+ sys.exit(0)
158
+ fetch_origin_branch(repo_directory, default_branch)
159
+ sys.exit(0)
160
+
161
+
162
+ if __name__ == "__main__":
163
+ main()