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,513 +0,0 @@
1
- """Unit tests for gh-pr-author-restore PostToolUse hook."""
2
-
3
- from __future__ import annotations
4
-
5
- import importlib.util
6
- import io
7
- import json
8
- import os
9
- import pathlib
10
- import stat
11
- import sys
12
- from typing import Iterator
13
- from unittest import mock
14
-
15
- import pytest
16
-
17
- from hooks_constants.gh_pr_author_swap_constants import STATE_FILE_PERMISSION_MODE
18
-
19
- _HOOK_DIR = pathlib.Path(__file__).parent
20
- if str(_HOOK_DIR) not in sys.path:
21
- sys.path.insert(0, str(_HOOK_DIR))
22
-
23
- hook_module_spec = importlib.util.spec_from_file_location(
24
- "gh_pr_author_restore",
25
- _HOOK_DIR / "gh_pr_author_restore.py",
26
- )
27
- assert hook_module_spec is not None
28
- assert hook_module_spec.loader is not None
29
- hook_module = importlib.util.module_from_spec(hook_module_spec)
30
- hook_module_spec.loader.exec_module(hook_module)
31
-
32
- import _gh_pr_author_swap_utils as swap_utils_module # noqa: E402
33
-
34
-
35
- def _make_stdin_payload(
36
- command: str,
37
- session_id: str = "test-session-001",
38
- tool_name: str = "Bash",
39
- ) -> str:
40
- return json.dumps(
41
- {
42
- "tool_name": tool_name,
43
- "tool_input": {"command": command},
44
- "session_id": session_id,
45
- }
46
- )
47
-
48
-
49
- def _write_state_file(state_file: pathlib.Path, original_account: str) -> None:
50
- state_file.write_text(
51
- json.dumps(
52
- {
53
- "original_account": original_account,
54
- "primary_account": "JonEcho",
55
- }
56
- ),
57
- encoding="utf-8",
58
- )
59
-
60
-
61
- @pytest.fixture
62
- def isolated_state_directory(
63
- monkeypatch: pytest.MonkeyPatch,
64
- tmp_path: pathlib.Path,
65
- ) -> Iterator[pathlib.Path]:
66
- monkeypatch.setattr(swap_utils_module.tempfile, "gettempdir", lambda: str(tmp_path))
67
- yield tmp_path
68
-
69
-
70
- def _run_hook_with(
71
- stdin_text: str,
72
- monkeypatch: pytest.MonkeyPatch,
73
- switch_succeeds: bool,
74
- ) -> tuple[int, str, list[str]]:
75
- monkeypatch.setattr(sys, "stdin", io.StringIO(stdin_text))
76
- captured_stdout = io.StringIO()
77
- monkeypatch.setattr(sys, "stdout", captured_stdout)
78
- switch_invocations: list[str] = []
79
-
80
- def _fake_switch(to_account: str) -> bool:
81
- switch_invocations.append(to_account)
82
- return switch_succeeds
83
-
84
- monkeypatch.setattr(hook_module, "_switch_gh_account", _fake_switch)
85
- with pytest.raises(SystemExit) as exit_info:
86
- hook_module.main()
87
- exit_code = exit_info.value.code if isinstance(exit_info.value.code, int) else 0
88
- return exit_code, captured_stdout.getvalue(), switch_invocations
89
-
90
-
91
- def test_command_invokes_gh_pr_create_matches_basic_form() -> None:
92
- assert hook_module._command_invokes_gh_pr_create_in_stripped(
93
- hook_module._preprocess_command_for_matching("gh pr create --title T")
94
- )
95
-
96
-
97
- def test_command_invokes_gh_pr_create_rejects_pr_edit() -> None:
98
- assert not hook_module._command_invokes_gh_pr_create_in_stripped(
99
- hook_module._preprocess_command_for_matching("gh pr edit 10")
100
- )
101
-
102
-
103
- def test_state_file_path_uses_session_id(
104
- isolated_state_directory: pathlib.Path,
105
- ) -> None:
106
- state_file = hook_module._state_file_path("abc-123")
107
- assert state_file.parent == isolated_state_directory
108
- assert state_file.name == "gh_pr_author_swap_abc-123.json"
109
-
110
-
111
- def test_state_file_path_falls_back_to_default_when_session_id_empty(
112
- isolated_state_directory: pathlib.Path,
113
- ) -> None:
114
- state_file = hook_module._state_file_path("")
115
- assert state_file.name == "gh_pr_author_swap_default.json"
116
-
117
-
118
- def test_main_no_op_when_tool_name_not_bash(
119
- monkeypatch: pytest.MonkeyPatch,
120
- isolated_state_directory: pathlib.Path,
121
- ) -> None:
122
- state_file = hook_module._state_file_path("test-session-001")
123
- _write_state_file(state_file, original_account="jl-cmd")
124
-
125
- exit_code, stdout_text, switch_invocations = _run_hook_with(
126
- _make_stdin_payload("gh pr create --title T", tool_name="Write"),
127
- monkeypatch=monkeypatch,
128
- switch_succeeds=True,
129
- )
130
- assert exit_code == 0
131
- assert stdout_text == ""
132
- assert switch_invocations == []
133
- assert state_file.exists()
134
-
135
-
136
- def test_main_no_op_when_command_does_not_match_pr_create(
137
- monkeypatch: pytest.MonkeyPatch,
138
- isolated_state_directory: pathlib.Path,
139
- ) -> None:
140
- state_file = hook_module._state_file_path("test-session-001")
141
- _write_state_file(state_file, original_account="jl-cmd")
142
-
143
- exit_code, stdout_text, switch_invocations = _run_hook_with(
144
- _make_stdin_payload("git status"),
145
- monkeypatch=monkeypatch,
146
- switch_succeeds=True,
147
- )
148
- assert exit_code == 0
149
- assert stdout_text == ""
150
- assert switch_invocations == []
151
- assert state_file.exists()
152
-
153
-
154
- def test_main_no_op_when_state_file_absent(
155
- monkeypatch: pytest.MonkeyPatch,
156
- isolated_state_directory: pathlib.Path,
157
- ) -> None:
158
- exit_code, stdout_text, switch_invocations = _run_hook_with(
159
- _make_stdin_payload("gh pr create --title T"),
160
- monkeypatch=monkeypatch,
161
- switch_succeeds=True,
162
- )
163
- assert exit_code == 0
164
- assert stdout_text == ""
165
- assert switch_invocations == []
166
-
167
-
168
- def test_main_switches_back_and_deletes_state_file(
169
- monkeypatch: pytest.MonkeyPatch,
170
- isolated_state_directory: pathlib.Path,
171
- ) -> None:
172
- state_file = hook_module._state_file_path("test-session-001")
173
- _write_state_file(state_file, original_account="jl-cmd")
174
-
175
- exit_code, stdout_text, switch_invocations = _run_hook_with(
176
- _make_stdin_payload("gh pr create --title T"),
177
- monkeypatch=monkeypatch,
178
- switch_succeeds=True,
179
- )
180
- assert exit_code == 0
181
- assert stdout_text == ""
182
- assert switch_invocations == ["jl-cmd"]
183
- assert not state_file.exists()
184
-
185
-
186
- def test_main_preserves_state_file_when_switch_fails(
187
- monkeypatch: pytest.MonkeyPatch,
188
- isolated_state_directory: pathlib.Path,
189
- ) -> None:
190
- state_file = hook_module._state_file_path("test-session-001")
191
- _write_state_file(state_file, original_account="jl-cmd")
192
-
193
- exit_code, stdout_text, switch_invocations = _run_hook_with(
194
- _make_stdin_payload("gh pr create --title T"),
195
- monkeypatch=monkeypatch,
196
- switch_succeeds=False,
197
- )
198
- assert exit_code == 0
199
- assert stdout_text == ""
200
- assert switch_invocations == ["jl-cmd"]
201
- assert state_file.exists()
202
-
203
-
204
- def test_main_no_op_on_invalid_stdin_json(
205
- monkeypatch: pytest.MonkeyPatch,
206
- isolated_state_directory: pathlib.Path,
207
- ) -> None:
208
- exit_code, stdout_text, switch_invocations = _run_hook_with(
209
- "not-json",
210
- monkeypatch=monkeypatch,
211
- switch_succeeds=True,
212
- )
213
- assert exit_code == 0
214
- assert stdout_text == ""
215
- assert switch_invocations == []
216
-
217
-
218
- def test_main_no_op_on_malformed_state_file(
219
- monkeypatch: pytest.MonkeyPatch,
220
- isolated_state_directory: pathlib.Path,
221
- ) -> None:
222
- state_file = hook_module._state_file_path("test-session-001")
223
- state_file.write_text("{not valid json", encoding="utf-8")
224
-
225
- exit_code, stdout_text, switch_invocations = _run_hook_with(
226
- _make_stdin_payload("gh pr create --title T"),
227
- monkeypatch=monkeypatch,
228
- switch_succeeds=True,
229
- )
230
- assert exit_code == 0
231
- assert stdout_text == ""
232
- assert switch_invocations == []
233
- assert not state_file.exists()
234
-
235
-
236
- def test_main_no_op_when_state_file_missing_original_account(
237
- monkeypatch: pytest.MonkeyPatch,
238
- isolated_state_directory: pathlib.Path,
239
- ) -> None:
240
- state_file = hook_module._state_file_path("test-session-001")
241
- state_file.write_text(
242
- json.dumps({"primary_account": "JonEcho"}),
243
- encoding="utf-8",
244
- )
245
-
246
- exit_code, stdout_text, switch_invocations = _run_hook_with(
247
- _make_stdin_payload("gh pr create --title T"),
248
- monkeypatch=monkeypatch,
249
- switch_succeeds=True,
250
- )
251
- assert exit_code == 0
252
- assert stdout_text == ""
253
- assert switch_invocations == []
254
- assert not state_file.exists()
255
-
256
-
257
- def test_main_deletes_state_file_when_original_account_wrong_type(
258
- monkeypatch: pytest.MonkeyPatch,
259
- isolated_state_directory: pathlib.Path,
260
- ) -> None:
261
- state_file = hook_module._state_file_path("test-session-001")
262
- state_file.write_text(
263
- json.dumps({"original_account": 42, "primary_account": "JonEcho"}),
264
- encoding="utf-8",
265
- )
266
-
267
- exit_code, stdout_text, switch_invocations = _run_hook_with(
268
- _make_stdin_payload("gh pr create --title T"),
269
- monkeypatch=monkeypatch,
270
- switch_succeeds=True,
271
- )
272
- assert exit_code == 0
273
- assert stdout_text == ""
274
- assert switch_invocations == []
275
- assert not state_file.exists()
276
-
277
-
278
- def test_main_deletes_state_file_when_original_account_blank(
279
- monkeypatch: pytest.MonkeyPatch,
280
- isolated_state_directory: pathlib.Path,
281
- ) -> None:
282
- state_file = hook_module._state_file_path("test-session-001")
283
- state_file.write_text(
284
- json.dumps({"original_account": " ", "primary_account": "JonEcho"}),
285
- encoding="utf-8",
286
- )
287
-
288
- exit_code, stdout_text, switch_invocations = _run_hook_with(
289
- _make_stdin_payload("gh pr create --title T"),
290
- monkeypatch=monkeypatch,
291
- switch_succeeds=True,
292
- )
293
- assert exit_code == 0
294
- assert stdout_text == ""
295
- assert switch_invocations == []
296
- assert not state_file.exists()
297
-
298
-
299
- def test_main_no_op_does_not_create_state_file_when_absent(
300
- monkeypatch: pytest.MonkeyPatch,
301
- isolated_state_directory: pathlib.Path,
302
- ) -> None:
303
- state_file = hook_module._state_file_path("test-session-001")
304
- assert not state_file.exists()
305
-
306
- exit_code, stdout_text, switch_invocations = _run_hook_with(
307
- _make_stdin_payload("gh pr create --title T"),
308
- monkeypatch=monkeypatch,
309
- switch_succeeds=True,
310
- )
311
- assert exit_code == 0
312
- assert stdout_text == ""
313
- assert switch_invocations == []
314
- assert not state_file.exists()
315
-
316
-
317
- def test_main_per_session_key_isolation(
318
- monkeypatch: pytest.MonkeyPatch,
319
- isolated_state_directory: pathlib.Path,
320
- ) -> None:
321
- state_file_a = hook_module._state_file_path("session-A")
322
- state_file_b = hook_module._state_file_path("session-B")
323
- _write_state_file(state_file_a, original_account="jl-cmd")
324
- _write_state_file(state_file_b, original_account="other-user")
325
-
326
- exit_code, _stdout_text, switch_invocations = _run_hook_with(
327
- _make_stdin_payload("gh pr create --title T", session_id="session-A"),
328
- monkeypatch=monkeypatch,
329
- switch_succeeds=True,
330
- )
331
- assert exit_code == 0
332
- assert switch_invocations == ["jl-cmd"]
333
- assert not state_file_a.exists()
334
- assert state_file_b.exists()
335
-
336
-
337
- def test_read_original_account_returns_none_for_missing_file(
338
- isolated_state_directory: pathlib.Path,
339
- ) -> None:
340
- missing_file = isolated_state_directory / "does_not_exist.json"
341
- assert hook_module._read_original_account(missing_file) is None
342
-
343
-
344
- def test_read_original_account_returns_none_for_non_dict_payload(
345
- isolated_state_directory: pathlib.Path,
346
- ) -> None:
347
- list_payload_file = isolated_state_directory / "list_payload.json"
348
- list_payload_file.write_text(json.dumps(["jl-cmd"]), encoding="utf-8")
349
- assert hook_module._read_original_account(list_payload_file) is None
350
-
351
-
352
- def test_read_original_account_returns_none_for_non_string_value(
353
- isolated_state_directory: pathlib.Path,
354
- ) -> None:
355
- bad_type_file = isolated_state_directory / "bad_type.json"
356
- bad_type_file.write_text(json.dumps({"original_account": 42}), encoding="utf-8")
357
- assert hook_module._read_original_account(bad_type_file) is None
358
-
359
-
360
- def test_read_original_account_returns_none_for_blank_value(
361
- isolated_state_directory: pathlib.Path,
362
- ) -> None:
363
- blank_value_file = isolated_state_directory / "blank.json"
364
- blank_value_file.write_text(json.dumps({"original_account": " "}), encoding="utf-8")
365
- assert hook_module._read_original_account(blank_value_file) is None
366
-
367
-
368
- def test_switch_gh_account_returns_true_on_success() -> None:
369
- completed = mock.Mock(returncode=0, stdout="", stderr="")
370
- with mock.patch.object(swap_utils_module.subprocess, "run", return_value=completed):
371
- assert hook_module._switch_gh_account("jl-cmd") is True
372
-
373
-
374
- def test_switch_gh_account_returns_false_on_nonzero_exit() -> None:
375
- completed = mock.Mock(returncode=1, stdout="", stderr="boom")
376
- with mock.patch.object(swap_utils_module.subprocess, "run", return_value=completed):
377
- assert hook_module._switch_gh_account("jl-cmd") is False
378
-
379
-
380
- def test_switch_gh_account_returns_false_when_gh_missing() -> None:
381
- with mock.patch.object(swap_utils_module.subprocess, "run", side_effect=FileNotFoundError):
382
- assert hook_module._switch_gh_account("jl-cmd") is False
383
-
384
-
385
- def test_switch_gh_account_returns_false_on_timeout() -> None:
386
- with mock.patch.object(
387
- swap_utils_module.subprocess,
388
- "run",
389
- side_effect=swap_utils_module.subprocess.TimeoutExpired(cmd="gh", timeout=10),
390
- ):
391
- assert hook_module._switch_gh_account("jl-cmd") is False
392
-
393
-
394
- def test_delete_state_file_is_silent_when_already_absent(
395
- isolated_state_directory: pathlib.Path,
396
- ) -> None:
397
- missing_file = isolated_state_directory / "does_not_exist.json"
398
- hook_module._delete_state_file(missing_file)
399
- assert not missing_file.exists()
400
-
401
-
402
- def test_main_logs_high_level_failure_when_restore_switch_fails(
403
- monkeypatch: pytest.MonkeyPatch,
404
- capsys: pytest.CaptureFixture[str],
405
- isolated_state_directory: pathlib.Path,
406
- ) -> None:
407
- state_file = hook_module._state_file_path("test-session-001")
408
- _write_state_file(state_file, original_account="jl-cmd")
409
-
410
- monkeypatch.setattr(sys, "stdin", io.StringIO(_make_stdin_payload("gh pr create --title T")))
411
-
412
- def _fake_switch(to_account: str) -> bool:
413
- return False
414
-
415
- monkeypatch.setattr(hook_module, "_switch_gh_account", _fake_switch)
416
-
417
- with pytest.raises(SystemExit) as exit_info:
418
- hook_module.main()
419
-
420
- exit_code = exit_info.value.code if isinstance(exit_info.value.code, int) else 0
421
- captured_streams = capsys.readouterr()
422
-
423
- assert exit_code == 0
424
- assert state_file.exists()
425
- assert "[gh-pr-author-restore] Restore the gh account" in captured_streams.err
426
- assert "gh account switch requires attention" in captured_streams.err
427
- assert "'jl-cmd'" in captured_streams.err
428
- assert str(state_file) in captured_streams.err
429
-
430
-
431
- def test_main_skips_switch_and_preserves_state_file_when_planted_with_wrong_mode(
432
- monkeypatch: pytest.MonkeyPatch,
433
- capsys: pytest.CaptureFixture[str],
434
- isolated_state_directory: pathlib.Path,
435
- ) -> None:
436
- """A state file with mode 0o644 must not drive a gh-account switch.
437
-
438
- Regression guard: an attacker on the same workstation could plant a
439
- state file at the predictable swap-state path with an
440
- attacker-controlled ``original_account`` value. The restore hook
441
- must validate the file's mode and owner before reading the
442
- payload — a divergent mode signals the file was not written by the
443
- enforcer running as this user. The hook must skip the switch, leave
444
- the file on disk for inspection, and log a rejection line to
445
- stderr.
446
- """
447
- if not hasattr(os, "getuid"):
448
- return
449
- state_file = hook_module._state_file_path("test-session-001")
450
- _write_state_file(state_file, original_account="attacker")
451
- os.chmod(state_file, 0o644)
452
-
453
- monkeypatch.setattr(
454
- sys, "stdin", io.StringIO(_make_stdin_payload("gh pr create --title T"))
455
- )
456
- switch_invocations: list[str] = []
457
-
458
- def _fake_switch(to_account: str) -> bool:
459
- switch_invocations.append(to_account)
460
- return True
461
-
462
- monkeypatch.setattr(hook_module, "_switch_gh_account", _fake_switch)
463
-
464
- with pytest.raises(SystemExit) as exit_info:
465
- hook_module.main()
466
-
467
- exit_code = exit_info.value.code if isinstance(exit_info.value.code, int) else 0
468
- captured_streams = capsys.readouterr()
469
-
470
- assert exit_code == 0
471
- assert switch_invocations == []
472
- assert state_file.exists()
473
- expected_mode_bits = stat.S_IMODE(state_file.stat().st_mode)
474
- assert expected_mode_bits == 0o644
475
- assert "[gh-pr-author-restore]" in captured_streams.err
476
- assert "unexpected mode" in captured_streams.err or "unexpected" in captured_streams.err
477
- assert str(state_file) in captured_streams.err
478
-
479
-
480
- def test_module_imports_and_main_runs_under_production_sys_path_layout(
481
- monkeypatch: pytest.MonkeyPatch,
482
- ) -> None:
483
- """Module imports cleanly AND main() executes a no-op path when only blocking/ is on sys.path.
484
-
485
- pytest's ``pythonpath = packages/claude-dev-env/hooks`` lets the
486
- in-test import work even without the sys.path shim. The Claude Code
487
- hook runner does NOT set that path — it invokes
488
- ``python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/gh_pr_author_restore.py``,
489
- so only ``blocking/`` lands on sys.path. This test reproduces that
490
- layout, imports the module via its own sys.path shim, then exercises
491
- ``main()`` against a non-Bash tool_name so the no-op path runs end to
492
- end — proving the module not only imports without
493
- ``ModuleNotFoundError`` but also executes correctly under the
494
- production layout.
495
- """
496
- blocking_dir = pathlib.Path(__file__).resolve().parent
497
- monkeypatch.setattr(sys, "path", [str(blocking_dir)])
498
- spec = importlib.util.spec_from_file_location(
499
- "gh_pr_author_restore_production_path_check",
500
- blocking_dir / "gh_pr_author_restore.py",
501
- )
502
- assert spec is not None
503
- assert spec.loader is not None
504
- fresh_module = importlib.util.module_from_spec(spec)
505
- spec.loader.exec_module(fresh_module)
506
- non_bash_hook_payload = json.dumps({"tool_name": "Read", "tool_input": {}})
507
- monkeypatch.setattr(sys, "stdin", io.StringIO(non_bash_hook_payload))
508
- captured_stdout = io.StringIO()
509
- monkeypatch.setattr(sys, "stdout", captured_stdout)
510
- with pytest.raises(SystemExit) as exit_info:
511
- fresh_module.main()
512
- assert exit_info.value.code == 0
513
- assert captured_stdout.getvalue() == ""