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,439 @@
1
+ """Tests for orchestrator status_gate (status file + single-pending re-arm)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.util
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+ from types import ModuleType
10
+
11
+ import pytest
12
+
13
+ SCRIPTS_DIRECTORY = Path(__file__).resolve().parent
14
+ if str(SCRIPTS_DIRECTORY) not in sys.path:
15
+ sys.path.insert(0, str(SCRIPTS_DIRECTORY))
16
+
17
+ from status_gate_constants.config.constants import ( # noqa: E402
18
+ ALL_DEFAULT_STATUS_DIRECTORY_PARTS,
19
+ EXIT_CODE_STOP,
20
+ EXIT_CODE_SUCCESS,
21
+ REARM_PENDING_FIELD_NAME,
22
+ REASON_ACTIVE,
23
+ REASON_MISSING_STATUS_FILE,
24
+ REASON_REARM_ALREADY_PENDING,
25
+ REASON_STATUS_NOT_ACTIVE,
26
+ RUN_STATUS_ACTIVE,
27
+ RUN_STATUS_DONE,
28
+ STATUS_FIELD_NAME,
29
+ STATUS_FILE_ENV_VAR,
30
+ STATUS_FILE_NAME,
31
+ )
32
+
33
+
34
+ def load_status_gate_module() -> ModuleType:
35
+ module_path = SCRIPTS_DIRECTORY / "status_gate.py"
36
+ spec = importlib.util.spec_from_file_location("status_gate", module_path)
37
+ assert spec is not None
38
+ assert spec.loader is not None
39
+ status_gate_module = importlib.util.module_from_spec(spec)
40
+ sys.modules[spec.name] = status_gate_module
41
+ spec.loader.exec_module(status_gate_module)
42
+ return status_gate_module
43
+
44
+
45
+ class TestWriteAndDecide:
46
+ def should_allow_reschedule_when_status_is_active(
47
+ self, temporary_directory: Path
48
+ ) -> None:
49
+ status_gate = load_status_gate_module()
50
+ status_file_path = temporary_directory / "run-status.json"
51
+ status_gate.write_status_file(
52
+ status_file_path, RUN_STATUS_ACTIVE, "", is_rearm_pending=False
53
+ )
54
+ is_allowed, reason_code = status_gate.decide_should_reschedule(status_file_path)
55
+ assert is_allowed is True
56
+ assert reason_code == REASON_ACTIVE
57
+
58
+ def should_stop_when_status_is_done(self, temporary_directory: Path) -> None:
59
+ status_gate = load_status_gate_module()
60
+ status_file_path = temporary_directory / "run-status.json"
61
+ status_gate.write_status_file(
62
+ status_file_path, RUN_STATUS_DONE, "", is_rearm_pending=False
63
+ )
64
+ is_allowed, reason_code = status_gate.decide_should_reschedule(status_file_path)
65
+ assert is_allowed is False
66
+ assert reason_code == REASON_STATUS_NOT_ACTIVE
67
+
68
+ def should_fail_closed_when_status_file_missing(
69
+ self, temporary_directory: Path
70
+ ) -> None:
71
+ status_gate = load_status_gate_module()
72
+ status_file_path = temporary_directory / "missing.json"
73
+ is_allowed, reason_code = status_gate.decide_should_reschedule(status_file_path)
74
+ assert is_allowed is False
75
+ assert reason_code == REASON_MISSING_STATUS_FILE
76
+
77
+ def should_fail_closed_when_status_file_is_invalid_json(
78
+ self, temporary_directory: Path
79
+ ) -> None:
80
+ status_gate = load_status_gate_module()
81
+ status_file_path = temporary_directory / "bad.json"
82
+ status_file_path.write_text("{not-json", encoding="utf-8")
83
+ is_allowed, reason_code = status_gate.decide_should_reschedule(status_file_path)
84
+ assert is_allowed is False
85
+
86
+ def should_deny_reschedule_when_rearm_already_pending(
87
+ self, temporary_directory: Path
88
+ ) -> None:
89
+ status_gate = load_status_gate_module()
90
+ status_file_path = temporary_directory / "run-status.json"
91
+ status_gate.write_status_file(
92
+ status_file_path, RUN_STATUS_ACTIVE, "", is_rearm_pending=True
93
+ )
94
+ is_allowed, reason_code = status_gate.decide_should_reschedule(status_file_path)
95
+ assert is_allowed is False
96
+ assert reason_code == REASON_REARM_ALREADY_PENDING
97
+
98
+
99
+ class TestSetPreservesPending:
100
+ def should_preserve_rearm_pending_when_reasserting_active(
101
+ self, temporary_directory: Path, monkeypatch: pytest.MonkeyPatch
102
+ ) -> None:
103
+ status_gate = load_status_gate_module()
104
+ status_file_path = temporary_directory / "status.json"
105
+ status_gate.write_status_file(
106
+ status_file_path, RUN_STATUS_ACTIVE, "demo", is_rearm_pending=False
107
+ )
108
+ assert status_gate.claim_rearm_slot(status_file_path, "demo")[0] is True
109
+ monkeypatch.setattr(
110
+ sys,
111
+ "argv",
112
+ [
113
+ "status_gate.py",
114
+ "set",
115
+ "--status-file",
116
+ str(status_file_path),
117
+ "--status",
118
+ RUN_STATUS_ACTIVE,
119
+ "--run-slug",
120
+ "demo",
121
+ ],
122
+ )
123
+ assert status_gate.main() == EXIT_CODE_SUCCESS
124
+ payload = json.loads(status_file_path.read_text(encoding="utf-8"))
125
+ assert payload[REARM_PENDING_FIELD_NAME] is True
126
+ is_allowed, reason_code = status_gate.decide_should_reschedule(
127
+ status_file_path
128
+ )
129
+ assert is_allowed is False
130
+ assert reason_code == REASON_REARM_ALREADY_PENDING
131
+
132
+ def should_clear_rearm_pending_when_setting_done(
133
+ self, temporary_directory: Path, monkeypatch: pytest.MonkeyPatch
134
+ ) -> None:
135
+ status_gate = load_status_gate_module()
136
+ status_file_path = temporary_directory / "status.json"
137
+ status_gate.write_status_file(
138
+ status_file_path, RUN_STATUS_ACTIVE, "", is_rearm_pending=True
139
+ )
140
+ monkeypatch.setattr(
141
+ sys,
142
+ "argv",
143
+ [
144
+ "status_gate.py",
145
+ "set",
146
+ "--status-file",
147
+ str(status_file_path),
148
+ "--status",
149
+ RUN_STATUS_DONE,
150
+ ],
151
+ )
152
+ assert status_gate.main() == EXIT_CODE_SUCCESS
153
+ payload = json.loads(status_file_path.read_text(encoding="utf-8"))
154
+ assert payload[STATUS_FIELD_NAME] == RUN_STATUS_DONE
155
+ assert payload[REARM_PENDING_FIELD_NAME] is False
156
+
157
+
158
+ class TestBeginClaimRelease:
159
+ def should_claim_rearm_only_once_until_begin_firing(
160
+ self, temporary_directory: Path
161
+ ) -> None:
162
+ status_gate = load_status_gate_module()
163
+ status_file_path = temporary_directory / "run-status.json"
164
+ status_gate.write_status_file(
165
+ status_file_path, RUN_STATUS_ACTIVE, "demo", is_rearm_pending=False
166
+ )
167
+ is_claimed, reason_code, payload = status_gate.claim_rearm_slot(
168
+ status_file_path, "demo"
169
+ )
170
+ assert is_claimed is True
171
+ assert payload is not None
172
+ assert payload[REARM_PENDING_FIELD_NAME] is True
173
+
174
+ is_claimed_again, second_reason, _second_payload = status_gate.claim_rearm_slot(
175
+ status_file_path, "demo"
176
+ )
177
+ assert is_claimed_again is False
178
+ assert second_reason == REASON_REARM_ALREADY_PENDING
179
+
180
+ is_firing, firing_reason, firing_payload = status_gate.begin_firing(
181
+ status_file_path, "demo"
182
+ )
183
+ assert is_firing is True
184
+ assert firing_payload is not None
185
+ assert firing_payload[REARM_PENDING_FIELD_NAME] is False
186
+ assert firing_reason
187
+
188
+ is_claimed_after_firing, _claim_reason, after_payload = (
189
+ status_gate.claim_rearm_slot(status_file_path, "demo")
190
+ )
191
+ assert is_claimed_after_firing is True
192
+ assert after_payload is not None
193
+ assert after_payload[REARM_PENDING_FIELD_NAME] is True
194
+
195
+ def should_release_rearm_after_failed_schedule(
196
+ self, temporary_directory: Path
197
+ ) -> None:
198
+ status_gate = load_status_gate_module()
199
+ status_file_path = temporary_directory / "run-status.json"
200
+ status_gate.write_status_file(
201
+ status_file_path, RUN_STATUS_ACTIVE, "", is_rearm_pending=False
202
+ )
203
+ assert status_gate.claim_rearm_slot(status_file_path, "")[0] is True
204
+ is_released, reason_code, payload = status_gate.release_rearm_slot(
205
+ status_file_path, ""
206
+ )
207
+ assert is_released is True
208
+ assert payload is not None
209
+ assert payload[REARM_PENDING_FIELD_NAME] is False
210
+ assert reason_code
211
+ is_allowed, allow_reason = status_gate.decide_should_reschedule(
212
+ status_file_path
213
+ )
214
+ assert is_allowed is True
215
+ assert allow_reason == REASON_ACTIVE
216
+
217
+
218
+ class TestResolveAndCli:
219
+ def should_resolve_explicit_status_file_path(
220
+ self, temporary_directory: Path
221
+ ) -> None:
222
+ status_gate = load_status_gate_module()
223
+ target_path = temporary_directory / "explicit.json"
224
+ resolved_path = status_gate.resolve_status_file_path(
225
+ str(target_path), None, ""
226
+ )
227
+ assert resolved_path == target_path.resolve()
228
+
229
+ def should_resolve_default_path_under_base_directory(
230
+ self, temporary_directory: Path
231
+ ) -> None:
232
+ status_gate = load_status_gate_module()
233
+ resolved_path = status_gate.resolve_status_file_path(
234
+ None, temporary_directory, ""
235
+ )
236
+ expected_path = temporary_directory.joinpath(
237
+ *ALL_DEFAULT_STATUS_DIRECTORY_PARTS, STATUS_FILE_NAME
238
+ ).resolve()
239
+ assert resolved_path == expected_path
240
+
241
+ def should_scope_default_path_by_run_slug(
242
+ self, temporary_directory: Path
243
+ ) -> None:
244
+ status_gate = load_status_gate_module()
245
+ run_slug = "demo-run"
246
+ resolved_path = status_gate.resolve_status_file_path(
247
+ None, temporary_directory, run_slug
248
+ )
249
+ expected_path = temporary_directory.joinpath(
250
+ *ALL_DEFAULT_STATUS_DIRECTORY_PARTS, run_slug, STATUS_FILE_NAME
251
+ ).resolve()
252
+ assert resolved_path == expected_path
253
+
254
+ def should_set_and_gate_via_main(
255
+ self,
256
+ temporary_directory: Path,
257
+ monkeypatch: pytest.MonkeyPatch,
258
+ ) -> None:
259
+ status_gate = load_status_gate_module()
260
+ status_file_path = temporary_directory / "status.json"
261
+ monkeypatch.setattr(
262
+ sys,
263
+ "argv",
264
+ [
265
+ "status_gate.py",
266
+ "set",
267
+ "--status-file",
268
+ str(status_file_path),
269
+ "--status",
270
+ RUN_STATUS_ACTIVE,
271
+ "--run-slug",
272
+ "demo-run",
273
+ ],
274
+ )
275
+ assert status_gate.main() == EXIT_CODE_SUCCESS
276
+ payload = json.loads(status_file_path.read_text(encoding="utf-8"))
277
+ assert payload[STATUS_FIELD_NAME] == RUN_STATUS_ACTIVE
278
+ assert payload[REARM_PENDING_FIELD_NAME] is False
279
+
280
+ monkeypatch.setattr(
281
+ sys,
282
+ "argv",
283
+ [
284
+ "status_gate.py",
285
+ "should-reschedule",
286
+ "--status-file",
287
+ str(status_file_path),
288
+ ],
289
+ )
290
+ assert status_gate.main() == EXIT_CODE_SUCCESS
291
+
292
+ monkeypatch.setattr(
293
+ sys,
294
+ "argv",
295
+ [
296
+ "status_gate.py",
297
+ "claim-rearm",
298
+ "--status-file",
299
+ str(status_file_path),
300
+ ],
301
+ )
302
+ assert status_gate.main() == EXIT_CODE_SUCCESS
303
+ monkeypatch.setattr(
304
+ sys,
305
+ "argv",
306
+ [
307
+ "status_gate.py",
308
+ "claim-rearm",
309
+ "--status-file",
310
+ str(status_file_path),
311
+ ],
312
+ )
313
+ assert status_gate.main() == EXIT_CODE_STOP
314
+ monkeypatch.setattr(
315
+ sys,
316
+ "argv",
317
+ [
318
+ "status_gate.py",
319
+ "should-reschedule",
320
+ "--status-file",
321
+ str(status_file_path),
322
+ ],
323
+ )
324
+ assert status_gate.main() == EXIT_CODE_STOP
325
+
326
+ monkeypatch.setattr(
327
+ sys,
328
+ "argv",
329
+ [
330
+ "status_gate.py",
331
+ "begin-firing",
332
+ "--status-file",
333
+ str(status_file_path),
334
+ ],
335
+ )
336
+ assert status_gate.main() == EXIT_CODE_SUCCESS
337
+ monkeypatch.setattr(
338
+ sys,
339
+ "argv",
340
+ [
341
+ "status_gate.py",
342
+ "should-reschedule",
343
+ "--status-file",
344
+ str(status_file_path),
345
+ ],
346
+ )
347
+ assert status_gate.main() == EXIT_CODE_SUCCESS
348
+
349
+ monkeypatch.setattr(
350
+ sys,
351
+ "argv",
352
+ [
353
+ "status_gate.py",
354
+ "set",
355
+ "--status-file",
356
+ str(status_file_path),
357
+ "--status",
358
+ RUN_STATUS_DONE,
359
+ ],
360
+ )
361
+ assert status_gate.main() == EXIT_CODE_SUCCESS
362
+ monkeypatch.setattr(
363
+ sys,
364
+ "argv",
365
+ [
366
+ "status_gate.py",
367
+ "should-reschedule",
368
+ "--status-file",
369
+ str(status_file_path),
370
+ ],
371
+ )
372
+ assert status_gate.main() == EXIT_CODE_STOP
373
+
374
+ def should_resolve_status_file_from_environment(
375
+ self, temporary_directory: Path, monkeypatch: pytest.MonkeyPatch
376
+ ) -> None:
377
+ status_gate = load_status_gate_module()
378
+ status_file_path = temporary_directory / "env-status.json"
379
+ monkeypatch.setenv(STATUS_FILE_ENV_VAR, str(status_file_path))
380
+ monkeypatch.setattr(
381
+ sys,
382
+ "argv",
383
+ ["status_gate.py", "set", "--status", RUN_STATUS_ACTIVE],
384
+ )
385
+ assert status_gate.main() == EXIT_CODE_SUCCESS
386
+ assert status_file_path.is_file()
387
+ is_allowed, _reason = status_gate.decide_should_reschedule(status_file_path)
388
+ assert is_allowed is True
389
+
390
+ def should_scope_set_and_should_reschedule_with_matching_run_slug(
391
+ self, temporary_directory: Path, monkeypatch: pytest.MonkeyPatch
392
+ ) -> None:
393
+ status_gate = load_status_gate_module()
394
+ monkeypatch.chdir(temporary_directory)
395
+ monkeypatch.delenv(STATUS_FILE_ENV_VAR, raising=False)
396
+ run_slug = "demo-run"
397
+ monkeypatch.setattr(
398
+ sys,
399
+ "argv",
400
+ [
401
+ "status_gate.py",
402
+ "set",
403
+ "--status",
404
+ RUN_STATUS_ACTIVE,
405
+ "--run-slug",
406
+ run_slug,
407
+ ],
408
+ )
409
+ assert status_gate.main() == EXIT_CODE_SUCCESS
410
+ slug_path = status_gate.resolve_status_file_path(
411
+ None, temporary_directory, run_slug
412
+ )
413
+ default_path = status_gate.resolve_status_file_path(
414
+ None, temporary_directory, ""
415
+ )
416
+ assert slug_path.is_file()
417
+ assert not default_path.is_file()
418
+ monkeypatch.setattr(
419
+ sys,
420
+ "argv",
421
+ [
422
+ "status_gate.py",
423
+ "should-reschedule",
424
+ "--run-slug",
425
+ run_slug,
426
+ ],
427
+ )
428
+ assert status_gate.main() == EXIT_CODE_SUCCESS
429
+ monkeypatch.setattr(
430
+ sys,
431
+ "argv",
432
+ ["status_gate.py", "should-reschedule"],
433
+ )
434
+ assert status_gate.main() == EXIT_CODE_STOP
435
+
436
+
437
+ @pytest.fixture
438
+ def temporary_directory(tmp_path: Path) -> Path:
439
+ return tmp_path
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: orchestrator-refresh
3
3
  description: >-
4
- Fired by the /orchestrator loop reminder about every 45 minutes to
5
- re-assert the advisor discipline mid-run: orchestrate, route hard decisions
6
- to the shared advisor (ENDORSE / CORRECTION / PLAN / STOP — SendMessage on
7
- Claude, Claude CLI chain on a third-party host), reuse warm agents. Triggers:
8
- '/orchestrator-refresh'.
4
+ Re-assert orchestrator discipline on a one-shot delayed wake: ledger
5
+ reconcile, advisor routing, warm executor reuse, single-pending re-arm
6
+ via status_gate. Terminates when the gate says stop. Triggers:
7
+ '/orchestrator-refresh', orchestrator-refresh, refresh the orchestrator
8
+ loop, re-arm orchestrator.
9
9
  ---
10
10
 
11
11
  # Orchestrator Refresh
@@ -15,35 +15,129 @@ Detect the host profile first (see Host profiles in
15
15
  Re-assert the discipline for that host only — do not invent an Agent-tool
16
16
  Claude `session-advisor` spawn on a third-party host.
17
17
 
18
- 1. **Reconcile the task ledger first.** Call `TaskList` before anything else
19
- this firing. The ledger is stale when any of these holds: a running or
20
- finished executor has no `in_progress` task naming it as owner; a finished
21
- executor's task is still open (or was closed without its result merged);
22
- the next phase you will dispatch has no pending task; a `blockedBy` link
23
- contradicts the actual run order. Fix every mismatch with TaskCreate /
24
- TaskUpdate in this same firing — never defer reconciliation to "when the
25
- agent reports".
26
- 2. **You are the orchestrator.** Orchestrate and hold the user conversation;
27
- spawn executor subagents to do all the work — every code edit and build or
28
- test run.
18
+ ## 0. status_gate first (deterministic)
19
+
20
+ Script home (prefer install path, else package checkout):
21
+
22
+ - `%USERPROFILE%/.claude/skills/orchestrator/scripts/status_gate.py`
23
+ - `~/.claude/skills/orchestrator/scripts/status_gate.py`
24
+ - `skills/orchestrator/scripts/status_gate.py`
25
+
26
+ Pass the same `--run-slug` used at activate, if any.
27
+
28
+ ### 0a. begin-firing (consume prior re-arm)
29
+
30
+ ```
31
+ python <status_gate.py> begin-firing [--run-slug SLUG]
32
+ ```
33
+
34
+ | Exit | Action |
35
+ |---|---|
36
+ | **1** | End the refresh. Cancel matching host schedules for `/orchestrator-refresh` if the host allows. Report inactive/done. **Do not** re-arm. Do not spawn. Any work already in flight keeps running. |
37
+ | **0** | Latch cleared. Continue with steps 1–6. |
38
+
39
+ ### 0b. Done after ledger (step 1)
40
+
41
+ After ledger reconcile: if every task is completed/cancelled and no
42
+ executor is running:
43
+
44
+ ```
45
+ python <status_gate.py> set --status done [--run-slug SLUG]
46
+ ```
47
+
48
+ Cancel matching host schedules; stop without re-arming.
49
+
50
+ ## The refresh never interrupts the run
51
+
52
+ A refresh firing reinforces discipline alongside work already in flight.
53
+ It never pauses, cancels, or waits on a running executor. Reconcile the
54
+ ledger, re-assert the routing, re-arm once, and hand control straight back
55
+ to the work in progress.
56
+
57
+ Inside the re-arm protocol (step 6), every "stop" ends the *re-arm* and
58
+ nothing else. A `should-reschedule` exit 1 means no schedule is created this
59
+ firing, and a `claim-rearm` exit 1 means the schedule just created is
60
+ cancelled; either way the session keeps orchestrating in the same turn.
61
+
62
+ Two stops end the whole firing, and both leave running executors alone:
63
+ `begin-firing` exit 1 (step 0a) and the done branch (step 0b). Each means the
64
+ run is finished, not active, or has no readable status file, so the refresh
65
+ reports and adds nothing further. A fail-closed advisor bind (step 3) stops
66
+ advisor consultation alone; the firing still reconciles the ledger, re-arms
67
+ once, and reports the unreachable advisor.
68
+
69
+ ## Discipline steps
70
+
71
+ 1. **Reconcile the task ledger first.** Call `TaskList` after the gate.
72
+ The ledger is stale when any of these holds: a running or finished
73
+ executor has no `in_progress` task naming it as owner; a finished
74
+ executor's task is still open (or was closed without its result
75
+ merged); the next phase you will dispatch has no pending task; a
76
+ `blockedBy` link contradicts the actual run order. Fix every mismatch
77
+ with TaskCreate / TaskUpdate in this same firing — never defer.
78
+ 2. **You are the orchestrator.** Orchestrate and hold the user
79
+ conversation; spawn executor subagents for every code edit and build
80
+ or test run.
81
+ - **Worker model.** Every coding spawn sets `model: sonnet` on a
82
+ Claude host, or the resolver-printed sonnet-equivalent id on a
83
+ third-party host, and fails closed — see Workflow Agent Routing in
84
+ [`skills/orchestrator/SKILL.md`](../orchestrator/SKILL.md#workflow-agent-routing).
85
+ - **Focused tickets.** One mechanical done-check per ticket; resume a
86
+ warm agent with a thin next-slice ticket rather than a fresh cold
87
+ spawn, and keep thick context in the assignment file.
29
88
  3. **Hard decisions go to the shared advisor.**
30
89
  - **Claude host:** executors consult the warm `session-advisor` via
31
- `SendMessage` and receive one of four signals ENDORSE, CORRECTION, PLAN,
32
- or STOP. The orchestrating session routes its own hard decisions the same
33
- way and keeps its tool use to orchestration and light verification reads.
34
- - **Third-party host:** the advisor is a max-tier Claude CLI bind owned by this
35
- session (`claude_chain_runner.py`, Fable high then Opus max). Do **not**
36
- spawn `session-advisor` via Agent and do **not** tell executors to
37
- SendMessage a separate advisor agent. Executors report blockers to this
38
- session; consult the Claude CLI advisor and relay ENDORSE / CORRECTION /
39
- PLAN / STOP. When the CLI bind is unreachable, fail closed and report to
40
- the user do not answer the four signals as this third-party session.
41
- 4. **Resume before you spawn.** `SendMessage` an existing *executor* agent by
42
- name or `agentId` to reuse its warm context; prefer that over a cold spawn.
43
- (On a third-party host this is executor reuse only advisor re-bind stays on the CLI
44
- chain path in the shared protocol.)
45
- 5. **Fresh spawn only for a genuine task switch.** No tool compacts or clears a
46
- subagent's context, so a clean context comes from a fresh spawn — never tell
47
- an agent to compact.
48
- 5. **Re-schedule the next refresh** (about 1200 seconds out) when the loop
49
- mechanism needs each firing to queue the following one.
90
+ `SendMessage` (ENDORSE / CORRECTION / PLAN / STOP). This session
91
+ routes the same way; keep tool use to orchestration and light
92
+ verification reads.
93
+ - **Third-party host:** advisor is a max-tier Claude CLI bind owned
94
+ by this session (`claude_chain_runner.py`, Fable high then Opus
95
+ max). Do **not** spawn `session-advisor` via Agent. Executors
96
+ report blockers here; consult the CLI advisor and relay signals.
97
+ If the CLI bind is unreachable, fail closed do not answer the
98
+ four signals as this third-party session.
99
+ 4. **Resume before you spawn.** `SendMessage` an existing *executor* by
100
+ name or `agentId` before a cold spawn. (Third-party: executor reuse
101
+ only advisor stays on the CLI chain.)
102
+ 5. **Fresh spawn only for a genuine task switch.** Never tell an agent
103
+ to compact for a clean context.
104
+ 6. **Single-pending re-arm only.** Same protocol as the orchestrator
105
+ skill (host-agnostic; **create then claim**):
106
+
107
+ 1. Cancel matching schedules only when the host can list/cancel by
108
+ prompt (`/orchestrator-refresh` + `--run-slug` if used). On Claude,
109
+ skip selective cancel — the latch is the sole stacking enforcement.
110
+ 2. `python <status_gate.py> should-reschedule [--run-slug SLUG]`
111
+ - Exit **1** → stop; do not schedule.
112
+ - Exit **0** → continue.
113
+ 3. Create **exactly one** non-recurring delayed wake (~1200–2700s)
114
+ with prompt `/orchestrator-refresh` (plus `--run-slug` when used).
115
+ Host one-shot tool only (on Claude: `ScheduleWakeup`). Never
116
+ recurring / never cadence / never a second create this firing.
117
+ 4. `python <status_gate.py> claim-rearm [--run-slug SLUG]` right
118
+ after a successful create. Exit 1 → cancel that schedule and stop.
119
+ 5. If create fails: do not claim; stop or retry once from cancel.
120
+
121
+ ## Gotchas
122
+
123
+ - **Stacking loops.** A second schedule create while one is already
124
+ queued multiplies firings. Cancel → `should-reschedule` → one create
125
+ → `claim-rearm`. Never claim before create on Claude (PreToolUse
126
+ denies `ScheduleWakeup` when pending).
127
+ - **Skipping `begin-firing`.** Prior `rearm_pending` stays set; re-arm
128
+ stays denied. Always run step 0a first.
129
+ - **Create without claim.** Skip claim after create and a second create
130
+ can stack. Claim immediately after success.
131
+
132
+ ## File Index
133
+
134
+ | File | Purpose |
135
+ |---|---|
136
+ | `SKILL.md` | Refresh firing steps; points at orchestrator `status_gate.py`. |
137
+
138
+ ## Folder Map
139
+
140
+ - `SKILL.md` — this skill (thin); gate implementation lives under
141
+ `skills/orchestrator/scripts/`.
142
+ - Advisor policy:
143
+ [`_shared/advisor/advisor-protocol.md`](../../_shared/advisor/advisor-protocol.md).