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,159 @@
1
+ """Prove retirement and local enforcement through an actual disposable install."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import os
8
+ import subprocess
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ import pytest
13
+ from test_pre_commit import ALL_RULE_FIXTURES, _git, _stage
14
+ from test_pre_commit import repository_root as repository_root
15
+
16
+ ALL_RETIRED_HOOK_PATHS = (
17
+ "blocking/code_rules_enforcer.py",
18
+ "blocking/tdd_enforcer.py",
19
+ "blocking/windows_rmtree_blocker.py",
20
+ "blocking/state_description_blocker.py",
21
+ "blocking/subprocess_budget_completeness.py",
22
+ "blocking/hook_prose_detector_consistency.py",
23
+ "blocking/workflow_substitution_slot_blocker.py",
24
+ )
25
+ PACKAGE_ROOT = Path(__file__).resolve().parents[2]
26
+
27
+
28
+ def _legacy_settings_bytes(managed_root: Path, foreign_command: str) -> bytes:
29
+ original_settings = {
30
+ "permissions": {"allow": ["Read"]},
31
+ "hooks": {"PreToolUse": [{
32
+ "matcher": "Write|Edit|MultiEdit|apply_patch",
33
+ "hooks": [
34
+ {"type": "command", "command": foreign_command, "timeout": 17},
35
+ *({
36
+ "type": "command",
37
+ "command": f"python {managed_root.as_posix()}/hooks/{each_path}",
38
+ "timeout": 10,
39
+ } for each_path in ALL_RETIRED_HOOK_PATHS),
40
+ ],
41
+ }]},
42
+ }
43
+ return json.dumps(original_settings, indent=2).encode("utf-8")
44
+
45
+
46
+ def _install_environment(home: Path) -> dict[str, str]:
47
+ environment = {
48
+ each_name: each_value for each_name, each_value in os.environ.items()
49
+ if not each_name.startswith("GIT_")
50
+ }
51
+ environment.update({
52
+ "HOME": str(home),
53
+ "USERPROFILE": str(home),
54
+ "CLAUDE_CONFIG_DIR": str(home / ".claude"),
55
+ "CODEX_HOME": str(home / ".codex"),
56
+ "GIT_CONFIG_GLOBAL": str(home / "gitconfig"),
57
+ "GIT_CONFIG_NOSYSTEM": "1",
58
+ })
59
+ return environment
60
+
61
+
62
+ def _install_twice(home: Path, settings_path: Path) -> bytes:
63
+ all_settings_bytes: list[bytes] = []
64
+ for each_run in range(2):
65
+ completed = subprocess.run(
66
+ ["node", str(PACKAGE_ROOT / "bin/install.mjs"), "--only", "core"],
67
+ cwd=PACKAGE_ROOT, env=_install_environment(home),
68
+ capture_output=True, text=True, check=False, timeout=180,
69
+ )
70
+ (home / f"install-{each_run}.log").write_text(
71
+ completed.stdout + completed.stderr, encoding="utf-8"
72
+ )
73
+ assert completed.returncode == 0, completed.stdout + completed.stderr
74
+ all_settings_bytes.append(settings_path.read_bytes())
75
+ assert all_settings_bytes[0] == all_settings_bytes[1]
76
+ return all_settings_bytes[-1]
77
+
78
+
79
+ def _assert_retired_registrations(managed_root: Path, settings_bytes: bytes, foreign_command: str) -> None:
80
+ settings = json.loads(settings_bytes)
81
+ all_commands = [
82
+ each_hook["command"]
83
+ for each_groups in settings["hooks"].values()
84
+ for each_group in each_groups
85
+ for each_hook in each_group["hooks"]
86
+ ]
87
+ assert all_commands.count(foreign_command) == 1
88
+ assert "Read" in settings["permissions"]["allow"]
89
+ for each_relative_path in ALL_RETIRED_HOOK_PATHS:
90
+ assert not any(each_relative_path in each_command for each_command in all_commands)
91
+ source = PACKAGE_ROOT / "hooks" / each_relative_path
92
+ installed = managed_root / "hooks" / each_relative_path
93
+ assert hashlib.sha256(installed.read_bytes()).digest() == hashlib.sha256(source.read_bytes()).digest()
94
+
95
+
96
+ @pytest.fixture(scope="module")
97
+ def managed_installation(tmp_path_factory: pytest.TempPathFactory) -> Path:
98
+ """Install twice into a private home while retaining exact prior settings."""
99
+ home = tmp_path_factory.mktemp("installed-policy")
100
+ managed_root = home / ".claude"
101
+ managed_root.mkdir()
102
+ foreign_command = "python /unmanaged/fixture_notice.py"
103
+ original_bytes = _legacy_settings_bytes(managed_root, foreign_command)
104
+ settings_path = managed_root / "settings.json"
105
+ settings_path.write_bytes(original_bytes)
106
+ backup_directory = home / "before-cutover"
107
+ backup_directory.mkdir()
108
+ (backup_directory / "settings.json").write_bytes(original_bytes)
109
+ installed_settings = _install_twice(home, settings_path)
110
+ _assert_retired_registrations(managed_root, installed_settings, foreign_command)
111
+ assert (backup_directory / "settings.json").read_bytes() == original_bytes
112
+ hook_path = _git(home, "config", "--file", str(home / "gitconfig"), "--get", "core.hooksPath")
113
+ assert Path(hook_path).resolve() == (managed_root / "hooks/git-hooks").resolve()
114
+ return managed_root
115
+
116
+
117
+ @pytest.mark.parametrize(("rule_id", "relative_path", "content"), ALL_RULE_FIXTURES)
118
+ def test_installed_native_commit_catches_each_retired_file_rule(
119
+ managed_installation: Path,
120
+ repository_root: Path,
121
+ monkeypatch: pytest.MonkeyPatch,
122
+ rule_id: str,
123
+ relative_path: str,
124
+ content: str,
125
+ ) -> None:
126
+ _git(repository_root, "config", "core.hooksPath", str(managed_installation / "hooks/git-hooks"))
127
+ monkeypatch.delenv("CLAUDE_CONFIG_DIR", raising=False)
128
+ monkeypatch.setenv("CODE_RULES_GATE_PATH", str(repository_root / "missing-old-gate.py"))
129
+ before = _git(repository_root, "rev-parse", "HEAD")
130
+ _stage(repository_root, relative_path, content)
131
+ completed = subprocess.run(
132
+ ["git", "commit", "-m", "invalid fixture"],
133
+ cwd=repository_root, capture_output=True, text=True, check=False, timeout=240,
134
+ )
135
+ assert completed.returncode != 0
136
+ assert rule_id in completed.stdout + completed.stderr
137
+ assert _git(repository_root, "rev-parse", "HEAD") == before
138
+
139
+
140
+ def test_installed_linter_and_native_commit_allow_valid_changes(
141
+ managed_installation: Path,
142
+ repository_root: Path,
143
+ monkeypatch: pytest.MonkeyPatch,
144
+ ) -> None:
145
+ _git(repository_root, "config", "core.hooksPath", str(managed_installation / "hooks/git-hooks"))
146
+ monkeypatch.delenv("CLAUDE_CONFIG_DIR", raising=False)
147
+ monkeypatch.setenv("CODE_RULES_GATE_PATH", str(repository_root / "missing-old-gate.py"))
148
+ _stage(repository_root, "docs/guide.md", "# Guide\n\nThe API uses port 8080.\n")
149
+ completed = subprocess.run(
150
+ [sys.executable, str(managed_installation / "scripts/cde_lint.py"), "--staged", "--format", "json"],
151
+ cwd=repository_root, capture_output=True, text=True, check=False, timeout=240,
152
+ )
153
+ assert completed.returncode == 0, completed.stdout + completed.stderr
154
+ report = json.loads(completed.stdout)
155
+ assert report["failed_rules"] == []
156
+ assert "terminology-sweep" in report["executed_rules"]
157
+ before = _git(repository_root, "rev-parse", "HEAD")
158
+ _git(repository_root, "commit", "-m", "valid installed fixture")
159
+ assert _git(repository_root, "rev-parse", "HEAD") != before
@@ -0,0 +1,87 @@
1
+ """Exercise the native Git commit boundary in disposable repositories."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import shlex
7
+ import subprocess
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ import pre_commit
12
+ import pytest
13
+ from test_pre_commit import _git, _stage
14
+ from test_pre_commit import repository_root as repository_root
15
+
16
+
17
+ @pytest.fixture()
18
+ def installed_native_hook(
19
+ repository_root: Path,
20
+ monkeypatch: pytest.MonkeyPatch,
21
+ ) -> Path:
22
+ """Install a native fixture shim pointing to the actual pre-commit owner."""
23
+ hook_directory = repository_root / ".git" / "fixture-native-hooks"
24
+ hook_directory.mkdir()
25
+ hook_path = hook_directory / "pre-commit"
26
+ command = " ".join(
27
+ shlex.quote(each_argument)
28
+ for each_argument in (sys.executable, str(Path(pre_commit.__file__).resolve()))
29
+ )
30
+ hook_path.write_text(f"#!/bin/sh\nexec {command}\n", encoding="utf-8")
31
+ hook_path.chmod(0o755)
32
+ _git(repository_root, "config", "core.hooksPath", str(hook_directory))
33
+ monkeypatch.setenv("CODE_RULES_GATE_PATH", str(repository_root / "missing-legacy-gate.py"))
34
+ return repository_root
35
+
36
+
37
+ def test_valid_commit_passes_the_installed_native_hook(installed_native_hook: Path) -> None:
38
+ before = _git(installed_native_hook, "rev-parse", "HEAD")
39
+ _stage(installed_native_hook, "docs/config.md", "The API uses port 8080.\n")
40
+ _git(installed_native_hook, "commit", "-m", "valid fixture")
41
+ assert _git(installed_native_hook, "rev-parse", "HEAD") != before
42
+
43
+
44
+ def _assert_write_proceeds(repository_root: Path, relative_path: str, content: str) -> None:
45
+ """Exercise the actual dispatcher before attempting the staged commit."""
46
+ hooks_root = Path(pre_commit.__file__).resolve().parent.parent
47
+ payload = {
48
+ "tool_name": "Write",
49
+ "cwd": str(repository_root),
50
+ "tool_input": {
51
+ "file_path": str(repository_root / relative_path),
52
+ "content": content,
53
+ },
54
+ }
55
+ edit_result = subprocess.run(
56
+ [sys.executable, str(hooks_root / "blocking/pre_tool_use_dispatcher.py")],
57
+ cwd=repository_root,
58
+ input=json.dumps(payload),
59
+ capture_output=True,
60
+ text=True,
61
+ check=False,
62
+ timeout=90,
63
+ )
64
+ assert edit_result.returncode == 0, edit_result.stderr
65
+ edit_output = json.loads(edit_result.stdout or "{}")
66
+ assert edit_output.get("hookSpecificOutput", {}).get("permissionDecision") != "deny"
67
+
68
+
69
+ def test_retired_write_check_allows_edit_but_local_commit_catches_violation(
70
+ installed_native_hook: Path,
71
+ ) -> None:
72
+ relative_path = "docs/config.md"
73
+ content = "Previously set via env var.\n"
74
+ _assert_write_proceeds(installed_native_hook, relative_path, content)
75
+ before = _git(installed_native_hook, "rev-parse", "HEAD")
76
+ _stage(installed_native_hook, relative_path, content)
77
+ commit_result = subprocess.run(
78
+ ["git", "commit", "-m", "invalid fixture"],
79
+ cwd=installed_native_hook,
80
+ capture_output=True,
81
+ text=True,
82
+ check=False,
83
+ timeout=240,
84
+ )
85
+ assert commit_result.returncode != 0
86
+ assert "state-description" in commit_result.stdout + commit_result.stderr
87
+ assert _git(installed_native_hook, "rev-parse", "HEAD") == before
@@ -1,10 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import json
4
+ import os
5
+ import subprocess
6
+ import sys
3
7
  from pathlib import Path
4
8
 
5
9
  import pre_commit
6
10
  import pytest
7
11
 
12
+ from git_hooks_constants.staged_policy_lint import POLICY_LINT_TIMEOUT_SECONDS
13
+
8
14
 
9
15
  def make_gate_script_returning(exit_code: int, target_path: Path) -> Path:
10
16
  target_path.write_text(
@@ -170,3 +176,243 @@ def test_invoke_gate_uses_resolved_path(
170
176
  assert exit_code == 0
171
177
  executed_path = recorded_path_file.read_text(encoding="utf-8")
172
178
  assert executed_path == str(resolved_path)
179
+
180
+
181
+ RMTREE_FIXTURE_KEYWORD = "ignore_errors"
182
+ ALL_RULE_FIXTURES = (
183
+ (
184
+ "code-rules",
185
+ "src/worker.py",
186
+ "from typing import Any\n\ndef worker() -> Any:\n return None\n",
187
+ ),
188
+ (
189
+ "test-pairing",
190
+ "src/feature.py",
191
+ "def work() -> None:\n pass\n",
192
+ ),
193
+ (
194
+ "rmtree-safety",
195
+ "src/cleanup.py",
196
+ f"import shutil\nshutil.rmtree('fixture', {RMTREE_FIXTURE_KEYWORD}=True)\n",
197
+ ),
198
+ (
199
+ "state-description",
200
+ "docs/config.md",
201
+ "# Config\n\nPreviously set via env var.\n",
202
+ ),
203
+ (
204
+ "subprocess-budget",
205
+ "src/timing.py",
206
+ "import subprocess\n"
207
+ "PYTHON_FORMAT_TIMEOUT_SECONDS = 12\n"
208
+ "GIT_CHECK_TIMEOUT_SECONDS = 5\n"
209
+ "def worst_case_python_format_seconds() -> int:\n"
210
+ " fix_phase_seconds = PYTHON_FORMAT_TIMEOUT_SECONDS\n"
211
+ " format_phase_seconds = PYTHON_FORMAT_TIMEOUT_SECONDS\n"
212
+ " return fix_phase_seconds + format_phase_seconds\n"
213
+ "def is_untracked_in_git(file_path: str) -> bool:\n"
214
+ " git_check = subprocess.run(['git', 'ls-files', file_path], "
215
+ "timeout=GIT_CHECK_TIMEOUT_SECONDS)\n"
216
+ " return git_check.returncode != 0\n"
217
+ "def run_format(file_path: str) -> None:\n"
218
+ " subprocess.run(['ruff', 'format', file_path], "
219
+ "timeout=PYTHON_FORMAT_TIMEOUT_SECONDS)\n"
220
+ "def main(file_path: str) -> None:\n"
221
+ " if is_untracked_in_git(file_path):\n"
222
+ " return\n"
223
+ " run_format(file_path)\n",
224
+ ),
225
+ (
226
+ "hook-prose-consistency",
227
+ "hooks/hooks_constants/probe_constants.py",
228
+ 'CORRECTIVE_MESSAGE = "appears as a path or output-key segment"\n',
229
+ ),
230
+ (
231
+ "workflow-substitution",
232
+ "scripts/sample.workflow.js",
233
+ "For EACH candidate i, build a bible dir cand_i per the contract.\n"
234
+ " & ${PY} compose.py --out ${args.work_dir}\\\\cand_i\\\\sample.png "
235
+ "--glow <candidate glow_hex>\n"
236
+ 'Return: {key: "cand_i", name, sample_png}\n',
237
+ ),
238
+ )
239
+
240
+
241
+ def _git(repository_root: Path, *all_arguments: str) -> str:
242
+ completed = subprocess.run(
243
+ ["git", *all_arguments],
244
+ cwd=repository_root,
245
+ check=True,
246
+ capture_output=True,
247
+ text=True,
248
+ timeout=30,
249
+ )
250
+ return completed.stdout.strip()
251
+
252
+
253
+ @pytest.fixture()
254
+ def repository_root(
255
+ tmp_path_factory: pytest.TempPathFactory, monkeypatch: pytest.MonkeyPatch,
256
+ ) -> Path:
257
+ """Create a repository with private Git configuration and no inherited hooks."""
258
+ tmp_path = tmp_path_factory.mktemp("policy-fixture")
259
+ home_directory = tmp_path / "home"
260
+ home_directory.mkdir()
261
+ monkeypatch.setenv("HOME", str(home_directory))
262
+ monkeypatch.setenv("USERPROFILE", str(home_directory))
263
+ monkeypatch.setenv("GIT_CONFIG_GLOBAL", os.devnull)
264
+ monkeypatch.setenv("GIT_CONFIG_NOSYSTEM", "1")
265
+ monkeypatch.delenv("GIT_INDEX_FILE", raising=False)
266
+ monkeypatch.delenv("GIT_DIR", raising=False)
267
+ monkeypatch.delenv("GIT_WORK_TREE", raising=False)
268
+ root = tmp_path / "repository"
269
+ root.mkdir()
270
+ _git(root, "init")
271
+ _git(root, "config", "core.hooksPath", str(tmp_path / "disabled-fixture-hooks"))
272
+ _git(root, "config", "user.name", "Fixture")
273
+ _git(root, "config", "user.email", "fixture@example.com")
274
+ _git(root, "commit", "--allow-empty", "-m", "fixture base")
275
+ monkeypatch.chdir(root)
276
+ return root
277
+
278
+
279
+ def _stage(repository_root: Path, relative_path: str, content: str) -> Path:
280
+ file_path = repository_root / relative_path
281
+ file_path.parent.mkdir(parents=True, exist_ok=True)
282
+ file_path.write_text(content, encoding="utf-8")
283
+ _git(repository_root, "add", "--", relative_path)
284
+ return file_path
285
+
286
+
287
+ def _lint_report(repository_root: Path) -> tuple[int, dict[str, object]]:
288
+ completed = subprocess.run(
289
+ [
290
+ sys.executable,
291
+ str(pre_commit.resolve_policy_lint_script_path()),
292
+ "--staged",
293
+ "--format",
294
+ "json",
295
+ ],
296
+ cwd=repository_root,
297
+ check=False,
298
+ capture_output=True,
299
+ text=True,
300
+ timeout=POLICY_LINT_TIMEOUT_SECONDS,
301
+ )
302
+ assert completed.stderr == "", completed.stderr
303
+ return completed.returncode, json.loads(completed.stdout)
304
+
305
+
306
+ @pytest.mark.parametrize(("rule_id", "relative_path", "content"), ALL_RULE_FIXTURES)
307
+ def test_real_staged_linter_catches_each_replaced_rule(
308
+ repository_root: Path,
309
+ rule_id: str,
310
+ relative_path: str,
311
+ content: str,
312
+ ) -> None:
313
+ _stage(repository_root, relative_path, content)
314
+ exit_code, report = _lint_report(repository_root)
315
+ assert exit_code == 1, report
316
+ assert report["failed_rules"] == [], report
317
+ all_diagnostics = report["diagnostics"]
318
+ assert isinstance(all_diagnostics, list)
319
+ assert rule_id in {each["rule_id"] for each in all_diagnostics}, report
320
+
321
+
322
+ def _stage_valid_pair(repository_root: Path, relative_path: str) -> None:
323
+ source_path = Path(relative_path)
324
+ clean_content = {
325
+ ".py": (
326
+ "def is_ready() -> bool:\n"
327
+ ' """Report readiness.\n\n'
328
+ " Returns:\n True for the fixture.\n"
329
+ ' """\n'
330
+ " return True\n"
331
+ ),
332
+ ".js": "export const isEnabled = true;\n",
333
+ ".md": "# Config\n\nThe API uses port 8080.\n",
334
+ }[source_path.suffix]
335
+ _stage(repository_root, relative_path, clean_content)
336
+ paired_name = (
337
+ f"test_{source_path.stem}.py"
338
+ if source_path.suffix == ".py"
339
+ else f"{source_path.stem}.test{source_path.suffix}"
340
+ )
341
+ paired_content = (
342
+ f"from {source_path.stem} import is_ready\n\n"
343
+ "def test_is_ready() -> None:\n assert is_ready()\n"
344
+ if source_path.suffix == ".py"
345
+ else ""
346
+ )
347
+ _stage(repository_root, (source_path.parent / paired_name).as_posix(), paired_content)
348
+
349
+
350
+ @pytest.mark.parametrize(("rule_id", "relative_path", "content"), ALL_RULE_FIXTURES)
351
+ def test_valid_paired_content_passes_the_real_local_commit_linter(
352
+ repository_root: Path,
353
+ rule_id: str,
354
+ relative_path: str,
355
+ content: str,
356
+ ) -> None:
357
+ _stage_valid_pair(repository_root, relative_path)
358
+ assert pre_commit.run_staged_policy_lint() == 0
359
+
360
+
361
+ def test_unstaged_fix_does_not_hide_staged_violation(repository_root: Path) -> None:
362
+ target = _stage(repository_root, "docs/config.md", "Previously set via env var.\n")
363
+ target.write_text("The API uses port 8080.\n", encoding="utf-8")
364
+ assert pre_commit.run_staged_policy_lint() == 1
365
+
366
+
367
+ def test_alternate_git_index_is_checked(
368
+ repository_root: Path,
369
+ monkeypatch: pytest.MonkeyPatch,
370
+ ) -> None:
371
+ alternate_index = repository_root / ".git" / "alternate-index"
372
+ monkeypatch.setenv("GIT_INDEX_FILE", str(alternate_index))
373
+ _git(repository_root, "read-tree", "HEAD")
374
+ _stage(repository_root, "docs/config.md", "Previously set via env var.\n")
375
+ assert pre_commit.run_staged_policy_lint() == 1
376
+ monkeypatch.delenv("GIT_INDEX_FILE")
377
+ assert pre_commit.run_staged_policy_lint() == 0
378
+
379
+
380
+ def test_missing_linter_does_not_allow_commit(
381
+ tmp_path: Path,
382
+ monkeypatch: pytest.MonkeyPatch,
383
+ ) -> None:
384
+ monkeypatch.setattr(
385
+ pre_commit, "resolve_policy_lint_script_path", lambda: tmp_path / "missing.py"
386
+ )
387
+ assert pre_commit.main() == 2
388
+
389
+
390
+ @pytest.mark.parametrize("exit_code", (1, 2, 3))
391
+ def test_linter_failure_reaches_git_before_legacy_gate(
392
+ monkeypatch: pytest.MonkeyPatch,
393
+ exit_code: int,
394
+ ) -> None:
395
+ monkeypatch.setattr(pre_commit, "run_staged_policy_lint", lambda: exit_code)
396
+ all_gate_calls: list[object] = []
397
+ monkeypatch.setattr(
398
+ pre_commit,
399
+ "resolve_gate_script_path",
400
+ lambda: all_gate_calls.append("unexpected gate"),
401
+ )
402
+ assert pre_commit.main() == exit_code
403
+ assert all_gate_calls == []
404
+
405
+
406
+ def test_timeout_is_an_infrastructure_failure(
407
+ tmp_path: Path,
408
+ monkeypatch: pytest.MonkeyPatch,
409
+ ) -> None:
410
+ script_path = tmp_path / "lint.py"
411
+ script_path.write_text("", encoding="utf-8")
412
+ monkeypatch.setattr(pre_commit, "resolve_policy_lint_script_path", lambda: script_path)
413
+
414
+ def timed_out(*all_arguments: object, **all_options: object) -> object:
415
+ raise subprocess.TimeoutExpired("lint fixture", POLICY_LINT_TIMEOUT_SECONDS)
416
+
417
+ monkeypatch.setattr(pre_commit.subprocess, "run", timed_out)
418
+ assert pre_commit.run_staged_policy_lint() == 2
package/hooks/hooks.json CHANGED
@@ -245,7 +245,7 @@
245
245
  ]
246
246
  },
247
247
  {
248
- "matcher": "Bash",
248
+ "matcher": "Bash|PowerShell",
249
249
  "hooks": [
250
250
  {
251
251
  "type": "command",
@@ -10,12 +10,25 @@ no second copy of either.
10
10
  from __future__ import annotations
11
11
 
12
12
  from hooks_constants.bash_pre_tool_use_dispatcher_constants import (
13
+ ALL_BASH_AND_POWERSHELL_TOOL_NAMES,
13
14
  ALL_BASH_ONLY_TOOL_NAMES,
14
15
  BashHostedHookEntry,
15
16
  )
16
17
 
17
- __all__ = ["ALL_BASH_POST_TOOL_USE_HOSTED_HOOK_ENTRIES"]
18
+ __all__ = [
19
+ "ALL_BASH_POST_TOOL_USE_HOSTED_HOOK_ENTRIES",
20
+ "POST_TOOL_USE_HOOK_EVENT_NAME",
21
+ "HOOK_SPECIFIC_OUTPUT_KEY",
22
+ "ADDITIONAL_CONTEXT_KEY",
23
+ "ADDITIONAL_CONTEXT_JOIN_SEPARATOR",
24
+ ]
18
25
 
19
26
  ALL_BASH_POST_TOOL_USE_HOSTED_HOOK_ENTRIES: tuple[BashHostedHookEntry, ...] = (
20
27
  BashHostedHookEntry("observability/test_failure_recorder.py", ALL_BASH_ONLY_TOOL_NAMES),
28
+ BashHostedHookEntry("advisory/pr_done_reminder.py", ALL_BASH_AND_POWERSHELL_TOOL_NAMES),
21
29
  )
30
+
31
+ POST_TOOL_USE_HOOK_EVENT_NAME: str = "PostToolUse"
32
+ HOOK_SPECIFIC_OUTPUT_KEY: str = "hookSpecificOutput"
33
+ ADDITIONAL_CONTEXT_KEY: str = "additionalContext"
34
+ ADDITIONAL_CONTEXT_JOIN_SEPARATOR: str = "\n\n"
@@ -0,0 +1,98 @@
1
+ """Constants for the PR done-checklist context reminder.
2
+
3
+ The reminder is a PostToolUse observer on Bash. It never blocks. After a
4
+ successful ``git push`` or ``gh pr create`` it probes the branch's pull request
5
+ through ``gh`` and adds one loud checklist to the agent's context.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ __all__ = [
11
+ "GIT_PROGRAM_NAME",
12
+ "GIT_PUSH_SUBCOMMAND",
13
+ "GH_PROGRAM_NAME",
14
+ "GH_PR_SUBCOMMAND",
15
+ "GH_PR_CREATE_ACTION",
16
+ "ALL_GIT_OPTIONS_WITH_VALUE",
17
+ "ALL_POWERSHELL_PROGRAM_NAMES",
18
+ "ALL_POWERSHELL_COMMAND_FLAGS",
19
+ "ALL_GH_PR_VIEW_ARGUMENTS",
20
+ "GH_PR_VIEW_TIMEOUT_SECONDS",
21
+ "NO_PULL_REQUEST_MARKER",
22
+ "EXIT_CODE_ERROR_PREFIX",
23
+ "DONE_LABEL_NAME",
24
+ "MERGEABLE_CLEAN_VALUE",
25
+ "MERGEABLE_CONFLICTING_VALUE",
26
+ "MERGEABLE_UNKNOWN_VALUE",
27
+ "ALL_FAILING_CHECK_CONCLUSIONS",
28
+ "ALL_PASSING_CHECK_CONCLUSIONS",
29
+ "CHECK_COMPLETED_STATUS",
30
+ "STATUS_CONTEXT_STATE_KEY",
31
+ "REMINDER_HEADER",
32
+ "REMINDER_FOOTER",
33
+ "REMINDER_LINE_SEPARATOR",
34
+ "ALL_REMINDER_HINTS_BY_MERGEABLE",
35
+ "VERDICT_DONE",
36
+ "VERDICT_NOT_DONE",
37
+ "NO_PULL_REQUEST_REMINDER",
38
+ "RECHECK_COMMAND_TEMPLATE",
39
+ "ADD_LABEL_COMMAND_TEMPLATE",
40
+ ]
41
+
42
+ GIT_PROGRAM_NAME: str = "git"
43
+ GIT_PUSH_SUBCOMMAND: str = "push"
44
+ GH_PROGRAM_NAME: str = "gh"
45
+ GH_PR_SUBCOMMAND: str = "pr"
46
+ GH_PR_CREATE_ACTION: str = "create"
47
+ ALL_GIT_OPTIONS_WITH_VALUE: frozenset[str] = frozenset({"-C", "-c"})
48
+ ALL_POWERSHELL_PROGRAM_NAMES: frozenset[str] = frozenset(
49
+ {"pwsh", "pwsh.exe", "powershell", "powershell.exe"}
50
+ )
51
+ ALL_POWERSHELL_COMMAND_FLAGS: frozenset[str] = frozenset({"-command", "-c"})
52
+
53
+ ALL_GH_PR_VIEW_ARGUMENTS: tuple[str, ...] = (
54
+ "gh",
55
+ "pr",
56
+ "view",
57
+ "--json",
58
+ "number,url,isDraft,mergeable,mergeStateStatus,statusCheckRollup,labels",
59
+ )
60
+ GH_PR_VIEW_TIMEOUT_SECONDS: int = 20
61
+ NO_PULL_REQUEST_MARKER: str = "no pull requests found"
62
+ EXIT_CODE_ERROR_PREFIX: str = "Error: Exit code "
63
+
64
+ DONE_LABEL_NAME: str = "done"
65
+
66
+ MERGEABLE_CLEAN_VALUE: str = "MERGEABLE"
67
+ MERGEABLE_CONFLICTING_VALUE: str = "CONFLICTING"
68
+ MERGEABLE_UNKNOWN_VALUE: str = "UNKNOWN"
69
+
70
+ ALL_FAILING_CHECK_CONCLUSIONS: frozenset[str] = frozenset(
71
+ {"FAILURE", "ERROR", "CANCELLED", "TIMED_OUT", "ACTION_REQUIRED", "STARTUP_FAILURE"}
72
+ )
73
+ ALL_PASSING_CHECK_CONCLUSIONS: frozenset[str] = frozenset({"SUCCESS", "NEUTRAL", "SKIPPED"})
74
+ CHECK_COMPLETED_STATUS: str = "COMPLETED"
75
+ STATUS_CONTEXT_STATE_KEY: str = "state"
76
+
77
+ REMINDER_HEADER: str = "=== PR DONE CHECKLIST (context reminder, never a block) ==="
78
+ REMINDER_FOOTER: str = "A PR is done only when every line above is clean."
79
+ REMINDER_LINE_SEPARATOR: str = "\n"
80
+ ALL_REMINDER_HINTS_BY_MERGEABLE: dict[str, str] = {
81
+ MERGEABLE_CLEAN_VALUE: "no conflicts with the base branch",
82
+ MERGEABLE_CONFLICTING_VALUE: (
83
+ "CONFLICTS with the base branch. Merge origin/main into this branch, "
84
+ "resolve every conflict, run the tests, push again."
85
+ ),
86
+ MERGEABLE_UNKNOWN_VALUE: "GitHub is still computing. Re-run the re-check command in a minute.",
87
+ }
88
+ VERDICT_DONE: str = "DONE. Add the label now:"
89
+ VERDICT_NOT_DONE: str = "NOT DONE. Do not add the done label yet."
90
+ NO_PULL_REQUEST_REMINDER: str = (
91
+ "=== PR DONE CHECKLIST (context reminder, never a block) ===\n"
92
+ "No open pull request found for this branch. Open a draft PR with gh pr create --draft.\n"
93
+ "A branch is done only when its PR has clean CI, no merge conflicts, and the done label."
94
+ )
95
+ RECHECK_COMMAND_TEMPLATE: str = (
96
+ "gh pr view {number} --json mergeable,mergeStateStatus,statusCheckRollup,labels"
97
+ )
98
+ ADD_LABEL_COMMAND_TEMPLATE: str = "gh pr edit {number} --add-label {label}"