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
@@ -1,280 +1,280 @@
1
- """SubagentStop hook: mint a commit-gate verdict when code-verifier finishes.
2
-
3
- Only this hook writes verdict files — the main session is denied writes to
4
- the verdict directory, so a session cannot fabricate a passing verdict. The
5
- SubagentStop payload names the stopping subagent's own transcript
6
- (``agent_transcript_path``), which sits beside a harness-written
7
- ``agent-<id>.meta.json`` sidecar naming the spawning ``agentType``. The hook
8
- reads that type from the sidecar, so it resolves identically in interactive,
9
- background, and worktree-switched sessions; only the harness writes the
10
- sidecar, so the type it records cannot be forged. When that type is
11
- ``code-verifier``, the hook pulls the verdict block out of the agent's own
12
- transcript (``agent_transcript_path``), recomputes the live change-surface
13
- hash for the session repository, and writes the verdict bound to that hash.
14
- The companion ``verified_commit_gate.py`` (PreToolUse) then allows ``git
15
- commit`` / ``git push`` only while the work tree still matches the verified
16
- state.
17
-
18
- The verifier's final message must end with a fenced block::
19
-
20
- ```verdict
21
- {"all_pass": true, "findings": []}
22
- ```
23
-
24
- A missing or unparseable block mints nothing, which leaves the gate closed.
25
- """
26
-
27
- from __future__ import annotations
28
-
29
- import json
30
- import re
31
- import sys
32
- from pathlib import Path
33
-
34
- blocking_directory = str(Path(__file__).resolve().parent)
35
- if blocking_directory not in sys.path:
36
- sys.path.insert(0, blocking_directory)
37
-
38
- from verified_commit_config_bootstrap import ( # noqa: E402
39
- register_verified_commit_constants,
40
- )
41
-
42
- register_verified_commit_constants()
43
-
44
- from config.verified_commit_constants import (
45
- MINTING_AGENT_TYPE,
46
- VERDICT_KEY_MANIFEST_SHA256,
47
- )
48
- from verification_verdict_store import (
49
- branch_surface_manifest,
50
- empty_surface_hash,
51
- manifest_sha256,
52
- resolve_merge_base,
53
- resolve_repo_root,
54
- write_verdict,
55
- )
56
-
57
-
58
- def assistant_text_blocks(transcript_path: str) -> list[str]:
59
- """Collect every assistant text block from a transcript JSONL file.
60
-
61
- Args:
62
- transcript_path: Path to the subagent's transcript.
63
-
64
- Returns:
65
- The text of each assistant content block, in transcript order;
66
- empty when the file is missing or holds no assistant text.
67
- """
68
- collected_blocks: list[str] = []
69
- try:
70
- transcript_lines = (
71
- Path(transcript_path).read_text(encoding="utf-8", errors="replace").splitlines()
72
- )
73
- except OSError:
74
- return collected_blocks
75
- for each_line in transcript_lines:
76
- try:
77
- transcript_entry = json.loads(each_line)
78
- except json.JSONDecodeError:
79
- continue
80
- if not isinstance(transcript_entry, dict):
81
- continue
82
- if transcript_entry.get("type") != "assistant":
83
- continue
84
- message_body = transcript_entry.get("message")
85
- if not isinstance(message_body, dict):
86
- continue
87
- content_blocks = message_body.get("content")
88
- if not isinstance(content_blocks, list):
89
- continue
90
- for each_block in content_blocks:
91
- if isinstance(each_block, dict) and each_block.get("type") == "text":
92
- block_text = each_block.get("text")
93
- if isinstance(block_text, str):
94
- collected_blocks.append(block_text)
95
- return collected_blocks
96
-
97
-
98
- def last_verdict_in_blocks(all_text_blocks: list[str]) -> dict | None:
99
- """Extract the final verdict fence from assistant text blocks.
100
-
101
- Args:
102
- all_text_blocks: Assistant text blocks in transcript order.
103
-
104
- Returns:
105
- The parsed verdict mapping carrying a boolean ``all_pass`` and a
106
- list ``findings``, or None when no block holds a wellformed fence.
107
- """
108
- verdict_fence_pattern = re.compile(r"```verdict\s*\n(.*?)```", re.DOTALL)
109
- fence_bodies: list[str] = []
110
- for each_block in all_text_blocks:
111
- fence_bodies.extend(verdict_fence_pattern.findall(each_block))
112
- for each_fence_body in reversed(fence_bodies):
113
- try:
114
- verdict_record = json.loads(each_fence_body)
115
- except json.JSONDecodeError:
116
- continue
117
- if not isinstance(verdict_record, dict):
118
- continue
119
- if not isinstance(verdict_record.get("all_pass"), bool):
120
- continue
121
- if not isinstance(verdict_record.get("findings"), list):
122
- continue
123
- return verdict_record
124
- return None
125
-
126
-
127
- def _agent_type_from_meta_sidecar(agent_transcript_path: str) -> str | None:
128
- """Read the spawning agentType from a subagent transcript's sidecar.
129
-
130
- Each subagent transcript ``agent-<id>.jsonl`` sits beside a harness-written
131
- ``agent-<id>.meta.json`` naming the spawning ``agentType``. Reading the type
132
- from this sidecar binds it to the stopping subagent itself, so it resolves
133
- identically in interactive, background, and worktree-switched sessions and
134
- needs no parent-transcript scan or flush retry. Only the harness writes this
135
- sidecar, so a session cannot forge the type it reports.
136
-
137
- Args:
138
- agent_transcript_path: The stopping subagent's own transcript path from
139
- the SubagentStop payload.
140
-
141
- Returns:
142
- The recorded ``agentType``, or None when the path is empty, the sidecar
143
- is absent, unreadable, or unparseable, it does not hold a JSON object,
144
- or it names no string ``agentType``.
145
- """
146
- if not agent_transcript_path:
147
- return None
148
- transcript_file = Path(agent_transcript_path)
149
- sidecar_file = transcript_file.with_name(f"{transcript_file.stem}.meta.json")
150
- try:
151
- sidecar_record = json.loads(sidecar_file.read_text(encoding="utf-8"))
152
- except (OSError, UnicodeDecodeError, json.JSONDecodeError):
153
- return None
154
- if not isinstance(sidecar_record, dict):
155
- return None
156
- recorded_type = sidecar_record.get("agentType")
157
- return recorded_type if isinstance(recorded_type, str) else None
158
-
159
-
160
- def resolved_subagent_type(subagent_stop_payload: dict) -> str | None:
161
- """Recover the spawning agent type for a SubagentStop payload.
162
-
163
- The stopping subagent's own transcript (``agent_transcript_path``) sits
164
- beside a harness-written ``agent-<id>.meta.json`` sidecar naming its
165
- ``agentType``. The type comes solely from that sidecar, which only the
166
- harness writes — the main session can neither write the sidecar nor forge
167
- the agentType it records. A verdict fence in the transcript is never used to
168
- infer the type, because the main session controls the prompt of any
169
- Agent-tool subagent it spawns and could instruct one to print such a fence.
170
-
171
- Args:
172
- subagent_stop_payload: The SubagentStop hook payload.
173
-
174
- Returns:
175
- The agent type the sidecar records, or None when the
176
- ``agent_transcript_path`` is empty, the sidecar is absent, unreadable,
177
- or unparseable, it does not hold a JSON object, or it names no string
178
- ``agentType``.
179
- """
180
- return _agent_type_from_meta_sidecar(
181
- subagent_stop_payload.get("agent_transcript_path", "")
182
- )
183
-
184
-
185
- def _attested_or_recomputed_hash(verdict_record: dict, repo_root: str) -> str | None:
186
- """Choose the surface hash the minted verdict binds to.
187
-
188
- A code-verifier that verifies a work tree other than the stop event's cwd
189
- attests the surface it checked by emitting ``manifest_sha256`` in its
190
- verdict fence (computed against the verified work tree via the
191
- ``--manifest-hash`` CLI). Binding the minted verdict to that attested hash
192
- keeps the verdict tied to the code actually verified rather than the
193
- subagent's cwd, so a verdict earned for one work tree clears a commit in a
194
- sibling work tree of the same surface. When the fence attests no hash, the
195
- minter recomputes one from the cwd work tree, which is correct whenever the
196
- verifier ran in the work tree it verified.
197
-
198
- Returns None (mints nothing) in two empty-surface cases:
199
-
200
- - The attested hash equals ``empty_surface_hash()`` — the verifier called
201
- the store CLI on a wrong (empty) work tree and the hash it got back
202
- represents nothing.
203
- - The recompute branch produces an empty manifest — the cwd work tree also
204
- has no changed or untracked files versus the merge base.
205
-
206
- Args:
207
- verdict_record: The parsed verdict fence from the verifier transcript.
208
- repo_root: The work-tree root resolved from the stop event's cwd, used
209
- for the recompute fallback.
210
-
211
- Returns:
212
- The attested ``manifest_sha256`` when the fence carries a non-empty
213
- string one that is not the empty-surface sentinel; the cwd work tree's
214
- recomputed surface hash when the fence attests nothing and the surface
215
- is non-empty; or None when the attested hash is the empty-surface
216
- sentinel, the surface manifest is empty, or no upstream base resolves.
217
- """
218
- attested_manifest_sha256 = verdict_record.get(VERDICT_KEY_MANIFEST_SHA256)
219
- if isinstance(attested_manifest_sha256, str) and attested_manifest_sha256:
220
- if attested_manifest_sha256 == empty_surface_hash():
221
- return None
222
- return attested_manifest_sha256
223
- merge_base_sha = resolve_merge_base(repo_root)
224
- if merge_base_sha is None:
225
- return None
226
- surface_manifest_text = branch_surface_manifest(repo_root, merge_base_sha)
227
- if not surface_manifest_text:
228
- return None
229
- return manifest_sha256(surface_manifest_text)
230
-
231
-
232
- def mint_for_payload(subagent_stop_payload: dict) -> Path | None:
233
- """Mint a verdict file for a code-verifier stop event.
234
-
235
- Args:
236
- subagent_stop_payload: The SubagentStop hook payload.
237
-
238
- Returns:
239
- The verdict file path when minted; None when the payload is not a
240
- code-verifier stop, the transcript holds no verdict, the cwd is not a
241
- work tree, or — for a verdict that attests no ``manifest_sha256`` of
242
- its own — that work tree has no upstream base to recompute the surface
243
- hash from.
244
- """
245
- if resolved_subagent_type(subagent_stop_payload) != MINTING_AGENT_TYPE:
246
- return None
247
- agent_transcript_path = subagent_stop_payload.get("agent_transcript_path", "")
248
- if not agent_transcript_path:
249
- return None
250
- verdict_record = last_verdict_in_blocks(assistant_text_blocks(agent_transcript_path))
251
- if verdict_record is None:
252
- return None
253
- repo_root = resolve_repo_root(subagent_stop_payload.get("cwd", "."))
254
- if repo_root is None:
255
- return None
256
- bound_manifest_sha256 = _attested_or_recomputed_hash(verdict_record, repo_root)
257
- if bound_manifest_sha256 is None:
258
- return None
259
- return write_verdict(
260
- repo_root,
261
- bound_manifest_sha256,
262
- verdict_record["all_pass"],
263
- verdict_record["findings"],
264
- str(subagent_stop_payload.get("agent_id", "")),
265
- )
266
-
267
-
268
- def main() -> None:
269
- """Read the SubagentStop payload and mint a verdict when one applies."""
270
- try:
271
- subagent_stop_payload = json.load(sys.stdin)
272
- except json.JSONDecodeError:
273
- return
274
- if not isinstance(subagent_stop_payload, dict):
275
- return
276
- mint_for_payload(subagent_stop_payload)
277
-
278
-
279
- if __name__ == "__main__":
280
- main()
1
+ """SubagentStop hook: mint a commit-gate verdict when code-verifier finishes.
2
+
3
+ Only this hook writes verdict files — the main session is denied writes to
4
+ the verdict directory, so a session cannot fabricate a passing verdict. The
5
+ SubagentStop payload names the stopping subagent's own transcript
6
+ (``agent_transcript_path``), which sits beside a harness-written
7
+ ``agent-<id>.meta.json`` sidecar naming the spawning ``agentType``. The hook
8
+ reads that type from the sidecar, so it resolves identically in interactive,
9
+ background, and worktree-switched sessions; only the harness writes the
10
+ sidecar, so the type it records cannot be forged. When that type is
11
+ ``code-verifier``, the hook pulls the verdict block out of the agent's own
12
+ transcript (``agent_transcript_path``), recomputes the live change-surface
13
+ hash for the session repository, and writes the verdict bound to that hash.
14
+ The companion ``verified_commit_gate.py`` (PreToolUse) then allows ``git
15
+ commit`` / ``git push`` only while the work tree still matches the verified
16
+ state.
17
+
18
+ The verifier's final message must end with a fenced block::
19
+
20
+ ```verdict
21
+ {"all_pass": true, "findings": []}
22
+ ```
23
+
24
+ A missing or unparseable block mints nothing, which leaves the gate closed.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import json
30
+ import re
31
+ import sys
32
+ from pathlib import Path
33
+
34
+ blocking_directory = str(Path(__file__).resolve().parent)
35
+ if blocking_directory not in sys.path:
36
+ sys.path.insert(0, blocking_directory)
37
+
38
+ from verified_commit_config_bootstrap import ( # noqa: E402
39
+ register_verified_commit_constants,
40
+ )
41
+
42
+ register_verified_commit_constants()
43
+
44
+ from config.verified_commit_constants import (
45
+ MINTING_AGENT_TYPE,
46
+ VERDICT_KEY_MANIFEST_SHA256,
47
+ )
48
+ from verification_verdict_store import (
49
+ branch_surface_manifest,
50
+ empty_surface_hash,
51
+ manifest_sha256,
52
+ resolve_merge_base,
53
+ resolve_repo_root,
54
+ write_verdict,
55
+ )
56
+
57
+
58
+ def assistant_text_blocks(transcript_path: str) -> list[str]:
59
+ """Collect every assistant text block from a transcript JSONL file.
60
+
61
+ Args:
62
+ transcript_path: Path to the subagent's transcript.
63
+
64
+ Returns:
65
+ The text of each assistant content block, in transcript order;
66
+ empty when the file is missing or holds no assistant text.
67
+ """
68
+ collected_blocks: list[str] = []
69
+ try:
70
+ transcript_lines = (
71
+ Path(transcript_path).read_text(encoding="utf-8", errors="replace").splitlines()
72
+ )
73
+ except OSError:
74
+ return collected_blocks
75
+ for each_line in transcript_lines:
76
+ try:
77
+ transcript_entry = json.loads(each_line)
78
+ except json.JSONDecodeError:
79
+ continue
80
+ if not isinstance(transcript_entry, dict):
81
+ continue
82
+ if transcript_entry.get("type") != "assistant":
83
+ continue
84
+ message_body = transcript_entry.get("message")
85
+ if not isinstance(message_body, dict):
86
+ continue
87
+ content_blocks = message_body.get("content")
88
+ if not isinstance(content_blocks, list):
89
+ continue
90
+ for each_block in content_blocks:
91
+ if isinstance(each_block, dict) and each_block.get("type") == "text":
92
+ block_text = each_block.get("text")
93
+ if isinstance(block_text, str):
94
+ collected_blocks.append(block_text)
95
+ return collected_blocks
96
+
97
+
98
+ def last_verdict_in_blocks(all_text_blocks: list[str]) -> dict | None:
99
+ """Extract the final verdict fence from assistant text blocks.
100
+
101
+ Args:
102
+ all_text_blocks: Assistant text blocks in transcript order.
103
+
104
+ Returns:
105
+ The parsed verdict mapping carrying a boolean ``all_pass`` and a
106
+ list ``findings``, or None when no block holds a wellformed fence.
107
+ """
108
+ verdict_fence_pattern = re.compile(r"```verdict\s*\n(.*?)```", re.DOTALL)
109
+ fence_bodies: list[str] = []
110
+ for each_block in all_text_blocks:
111
+ fence_bodies.extend(verdict_fence_pattern.findall(each_block))
112
+ for each_fence_body in reversed(fence_bodies):
113
+ try:
114
+ verdict_record = json.loads(each_fence_body)
115
+ except json.JSONDecodeError:
116
+ continue
117
+ if not isinstance(verdict_record, dict):
118
+ continue
119
+ if not isinstance(verdict_record.get("all_pass"), bool):
120
+ continue
121
+ if not isinstance(verdict_record.get("findings"), list):
122
+ continue
123
+ return verdict_record
124
+ return None
125
+
126
+
127
+ def _agent_type_from_meta_sidecar(agent_transcript_path: str) -> str | None:
128
+ """Read the spawning agentType from a subagent transcript's sidecar.
129
+
130
+ Each subagent transcript ``agent-<id>.jsonl`` sits beside a harness-written
131
+ ``agent-<id>.meta.json`` naming the spawning ``agentType``. Reading the type
132
+ from this sidecar binds it to the stopping subagent itself, so it resolves
133
+ identically in interactive, background, and worktree-switched sessions and
134
+ needs no parent-transcript scan or flush retry. Only the harness writes this
135
+ sidecar, so a session cannot forge the type it reports.
136
+
137
+ Args:
138
+ agent_transcript_path: The stopping subagent's own transcript path from
139
+ the SubagentStop payload.
140
+
141
+ Returns:
142
+ The recorded ``agentType``, or None when the path is empty, the sidecar
143
+ is absent, unreadable, or unparseable, it does not hold a JSON object,
144
+ or it names no string ``agentType``.
145
+ """
146
+ if not agent_transcript_path:
147
+ return None
148
+ transcript_file = Path(agent_transcript_path)
149
+ sidecar_file = transcript_file.with_name(f"{transcript_file.stem}.meta.json")
150
+ try:
151
+ sidecar_record = json.loads(sidecar_file.read_text(encoding="utf-8"))
152
+ except (OSError, UnicodeDecodeError, json.JSONDecodeError):
153
+ return None
154
+ if not isinstance(sidecar_record, dict):
155
+ return None
156
+ recorded_type = sidecar_record.get("agentType")
157
+ return recorded_type if isinstance(recorded_type, str) else None
158
+
159
+
160
+ def resolved_subagent_type(subagent_stop_payload: dict) -> str | None:
161
+ """Recover the spawning agent type for a SubagentStop payload.
162
+
163
+ The stopping subagent's own transcript (``agent_transcript_path``) sits
164
+ beside a harness-written ``agent-<id>.meta.json`` sidecar naming its
165
+ ``agentType``. The type comes solely from that sidecar, which only the
166
+ harness writes — the main session can neither write the sidecar nor forge
167
+ the agentType it records. A verdict fence in the transcript is never used to
168
+ infer the type, because the main session controls the prompt of any
169
+ Agent-tool subagent it spawns and could instruct one to print such a fence.
170
+
171
+ Args:
172
+ subagent_stop_payload: The SubagentStop hook payload.
173
+
174
+ Returns:
175
+ The agent type the sidecar records, or None when the
176
+ ``agent_transcript_path`` is empty, the sidecar is absent, unreadable,
177
+ or unparseable, it does not hold a JSON object, or it names no string
178
+ ``agentType``.
179
+ """
180
+ return _agent_type_from_meta_sidecar(
181
+ subagent_stop_payload.get("agent_transcript_path", "")
182
+ )
183
+
184
+
185
+ def _attested_or_recomputed_hash(verdict_record: dict, repo_root: str) -> str | None:
186
+ """Choose the surface hash the minted verdict binds to.
187
+
188
+ A code-verifier that verifies a work tree other than the stop event's cwd
189
+ attests the surface it checked by emitting ``manifest_sha256`` in its
190
+ verdict fence (computed against the verified work tree via the
191
+ ``--manifest-hash`` CLI). Binding the minted verdict to that attested hash
192
+ keeps the verdict tied to the code actually verified rather than the
193
+ subagent's cwd, so a verdict earned for one work tree clears a commit in a
194
+ sibling work tree of the same surface. When the fence attests no hash, the
195
+ minter recomputes one from the cwd work tree, which is correct whenever the
196
+ verifier ran in the work tree it verified.
197
+
198
+ Returns None (mints nothing) in two empty-surface cases:
199
+
200
+ - The attested hash equals ``empty_surface_hash()`` — the verifier called
201
+ the store CLI on a wrong (empty) work tree and the hash it got back
202
+ represents nothing.
203
+ - The recompute branch produces an empty manifest — the cwd work tree also
204
+ has no changed or untracked files versus the merge base.
205
+
206
+ Args:
207
+ verdict_record: The parsed verdict fence from the verifier transcript.
208
+ repo_root: The work-tree root resolved from the stop event's cwd, used
209
+ for the recompute fallback.
210
+
211
+ Returns:
212
+ The attested ``manifest_sha256`` when the fence carries a non-empty
213
+ string one that is not the empty-surface sentinel; the cwd work tree's
214
+ recomputed surface hash when the fence attests nothing and the surface
215
+ is non-empty; or None when the attested hash is the empty-surface
216
+ sentinel, the surface manifest is empty, or no upstream base resolves.
217
+ """
218
+ attested_manifest_sha256 = verdict_record.get(VERDICT_KEY_MANIFEST_SHA256)
219
+ if isinstance(attested_manifest_sha256, str) and attested_manifest_sha256:
220
+ if attested_manifest_sha256 == empty_surface_hash():
221
+ return None
222
+ return attested_manifest_sha256
223
+ merge_base_sha = resolve_merge_base(repo_root)
224
+ if merge_base_sha is None:
225
+ return None
226
+ surface_manifest_text = branch_surface_manifest(repo_root, merge_base_sha)
227
+ if not surface_manifest_text:
228
+ return None
229
+ return manifest_sha256(surface_manifest_text)
230
+
231
+
232
+ def mint_for_payload(subagent_stop_payload: dict) -> Path | None:
233
+ """Mint a verdict file for a code-verifier stop event.
234
+
235
+ Args:
236
+ subagent_stop_payload: The SubagentStop hook payload.
237
+
238
+ Returns:
239
+ The verdict file path when minted; None when the payload is not a
240
+ code-verifier stop, the transcript holds no verdict, the cwd is not a
241
+ work tree, or — for a verdict that attests no ``manifest_sha256`` of
242
+ its own — that work tree has no upstream base to recompute the surface
243
+ hash from.
244
+ """
245
+ if resolved_subagent_type(subagent_stop_payload) != MINTING_AGENT_TYPE:
246
+ return None
247
+ agent_transcript_path = subagent_stop_payload.get("agent_transcript_path", "")
248
+ if not agent_transcript_path:
249
+ return None
250
+ verdict_record = last_verdict_in_blocks(assistant_text_blocks(agent_transcript_path))
251
+ if verdict_record is None:
252
+ return None
253
+ repo_root = resolve_repo_root(subagent_stop_payload.get("cwd", "."))
254
+ if repo_root is None:
255
+ return None
256
+ bound_manifest_sha256 = _attested_or_recomputed_hash(verdict_record, repo_root)
257
+ if bound_manifest_sha256 is None:
258
+ return None
259
+ return write_verdict(
260
+ repo_root,
261
+ bound_manifest_sha256,
262
+ verdict_record["all_pass"],
263
+ verdict_record["findings"],
264
+ str(subagent_stop_payload.get("agent_id", "")),
265
+ )
266
+
267
+
268
+ def main() -> None:
269
+ """Read the SubagentStop payload and mint a verdict when one applies."""
270
+ try:
271
+ subagent_stop_payload = json.load(sys.stdin)
272
+ except json.JSONDecodeError:
273
+ return
274
+ if not isinstance(subagent_stop_payload, dict):
275
+ return
276
+ mint_for_payload(subagent_stop_payload)
277
+
278
+
279
+ if __name__ == "__main__":
280
+ main()
@@ -8,12 +8,15 @@ Native git hooks that run outside the Claude Code lifecycle — invoked directly
8
8
  |---|---|---|
9
9
  | `pre_commit.py` | `pre-commit` | Runs the CODE_RULES gate (`precommit_code_rules_gate.py`) over staged changes; exits 1 when any staged file has a blocking violation |
10
10
  | `pre_push.py` | `pre-push` | Blocks a push that would land a non-`main` local branch onto remote `main` (or `master`), then runs the CODE_RULES gate over the commits about to be pushed |
11
+ | `pre_push_base_reference.py` | — | Resolves the gate base for `pre_push.py`: reads the pushed remote name from git's arguments, then names that remote's default branch from its symbolic ref or its candidate default branches |
11
12
  | `post_commit.py` | `post-commit` | Runs after a commit lands; performs any post-commit bookkeeping |
12
13
  | `gate_utils.py` | — | Shared helpers: resolves the gate script path, checks that the path is a safe regular file |
13
14
  | `test_config.py` | — | Test configuration helpers |
14
15
  | `test_gate_utils.py` | — | Tests for `gate_utils.py` |
15
16
  | `test_pre_commit.py` | — | Tests for `pre_commit.py` |
16
17
  | `test_pre_push.py` | — | Tests for `pre_push.py` |
18
+ | `test_pre_push_base_reference.py` | — | Tests for the gate base resolution in `pre_push.py` |
19
+ | `conftest.py` | — | Puts this directory on `sys.path` and clears cached `config` modules, so each test module imports the hooks at the top of the file |
17
20
 
18
21
  ## Subdirectory
19
22
 
@@ -0,0 +1,30 @@
1
+ """Put this directory on ``sys.path`` before the git-hook test modules import.
2
+
3
+ The git-hook entry points sit beside their constants package rather than inside
4
+ an installed package, so ``import pre_push`` resolves only once this directory
5
+ leads ``sys.path``. Pytest loads a conftest before the test modules it covers,
6
+ so doing the insertion here lets each test module import the hooks at the top
7
+ of the file.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import importlib
13
+ import sys
14
+ from pathlib import Path
15
+
16
+
17
+ _GIT_HOOKS_DIRECTORY_STRING = str(Path(__file__).resolve().parent)
18
+ _CONFIG_PACKAGE_NAME = "config"
19
+ _CONFIG_PACKAGE_PREFIX = "config."
20
+
21
+ while _GIT_HOOKS_DIRECTORY_STRING in sys.path:
22
+ sys.path.remove(_GIT_HOOKS_DIRECTORY_STRING)
23
+ sys.path.insert(0, _GIT_HOOKS_DIRECTORY_STRING)
24
+
25
+ for each_module_name in list(sys.modules):
26
+ if each_module_name == _CONFIG_PACKAGE_NAME or each_module_name.startswith(
27
+ _CONFIG_PACKAGE_PREFIX
28
+ ):
29
+ del sys.modules[each_module_name]
30
+ importlib.invalidate_caches()
@@ -7,10 +7,10 @@ import stat
7
7
  from pathlib import Path
8
8
 
9
9
  from git_hooks_constants import (
10
+ ALL_GATE_SCRIPT_RELATIVE_PATH,
10
11
  CLAUDE_HOME_DEFAULT_SUBDIRECTORY,
11
12
  CLAUDE_HOME_ENV_VAR,
12
13
  GATE_PATH_OVERRIDE_ENV_VAR,
13
- GATE_SCRIPT_RELATIVE_PATH,
14
14
  )
15
15
 
16
16
 
@@ -34,7 +34,7 @@ def resolve_gate_script_path() -> tuple[Path, Path | None]:
34
34
  claude_home_directory = Path(claude_home_override).resolve()
35
35
  else:
36
36
  claude_home_directory = Path.home() / CLAUDE_HOME_DEFAULT_SUBDIRECTORY
37
- gate_path = claude_home_directory.joinpath(*GATE_SCRIPT_RELATIVE_PATH)
37
+ gate_path = claude_home_directory.joinpath(*ALL_GATE_SCRIPT_RELATIVE_PATH)
38
38
  return gate_path, None
39
39
 
40
40