claude-dev-env 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/CLAUDE.md +53 -49
  2. package/_shared/pr-loop/scripts/_claude_permissions_common.py +84 -0
  3. package/_shared/pr-loop/scripts/code_rules_gate.py +4 -2
  4. package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +306 -306
  5. package/_shared/pr-loop/scripts/pr_loop_shared_constants/claude_permissions_constants.py +44 -0
  6. package/_shared/pr-loop/scripts/pr_loop_shared_constants/copilot_quota_constants.py +24 -24
  7. package/_shared/pr-loop/scripts/pr_loop_shared_constants/stale_worktree_rule_sweep_constants.py +107 -107
  8. package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +290 -48
  9. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +42 -2
  10. package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +36 -0
  11. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +100 -1
  12. package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +497 -497
  13. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +311 -2
  14. package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep.py +301 -301
  15. package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep_constants.py +85 -85
  16. package/_shared/pr-loop/worker-spawn.md +1 -1
  17. package/agents/CLAUDE.md +2 -1
  18. package/agents/caveman.md +0 -1
  19. package/agents/clasp-deployment-orchestrator.md +0 -1
  20. package/agents/clean-coder.md +0 -1
  21. package/agents/code-advisor.md +0 -1
  22. package/agents/code-quality-agent.md +1 -2
  23. package/agents/code-verifier.md +0 -1
  24. package/agents/deep-research.md +0 -1
  25. package/agents/docs-agent.md +0 -1
  26. package/agents/git-commit-crafter.md +0 -1
  27. package/agents/issue-tracker.md +42 -0
  28. package/agents/plan-packet-validator.md +0 -1
  29. package/agents/pr-description-writer.md +0 -1
  30. package/agents/test_agent_frontmatter.py +67 -18
  31. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +143 -141
  32. package/bin/CLAUDE.md +68 -5
  33. package/bin/ever-shipped-skills.mjs +1 -0
  34. package/bin/install-constants.mjs +88 -0
  35. package/bin/install.mjs +1138 -114
  36. package/bin/install.prune.test.mjs +869 -19
  37. package/bin/install.test.mjs +906 -2
  38. package/commands/implement.md +1 -1
  39. package/commands/right-size.md +1 -1
  40. package/docs/CLAUDE.md +1 -0
  41. package/docs/host-pool-health-monitor.md +102 -0
  42. package/docs/references/CLAUDE.md +4 -2
  43. package/docs/references/advisor-tool.md +13 -0
  44. package/docs/references/code-review-enforcement.md +10 -0
  45. package/docs/references/team-advisor-skill.md +14 -0
  46. package/hooks/blocking/CLAUDE.md +1 -0
  47. package/hooks/blocking/code_review_pr_create_gate.py +7 -3
  48. package/hooks/blocking/code_review_push_gate.py +9 -4
  49. package/hooks/blocking/code_review_stamp_directory_write_blocker.py +8 -0
  50. package/hooks/blocking/config/__init__.py +5 -5
  51. package/hooks/blocking/config/code_review_enforcement_constants.py +4 -1
  52. package/hooks/blocking/config/test_code_review_enforcement_constants.py +5 -0
  53. package/hooks/blocking/config/verified_commit_constants.py +160 -159
  54. package/hooks/blocking/orchestrator_refresh_reschedule_gate.py +256 -0
  55. package/hooks/blocking/pre_tool_use_dispatcher.py +24 -24
  56. package/hooks/blocking/test_code_review_pr_create_gate.py +14 -0
  57. package/hooks/blocking/test_code_review_push_gate.py +16 -0
  58. package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +19 -0
  59. package/hooks/blocking/test_orchestrator_refresh_reschedule_gate.py +231 -0
  60. package/hooks/blocking/test_pre_tool_use_dispatcher.py +10 -1
  61. package/hooks/blocking/test_verdict_directory_write_blocker.py +808 -808
  62. package/hooks/blocking/test_verification_verdict_store.py +54 -0
  63. package/hooks/blocking/test_verified_commit_gate.py +581 -581
  64. package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +131 -131
  65. package/hooks/blocking/verdict_directory_write_blocker.py +687 -687
  66. package/hooks/blocking/verification_verdict_store.py +1039 -1036
  67. package/hooks/blocking/verified_commit_message_accuracy_blocker.py +167 -167
  68. package/hooks/blocking/verifier_verdict_minter.py +280 -280
  69. package/hooks/git-hooks/test_pre_push.py +25 -0
  70. package/hooks/hooks.json +10 -0
  71. package/hooks/hooks_constants/CLAUDE.md +2 -1
  72. package/hooks/hooks_constants/enter_worktree_prefetch_constants.py +18 -18
  73. package/hooks/hooks_constants/orchestrator_refresh_reschedule_gate_constants.py +48 -0
  74. package/hooks/hooks_constants/ruff_integration_constants.py +16 -0
  75. package/hooks/lifecycle/enter_worktree_origin_prefetch.py +163 -146
  76. package/hooks/lifecycle/test_enter_worktree_origin_prefetch.py +185 -178
  77. package/hooks/pyproject.toml +1 -0
  78. package/hooks/validators/CLAUDE.md +1 -0
  79. package/hooks/validators/config/__init__.py +0 -0
  80. package/hooks/validators/config/directory_exemption_constants.py +183 -0
  81. package/hooks/validators/config/test_directory_exemption_constants.py +21 -0
  82. package/hooks/validators/conftest.py +4 -0
  83. package/hooks/validators/ruff_integration.py +49 -5
  84. package/hooks/validators/run_all_validators.py +206 -9
  85. package/hooks/validators/test_directory_exemption_constants.py +185 -0
  86. package/hooks/validators/test_python_antipattern_checks.py +110 -5
  87. package/hooks/validators/test_ruff_integration.py +92 -1
  88. package/hooks/validators/test_run_all_validators.py +115 -68
  89. package/hooks/validators/test_run_all_validators_pretooluse.py +159 -1
  90. package/package.json +10 -2
  91. package/rules/CLAUDE.md +1 -0
  92. package/rules/docstring-prose-matches-implementation.md +45 -44
  93. package/rules/state-what-is.md +25 -0
  94. package/rules/verified-commit-gate-skip.md +1 -1
  95. package/scripts/CLAUDE.md +1 -0
  96. package/scripts/Capture-PoolHealth.ps1 +410 -0
  97. package/scripts/_code_review_test_support.py +404 -0
  98. package/scripts/claude_chain_runner.py +141 -1
  99. package/scripts/conftest.py +16 -1
  100. package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -1
  101. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +9 -0
  102. package/scripts/resolve_worker_spawn.py +626 -626
  103. package/scripts/spawn_grok_batch.py +672 -672
  104. package/scripts/test_claude_chain_runner.py +131 -0
  105. package/scripts/test_invoke_code_review_chain.py +70 -0
  106. package/scripts/test_invoke_code_review_cli.py +192 -0
  107. package/scripts/test_invoke_code_review_contract.py +256 -0
  108. package/scripts/test_invoke_code_review_git.py +123 -0
  109. package/scripts/test_invoke_code_review_mode.py +99 -0
  110. package/scripts/test_resolve_worker_spawn.py +1014 -1014
  111. package/skills/CLAUDE.md +2 -0
  112. package/skills/auditing-claude-config/SKILL.md +114 -114
  113. package/skills/autoconverge/SKILL.md +427 -427
  114. package/skills/autoconverge/reference/convergence.md +24 -3
  115. package/skills/autoconverge/workflow/CLAUDE.md +1 -0
  116. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +3 -3
  117. package/skills/autoconverge/workflow/converge.contract.test.mjs +1263 -1263
  118. package/skills/autoconverge/workflow/converge.mjs +167 -0
  119. package/skills/autoconverge/workflow/converge.p2-advance.test.mjs +202 -0
  120. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +2 -2
  121. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +2 -2
  122. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +2 -2
  123. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +2 -2
  124. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +2 -2
  125. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +2 -2
  126. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +2 -2
  127. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +2 -2
  128. package/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +265 -265
  129. package/skills/closeout/SKILL.md +33 -50
  130. package/skills/codex-review/scripts/codex_review_scripts_constants/run_constants.py +8 -0
  131. package/skills/codex-review/scripts/run_codex_review.py +233 -1
  132. package/skills/codex-review/scripts/test_run_codex_review.py +189 -0
  133. package/skills/condensing-instructions/SKILL.md +81 -0
  134. package/skills/copilot-review/SKILL.md +119 -119
  135. package/skills/e-code-review/SKILL.md +52 -0
  136. package/skills/e-code-review/reference/fix.md +54 -0
  137. package/skills/e-code-review/reference/loop.md +43 -0
  138. package/skills/e-code-review/reference/low.md +57 -0
  139. package/skills/e-code-review/reference/medium.md +153 -0
  140. package/skills/e-code-review/reference/xhigh.md +182 -0
  141. package/skills/e-simplify/SKILL.md +97 -0
  142. package/skills/issue-tracker/SKILL.md +92 -0
  143. package/skills/issue-tracker/reference/epic-and-sub-issue-model.md +55 -0
  144. package/skills/issue-tracker/reference/handoff-schema.md +64 -0
  145. package/skills/issue-tracker/reference/operation-matrix.md +41 -0
  146. package/skills/orchestrator/SKILL.md +162 -21
  147. package/skills/orchestrator/scripts/status_gate.py +625 -0
  148. package/skills/orchestrator/scripts/status_gate_constants/__init__.py +1 -0
  149. package/skills/orchestrator/scripts/status_gate_constants/config/__init__.py +1 -0
  150. package/skills/orchestrator/scripts/status_gate_constants/config/constants.py +47 -0
  151. package/skills/orchestrator/scripts/test_status_gate.py +439 -0
  152. package/skills/orchestrator-refresh/SKILL.md +110 -35
  153. package/skills/plan-to-pr/SKILL.md +155 -0
  154. package/skills/plan-to-pr/reference/final-validation-tasks.md +15 -0
  155. package/skills/plan-to-pr/reference/model-routing.md +36 -0
  156. package/skills/plan-to-pr/reference/packet-contract.md +43 -0
  157. package/skills/plan-to-pr/reference/packet-schema.json +57 -0
  158. package/skills/plan-to-pr/reference/process-inventory.md +22 -0
  159. package/skills/plan-to-pr/reference/review-loop.md +33 -0
  160. package/skills/plan-to-pr/reference/run-record.schema.json +27 -0
  161. package/skills/plan-to-pr/reference/self-audit-tasks.md +15 -0
  162. package/skills/plan-to-pr/reference/task-seeds.md +14 -0
  163. package/skills/plan-to-pr/reference/task-ticket.md +38 -0
  164. package/skills/plan-to-pr/scripts/config/__init__.py +1 -0
  165. package/skills/plan-to-pr/scripts/config/constants.py +193 -0
  166. package/skills/plan-to-pr/scripts/create_packet.py +173 -0
  167. package/skills/plan-to-pr/scripts/test_create_packet.py +102 -0
  168. package/skills/plan-to-pr/scripts/test_validate_packet.py +256 -0
  169. package/skills/plan-to-pr/scripts/test_validate_protocol.py +135 -0
  170. package/skills/plan-to-pr/scripts/test_validate_run.py +158 -0
  171. package/skills/plan-to-pr/scripts/validate_packet.py +655 -0
  172. package/skills/plan-to-pr/scripts/validate_protocol.py +622 -0
  173. package/skills/plan-to-pr/scripts/validate_run.py +173 -0
  174. package/skills/plan-to-pr/test_skill_contract.py +207 -0
  175. package/skills/plan-to-pr/test_task_ticket_contract.py +151 -0
  176. package/skills/pr-converge/SKILL.md +472 -469
  177. package/skills/pr-converge/reference/examples.md +3 -3
  178. package/skills/pr-converge/reference/fix-protocol.md +1 -1
  179. package/skills/pr-converge/reference/ground-rules.md +7 -4
  180. package/skills/pr-converge/reference/multi-pr-orchestration.md +4 -1
  181. package/skills/pr-converge/reference/per-tick.md +5 -5
  182. package/skills/pr-converge/reference/progress-checklist.md +1 -1
  183. package/skills/pr-converge/scripts/check_convergence_gates.py +279 -279
  184. package/skills/pr-converge/scripts/test_check_convergence_codex.py +507 -507
  185. package/skills/pr-converge/scripts/test_check_convergence_gates.py +84 -84
  186. package/skills/pr-converge/test_step5_host_branch.py +1 -1
  187. package/skills/pr-fix-protocol/SKILL.md +1 -1
  188. package/skills/privacy-hygiene/SKILL.md +68 -68
  189. package/skills/prototype/workflows/promotion.md +1 -1
  190. package/skills/release-notes-html/SKILL.md +164 -0
  191. package/skills/task-build/CLAUDE.md +8 -7
  192. package/skills/task-build/SKILL.md +16 -8
  193. package/skills/task-build/reference/tool-routing.md +19 -0
  194. package/scripts/test_invoke_code_review.py +0 -966
  195. package/skills/closeout/reference/issue-body-templates.md +0 -108
@@ -1,159 +1,160 @@
1
- """Constants for the verified-commit gate hook family.
2
-
3
- Shared by ``verification_verdict_store.py``, ``verified_commit_gate.py``,
4
- and ``verifier_verdict_minter.py`` so every tunable lives in one place.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- GIT_TIMEOUT_SECONDS = 30
10
- ROOT_KEY_HEX_LENGTH = 16
11
- VERDICT_JSON_INDENT = 2
12
- CLAUDE_HOME_DIRECTORY_NAME = ".claude"
13
- VERDICT_DIRECTORY_NAME = "verification"
14
- VERDICT_DIRECTORY_NAME_SEPARATOR_PATTERN = r"['\"\\/,\s]+"
15
- VERDICT_DIRECTORY_PATH_BOUNDARY_PATTERN = r"(?=['\"]*[\\/,])"
16
- RELATIVE_VERDICT_DIRECTORY_PATTERN = r"(?:^|(?<=[\s;&|(='\"]))verification[\\/]"
17
- VERDICT_PATH_GLUE_PATTERN = r"['\"+\\/\s]*[\\/]['\"+\\/\s]*"
18
- VERDICT_DIRECTORY_CHANGE_TARGET_PATTERN = r"[ \t]+['\"]?verification[\\/]?['\"]?(?=[\s;&|]|$)"
19
- VERDICT_PATH_JOINED_VARIABLE_PATTERN = r"\$\{?(\w+)\}?[\\/]|[\\/]\$\{?(\w+)\}?"
20
- VERDICT_PATH_VARIABLE_ASSIGNMENT_PATTERN = r"(?:^|(?<=[\s;&|(]))%s=(\S+)"
21
- VERDICT_FILE_RELATIVE_REFERENCE_PATTERN = (
22
- rf"(?:^|(?<=[\s;&|(='\"\\/]))verification[\\/][0-9a-f]{{{ROOT_KEY_HEX_LENGTH}}}\.json"
23
- )
24
- PATH_OBFUSCATION_PRIMITIVE_PATTERN = (
25
- r"chr\s*\(|bytes\.fromhex\s*\(|b64decode\s*\(|codecs\.decode\s*\("
26
- r"|(?:bytes|bytearray)\s*\(\s*\[|\[char\[?\]?\]"
27
- )
28
- ALL_VERDICT_PATH_SEGMENT_NAMES = (".claude", "verification")
29
- ALL_VERDICT_PATH_SEGMENT_BODIES = ("claude", "verification")
30
- HEX_TOKEN_PATTERN = r"(?<![0-9a-fx])([0-9a-f]{6,})(?![0-9a-f])"
31
- BASE64_TOKEN_PATTERN = r"[A-Za-z0-9+/]{8,}={0,2}"
32
- CHARACTER_CODE_SEQUENCE_PATTERN = r"\d{1,3}(?:\s*,\s*\d{1,3})+"
33
- CHR_CALL_CHAIN_PATTERN = r"chr\(\s*\d{1,3}\s*\)(?:\s*\+\s*chr\(\s*\d{1,3}\s*\))+"
34
- CHR_CALL_CODE_PATTERN = r"chr\(\s*(\d{1,3})\s*\)"
35
- HEX_DIGITS_PER_BYTE = 2
36
- FILE_WRITE_PRIMITIVE_PATTERN = (
37
- r"\bopen\s*\(|\.write_text\s*\(|\.write_bytes\s*\("
38
- r"|Out-File|Set-Content|Add-Content|\btee\b|>"
39
- )
40
- NON_REDIRECT_FILE_WRITE_PRIMITIVE_PATTERN = (
41
- r"\bopen\s*\(|\.write_text\s*\(|\.write_bytes\s*\("
42
- r"|Out-File|Set-Content|Add-Content|\btee\b"
43
- )
44
- WRITE_CALL_REGION_PATTERN = (
45
- r"(?:\bopen\s*\(|\.write_text\s*\(|\.write_bytes\s*\("
46
- r"|Out-File|Set-Content|Add-Content|\btee\b)[^;&|\n]*"
47
- )
48
- VERDICT_KEY_ALL_PASS = "all_pass"
49
- VERDICT_KEY_MANIFEST_SHA256 = "manifest_sha256"
50
- VERDICT_KEY_FINDINGS = "findings"
51
- VERDICT_FINDING_CHECK_KEY = "check"
52
- LOGS_DIRECTORY_NAME = "logs"
53
- VERIFIER_VERDICTS_JSONL_FILENAME = "verifier-verdicts.jsonl"
54
- LEDGER_KEY_TIMESTAMP = "timestamp"
55
- LEDGER_KEY_REPO_ROOT = "repo_root"
56
- LEDGER_KEY_BRANCH = "branch"
57
- LEDGER_KEY_MANIFEST_HASH = "manifest_hash"
58
- LEDGER_KEY_ALL_PASS = "all_pass"
59
- LEDGER_KEY_FINDING_COUNT = "finding_count"
60
- LEDGER_KEY_FINDING_CHECK_NAMES = "finding_check_names"
61
- LEDGER_KEY_MINTED_FROM_AGENT_ID = "minted_from_agent_id"
62
- VERDICT_FILE_GLOB = "*.json"
63
- SUBAGENTS_DIRECTORY_NAME = "subagents"
64
- AGENT_TRANSCRIPT_GLOB = "agent-*.jsonl"
65
- AGENT_META_SIDECAR_SUFFIX = ".meta.json"
66
- AGENT_META_TYPE_KEY = "agentType"
67
- TRANSCRIPT_ENTRY_TYPE_KEY = "type"
68
- TRANSCRIPT_ASSISTANT_ENTRY_TYPE = "assistant"
69
- TRANSCRIPT_MESSAGE_KEY = "message"
70
- TRANSCRIPT_CONTENT_KEY = "content"
71
- TRANSCRIPT_CONTENT_TYPE_KEY = "type"
72
- TRANSCRIPT_TEXT_CONTENT_TYPE = "text"
73
- TRANSCRIPT_TEXT_KEY = "text"
74
- VERDICT_FENCE_PATTERN = r"```verdict\s*\n(.*?)```"
75
- MANIFEST_HASH_CLI_FLAG = "--manifest-hash"
76
- MANIFEST_HASH_FOR_BRANCH_CLI_FLAG = "--manifest-hash-for-branch"
77
- WORKTREE_LIST_PATH_PREFIX = "worktree "
78
- WORKTREE_LIST_BRANCH_PREFIX = "branch "
79
- BRANCH_REFERENCE_PREFIX = "refs/heads/"
80
- EMPTY_SURFACE_GUARD_MESSAGE = (
81
- "ERROR: The work tree at {repo_root} has no changed or untracked files "
82
- "versus origin/main (empty change surface). This work tree holds nothing "
83
- "to verify — you are pointed at the wrong work tree. Run `git worktree list` "
84
- "to see all checked-out work trees and target the one on the branch under review."
85
- )
86
- BRANCH_WORKTREE_ABSENT_MESSAGE = (
87
- "ERROR: No work tree has branch '{branch}' checked out. "
88
- "Check it out in a work tree first: "
89
- "`git worktree add <path> <branch>` or `git checkout <branch>`."
90
- )
91
- DOCS_ONLY_EXTENSIONS = frozenset(
92
- {".md", ".txt", ".rst", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".ico"}
93
- )
94
- PYTHON_EXTENSION = ".py"
95
- TEST_FILE_PREFIX = "test_"
96
- TEST_FILE_SUFFIX = "_test.py"
97
- CONFTEST_FILE_NAME = "conftest.py"
98
- MINIMUM_STATUS_FIELD_COUNT = 2
99
- ALL_FALLBACK_BASE_REFERENCES = ("origin/main", "origin/master")
100
- DETACHED_HEAD_LABEL = "HEAD"
101
- BRANCH_REMOTE_CONFIG_KEY_TEMPLATE = "branch.{branch_name}.remote"
102
- ALL_TOOLING_STATE_PREFIXES = (
103
- ".claude/verification/",
104
- ".claude/worktrees/",
105
- ".claude/daemon/",
106
- ".claude/teams/",
107
- ".claude/sessions/",
108
- ".cursor/worktrees/",
109
- )
110
- GATED_GIT_SUBCOMMANDS = frozenset({"commit", "push"})
111
- ALL_GIT_BINARY_NAMES = frozenset({"git", "git.exe"})
112
- VALUE_TAKING_GIT_OPTIONS = frozenset({"-C", "-c", "--git-dir", "--work-tree", "--namespace"})
113
- REPO_DIRECTORY_OPTION = "-C"
114
- WORK_TREE_OPTION = "--work-tree"
115
- DIRECTORY_CHANGE_VERBS = frozenset({"cd", "pushd", "set-location", "sl"})
116
- DIRECTORY_CHANGE_PATH_OPTIONS = frozenset({"-path", "-literalpath"})
117
- DIRECTORY_CHANGE_OPTION_TERMINATOR = "--"
118
- DIRECTORY_CHANGE_PATTERN_PREFIX = r"(?:^|(?<=[\s;&|(]))(?:"
119
- DIRECTORY_CHANGE_PATTERN_SUFFIX = r")(?=\s|$)"
120
- DIRECTORY_CHANGE_OPTION_PREFIX_PATTERN = r"(?:[ \t]+(?:%s)(?=\s|$))*"
121
- DIRECTORY_CHANGE_TARGET_PATTERN = r"[ \t]+['\"]?\S*"
122
- CLAUDE_HOME_TARGET_BOUNDARY_PATTERN = r"[\\/]?['\"]?(?=[\s;&|]|$)"
123
- VERDICT_DIRECTORY_TARGET_BOUNDARY_PATTERN = r"[\\/]?['\"]?(?=[\s;&|]|$)"
124
- COMMAND_AFTER_DIRECTORY_CHANGE_PATTERN = r"[;&|\n][\s]*\S"
125
- OPTION_WITH_VALUE_STEP = 2
126
- ALL_GATED_TOOL_NAMES = ("Bash", "PowerShell")
127
- HASH_PREVIEW_LENGTH = 16
128
- VERIFICATION_BYPASS_MARKER = "# verify-skip"
129
- MINTING_AGENT_TYPE = "code-verifier"
130
- VERDICT_DIRECTORY_GUARD_MESSAGE = (
131
- "BLOCKED: [VERDICT_DIRECTORY_GUARD] Shell access to the verification "
132
- "verdict directory (~/.claude/verification/) is denied. Only the "
133
- "verifier_verdict_minter.py SubagentStop hook writes verdict files; a "
134
- "shell write here would forge a passing verdict and defeat the "
135
- "verified-commit gate. Spawn the code-verifier agent to earn a verdict "
136
- "instead of writing one."
137
- )
138
- CORRECTIVE_MESSAGE = (
139
- "BLOCKED: [VERIFIED_COMMIT_GATE] This branch surface has no passing "
140
- "verification verdict. Clear commit-committability gates first "
141
- "(CODE_RULES on changed lines and merge conflicts vs base — the "
142
- "code_verifier_spawn_preflight_gate enforces these on the spawn). If a "
143
- "prior commit failed on CODE_RULES or staging, fix those before "
144
- "re-verifying. Then spawn the code-verifier agent (Agent or Task tool, "
145
- "subagent_type 'code-verifier') with the task texts, the diff scope, "
146
- "and recorded baselines; when it finishes with a clean verdict the "
147
- "SubagentStop hook mints the verdict and this command will pass. Any "
148
- "file change after verification invalidates the verdict, so verify "
149
- "last only after commit gates are green. The verdict binds to the exact "
150
- "surface content (every changed and untracked file's bytes), not to a "
151
- "work-tree path, so a commit from a different work tree passes only "
152
- "when its surface is byte-identical; a work tree carrying any extra or "
153
- "later edit has a surface no verdict covers. To land this change, run "
154
- "this command from the work tree that holds the verified surface, or "
155
- "verify this surface here. Exempt automatically: docs/image files, "
156
- "pytest test files, and "
157
- "Python files whose docstring- and comment-stripped AST is unchanged "
158
- "(comment-only edits in non-Python files are not exempt)."
159
- )
1
+ """Constants for the verified-commit gate hook family.
2
+
3
+ Shared by ``verification_verdict_store.py``, ``verified_commit_gate.py``,
4
+ and ``verifier_verdict_minter.py`` so every tunable lives in one place.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ GIT_TIMEOUT_SECONDS = 30
10
+ ROOT_KEY_HEX_LENGTH = 16
11
+ VERDICT_JSON_INDENT = 2
12
+ CLAUDE_HOME_DIRECTORY_NAME = ".claude"
13
+ VERDICT_DIRECTORY_NAME = "verification"
14
+ VERDICT_DIRECTORY_NAME_SEPARATOR_PATTERN = r"['\"\\/,\s]+"
15
+ VERDICT_DIRECTORY_PATH_BOUNDARY_PATTERN = r"(?=['\"]*[\\/,])"
16
+ RELATIVE_VERDICT_DIRECTORY_PATTERN = r"(?:^|(?<=[\s;&|(='\"]))verification[\\/]"
17
+ VERDICT_PATH_GLUE_PATTERN = r"['\"+\\/\s]*[\\/]['\"+\\/\s]*"
18
+ VERDICT_DIRECTORY_CHANGE_TARGET_PATTERN = r"[ \t]+['\"]?verification[\\/]?['\"]?(?=[\s;&|]|$)"
19
+ VERDICT_PATH_JOINED_VARIABLE_PATTERN = r"\$\{?(\w+)\}?[\\/]|[\\/]\$\{?(\w+)\}?"
20
+ VERDICT_PATH_VARIABLE_ASSIGNMENT_PATTERN = r"(?:^|(?<=[\s;&|(]))%s=(\S+)"
21
+ VERDICT_FILE_RELATIVE_REFERENCE_PATTERN = (
22
+ rf"(?:^|(?<=[\s;&|(='\"\\/]))verification[\\/][0-9a-f]{{{ROOT_KEY_HEX_LENGTH}}}\.json"
23
+ )
24
+ PATH_OBFUSCATION_PRIMITIVE_PATTERN = (
25
+ r"chr\s*\(|bytes\.fromhex\s*\(|b64decode\s*\(|codecs\.decode\s*\("
26
+ r"|(?:bytes|bytearray)\s*\(\s*\[|\[char\[?\]?\]"
27
+ )
28
+ ALL_VERDICT_PATH_SEGMENT_NAMES = (".claude", "verification")
29
+ ALL_VERDICT_PATH_SEGMENT_BODIES = ("claude", "verification")
30
+ HEX_TOKEN_PATTERN = r"(?<![0-9a-fx])([0-9a-f]{6,})(?![0-9a-f])"
31
+ BASE64_TOKEN_PATTERN = r"[A-Za-z0-9+/]{8,}={0,2}"
32
+ CHARACTER_CODE_SEQUENCE_PATTERN = r"\d{1,3}(?:\s*,\s*\d{1,3})+"
33
+ CHR_CALL_CHAIN_PATTERN = r"chr\(\s*\d{1,3}\s*\)(?:\s*\+\s*chr\(\s*\d{1,3}\s*\))+"
34
+ CHR_CALL_CODE_PATTERN = r"chr\(\s*(\d{1,3})\s*\)"
35
+ HEX_DIGITS_PER_BYTE = 2
36
+ FILE_WRITE_PRIMITIVE_PATTERN = (
37
+ r"\bopen\s*\(|\.write_text\s*\(|\.write_bytes\s*\("
38
+ r"|Out-File|Set-Content|Add-Content|\btee\b|>"
39
+ )
40
+ NON_REDIRECT_FILE_WRITE_PRIMITIVE_PATTERN = (
41
+ r"\bopen\s*\(|\.write_text\s*\(|\.write_bytes\s*\("
42
+ r"|Out-File|Set-Content|Add-Content|\btee\b"
43
+ )
44
+ WRITE_CALL_REGION_PATTERN = (
45
+ r"(?:\bopen\s*\(|\.write_text\s*\(|\.write_bytes\s*\("
46
+ r"|Out-File|Set-Content|Add-Content|\btee\b)[^;&|\n]*"
47
+ )
48
+ VERDICT_KEY_ALL_PASS = "all_pass"
49
+ VERDICT_KEY_MANIFEST_SHA256 = "manifest_sha256"
50
+ VERDICT_KEY_FINDINGS = "findings"
51
+ VERDICT_FINDING_CHECK_KEY = "check"
52
+ LOGS_DIRECTORY_NAME = "logs"
53
+ VERIFIER_VERDICTS_JSONL_FILENAME = "verifier-verdicts.jsonl"
54
+ LEDGER_KEY_TIMESTAMP = "timestamp"
55
+ LEDGER_KEY_REPO_ROOT = "repo_root"
56
+ LEDGER_KEY_BRANCH = "branch"
57
+ LEDGER_KEY_MANIFEST_HASH = "manifest_hash"
58
+ LEDGER_KEY_ALL_PASS = "all_pass"
59
+ LEDGER_KEY_FINDING_COUNT = "finding_count"
60
+ LEDGER_KEY_FINDING_CHECK_NAMES = "finding_check_names"
61
+ LEDGER_KEY_MINTED_FROM_AGENT_ID = "minted_from_agent_id"
62
+ VERDICT_FILE_GLOB = "*.json"
63
+ SUBAGENTS_DIRECTORY_NAME = "subagents"
64
+ AGENT_TRANSCRIPT_GLOB = "agent-*.jsonl"
65
+ AGENT_META_SIDECAR_SUFFIX = ".meta.json"
66
+ AGENT_META_TYPE_KEY = "agentType"
67
+ TRANSCRIPT_ENTRY_TYPE_KEY = "type"
68
+ TRANSCRIPT_ASSISTANT_ENTRY_TYPE = "assistant"
69
+ TRANSCRIPT_MESSAGE_KEY = "message"
70
+ TRANSCRIPT_CONTENT_KEY = "content"
71
+ TRANSCRIPT_CONTENT_TYPE_KEY = "type"
72
+ TRANSCRIPT_TEXT_CONTENT_TYPE = "text"
73
+ TRANSCRIPT_TEXT_KEY = "text"
74
+ VERDICT_FENCE_PATTERN = r"```verdict\s*\n(.*?)```"
75
+ MANIFEST_HASH_CLI_FLAG = "--manifest-hash"
76
+ MANIFEST_HASH_FOR_BRANCH_CLI_FLAG = "--manifest-hash-for-branch"
77
+ WORKTREE_LIST_PATH_PREFIX = "worktree "
78
+ WORKTREE_LIST_BRANCH_PREFIX = "branch "
79
+ BRANCH_REFERENCE_PREFIX = "refs/heads/"
80
+ EMPTY_SURFACE_GUARD_MESSAGE = (
81
+ "ERROR: The work tree at {repo_root} has no changed or untracked files "
82
+ "versus origin/main (empty change surface). This work tree holds nothing "
83
+ "to verify — you are pointed at the wrong work tree. Run `git worktree list` "
84
+ "to see all checked-out work trees and target the one on the branch under review."
85
+ )
86
+ BRANCH_WORKTREE_ABSENT_MESSAGE = (
87
+ "ERROR: No work tree has branch '{branch}' checked out. "
88
+ "Check it out in a work tree first: "
89
+ "`git worktree add <path> <branch>` or `git checkout <branch>`."
90
+ )
91
+ DOCS_ONLY_EXTENSIONS = frozenset(
92
+ {".md", ".txt", ".rst", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".ico"}
93
+ )
94
+ PYTHON_EXTENSION = ".py"
95
+ TEST_FILE_PREFIX = "test_"
96
+ TEST_FILE_SUFFIX = "_test.py"
97
+ CONFTEST_FILE_NAME = "conftest.py"
98
+ MINIMUM_STATUS_FIELD_COUNT = 2
99
+ ALL_FALLBACK_BASE_REFERENCES = ("origin/main", "origin/master")
100
+ DETACHED_HEAD_LABEL = "HEAD"
101
+ BRANCH_REMOTE_CONFIG_KEY_TEMPLATE = "branch.{branch_name}.remote"
102
+ ALL_TOOLING_STATE_PREFIXES = (
103
+ ".claude/verification/",
104
+ ".claude/worktrees/",
105
+ ".claude/daemon/",
106
+ ".claude/teams/",
107
+ ".claude/sessions/",
108
+ ".cursor/worktrees/",
109
+ ".claude-plugin-data/",
110
+ )
111
+ GATED_GIT_SUBCOMMANDS = frozenset({"commit", "push"})
112
+ ALL_GIT_BINARY_NAMES = frozenset({"git", "git.exe"})
113
+ VALUE_TAKING_GIT_OPTIONS = frozenset({"-C", "-c", "--git-dir", "--work-tree", "--namespace"})
114
+ REPO_DIRECTORY_OPTION = "-C"
115
+ WORK_TREE_OPTION = "--work-tree"
116
+ DIRECTORY_CHANGE_VERBS = frozenset({"cd", "pushd", "set-location", "sl"})
117
+ DIRECTORY_CHANGE_PATH_OPTIONS = frozenset({"-path", "-literalpath"})
118
+ DIRECTORY_CHANGE_OPTION_TERMINATOR = "--"
119
+ DIRECTORY_CHANGE_PATTERN_PREFIX = r"(?:^|(?<=[\s;&|(]))(?:"
120
+ DIRECTORY_CHANGE_PATTERN_SUFFIX = r")(?=\s|$)"
121
+ DIRECTORY_CHANGE_OPTION_PREFIX_PATTERN = r"(?:[ \t]+(?:%s)(?=\s|$))*"
122
+ DIRECTORY_CHANGE_TARGET_PATTERN = r"[ \t]+['\"]?\S*"
123
+ CLAUDE_HOME_TARGET_BOUNDARY_PATTERN = r"[\\/]?['\"]?(?=[\s;&|]|$)"
124
+ VERDICT_DIRECTORY_TARGET_BOUNDARY_PATTERN = r"[\\/]?['\"]?(?=[\s;&|]|$)"
125
+ COMMAND_AFTER_DIRECTORY_CHANGE_PATTERN = r"[;&|\n][\s]*\S"
126
+ OPTION_WITH_VALUE_STEP = 2
127
+ ALL_GATED_TOOL_NAMES = ("Bash", "PowerShell")
128
+ HASH_PREVIEW_LENGTH = 16
129
+ VERIFICATION_BYPASS_MARKER = "# verify-skip"
130
+ MINTING_AGENT_TYPE = "code-verifier"
131
+ VERDICT_DIRECTORY_GUARD_MESSAGE = (
132
+ "BLOCKED: [VERDICT_DIRECTORY_GUARD] Shell access to the verification "
133
+ "verdict directory (~/.claude/verification/) is denied. Only the "
134
+ "verifier_verdict_minter.py SubagentStop hook writes verdict files; a "
135
+ "shell write here would forge a passing verdict and defeat the "
136
+ "verified-commit gate. Spawn the code-verifier agent to earn a verdict "
137
+ "instead of writing one."
138
+ )
139
+ CORRECTIVE_MESSAGE = (
140
+ "BLOCKED: [VERIFIED_COMMIT_GATE] This branch surface has no passing "
141
+ "verification verdict. Clear commit-committability gates first "
142
+ "(CODE_RULES on changed lines and merge conflicts vs base — the "
143
+ "code_verifier_spawn_preflight_gate enforces these on the spawn). If a "
144
+ "prior commit failed on CODE_RULES or staging, fix those before "
145
+ "re-verifying. Then spawn the code-verifier agent (Agent or Task tool, "
146
+ "subagent_type 'code-verifier') with the task texts, the diff scope, "
147
+ "and recorded baselines; when it finishes with a clean verdict the "
148
+ "SubagentStop hook mints the verdict and this command will pass. Any "
149
+ "file change after verification invalidates the verdict, so verify "
150
+ "last only after commit gates are green. The verdict binds to the exact "
151
+ "surface content (every changed and untracked file's bytes), not to a "
152
+ "work-tree path, so a commit from a different work tree passes only "
153
+ "when its surface is byte-identical; a work tree carrying any extra or "
154
+ "later edit has a surface no verdict covers. To land this change, run "
155
+ "this command from the work tree that holds the verified surface, or "
156
+ "verify this surface here. Exempt automatically: docs/image files, "
157
+ "pytest test files, and "
158
+ "Python files whose docstring- and comment-stripped AST is unchanged "
159
+ "(comment-only edits in non-Python files are not exempt)."
160
+ )
@@ -0,0 +1,256 @@
1
+ #!/usr/bin/env python3
2
+ """PreToolUse gate: deny orchestrator-refresh re-arm when the run is not active.
3
+
4
+ Fires on ``ScheduleWakeup`` and ``CronCreate`` when the prompt targets
5
+ ``/orchestrator-refresh``. Uses the same status-file gate as
6
+ ``skills/orchestrator/scripts/status_gate.py`` so idle loops
7
+ cannot be re-armed on the Claude host without an active status file.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import importlib.util
13
+ import json
14
+ import os
15
+ import re
16
+ import sys
17
+ from pathlib import Path
18
+ from types import ModuleType
19
+ from typing import Mapping
20
+
21
+ _hooks_dir = str(Path(__file__).resolve().parent.parent)
22
+ if _hooks_dir not in sys.path:
23
+ sys.path.insert(0, _hooks_dir)
24
+
25
+ from hooks_constants.hook_block_logger import log_hook_block # noqa: E402
26
+ from hooks_constants.orchestrator_refresh_reschedule_gate_constants import ( # noqa: E402
27
+ ALL_HOME_SKILLS_RELATIVE_PARTS,
28
+ ALL_SCHEDULE_TOOL_NAMES,
29
+ ALL_SKILL_SCRIPTS_RELATIVE_PARTS,
30
+ CALLING_HOOK_NAME,
31
+ CRON_CREATE_TOOL_NAME,
32
+ CWD_FIELD_NAME,
33
+ DENY_REASON_TEMPLATE,
34
+ HOOK_EVENT_NAME,
35
+ ORCHESTRATOR_REFRESH_PROMPT_TOKEN,
36
+ PLUGIN_ROOT_ENV_VAR,
37
+ PROMPT_FIELD_NAME,
38
+ REASON_CRON_CREATE_FORBIDDEN,
39
+ RUN_SLUG_PROMPT_FLAG,
40
+ RUN_SLUG_PROMPT_VALUE_PATTERN,
41
+ TOOL_INPUT_FIELD_NAME,
42
+ TOOL_NAME_FIELD_NAME,
43
+ )
44
+
45
+
46
+ def _candidate_skill_script_directories() -> list[Path]:
47
+ """Return ordered paths that may contain status_gate.py.
48
+
49
+ Returns:
50
+ Absolute candidate directories in preference order.
51
+ """
52
+ all_candidates: list[Path] = []
53
+ plugin_root = os.environ.get(PLUGIN_ROOT_ENV_VAR)
54
+ if plugin_root:
55
+ all_candidates.append(
56
+ Path(plugin_root).joinpath(*ALL_SKILL_SCRIPTS_RELATIVE_PARTS)
57
+ )
58
+ package_hooks_parent = Path(__file__).resolve().parents[2]
59
+ all_candidates.append(
60
+ package_hooks_parent.joinpath(*ALL_SKILL_SCRIPTS_RELATIVE_PARTS)
61
+ )
62
+ all_candidates.append(Path.home().joinpath(*ALL_HOME_SKILLS_RELATIVE_PARTS))
63
+ return all_candidates
64
+
65
+
66
+ def _load_status_gate_module() -> ModuleType | None:
67
+ """Import status_gate from the skill scripts package.
68
+
69
+ Returns:
70
+ The loaded module, or None when no candidate path is usable.
71
+ """
72
+ for each_directory in _candidate_skill_script_directories():
73
+ module_path = each_directory / "status_gate.py"
74
+ if not module_path.is_file():
75
+ continue
76
+ scripts_directory = str(each_directory)
77
+ if scripts_directory not in sys.path:
78
+ sys.path.insert(0, scripts_directory)
79
+ spec = importlib.util.spec_from_file_location(
80
+ "orchestrator_status_gate_for_reschedule_hook",
81
+ module_path,
82
+ )
83
+ if spec is None or spec.loader is None:
84
+ continue
85
+ status_gate_module = importlib.util.module_from_spec(spec)
86
+ sys.modules[spec.name] = status_gate_module
87
+ spec.loader.exec_module(status_gate_module)
88
+ return status_gate_module
89
+ return None
90
+
91
+
92
+ def _prompt_targets_orchestrator_refresh(
93
+ all_tool_input: Mapping[str, object],
94
+ ) -> bool:
95
+ """Return True when the schedule prompt re-enters orchestrator-refresh.
96
+
97
+ Args:
98
+ all_tool_input: PreToolUse tool_input mapping.
99
+
100
+ Returns:
101
+ True when the prompt field contains the refresh skill token.
102
+ """
103
+ prompt_value = all_tool_input.get(PROMPT_FIELD_NAME, "")
104
+ if not isinstance(prompt_value, str):
105
+ return False
106
+ stripped_prompt = prompt_value.lstrip()
107
+ if not stripped_prompt.startswith(ORCHESTRATOR_REFRESH_PROMPT_TOKEN):
108
+ return False
109
+ if len(stripped_prompt) == len(ORCHESTRATOR_REFRESH_PROMPT_TOKEN):
110
+ return True
111
+ return stripped_prompt[len(ORCHESTRATOR_REFRESH_PROMPT_TOKEN)] in (" ", "\n", "\t")
112
+
113
+
114
+ def _should_evaluate(all_payload_by_field: Mapping[str, object]) -> bool:
115
+ """Return True for schedule tools re-arming orchestrator-refresh.
116
+
117
+ Args:
118
+ all_payload_by_field: Full PreToolUse payload.
119
+
120
+ Returns:
121
+ True when this gate must run.
122
+ """
123
+ tool_name = all_payload_by_field.get(TOOL_NAME_FIELD_NAME, "")
124
+ if tool_name not in ALL_SCHEDULE_TOOL_NAMES:
125
+ return False
126
+ tool_input = all_payload_by_field.get(TOOL_INPUT_FIELD_NAME, {})
127
+ if not isinstance(tool_input, dict):
128
+ return False
129
+ return _prompt_targets_orchestrator_refresh(tool_input)
130
+
131
+
132
+ def _run_slug_from_prompt(prompt_value: str) -> str:
133
+ """Extract ``--run-slug`` from a ScheduleWakeup prompt when present.
134
+
135
+ Args:
136
+ prompt_value: The schedule tool prompt text.
137
+
138
+ Returns:
139
+ The slug string, or empty when the flag is absent.
140
+ """
141
+ run_slug_from_prompt_pattern = re.compile(
142
+ re.escape(RUN_SLUG_PROMPT_FLAG) + RUN_SLUG_PROMPT_VALUE_PATTERN
143
+ )
144
+ matched = run_slug_from_prompt_pattern.search(prompt_value)
145
+ if matched is None:
146
+ return ""
147
+ return matched.group(1)
148
+
149
+
150
+ def _resolve_status_file_path(
151
+ status_gate_module: ModuleType,
152
+ all_payload_by_field: Mapping[str, object],
153
+ ) -> Path:
154
+ """Resolve the run status file for this schedule attempt without chdir.
155
+
156
+ Args:
157
+ status_gate_module: Loaded status_gate module.
158
+ all_payload_by_field: PreToolUse payload (may carry cwd and prompt).
159
+
160
+ Returns:
161
+ Absolute status file path.
162
+ """
163
+ base_directory: Path | None = None
164
+ working_directory_value = all_payload_by_field.get(CWD_FIELD_NAME)
165
+ if isinstance(working_directory_value, str) and working_directory_value:
166
+ base_directory = Path(working_directory_value)
167
+ run_slug = ""
168
+ tool_input = all_payload_by_field.get(TOOL_INPUT_FIELD_NAME, {})
169
+ if isinstance(tool_input, dict):
170
+ prompt_value = tool_input.get(PROMPT_FIELD_NAME, "")
171
+ if isinstance(prompt_value, str):
172
+ run_slug = _run_slug_from_prompt(prompt_value)
173
+ return status_gate_module.resolve_status_file_path(
174
+ None, base_directory, run_slug
175
+ )
176
+
177
+
178
+ def build_denial_response(reason_code: str) -> dict[str, dict[str, str]]:
179
+ """Build the PreToolUse deny payload.
180
+
181
+ Args:
182
+ reason_code: Machine reason from decide_should_reschedule.
183
+
184
+ Returns:
185
+ Hook-specific deny JSON structure.
186
+ """
187
+ denial_reason = DENY_REASON_TEMPLATE.format(reason=reason_code)
188
+ return {
189
+ "hookSpecificOutput": {
190
+ "hookEventName": HOOK_EVENT_NAME,
191
+ "permissionDecision": "deny",
192
+ "permissionDecisionReason": denial_reason,
193
+ }
194
+ }
195
+
196
+
197
+ def _emit_denial(
198
+ reason_code: str,
199
+ all_payload_by_field: Mapping[str, object],
200
+ ) -> None:
201
+ """Log and print a deny decision for the current schedule attempt.
202
+
203
+ Args:
204
+ reason_code: Machine reason string.
205
+ all_payload_by_field: PreToolUse payload for logging context.
206
+ """
207
+ denial = build_denial_response(reason_code)
208
+ hook_specific_output = denial["hookSpecificOutput"]
209
+ block_reason = hook_specific_output["permissionDecisionReason"]
210
+ log_hook_block(
211
+ calling_hook_name=CALLING_HOOK_NAME,
212
+ hook_event=HOOK_EVENT_NAME,
213
+ block_reason=block_reason,
214
+ tool_name=str(all_payload_by_field.get(TOOL_NAME_FIELD_NAME, "")),
215
+ offending_input_preview=str(
216
+ all_payload_by_field.get(TOOL_INPUT_FIELD_NAME, {})
217
+ ),
218
+ )
219
+ print(json.dumps(denial))
220
+
221
+
222
+ def main() -> None:
223
+ """Read PreToolUse stdin and deny idle orchestrator-refresh re-arms."""
224
+ try:
225
+ payload_by_field = json.load(sys.stdin)
226
+ except json.JSONDecodeError:
227
+ sys.exit(0)
228
+
229
+ if not isinstance(payload_by_field, dict):
230
+ sys.exit(0)
231
+ if not _should_evaluate(payload_by_field):
232
+ sys.exit(0)
233
+
234
+ tool_name = payload_by_field.get(TOOL_NAME_FIELD_NAME, "")
235
+ if tool_name == CRON_CREATE_TOOL_NAME:
236
+ _emit_denial(REASON_CRON_CREATE_FORBIDDEN, payload_by_field)
237
+ sys.exit(0)
238
+
239
+ status_gate_module = _load_status_gate_module()
240
+ if status_gate_module is None:
241
+ _emit_denial("status_gate_unavailable", payload_by_field)
242
+ sys.exit(0)
243
+
244
+ status_file_path = _resolve_status_file_path(status_gate_module, payload_by_field)
245
+ is_reschedule_allowed, reason_code = status_gate_module.decide_should_reschedule(
246
+ status_file_path
247
+ )
248
+ if is_reschedule_allowed:
249
+ sys.exit(0)
250
+
251
+ _emit_denial(reason_code, payload_by_field)
252
+ sys.exit(0)
253
+
254
+
255
+ if __name__ == "__main__":
256
+ main()