claude-dev-env 2.3.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. package/CLAUDE.md +53 -48
  2. package/_shared/pr-loop/scripts/_claude_permissions_common.py +84 -0
  3. package/_shared/pr-loop/scripts/code_rules_gate.py +4 -2
  4. package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +306 -306
  5. package/_shared/pr-loop/scripts/pr_loop_shared_constants/claude_permissions_constants.py +44 -0
  6. package/_shared/pr-loop/scripts/pr_loop_shared_constants/copilot_quota_constants.py +24 -24
  7. package/_shared/pr-loop/scripts/pr_loop_shared_constants/stale_worktree_rule_sweep_constants.py +107 -107
  8. package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +290 -48
  9. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +42 -2
  10. package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +36 -0
  11. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +100 -1
  12. package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +497 -497
  13. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +311 -2
  14. package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep.py +301 -301
  15. package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep_constants.py +85 -85
  16. package/_shared/pr-loop/worker-spawn.md +1 -1
  17. package/agents/CLAUDE.md +3 -1
  18. package/agents/caveman.md +0 -1
  19. package/agents/clasp-deployment-orchestrator.md +0 -1
  20. package/agents/clean-coder.md +0 -1
  21. package/agents/code-advisor.md +0 -1
  22. package/agents/code-quality-agent.md +1 -2
  23. package/agents/code-verifier.md +3 -4
  24. package/agents/deep-research.md +0 -1
  25. package/agents/docs-agent.md +0 -1
  26. package/agents/git-commit-crafter.md +0 -1
  27. package/agents/issue-tracker.md +42 -0
  28. package/agents/plan-packet-validator.md +0 -1
  29. package/agents/pr-description-writer.md +0 -1
  30. package/agents/skill-writer-agent.md +84 -0
  31. package/agents/test_agent_frontmatter.py +67 -18
  32. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +105 -3
  33. package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +29 -13
  34. package/bin/CLAUDE.md +68 -5
  35. package/bin/ever-shipped-skills.mjs +1 -0
  36. package/bin/install-constants.mjs +88 -0
  37. package/bin/install.mjs +1138 -114
  38. package/bin/install.prune.test.mjs +869 -19
  39. package/bin/install.test.mjs +906 -2
  40. package/commands/implement.md +1 -1
  41. package/commands/right-size.md +1 -1
  42. package/docs/CLAUDE.md +2 -0
  43. package/docs/host-pool-health-monitor.md +102 -0
  44. package/docs/references/CLAUDE.md +5 -2
  45. package/docs/references/advisor-tool.md +13 -0
  46. package/docs/references/code-review-enforcement.md +107 -0
  47. package/docs/references/team-advisor-skill.md +14 -0
  48. package/docs/wsl-docker-cowork-starter-matrix.md +89 -0
  49. package/hooks/blocking/CLAUDE.md +9 -1
  50. package/hooks/blocking/code_review_enforcement_config_bootstrap.py +53 -0
  51. package/hooks/blocking/code_review_gate_deny.py +74 -0
  52. package/hooks/blocking/code_review_pr_create_gate.py +198 -0
  53. package/hooks/blocking/code_review_push_gate.py +145 -0
  54. package/hooks/blocking/code_review_stamp_directory_write_blocker.py +348 -0
  55. package/hooks/blocking/code_review_stamp_store.py +233 -0
  56. package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +7 -0
  57. package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +15 -0
  58. package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +212 -0
  59. package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +138 -0
  60. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +49 -0
  61. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +38 -0
  62. package/hooks/blocking/code_verifier_spawn_preflight_gate.py +39 -27
  63. package/hooks/blocking/config/__init__.py +5 -5
  64. package/hooks/blocking/config/code_review_enforcement_constants.py +113 -0
  65. package/hooks/blocking/config/test_code_review_enforcement_constants.py +113 -0
  66. package/hooks/blocking/config/verified_commit_constants.py +160 -155
  67. package/hooks/blocking/conftest.py +2 -0
  68. package/hooks/blocking/convergence_gate_blocker.py +112 -23
  69. package/hooks/blocking/destructive_command_blocker.py +19 -6
  70. package/hooks/blocking/orchestrator_refresh_reschedule_gate.py +256 -0
  71. package/hooks/blocking/pr_description_proof_of_work.py +52 -34
  72. package/hooks/blocking/pre_tool_use_dispatcher.py +24 -24
  73. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +4 -1
  74. package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +62 -0
  75. package/hooks/blocking/test_code_review_gate_deny.py +54 -0
  76. package/hooks/blocking/test_code_review_pr_create_gate.py +199 -0
  77. package/hooks/blocking/test_code_review_push_gate.py +205 -0
  78. package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +199 -0
  79. package/hooks/blocking/test_code_review_stamp_store.py +205 -0
  80. package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +124 -2
  81. package/hooks/blocking/test_convergence_gate_blocker.py +153 -5
  82. package/hooks/blocking/test_destructive_command_blocker.py +1 -1
  83. package/hooks/blocking/test_destructive_command_blocker_deny_mode.py +45 -0
  84. package/hooks/blocking/test_orchestrator_refresh_reschedule_gate.py +231 -0
  85. package/hooks/blocking/test_pr_description_proof_of_work.py +151 -0
  86. package/hooks/blocking/test_pre_tool_use_dispatcher.py +17 -8
  87. package/hooks/blocking/test_verdict_directory_write_blocker.py +808 -808
  88. package/hooks/blocking/test_verification_verdict_store.py +974 -903
  89. package/hooks/blocking/test_verified_commit_gate.py +581 -581
  90. package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +131 -131
  91. package/hooks/blocking/test_volatile_path_in_post_blocker.py +114 -2
  92. package/hooks/blocking/verdict_directory_write_blocker.py +687 -687
  93. package/hooks/blocking/verification_verdict_store.py +1039 -1014
  94. package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +29 -17
  95. package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +35 -0
  96. package/hooks/blocking/verified_commit_message_accuracy_blocker.py +167 -167
  97. package/hooks/blocking/verifier_verdict_minter.py +280 -280
  98. package/hooks/blocking/volatile_path_in_post_blocker.py +69 -8
  99. package/hooks/git-hooks/git_hooks_constants/__init__.py +6 -0
  100. package/hooks/git-hooks/pre_push.py +89 -2
  101. package/hooks/git-hooks/test_pre_push.py +128 -0
  102. package/hooks/hooks.json +26 -1
  103. package/hooks/hooks_constants/CLAUDE.md +3 -1
  104. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +8 -0
  105. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
  106. package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +26 -11
  107. package/hooks/hooks_constants/convergence_gate_blocker_constants.py +20 -3
  108. package/hooks/hooks_constants/destructive_command_segment_constants.py +3 -1
  109. package/hooks/hooks_constants/enter_worktree_prefetch_constants.py +18 -18
  110. package/hooks/hooks_constants/orchestrator_refresh_reschedule_gate_constants.py +48 -0
  111. package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -4
  112. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
  113. package/hooks/hooks_constants/pyproject_config_discovery_constants.py +16 -0
  114. package/hooks/hooks_constants/ruff_integration_constants.py +16 -0
  115. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +24 -0
  116. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +6 -0
  117. package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +8 -1
  118. package/hooks/lifecycle/enter_worktree_origin_prefetch.py +163 -146
  119. package/hooks/lifecycle/test_enter_worktree_origin_prefetch.py +185 -178
  120. package/hooks/pyproject.toml +1 -0
  121. package/hooks/validators/CLAUDE.md +2 -0
  122. package/hooks/validators/config/__init__.py +0 -0
  123. package/hooks/validators/config/directory_exemption_constants.py +183 -0
  124. package/hooks/validators/config/test_directory_exemption_constants.py +21 -0
  125. package/hooks/validators/conftest.py +4 -0
  126. package/hooks/validators/mypy_integration.py +63 -50
  127. package/hooks/validators/pyproject_config_discovery.py +101 -0
  128. package/hooks/validators/ruff_integration.py +257 -25
  129. package/hooks/validators/run_all_validators.py +223 -19
  130. package/hooks/validators/test_directory_exemption_constants.py +185 -0
  131. package/hooks/validators/test_mypy_integration.py +32 -0
  132. package/hooks/validators/test_pyproject_config_discovery.py +94 -0
  133. package/hooks/validators/test_python_antipattern_checks.py +110 -5
  134. package/hooks/validators/test_ruff_integration.py +160 -2
  135. package/hooks/validators/test_run_all_validators.py +140 -68
  136. package/hooks/validators/test_run_all_validators_config_discovery.py +123 -0
  137. package/hooks/validators/test_run_all_validators_pretooluse.py +159 -1
  138. package/package.json +10 -2
  139. package/rules/CLAUDE.md +1 -0
  140. package/rules/docstring-prose-matches-implementation.md +45 -67
  141. package/rules/durable-post-artifacts.md +7 -0
  142. package/rules/state-what-is.md +25 -0
  143. package/rules/verified-commit-gate-skip.md +1 -1
  144. package/scripts/CLAUDE.md +1 -0
  145. package/scripts/Capture-PoolHealth.ps1 +410 -0
  146. package/scripts/_code_review_test_support.py +404 -0
  147. package/scripts/claude_chain_runner.py +141 -1
  148. package/scripts/codec_forwarding_test_support.py +83 -0
  149. package/scripts/conftest.py +23 -0
  150. package/scripts/dev_env_scripts_constants/CLAUDE.md +2 -2
  151. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +53 -1
  152. package/scripts/dev_env_scripts_constants/code_review_constants.py +129 -12
  153. package/scripts/dev_env_scripts_constants/test_code_review_constants.py +55 -0
  154. package/scripts/invoke_code_review.py +550 -38
  155. package/scripts/resolve_worker_spawn.py +626 -619
  156. package/scripts/spawn_grok_batch.py +672 -672
  157. package/scripts/test_claude_chain_runner.py +131 -0
  158. package/scripts/test_invoke_code_review_chain.py +70 -0
  159. package/scripts/test_invoke_code_review_cli.py +192 -0
  160. package/scripts/test_invoke_code_review_codec.py +77 -0
  161. package/scripts/test_invoke_code_review_contract.py +256 -0
  162. package/scripts/test_invoke_code_review_git.py +123 -0
  163. package/scripts/test_invoke_code_review_mode.py +99 -0
  164. package/scripts/test_resolve_worker_spawn.py +1014 -1014
  165. package/scripts/test_resolve_worker_spawn_codec.py +101 -0
  166. package/skills/CLAUDE.md +2 -0
  167. package/skills/auditing-claude-config/SKILL.md +114 -114
  168. package/skills/autoconverge/SKILL.md +427 -421
  169. package/skills/autoconverge/reference/convergence.md +26 -4
  170. package/skills/autoconverge/reference/multi-pr.md +6 -1
  171. package/skills/autoconverge/reference/stop-conditions.md +16 -10
  172. package/skills/autoconverge/workflow/CLAUDE.md +1 -0
  173. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +4 -4
  174. package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +175 -3
  175. package/skills/autoconverge/workflow/converge.contract.test.mjs +1263 -1244
  176. package/skills/autoconverge/workflow/converge.mjs +191 -8
  177. package/skills/autoconverge/workflow/converge.p2-advance.test.mjs +202 -0
  178. package/skills/autoconverge/workflow/converge_multi.mjs +7 -3
  179. package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +5 -0
  180. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +2 -2
  181. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +2 -2
  182. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +2 -2
  183. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +2 -2
  184. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +2 -2
  185. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +2 -2
  186. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +2 -2
  187. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +2 -2
  188. package/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +265 -265
  189. package/skills/closeout/SKILL.md +33 -50
  190. package/skills/codex-review/scripts/codex_review_scripts_constants/run_constants.py +8 -0
  191. package/skills/codex-review/scripts/run_codex_review.py +233 -1
  192. package/skills/codex-review/scripts/test_run_codex_review.py +189 -0
  193. package/skills/condensing-instructions/SKILL.md +81 -0
  194. package/skills/copilot-review/SKILL.md +119 -119
  195. package/skills/e-code-review/SKILL.md +52 -0
  196. package/skills/e-code-review/reference/fix.md +54 -0
  197. package/skills/e-code-review/reference/loop.md +43 -0
  198. package/skills/e-code-review/reference/low.md +57 -0
  199. package/skills/e-code-review/reference/medium.md +153 -0
  200. package/skills/e-code-review/reference/xhigh.md +182 -0
  201. package/skills/e-simplify/SKILL.md +97 -0
  202. package/skills/fresh-branch/CLAUDE.md +2 -0
  203. package/skills/fresh-branch/SKILL.md +2 -0
  204. package/skills/fresh-branch/scripts/create_fresh_branch.py +78 -180
  205. package/skills/fresh-branch/scripts/fresh_branch_git_commands.py +285 -0
  206. package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +1 -0
  207. package/skills/fresh-branch/scripts/pytest.ini +4 -0
  208. package/skills/fresh-branch/scripts/test_create_fresh_branch.py +98 -0
  209. package/skills/fresh-branch/scripts/test_fresh_branch_git_commands.py +310 -0
  210. package/skills/issue-tracker/SKILL.md +92 -0
  211. package/skills/issue-tracker/reference/epic-and-sub-issue-model.md +55 -0
  212. package/skills/issue-tracker/reference/handoff-schema.md +64 -0
  213. package/skills/issue-tracker/reference/operation-matrix.md +41 -0
  214. package/skills/orchestrator/SKILL.md +162 -21
  215. package/skills/orchestrator/scripts/status_gate.py +625 -0
  216. package/skills/orchestrator/scripts/status_gate_constants/__init__.py +1 -0
  217. package/skills/orchestrator/scripts/status_gate_constants/config/__init__.py +1 -0
  218. package/skills/orchestrator/scripts/status_gate_constants/config/constants.py +47 -0
  219. package/skills/orchestrator/scripts/test_status_gate.py +439 -0
  220. package/skills/orchestrator-refresh/SKILL.md +110 -35
  221. package/skills/plan-to-pr/SKILL.md +155 -0
  222. package/skills/plan-to-pr/reference/final-validation-tasks.md +15 -0
  223. package/skills/plan-to-pr/reference/model-routing.md +36 -0
  224. package/skills/plan-to-pr/reference/packet-contract.md +43 -0
  225. package/skills/plan-to-pr/reference/packet-schema.json +57 -0
  226. package/skills/plan-to-pr/reference/process-inventory.md +22 -0
  227. package/skills/plan-to-pr/reference/review-loop.md +33 -0
  228. package/skills/plan-to-pr/reference/run-record.schema.json +27 -0
  229. package/skills/plan-to-pr/reference/self-audit-tasks.md +15 -0
  230. package/skills/plan-to-pr/reference/task-seeds.md +14 -0
  231. package/skills/plan-to-pr/reference/task-ticket.md +38 -0
  232. package/skills/plan-to-pr/scripts/config/__init__.py +1 -0
  233. package/skills/plan-to-pr/scripts/config/constants.py +193 -0
  234. package/skills/plan-to-pr/scripts/create_packet.py +173 -0
  235. package/skills/plan-to-pr/scripts/test_create_packet.py +102 -0
  236. package/skills/plan-to-pr/scripts/test_validate_packet.py +256 -0
  237. package/skills/plan-to-pr/scripts/test_validate_protocol.py +135 -0
  238. package/skills/plan-to-pr/scripts/test_validate_run.py +158 -0
  239. package/skills/plan-to-pr/scripts/validate_packet.py +655 -0
  240. package/skills/plan-to-pr/scripts/validate_protocol.py +622 -0
  241. package/skills/plan-to-pr/scripts/validate_run.py +173 -0
  242. package/skills/plan-to-pr/test_skill_contract.py +207 -0
  243. package/skills/plan-to-pr/test_task_ticket_contract.py +151 -0
  244. package/skills/pr-converge/SKILL.md +472 -469
  245. package/skills/pr-converge/reference/examples.md +3 -3
  246. package/skills/pr-converge/reference/fix-protocol.md +1 -1
  247. package/skills/pr-converge/reference/ground-rules.md +7 -4
  248. package/skills/pr-converge/reference/multi-pr-orchestration.md +4 -1
  249. package/skills/pr-converge/reference/per-tick.md +5 -5
  250. package/skills/pr-converge/reference/progress-checklist.md +1 -1
  251. package/skills/pr-converge/scripts/check_convergence_gates.py +279 -279
  252. package/skills/pr-converge/scripts/test_check_convergence_codex.py +507 -507
  253. package/skills/pr-converge/scripts/test_check_convergence_gates.py +84 -84
  254. package/skills/pr-converge/test_step5_host_branch.py +1 -1
  255. package/skills/pr-fix-protocol/SKILL.md +1 -1
  256. package/skills/privacy-hygiene/SKILL.md +68 -68
  257. package/skills/prototype/SKILL.md +86 -0
  258. package/skills/prototype/reference/honest-limitations.md +23 -0
  259. package/skills/prototype/reference/promotion-tasks.md +23 -0
  260. package/skills/prototype/scripts/build_sandbox_settings.py +249 -0
  261. package/skills/prototype/scripts/conftest.py +15 -0
  262. package/skills/prototype/scripts/launch_sandbox.py +205 -0
  263. package/skills/prototype/scripts/probe_sandbox_safety.py +311 -0
  264. package/skills/prototype/scripts/prototype_scripts_constants/__init__.py +1 -0
  265. package/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
  266. package/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +41 -0
  267. package/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +23 -0
  268. package/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +45 -0
  269. package/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +10 -0
  270. package/skills/prototype/scripts/test_build_sandbox_settings.py +275 -0
  271. package/skills/prototype/scripts/test_launch_sandbox.py +303 -0
  272. package/skills/prototype/scripts/test_probe_sandbox_safety.py +284 -0
  273. package/skills/prototype/workflows/promotion.md +27 -0
  274. package/skills/prototype/workflows/sandbox.md +35 -0
  275. package/skills/release-notes-html/SKILL.md +164 -0
  276. package/skills/skill-builder/CLAUDE.md +3 -3
  277. package/skills/skill-builder/SKILL.md +5 -5
  278. package/skills/skill-builder/references/CLAUDE.md +1 -1
  279. package/skills/skill-builder/references/delegation-map.md +3 -3
  280. package/skills/skill-builder/references/description-field.md +1 -1
  281. package/skills/skill-builder/references/skill-modularity.md +2 -3
  282. package/skills/skill-builder/workflows/CLAUDE.md +1 -1
  283. package/skills/skill-builder/workflows/improve-skill.md +1 -1
  284. package/skills/skill-builder/workflows/new-skill.md +2 -2
  285. package/skills/task-build/CLAUDE.md +8 -7
  286. package/skills/task-build/SKILL.md +16 -8
  287. package/skills/task-build/reference/tool-routing.md +19 -0
  288. package/skills/team-advisor/SKILL.md +2 -2
  289. package/scripts/test_invoke_code_review.py +0 -672
  290. 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
@@ -15,13 +15,15 @@ Agent definition files installed into `~/.claude/agents/` by `bin/install.mjs`.
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
+ | `skill-writer-agent.md` | Skill Writer Agent | Authors SKILL.md and companion files to skill-builder conventions; caller-agnostic authoring specialist |
21
23
 
22
24
  ## Format
23
25
 
24
- 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.
25
27
 
26
28
  ## Adding an agent
27
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,12 +1,11 @@
1
1
  ---
2
2
  name: code-verifier
3
- description: Post-hoc verification agent for the two-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. Read and execute only; it never edits files.
4
4
  tools: Read, Grep, Glob, Bash, Task
5
- model: sonnet
6
5
  color: orange
7
6
  ---
8
7
 
9
- You are the verifier in a two-phase code workflow: coder agents wrote changes, and you grade the result on its own terms (Claude Code best practices, fresh-context review: https://code.claude.com/docs/en/best-practices). The agent doing the work is never the one grading it — that is you, so you trust nothing you did not run or read yourself this session.
8
+ You are the verifier in a three-phase code workflow: coder agents wrote changes, and you grade the result on its own terms (Claude Code best practices, fresh-context review: https://code.claude.com/docs/en/best-practices). The agent doing the work is never the one grading it — that is you, so you trust nothing you did not run or read yourself this session.
10
9
 
11
10
  The caller gives you task texts, the diff scope, and baselines recorded before the coders ran. Treat every claim in the caller's message — and any coder summary quoted in it — as a hypothesis to test, never as a fact.
12
11
 
@@ -43,4 +42,4 @@ End your final message with exactly one fenced verdict block — the verifier_ve
43
42
  {"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
43
  ```
45
44
 
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. Any file change after you finish moves that hash and invalidates the verdict, so you are the last step before the commit.
45
+ 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.
@@ -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
@@ -0,0 +1,84 @@
1
+ ---
2
+ name: skill-writer-agent
3
+ description: >-
4
+ Authors a skill package — SKILL.md plus companion files — to skill-builder conventions: hub layout, trigger-catalog description, deterministic work shipped as scripts and task-seed lists. Triggers: author a skill, write a SKILL.md, skill-writer-agent, delegate skill file authoring, produce skill package files.
5
+ tools: Read, Write, Edit, Grep, Glob
6
+ color: green
7
+ ---
8
+
9
+ # Skill Writer Agent
10
+
11
+ You author skill files. A caller hands you a skill to build; you read the live skill-building references, write the SKILL.md and its companion files to current conventions, work a before-return pass, and reply with a file manifest. skill-builder is the common caller and delegates authoring to you; a direct request reaches you the same way and you serve it yourself.
12
+
13
+ ## Input
14
+
15
+ Callers reach you in one of two shapes.
16
+
17
+ **Structured packet** (skill-builder sends this). A new-skill packet carries these fields, in order:
18
+
19
+ - `Skill type` — one of the skill-builder skill types.
20
+ - `Folder structure` — the directories to create (`reference/`, `scripts/`, `templates/`, `workflow/`).
21
+ - `What it does` — one sentence naming the single job.
22
+ - `Domain context` — what the authored skill needs Claude to know.
23
+ - `Initial gotchas` — failure patterns to document from the start.
24
+ - `Degree of freedom` — high, medium, or low, with reasoning.
25
+ - `Constraints` — the non-negotiables.
26
+ - `Composition plan` — related skills, sub-skills with when/produces/missing, leaf-versus-orchestrator call.
27
+ - `Description` — the exact frontmatter string, written as a trigger catalog.
28
+ - `Deterministic inventory` — each process step mapped to its class, its home path, and its paired test.
29
+
30
+ A caller may bundle three of these fields — `Composition plan`, `Description`, and `Deterministic inventory` — under one `gap analysis` label; treat that label as those three fields together, and read them from the packet either way.
31
+
32
+ A refine packet instead carries: the current SKILL.md, what was observed, what to change, new gotchas to add, what to preserve, a description rewrite, a composition change, and a deterministic fix.
33
+
34
+ **Direct request.** A looser prompt names the skill to build or the file to edit. Fill the packet yourself from the references below, and ask the caller for any field only they hold, such as domain context or constraints.
35
+
36
+ ## Read first
37
+
38
+ At the start of every job, read the live references at `~/.claude/skills/skill-builder/references/` so you author to current conventions:
39
+
40
+ - `delegation-map.md` — the handoff packet and the Produce contract.
41
+ - `deterministic-elements.md` — the rule that deterministic work ships as code, artifacts, or session tasks.
42
+ - `description-field.md` — the trigger-catalog description shape.
43
+ - `skill-modularity.md` — hub layout and compose-by-name.
44
+
45
+ Read them fresh each run; they carry the rules you author to.
46
+
47
+ ## Standards every file meets
48
+
49
+ 1. SKILL.md follows the hub layout: principle, gotchas, when-applies, process, file index, folder map.
50
+ 2. The frontmatter description is the exact trigger-catalog string — a `capability stem` plus a `Triggers:` list.
51
+ 3. A sub-skills table appears whenever the composition plan lists peer skills.
52
+ 4. Companion files carry the detail: reference docs, workflow steps, templates, scripts.
53
+ 5. Every deterministic-inventory row gets a script or workflow; SKILL.md points to it and handles its exit codes.
54
+ 6. Skill scripts follow CODE_RULES: a `*_constants/` package, full type hints, specific errors, and a paired test.
55
+ 7. Every file stays under 500 lines, with a table of contents on any file over 100 lines.
56
+ 8. A file index lists every file and its purpose.
57
+ 9. Steps owned by a named sub-skill stay in that sub-skill; SKILL.md invokes it by name.
58
+
59
+ ## Deterministic elements
60
+
61
+ Honor `deterministic-elements.md` in every skill you author. A step is deterministic when the same inputs give the same outputs, success is machine-checkable, and a human could write it as a pure function or a fixed sequence. Route each kind to its home:
62
+
63
+ - Validators, transforms, detection, and mechanical sequences ship as `scripts/` or `workflow/*.mjs`, each with tests and a `*_constants/` package.
64
+ - Verbatim templates ship in `templates/`.
65
+ - Long fixed catalogs Claude reads word-for-word ship in `reference/`.
66
+ - Ordered work the future skill-runner must finish ships as a task-seed list: plain bullets or numbered lines in a reference file, plus an instruction in SKILL.md to register one session task per item through the host task tool (`TaskCreate`, else `TodoWrite`). Judgment, routing, and refusal stay in markdown prose.
67
+
68
+ Keep required step lists on the task list. Where a host task tool is present, the authored SKILL.md registers each item through it; where the host offers only prose, the SKILL.md states that limit and stops.
69
+
70
+ ## Before you return
71
+
72
+ Work your before-return checks as one pass over the file set:
73
+
74
+ - The SKILL.md hub layout is present and complete.
75
+ - The description reads as a trigger catalog.
76
+ - Every deterministic row carries its script and paired test.
77
+ - Files stay under the line caps, with a table of contents wherever the cap calls for one.
78
+ - The file index is present, and the sub-skills table is present whenever the skill composes others.
79
+
80
+ On a direct call, this pass is your whole quality gate — work it in full. When skill-builder is the caller, it runs its 38-point audit next; hand it a coherent, complete file set so that audit starts clean.
81
+
82
+ ## Return
83
+
84
+ Reply with a file manifest kept thin: each path you created or edited on its own line, then one line naming what the caller picks up next, such as the audit or the tests to run. Keep the reply light so the caller acts on it directly.
@@ -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
+ )