claude-dev-env 4.3.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 (293) 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 +5 -4
  12. package/.agents/skills/pull-request/SKILL.md +11 -13
  13. package/.agents/skills/pull-request/reference/publication-tasks.md +3 -3
  14. package/.agents/skills/pull-request/scripts/github_pr_command_constants/config/constants.py +9 -1
  15. package/.agents/skills/pull-request/scripts/pull_request.py +33 -44
  16. package/.agents/skills/pull-request/scripts/test_github_pr_command_constants.py +9 -0
  17. package/.agents/skills/pull-request/scripts/test_pull_request.py +1 -3
  18. package/.agents/skills/skill-builder/SKILL.md +3 -196
  19. package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +6 -0
  20. package/_shared/pr-loop/scripts/terminology_sweep.py +64 -19
  21. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +59 -0
  22. package/bin/install.agents-home.test.mjs +15 -13
  23. package/bin/install.mjs +28 -0
  24. package/bin/install.policy-lint.test.mjs +78 -0
  25. package/bin/install.prune.test.mjs +59 -20
  26. package/bin/install.pstack-plugin.test.mjs +114 -0
  27. package/bin/install.test.mjs +37 -0
  28. package/bin/refresh-pstack-plugin-skills.test.mjs +211 -0
  29. package/hooks/advisory/pr_done_reminder.py +342 -0
  30. package/hooks/advisory/test_pr_done_reminder.py +249 -0
  31. package/hooks/blocking/bash_post_call_dispatcher.py +53 -6
  32. package/hooks/blocking/code_rules_paired_test.py +22 -15
  33. package/hooks/blocking/test_bash_post_call_dispatcher.py +65 -4
  34. package/hooks/blocking/test_code_rules_enforcer_paired_test.py +36 -29
  35. package/hooks/blocking/test_code_rules_js_resume_task_enumeration.py +4 -4
  36. package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +5 -5
  37. package/hooks/blocking/test_pre_tool_use_dispatcher.py +68 -133
  38. package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +20 -51
  39. package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +18 -18
  40. package/hooks/git-hooks/git_hooks_constants/staged_policy_lint.py +12 -0
  41. package/hooks/git-hooks/pre_commit.py +52 -11
  42. package/hooks/git-hooks/test_installed_policy_lint.py +159 -0
  43. package/hooks/git-hooks/test_native_policy_commit.py +87 -0
  44. package/hooks/git-hooks/test_pre_commit.py +246 -0
  45. package/hooks/hooks.json +1 -1
  46. package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +14 -1
  47. package/hooks/hooks_constants/pr_done_reminder_constants.py +98 -0
  48. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -43
  49. package/hooks/hooks_constants/test_bash_post_call_dispatcher_constants.py +19 -2
  50. package/hooks/hooks_constants/test_pr_done_reminder_constants.py +40 -0
  51. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +20 -32
  52. package/package.json +2 -2
  53. package/rules/durable-post-artifacts.md +2 -1
  54. package/rules/git-workflow.md +6 -6
  55. package/scripts/check.ps1 +0 -1
  56. package/{.agents/skills/run-claude-dev-env/driver.mjs → scripts/ci/installer-lifecycle.mjs} +3 -28
  57. package/scripts/ci/installer-lifecycle.test.mjs +26 -0
  58. package/scripts/ci/windows-installer-lifecycle.ps1 +1 -11
  59. package/scripts/dev_env_scripts_constants/shared_tree_constants.py +4 -5
  60. package/{.agents/skills/_shared/pr-loop/scripts → scripts}/durable_post_lint.py +5 -41
  61. package/{.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/durable_post_lint_constants.py → scripts/durable_post_lint_config/config/constants.py} +0 -10
  62. package/{.agents/skills/_shared/pr-loop/scripts/test_durable_post_lint_constants.py → scripts/durable_post_lint_config/config/test_constants.py} +1 -1
  63. package/scripts/policy_lint/adapter_detectors.py +13 -6
  64. package/scripts/policy_lint/adapters.py +7 -4
  65. package/scripts/policy_lint/config/constants.py +5 -0
  66. package/scripts/policy_lint/engine.py +48 -4
  67. package/scripts/policy_lint/model.py +1 -0
  68. package/scripts/policy_lint/registry.py +1 -1
  69. package/scripts/policy_lint/selection.py +2 -1
  70. package/scripts/policy_lint/selection_git.py +2 -0
  71. package/scripts/refresh_pstack_plugin_skills.mjs +200 -0
  72. package/scripts/shared_tree_paths.py +32 -3
  73. package/scripts/test_shared_tree_paths.py +33 -0
  74. package/scripts/tests/test_policy_lint_rules_archive.py +120 -0
  75. package/scripts/tests/test_policy_lint_rules_engine.py +25 -4
  76. package/scripts/tests/test_policy_lint_rules_installed_loader.py +29 -0
  77. package/scripts/tests/test_policy_lint_rules_registry.py +227 -13
  78. package/scripts/tests/test_policy_lint_selection.py +25 -2
  79. package/scripts/tests/test_policy_lint_selection_native_index.py +30 -0
  80. package/scripts/tests/test_rules.py +3 -7
  81. package/.agents/skills/_shared/pr-loop/.claude/CLAUDE.md +0 -1
  82. package/.agents/skills/_shared/pr-loop/AGENTS.md +0 -1
  83. package/.agents/skills/_shared/pr-loop/audit-contract.md +0 -5
  84. package/.agents/skills/_shared/pr-loop/audit-reply-template.md +0 -5
  85. package/.agents/skills/_shared/pr-loop/code-rules-gate.md +0 -5
  86. package/.agents/skills/_shared/pr-loop/fix-protocol.md +0 -5
  87. package/.agents/skills/_shared/pr-loop/gh-payloads.md +0 -5
  88. package/.agents/skills/_shared/pr-loop/portable-driver.md +0 -149
  89. package/.agents/skills/_shared/pr-loop/post-audit-thread-contract.md +0 -5
  90. package/.agents/skills/_shared/pr-loop/precatch-rubric.md +0 -5
  91. package/.agents/skills/_shared/pr-loop/preflight-proposal.contract.test.mjs +0 -119
  92. package/.agents/skills/_shared/pr-loop/preflight-proposal.md +0 -5
  93. package/.agents/skills/_shared/pr-loop/prompts/.claude/CLAUDE.md +0 -1
  94. package/.agents/skills/_shared/pr-loop/prompts/AGENTS.md +0 -1
  95. package/.agents/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +0 -324
  96. package/.agents/skills/_shared/pr-loop/scripts/.claude/CLAUDE.md +0 -1
  97. package/.agents/skills/_shared/pr-loop/scripts/AGENTS.md +0 -1
  98. package/.agents/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +0 -29
  99. package/.agents/skills/_shared/pr-loop/scripts/_cli_utils.py +0 -22
  100. package/.agents/skills/_shared/pr-loop/scripts/_path_resolver.py +0 -186
  101. package/.agents/skills/_shared/pr-loop/scripts/_xml_utils.py +0 -20
  102. package/.agents/skills/_shared/pr-loop/scripts/audit_category_schema.py +0 -355
  103. package/.agents/skills/_shared/pr-loop/scripts/build_audit_prompt.py +0 -289
  104. package/.agents/skills/_shared/pr-loop/scripts/build_converge_task_list.py +0 -310
  105. package/.agents/skills/_shared/pr-loop/scripts/build_fix_prompt.py +0 -292
  106. package/.agents/skills/_shared/pr-loop/scripts/init_loop_state.py +0 -134
  107. package/.agents/skills/_shared/pr-loop/scripts/portable_converge_driver.py +0 -1646
  108. package/.agents/skills/_shared/pr-loop/scripts/preflight_worktree.py +0 -392
  109. package/.agents/skills/_shared/pr-loop/scripts/select_converge_pacer.py +0 -215
  110. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/.claude/CLAUDE.md +0 -1
  111. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +0 -1
  112. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/__init__.py +0 -0
  113. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +0 -37
  114. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/converge_task_list_constants.py +0 -56
  115. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/handoff_constants.py +0 -45
  116. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/pacer_constants.py +0 -47
  117. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +0 -157
  118. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/portable_driver_constants.py +0 -177
  119. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/preflight_constants.py +0 -70
  120. package/.agents/skills/_shared/pr-loop/scripts/teardown_worktrees.py +0 -172
  121. package/.agents/skills/_shared/pr-loop/scripts/test__path_resolver.py +0 -106
  122. package/.agents/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +0 -94
  123. package/.agents/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +0 -499
  124. package/.agents/skills/_shared/pr-loop/scripts/test_build_converge_task_list.py +0 -140
  125. package/.agents/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +0 -262
  126. package/.agents/skills/_shared/pr-loop/scripts/test_init_loop_state.py +0 -71
  127. package/.agents/skills/_shared/pr-loop/scripts/test_portable_converge_driver.py +0 -1119
  128. package/.agents/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +0 -287
  129. package/.agents/skills/_shared/pr-loop/scripts/test_select_converge_pacer.py +0 -207
  130. package/.agents/skills/_shared/pr-loop/scripts/test_teardown_worktrees.py +0 -59
  131. package/.agents/skills/_shared/pr-loop/scripts/test_write_handoff.py +0 -201
  132. package/.agents/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +0 -182
  133. package/.agents/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +0 -206
  134. package/.agents/skills/_shared/pr-loop/scripts/write_handoff.py +0 -309
  135. package/.agents/skills/_shared/pr-loop/state-schema.md +0 -5
  136. package/.agents/skills/_shared/pr-loop/worker-spawn.md +0 -5
  137. package/.agents/skills/autoconverge/.claude/CLAUDE.md +0 -1
  138. package/.agents/skills/autoconverge/AGENTS.md +0 -1
  139. package/.agents/skills/autoconverge/SKILL.md +0 -431
  140. package/.agents/skills/autoconverge/reference/.claude/CLAUDE.md +0 -1
  141. package/.agents/skills/autoconverge/reference/AGENTS.md +0 -1
  142. package/.agents/skills/autoconverge/reference/closing-report.md +0 -93
  143. package/.agents/skills/autoconverge/reference/convergence-gates.md +0 -31
  144. package/.agents/skills/autoconverge/reference/convergence.md +0 -219
  145. package/.agents/skills/autoconverge/reference/copilot-findings.md +0 -51
  146. package/.agents/skills/autoconverge/reference/gotchas.md +0 -66
  147. package/.agents/skills/autoconverge/reference/headless-safety.md +0 -44
  148. package/.agents/skills/autoconverge/reference/multi-pr.md +0 -128
  149. package/.agents/skills/autoconverge/reference/self-closing-loop.md +0 -84
  150. package/.agents/skills/autoconverge/reference/stop-conditions.md +0 -127
  151. package/.agents/skills/autoconverge/test_portable_pacer_gate.py +0 -54
  152. package/.agents/skills/autoconverge/workflow/.claude/CLAUDE.md +0 -1
  153. package/.agents/skills/autoconverge/workflow/AGENTS.md +0 -1
  154. package/.agents/skills/autoconverge/workflow/aggregate_runs.py +0 -371
  155. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/.claude/CLAUDE.md +0 -1
  156. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +0 -1
  157. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/__init__.py +0 -0
  158. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +0 -250
  159. package/.agents/skills/autoconverge/workflow/converge.clean-audit.test.mjs +0 -665
  160. package/.agents/skills/autoconverge/workflow/converge.codex-gate.test.mjs +0 -472
  161. package/.agents/skills/autoconverge/workflow/converge.contract.test.mjs +0 -64
  162. package/.agents/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +0 -1009
  163. package/.agents/skills/autoconverge/workflow/converge.fix-progress.test.mjs +0 -107
  164. package/.agents/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +0 -33
  165. package/.agents/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +0 -15
  166. package/.agents/skills/autoconverge/workflow/converge.mjs +0 -3132
  167. package/.agents/skills/autoconverge/workflow/converge.p2-advance.test.mjs +0 -202
  168. package/.agents/skills/autoconverge/workflow/converge.path-aware.test.mjs +0 -47
  169. package/.agents/skills/autoconverge/workflow/converge.precatch.test.mjs +0 -152
  170. package/.agents/skills/autoconverge/workflow/converge.run-input.test.mjs +0 -116
  171. package/.agents/skills/autoconverge/workflow/converge_multi.mjs +0 -169
  172. package/.agents/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +0 -173
  173. package/.agents/skills/autoconverge/workflow/convergence_summary.py +0 -110
  174. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +0 -2
  175. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +0 -2
  176. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +0 -2
  177. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +0 -2
  178. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +0 -2
  179. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +0 -2
  180. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ab1c2d3e4f5a6b7c8.jsonl +0 -2
  181. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +0 -2
  182. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +0 -2
  183. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +0 -266
  184. package/.agents/skills/autoconverge/workflow/render_report.py +0 -1024
  185. package/.agents/skills/autoconverge/workflow/test_aggregate_runs.py +0 -134
  186. package/.agents/skills/autoconverge/workflow/test_convergence_summary.py +0 -149
  187. package/.agents/skills/autoconverge/workflow/test_render_report.py +0 -812
  188. package/.agents/skills/hitl/SKILL.md +0 -45
  189. package/.agents/skills/pr-cleanup/SKILL.md +0 -140
  190. package/.agents/skills/pr-cleanup/reference/process-inventory.md +0 -17
  191. package/.agents/skills/pr-cleanup/reference/task-seeds.md +0 -22
  192. package/.agents/skills/pr-name-by-capability/SKILL.md +0 -73
  193. package/.agents/skills/pr-name-by-capability/reference/fetch-commands.md +0 -29
  194. package/.agents/skills/pr-name-by-capability/reference/offense-examples.md +0 -32
  195. package/.agents/skills/pr-name-by-capability/reference/report-template.md +0 -29
  196. package/.agents/skills/pr-name-by-capability/reference/rule-checklist.md +0 -38
  197. package/.agents/skills/pr-name-by-capability/reference/task-seeds.md +0 -10
  198. package/.agents/skills/pr-plain-language-cleanup/SKILL.md +0 -93
  199. package/.agents/skills/pr-refinement/SKILL.md +0 -31
  200. package/.agents/skills/pr-shared-extraction/SKILL.md +0 -177
  201. package/.agents/skills/pr-shared-extraction/reference/examples.md +0 -46
  202. package/.agents/skills/pr-shared-extraction/reference/offense-taxonomy.md +0 -100
  203. package/.agents/skills/pr-small-cl/SKILL.md +0 -52
  204. package/.agents/skills/pr-title-description/SKILL.md +0 -96
  205. package/.agents/skills/prototype/SKILL.md +0 -85
  206. package/.agents/skills/prototype/reference/honest-limitations.md +0 -23
  207. package/.agents/skills/prototype/reference/promotion-tasks.md +0 -23
  208. package/.agents/skills/prototype/scripts/build_sandbox_settings.py +0 -249
  209. package/.agents/skills/prototype/scripts/conftest.py +0 -15
  210. package/.agents/skills/prototype/scripts/launch_sandbox.py +0 -205
  211. package/.agents/skills/prototype/scripts/probe_sandbox_safety.py +0 -311
  212. package/.agents/skills/prototype/scripts/prototype_scripts_constants/__init__.py +0 -1
  213. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
  214. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +0 -41
  215. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +0 -23
  216. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +0 -45
  217. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +0 -10
  218. package/.agents/skills/prototype/scripts/test_build_sandbox_settings.py +0 -275
  219. package/.agents/skills/prototype/scripts/test_launch_sandbox.py +0 -303
  220. package/.agents/skills/prototype/scripts/test_probe_sandbox_safety.py +0 -312
  221. package/.agents/skills/prototype/workflows/promotion.md +0 -25
  222. package/.agents/skills/prototype/workflows/sandbox.md +0 -35
  223. package/.agents/skills/rebase/.claude/CLAUDE.md +0 -1
  224. package/.agents/skills/rebase/AGENTS.md +0 -1
  225. package/.agents/skills/rebase/SKILL.md +0 -178
  226. package/.agents/skills/review-router/SKILL.md +0 -51
  227. package/.agents/skills/review-router/reference/route-policy.json +0 -7
  228. package/.agents/skills/review-router/reference/task-seeds.md +0 -1
  229. package/.agents/skills/review-router/scripts/conftest.py +0 -4
  230. package/.agents/skills/review-router/scripts/review_router.py +0 -50
  231. package/.agents/skills/review-router/scripts/review_router_cli.py +0 -211
  232. package/.agents/skills/review-router/scripts/review_router_constants/__init__.py +0 -1
  233. package/.agents/skills/review-router/scripts/review_router_constants/config/__init__.py +0 -0
  234. package/.agents/skills/review-router/scripts/review_router_constants/config/constants.py +0 -13
  235. package/.agents/skills/review-router/scripts/test_review_router.py +0 -23
  236. package/.agents/skills/review-router/scripts/test_review_router_cli.py +0 -47
  237. package/.agents/skills/review-tier/SKILL.md +0 -56
  238. package/.agents/skills/review-tier/reference/task-seeds.md +0 -1
  239. package/.agents/skills/review-tier/reference/tier-policy.json +0 -9
  240. package/.agents/skills/review-tier/scripts/conftest.py +0 -4
  241. package/.agents/skills/review-tier/scripts/review_tier.py +0 -359
  242. package/.agents/skills/review-tier/scripts/review_tier_constants/__init__.py +0 -1
  243. package/.agents/skills/review-tier/scripts/review_tier_constants/config/__init__.py +0 -0
  244. package/.agents/skills/review-tier/scripts/review_tier_constants/config/constants.py +0 -29
  245. package/.agents/skills/review-tier/scripts/test_generation.py +0 -159
  246. package/.agents/skills/review-tier/scripts/test_review_tier.py +0 -33
  247. package/.agents/skills/run-claude-dev-env/SKILL.md +0 -84
  248. package/.agents/skills/session-log/.claude/CLAUDE.md +0 -1
  249. package/.agents/skills/session-log/AGENTS.md +0 -1
  250. package/.agents/skills/session-log/SKILL.md +0 -170
  251. package/.agents/skills/session-log/templates/frontmatter.md +0 -40
  252. package/.agents/skills/session-tidy/.claude/CLAUDE.md +0 -1
  253. package/.agents/skills/session-tidy/AGENTS.md +0 -1
  254. package/.agents/skills/session-tidy/SKILL.md +0 -191
  255. package/.agents/skills/skill-builder/references/.claude/CLAUDE.md +0 -1
  256. package/.agents/skills/skill-builder/references/AGENTS.md +0 -1
  257. package/.agents/skills/skill-builder/references/delegation-map.md +0 -130
  258. package/.agents/skills/skill-builder/references/description-field.md +0 -111
  259. package/.agents/skills/skill-builder/references/deterministic-elements.md +0 -218
  260. package/.agents/skills/skill-builder/references/progressive-disclosure.md +0 -122
  261. package/.agents/skills/skill-builder/references/self-audit-checklist.md +0 -126
  262. package/.agents/skills/skill-builder/references/skill-modularity.md +0 -111
  263. package/.agents/skills/skill-builder/references/skill-types.md +0 -232
  264. package/.agents/skills/skill-builder/references/thariq-x-post-skills.json +0 -33
  265. package/.agents/skills/skill-builder/templates/.claude/CLAUDE.md +0 -1
  266. package/.agents/skills/skill-builder/templates/AGENTS.md +0 -1
  267. package/.agents/skills/skill-builder/templates/gap-analysis.md +0 -87
  268. package/.agents/skills/skill-builder/workflows/.claude/CLAUDE.md +0 -1
  269. package/.agents/skills/skill-builder/workflows/AGENTS.md +0 -1
  270. package/.agents/skills/skill-builder/workflows/improve-skill.md +0 -136
  271. package/.agents/skills/skill-builder/workflows/new-skill.md +0 -179
  272. package/.agents/skills/skill-builder/workflows/polish-skill.md +0 -139
  273. package/.agents/skills/source-command-sr-loop/SKILL.md +0 -82
  274. package/.agents/skills/update/.claude/CLAUDE.md +0 -1
  275. package/.agents/skills/update/AGENTS.md +0 -1
  276. package/.agents/skills/update/SKILL.md +0 -190
  277. package/_shared/pr-loop/AGENTS.md +0 -1
  278. package/_shared/pr-loop/CLAUDE.md +0 -1
  279. package/_shared/pr-loop/audit-contract.md +0 -183
  280. package/_shared/pr-loop/audit-reply-template.md +0 -151
  281. package/_shared/pr-loop/code-rules-gate.md +0 -65
  282. package/_shared/pr-loop/fix-protocol.md +0 -60
  283. package/_shared/pr-loop/gh-payloads.md +0 -75
  284. package/_shared/pr-loop/post-audit-thread-contract.md +0 -51
  285. package/_shared/pr-loop/precatch-rubric.md +0 -66
  286. package/_shared/pr-loop/preflight-proposal.md +0 -56
  287. package/_shared/pr-loop/scripts/post_audit_thread.py +0 -1242
  288. package/_shared/pr-loop/scripts/pr_loop_shared_constants/post_audit_thread_constants.py +0 -199
  289. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +0 -1115
  290. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +0 -129
  291. package/_shared/pr-loop/state-schema.md +0 -45
  292. package/_shared/pr-loop/worker-spawn.md +0 -189
  293. /package/{.agents/skills/_shared/pr-loop/scripts → scripts}/test_durable_post_lint.py +0 -0
@@ -0,0 +1,211 @@
1
+ import { test } from 'node:test';
2
+ import { strict as assert } from 'node:assert';
3
+ import {
4
+ mkdtempSync,
5
+ mkdirSync,
6
+ readFileSync,
7
+ rmSync,
8
+ symlinkSync,
9
+ writeFileSync,
10
+ } from 'node:fs';
11
+ import { tmpdir } from 'node:os';
12
+ import { dirname, join } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+ import { execFileSync } from 'node:child_process';
15
+ import {
16
+ PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH,
17
+ findPstackSkillDirectoryNames,
18
+ refreshPstackPluginManifest,
19
+ } from '../scripts/refresh_pstack_plugin_skills.mjs';
20
+
21
+ function buildPluginRoot(homeDirectory, skillNames, manifest) {
22
+ const pluginRoot = join(homeDirectory, 'pstack');
23
+ for (const eachSkillName of skillNames) {
24
+ const skillDirectory = join(pluginRoot, eachSkillName);
25
+ mkdirSync(skillDirectory, { recursive: true });
26
+ writeFileSync(join(skillDirectory, 'SKILL.md'), '---\n');
27
+ }
28
+ const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
29
+ mkdirSync(join(pluginRoot, '.claude-plugin'), { recursive: true });
30
+ if (manifest !== null) {
31
+ writeFileSync(manifestPath, JSON.stringify(manifest));
32
+ }
33
+ return pluginRoot;
34
+ }
35
+
36
+ function withTemporaryHome(runAssertions) {
37
+ const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-pstack-manifest-'));
38
+ try {
39
+ runAssertions(homeDirectory);
40
+ } finally {
41
+ rmSync(homeDirectory, { recursive: true, force: true });
42
+ }
43
+ }
44
+
45
+ test('finds only sub-folders that hold a skill entry file', () => {
46
+ withTemporaryHome((homeDirectory) => {
47
+ const pluginRoot = buildPluginRoot(homeDirectory, ['how', 'why'], { skills: [] });
48
+ mkdirSync(join(pluginRoot, 'docs'), { recursive: true });
49
+ writeFileSync(join(pluginRoot, '.claude-plugin', 'SKILL.md'), '---\n');
50
+
51
+ assert.deepEqual(findPstackSkillDirectoryNames(pluginRoot), ['how', 'why']);
52
+ });
53
+ });
54
+
55
+ test('writes a manifest when the plugin folder has none', () => {
56
+ withTemporaryHome((homeDirectory) => {
57
+ const pluginRoot = buildPluginRoot(homeDirectory, ['how'], null);
58
+
59
+ const outcome = refreshPstackPluginManifest(pluginRoot);
60
+
61
+ assert.equal(outcome.didWrite, true);
62
+ const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
63
+ const written = JSON.parse(readFileSync(manifestPath, 'utf8'));
64
+ assert.equal(written.name, 'pstack');
65
+ assert.deepEqual(written.skills, ['./how']);
66
+ });
67
+ });
68
+
69
+ test('adds a skill folder the manifest does not list', () => {
70
+ withTemporaryHome((homeDirectory) => {
71
+ const pluginRoot = buildPluginRoot(homeDirectory, ['how', 'why'], {
72
+ name: 'pstack',
73
+ skills: ['./how'],
74
+ });
75
+
76
+ const outcome = refreshPstackPluginManifest(pluginRoot);
77
+
78
+ assert.equal(outcome.didWrite, true);
79
+ assert.deepEqual(outcome.previousSkillPaths, ['./how']);
80
+ assert.deepEqual(outcome.currentSkillPaths, ['./how', './why']);
81
+ });
82
+ });
83
+
84
+ test('leaves the manifest byte for byte when nothing changed', () => {
85
+ withTemporaryHome((homeDirectory) => {
86
+ const pluginRoot = buildPluginRoot(homeDirectory, ['how'], {
87
+ name: 'pstack',
88
+ skills: ['./how'],
89
+ });
90
+ const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
91
+ writeFileSync(manifestPath, JSON.stringify({ name: 'pstack', skills: ['./how'] }));
92
+ const before = readFileSync(manifestPath, 'utf8');
93
+
94
+ const outcome = refreshPstackPluginManifest(pluginRoot);
95
+
96
+ assert.equal(outcome.didWrite, false);
97
+ assert.equal(readFileSync(manifestPath, 'utf8'), before);
98
+ });
99
+ });
100
+
101
+ test('reads a string skills value as a one-entry list', () => {
102
+ withTemporaryHome((homeDirectory) => {
103
+ const pluginRoot = buildPluginRoot(homeDirectory, ['how'], {
104
+ name: 'pstack',
105
+ skills: './',
106
+ });
107
+
108
+ const outcome = refreshPstackPluginManifest(pluginRoot);
109
+
110
+ assert.deepEqual(outcome.previousSkillPaths, ['./']);
111
+ assert.deepEqual(outcome.currentSkillPaths, ['./how']);
112
+ });
113
+ });
114
+
115
+ test('keeps manifest fields the refresher does not own', () => {
116
+ withTemporaryHome((homeDirectory) => {
117
+ const pluginRoot = buildPluginRoot(homeDirectory, ['how'], {
118
+ name: 'pstack',
119
+ version: '9.9.9',
120
+ description: 'a description the user edited',
121
+ skills: [],
122
+ });
123
+
124
+ refreshPstackPluginManifest(pluginRoot);
125
+
126
+ const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
127
+ const written = JSON.parse(readFileSync(manifestPath, 'utf8'));
128
+ assert.equal(written.version, '9.9.9');
129
+ assert.equal(written.description, 'a description the user edited');
130
+ });
131
+ });
132
+
133
+ test('refuses a folder that is itself one skill', () => {
134
+ withTemporaryHome((homeDirectory) => {
135
+ const pluginRoot = buildPluginRoot(homeDirectory, ['how'], null);
136
+ writeFileSync(join(pluginRoot, 'SKILL.md'), '---\n');
137
+
138
+ const outcome = refreshPstackPluginManifest(pluginRoot);
139
+
140
+ assert.equal(outcome.didWrite, false);
141
+ assert.equal(outcome.reason, 'plugin root is itself a skill');
142
+ });
143
+ });
144
+
145
+ test('running the file directly reports what it did', () => {
146
+ withTemporaryHome((homeDirectory) => {
147
+ const pluginRoot = buildPluginRoot(homeDirectory, ['how'], null);
148
+ const modulePath = join(
149
+ dirname(dirname(fileURLToPath(import.meta.url))),
150
+ 'scripts',
151
+ 'refresh_pstack_plugin_skills.mjs',
152
+ );
153
+
154
+ const firstRun = execFileSync('node', [modulePath, pluginRoot], { encoding: 'utf8' });
155
+ const secondRun = execFileSync('node', [modulePath, pluginRoot], { encoding: 'utf8' });
156
+
157
+ assert.match(firstRun, /Updated\. 1 skills listed\./);
158
+ assert.match(secondRun, /No change\. 1 skills listed\./);
159
+ });
160
+ });
161
+
162
+ test('running the file through a directory link still reports what it did', () => {
163
+ withTemporaryHome((homeDirectory) => {
164
+ const pluginRoot = buildPluginRoot(homeDirectory, ['how'], null);
165
+ const scriptsDirectory = join(
166
+ dirname(dirname(fileURLToPath(import.meta.url))),
167
+ 'scripts',
168
+ );
169
+ const linkedScriptsDirectory = join(homeDirectory, 'linked-scripts');
170
+ symlinkSync(scriptsDirectory, linkedScriptsDirectory, 'junction');
171
+ const linkedModulePath = join(linkedScriptsDirectory, 'refresh_pstack_plugin_skills.mjs');
172
+
173
+ const output = execFileSync('node', [linkedModulePath, pluginRoot], { encoding: 'utf8' });
174
+
175
+ assert.match(output, /Updated\. 1 skills listed\./);
176
+ });
177
+ });
178
+
179
+ test('with no folder argument it follows CLAUDE_CONFIG_DIR', () => {
180
+ withTemporaryHome((homeDirectory) => {
181
+ const claudeConfigDirectory = join(homeDirectory, 'profile', '.claude');
182
+ const pluginRoot = join(claudeConfigDirectory, 'skills', 'pstack');
183
+ mkdirSync(join(pluginRoot, 'how'), { recursive: true });
184
+ writeFileSync(join(pluginRoot, 'how', 'SKILL.md'), '---\n');
185
+ const modulePath = join(
186
+ dirname(dirname(fileURLToPath(import.meta.url))),
187
+ 'scripts',
188
+ 'refresh_pstack_plugin_skills.mjs',
189
+ );
190
+
191
+ const output = execFileSync('node', [modulePath], {
192
+ encoding: 'utf8',
193
+ env: { ...process.env, CLAUDE_CONFIG_DIR: claudeConfigDirectory },
194
+ });
195
+
196
+ assert.match(output, /Updated\. 1 skills listed\./);
197
+ const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
198
+ assert.deepEqual(JSON.parse(readFileSync(manifestPath, 'utf8')).skills, ['./how']);
199
+ });
200
+ });
201
+
202
+ test('refuses a folder that holds no skills', () => {
203
+ withTemporaryHome((homeDirectory) => {
204
+ const pluginRoot = buildPluginRoot(homeDirectory, [], null);
205
+
206
+ const outcome = refreshPstackPluginManifest(pluginRoot);
207
+
208
+ assert.equal(outcome.didWrite, false);
209
+ assert.equal(outcome.reason, 'plugin root holds no skills');
210
+ });
211
+ });
@@ -0,0 +1,342 @@
1
+ #!/usr/bin/env python3
2
+ """PostToolUse context reminder: the PR done checklist after a push.
3
+
4
+ This hook never blocks. It watches every Bash call finish and, after a
5
+ successful ``git push`` or ``gh pr create``, probes the branch's pull request
6
+ through ``gh pr view`` and adds one loud checklist to the agent's context::
7
+
8
+ === PR DONE CHECKLIST (context reminder, never a block) ===
9
+ PR #42 https://github.com/acme/widgets/pull/42
10
+ Mergeable: CONFLICTING (CONFLICTS with the base branch. Merge origin/main ...)
11
+ CI checks: 1 failing, 2 pending, 4 total
12
+ Draft: yes
13
+ Label done: not set
14
+ Verdict: NOT DONE. Do not add the done label yet.
15
+ Re-check: gh pr view 42 --json mergeable,mergeStateStatus,statusCheckRollup,labels
16
+ A PR is done only when every line above is clean.
17
+
18
+ Why a reminder and not a gate: agents keep working. The rule "a PR is done
19
+ only when GitHub reports it mergeable and CI is clean" arrives as live state
20
+ at the exact moment the agent just pushed, so no instruction file has to be
21
+ remembered. A block would stall the run; this only informs it.
22
+
23
+ Quiet branches: a non-Bash tool, a command that is not a push or a PR
24
+ creation, a push whose harness-reported exit status is non-zero, or a ``gh``
25
+ failure other than "no pull requests found" each emit nothing.
26
+
27
+ Hosted by ``blocking/bash_post_call_dispatcher.py``, which forwards the
28
+ ``hookSpecificOutput.additionalContext`` this hook prints.
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import json
34
+ import shlex
35
+ import subprocess
36
+ import sys
37
+ from pathlib import Path
38
+
39
+ try:
40
+ _hooks_root_directory = str(Path(__file__).resolve().parent.parent)
41
+ if _hooks_root_directory not in sys.path:
42
+ sys.path.insert(0, _hooks_root_directory)
43
+ from hooks_constants.bash_pre_tool_use_dispatcher_constants import (
44
+ ALL_BASH_AND_POWERSHELL_TOOL_NAMES,
45
+ )
46
+ from hooks_constants.pr_done_reminder_constants import (
47
+ ADD_LABEL_COMMAND_TEMPLATE,
48
+ ALL_FAILING_CHECK_CONCLUSIONS,
49
+ ALL_PASSING_CHECK_CONCLUSIONS,
50
+ CHECK_COMPLETED_STATUS,
51
+ DONE_LABEL_NAME,
52
+ EXIT_CODE_ERROR_PREFIX,
53
+ GH_PR_CREATE_ACTION,
54
+ GH_PR_SUBCOMMAND,
55
+ ALL_GH_PR_VIEW_ARGUMENTS,
56
+ ALL_GIT_OPTIONS_WITH_VALUE,
57
+ ALL_POWERSHELL_COMMAND_FLAGS,
58
+ ALL_POWERSHELL_PROGRAM_NAMES,
59
+ GH_PR_VIEW_TIMEOUT_SECONDS,
60
+ GH_PROGRAM_NAME,
61
+ GIT_PROGRAM_NAME,
62
+ GIT_PUSH_SUBCOMMAND,
63
+ MERGEABLE_CLEAN_VALUE,
64
+ MERGEABLE_UNKNOWN_VALUE,
65
+ NO_PULL_REQUEST_MARKER,
66
+ NO_PULL_REQUEST_REMINDER,
67
+ RECHECK_COMMAND_TEMPLATE,
68
+ REMINDER_FOOTER,
69
+ REMINDER_HEADER,
70
+ REMINDER_LINE_SEPARATOR,
71
+ ALL_REMINDER_HINTS_BY_MERGEABLE,
72
+ STATUS_CONTEXT_STATE_KEY,
73
+ VERDICT_DONE,
74
+ VERDICT_NOT_DONE,
75
+ )
76
+ from hooks_constants.pre_tool_use_stdin import read_hook_input_dictionary_from_stdin
77
+ from hooks_constants.shell_command_segments import (
78
+ effective_leading_program,
79
+ split_into_segments,
80
+ token_basename,
81
+ )
82
+ except ImportError as import_error:
83
+ raise ImportError(
84
+ "pr_done_reminder: cannot import its dependencies; "
85
+ "ensure the hooks directory is importable."
86
+ ) from import_error
87
+
88
+
89
+ def _command_tokens(command: str) -> list[str]:
90
+ try:
91
+ return shlex.split(command, posix=True)
92
+ except ValueError:
93
+ return command.split()
94
+
95
+
96
+ def _segment_program_and_arguments(all_segment_tokens: list[str]) -> tuple[str, list[str]]:
97
+ program_token = effective_leading_program(all_segment_tokens)
98
+ if program_token is None:
99
+ return "", []
100
+ program_index = all_segment_tokens.index(program_token)
101
+ return token_basename(program_token), all_segment_tokens[program_index + 1 :]
102
+
103
+
104
+ def _git_subcommand(all_arguments: list[str]) -> str | None:
105
+ """Return git's subcommand after its global options.
106
+
107
+ ``git -C <path> push`` and ``git -c <key=value> push`` carry an option
108
+ value before the subcommand; ``--git-dir=<path>`` carries none. The first
109
+ token left after those is the subcommand, so ``git stash push`` yields
110
+ ``stash`` and ``git log --grep push`` yields ``log``.
111
+ """
112
+ should_skip_next_token = False
113
+ for each_argument in all_arguments:
114
+ if should_skip_next_token:
115
+ should_skip_next_token = False
116
+ continue
117
+ if each_argument in ALL_GIT_OPTIONS_WITH_VALUE:
118
+ should_skip_next_token = True
119
+ continue
120
+ if each_argument.startswith("-"):
121
+ continue
122
+ return each_argument
123
+ return None
124
+
125
+
126
+ def _is_git_push(program_name: str, all_arguments: list[str]) -> bool:
127
+ return (
128
+ program_name == GIT_PROGRAM_NAME and _git_subcommand(all_arguments) == GIT_PUSH_SUBCOMMAND
129
+ )
130
+
131
+
132
+ def _is_gh_pr_create(program_name: str, all_arguments: list[str]) -> bool:
133
+ return program_name == GH_PROGRAM_NAME and all_arguments[:2] == [
134
+ GH_PR_SUBCOMMAND,
135
+ GH_PR_CREATE_ACTION,
136
+ ]
137
+
138
+
139
+ def _powershell_inner_commands(all_command_tokens: list[str]) -> list[str]:
140
+ """Return the script texts every ``pwsh -Command "..."`` wrapper runs.
141
+
142
+ Read from the raw shlex tokens, before segment splitting: the quoted
143
+ script is one token here, and splitting it on ``;`` first would cut a
144
+ ``git add -A; git push`` script in half.
145
+ """
146
+ all_inner_commands: list[str] = []
147
+ has_seen_powershell_program = False
148
+ for each_token_index, each_token in enumerate(all_command_tokens[:-1]):
149
+ if token_basename(each_token) in ALL_POWERSHELL_PROGRAM_NAMES:
150
+ has_seen_powershell_program = True
151
+ continue
152
+ if has_seen_powershell_program and each_token.lower() in ALL_POWERSHELL_COMMAND_FLAGS:
153
+ all_inner_commands.append(all_command_tokens[each_token_index + 1])
154
+ has_seen_powershell_program = False
155
+ return all_inner_commands
156
+
157
+
158
+ def command_triggers_reminder(command: str) -> bool:
159
+ """Return True when any segment of the command is a git push or a gh pr create.
160
+
161
+ A ``pwsh -Command "..."`` wrapper is unwrapped and its inner text scanned
162
+ the same way, since the shell rule routes Bash-tool commands through pwsh.
163
+
164
+ Args:
165
+ command: The Bash command text the agent ran.
166
+ """
167
+ all_command_tokens = _command_tokens(command)
168
+ for each_inner_command in _powershell_inner_commands(all_command_tokens):
169
+ if command_triggers_reminder(each_inner_command):
170
+ return True
171
+ for each_segment in split_into_segments(all_command_tokens):
172
+ program_name, all_arguments = _segment_program_and_arguments(each_segment)
173
+ if _is_git_push(program_name, all_arguments) or _is_gh_pr_create(
174
+ program_name, all_arguments
175
+ ):
176
+ return True
177
+ return False
178
+
179
+
180
+ def _harness_reported_failure(tool_response: object) -> bool:
181
+ return isinstance(tool_response, str) and tool_response.startswith(EXIT_CODE_ERROR_PREFIX)
182
+
183
+
184
+ def _check_outcome(all_check_fields: dict[str, object]) -> str:
185
+ """Return failing, pending, or passing for one statusCheckRollup entry.
186
+
187
+ A CheckRun entry carries ``status`` and ``conclusion``; a StatusContext
188
+ entry carries ``state``. Both shapes appear in the same rollup list.
189
+ """
190
+ state_text = str(all_check_fields.get(STATUS_CONTEXT_STATE_KEY) or "").upper()
191
+ if state_text:
192
+ if state_text in ALL_FAILING_CHECK_CONCLUSIONS:
193
+ return "failing"
194
+ return "passing" if state_text in ALL_PASSING_CHECK_CONCLUSIONS else "pending"
195
+ if str(all_check_fields.get("status") or "").upper() != CHECK_COMPLETED_STATUS:
196
+ return "pending"
197
+ conclusion_text = str(all_check_fields.get("conclusion") or "").upper()
198
+ return "failing" if conclusion_text in ALL_FAILING_CHECK_CONCLUSIONS else "passing"
199
+
200
+
201
+ def _check_counts(all_pr_fields: dict[str, object]) -> dict[str, int]:
202
+ all_checks = all_pr_fields.get("statusCheckRollup")
203
+ all_check_counts = {"failing": 0, "pending": 0, "passing": 0, "total": 0}
204
+ if not isinstance(all_checks, list):
205
+ return all_check_counts
206
+ for each_check_fields in all_checks:
207
+ if isinstance(each_check_fields, dict):
208
+ all_check_counts[_check_outcome(each_check_fields)] += 1
209
+ all_check_counts["total"] += 1
210
+ return all_check_counts
211
+
212
+
213
+ def _checks_line(all_check_counts: dict[str, int]) -> str:
214
+ if all_check_counts["total"] == 0:
215
+ return "none reported"
216
+ return (
217
+ f"{all_check_counts['failing']} failing, "
218
+ f"{all_check_counts['pending']} pending, "
219
+ f"{all_check_counts['total']} total"
220
+ )
221
+
222
+
223
+ def _has_done_label(all_pr_fields: dict[str, object]) -> bool:
224
+ all_labels = all_pr_fields.get("labels")
225
+ if not isinstance(all_labels, list):
226
+ return False
227
+ return any(
228
+ isinstance(each_label, dict) and each_label.get("name") == DONE_LABEL_NAME
229
+ for each_label in all_labels
230
+ )
231
+
232
+
233
+ def _is_done(mergeable_value: str, all_check_counts: dict[str, int]) -> bool:
234
+ return (
235
+ mergeable_value == MERGEABLE_CLEAN_VALUE
236
+ and all_check_counts["failing"] == 0
237
+ and all_check_counts["pending"] == 0
238
+ )
239
+
240
+
241
+ def _verdict_lines(number: object, is_done: bool) -> list[str]:
242
+ """Return the verdict line, the add-label command on DONE, and the re-check line."""
243
+ all_lines = [f"Verdict: {VERDICT_DONE if is_done else VERDICT_NOT_DONE}"]
244
+ if is_done:
245
+ add_label_command = ADD_LABEL_COMMAND_TEMPLATE.format(number=number, label=DONE_LABEL_NAME)
246
+ all_lines.append(f"Add label: {add_label_command}")
247
+ all_lines.append(f"Re-check: {RECHECK_COMMAND_TEMPLATE.format(number=number)}")
248
+ return all_lines
249
+
250
+
251
+ def build_reminder_context(all_pr_fields: dict[str, object]) -> str:
252
+ """Build the checklist text from a ``gh pr view --json`` object.
253
+
254
+ Args:
255
+ all_pr_fields: The parsed pull request JSON with number, url, isDraft,
256
+ mergeable, mergeStateStatus, statusCheckRollup, and labels.
257
+
258
+ Returns:
259
+ The multi-line checklist ending in a DONE or NOT DONE verdict.
260
+ """
261
+ number = all_pr_fields.get("number", "?")
262
+ mergeable_value = str(all_pr_fields.get("mergeable") or MERGEABLE_UNKNOWN_VALUE).upper()
263
+ mergeable_hint = ALL_REMINDER_HINTS_BY_MERGEABLE.get(
264
+ mergeable_value, ALL_REMINDER_HINTS_BY_MERGEABLE[MERGEABLE_UNKNOWN_VALUE]
265
+ )
266
+ all_check_counts = _check_counts(all_pr_fields)
267
+ all_lines = [
268
+ REMINDER_HEADER,
269
+ f"PR #{number} {all_pr_fields.get('url', '')}",
270
+ f"Mergeable: {mergeable_value} ({mergeable_hint})",
271
+ f"CI checks: {_checks_line(all_check_counts)}",
272
+ f"Draft: {'yes' if all_pr_fields.get('isDraft') else 'no'}",
273
+ f"Label done: {'set' if _has_done_label(all_pr_fields) else 'not set'}",
274
+ *_verdict_lines(number, _is_done(mergeable_value, all_check_counts)),
275
+ REMINDER_FOOTER,
276
+ ]
277
+ return REMINDER_LINE_SEPARATOR.join(all_lines)
278
+
279
+
280
+ def _probe_pull_request(cwd: str) -> str | None:
281
+ """Return the reminder text from a live ``gh pr view``, or None to stay quiet."""
282
+ try:
283
+ completed_process = subprocess.run(
284
+ list(ALL_GH_PR_VIEW_ARGUMENTS),
285
+ cwd=cwd or None,
286
+ capture_output=True,
287
+ text=True,
288
+ encoding="utf-8",
289
+ errors="replace",
290
+ timeout=GH_PR_VIEW_TIMEOUT_SECONDS,
291
+ check=False,
292
+ )
293
+ except (OSError, subprocess.TimeoutExpired):
294
+ return None
295
+ if completed_process.returncode != 0:
296
+ combined_output = f"{completed_process.stdout}\n{completed_process.stderr}".lower()
297
+ return NO_PULL_REQUEST_REMINDER if NO_PULL_REQUEST_MARKER in combined_output else None
298
+ try:
299
+ all_pr_fields = json.loads(completed_process.stdout)
300
+ except json.JSONDecodeError:
301
+ return None
302
+ if not isinstance(all_pr_fields, dict):
303
+ return None
304
+ return build_reminder_context(all_pr_fields)
305
+
306
+
307
+ def _emit_context(context_text: str) -> None:
308
+ payload = {
309
+ "hookSpecificOutput": {
310
+ "hookEventName": "PostToolUse",
311
+ "additionalContext": context_text,
312
+ }
313
+ }
314
+ sys.stdout.write(json.dumps(payload))
315
+ sys.stdout.flush()
316
+
317
+
318
+ def main() -> None:
319
+ """Add the PR done checklist to context after a successful push or PR creation.
320
+
321
+ Reads the PostToolUse payload from stdin. Every quiet branch returns with
322
+ no output, so this hook can never alter or block a tool call.
323
+ """
324
+ hook_payload = read_hook_input_dictionary_from_stdin()
325
+ if (
326
+ hook_payload is None
327
+ or hook_payload.get("tool_name") not in ALL_BASH_AND_POWERSHELL_TOOL_NAMES
328
+ ):
329
+ return
330
+ tool_input = hook_payload.get("tool_input")
331
+ command = tool_input.get("command", "") if isinstance(tool_input, dict) else ""
332
+ if not isinstance(command, str) or not command_triggers_reminder(command):
333
+ return
334
+ if _harness_reported_failure(hook_payload.get("tool_response")):
335
+ return
336
+ context_text = _probe_pull_request(str(hook_payload.get("cwd") or ""))
337
+ if context_text is not None:
338
+ _emit_context(context_text)
339
+
340
+
341
+ if __name__ == "__main__":
342
+ main()