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,249 @@
1
+ """Behavior tests for the PR done-checklist context reminder.
2
+
3
+ The hook is a PostToolUse observer on Bash. It never blocks. These tests pin
4
+ three things: which commands wake it, what the checklist says for each live
5
+ PR state, and that a probe failure stays quiet. The ``gh`` probe is faked at
6
+ the subprocess boundary so no test touches the network.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import io
12
+ import json
13
+ import subprocess
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ import pytest
18
+
19
+ try:
20
+ advisory_directory = str(Path(__file__).resolve().parent)
21
+ if advisory_directory not in sys.path:
22
+ sys.path.insert(0, advisory_directory)
23
+ import pr_done_reminder
24
+ except ImportError as import_error:
25
+ raise ImportError(
26
+ "test_pr_done_reminder: cannot import its sibling modules; "
27
+ "ensure the advisory directory is importable."
28
+ ) from import_error
29
+
30
+
31
+ _CLEAN_PR_OBJECT: dict[str, object] = {
32
+ "number": 42,
33
+ "url": "https://github.com/acme/widgets/pull/42",
34
+ "isDraft": True,
35
+ "mergeable": "MERGEABLE",
36
+ "mergeStateStatus": "CLEAN",
37
+ "statusCheckRollup": [
38
+ {"name": "pytest", "status": "COMPLETED", "conclusion": "SUCCESS"},
39
+ {"context": "lint", "state": "SUCCESS"},
40
+ ],
41
+ "labels": [],
42
+ }
43
+
44
+
45
+ def _payload(command: str, tool_response: object = None) -> str:
46
+ response: object = {"stdout": "", "stderr": ""} if tool_response is None else tool_response
47
+ return json.dumps(
48
+ {
49
+ "session_id": "reminder-session",
50
+ "cwd": "C:/repo",
51
+ "tool_name": "Bash",
52
+ "tool_input": {"command": command},
53
+ "tool_response": response,
54
+ }
55
+ )
56
+
57
+
58
+ def _fake_gh(pr_object: dict[str, object] | None, stderr: str = "") -> object:
59
+ def _run(*args: object, **kwargs: object) -> subprocess.CompletedProcess[str]:
60
+ del args, kwargs
61
+ if pr_object is None:
62
+ return subprocess.CompletedProcess([], 1, stdout="", stderr=stderr)
63
+ return subprocess.CompletedProcess([], 0, stdout=json.dumps(pr_object), stderr="")
64
+
65
+ return _run
66
+
67
+
68
+ def _run_main(
69
+ monkeypatch: pytest.MonkeyPatch,
70
+ capsys: pytest.CaptureFixture[str],
71
+ payload_text: str,
72
+ fake_gh: object,
73
+ ) -> str:
74
+ monkeypatch.setattr(pr_done_reminder.subprocess, "run", fake_gh)
75
+ monkeypatch.setattr(sys, "stdin", io.StringIO(payload_text))
76
+ pr_done_reminder.main()
77
+ return capsys.readouterr().out
78
+
79
+
80
+ def _additional_context(stdout_text: str) -> str:
81
+ return str(json.loads(stdout_text)["hookSpecificOutput"]["additionalContext"])
82
+
83
+
84
+ @pytest.mark.parametrize(
85
+ "command",
86
+ [
87
+ "git push",
88
+ "git push -u origin feat/x",
89
+ "git -C C:/repo push origin HEAD",
90
+ "gh pr create --draft --title t --body-file b.md",
91
+ "git add -A && git commit -F m.txt && git push",
92
+ "git -c push.default=current push",
93
+ "git --git-dir=C:/repo/.git push",
94
+ 'pwsh -NoProfile -Command "git push -u origin feat/x"',
95
+ "pwsh -NoProfile -Command 'gh pr create --draft --fill'",
96
+ 'powershell -Command "git add -A; git push"',
97
+ ],
98
+ )
99
+ def test_should_wake_on_git_push_and_gh_pr_create(command: str) -> None:
100
+ assert pr_done_reminder.command_triggers_reminder(command) is True
101
+
102
+
103
+ @pytest.mark.parametrize(
104
+ "command",
105
+ [
106
+ "git status",
107
+ "git pull",
108
+ "git stash push -u -q -- packages",
109
+ "git log --grep push",
110
+ "git commit -m push",
111
+ "gh pr view 42",
112
+ "gh pr ready --undo",
113
+ "echo git push",
114
+ 'pwsh -NoProfile -Command "git status"',
115
+ "pytest tests/",
116
+ ],
117
+ )
118
+ def test_should_stay_quiet_for_other_commands(command: str) -> None:
119
+ assert pr_done_reminder.command_triggers_reminder(command) is False
120
+
121
+
122
+ def test_should_say_done_when_mergeable_and_checks_pass() -> None:
123
+ context = pr_done_reminder.build_reminder_context(_CLEAN_PR_OBJECT)
124
+
125
+ assert context.startswith("=== PR DONE CHECKLIST")
126
+ assert "never a block" in context
127
+ assert "DONE. Add the label now:" in context
128
+ assert "gh pr edit 42 --add-label done" in context
129
+ assert "NOT DONE" not in context
130
+
131
+
132
+ def test_should_keep_the_checklist_order_verdict_then_recheck_then_footer() -> None:
133
+ all_lines = pr_done_reminder.build_reminder_context(_CLEAN_PR_OBJECT).split("\n")
134
+
135
+ all_labels = [each_line.split(":")[0] for each_line in all_lines[1:-1]]
136
+ assert all_labels == [
137
+ "PR #42 https",
138
+ "Mergeable",
139
+ "CI checks",
140
+ "Draft",
141
+ "Label done",
142
+ "Verdict",
143
+ "Add label",
144
+ "Re-check",
145
+ ]
146
+ assert all_lines[-1] == "A PR is done only when every line above is clean."
147
+
148
+
149
+ def test_should_say_not_done_and_name_the_conflict_fix() -> None:
150
+ conflicted = {**_CLEAN_PR_OBJECT, "mergeable": "CONFLICTING", "mergeStateStatus": "DIRTY"}
151
+
152
+ context = pr_done_reminder.build_reminder_context(conflicted)
153
+
154
+ assert "NOT DONE" in context
155
+ assert "CONFLICTS with the base branch" in context
156
+ assert "gh pr view 42 --json mergeable" in context
157
+ assert "gh pr edit 42 --add-label" not in context
158
+
159
+
160
+ def test_should_count_failing_and_pending_checks() -> None:
161
+ mixed_checks = {
162
+ **_CLEAN_PR_OBJECT,
163
+ "statusCheckRollup": [
164
+ {"name": "pytest", "status": "COMPLETED", "conclusion": "FAILURE"},
165
+ {"name": "mypy", "status": "IN_PROGRESS", "conclusion": ""},
166
+ {"context": "lint", "state": "PENDING"},
167
+ {"context": "docs", "state": "SUCCESS"},
168
+ ],
169
+ }
170
+
171
+ context = pr_done_reminder.build_reminder_context(mixed_checks)
172
+
173
+ assert "1 failing" in context
174
+ assert "2 pending" in context
175
+ assert "4 total" in context
176
+ assert "NOT DONE" in context
177
+
178
+
179
+ def test_should_tell_the_agent_to_wait_when_github_is_still_computing() -> None:
180
+ unknown = {**_CLEAN_PR_OBJECT, "mergeable": "UNKNOWN", "mergeStateStatus": "UNKNOWN"}
181
+
182
+ context = pr_done_reminder.build_reminder_context(unknown)
183
+
184
+ assert "still computing" in context
185
+ assert "NOT DONE" in context
186
+
187
+
188
+ def test_should_report_the_done_label_when_already_set() -> None:
189
+ labeled = {**_CLEAN_PR_OBJECT, "labels": [{"name": "done"}]}
190
+
191
+ context = pr_done_reminder.build_reminder_context(labeled)
192
+
193
+ assert "Label done: set" in context
194
+
195
+
196
+ def test_main_should_emit_additional_context_after_a_successful_push(
197
+ monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
198
+ ) -> None:
199
+ stdout_text = _run_main(monkeypatch, capsys, _payload("git push"), _fake_gh(_CLEAN_PR_OBJECT))
200
+
201
+ payload = json.loads(stdout_text)
202
+ assert payload["hookSpecificOutput"]["hookEventName"] == "PostToolUse"
203
+ assert "permissionDecision" not in payload["hookSpecificOutput"]
204
+ assert "decision" not in payload
205
+ assert "PR #42" in _additional_context(stdout_text)
206
+
207
+
208
+ def test_main_should_also_serve_the_powershell_tool(
209
+ monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
210
+ ) -> None:
211
+ payload = json.loads(_payload("git push"))
212
+ payload["tool_name"] = "PowerShell"
213
+
214
+ stdout_text = _run_main(monkeypatch, capsys, json.dumps(payload), _fake_gh(_CLEAN_PR_OBJECT))
215
+
216
+ assert "PR #42" in _additional_context(stdout_text)
217
+
218
+
219
+ def test_main_should_stay_quiet_when_the_push_failed(
220
+ monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
221
+ ) -> None:
222
+ failed_push = _payload("git push", "Error: Exit code 1\nrejected")
223
+
224
+ assert _run_main(monkeypatch, capsys, failed_push, _fake_gh(_CLEAN_PR_OBJECT)) == ""
225
+
226
+
227
+ def test_main_should_stay_quiet_for_a_non_trigger_command(
228
+ monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
229
+ ) -> None:
230
+ assert _run_main(monkeypatch, capsys, _payload("git status"), _fake_gh(_CLEAN_PR_OBJECT)) == ""
231
+
232
+
233
+ def test_main_should_remind_to_open_a_pr_when_the_branch_has_none(
234
+ monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
235
+ ) -> None:
236
+ no_pr = _fake_gh(None, stderr='no pull requests found for branch "feat/x"')
237
+
238
+ context = _additional_context(_run_main(monkeypatch, capsys, _payload("git push"), no_pr))
239
+
240
+ assert "No open pull request found" in context
241
+ assert "gh pr create --draft" in context
242
+
243
+
244
+ def test_main_should_stay_quiet_on_any_other_gh_failure(
245
+ monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
246
+ ) -> None:
247
+ gh_down = _fake_gh(None, stderr="error connecting to api.github.com")
248
+
249
+ assert _run_main(monkeypatch, capsys, _payload("git push"), gh_down) == ""
@@ -14,9 +14,11 @@ collision, and needs no change to that shared, out-of-scope test file.
14
14
  Reads the tool payload from stdin once, selects the hosted hooks applicable to
15
15
  the payload's tool name, and runs each one in-process via the shared
16
16
  hosted-hook runner. A hosted PostToolUse hook here carries no permission
17
- decision -- it is a side-effecting observer, not a gate -- so this dispatcher
18
- runs the whole roster unconditionally and emits nothing itself, matching every
19
- hosted hook's own empty-stdout contract.
17
+ decision -- it is an observer, not a gate -- so this dispatcher runs the whole
18
+ roster unconditionally and never blocks. The one thing it forwards is context:
19
+ a hosted hook may print a ``hookSpecificOutput.additionalContext`` string (the
20
+ PR done reminder does), and the dispatcher joins every such string into one
21
+ PostToolUse payload. A hook that prints nothing adds nothing.
20
22
 
21
23
  A single hosted hook crash fails open: ``run_hook_capturing_output`` isolates
22
24
  it, so it contributes nothing and does not stop the remaining hosted hooks.
@@ -27,11 +29,17 @@ imports load once into this process rather than once per hook.
27
29
 
28
30
  from __future__ import annotations
29
31
 
32
+ import json
33
+ import sys
30
34
 
31
35
  import _path_setup # noqa: F401
32
36
 
33
37
  from hooks_constants.bash_post_call_dispatcher_constants import (
38
+ ADDITIONAL_CONTEXT_JOIN_SEPARATOR,
39
+ ADDITIONAL_CONTEXT_KEY,
34
40
  ALL_BASH_POST_TOOL_USE_HOSTED_HOOK_ENTRIES,
41
+ HOOK_SPECIFIC_OUTPUT_KEY,
42
+ POST_TOOL_USE_HOOK_EVENT_NAME,
35
43
  )
36
44
  from hooks_constants.bash_pre_tool_use_dispatcher_constants import BashHostedHookEntry
37
45
  from hooks_constants.hosted_hook_runner import (
@@ -50,15 +58,54 @@ def select_applicable_entries(tool_name: str) -> list[BashHostedHookEntry]:
50
58
  ]
51
59
 
52
60
 
61
+ def additional_context_from_hook_output(captured_stdout: str) -> str | None:
62
+ """Return the additionalContext string a hosted hook printed, or None.
63
+
64
+ Empty output, non-JSON output, and JSON without a non-empty
65
+ ``hookSpecificOutput.additionalContext`` string all return None.
66
+ """
67
+ if not captured_stdout.strip():
68
+ return None
69
+ try:
70
+ hook_output = json.loads(captured_stdout)
71
+ except json.JSONDecodeError:
72
+ return None
73
+ if not isinstance(hook_output, dict):
74
+ return None
75
+ hook_specific_output = hook_output.get(HOOK_SPECIFIC_OUTPUT_KEY)
76
+ if not isinstance(hook_specific_output, dict):
77
+ return None
78
+ additional_context = hook_specific_output.get(ADDITIONAL_CONTEXT_KEY)
79
+ if not isinstance(additional_context, str) or not additional_context.strip():
80
+ return None
81
+ return additional_context
82
+
83
+
53
84
  def dispatch(payload_text: str, tool_name: str) -> None:
54
- """Run every hosted hook applicable to tool_name, in registration order.
85
+ """Run every hosted hook applicable to tool_name, then forward their context.
55
86
 
56
87
  Each hosted hook's crash is isolated by ``run_hook_capturing_output``, so
57
- one hook failing never stops a later one in the same roster.
88
+ one hook failing never stops a later one in the same roster. Every
89
+ additionalContext string the hooks printed is joined into one PostToolUse
90
+ payload on stdout; when no hook printed one, nothing is written.
58
91
  """
92
+ all_additional_context: list[str] = []
59
93
  for each_entry in select_applicable_entries(tool_name):
60
94
  script_path = resolved_hook_script_path(each_entry.script_relative_path)
61
- run_hook_capturing_output(script_path, payload_text)
95
+ hook_run = run_hook_capturing_output(script_path, payload_text)
96
+ additional_context = additional_context_from_hook_output(hook_run.captured_stdout)
97
+ if additional_context is not None:
98
+ all_additional_context.append(additional_context)
99
+ if not all_additional_context:
100
+ return
101
+ payload = {
102
+ HOOK_SPECIFIC_OUTPUT_KEY: {
103
+ "hookEventName": POST_TOOL_USE_HOOK_EVENT_NAME,
104
+ ADDITIONAL_CONTEXT_KEY: ADDITIONAL_CONTEXT_JOIN_SEPARATOR.join(all_additional_context),
105
+ }
106
+ }
107
+ sys.stdout.write(json.dumps(payload))
108
+ sys.stdout.flush()
62
109
 
63
110
 
64
111
  def main() -> None:
@@ -215,12 +215,27 @@ def _collect_referenced_identifiers(source: str) -> set[str]:
215
215
  return all_referenced_identifiers
216
216
 
217
217
 
218
+ def _sibling_test_paths(stem_test_path: Path) -> list[Path]:
219
+ """Return the capped, sorted test files beside the stem test, stem excluded."""
220
+ suite_directory = stem_test_path.parent
221
+ stem_path_key = os.path.normcase(str(stem_test_path.resolve()))
222
+ all_sibling_paths: list[Path] = []
223
+ for each_glob in ALL_TEST_FILENAME_GLOBS:
224
+ all_sibling_paths.extend(
225
+ each_path
226
+ for each_path in sorted(suite_directory.glob(each_glob))
227
+ if os.path.normcase(str(each_path.resolve())) != stem_path_key
228
+ )
229
+ return all_sibling_paths[:MAX_TEST_FILES_SCANNED]
230
+
231
+
218
232
  def _suite_referenced_identifiers(stem_test_path: Path) -> set[str]:
219
233
  """Return identifiers referenced across every test file beside the stem test.
220
234
 
221
- Scans every ``test_*.py`` and ``*_test.py`` file in the directory holding the
222
- stem-matched test file, bounded by the scan cap, and unions the identifiers
223
- each one references.
235
+ Reads the stem-matched test file first, then scans every other ``test_*.py``
236
+ and ``*_test.py`` file in its directory, bounded by the scan cap, and unions
237
+ the identifiers each one references. The stem test always counts, so a
238
+ directory with more test files than the cap cannot hide it.
224
239
 
225
240
  Args:
226
241
  stem_test_path: The stem-matched test file whose directory is scanned.
@@ -228,12 +243,9 @@ def _suite_referenced_identifiers(stem_test_path: Path) -> set[str]:
228
243
  Returns:
229
244
  The union of referenced identifiers across the scanned test files.
230
245
  """
231
- suite_directory = stem_test_path.parent
232
- all_test_file_paths: list[Path] = []
233
- for each_glob in ALL_TEST_FILENAME_GLOBS:
234
- all_test_file_paths.extend(sorted(suite_directory.glob(each_glob)))
246
+ all_test_file_paths = [stem_test_path, *_sibling_test_paths(stem_test_path)]
235
247
  all_referenced_identifiers: set[str] = set()
236
- for each_test_file_path in all_test_file_paths[:MAX_TEST_FILES_SCANNED]:
248
+ for each_test_file_path in all_test_file_paths:
237
249
  try:
238
250
  test_source = each_test_file_path.read_text(encoding="utf-8")
239
251
  except (OSError, UnicodeDecodeError):
@@ -364,16 +376,12 @@ def _production_stem_from_test_filename(test_filename: str) -> str | None:
364
376
  if test_filename.startswith(STEM_TEST_FILENAME_PREFIX) and test_filename.endswith(
365
377
  PYTHON_SOURCE_SUFFIX
366
378
  ):
367
- production_stem = test_filename[
368
- len(STEM_TEST_FILENAME_PREFIX) : -len(PYTHON_SOURCE_SUFFIX)
369
- ]
379
+ production_stem = test_filename[len(STEM_TEST_FILENAME_PREFIX) : -len(PYTHON_SOURCE_SUFFIX)]
370
380
  return production_stem or None
371
381
  return None
372
382
 
373
383
 
374
- def _paired_production_module_path(
375
- test_path: Path, production_stem: str
376
- ) -> Path | None:
384
+ def _paired_production_module_path(test_path: Path, production_stem: str) -> Path | None:
377
385
  """Return the production module a stem-matched test file pairs with, or None.
378
386
 
379
387
  Mirrors ``_stem_matched_test_path`` in reverse for the two common layouts: a
@@ -515,4 +523,3 @@ def check_test_file_omits_module_public_function(
515
523
  if len(all_omission_violations) >= MAX_PAIRED_TEST_COVERAGE_ISSUES:
516
524
  break
517
525
  return all_omission_violations
518
-
@@ -15,16 +15,26 @@ from pathlib import Path
15
15
 
16
16
  import pytest
17
17
 
18
- from bash_post_call_dispatcher import dispatch, main, select_applicable_entries
18
+ from bash_post_call_dispatcher import (
19
+ additional_context_from_hook_output,
20
+ dispatch,
21
+ main,
22
+ select_applicable_entries,
23
+ )
19
24
  from hooks_constants.bash_pre_tool_use_dispatcher_constants import BASH_TOOL_NAME
20
25
  from hooks_constants.hosted_hook_runner import HostedHookRun
21
26
  from tdd_enforcer_parts import content_hash_store
22
27
 
28
+ _ALL_EXPECTED_ROSTER_PATHS = [
29
+ "observability/test_failure_recorder.py",
30
+ "advisory/pr_done_reminder.py",
31
+ ]
23
32
 
24
- def test_select_applicable_entries_returns_the_hosted_hook_for_bash() -> None:
33
+
34
+ def test_select_applicable_entries_returns_the_hosted_hooks_for_bash() -> None:
25
35
  all_entries = select_applicable_entries(BASH_TOOL_NAME)
26
36
  all_script_paths = [each_entry.script_relative_path for each_entry in all_entries]
27
- assert all_script_paths == ["observability/test_failure_recorder.py"]
37
+ assert all_script_paths == _ALL_EXPECTED_ROSTER_PATHS
28
38
 
29
39
 
30
40
  def test_select_applicable_entries_returns_none_for_a_write_tool() -> None:
@@ -45,7 +55,58 @@ def test_dispatch_runs_every_hosted_hook_in_roster_order(
45
55
 
46
56
  dispatch('{"tool_name": "Bash"}', BASH_TOOL_NAME)
47
57
 
48
- assert all_call_paths == ["test_failure_recorder.py"]
58
+ assert all_call_paths == [Path(each_path).name for each_path in _ALL_EXPECTED_ROSTER_PATHS]
59
+
60
+
61
+ def test_dispatch_forwards_hosted_hook_additional_context_as_one_payload(
62
+ monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
63
+ ) -> None:
64
+ context_by_hook = {
65
+ "test_failure_recorder.py": "",
66
+ "pr_done_reminder.py": json.dumps(
67
+ {"hookSpecificOutput": {"hookEventName": "PostToolUse", "additionalContext": "PR #42"}}
68
+ ),
69
+ }
70
+
71
+ def _fake_run_hook(script_path: str, payload_text: str) -> HostedHookRun:
72
+ del payload_text
73
+ return HostedHookRun(
74
+ captured_stdout=context_by_hook[Path(script_path).name], did_crash=False
75
+ )
76
+
77
+ monkeypatch.setattr("bash_post_call_dispatcher.run_hook_capturing_output", _fake_run_hook)
78
+
79
+ dispatch('{"tool_name": "Bash"}', BASH_TOOL_NAME)
80
+
81
+ emitted = json.loads(capsys.readouterr().out)
82
+ assert emitted == {
83
+ "hookSpecificOutput": {"hookEventName": "PostToolUse", "additionalContext": "PR #42"}
84
+ }
85
+ assert "decision" not in emitted
86
+
87
+
88
+ def test_dispatch_writes_nothing_when_no_hosted_hook_printed_context(
89
+ monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
90
+ ) -> None:
91
+ def _fake_run_hook(script_path: str, payload_text: str) -> HostedHookRun:
92
+ del script_path, payload_text
93
+ return HostedHookRun(captured_stdout="", did_crash=False)
94
+
95
+ monkeypatch.setattr("bash_post_call_dispatcher.run_hook_capturing_output", _fake_run_hook)
96
+
97
+ dispatch('{"tool_name": "Bash"}', BASH_TOOL_NAME)
98
+
99
+ assert capsys.readouterr().out == ""
100
+
101
+
102
+ @pytest.mark.parametrize(
103
+ "captured_stdout",
104
+ ["", " ", "not json", "[]", '{"decision": "block"}', '{"hookSpecificOutput": {}}'],
105
+ )
106
+ def test_additional_context_from_hook_output_ignores_non_context_output(
107
+ captured_stdout: str,
108
+ ) -> None:
109
+ assert additional_context_from_hook_output(captured_stdout) is None
49
110
 
50
111
 
51
112
  def test_main_exits_zero_and_writes_nothing_to_stdout(
@@ -19,6 +19,10 @@ _BLOCKING_DIRECTORY = Path(__file__).resolve().parent
19
19
  if str(_BLOCKING_DIRECTORY) not in sys.path:
20
20
  sys.path.insert(0, str(_BLOCKING_DIRECTORY))
21
21
 
22
+ from hooks_constants.paired_test_coverage_constants import ( # noqa: E402
23
+ MAX_TEST_FILES_SCANNED,
24
+ )
25
+
22
26
  from code_rules_paired_test import ( # noqa: E402
23
27
  check_public_function_missing_paired_test,
24
28
  check_test_file_omits_module_public_function,
@@ -26,9 +30,7 @@ from code_rules_paired_test import ( # noqa: E402
26
30
 
27
31
  _TWO_PUBLIC_FUNCTIONS = "def alpha():\n return 1\n\n\ndef beta():\n return 2\n"
28
32
  _THREE_PUBLIC_FUNCTIONS = (
29
- "def alpha():\n return 1\n\n\n"
30
- "def beta():\n return 2\n\n\n"
31
- "def gamma():\n return 3\n"
33
+ "def alpha():\n return 1\n\n\ndef beta():\n return 2\n\n\ndef gamma():\n return 3\n"
32
34
  )
33
35
 
34
36
 
@@ -145,6 +147,28 @@ def test_counts_coverage_across_sibling_test_files(
145
147
  assert all_issues == []
146
148
 
147
149
 
150
+ def test_reads_the_stem_test_when_the_suite_exceeds_the_scan_cap(
151
+ neutral_package_directory: Path,
152
+ ) -> None:
153
+ filler_body = "def test_nothing():\n assert True\n"
154
+ _write_test_file(
155
+ neutral_package_directory,
156
+ "test_a0000.py",
157
+ "from pkg.mod import alpha\n\ndef test_alpha():\n assert alpha() == 1\n",
158
+ )
159
+ for each_index in range(1, MAX_TEST_FILES_SCANNED + 1):
160
+ _write_test_file(neutral_package_directory, f"test_a{each_index:04d}.py", filler_body)
161
+ _write_test_file(
162
+ neutral_package_directory,
163
+ "test_mod.py",
164
+ "from pkg.mod import beta\n\ndef test_beta():\n assert beta() == 2\n",
165
+ )
166
+ all_issues = check_public_function_missing_paired_test(
167
+ _TWO_PUBLIC_FUNCTIONS, str(neutral_package_directory / "mod.py")
168
+ )
169
+ assert all_issues == []
170
+
171
+
148
172
  def test_main_and_private_functions_are_never_required(
149
173
  neutral_package_directory: Path,
150
174
  ) -> None:
@@ -183,8 +207,7 @@ def _write_module(package_directory: Path, body: str) -> None:
183
207
 
184
208
 
185
209
  _SUITE_COVERS_ALPHA_BETA = (
186
- "from pkg.mod import alpha, beta\n\n"
187
- "def test_pair():\n assert alpha() == 1 and beta() == 2\n"
210
+ "from pkg.mod import alpha, beta\n\ndef test_pair():\n assert alpha() == 1 and beta() == 2\n"
188
211
  )
189
212
 
190
213
 
@@ -193,9 +216,7 @@ def test_flags_module_public_function_when_test_suite_omits_it(
193
216
  ) -> None:
194
217
  _write_module(neutral_package_directory, _THREE_PUBLIC_FUNCTIONS)
195
218
  test_path = str(neutral_package_directory / "tests" / "test_mod.py")
196
- all_issues = check_test_file_omits_module_public_function(
197
- _SUITE_COVERS_ALPHA_BETA, test_path
198
- )
219
+ all_issues = check_test_file_omits_module_public_function(_SUITE_COVERS_ALPHA_BETA, test_path)
199
220
  assert len(all_issues) == 1
200
221
  assert "gamma" in all_issues[0]
201
222
  assert "mod.py" in all_issues[0]
@@ -231,9 +252,7 @@ def test_skips_test_file_with_no_paired_production_module(
231
252
  neutral_package_directory: Path,
232
253
  ) -> None:
233
254
  test_path = str(neutral_package_directory / "tests" / "test_mod.py")
234
- all_issues = check_test_file_omits_module_public_function(
235
- _SUITE_COVERS_ALPHA_BETA, test_path
236
- )
255
+ all_issues = check_test_file_omits_module_public_function(_SUITE_COVERS_ALPHA_BETA, test_path)
237
256
  assert all_issues == []
238
257
 
239
258
 
@@ -242,9 +261,7 @@ def test_ignores_a_non_stem_matched_written_file(
242
261
  ) -> None:
243
262
  _write_module(neutral_package_directory, _THREE_PUBLIC_FUNCTIONS)
244
263
  helper_path = str(neutral_package_directory / "helper.py")
245
- all_issues = check_test_file_omits_module_public_function(
246
- _SUITE_COVERS_ALPHA_BETA, helper_path
247
- )
264
+ all_issues = check_test_file_omits_module_public_function(_SUITE_COVERS_ALPHA_BETA, helper_path)
248
265
  assert all_issues == []
249
266
 
250
267
 
@@ -274,9 +291,7 @@ def test_judges_post_edit_content_over_stale_on_disk_test(
274
291
  "from pkg.mod import alpha, beta, gamma\n\n"
275
292
  "def test_all():\n assert alpha() and beta() and gamma()\n"
276
293
  )
277
- all_issues = check_test_file_omits_module_public_function(
278
- post_edit_covers_all, test_path
279
- )
294
+ all_issues = check_test_file_omits_module_public_function(post_edit_covers_all, test_path)
280
295
  assert all_issues == []
281
296
 
282
297
 
@@ -290,9 +305,7 @@ def test_counts_coverage_across_sibling_test_files_on_test_write(
290
305
  "from pkg.mod import gamma\n\ndef test_gamma():\n assert gamma() == 3\n",
291
306
  )
292
307
  test_path = str(neutral_package_directory / "tests" / "test_mod.py")
293
- all_issues = check_test_file_omits_module_public_function(
294
- _SUITE_COVERS_ALPHA_BETA, test_path
295
- )
308
+ all_issues = check_test_file_omits_module_public_function(_SUITE_COVERS_ALPHA_BETA, test_path)
296
309
  assert all_issues == []
297
310
 
298
311
 
@@ -301,9 +314,7 @@ def test_test_side_message_names_module_and_function_without_line_prefix(
301
314
  ) -> None:
302
315
  _write_module(neutral_package_directory, _THREE_PUBLIC_FUNCTIONS)
303
316
  test_path = str(neutral_package_directory / "tests" / "test_mod.py")
304
- all_issues = check_test_file_omits_module_public_function(
305
- _SUITE_COVERS_ALPHA_BETA, test_path
306
- )
317
+ all_issues = check_test_file_omits_module_public_function(_SUITE_COVERS_ALPHA_BETA, test_path)
307
318
  assert len(all_issues) == 1
308
319
  assert not all_issues[0].startswith("Line ")
309
320
  assert "mod.py" in all_issues[0]
@@ -315,9 +326,7 @@ def test_test_side_omission_blocks_on_any_test_file_edit(
315
326
  ) -> None:
316
327
  _write_module(neutral_package_directory, _THREE_PUBLIC_FUNCTIONS)
317
328
  test_path = str(neutral_package_directory / "tests" / "test_mod.py")
318
- all_issues = check_test_file_omits_module_public_function(
319
- _SUITE_COVERS_ALPHA_BETA, test_path
320
- )
329
+ all_issues = check_test_file_omits_module_public_function(_SUITE_COVERS_ALPHA_BETA, test_path)
321
330
  assert len(all_issues) == 1
322
331
  assert "gamma" in all_issues[0]
323
332
 
@@ -333,7 +342,5 @@ def test_skips_when_paired_production_module_is_exempt(
333
342
  "from pkg import alpha, beta, gamma\n\n"
334
343
  "def test_all():\n assert alpha() and beta() and gamma()\n"
335
344
  )
336
- all_issues = check_test_file_omits_module_public_function(
337
- suite_covers_all, beside_test_path
338
- )
345
+ all_issues = check_test_file_omits_module_public_function(suite_covers_all, beside_test_path)
339
346
  assert all_issues == []
@@ -17,8 +17,8 @@ import pathlib
17
17
  import sys
18
18
 
19
19
  _PACKAGE_ROOT = pathlib.Path(__file__).resolve().parents[2]
20
- _SHIPPED_CONVERGE_MJS = (
21
- _PACKAGE_ROOT / ".agents" / "skills" / "autoconverge" / "workflow" / "converge.mjs"
20
+ _ARCHIVED_CONVERGE_MJS = (
21
+ _PACKAGE_ROOT.parents[1] / "skill-archive" / "autoconverge" / "workflow" / "converge.mjs"
22
22
  )
23
23
 
24
24
  _HOOK_DIRECTORY = pathlib.Path(__file__).parent
@@ -750,8 +750,8 @@ def should_still_flag_real_dispatch_alongside_task_substring_identifier() -> Non
750
750
  assert "spawnVerifierAgent" in issues[0]
751
751
 
752
752
 
753
- def should_not_flag_shipped_converge_workflow() -> None:
754
- converge_source = _SHIPPED_CONVERGE_MJS.read_text(encoding="utf-8")
753
+ def should_not_flag_archived_converge_regression_fixture() -> None:
754
+ converge_source = _ARCHIVED_CONVERGE_MJS.read_text(encoding="utf-8")
755
755
  issues = check_js_resume_task_enumeration_coverage(
756
756
  converge_source, "skills/autoconverge/workflow/converge.mjs"
757
757
  )