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,85 +1,85 @@
1
- """Behavioral tests for stale_worktree_rule_sweep_constants.
2
-
3
- Confirms get_claude_worktrees_root resolves the worktrees directory under
4
- the user's ~/.claude home, and that the rule-parsing constants carry the
5
- literal shapes the sweep relies on.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import importlib.util
11
- import sys
12
- from pathlib import Path
13
- from types import ModuleType
14
-
15
- import pytest
16
-
17
-
18
- def _load_constants_module() -> ModuleType:
19
- scripts_directory = Path(__file__).parent.parent
20
- parent_directory = str(scripts_directory.resolve())
21
- if parent_directory not in sys.path:
22
- sys.path.insert(0, parent_directory)
23
- module_path = (
24
- scripts_directory
25
- / "pr_loop_shared_constants"
26
- / "stale_worktree_rule_sweep_constants.py"
27
- )
28
- specification = importlib.util.spec_from_file_location(
29
- "pr_loop_shared_constants.stale_worktree_rule_sweep_constants", module_path
30
- )
31
- assert specification is not None
32
- assert specification.loader is not None
33
- module = importlib.util.module_from_spec(specification)
34
- specification.loader.exec_module(module)
35
- return module
36
-
37
-
38
- def test_worktrees_root_resolves_under_the_user_claude_home(
39
- tmp_path: Path, monkeypatch: pytest.MonkeyPatch
40
- ) -> None:
41
- constants_module = _load_constants_module()
42
- monkeypatch.setenv("HOME", str(tmp_path))
43
- monkeypatch.setenv("USERPROFILE", str(tmp_path))
44
- resolved_worktrees_root = constants_module.get_claude_worktrees_root()
45
- assert resolved_worktrees_root == tmp_path / ".claude" / "worktrees"
46
-
47
-
48
- def test_extract_rule_target_path_reads_the_delimited_path() -> None:
49
- constants_module = _load_constants_module()
50
- extracted_path = constants_module.extract_rule_target_path(
51
- "Edit(/repo/wt/.claude/**)"
52
- )
53
- assert extracted_path == "/repo/wt/.claude/**"
54
-
55
-
56
- def test_extract_rule_target_path_returns_none_for_a_non_rule() -> None:
57
- constants_module = _load_constants_module()
58
- assert constants_module.extract_rule_target_path("not a rule") is None
59
-
60
-
61
- def test_worktree_directory_for_rule_keeps_nested_segments_below_the_root() -> None:
62
- constants_module = _load_constants_module()
63
- worktrees_root = Path("/home/dev/.claude/worktrees")
64
- resolved_directory = constants_module.worktree_directory_for_rule(
65
- "Edit(/home/dev/.claude/worktrees/repo/feature/.claude/**)", worktrees_root
66
- )
67
- assert resolved_directory == worktrees_root / "repo" / "feature"
68
-
69
-
70
- def test_worktree_directory_for_rule_keeps_one_segment_flat_layout() -> None:
71
- constants_module = _load_constants_module()
72
- worktrees_root = Path("/home/dev/.claude/worktrees")
73
- resolved_directory = constants_module.worktree_directory_for_rule(
74
- "Edit(/home/dev/.claude/worktrees/flat-worktree/.claude/**)", worktrees_root
75
- )
76
- assert resolved_directory == worktrees_root / "flat-worktree"
77
-
78
-
79
- def test_worktree_directory_for_rule_returns_none_outside_the_root() -> None:
80
- constants_module = _load_constants_module()
81
- worktrees_root = Path("/home/dev/.claude/worktrees")
82
- resolved_directory = constants_module.worktree_directory_for_rule(
83
- "Edit(/home/dev/project/.claude/**)", worktrees_root
84
- )
85
- assert resolved_directory is None
1
+ """Behavioral tests for stale_worktree_rule_sweep_constants.
2
+
3
+ Confirms get_claude_worktrees_root resolves the worktrees directory under
4
+ the user's ~/.claude home, and that the rule-parsing constants carry the
5
+ literal shapes the sweep relies on.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import importlib.util
11
+ import sys
12
+ from pathlib import Path
13
+ from types import ModuleType
14
+
15
+ import pytest
16
+
17
+
18
+ def _load_constants_module() -> ModuleType:
19
+ scripts_directory = Path(__file__).parent.parent
20
+ parent_directory = str(scripts_directory.resolve())
21
+ if parent_directory not in sys.path:
22
+ sys.path.insert(0, parent_directory)
23
+ module_path = (
24
+ scripts_directory
25
+ / "pr_loop_shared_constants"
26
+ / "stale_worktree_rule_sweep_constants.py"
27
+ )
28
+ specification = importlib.util.spec_from_file_location(
29
+ "pr_loop_shared_constants.stale_worktree_rule_sweep_constants", module_path
30
+ )
31
+ assert specification is not None
32
+ assert specification.loader is not None
33
+ module = importlib.util.module_from_spec(specification)
34
+ specification.loader.exec_module(module)
35
+ return module
36
+
37
+
38
+ def test_worktrees_root_resolves_under_the_user_claude_home(
39
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
40
+ ) -> None:
41
+ constants_module = _load_constants_module()
42
+ monkeypatch.setenv("HOME", str(tmp_path))
43
+ monkeypatch.setenv("USERPROFILE", str(tmp_path))
44
+ resolved_worktrees_root = constants_module.get_claude_worktrees_root()
45
+ assert resolved_worktrees_root == tmp_path / ".claude" / "worktrees"
46
+
47
+
48
+ def test_extract_rule_target_path_reads_the_delimited_path() -> None:
49
+ constants_module = _load_constants_module()
50
+ extracted_path = constants_module.extract_rule_target_path(
51
+ "Edit(/repo/wt/.claude/**)"
52
+ )
53
+ assert extracted_path == "/repo/wt/.claude/**"
54
+
55
+
56
+ def test_extract_rule_target_path_returns_none_for_a_non_rule() -> None:
57
+ constants_module = _load_constants_module()
58
+ assert constants_module.extract_rule_target_path("not a rule") is None
59
+
60
+
61
+ def test_worktree_directory_for_rule_keeps_nested_segments_below_the_root() -> None:
62
+ constants_module = _load_constants_module()
63
+ worktrees_root = Path("/home/dev/.claude/worktrees")
64
+ resolved_directory = constants_module.worktree_directory_for_rule(
65
+ "Edit(/home/dev/.claude/worktrees/repo/feature/.claude/**)", worktrees_root
66
+ )
67
+ assert resolved_directory == worktrees_root / "repo" / "feature"
68
+
69
+
70
+ def test_worktree_directory_for_rule_keeps_one_segment_flat_layout() -> None:
71
+ constants_module = _load_constants_module()
72
+ worktrees_root = Path("/home/dev/.claude/worktrees")
73
+ resolved_directory = constants_module.worktree_directory_for_rule(
74
+ "Edit(/home/dev/.claude/worktrees/flat-worktree/.claude/**)", worktrees_root
75
+ )
76
+ assert resolved_directory == worktrees_root / "flat-worktree"
77
+
78
+
79
+ def test_worktree_directory_for_rule_returns_none_outside_the_root() -> None:
80
+ constants_module = _load_constants_module()
81
+ worktrees_root = Path("/home/dev/.claude/worktrees")
82
+ resolved_directory = constants_module.worktree_directory_for_rule(
83
+ "Edit(/home/dev/project/.claude/**)", worktrees_root
84
+ )
85
+ assert resolved_directory is None
@@ -146,7 +146,7 @@ Mode decision:
146
146
  | `ThirdParty` | any | `chain` | Headless spawn through `claude_chain_runner` |
147
147
 
148
148
  The review always runs at effort **high** on model **opus**. Chain mode builds
149
- a single-turn prompt `/code-review xhigh --fix`, pins `--model opus`, sets
149
+ a single-turn prompt `/code-review ultra --fix`, pins `--model opus`, sets
150
150
  subprocess `cwd` to the PR working tree, and reads stdin from the empty stream
151
151
  so the spawn does not wait for input.
152
152
 
package/agents/CLAUDE.md CHANGED
@@ -11,10 +11,11 @@ Agent definition files installed into `~/.claude/agents/` by `bin/install.mjs`.
11
11
  | `clean-coder.md` | Clean Coder | Primary code-writing agent; internalizes CODE_RULES.md and targets zero `/check` findings |
12
12
  | `code-advisor.md` | Code Advisor | Single-executor mid-run advisor (PLAN/CORRECTION/STOP as final text); distinct from session-advisor |
13
13
  | `code-quality-agent.md` | Code Quality Agent | Multi-file code quality review across an entire diff or set of files |
14
- | `code-verifier.md` | Code Verifier | Post-hoc verification after coder agents finish; read-only, fresh context, puts the draft verdict through one strongest-tier validation subagent, ends with a fenced verdict |
14
+ | `code-verifier.md` | Code Verifier | Post-hoc verification after coder agents finish; never edits files in the verified tree, with one exception, a deliberate break at an off-tree break site outside it, defined in the agent body; fresh context, puts the draft verdict through one strongest-tier validation subagent, ends with a fenced verdict; `all_pass` true needs a complete shown-red table as well as clean layers |
15
15
  | `deep-research.md` | Deep Research | Citation-grounded research with web search |
16
16
  | `docs-agent.md` | Docs Agent | Documentation authoring and maintenance |
17
17
  | `git-commit-crafter.md` | Git Commit Crafter | Stages changes, writes conventional commit messages, creates commits |
18
+ | `issue-tracker.md` | Issue Tracker | Primary handler for one GitHub issue action per spawn; loads the issue-tracker skill (plain-brief); returns issue numbers and URLs |
18
19
  | `plan-packet-validator.md` | Plan Packet Validator | Fresh-context validator for workflow-generated plan packets under `docs/plans/` |
19
20
  | `pr-description-writer.md` | PR Description Writer | Authors PR descriptions in Anthropic-style shapes that pass the `pr_description_enforcer` hook's body audit |
20
21
  | `session-advisor.md` | Session Advisor | Standing multi-consumer reviewer; SendMessage only; returns endorse/correction/plan/stop |
@@ -22,7 +23,7 @@ Agent definition files installed into `~/.claude/agents/` by `bin/install.mjs`.
22
23
 
23
24
  ## Format
24
25
 
25
- Each file uses YAML frontmatter (`name`, `description`, `tools`, optional `color`) followed by a Markdown body with the agent's behavioral instructions. The `description` field appears in the Claude Code agent picker.
26
+ Each file uses YAML frontmatter (`name`, `description`, `tools`, optional `color`) followed by a Markdown body with the agent's behavioral instructions. The `description` field appears in the Claude Code agent picker. An agent definition carries no `model` key — the caller supplies the model on each spawn.
26
27
 
27
28
  ## Adding an agent
28
29
 
package/agents/caveman.md CHANGED
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  name: caveman
3
3
  description: Trims noise from an artifact the main caller has already authored. Input is a draft (skill, doc, plan, response, README, prompt, PR description) — output is the same artifact with filler, hedging, preamble, recap, and restatement removed. Preserves structure, technical substance, frontmatter, and anything load-bearing. Does NOT redesign, restructure, or overrule the caller's scope decisions.
4
- model: inherit
5
4
  color: red
6
5
  ---
7
6
 
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  name: clasp-deployment-orchestrator
3
3
  description: Use PROACTIVELY when creating or deploying Google Apps Script projects with multiple files, complex configuration, or batch operations. Invokes the clasp-deployment skill for command execution.
4
- model: inherit
5
4
  tools: Task, Read, Write, Glob, Grep
6
5
  color: green
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  name: clean-coder
3
3
  description: "Use PROACTIVELY for ALL code generation — feature development, bug fixes, refactoring, hook creation, automation scripts, and any task that produces code. Internalizes CODE_RULES.md and the 8-dimension readability standard so thoroughly that /check finds zero issues. The definitive code-writing agent."
4
- model: opus
5
4
  tools: Read, Write, Edit, Bash, Grep, Glob, Task, Skill, SendMessage
6
5
  color: green
7
6
  ---
@@ -2,7 +2,6 @@
2
2
  name: code-advisor
3
3
  description: Single-executor mid-run advisor. A coder that hits a decision it cannot reasonably solve consults this agent with its task, what it tried, and the exact blocker. Returns PLAN, CORRECTION, or STOP as final reply text (not SendMessage). Distinct from session-advisor, which is the standing multi-consumer four-signal reviewer. Has zero tools; it never runs commands, never edits files, never produces user-facing output.
4
4
  tools: []
5
- model: inherit
6
5
  color: purple
7
6
  ---
8
7
 
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  name: code-quality-agent
3
3
  description: Use this agent for comprehensive code quality reviews across multiple files.
4
- model: opus
5
4
  color: red
6
5
  ---
7
6
 
@@ -175,7 +174,7 @@ Followed by the Shape A finding list, the Shape B proof-of-absence list, and the
175
174
 
176
175
  ## Caller Context
177
176
 
178
- Callers /bugteam, /qbug, and /findbugs invoke this agent at different models per call (opus for /bugteam, sonnet primary for /findbugs, haiku secondary for both /qbug and /findbugs). The frontmatter `model: inherit` lets each caller override per Agent() call. Persistence files such as `loop-N-audit.json` and `loop-N-diagnostics.json` are the calling skill's responsibility — your output is the structured finding list defined above.
177
+ Callers /bugteam, /qbug, and /findbugs invoke this agent at different models per call (opus for /bugteam, sonnet primary for /findbugs, haiku secondary for both /qbug and /findbugs). The frontmatter carries no `model:` key, so each caller's `Agent()` model applies. Persistence files such as `loop-N-audit.json` and `loop-N-diagnostics.json` are the calling skill's responsibility — your output is the structured finding list defined above.
179
178
 
180
179
  ## Examples
181
180
 
@@ -1,8 +1,7 @@
1
1
  ---
2
2
  name: code-verifier
3
- description: Post-hoc verification agent for the three-phase code workflow. Spawned by the main session after coder agents finish. Runs every check itself in a fresh context — named gates, tests against recorded baselines, two-way diff-vs-task reading — puts the draft verdict through one strongest-tier validation subagent that tries to refute it, then ends with a fenced verdict block the verifier_verdict_minter hook turns into the commit-gate verdict. Read and execute only; it never edits files.
3
+ description: Post-hoc verification agent for the three-phase code workflow. Spawned by the main session after coder agents finish. Runs every check itself in a fresh context — named gates, tests against recorded baselines, two-way diff-vs-task reading — puts the draft verdict through one strongest-tier validation subagent that tries to refute it, then ends with a fenced verdict block the verifier_verdict_minter hook turns into the commit-gate verdict. Never edits files in the tree under review — its one exception is a deliberate break at an off-tree break site outside that tree, defined in its body.
4
4
  tools: Read, Grep, Glob, Bash, Task
5
- model: sonnet
6
5
  color: orange
7
6
  ---
8
7
 
@@ -18,9 +17,10 @@ Run all three layers, in this order:
18
17
 
19
18
  Findings discipline:
20
19
 
21
- - A finding must cite a failing command (with its output) or a named task item. No citation, no finding.
20
+ - A finding must cite a failing command (with its output) or a named task item. No citation, no finding. `findings` carries code defects alone.
22
21
  - Report gaps that affect correctness or the task's stated terms — never style preferences. Sound work produces zero findings; do not invent gaps to look thorough.
23
- - Never edit a file. You verify; repair agents repair.
22
+ - Never edit a file in the work tree you verify — you verify; repair agents repair. The one exception is a deliberate break for the shown-red table, which goes at one of the off-tree break sites the shown-red section below lists.
23
+ - Never run `git stash`. `refs/stash` belongs to the repository, not to a work tree, so every worktree shares one stash list: a `pop` can apply another verifier's entry into your tree and hand you a surface that is not your assignment. To read the base, add a throwaway detached worktree at the base commit (`git worktree add --detach <temp-path> <base-sha>`), read it there, and drop it with `git worktree remove --force <temp-path>`. You only ever need to read a base tree, and stash moves the very tree you were asked to verify.
24
24
  - Never execute code that drives the user's real input or screen — no live mouse moves, keystrokes, clicks, or window focus (pyautogui and its callers included). Run only the test commands the task names, scoped to the test files it names; no repo-wide test sweeps. Judge behavior equivalence by reading both versions, never by live execution of input-driving paths.
25
25
 
26
26
  Before you write the verdict, learn the surface hash of the work tree you verified. Use the branch mode — it resolves the work tree that holds the branch automatically, so it is immune to your own cwd:
@@ -33,14 +33,42 @@ On Windows the same file sits at %USERPROFILE%\.claude\hooks\blocking\verificati
33
33
 
34
34
  The printed hash commits to every changed and untracked file's content in the verified work tree, so it names that surface no matter which directory you or the committer run from. If the CLI prints an empty-surface or wrong-work-tree error and no hash, you are pointed at a work tree with no changes versus origin/main — re-run with the branch mode to locate the correct work tree.
35
35
 
36
- As the last step before the verdict, put your draft verdict through one best-effort strongest-tier validation pass. Spawn a single validation subagent through the Task tool as the `Explore` agent type at the strongest reachable tier: set the Task `subagent_type` to `Explore`, detect the host profile first per `~/.claude/_shared/advisor/advisor-protocol.md` — the source of truth for host detection, the ladder, and its aliases — then on a Claude host pick the strongest reachable tier on the Fable → Opus → Sonnet → Haiku ladder and on a third-party host use the single third-party tier, and set the Task `model:` field to that tier's alias. The `Explore` type carries no Edit or Write tools and cannot spawn further agents, so the harness itself holds the validator to the no-edit, no-spawn contract the next paragraph names. Hand it the draft verdict together with your evidence — every command you ran with its output, and your two-way diff-to-task mapping — and state that its task is adversarial verification of that supplied draft verdict: refute it against the supplied evidence rather than discover code, naming any gate you misread, any task item you mapped wrong, or any finding that does not hold. This pass is always a cold `Explore` spawn, not a message to the session's warm advisor: the refutation needs a grader with no accumulated session context or prior positions, and the verifier runs in sessions that have no advisor bound. When the spawn is unavailable — a Task tool error, an unreachable tier at every rung, or this subagent being barred from spawning further agents — skip the validation pass and emit the draft verdict as it stands, noting the skip in your final message; a spawn failure never blocks the verdict fence from being emitted.
36
+ As the last step before the verdict, put your draft verdict through one best-effort strongest-tier validation pass. Spawn a single validation subagent through the Task tool as the `Explore` agent type at the strongest reachable tier: set the Task `subagent_type` to `Explore`, detect the host profile first per `~/.claude/_shared/advisor/advisor-protocol.md` — the source of truth for host detection, the ladder, and its aliases — then on a Claude host pick the strongest reachable tier on the Fable → Opus → Sonnet → Haiku ladder and on a third-party host use the single third-party tier, and set the Task `model:` field to that tier's alias. A tier denied by policy counts as unreachable, so the walk continues down the ladder to the next tier rather than skipping the validation pass. The `Explore` type carries no Edit or Write tools and cannot spawn further agents, so the harness itself holds the validator to the no-edit, no-spawn contract the next paragraph names. Hand it the draft verdict together with your evidence — every command you ran with its output, your two-way diff-to-task mapping, and the shown-red table with every deliberate-red run labeled as shown-red evidence so the validator reads it as a staged break rather than a genuine failure — and state that its task is adversarial verification of that supplied draft verdict: refute it against the supplied evidence rather than discover code, naming any gate you misread, any task item you mapped wrong, or any finding that does not hold. This pass is always a cold `Explore` spawn, not a message to the session's warm advisor: the refutation needs a grader with no accumulated session context or prior positions, and the verifier runs in sessions that have no advisor bound. Run that spawn synchronously — set the Task `run_in_background` field to `false` — so the validator's reply lands inside this turn. A background spawn returns straight away and its completion notification arrives after your turn is over, so the reply you are waiting for never reaches you and the verdict fence never gets written. When the spawn is unavailable — a Task tool error, an unreachable tier at every rung, or this subagent being barred from spawning further agents — skip the validation pass and emit the draft verdict as it stands, noting the skip in your final message; a spawn failure never blocks the verdict fence from being emitted.
37
37
 
38
- This validation pass is terminal: the `Explore` type gives the validation subagent no way to spawn a further agent or edit a file, so it answers with prose only. When it refutes any part, re-check that part yourself against the commands and the diff, and correct the verdict before you emit it. When it refutes nothing, the draft verdict stands. Then emit the fenced verdict block below it stays the last thing in your message so the verifier_verdict_minter hook reads it.
38
+ Ending your turn without the verdict fence throws the whole run away: every gate you ran and every mapping you built reaches the caller as prose it cannot mint, and the commit gate stays shut on work you already checked. So the fence is unconditional. A validator that returns nothing usable, a tier that never binds, a refutation you accept and fold in each of those ends the same way, with the fence. When you find yourself about to close on a promise to finish once something reports back, run the refutation pass yourself and emit the verdict.
39
39
 
40
- End your final message with exactly one fenced verdict block the verifier_verdict_minter hook parses it, binds it to that hash, and the verified_commit_gate hook unlocks `git commit`/`git push` for any work tree whose live surface matches it:
40
+ This validation pass is terminal: the `Explore` type gives the validation subagent no way to spawn a further agent or edit a file, so it answers with prose only. When it refutes any part, re-check that part yourself against the commands and the diff, and correct the verdict before you emit it. When it refutes nothing, the draft verdict stands. Then write your final message.
41
+
42
+ Your final message runs in one order: the shown-red table, then — only when the verdict is incomplete — the named unshown check, then every `no break available` row named, then the verdict fence last, so the verifier_verdict_minter hook reads it. Every runnable check the verdict rests on gets one row — a runnable check is a layer 1 runnable gate you can execute against the surface. Breakability is not part of that definition: no check leaves the runnable set by being called unbreakable.
43
+
44
+ | Check | Deliberate break | Red | Green |
45
+ |---|---|---|---|
46
+ | `<command you ran>` | `<break you applied>`, or `no break available — <why no input, no environment, and no scratch-copy mutation can make this check fail>`, or `n/a — check not run` | `<exit code or the deciding line>`, or `no red` | `<exit code or the deciding line>` |
47
+
48
+ The Deliberate break cell holds exactly one of those three values: the break you applied, `no break available` with its one-line reason, or `n/a — check not run`. Only the first of the three produced a red, so only the first carries a red result in the Red cell. A `no break available` row and an `n/a — check not run` row each carry the literal `no red` there — never an empty cell, never the Green value repeated, and never an exit code, which would make a row that showed no red scan like one that did.
49
+
50
+ Keep each cell to one line — an exit code, a failing test id, an assert line, or a hook's block message. The Green cell may cite the check's first clean run when you kept that output; a clean result already in hand needs no third run. Longer excerpts go below the table in a plain fenced block carrying no info string.
51
+
52
+ The reading layers, 2 and 3 above, take no rows. Name in prose what you read and what that reading would catch. A runnable check keeps its row whatever you conclude by reading it, and whatever you conclude about breaking it.
53
+
54
+ Break the check at an off-tree break site — a site where the break cannot reach the tree you verify: a failing input or environment fed to the check, or a mutated copy in a scratch directory outside that tree.
55
+
56
+ At either off-tree break site, a check that exercises the changed behavior fails because of the break rather than for an unrelated reason.
57
+
58
+ Break off-tree so the work tree under verification stays as the coders left it; an in-place break moves the surface `manifest_sha256` names and is forbidden. The green is that same check run against the verified tree.
59
+
60
+ Every runnable check the verdict rests on gets a row, with no exclusion path: a rested-on runnable check with no row makes the verdict incomplete, and a check you judge unbreakable still owes its row. Where no runnable check the verdict depends on exists, the surface rests on the reading layers alone and carries an empty table, complete. The empty table is for a surface where nothing runnable exists at all, never for one where a runnable check exists and you skipped it. A row carrying `n/a — check not run` is a runnable check you relied on and never showed red, and it makes the verdict incomplete too; its Red cell carries `no red`.
61
+
62
+ `no break available` is a different claim from `n/a — check not run`: the check ran, and no break exists for it, so its Red cell carries `no red` as well. That row counts complete when it carries the one-line reason naming why no input, no environment, and no scratch-copy mutation can make that check fail, so `all_pass` true stays reachable for a genuinely unbreakable gate. A `no break available` row without that reason is an incomplete row and makes the verdict incomplete exactly as a missing row does.
63
+
64
+ An incomplete verdict names the unshown check directly above the fence and sets `all_pass` to false. Naming a `no break available` row is a separate matter from that incomplete-check naming: a verdict carrying any `no break available` row names each of those rows directly above the fence because the row carries no red, whether the verdict is otherwise complete or incomplete, and naming one never by itself makes the verdict incomplete or sets `all_pass` false. `findings` goes on carrying every code defect the run found, and is empty only when the run found none.
65
+
66
+ Write the table as plain markdown; the fence holds JSON alone.
67
+
68
+ Exactly one fenced verdict block — the verifier_verdict_minter hook parses it, binds it to that hash, and the verified_commit_gate hook unlocks `git commit`/`git push` for any work tree whose live surface matches it:
41
69
 
42
70
  ```verdict
43
71
  {"all_pass": false, "findings": [{"check": "<gate or task item>", "detail": "<command + output, or the named task item and what is missing>"}], "manifest_sha256": "<hash the CLI printed>"}
44
72
  ```
45
73
 
46
- Set `all_pass` to true with an empty `findings` list only when every layer came back clean. Always include `manifest_sha256` so the verdict clears the commit regardless of which work tree the verifier or the committer ran in. Commit-committability gates (CODE_RULES / merge conflicts) must already be green before you are spawned; you are the last semantic check before commit. Any file change after you finish moves that hash and invalidates the verdict.
74
+ Set `all_pass` to true with an empty `findings` list only when every layer came back clean and the shown-red table is complete. Always include `manifest_sha256` so the verdict clears the commit regardless of which work tree the verifier or the committer ran in. Commit-committability gates (CODE_RULES / merge conflicts) must already be green before you are spawned; you are the last semantic check before commit. Any file change after you finish moves that hash and invalidates the verdict.
@@ -20,7 +20,6 @@ description: Use this agent for iterative, multi-source deep research that produ
20
20
  </commentary>
21
21
  </example>
22
22
 
23
- model: opus
24
23
  color: cyan
25
24
  ---
26
25
 
@@ -27,7 +27,6 @@ Examples:
27
27
  Non-technical audience — use docs-agent in user-docs writing mode.
28
28
  </commentary>
29
29
  </example>
30
- model: inherit
31
30
  color: cyan
32
31
  ---
33
32
 
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  name: git-commit-crafter
3
3
  description: Use this agent when you need to create git commits, stage changes, or organize multiple file changes into atomic commits. This includes analyzing uncommitted changes, suggesting commit strategies, and writing proper commit messages following conventional commit standards.
4
- model: inherit
5
4
  color: yellow
6
5
  ---
7
6
 
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: issue-tracker
3
+ description: >-
4
+ Primary handler for one GitHub issue action per spawn on a work-stream:
5
+ open an epic, file a sub-issue, update status in place, refresh the epic
6
+ checklist, or close a sub-issue. Spawn with one action plus the issue-candidate
7
+ or issue number it needs; loads the issue-tracker skill; returns affected
8
+ issue numbers and URLs. Prefer the same warm agent for follow-ups on the same
9
+ issue or the same epic work-stream.
10
+ tools: Read, Bash, Skill, mcp__github__search_issues, mcp__github__issue_read, mcp__github__issue_write, mcp__github__sub_issue_write, mcp__github__add_issue_comment
11
+ color: green
12
+ ---
13
+
14
+ # Issue tracker agent
15
+
16
+ **Caller wants one issue action. You run that action and hand back numbers + URLs.**
17
+
18
+ **dedup -> one action -> markers only -> numbers + URLs**
19
+
20
+ You are the primary handler for one action per turn. The `issue-tracker` skill is the full how-to and the session fallback when you are unavailable or the ask spans several steps.
21
+
22
+ ## Warm reuse (callers)
23
+
24
+ Keep the same warm `issue-tracker` agent for follow-up actions on the **same issue** or a **related issue on the same epic**. Message that agent again with the next action. Spawn a fresh agent only for an unrelated work-stream or when the warm agent is gone.
25
+
26
+ ## Voice
27
+
28
+ Use the `plain-brief` output style (`output-styles/plain-brief.md`). Final message is issue number(s) and URL(s) only.
29
+
30
+ ## On each turn
31
+
32
+ 1. **Load the skill** if this is the first turn of this agent. Use the Skill tool to load `issue-tracker`. Follow it for markers, dedup, tools, handoff schema, PR auto-close, and gotchas. Do not invent a second path.
33
+ 2. **Run the named action.** The ticket names one action: file a sub-issue from an issue-candidate, open an epic, update status in place, refresh the epic checklist, or close a sub-issue. Run that action end to end (every step *that* action needs: labels, attach, checklist refresh when the action creates or closes a child). Do not start a second, unrelated action in this turn.
34
+ 3. **Return numbers and URLs.** Affected issue number(s) and URL(s) only, no narration after that payload, so the caller can send the next action to this same agent or spawn another when the work-stream changes.
35
+
36
+ ## Hold these lines
37
+
38
+ - Dedup open **and** closed before create.
39
+ - Edit only between marker pairs; comments are cross-links only.
40
+ - Attach with REST `.id` and `gh -F`, never display `#N` or `-f`.
41
+ - Prefer GitHub MCP; fall back to `gh` per the skill matrix.
42
+ - When a fix ships: **PR body first** for auto-close. On the PR that merges into the **default branch**, put `Closes #N` for each finished sub-issue (`Closes #12` and `Closes #13`, not `Closes #12, #13`). Prefer the same line on the first commit. Stacked PRs that are not default-branch merges use plain `#N` only. A related UI link without a closing keyword does not close the issue. You own picking the correct `#N`.
@@ -2,7 +2,6 @@
2
2
  name: plan-packet-validator
3
3
  description: Fresh-context validator for workflow-generated plan packets. Use after a plan packet is written under docs/plans/<slug>/ to verify source accuracy, completeness, TDD readiness, scope control, handoff quality, and no invented repo behavior. Read-only; never edits files.
4
4
  tools: Read, Grep, Glob, Bash
5
- model: inherit
6
5
  color: purple
7
6
  ---
8
7
 
@@ -2,7 +2,6 @@
2
2
  name: pr-description-writer
3
3
  description: "Optional agent for PR descriptions and PR comments. Authors bodies in one of three Anthropic-derived shapes (Trivial / Standard / Heavy) so PRs match merged-PR style in `anthropics/claude-code`, `anthropics/claude-code-action`, and `anthropics/claude-code-sdk-python`, and pass the `pr_description_enforcer` PreToolUse hook's body audit on `gh pr create`, `gh pr edit`, and `gh pr comment`."
4
4
  tools: Read,Grep,Glob,Bash
5
- model: haiku
6
5
  ---
7
6
 
8
7
  # PR Description Writer
@@ -5,47 +5,79 @@ Code subagent loader reads. The loader accepts a fixed key set; an unrecognized
5
5
  top-level key breaks the spawn — the subagent starts with a broken definition,
6
6
  idles, and dies without a report::
7
7
 
8
- ok: name / description / tools / model / color
8
+ ok: name / description / tools / color
9
9
  flag: effort <- unrecognized, subagent dies delivering nothing
10
10
 
11
- The accepted set is the one `agents/CLAUDE.md` documents (name, description,
12
- tools, color) plus the `model` pin the working agents carry (clean-coder pins
13
- opus, pr-description-writer pins haiku). Top-level keys are read with a line
14
- scan so an agent whose `description` embeds informal `<example>` prose is not
15
- mistaken for one carrying extra keys.
11
+ An agent definition carries no `model` key at all — the caller supplies the
12
+ model on every spawn, so no agent definition names one, concrete or
13
+ `inherit`::
14
+
15
+ ok: <no model key at all>
16
+ flag: model: inherit <- caller can no longer choose the model
17
+ flag: model: opus <- pinned concrete model, caller can't override
18
+
19
+ Frontmatter parsing is self-contained here (stdlib only): the block is the
20
+ text between the file's opening and closing `---` fence lines, and top-level
21
+ keys are read with a line scan so an agent whose `description` embeds
22
+ informal `<example>` prose is not mistaken for one carrying extra keys.
16
23
  """
17
24
 
18
25
  from __future__ import annotations
19
26
 
20
27
  import re
28
+ from functools import cache
21
29
  from pathlib import Path
22
30
 
23
31
  import pytest
24
32
  import yaml
25
33
 
26
- ACCEPTED_FRONTMATTER_KEYS = frozenset(
27
- {"name", "description", "tools", "model", "color"}
28
- )
29
- FRONTMATTER_FENCE = "---"
30
- FRONTMATTER_SEGMENT_COUNT = 3
34
+ ACCEPTED_FRONTMATTER_KEYS = frozenset({"name", "description", "tools", "color"})
31
35
  CODE_VERIFIER_AGENT_NAME = "code-verifier"
36
+ EXEMPT_MARKDOWN_FILENAME = "CLAUDE.md"
37
+ FRONTMATTER_FENCE_LINE = "---"
38
+ MODEL_KEY_NAME = "model"
32
39
  TOP_LEVEL_KEY_PATTERN = re.compile(r"^([a-z][a-z0-9_]*):", re.MULTILINE)
33
40
 
34
41
 
35
- def _agent_definition_paths() -> list[Path]:
42
+ def _extract_frontmatter_block(markdown_text: str) -> str | None:
43
+ """Return the YAML text between the file's opening and closing --- fences.
44
+
45
+ Args:
46
+ markdown_text: The full text of an agent definition markdown file.
47
+
48
+ Returns:
49
+ The frontmatter text (the lines strictly between the two fence
50
+ lines), or None when the file does not open with a --- fence line or
51
+ never closes one.
52
+ """
53
+ all_lines = markdown_text.splitlines()
54
+ if not all_lines or all_lines[0].strip() != FRONTMATTER_FENCE_LINE:
55
+ return None
56
+ for each_line_index in range(1, len(all_lines)):
57
+ if all_lines[each_line_index].strip() == FRONTMATTER_FENCE_LINE:
58
+ return "\n".join(all_lines[1:each_line_index])
59
+ return None
60
+
61
+
62
+ @cache
63
+ def _agent_definition_paths() -> tuple[Path, ...]:
36
64
  agents_directory = Path(__file__).parent
37
65
  all_markdown_files = sorted(agents_directory.glob("*.md"))
38
- return [
66
+ return tuple(
39
67
  each_markdown_file
40
68
  for each_markdown_file in all_markdown_files
41
- if each_markdown_file.read_text(encoding="utf-8").startswith(FRONTMATTER_FENCE)
42
- ]
69
+ if each_markdown_file.name != EXEMPT_MARKDOWN_FILENAME
70
+ and _extract_frontmatter_block(each_markdown_file.read_text(encoding="utf-8"))
71
+ is not None
72
+ )
43
73
 
44
74
 
45
75
  def _frontmatter_block(agent_definition_path: Path) -> str:
46
- agent_text = agent_definition_path.read_text(encoding="utf-8")
47
- fence_segments = agent_text.split(FRONTMATTER_FENCE, FRONTMATTER_SEGMENT_COUNT - 1)
48
- return fence_segments[1]
76
+ frontmatter_block = _extract_frontmatter_block(
77
+ agent_definition_path.read_text(encoding="utf-8")
78
+ )
79
+ assert frontmatter_block is not None
80
+ return frontmatter_block
49
81
 
50
82
 
51
83
  def _top_level_keys(frontmatter_block: str) -> set[str]:
@@ -76,3 +108,20 @@ def test_code_verifier_frontmatter_parses_and_names_the_agent() -> None:
76
108
  parsed_frontmatter = yaml.safe_load(code_verifier_block)
77
109
  assert parsed_frontmatter["name"] == CODE_VERIFIER_AGENT_NAME
78
110
  assert set(parsed_frontmatter) <= ACCEPTED_FRONTMATTER_KEYS
111
+
112
+
113
+ @pytest.mark.parametrize(
114
+ "agent_definition_path",
115
+ _agent_definition_paths(),
116
+ ids=lambda each_path: each_path.name,
117
+ )
118
+ def test_agent_frontmatter_carries_no_model_key(
119
+ agent_definition_path: Path,
120
+ ) -> None:
121
+ frontmatter_block = _frontmatter_block(agent_definition_path)
122
+ declared_keys = _top_level_keys(frontmatter_block)
123
+ assert MODEL_KEY_NAME not in declared_keys, (
124
+ f"{agent_definition_path.name} carries a model: key in frontmatter; "
125
+ "the caller supplies the model on every spawn, so agent definitions "
126
+ "carry no model key at all, not even model: inherit"
127
+ )