claude-dev-env 4.2.0 → 7.0.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 (350) hide show
  1. package/.agents/agents/pr-description-writer.md +2 -2
  2. package/.agents/agents/test_agent_frontmatter.py +2 -2
  3. package/.agents/agents/test_pr_description_writer_style.py +1 -3
  4. package/.agents/skills/e-code-review/SKILL.md +12 -12
  5. package/.agents/skills/e-code-review/preflight-proposal.contract.test.mjs +20 -0
  6. package/.agents/skills/e-code-review/reference/fix.md +15 -17
  7. package/.agents/skills/e-code-review/reference/loop.md +35 -39
  8. package/.agents/skills/e-code-review/reference/mode-contract.test.mjs +2 -1
  9. package/.agents/skills/e-code-review/reference/preflight-proposal.md +44 -9
  10. package/.agents/skills/e-code-review/reference/runner-selection.md +13 -14
  11. package/.agents/skills/issue-tracker/SKILL.md +7 -0
  12. package/.agents/skills/pull-request/SKILL.md +216 -0
  13. package/.agents/skills/pull-request/reference/publication-tasks.md +18 -0
  14. package/.agents/skills/pull-request/scripts/github_pr_command_constants/__init__.py +1 -0
  15. package/.agents/skills/pull-request/scripts/github_pr_command_constants/config/__init__.py +1 -0
  16. package/.agents/skills/pull-request/scripts/github_pr_command_constants/config/constants.py +69 -0
  17. package/.agents/skills/pull-request/scripts/pull_request.py +265 -0
  18. package/.agents/skills/pull-request/scripts/recover_legacy_author.py +251 -0
  19. package/.agents/skills/pull-request/scripts/test_github_pr_command_constants.py +58 -0
  20. package/.agents/skills/pull-request/scripts/test_pull_request.py +396 -0
  21. package/.agents/skills/pull-request/scripts/test_recover_legacy_author.py +221 -0
  22. package/.agents/skills/skill-builder/SKILL.md +3 -196
  23. package/.agents/skills/test-runner/SKILL.md +69 -0
  24. package/.agents/skills/test-runner/scripts/preflight_checks.py +365 -0
  25. package/.agents/skills/test-runner/scripts/run_tests.py +316 -0
  26. package/.agents/skills/test-runner/scripts/test_preflight_checks.py +185 -0
  27. package/.agents/skills/test-runner/scripts/test_run_tests.py +281 -0
  28. package/.agents/skills/test-runner/scripts/test_runner_constants/__init__.py +1 -0
  29. package/.agents/skills/test-runner/scripts/test_runner_constants/config/__init__.py +1 -0
  30. package/.agents/skills/test-runner/scripts/test_runner_constants/config/constants.py +96 -0
  31. package/.agents/skills-archived/pr-loop-cloud-transport/reference/identity-and-hooks.md +1 -1
  32. package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +6 -0
  33. package/_shared/pr-loop/scripts/terminology_sweep.py +64 -19
  34. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +59 -0
  35. package/bin/ever-shipped-skills.mjs +2 -0
  36. package/bin/ever-shipped-skills.test.mjs +8 -0
  37. package/bin/install.agents-home.test.mjs +15 -13
  38. package/bin/install.mjs +37 -1
  39. package/bin/install.policy-lint.test.mjs +78 -0
  40. package/bin/install.prune.test.mjs +149 -20
  41. package/bin/install.pstack-plugin.test.mjs +114 -0
  42. package/bin/install.test.mjs +61 -7
  43. package/bin/refresh-pstack-plugin-skills.test.mjs +211 -0
  44. package/hooks/advisory/pr_done_reminder.py +342 -0
  45. package/hooks/advisory/test_pr_done_reminder.py +249 -0
  46. package/hooks/blocking/bash_post_call_dispatcher.py +53 -6
  47. package/hooks/blocking/code_rules_paired_test.py +22 -15
  48. package/hooks/blocking/pii_prevention_blocker.py +32 -51
  49. package/hooks/blocking/test_bash_post_call_dispatcher.py +55 -17
  50. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +1 -4
  51. package/hooks/blocking/test_code_rules_command_dispatch.py +2 -2
  52. package/hooks/blocking/test_code_rules_enforcer_paired_test.py +36 -29
  53. package/hooks/blocking/test_code_rules_js_resume_task_enumeration.py +4 -4
  54. package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +5 -5
  55. package/hooks/blocking/test_pii_prevention_blocker.py +37 -89
  56. package/hooks/blocking/test_pre_tool_use_dispatcher.py +68 -133
  57. package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +20 -51
  58. package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +18 -18
  59. package/hooks/gh_post_body_texts.py +195 -0
  60. package/hooks/git-hooks/git_hooks_constants/staged_policy_lint.py +12 -0
  61. package/hooks/git-hooks/pre_commit.py +52 -11
  62. package/hooks/git-hooks/test_installed_policy_lint.py +159 -0
  63. package/hooks/git-hooks/test_native_policy_commit.py +87 -0
  64. package/hooks/git-hooks/test_pre_commit.py +246 -0
  65. package/hooks/hooks.json +1 -16
  66. package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +14 -2
  67. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -8
  68. package/hooks/hooks_constants/gh_post_body_texts_constants.py +12 -0
  69. package/hooks/hooks_constants/pr_done_reminder_constants.py +98 -0
  70. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -43
  71. package/hooks/hooks_constants/test_bash_post_call_dispatcher_constants.py +17 -7
  72. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +0 -7
  73. package/hooks/hooks_constants/test_failure_recorder_constants.py +3 -3
  74. package/hooks/hooks_constants/test_gh_post_body_texts_constants.py +19 -0
  75. package/hooks/hooks_constants/test_pr_done_reminder_constants.py +40 -0
  76. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +20 -32
  77. package/hooks/observability/test_failure_recorder.py +1 -1
  78. package/hooks/pyproject.toml +1 -1
  79. package/hooks/test_gh_post_body_texts.py +79 -0
  80. package/package.json +2 -2
  81. package/rules/durable-post-artifacts.md +32 -20
  82. package/rules/gh-cli-conventions.md +23 -13
  83. package/rules/git-workflow.md +21 -8
  84. package/scripts/check.ps1 +0 -1
  85. package/{.agents/skills/run-claude-dev-env/driver.mjs → scripts/ci/installer-lifecycle.mjs} +3 -28
  86. package/scripts/ci/installer-lifecycle.test.mjs +26 -0
  87. package/scripts/ci/windows-installer-lifecycle.ps1 +1 -11
  88. package/scripts/dev_env_scripts_constants/shared_tree_constants.py +4 -5
  89. package/scripts/durable_post_lint.py +206 -0
  90. package/scripts/durable_post_lint_config/config/constants.py +96 -0
  91. package/scripts/durable_post_lint_config/config/test_constants.py +25 -0
  92. package/scripts/policy_lint/adapter_configuration.py +53 -12
  93. package/scripts/policy_lint/adapter_detectors.py +13 -6
  94. package/scripts/policy_lint/adapters.py +7 -4
  95. package/scripts/policy_lint/config/constants.py +5 -0
  96. package/scripts/policy_lint/engine.py +48 -4
  97. package/scripts/policy_lint/model.py +1 -0
  98. package/scripts/policy_lint/registry.py +1 -1
  99. package/scripts/policy_lint/selection.py +2 -1
  100. package/scripts/policy_lint/selection_git.py +2 -0
  101. package/scripts/refresh_pstack_plugin_skills.mjs +200 -0
  102. package/scripts/repository_checks/config/constants.py +0 -5
  103. package/scripts/shared_tree_paths.py +32 -3
  104. package/scripts/test_durable_post_lint.py +332 -0
  105. package/scripts/test_shared_tree_paths.py +33 -0
  106. package/scripts/tests/test_adapter_configuration.py +54 -0
  107. package/scripts/tests/test_policy_lint_rules_archive.py +120 -0
  108. package/scripts/tests/test_policy_lint_rules_engine.py +25 -4
  109. package/scripts/tests/test_policy_lint_rules_installed_loader.py +29 -0
  110. package/scripts/tests/test_policy_lint_rules_registry.py +227 -13
  111. package/scripts/tests/test_policy_lint_selection.py +25 -2
  112. package/scripts/tests/test_policy_lint_selection_native_index.py +30 -0
  113. package/scripts/tests/test_rules.py +3 -7
  114. package/.agents/skills/_shared/pr-loop/.claude/CLAUDE.md +0 -1
  115. package/.agents/skills/_shared/pr-loop/AGENTS.md +0 -1
  116. package/.agents/skills/_shared/pr-loop/audit-contract.md +0 -5
  117. package/.agents/skills/_shared/pr-loop/audit-reply-template.md +0 -5
  118. package/.agents/skills/_shared/pr-loop/code-rules-gate.md +0 -5
  119. package/.agents/skills/_shared/pr-loop/fix-protocol.md +0 -5
  120. package/.agents/skills/_shared/pr-loop/gh-payloads.md +0 -5
  121. package/.agents/skills/_shared/pr-loop/portable-driver.md +0 -149
  122. package/.agents/skills/_shared/pr-loop/post-audit-thread-contract.md +0 -5
  123. package/.agents/skills/_shared/pr-loop/precatch-rubric.md +0 -5
  124. package/.agents/skills/_shared/pr-loop/preflight-proposal.contract.test.mjs +0 -119
  125. package/.agents/skills/_shared/pr-loop/preflight-proposal.md +0 -5
  126. package/.agents/skills/_shared/pr-loop/prompts/.claude/CLAUDE.md +0 -1
  127. package/.agents/skills/_shared/pr-loop/prompts/AGENTS.md +0 -1
  128. package/.agents/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +0 -324
  129. package/.agents/skills/_shared/pr-loop/scripts/.claude/CLAUDE.md +0 -1
  130. package/.agents/skills/_shared/pr-loop/scripts/AGENTS.md +0 -1
  131. package/.agents/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +0 -29
  132. package/.agents/skills/_shared/pr-loop/scripts/_cli_utils.py +0 -22
  133. package/.agents/skills/_shared/pr-loop/scripts/_path_resolver.py +0 -186
  134. package/.agents/skills/_shared/pr-loop/scripts/_xml_utils.py +0 -20
  135. package/.agents/skills/_shared/pr-loop/scripts/audit_category_schema.py +0 -355
  136. package/.agents/skills/_shared/pr-loop/scripts/build_audit_prompt.py +0 -289
  137. package/.agents/skills/_shared/pr-loop/scripts/build_converge_task_list.py +0 -310
  138. package/.agents/skills/_shared/pr-loop/scripts/build_fix_prompt.py +0 -292
  139. package/.agents/skills/_shared/pr-loop/scripts/init_loop_state.py +0 -134
  140. package/.agents/skills/_shared/pr-loop/scripts/portable_converge_driver.py +0 -1646
  141. package/.agents/skills/_shared/pr-loop/scripts/preflight_worktree.py +0 -392
  142. package/.agents/skills/_shared/pr-loop/scripts/select_converge_pacer.py +0 -215
  143. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/.claude/CLAUDE.md +0 -1
  144. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +0 -1
  145. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/__init__.py +0 -0
  146. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +0 -37
  147. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/converge_task_list_constants.py +0 -56
  148. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/handoff_constants.py +0 -45
  149. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/pacer_constants.py +0 -47
  150. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +0 -157
  151. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/portable_driver_constants.py +0 -177
  152. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/preflight_constants.py +0 -70
  153. package/.agents/skills/_shared/pr-loop/scripts/teardown_worktrees.py +0 -172
  154. package/.agents/skills/_shared/pr-loop/scripts/test__path_resolver.py +0 -106
  155. package/.agents/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +0 -94
  156. package/.agents/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +0 -499
  157. package/.agents/skills/_shared/pr-loop/scripts/test_build_converge_task_list.py +0 -140
  158. package/.agents/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +0 -262
  159. package/.agents/skills/_shared/pr-loop/scripts/test_init_loop_state.py +0 -71
  160. package/.agents/skills/_shared/pr-loop/scripts/test_portable_converge_driver.py +0 -1119
  161. package/.agents/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +0 -287
  162. package/.agents/skills/_shared/pr-loop/scripts/test_select_converge_pacer.py +0 -207
  163. package/.agents/skills/_shared/pr-loop/scripts/test_teardown_worktrees.py +0 -59
  164. package/.agents/skills/_shared/pr-loop/scripts/test_write_handoff.py +0 -201
  165. package/.agents/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +0 -182
  166. package/.agents/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +0 -206
  167. package/.agents/skills/_shared/pr-loop/scripts/write_handoff.py +0 -309
  168. package/.agents/skills/_shared/pr-loop/state-schema.md +0 -5
  169. package/.agents/skills/_shared/pr-loop/worker-spawn.md +0 -5
  170. package/.agents/skills/autoconverge/.claude/CLAUDE.md +0 -1
  171. package/.agents/skills/autoconverge/AGENTS.md +0 -1
  172. package/.agents/skills/autoconverge/SKILL.md +0 -431
  173. package/.agents/skills/autoconverge/reference/.claude/CLAUDE.md +0 -1
  174. package/.agents/skills/autoconverge/reference/AGENTS.md +0 -1
  175. package/.agents/skills/autoconverge/reference/closing-report.md +0 -93
  176. package/.agents/skills/autoconverge/reference/convergence-gates.md +0 -31
  177. package/.agents/skills/autoconverge/reference/convergence.md +0 -219
  178. package/.agents/skills/autoconverge/reference/copilot-findings.md +0 -51
  179. package/.agents/skills/autoconverge/reference/gotchas.md +0 -66
  180. package/.agents/skills/autoconverge/reference/headless-safety.md +0 -44
  181. package/.agents/skills/autoconverge/reference/multi-pr.md +0 -128
  182. package/.agents/skills/autoconverge/reference/self-closing-loop.md +0 -84
  183. package/.agents/skills/autoconverge/reference/stop-conditions.md +0 -127
  184. package/.agents/skills/autoconverge/test_portable_pacer_gate.py +0 -54
  185. package/.agents/skills/autoconverge/workflow/.claude/CLAUDE.md +0 -1
  186. package/.agents/skills/autoconverge/workflow/AGENTS.md +0 -1
  187. package/.agents/skills/autoconverge/workflow/aggregate_runs.py +0 -371
  188. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/.claude/CLAUDE.md +0 -1
  189. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +0 -1
  190. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/__init__.py +0 -0
  191. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +0 -250
  192. package/.agents/skills/autoconverge/workflow/converge.clean-audit.test.mjs +0 -665
  193. package/.agents/skills/autoconverge/workflow/converge.codex-gate.test.mjs +0 -472
  194. package/.agents/skills/autoconverge/workflow/converge.contract.test.mjs +0 -64
  195. package/.agents/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +0 -1009
  196. package/.agents/skills/autoconverge/workflow/converge.fix-progress.test.mjs +0 -107
  197. package/.agents/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +0 -33
  198. package/.agents/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +0 -15
  199. package/.agents/skills/autoconverge/workflow/converge.mjs +0 -3132
  200. package/.agents/skills/autoconverge/workflow/converge.p2-advance.test.mjs +0 -202
  201. package/.agents/skills/autoconverge/workflow/converge.path-aware.test.mjs +0 -47
  202. package/.agents/skills/autoconverge/workflow/converge.precatch.test.mjs +0 -152
  203. package/.agents/skills/autoconverge/workflow/converge.run-input.test.mjs +0 -116
  204. package/.agents/skills/autoconverge/workflow/converge_multi.mjs +0 -169
  205. package/.agents/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +0 -173
  206. package/.agents/skills/autoconverge/workflow/convergence_summary.py +0 -110
  207. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +0 -2
  208. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +0 -2
  209. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +0 -2
  210. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +0 -2
  211. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +0 -2
  212. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +0 -2
  213. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ab1c2d3e4f5a6b7c8.jsonl +0 -2
  214. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +0 -2
  215. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +0 -2
  216. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +0 -266
  217. package/.agents/skills/autoconverge/workflow/render_report.py +0 -1024
  218. package/.agents/skills/autoconverge/workflow/test_aggregate_runs.py +0 -134
  219. package/.agents/skills/autoconverge/workflow/test_convergence_summary.py +0 -149
  220. package/.agents/skills/autoconverge/workflow/test_render_report.py +0 -812
  221. package/.agents/skills/hitl/SKILL.md +0 -45
  222. package/.agents/skills/pr-cleanup/SKILL.md +0 -140
  223. package/.agents/skills/pr-cleanup/reference/process-inventory.md +0 -17
  224. package/.agents/skills/pr-cleanup/reference/task-seeds.md +0 -22
  225. package/.agents/skills/pr-name-by-capability/SKILL.md +0 -73
  226. package/.agents/skills/pr-name-by-capability/reference/fetch-commands.md +0 -29
  227. package/.agents/skills/pr-name-by-capability/reference/offense-examples.md +0 -32
  228. package/.agents/skills/pr-name-by-capability/reference/report-template.md +0 -29
  229. package/.agents/skills/pr-name-by-capability/reference/rule-checklist.md +0 -38
  230. package/.agents/skills/pr-name-by-capability/reference/task-seeds.md +0 -10
  231. package/.agents/skills/pr-plain-language-cleanup/SKILL.md +0 -93
  232. package/.agents/skills/pr-refinement/SKILL.md +0 -31
  233. package/.agents/skills/pr-shared-extraction/SKILL.md +0 -177
  234. package/.agents/skills/pr-shared-extraction/reference/examples.md +0 -46
  235. package/.agents/skills/pr-shared-extraction/reference/offense-taxonomy.md +0 -100
  236. package/.agents/skills/pr-small-cl/SKILL.md +0 -52
  237. package/.agents/skills/pr-title-description/SKILL.md +0 -96
  238. package/.agents/skills/prototype/SKILL.md +0 -85
  239. package/.agents/skills/prototype/reference/honest-limitations.md +0 -23
  240. package/.agents/skills/prototype/reference/promotion-tasks.md +0 -23
  241. package/.agents/skills/prototype/scripts/build_sandbox_settings.py +0 -249
  242. package/.agents/skills/prototype/scripts/conftest.py +0 -15
  243. package/.agents/skills/prototype/scripts/launch_sandbox.py +0 -205
  244. package/.agents/skills/prototype/scripts/probe_sandbox_safety.py +0 -311
  245. package/.agents/skills/prototype/scripts/prototype_scripts_constants/__init__.py +0 -1
  246. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
  247. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +0 -41
  248. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +0 -23
  249. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +0 -45
  250. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +0 -10
  251. package/.agents/skills/prototype/scripts/test_build_sandbox_settings.py +0 -275
  252. package/.agents/skills/prototype/scripts/test_launch_sandbox.py +0 -303
  253. package/.agents/skills/prototype/scripts/test_probe_sandbox_safety.py +0 -312
  254. package/.agents/skills/prototype/workflows/promotion.md +0 -25
  255. package/.agents/skills/prototype/workflows/sandbox.md +0 -35
  256. package/.agents/skills/rebase/.claude/CLAUDE.md +0 -1
  257. package/.agents/skills/rebase/AGENTS.md +0 -1
  258. package/.agents/skills/rebase/SKILL.md +0 -178
  259. package/.agents/skills/review-router/SKILL.md +0 -51
  260. package/.agents/skills/review-router/reference/route-policy.json +0 -7
  261. package/.agents/skills/review-router/reference/task-seeds.md +0 -1
  262. package/.agents/skills/review-router/scripts/conftest.py +0 -4
  263. package/.agents/skills/review-router/scripts/review_router.py +0 -50
  264. package/.agents/skills/review-router/scripts/review_router_cli.py +0 -211
  265. package/.agents/skills/review-router/scripts/review_router_constants/__init__.py +0 -1
  266. package/.agents/skills/review-router/scripts/review_router_constants/config/__init__.py +0 -0
  267. package/.agents/skills/review-router/scripts/review_router_constants/config/constants.py +0 -13
  268. package/.agents/skills/review-router/scripts/test_review_router.py +0 -23
  269. package/.agents/skills/review-router/scripts/test_review_router_cli.py +0 -47
  270. package/.agents/skills/review-tier/SKILL.md +0 -56
  271. package/.agents/skills/review-tier/reference/task-seeds.md +0 -1
  272. package/.agents/skills/review-tier/reference/tier-policy.json +0 -9
  273. package/.agents/skills/review-tier/scripts/conftest.py +0 -4
  274. package/.agents/skills/review-tier/scripts/review_tier.py +0 -359
  275. package/.agents/skills/review-tier/scripts/review_tier_constants/__init__.py +0 -1
  276. package/.agents/skills/review-tier/scripts/review_tier_constants/config/__init__.py +0 -0
  277. package/.agents/skills/review-tier/scripts/review_tier_constants/config/constants.py +0 -29
  278. package/.agents/skills/review-tier/scripts/test_generation.py +0 -159
  279. package/.agents/skills/review-tier/scripts/test_review_tier.py +0 -33
  280. package/.agents/skills/run-claude-dev-env/SKILL.md +0 -84
  281. package/.agents/skills/session-log/.claude/CLAUDE.md +0 -1
  282. package/.agents/skills/session-log/AGENTS.md +0 -1
  283. package/.agents/skills/session-log/SKILL.md +0 -170
  284. package/.agents/skills/session-log/templates/frontmatter.md +0 -40
  285. package/.agents/skills/session-tidy/.claude/CLAUDE.md +0 -1
  286. package/.agents/skills/session-tidy/AGENTS.md +0 -1
  287. package/.agents/skills/session-tidy/SKILL.md +0 -191
  288. package/.agents/skills/skill-builder/references/.claude/CLAUDE.md +0 -1
  289. package/.agents/skills/skill-builder/references/AGENTS.md +0 -1
  290. package/.agents/skills/skill-builder/references/delegation-map.md +0 -130
  291. package/.agents/skills/skill-builder/references/description-field.md +0 -111
  292. package/.agents/skills/skill-builder/references/deterministic-elements.md +0 -218
  293. package/.agents/skills/skill-builder/references/progressive-disclosure.md +0 -122
  294. package/.agents/skills/skill-builder/references/self-audit-checklist.md +0 -126
  295. package/.agents/skills/skill-builder/references/skill-modularity.md +0 -111
  296. package/.agents/skills/skill-builder/references/skill-types.md +0 -232
  297. package/.agents/skills/skill-builder/references/thariq-x-post-skills.json +0 -33
  298. package/.agents/skills/skill-builder/templates/.claude/CLAUDE.md +0 -1
  299. package/.agents/skills/skill-builder/templates/AGENTS.md +0 -1
  300. package/.agents/skills/skill-builder/templates/gap-analysis.md +0 -87
  301. package/.agents/skills/skill-builder/workflows/.claude/CLAUDE.md +0 -1
  302. package/.agents/skills/skill-builder/workflows/AGENTS.md +0 -1
  303. package/.agents/skills/skill-builder/workflows/improve-skill.md +0 -136
  304. package/.agents/skills/skill-builder/workflows/new-skill.md +0 -179
  305. package/.agents/skills/skill-builder/workflows/polish-skill.md +0 -139
  306. package/.agents/skills/source-command-sr-loop/SKILL.md +0 -82
  307. package/.agents/skills/update/.claude/CLAUDE.md +0 -1
  308. package/.agents/skills/update/AGENTS.md +0 -1
  309. package/.agents/skills/update/SKILL.md +0 -190
  310. package/_shared/pr-loop/AGENTS.md +0 -1
  311. package/_shared/pr-loop/CLAUDE.md +0 -1
  312. package/_shared/pr-loop/audit-contract.md +0 -183
  313. package/_shared/pr-loop/audit-reply-template.md +0 -151
  314. package/_shared/pr-loop/code-rules-gate.md +0 -65
  315. package/_shared/pr-loop/fix-protocol.md +0 -60
  316. package/_shared/pr-loop/gh-payloads.md +0 -75
  317. package/_shared/pr-loop/post-audit-thread-contract.md +0 -51
  318. package/_shared/pr-loop/precatch-rubric.md +0 -66
  319. package/_shared/pr-loop/preflight-proposal.md +0 -56
  320. package/_shared/pr-loop/scripts/post_audit_thread.py +0 -1242
  321. package/_shared/pr-loop/scripts/pr_loop_shared_constants/post_audit_thread_constants.py +0 -199
  322. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +0 -1115
  323. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +0 -129
  324. package/_shared/pr-loop/state-schema.md +0 -45
  325. package/_shared/pr-loop/worker-spawn.md +0 -189
  326. package/hooks/_gh_pr_author_swap_utils.py +0 -1225
  327. package/hooks/blocking/conventional_pr_title_gate.py +0 -443
  328. package/hooks/blocking/gh_body_arg_blocker.py +0 -176
  329. package/hooks/blocking/gh_pr_author_enforcer.py +0 -487
  330. package/hooks/blocking/gh_pr_author_restore.py +0 -105
  331. package/hooks/blocking/pr_description_writer_gate.py +0 -256
  332. package/hooks/blocking/test_conventional_pr_title_gate.py +0 -640
  333. package/hooks/blocking/test_gh_body_arg_blocker.py +0 -441
  334. package/hooks/blocking/test_gh_pr_author_enforcer.py +0 -1166
  335. package/hooks/blocking/test_gh_pr_author_restore.py +0 -513
  336. package/hooks/blocking/test_gh_pr_author_swap_utils.py +0 -910
  337. package/hooks/blocking/test_pr_description_writer_gate.py +0 -234
  338. package/hooks/blocking/test_preflight_check.py +0 -343
  339. package/hooks/blocking/test_test_preflight_check.py +0 -80
  340. package/hooks/blocking/test_volatile_path_in_post_blocker.py +0 -322
  341. package/hooks/blocking/volatile_path_in_post_blocker.py +0 -461
  342. package/hooks/hooks_constants/conventional_pr_title_gate_constants.py +0 -58
  343. package/hooks/hooks_constants/gh_pr_author_swap_constants.py +0 -76
  344. package/hooks/hooks_constants/pr_description_writer_gate_constants.py +0 -73
  345. package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +0 -55
  346. package/hooks/observability/pr_description_writer_spawn_tracker.py +0 -113
  347. package/hooks/observability/test_pr_description_writer_spawn_tracker.py +0 -150
  348. package/hooks/session/gh_pr_author_session_cleanup.py +0 -175
  349. package/hooks/session/test_gh_pr_author_session_cleanup.py +0 -576
  350. package/hooks/test__gh_pr_author_swap_utils.py +0 -333
@@ -1,461 +0,0 @@
1
- #!/usr/bin/env python3
2
- """PreToolUse hook: block durable GitHub posts that reference volatile paths.
3
-
4
- Root cause: a background job posted a PR comment citing an artifact under a job
5
- scratch directory. Job tmp dirs, worktrees, and system temp locations are
6
- ephemeral — they are cleaned soon after the run — while a posted comment, PR
7
- body, issue, or review lives forever. A durable post that points at ephemeral
8
- scratch breaks the moment that scratch is removed.
9
-
10
- Detection strategy: gather the post body text from the tool call, then scan it
11
- for volatile-path markers. Two tool families carry post bodies: shell ``gh``
12
- post subcommands (``pr create/comment/edit/review``, ``issue
13
- create/comment/edit``) and the GitHub MCP post tools (any
14
- ``mcp__plugin_github_github__*`` tool that carries a ``body`` or ``comment``
15
- parameter). For a ``gh`` command the body comes from ``--body``/``-b`` inline
16
- strings and from the file named by ``--body-file``/``-F`` (its contents are read
17
- and scanned, since that content is what gets embedded in the post). The ``gh``
18
- command is tokenized with ``shlex.split`` and the ``gh`` word must be the first
19
- command token, so a ``gh pr comment`` that only appears as quoted data inside
20
- another argument never classifies.
21
-
22
- The marker scan runs in two tiers over the normalized body. Five bare markers
23
- — the system temp locations and env tokens — match as a plain substring
24
- anywhere. Two dot-relative directory markers, ``.claude-profile-a/jobs/`` and
25
- ``.claude/worktrees/``, match when either a slash sits immediately before the
26
- marker or a path segment follows it. A machine-local absolute path
27
- (``~/.claude/worktrees/wt/f.py``) and a relative path that names a child
28
- (``see .claude/worktrees/wt-1/notes.md``) both count; a bare directory-name
29
- mention with neither anchor posts cleanly.
30
- """
31
-
32
- import json
33
- import shlex
34
- import sys
35
- from pathlib import Path
36
-
37
- _hooks_dir = str(Path(__file__).resolve().parent.parent)
38
- if _hooks_dir not in sys.path:
39
- sys.path.insert(0, _hooks_dir)
40
-
41
- from blocking._gh_body_arg_utils import ( # noqa: E402
42
- all_body_flags,
43
- body_file_flag,
44
- body_file_short_flag,
45
- count_extra_tokens_to_skip_for_split_quoted_value,
46
- get_logical_first_line,
47
- is_unresolvable_shell_value,
48
- match_body_file_equals_prefix,
49
- match_body_flag_equals_prefix,
50
- strip_surrounding_quotes,
51
- )
52
- from hooks_constants.hook_block_logger import log_hook_block # noqa: E402
53
- from hooks_constants.volatile_path_in_post_blocker_constants import ( # noqa: E402
54
- ALL_BARE_VOLATILE_PATH_MARKERS,
55
- ALL_GH_POST_SUBCOMMANDS,
56
- ALL_MCP_BODY_PARAM_NAMES,
57
- ALL_PATH_ANCHORED_VOLATILE_PATH_MARKERS,
58
- BASH_TOOL_NAME,
59
- BODY_FILE_ENCODING,
60
- CORRECTIVE_MESSAGE,
61
- GH_COMMAND_NAME,
62
- MCP_GITHUB_TOOL_PREFIX,
63
- MINIMUM_POST_SUBCOMMAND_TOKEN_COUNT,
64
- PATH_ANCHOR_CHARACTER,
65
- PATH_SEGMENT_START_CHARACTERS,
66
- TOKEN_JOIN_SEPARATOR,
67
- )
68
-
69
-
70
- def _character_starts_path_segment(character: str) -> bool:
71
- """Return whether a character can open a path segment after a marker."""
72
- return bool(character) and (
73
- character.isalnum() or character in PATH_SEGMENT_START_CHARACTERS
74
- )
75
-
76
-
77
- def _text_has_anchored_marker(normalized_text: str, marker: str) -> bool:
78
- """Return whether the marker appears as part of a path.
79
-
80
- A match counts when either a slash sits immediately before the marker or a
81
- path segment follows it. Every occurrence is scanned, so a bare directory
82
- mention earlier in the text never masks a path occurrence later::
83
-
84
- ok: ".claude/worktrees/ is the manifest key" -> False
85
- flag: "path c:/users/me/.claude/worktrees/wt/f.py" -> True
86
- flag: "see .claude/worktrees/wt-1/notes.md" -> True
87
-
88
- Args:
89
- normalized_text: The body text with backslashes folded to forward
90
- slashes and lowercased.
91
- marker: A dot-relative directory marker to search for.
92
-
93
- Returns:
94
- True when at least one occurrence is path-anchored.
95
- """
96
- search_start = 0
97
- while True:
98
- found_index = normalized_text.find(marker, search_start)
99
- if found_index < 0:
100
- return False
101
- preceding_character = normalized_text[found_index - 1 : found_index]
102
- following_index = found_index + len(marker)
103
- following_character = normalized_text[following_index : following_index + 1]
104
- is_slash_before = preceding_character == PATH_ANCHOR_CHARACTER
105
- is_segment_after = _character_starts_path_segment(following_character)
106
- if is_slash_before or is_segment_after:
107
- return True
108
- search_start = found_index + 1
109
-
110
-
111
- def scan_text_for_volatile_marker(text: str) -> str | None:
112
- """Return the first volatile-path marker found in text, or None.
113
-
114
- Backslashes normalize to forward slashes and the text lowercases before the
115
- scan. The two dot-relative directory markers count when either a slash sits
116
- immediately before them or a path segment follows them; the five bare
117
- markers match anywhere::
118
-
119
- ok: "the `.claude/worktrees/` manifest key" -> None
120
- flag: r"C:\\Users\\me\\.claude-profile-a\\jobs\\x" -> ".claude-profile-a/jobs/"
121
- flag: "see .claude/worktrees/wt-1/notes.md" -> ".claude/worktrees/"
122
- flag: "saved to %TEMP%\\out.txt" -> "%temp%"
123
-
124
- A dot-relative marker with neither a slash before it nor a path segment
125
- after it — start of text naming the directory, a quoted config constant,
126
- or a placeholder form such as ``.claude/worktrees/<name>`` — posts cleanly.
127
-
128
- Args:
129
- text: The post body text to scan.
130
-
131
- Returns:
132
- The matched marker string, or None when the text names no volatile path.
133
- """
134
- normalized_text = text.replace("\\", PATH_ANCHOR_CHARACTER).lower()
135
- for each_marker in ALL_PATH_ANCHORED_VOLATILE_PATH_MARKERS:
136
- if _text_has_anchored_marker(normalized_text, each_marker):
137
- return each_marker
138
- for each_marker in ALL_BARE_VOLATILE_PATH_MARKERS:
139
- if each_marker in normalized_text:
140
- return each_marker
141
- return None
142
-
143
-
144
- def _is_environment_assignment(token: str) -> bool:
145
- """Return whether a token is a ``NAME=value`` shell environment assignment."""
146
- equals_index = token.find("=")
147
- if equals_index <= 0:
148
- return False
149
- name_part = token[:equals_index]
150
- if not (name_part[0].isalpha() or name_part[0] == "_"):
151
- return False
152
- return all(
153
- each_character.isalnum() or each_character == "_"
154
- for each_character in name_part
155
- )
156
-
157
-
158
- def _tokens_name_gh_post_command(all_command_tokens: list[str]) -> bool:
159
- """Return whether the tokens start with an affected ``gh`` post subcommand.
160
-
161
- Leading ``NAME=value`` env assignments are skipped, then the next token must
162
- be the literal ``gh`` command word followed by a post noun and verb. Anchoring
163
- to the first command word keeps a ``gh pr comment`` mentioned only as quoted
164
- argument data from classifying.
165
-
166
- Args:
167
- all_command_tokens: The shlex-split tokens of the logical command line.
168
-
169
- Returns:
170
- True when the command invokes an affected ``gh`` post subcommand.
171
- """
172
- command_index = 0
173
- while command_index < len(all_command_tokens) and _is_environment_assignment(
174
- all_command_tokens[command_index]
175
- ):
176
- command_index += 1
177
- if command_index >= len(all_command_tokens):
178
- return False
179
- if all_command_tokens[command_index] != GH_COMMAND_NAME:
180
- return False
181
- remaining_tokens = all_command_tokens[command_index + 1 :]
182
- if len(remaining_tokens) < MINIMUM_POST_SUBCOMMAND_TOKEN_COUNT:
183
- return False
184
- post_noun = remaining_tokens[0]
185
- post_verb = remaining_tokens[1]
186
- return post_verb in ALL_GH_POST_SUBCOMMANDS.get(post_noun, frozenset())
187
-
188
-
189
- def _reassemble_split_quoted_value(
190
- value_token: str, all_following_tokens: list[str]
191
- ) -> tuple[str, int]:
192
- """Rejoin a quoted value that ``shlex.split(posix=False)`` split on spaces.
193
-
194
- ``posix=False`` keeps backslashes intact (so Windows paths survive) but
195
- splits a quoted value such as ``"dump in %TEMP%"`` into three tokens. This
196
- rejoins the value token with the follow-on tokens up to the closing quote,
197
- then strips the surrounding quotes so the full body text can be scanned.
198
-
199
- Args:
200
- value_token: The first token of the value, following the flag.
201
- all_following_tokens: The tokens after ``value_token`` on the line.
202
-
203
- Returns:
204
- A ``(full_value, extra_tokens_consumed)`` pair; ``extra_tokens_consumed``
205
- counts the follow-on tokens joined into the value.
206
- """
207
- extra_tokens = count_extra_tokens_to_skip_for_split_quoted_value(
208
- all_following_tokens, value_token
209
- )
210
- if not extra_tokens:
211
- return strip_surrounding_quotes(value_token), 0
212
- joined_value = value_token + TOKEN_JOIN_SEPARATOR + TOKEN_JOIN_SEPARATOR.join(
213
- all_following_tokens[:extra_tokens]
214
- )
215
- return strip_surrounding_quotes(joined_value), extra_tokens
216
-
217
-
218
- def _extract_flag_value_at(
219
- all_command_tokens: list[str], token_index: int
220
- ) -> tuple[bool, str, int] | None:
221
- """Return a body/body-file value starting at token_index, or None.
222
-
223
- Args:
224
- all_command_tokens: The shlex-split tokens of the logical command line.
225
- token_index: The index of the token to inspect.
226
-
227
- Returns:
228
- An ``(is_body_file, value, next_index)`` triple when the token opens a
229
- body or body-file flag, else None. ``next_index`` is where scanning
230
- resumes after the value's tokens.
231
- """
232
- current_token = all_command_tokens[token_index]
233
- following_tokens = all_command_tokens[token_index + 1:]
234
- body_equals_prefix = match_body_flag_equals_prefix(current_token)
235
- if body_equals_prefix is not None:
236
- value, extra = _reassemble_split_quoted_value(
237
- current_token[len(body_equals_prefix):], following_tokens
238
- )
239
- return False, value, token_index + 1 + extra
240
- body_file_equals_prefix = match_body_file_equals_prefix(current_token)
241
- if body_file_equals_prefix is not None:
242
- value, extra = _reassemble_split_quoted_value(
243
- current_token[len(body_file_equals_prefix):], following_tokens
244
- )
245
- return True, value, token_index + 1 + extra
246
- if current_token in all_body_flags and following_tokens:
247
- value, extra = _reassemble_split_quoted_value(following_tokens[0], following_tokens[1:])
248
- return False, value, token_index + 2 + extra
249
- if current_token in (body_file_flag, body_file_short_flag) and following_tokens:
250
- value, extra = _reassemble_split_quoted_value(following_tokens[0], following_tokens[1:])
251
- return True, value, token_index + 2 + extra
252
- return None
253
-
254
-
255
- def _collect_body_flag_values(
256
- all_command_tokens: list[str],
257
- ) -> tuple[list[str], list[str]]:
258
- """Return inline body strings and body-file paths from the command tokens.
259
-
260
- Args:
261
- all_command_tokens: The shlex-split tokens of the logical command line.
262
-
263
- Returns:
264
- A ``(all_inline_bodies, all_body_file_paths)`` pair, each value stripped
265
- of its surrounding quotes and rejoined across split-quote tokens.
266
- """
267
- all_inline_bodies: list[str] = []
268
- all_body_file_paths: list[str] = []
269
- token_index = 0
270
- while token_index < len(all_command_tokens):
271
- extraction = _extract_flag_value_at(all_command_tokens, token_index)
272
- if extraction is None:
273
- token_index += 1
274
- continue
275
- is_body_file, value, next_index = extraction
276
- if is_body_file:
277
- all_body_file_paths.append(value)
278
- else:
279
- all_inline_bodies.append(value)
280
- token_index = next_index
281
- return all_inline_bodies, all_body_file_paths
282
-
283
-
284
- def _read_body_file(
285
- body_file_path: str, working_directory: str | None = None
286
- ) -> str | None:
287
- """Return the contents of a body-file path, or None when it cannot be read.
288
-
289
- Args:
290
- body_file_path: The path given to ``--body-file``/``-F``.
291
- working_directory: Optional base directory for relative body-file paths.
292
-
293
- Returns:
294
- The file text, or None for an unresolvable shell value (such as ``-`` for
295
- stdin or a ``$VAR`` path) or an unreadable file.
296
- """
297
- if is_unresolvable_shell_value(body_file_path):
298
- return None
299
- resolved_path = Path(body_file_path)
300
- if not resolved_path.is_absolute() and working_directory:
301
- resolved_path = Path(working_directory) / body_file_path
302
- try:
303
- return resolved_path.read_text(encoding=BODY_FILE_ENCODING)
304
- except OSError:
305
- return None
306
-
307
-
308
- def extract_gh_post_body_texts(
309
- command: str, working_directory: str | None = None
310
- ) -> list[str]:
311
- """Return every post body text an affected ``gh`` command would send.
312
-
313
- Non-post ``gh`` commands and unparseable command lines yield an empty list.
314
- Body-file contents are read from disk so the embedded text is scanned rather
315
- than the file path. Relative body-file paths resolve against
316
- *working_directory* when provided.
317
-
318
- Args:
319
- command: The raw Bash tool command string.
320
- working_directory: Optional base directory for relative body-file paths.
321
-
322
- Returns:
323
- Inline ``--body`` strings plus the contents of each readable body-file.
324
- """
325
- logical_line = get_logical_first_line(command)
326
- if not logical_line:
327
- return []
328
- try:
329
- all_command_tokens = shlex.split(logical_line, posix=False)
330
- except ValueError:
331
- return []
332
- if not _tokens_name_gh_post_command(all_command_tokens):
333
- return []
334
- all_inline_bodies, all_body_file_paths = _collect_body_flag_values(
335
- all_command_tokens
336
- )
337
- all_body_texts = list(all_inline_bodies)
338
- for each_path in all_body_file_paths:
339
- file_text = _read_body_file(each_path, working_directory=working_directory)
340
- if file_text is not None:
341
- all_body_texts.append(file_text)
342
- return all_body_texts
343
-
344
-
345
- def extract_gh_post_body_texts_for_privacy_gate(
346
- command: str, working_directory: str | None = None
347
- ) -> tuple[list[str], str | None]:
348
- """Like ``extract_gh_post_body_texts``, but fail-closed on unreadable body-files.
349
-
350
- Args:
351
- command: The raw Bash tool command string.
352
- working_directory: Optional base directory for relative body-file paths.
353
-
354
- Returns:
355
- ``(all_body_texts, None)`` when every declared body-file was read (or
356
- none were declared), else ``([], deny_reason)`` when a body-file flag is
357
- present but its contents could not be loaded for scanning.
358
- """
359
- logical_line = get_logical_first_line(command)
360
- if not logical_line:
361
- return [], None
362
- try:
363
- all_command_tokens = shlex.split(logical_line, posix=False)
364
- except ValueError:
365
- return [], None
366
- if not _tokens_name_gh_post_command(all_command_tokens):
367
- return [], None
368
- all_inline_bodies, all_body_file_paths = _collect_body_flag_values(
369
- all_command_tokens
370
- )
371
- all_body_texts = list(all_inline_bodies)
372
- for each_path in all_body_file_paths:
373
- file_text = _read_body_file(each_path, working_directory=working_directory)
374
- if file_text is None:
375
- return [], (
376
- "BLOCKED [pii_prevention_blocker]: durable post uses --body-file "
377
- f"but '{each_path}' could not be read for PII scanning. Use an "
378
- "absolute path, ensure the file exists, or pass --body text."
379
- )
380
- all_body_texts.append(file_text)
381
- return all_body_texts, None
382
-
383
-
384
- def extract_mcp_body_texts(all_tool_input: dict[str, object]) -> list[str]:
385
- """Return the body and comment strings from a GitHub MCP post tool input.
386
-
387
- Args:
388
- all_tool_input: The MCP tool's input mapping.
389
-
390
- Returns:
391
- The non-empty string values of the ``body`` and ``comment`` parameters.
392
- """
393
- all_body_texts: list[str] = []
394
- for each_param_name in ALL_MCP_BODY_PARAM_NAMES:
395
- param_value = all_tool_input.get(each_param_name)
396
- if isinstance(param_value, str) and param_value:
397
- all_body_texts.append(param_value)
398
- return all_body_texts
399
-
400
-
401
- def _collect_body_texts(tool_name: str, all_tool_input: dict[str, object]) -> list[str]:
402
- if tool_name == BASH_TOOL_NAME:
403
- command = all_tool_input.get("command", "")
404
- if not isinstance(command, str) or not command:
405
- return []
406
- return extract_gh_post_body_texts(command)
407
- if tool_name.startswith(MCP_GITHUB_TOOL_PREFIX):
408
- return extract_mcp_body_texts(all_tool_input)
409
- return []
410
-
411
-
412
- def _first_volatile_marker(all_body_texts: list[str]) -> str | None:
413
- for each_text in all_body_texts:
414
- matched_marker = scan_text_for_volatile_marker(each_text)
415
- if matched_marker is not None:
416
- return matched_marker
417
- return None
418
-
419
-
420
- def _emit_block(tool_name: str, matched_marker: str) -> None:
421
- deny_payload = {
422
- "hookSpecificOutput": {
423
- "hookEventName": "PreToolUse",
424
- "permissionDecision": "deny",
425
- "permissionDecisionReason": CORRECTIVE_MESSAGE,
426
- }
427
- }
428
- log_hook_block(
429
- calling_hook_name="volatile_path_in_post_blocker.py",
430
- hook_event="PreToolUse",
431
- block_reason=CORRECTIVE_MESSAGE,
432
- tool_name=tool_name,
433
- offending_input_preview=matched_marker,
434
- )
435
- print(json.dumps(deny_payload))
436
- sys.stdout.flush()
437
-
438
-
439
- def main() -> None:
440
- """Read the PreToolUse payload and block a post that names a volatile path."""
441
- try:
442
- hook_input = json.load(sys.stdin)
443
- except json.JSONDecodeError:
444
- sys.exit(0)
445
-
446
- tool_name = hook_input.get("tool_name", "")
447
- tool_input = hook_input.get("tool_input", {})
448
- if not isinstance(tool_name, str) or not isinstance(tool_input, dict):
449
- sys.exit(0)
450
-
451
- all_body_texts = _collect_body_texts(tool_name, tool_input)
452
- matched_marker = _first_volatile_marker(all_body_texts)
453
- if matched_marker is None:
454
- sys.exit(0)
455
-
456
- _emit_block(tool_name, matched_marker)
457
- sys.exit(0)
458
-
459
-
460
- if __name__ == "__main__":
461
- main()
@@ -1,58 +0,0 @@
1
- """Configuration constants for the conventional_pr_title_gate PreToolUse hook."""
2
-
3
- import re
4
-
5
- ALL_GH_EXECUTABLE_BASENAMES: frozenset[str] = frozenset({"gh", "gh.exe"})
6
- PR_SUBCOMMAND_TOKEN: str = "pr"
7
- ALL_PR_TITLE_SUBCOMMAND_VERBS: frozenset[str] = frozenset({"create", "edit"})
8
- GH_PR_SUBCOMMAND_MINIMUM_TOKEN_COUNT: int = 3
9
-
10
- BASH_TOOL_NAME: str = "Bash"
11
-
12
- TITLE_LONG_FLAG: str = "--title"
13
- TITLE_SHORT_FLAG: str = "-t"
14
- REPO_LONG_FLAG: str = "--repo"
15
- REPO_SHORT_FLAG: str = "-R"
16
-
17
- WORKFLOWS_DIRECTORY_RELATIVE_PATH: str = ".github/workflows"
18
- ALL_WORKFLOW_FILE_GLOB_PATTERNS: tuple[str, ...] = ("*.yml", "*.yaml")
19
-
20
- ALL_SEMANTIC_TITLE_CI_MARKERS: tuple[str, ...] = (
21
- "semantic-pull-request",
22
- "action-semantic-pull-request",
23
- "semantic_pull_request",
24
- )
25
-
26
- ALL_CONVENTIONAL_COMMIT_TYPES: tuple[str, ...] = (
27
- "feat",
28
- "fix",
29
- "chore",
30
- "docs",
31
- "refactor",
32
- "perf",
33
- "ci",
34
- "style",
35
- "test",
36
- "build",
37
- "revert",
38
- )
39
-
40
- CONVENTIONAL_COMMIT_TITLE_PATTERN: re.Pattern[str] = re.compile(
41
- r"^(?:" + "|".join(ALL_CONVENTIONAL_COMMIT_TYPES) + r")(?:\([^)]+\))?!?: .+"
42
- )
43
-
44
- SEMANTIC_ACTION_TYPES_INPUT_PATTERN: re.Pattern[str] = re.compile(r"^\s*types\s*:")
45
-
46
- SEMANTIC_ACTION_FLOW_TYPES_INPUT_PATTERN: re.Pattern[str] = re.compile(r"[{,]\s*types\s*:")
47
-
48
- YAML_LIST_ITEM_PREFIX: str = "- "
49
-
50
- CORRECTIVE_MESSAGE: str = (
51
- "BLOCKED [conventional-pr-title]: this repository's CI validates PR titles "
52
- "against Conventional Commits, and the --title value here does not match. "
53
- "Required shape: type(scope)!: description, where the scope and the "
54
- "breaking-change marker (!) are optional. Allowed types: "
55
- f"{', '.join(ALL_CONVENTIONAL_COMMIT_TYPES)}.\n\n"
56
- "Example: feat(hooks): add the conventional PR title gate\n\n"
57
- "Fix the --title value and retry."
58
- )
@@ -1,76 +0,0 @@
1
- """Configuration constants for the gh-pr-author swap hook trio.
2
-
3
- The PreToolUse enforcer (``gh_pr_author_enforcer.py``) auto-switches the
4
- active ``gh`` CLI account to ``GITHUB_DEFAULT_ACCOUNT`` before a
5
- ``gh pr create`` invocation, the PostToolUse companion
6
- (``gh_pr_author_restore.py``) restores the prior account afterwards, and
7
- the SessionStart cleanup hook (``gh_pr_author_session_cleanup.py``)
8
- sweeps any stale state files left behind when a prior session was
9
- interrupted between the swap and the restore. The state file written
10
- between the hooks is keyed per session so parallel Claude Code sessions
11
- cannot stomp on each other's swap state.
12
- """
13
-
14
- from __future__ import annotations
15
-
16
- import re
17
-
18
- REQUIRED_ACCOUNT_ENV_VAR: str = "GITHUB_DEFAULT_ACCOUNT"
19
-
20
- BASH_TOOL_NAME: str = "Bash"
21
-
22
- GH_PR_CREATE_PATTERN: re.Pattern[str] = re.compile(
23
- r"(?:^|[;&|\n`({]|\$\()[ \t]*"
24
- r"(?:(?:if|then|else|elif|while|until|do|!)[ \t]+)*"
25
- r"(?:[A-Za-z_][A-Za-z0-9_]*=\S+[ \t]+)*"
26
- r"gh(?:[ \t]+(?:--[A-Za-z][\w-]*(?:=\S+)?|-[A-Za-z])(?:[ \t]+(?!-)\S+)?)*"
27
- r"[ \t]+pr[ \t]+create\b",
28
- re.IGNORECASE,
29
- )
30
- WEB_FLAG_PATTERN: re.Pattern[str] = re.compile(r"(?<!\S)(?:--web|-w)(?!\S)")
31
- COMMAND_SEPARATOR_PATTERN: re.Pattern[str] = re.compile(
32
- r"(?:&&|\|\||;|(?<!\|)\|(?!\|)|(?<!&)&(?!&)|[\r\n])"
33
- )
34
- BASH_COMMENT_INTRODUCER_CHARACTER: str = "#"
35
- COMMAND_SUBSTITUTION_OPENER_LENGTH: int = 2
36
-
37
- ALL_GH_API_USER_COMMAND: tuple[str, ...] = ("gh", "api", "user", "--jq", ".login")
38
- GH_API_USER_TIMEOUT_SECONDS: int = 5
39
-
40
- ALL_GH_AUTH_SWITCH_COMMAND_HEAD: tuple[str, ...] = ("gh", "auth", "switch", "--user")
41
- GH_AUTH_SWITCH_TIMEOUT_SECONDS: int = 10
42
-
43
- STATE_FILE_PREFIX: str = "gh_pr_author_swap_"
44
- STATE_FILE_SUFFIX: str = ".json"
45
- STATE_FILE_DEFAULT_SESSION_ID: str = "default"
46
-
47
- SESSION_ID_UNSAFE_CHARACTERS_PATTERN: re.Pattern[str] = re.compile(r"[^A-Za-z0-9_-]")
48
-
49
- STATE_FILE_ORIGINAL_ACCOUNT_KEY: str = "original_account"
50
- STATE_FILE_PRIMARY_ACCOUNT_KEY: str = "primary_account"
51
-
52
- STATE_FILE_PERMISSION_MODE: int = 0o600
53
-
54
- STATE_FILE_PAYLOAD_TEXT_ENCODING_NAME: str = "utf-8"
55
-
56
- OS_O_NOFOLLOW_ATTRIBUTE_NAME: str = "O_NOFOLLOW"
57
-
58
- STATE_FILE_STALE_AGE_SECONDS: int = 1800
59
-
60
- ALL_SHELL_QUOTE_CHARACTERS: tuple[str, ...] = ("\"", "'")
61
- SHELL_QUOTE_REPLACEMENT_CHARACTER: str = " "
62
- SHELL_BACKSLASH_ESCAPE_PAIR_LENGTH: int = 2
63
- SHELL_BACKTICK_CHARACTER: str = "`"
64
- SHELL_DOLLAR_CHARACTER: str = "$"
65
- SHELL_PAREN_OPEN_CHARACTER: str = "("
66
- SHELL_PAREN_CLOSE_CHARACTER: str = ")"
67
- SHELL_LESS_THAN_CHARACTER: str = "<"
68
- SHELL_BACKSLASH_CHARACTER: str = "\\"
69
- SHELL_NEWLINE_CHARACTER: str = "\n"
70
-
71
- HEREDOC_OPENER_TAG_PATTERN: re.Pattern[str] = re.compile(
72
- r"[ \t]*(?P<dash>-?)[ \t]*(?:'(?P<sq_tag>[A-Za-z_][A-Za-z0-9_]*)'"
73
- r"|\"(?P<dq_tag>[A-Za-z_][A-Za-z0-9_]*)\""
74
- r"|(?P<bare_tag>[A-Za-z_][A-Za-z0-9_]*))"
75
- )
76
- HEREDOC_OPENER_TOKEN_LENGTH: int = 2
@@ -1,73 +0,0 @@
1
- """Constants for the pr-description-writer spawn tracker and its gh pr create gate.
2
-
3
- The tracker records one fact per session: the session spawned the
4
- ``pr-description-writer`` agent. The gate reads that fact when the session runs
5
- ``gh pr create`` and denies the command when the fact is absent.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import tempfile
11
- from pathlib import Path
12
-
13
- from hooks_constants.session_edit_stage_gate_constants import (
14
- SESSION_ID_UNSAFE_CHARACTERS_PATTERN,
15
- )
16
-
17
- PR_DESCRIPTION_WRITER_SUBAGENT_TYPE: str = "pr-description-writer"
18
-
19
- ALL_AGENT_SPAWN_TOOL_NAMES: frozenset[str] = frozenset({"Agent", "Task"})
20
-
21
- SPAWN_MARKER_FILE_PREFIX: str = "claude-session-pr-description-writer-"
22
- SPAWN_MARKER_FILE_SUFFIX: str = ".marker"
23
-
24
- SUBAGENT_TYPE_FIELD_NAME: str = "subagent_type"
25
- TOOL_INPUT_FIELD_NAME: str = "tool_input"
26
- TOOL_NAME_FIELD_NAME: str = "tool_name"
27
- COMMAND_FIELD_NAME: str = "command"
28
-
29
- ALL_GH_EXECUTABLE_BASENAMES: frozenset[str] = frozenset({"gh", "gh.exe", "gh.cmd"})
30
- PR_SUBCOMMAND_TOKEN: str = "pr"
31
- CREATE_SUBCOMMAND_TOKEN: str = "create"
32
- GH_PR_CREATE_MINIMUM_TOKEN_COUNT: int = 3
33
-
34
- SPAWN_BYPASS_MARKER: str = "# pr-description-skip"
35
-
36
- CORRECTIVE_MESSAGE: str = (
37
- "BLOCKED: [PR_DESCRIPTION_WRITER] This session runs `gh pr create` with no "
38
- "`pr-description-writer` agent spawn recorded.\n\n"
39
- "The agent writes the title and body from the current diff, then returns the "
40
- "markdown and a body-file path for you to publish.\n\n"
41
- "Two ways forward:\n"
42
- " 1. Spawn the agent (Agent tool, subagent_type `pr-description-writer`), "
43
- "then run `gh pr create --body-file <path>` with what it returns.\n"
44
- " 2. Append the trailing comment `# pr-description-skip` to the command when "
45
- "you are writing the body yourself on purpose."
46
- )
47
-
48
-
49
- def spawn_marker_path(session_id: str, temp_directory: Path | None = None) -> Path | None:
50
- """Return this session's marker file path, or None when it names no session.
51
-
52
- The tracker writes this path and the gate reads it, so the file name lives
53
- in one place. A session id that sanitizes to nothing yields no path, so
54
- every marker on disk belongs to one real session and a marker a crashed
55
- session leaves behind is inert.
56
-
57
- Args:
58
- session_id: Raw ``session_id`` from the hook payload.
59
- temp_directory: The directory holding the marker. Defaults to the
60
- system temp directory.
61
-
62
- Returns:
63
- Absolute path to this session's marker file, or None when the payload
64
- names no session this hook can key on.
65
- """
66
- sanitized_session_id = SESSION_ID_UNSAFE_CHARACTERS_PATTERN.sub("", session_id)
67
- if not sanitized_session_id:
68
- return None
69
- file_name = (
70
- f"{SPAWN_MARKER_FILE_PREFIX}{sanitized_session_id}{SPAWN_MARKER_FILE_SUFFIX}"
71
- )
72
- resolved_temp_directory = temp_directory or Path(tempfile.gettempdir())
73
- return resolved_temp_directory / file_name