claude-dev-env 4.3.0 → 7.1.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 (480) 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 +35 -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 +35 -3
  18. package/.agents/skills/session-continuity/README.md +189 -0
  19. package/.agents/skills/session-continuity/SKILL.md +114 -0
  20. package/.agents/skills/session-continuity/continuity.mjs +375 -0
  21. package/.agents/skills/session-continuity/continuity.test.mjs +303 -0
  22. package/.agents/skills/skill-builder/SKILL.md +3 -196
  23. package/.agents/skills/syncing-submodules/scripts/test_sync_parent_pointer.py +0 -7
  24. package/.agents/skills/windows-scheduled-task/SKILL.md +153 -0
  25. package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +6 -0
  26. package/_shared/pr-loop/scripts/terminology_sweep.py +64 -19
  27. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +59 -0
  28. package/bin/cde.mjs +36 -8
  29. package/bin/cde.test.mjs +48 -0
  30. package/bin/ever-shipped-skills.mjs +2 -20
  31. package/bin/ever-shipped-skills.test.mjs +8 -0
  32. package/bin/expand_home_directory_tokens.mjs +4 -5
  33. package/bin/expand_home_directory_tokens.test.mjs +128 -0
  34. package/bin/git_hooks_installer.mjs +22 -3
  35. package/bin/git_hooks_installer.test.mjs +159 -5
  36. package/bin/install-session-continuity.mjs +78 -0
  37. package/bin/install-session-continuity.test.mjs +42 -0
  38. package/bin/install.agents-home.test.mjs +15 -13
  39. package/bin/install.codex-rules.test.mjs +5 -2
  40. package/bin/install.cursor-rules.test.mjs +5 -2
  41. package/bin/install.mjs +506 -63
  42. package/bin/install.policy-lint.test.mjs +130 -0
  43. package/bin/install.profiles.test.mjs +10 -0
  44. package/bin/install.prune.test.mjs +286 -37
  45. package/bin/install.pstack-plugin.test.mjs +114 -0
  46. package/bin/install.test.mjs +537 -120
  47. package/bin/install.transaction.test.mjs +3 -3
  48. package/bin/refresh-pstack-plugin-skills.test.mjs +211 -0
  49. package/docs/CODE_RULES.md +1 -1
  50. package/hooks/advisory/pr_done_reminder.py +342 -0
  51. package/hooks/advisory/test_pr_done_reminder.py +249 -0
  52. package/hooks/blocking/bash_post_call_dispatcher.py +53 -6
  53. package/hooks/blocking/code_rules_banned_identifiers.py +3 -0
  54. package/hooks/blocking/code_rules_paired_test.py +22 -15
  55. package/hooks/blocking/precommit_code_rules_gate.py +5 -8
  56. package/hooks/blocking/test_bash_dispatcher_interpreter_starts.py +7 -52
  57. package/hooks/blocking/test_bash_post_call_dispatcher.py +65 -4
  58. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -69
  59. package/hooks/blocking/test_code_rules_banned_identifiers.py +64 -0
  60. package/hooks/blocking/test_code_rules_enforcer_paired_test.py +36 -29
  61. package/hooks/blocking/test_code_rules_js_resume_task_enumeration.py +4 -4
  62. package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +5 -5
  63. package/hooks/blocking/test_pre_tool_use_dispatcher.py +43 -342
  64. package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +20 -51
  65. package/hooks/blocking/test_shared_stdin_adoption.py +1 -47
  66. package/hooks/git-hooks/git_hooks_constants/staged_policy_lint.py +12 -0
  67. package/hooks/git-hooks/git_hooks_constants/verification_notice_constants.py +116 -0
  68. package/hooks/git-hooks/post_commit.py +33 -0
  69. package/hooks/git-hooks/pre_commit.py +18 -49
  70. package/hooks/git-hooks/pre_push.py +45 -541
  71. package/hooks/git-hooks/test_installed_policy_lint.py +208 -0
  72. package/hooks/git-hooks/test_native_hook_support.py +129 -0
  73. package/hooks/git-hooks/test_native_policy_commit.py +120 -0
  74. package/hooks/git-hooks/test_post_commit.py +43 -0
  75. package/hooks/git-hooks/test_pre_commit.py +38 -148
  76. package/hooks/git-hooks/test_pre_push.py +48 -1631
  77. package/hooks/git-hooks/test_verification_notice.py +60 -0
  78. package/hooks/git-hooks/test_verification_start.py +173 -0
  79. package/hooks/git-hooks/verification_notice.py +192 -0
  80. package/hooks/git-hooks/verification_notice_context.py +253 -0
  81. package/hooks/git-hooks/verification_notice_state.py +237 -0
  82. package/hooks/git-hooks/verification_start.py +100 -0
  83. package/hooks/hooks.json +1 -120
  84. package/hooks/hooks_constants/banned_identifiers_constants.py +4 -0
  85. package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +14 -1
  86. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +1 -15
  87. package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +0 -4
  88. package/hooks/hooks_constants/pr_done_reminder_constants.py +98 -0
  89. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -93
  90. package/hooks/hooks_constants/stop_dispatcher_constants.py +1 -4
  91. package/hooks/hooks_constants/test_bash_post_call_dispatcher_constants.py +19 -2
  92. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +3 -66
  93. package/hooks/hooks_constants/test_pr_done_reminder_constants.py +40 -0
  94. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +46 -201
  95. package/hooks/hooks_constants/test_stop_dispatcher_constants.py +3 -25
  96. package/hooks/validation/test_post_tool_use_dispatcher.py +35 -298
  97. package/hooks/validators/run_all_validators.py +5 -1
  98. package/hooks/validators/security_checks.py +57 -2
  99. package/hooks/validators/test_hook_timing_harness.py +2 -5
  100. package/hooks/validators/test_run_all_validators_mutation_tool_parity.py +3 -11
  101. package/hooks/validators/test_run_all_validators_policy_lint.py +36 -0
  102. package/hooks/validators/test_security_checks.py +57 -0
  103. package/package.json +2 -2
  104. package/rules/confirm-implementation-forks.md +2 -0
  105. package/rules/durable-post-artifacts.md +2 -1
  106. package/rules/git-workflow.md +18 -6
  107. package/rules/pstack-host-mapping.md +48 -0
  108. package/rules/vault-context.md +3 -5
  109. package/rules/verify-runtime-state.md +7 -0
  110. package/scripts/audit-hooks-cli.mjs +5 -0
  111. package/scripts/audit-hooks.mjs +72 -16
  112. package/scripts/automatic_advisory/__init__.py +1 -0
  113. package/scripts/automatic_advisory/checkout.py +51 -0
  114. package/scripts/automatic_advisory/cli.py +334 -0
  115. package/scripts/automatic_advisory/config/__init__.py +1 -0
  116. package/scripts/automatic_advisory/config/constants.py +93 -0
  117. package/scripts/automatic_advisory/config/timing.py +4 -0
  118. package/scripts/automatic_advisory/configuration.py +197 -0
  119. package/scripts/automatic_advisory/execution.py +350 -0
  120. package/scripts/automatic_advisory/git.py +69 -0
  121. package/scripts/automatic_advisory/model.py +107 -0
  122. package/scripts/automatic_advisory/process_host.py +32 -0
  123. package/scripts/automatic_advisory/publisher.py +84 -0
  124. package/scripts/automatic_advisory/runner.py +399 -0
  125. package/scripts/automatic_advisory/state.py +116 -0
  126. package/scripts/automatic_advisory/window_flags.py +41 -0
  127. package/scripts/automatic_advisory/windows_job.py +122 -0
  128. package/scripts/check.ps1 +0 -1
  129. package/{.agents/skills/run-claude-dev-env/driver.mjs → scripts/ci/installer-lifecycle.mjs} +3 -28
  130. package/scripts/ci/installer-lifecycle.test.mjs +26 -0
  131. package/scripts/ci/windows-installer-lifecycle.ps1 +1 -11
  132. package/scripts/codex_compat_materializer.py +99 -13
  133. package/scripts/dev_env_scripts_constants/shared_tree_constants.py +4 -5
  134. package/{.agents/skills/_shared/pr-loop/scripts → scripts}/durable_post_lint.py +35 -43
  135. package/{.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/durable_post_lint_constants.py → scripts/durable_post_lint_config/config/constants.py} +10 -10
  136. package/{.agents/skills/_shared/pr-loop/scripts/test_durable_post_lint_constants.py → scripts/durable_post_lint_config/config/test_constants.py} +14 -1
  137. package/scripts/local_report_cli.py +184 -0
  138. package/scripts/local_report_core.py +247 -0
  139. package/scripts/local_report_publisher.py +12 -0
  140. package/scripts/local_report_validation.py +357 -0
  141. package/scripts/local_verification/__init__.py +23 -0
  142. package/scripts/local_verification/check_runner.py +364 -0
  143. package/scripts/local_verification/check_support.py +99 -0
  144. package/scripts/local_verification/cli.py +158 -0
  145. package/scripts/local_verification/command_runner.py +84 -0
  146. package/scripts/local_verification/config/__init__.py +1 -0
  147. package/scripts/local_verification/config/constants.py +89 -0
  148. package/scripts/local_verification/config/timing.py +1 -0
  149. package/scripts/local_verification/git_state.py +146 -0
  150. package/scripts/local_verification/manifest.py +224 -0
  151. package/scripts/local_verification/model.py +174 -0
  152. package/scripts/local_verification/runner.py +234 -0
  153. package/scripts/policy_lint/adapter_detectors.py +13 -6
  154. package/scripts/policy_lint/adapter_pairing.py +58 -3
  155. package/scripts/policy_lint/adapters.py +7 -4
  156. package/scripts/policy_lint/config/approved_test_pairs.py +323 -0
  157. package/scripts/policy_lint/config/constants.py +5 -0
  158. package/scripts/policy_lint/engine.py +48 -4
  159. package/scripts/policy_lint/model.py +1 -0
  160. package/scripts/policy_lint/registry.py +1 -1
  161. package/scripts/policy_lint/selection.py +2 -1
  162. package/scripts/policy_lint/selection_git.py +2 -0
  163. package/scripts/pr_verification/__init__.py +1 -0
  164. package/scripts/pr_verification/branch_refs.py +114 -0
  165. package/scripts/pr_verification/config/__init__.py +1 -0
  166. package/scripts/pr_verification/config/constants.py +84 -0
  167. package/scripts/pr_verification/config/timing.py +5 -0
  168. package/scripts/pr_verification/github.py +137 -0
  169. package/scripts/pr_verification/github_api.py +376 -0
  170. package/scripts/pr_verification/github_labels.py +88 -0
  171. package/scripts/pr_verification/github_parsing.py +182 -0
  172. package/scripts/pr_verification/github_transport.py +91 -0
  173. package/scripts/pr_verification/lock.py +122 -0
  174. package/scripts/pr_verification/model.py +43 -0
  175. package/scripts/pr_verification/requirements.txt +1 -0
  176. package/scripts/refresh_pstack_plugin_skills.mjs +200 -0
  177. package/scripts/shared_tree_paths.py +32 -3
  178. package/{.agents/skills/_shared/pr-loop/scripts → scripts}/test_durable_post_lint.py +97 -0
  179. package/scripts/test_shared_tree_paths.py +33 -0
  180. package/scripts/tests/test-audit-hooks-cli.mjs +150 -0
  181. package/scripts/tests/test-audit-hooks.mjs +94 -2
  182. package/scripts/tests/test_automatic_advisory.py +376 -0
  183. package/scripts/tests/test_automatic_advisory_job_cleanup.py +71 -0
  184. package/scripts/tests/test_automatic_advisory_polling.py +216 -0
  185. package/scripts/tests/test_automatic_advisory_recovery.py +320 -0
  186. package/scripts/tests/test_automatic_advisory_repairs.py +400 -0
  187. package/scripts/tests/test_automatic_advisory_settings.py +48 -0
  188. package/scripts/tests/test_automatic_advisory_window_flags.py +163 -0
  189. package/scripts/tests/test_closed_pr_label.py +157 -0
  190. package/scripts/tests/test_codex_compat_materializer.py +189 -6
  191. package/scripts/tests/test_github_labels.py +101 -0
  192. package/scripts/tests/test_github_transport.py +125 -0
  193. package/scripts/tests/test_local_report_publisher.py +396 -0
  194. package/scripts/tests/test_local_report_publisher_cli.py +85 -0
  195. package/scripts/tests/test_local_verification.py +348 -0
  196. package/scripts/tests/test_local_verification_cli.py +98 -0
  197. package/scripts/tests/test_policy_lint_rules_archive.py +120 -0
  198. package/scripts/tests/test_policy_lint_rules_engine.py +25 -4
  199. package/scripts/tests/test_policy_lint_rules_installed_loader.py +29 -0
  200. package/scripts/tests/test_policy_lint_rules_pairing.py +163 -0
  201. package/scripts/tests/test_policy_lint_rules_registry.py +227 -13
  202. package/scripts/tests/test_policy_lint_selection.py +25 -2
  203. package/scripts/tests/test_policy_lint_selection_native_index.py +30 -0
  204. package/scripts/tests/test_pr_verification_github.py +392 -0
  205. package/scripts/tests/test_pr_verification_lock.py +168 -0
  206. package/scripts/tests/test_rules.py +3 -7
  207. package/scripts/tests/test_verification_notice.py +347 -0
  208. package/.agents/skills/_shared/pr-loop/.claude/CLAUDE.md +0 -1
  209. package/.agents/skills/_shared/pr-loop/audit-contract.md +0 -5
  210. package/.agents/skills/_shared/pr-loop/audit-reply-template.md +0 -5
  211. package/.agents/skills/_shared/pr-loop/code-rules-gate.md +0 -5
  212. package/.agents/skills/_shared/pr-loop/fix-protocol.md +0 -5
  213. package/.agents/skills/_shared/pr-loop/gh-payloads.md +0 -5
  214. package/.agents/skills/_shared/pr-loop/portable-driver.md +0 -149
  215. package/.agents/skills/_shared/pr-loop/post-audit-thread-contract.md +0 -5
  216. package/.agents/skills/_shared/pr-loop/precatch-rubric.md +0 -5
  217. package/.agents/skills/_shared/pr-loop/preflight-proposal.contract.test.mjs +0 -119
  218. package/.agents/skills/_shared/pr-loop/preflight-proposal.md +0 -5
  219. package/.agents/skills/_shared/pr-loop/prompts/.claude/CLAUDE.md +0 -1
  220. package/.agents/skills/_shared/pr-loop/prompts/AGENTS.md +0 -1
  221. package/.agents/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +0 -324
  222. package/.agents/skills/_shared/pr-loop/scripts/.claude/CLAUDE.md +0 -1
  223. package/.agents/skills/_shared/pr-loop/scripts/AGENTS.md +0 -1
  224. package/.agents/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +0 -29
  225. package/.agents/skills/_shared/pr-loop/scripts/_cli_utils.py +0 -22
  226. package/.agents/skills/_shared/pr-loop/scripts/_path_resolver.py +0 -186
  227. package/.agents/skills/_shared/pr-loop/scripts/_xml_utils.py +0 -20
  228. package/.agents/skills/_shared/pr-loop/scripts/audit_category_schema.py +0 -355
  229. package/.agents/skills/_shared/pr-loop/scripts/build_audit_prompt.py +0 -289
  230. package/.agents/skills/_shared/pr-loop/scripts/build_converge_task_list.py +0 -310
  231. package/.agents/skills/_shared/pr-loop/scripts/build_fix_prompt.py +0 -292
  232. package/.agents/skills/_shared/pr-loop/scripts/init_loop_state.py +0 -134
  233. package/.agents/skills/_shared/pr-loop/scripts/portable_converge_driver.py +0 -1646
  234. package/.agents/skills/_shared/pr-loop/scripts/preflight_worktree.py +0 -392
  235. package/.agents/skills/_shared/pr-loop/scripts/select_converge_pacer.py +0 -215
  236. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/.claude/CLAUDE.md +0 -1
  237. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +0 -1
  238. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/__init__.py +0 -0
  239. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +0 -37
  240. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/converge_task_list_constants.py +0 -56
  241. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/handoff_constants.py +0 -45
  242. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/pacer_constants.py +0 -47
  243. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +0 -157
  244. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/portable_driver_constants.py +0 -177
  245. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/preflight_constants.py +0 -70
  246. package/.agents/skills/_shared/pr-loop/scripts/teardown_worktrees.py +0 -172
  247. package/.agents/skills/_shared/pr-loop/scripts/test__path_resolver.py +0 -106
  248. package/.agents/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +0 -94
  249. package/.agents/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +0 -499
  250. package/.agents/skills/_shared/pr-loop/scripts/test_build_converge_task_list.py +0 -140
  251. package/.agents/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +0 -262
  252. package/.agents/skills/_shared/pr-loop/scripts/test_init_loop_state.py +0 -71
  253. package/.agents/skills/_shared/pr-loop/scripts/test_portable_converge_driver.py +0 -1119
  254. package/.agents/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +0 -287
  255. package/.agents/skills/_shared/pr-loop/scripts/test_select_converge_pacer.py +0 -207
  256. package/.agents/skills/_shared/pr-loop/scripts/test_teardown_worktrees.py +0 -59
  257. package/.agents/skills/_shared/pr-loop/scripts/test_write_handoff.py +0 -201
  258. package/.agents/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +0 -182
  259. package/.agents/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +0 -206
  260. package/.agents/skills/_shared/pr-loop/scripts/write_handoff.py +0 -309
  261. package/.agents/skills/_shared/pr-loop/state-schema.md +0 -5
  262. package/.agents/skills/_shared/pr-loop/worker-spawn.md +0 -5
  263. package/.agents/skills/autoconverge/.claude/CLAUDE.md +0 -1
  264. package/.agents/skills/autoconverge/AGENTS.md +0 -1
  265. package/.agents/skills/autoconverge/SKILL.md +0 -431
  266. package/.agents/skills/autoconverge/reference/.claude/CLAUDE.md +0 -1
  267. package/.agents/skills/autoconverge/reference/AGENTS.md +0 -1
  268. package/.agents/skills/autoconverge/reference/closing-report.md +0 -93
  269. package/.agents/skills/autoconverge/reference/convergence-gates.md +0 -31
  270. package/.agents/skills/autoconverge/reference/convergence.md +0 -219
  271. package/.agents/skills/autoconverge/reference/copilot-findings.md +0 -51
  272. package/.agents/skills/autoconverge/reference/gotchas.md +0 -66
  273. package/.agents/skills/autoconverge/reference/headless-safety.md +0 -44
  274. package/.agents/skills/autoconverge/reference/multi-pr.md +0 -128
  275. package/.agents/skills/autoconverge/reference/self-closing-loop.md +0 -84
  276. package/.agents/skills/autoconverge/reference/stop-conditions.md +0 -127
  277. package/.agents/skills/autoconverge/test_portable_pacer_gate.py +0 -54
  278. package/.agents/skills/autoconverge/workflow/.claude/CLAUDE.md +0 -1
  279. package/.agents/skills/autoconverge/workflow/AGENTS.md +0 -1
  280. package/.agents/skills/autoconverge/workflow/aggregate_runs.py +0 -371
  281. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/.claude/CLAUDE.md +0 -1
  282. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +0 -1
  283. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/__init__.py +0 -0
  284. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +0 -250
  285. package/.agents/skills/autoconverge/workflow/converge.clean-audit.test.mjs +0 -665
  286. package/.agents/skills/autoconverge/workflow/converge.codex-gate.test.mjs +0 -472
  287. package/.agents/skills/autoconverge/workflow/converge.contract.test.mjs +0 -64
  288. package/.agents/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +0 -1009
  289. package/.agents/skills/autoconverge/workflow/converge.fix-progress.test.mjs +0 -107
  290. package/.agents/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +0 -33
  291. package/.agents/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +0 -15
  292. package/.agents/skills/autoconverge/workflow/converge.mjs +0 -3132
  293. package/.agents/skills/autoconverge/workflow/converge.p2-advance.test.mjs +0 -202
  294. package/.agents/skills/autoconverge/workflow/converge.path-aware.test.mjs +0 -47
  295. package/.agents/skills/autoconverge/workflow/converge.precatch.test.mjs +0 -152
  296. package/.agents/skills/autoconverge/workflow/converge.run-input.test.mjs +0 -116
  297. package/.agents/skills/autoconverge/workflow/converge_multi.mjs +0 -169
  298. package/.agents/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +0 -173
  299. package/.agents/skills/autoconverge/workflow/convergence_summary.py +0 -110
  300. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +0 -2
  301. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +0 -2
  302. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +0 -2
  303. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +0 -2
  304. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +0 -2
  305. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +0 -2
  306. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ab1c2d3e4f5a6b7c8.jsonl +0 -2
  307. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +0 -2
  308. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +0 -2
  309. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +0 -266
  310. package/.agents/skills/autoconverge/workflow/render_report.py +0 -1024
  311. package/.agents/skills/autoconverge/workflow/test_aggregate_runs.py +0 -134
  312. package/.agents/skills/autoconverge/workflow/test_convergence_summary.py +0 -149
  313. package/.agents/skills/autoconverge/workflow/test_render_report.py +0 -812
  314. package/.agents/skills/hitl/SKILL.md +0 -45
  315. package/.agents/skills/pr-cleanup/SKILL.md +0 -140
  316. package/.agents/skills/pr-cleanup/reference/process-inventory.md +0 -17
  317. package/.agents/skills/pr-cleanup/reference/task-seeds.md +0 -22
  318. package/.agents/skills/pr-name-by-capability/SKILL.md +0 -73
  319. package/.agents/skills/pr-name-by-capability/reference/fetch-commands.md +0 -29
  320. package/.agents/skills/pr-name-by-capability/reference/offense-examples.md +0 -32
  321. package/.agents/skills/pr-name-by-capability/reference/report-template.md +0 -29
  322. package/.agents/skills/pr-name-by-capability/reference/rule-checklist.md +0 -38
  323. package/.agents/skills/pr-name-by-capability/reference/task-seeds.md +0 -10
  324. package/.agents/skills/pr-plain-language-cleanup/SKILL.md +0 -93
  325. package/.agents/skills/pr-refinement/SKILL.md +0 -31
  326. package/.agents/skills/pr-shared-extraction/SKILL.md +0 -177
  327. package/.agents/skills/pr-shared-extraction/reference/examples.md +0 -46
  328. package/.agents/skills/pr-shared-extraction/reference/offense-taxonomy.md +0 -100
  329. package/.agents/skills/pr-small-cl/SKILL.md +0 -52
  330. package/.agents/skills/pr-title-description/SKILL.md +0 -96
  331. package/.agents/skills/prototype/SKILL.md +0 -85
  332. package/.agents/skills/prototype/reference/honest-limitations.md +0 -23
  333. package/.agents/skills/prototype/reference/promotion-tasks.md +0 -23
  334. package/.agents/skills/prototype/scripts/build_sandbox_settings.py +0 -249
  335. package/.agents/skills/prototype/scripts/conftest.py +0 -15
  336. package/.agents/skills/prototype/scripts/launch_sandbox.py +0 -205
  337. package/.agents/skills/prototype/scripts/probe_sandbox_safety.py +0 -311
  338. package/.agents/skills/prototype/scripts/prototype_scripts_constants/__init__.py +0 -1
  339. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
  340. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +0 -41
  341. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +0 -23
  342. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +0 -45
  343. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +0 -10
  344. package/.agents/skills/prototype/scripts/test_build_sandbox_settings.py +0 -275
  345. package/.agents/skills/prototype/scripts/test_launch_sandbox.py +0 -303
  346. package/.agents/skills/prototype/scripts/test_probe_sandbox_safety.py +0 -312
  347. package/.agents/skills/prototype/workflows/promotion.md +0 -25
  348. package/.agents/skills/prototype/workflows/sandbox.md +0 -35
  349. package/.agents/skills/rebase/.claude/CLAUDE.md +0 -1
  350. package/.agents/skills/rebase/AGENTS.md +0 -1
  351. package/.agents/skills/rebase/SKILL.md +0 -178
  352. package/.agents/skills/review-router/SKILL.md +0 -51
  353. package/.agents/skills/review-router/reference/route-policy.json +0 -7
  354. package/.agents/skills/review-router/reference/task-seeds.md +0 -1
  355. package/.agents/skills/review-router/scripts/conftest.py +0 -4
  356. package/.agents/skills/review-router/scripts/review_router.py +0 -50
  357. package/.agents/skills/review-router/scripts/review_router_cli.py +0 -211
  358. package/.agents/skills/review-router/scripts/review_router_constants/__init__.py +0 -1
  359. package/.agents/skills/review-router/scripts/review_router_constants/config/__init__.py +0 -0
  360. package/.agents/skills/review-router/scripts/review_router_constants/config/constants.py +0 -13
  361. package/.agents/skills/review-router/scripts/test_review_router.py +0 -23
  362. package/.agents/skills/review-router/scripts/test_review_router_cli.py +0 -47
  363. package/.agents/skills/review-tier/SKILL.md +0 -56
  364. package/.agents/skills/review-tier/reference/task-seeds.md +0 -1
  365. package/.agents/skills/review-tier/reference/tier-policy.json +0 -9
  366. package/.agents/skills/review-tier/scripts/conftest.py +0 -4
  367. package/.agents/skills/review-tier/scripts/review_tier.py +0 -359
  368. package/.agents/skills/review-tier/scripts/review_tier_constants/__init__.py +0 -1
  369. package/.agents/skills/review-tier/scripts/review_tier_constants/config/__init__.py +0 -0
  370. package/.agents/skills/review-tier/scripts/review_tier_constants/config/constants.py +0 -29
  371. package/.agents/skills/review-tier/scripts/test_generation.py +0 -159
  372. package/.agents/skills/review-tier/scripts/test_review_tier.py +0 -33
  373. package/.agents/skills/run-claude-dev-env/SKILL.md +0 -84
  374. package/.agents/skills/session-log/.claude/CLAUDE.md +0 -1
  375. package/.agents/skills/session-log/AGENTS.md +0 -1
  376. package/.agents/skills/session-log/SKILL.md +0 -170
  377. package/.agents/skills/session-log/templates/frontmatter.md +0 -40
  378. package/.agents/skills/session-tidy/.claude/CLAUDE.md +0 -1
  379. package/.agents/skills/session-tidy/AGENTS.md +0 -1
  380. package/.agents/skills/session-tidy/SKILL.md +0 -191
  381. package/.agents/skills/skill-builder/references/.claude/CLAUDE.md +0 -1
  382. package/.agents/skills/skill-builder/references/AGENTS.md +0 -1
  383. package/.agents/skills/skill-builder/references/delegation-map.md +0 -130
  384. package/.agents/skills/skill-builder/references/description-field.md +0 -111
  385. package/.agents/skills/skill-builder/references/deterministic-elements.md +0 -218
  386. package/.agents/skills/skill-builder/references/progressive-disclosure.md +0 -122
  387. package/.agents/skills/skill-builder/references/self-audit-checklist.md +0 -126
  388. package/.agents/skills/skill-builder/references/skill-modularity.md +0 -111
  389. package/.agents/skills/skill-builder/references/skill-types.md +0 -232
  390. package/.agents/skills/skill-builder/references/thariq-x-post-skills.json +0 -33
  391. package/.agents/skills/skill-builder/templates/.claude/CLAUDE.md +0 -1
  392. package/.agents/skills/skill-builder/templates/AGENTS.md +0 -1
  393. package/.agents/skills/skill-builder/templates/gap-analysis.md +0 -87
  394. package/.agents/skills/skill-builder/workflows/.claude/CLAUDE.md +0 -1
  395. package/.agents/skills/skill-builder/workflows/AGENTS.md +0 -1
  396. package/.agents/skills/skill-builder/workflows/improve-skill.md +0 -136
  397. package/.agents/skills/skill-builder/workflows/new-skill.md +0 -179
  398. package/.agents/skills/skill-builder/workflows/polish-skill.md +0 -139
  399. package/.agents/skills/source-command-sr-loop/SKILL.md +0 -82
  400. package/.agents/skills/update/.claude/CLAUDE.md +0 -1
  401. package/.agents/skills/update/AGENTS.md +0 -1
  402. package/.agents/skills/update/SKILL.md +0 -190
  403. package/_shared/pr-loop/AGENTS.md +0 -1
  404. package/_shared/pr-loop/audit-contract.md +0 -183
  405. package/_shared/pr-loop/audit-reply-template.md +0 -151
  406. package/_shared/pr-loop/code-rules-gate.md +0 -65
  407. package/_shared/pr-loop/fix-protocol.md +0 -60
  408. package/_shared/pr-loop/gh-payloads.md +0 -75
  409. package/_shared/pr-loop/post-audit-thread-contract.md +0 -51
  410. package/_shared/pr-loop/precatch-rubric.md +0 -66
  411. package/_shared/pr-loop/preflight-proposal.md +0 -56
  412. package/_shared/pr-loop/scripts/post_audit_thread.py +0 -1242
  413. package/_shared/pr-loop/scripts/pr_loop_shared_constants/post_audit_thread_constants.py +0 -199
  414. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +0 -1115
  415. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +0 -129
  416. package/_shared/pr-loop/state-schema.md +0 -45
  417. package/_shared/pr-loop/worker-spawn.md +0 -189
  418. package/hooks/blocking/ask_user_question_shape_blocker.py +0 -83
  419. package/hooks/blocking/bot_mention_comment_blocker.py +0 -76
  420. package/hooks/blocking/claude_md_orphan_file_blocker.py +0 -129
  421. package/hooks/blocking/cursor_cli_python_misfire_blocker.py +0 -108
  422. package/hooks/blocking/destructive_command_blocker.py +0 -2073
  423. package/hooks/blocking/fable_spawn_gate.py +0 -187
  424. package/hooks/blocking/luna_fast_mode_gate.py +0 -160
  425. package/hooks/blocking/nas_ssh_binary_enforcer.py +0 -163
  426. package/hooks/blocking/orchestrator_refresh_reschedule_gate.py +0 -260
  427. package/hooks/blocking/package_inventory_stale_blocker.py +0 -107
  428. package/hooks/blocking/pii_prevention_blocker.py +0 -365
  429. package/hooks/blocking/piped_pytest_blocker.py +0 -1223
  430. package/hooks/blocking/question_to_user_enforcer.py +0 -130
  431. package/hooks/blocking/send_user_file_open_locally_blocker.py +0 -71
  432. package/hooks/blocking/sensitive_file_protector.py +0 -167
  433. package/hooks/blocking/session_edit_stage_gate.py +0 -684
  434. package/hooks/blocking/session_handoff_blocker.py +0 -182
  435. package/hooks/blocking/shell_substitution_blocker.py +0 -350
  436. package/hooks/blocking/test_ask_user_question_shape.py +0 -69
  437. package/hooks/blocking/test_bot_mention_comment_blocker.py +0 -131
  438. package/hooks/blocking/test_claude_md_orphan_file_blocker.py +0 -1277
  439. package/hooks/blocking/test_cursor_cli_python_misfire_blocker.py +0 -208
  440. package/hooks/blocking/test_destructive_command_blocker.py +0 -2024
  441. package/hooks/blocking/test_destructive_command_blocker_deny_mode.py +0 -105
  442. package/hooks/blocking/test_destructive_command_blocker_no_verify.py +0 -122
  443. package/hooks/blocking/test_destructive_command_blocker_patterns.py +0 -133
  444. package/hooks/blocking/test_fable_spawn_gate.py +0 -381
  445. package/hooks/blocking/test_inventory_deadlock_resolution.py +0 -154
  446. package/hooks/blocking/test_luna_fast_mode_gate.py +0 -211
  447. package/hooks/blocking/test_nas_ssh_binary_enforcer.py +0 -225
  448. package/hooks/blocking/test_orchestrator_refresh_reschedule_gate.py +0 -231
  449. package/hooks/blocking/test_package_inventory_stale_blocker.py +0 -382
  450. package/hooks/blocking/test_pii_prevention_blocker.py +0 -1222
  451. package/hooks/blocking/test_pii_prevention_windows_git_path.py +0 -41
  452. package/hooks/blocking/test_pii_write_surface_ephemeral.py +0 -221
  453. package/hooks/blocking/test_piped_pytest_blocker.py +0 -587
  454. package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +0 -233
  455. package/hooks/blocking/test_question_to_user_enforcer.py +0 -169
  456. package/hooks/blocking/test_send_user_file_open_locally_blocker.py +0 -118
  457. package/hooks/blocking/test_sensitive_file_protector.py +0 -365
  458. package/hooks/blocking/test_session_edit_stage_gate.py +0 -537
  459. package/hooks/blocking/test_session_edit_stage_gate_staging.py +0 -64
  460. package/hooks/blocking/test_session_edit_stage_gate_support.py +0 -204
  461. package/hooks/blocking/test_session_edit_stage_gate_tracker.py +0 -116
  462. package/hooks/blocking/test_session_handoff_blocker.py +0 -318
  463. package/hooks/blocking/test_shell_substitution_blocker.py +0 -298
  464. package/hooks/blocking/test_unscoped_search_blocker.py +0 -102
  465. package/hooks/blocking/test_write_existing_file_blocker.py +0 -122
  466. package/hooks/blocking/tests/test_pii_prevention_blocker.py +0 -259
  467. package/hooks/blocking/unscoped_search_blocker.py +0 -391
  468. package/hooks/blocking/write_existing_file_blocker.py +0 -124
  469. package/hooks/hooks_constants/bot_mention_comment_blocker_constants.py +0 -19
  470. package/hooks/hooks_constants/cursor_cli_python_misfire_blocker_constants.py +0 -79
  471. package/hooks/hooks_constants/fable_spawn_gate_constants.py +0 -61
  472. package/hooks/hooks_constants/luna_fast_mode_gate_constants.py +0 -45
  473. package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +0 -20
  474. package/hooks/hooks_constants/orchestrator_refresh_reschedule_gate_constants.py +0 -57
  475. package/hooks/hooks_constants/send_user_file_open_locally_blocker_constants.py +0 -17
  476. package/hooks/hooks_constants/sensitive_file_protector_constants.py +0 -40
  477. package/hooks/hooks_constants/session_handoff_blocker_constants.py +0 -10
  478. package/hooks/hooks_constants/shell_substitution_blocker_constants.py +0 -80
  479. /package/.agents/skills/{_shared/pr-loop → session-continuity}/AGENTS.md +0 -0
  480. /package/{_shared/pr-loop → .agents/skills/session-continuity}/CLAUDE.md +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:
@@ -30,6 +30,7 @@ from code_rules_shared import ( # noqa: E402
30
30
  from hooks_constants.banned_identifiers_constants import ( # noqa: E402
31
31
  ALL_BANNED_IDENTIFIERS,
32
32
  ALL_BANNED_NOUN_WORDS,
33
+ ALL_BLAST_RADIUS_EXCEPTION_SUFFIXES,
33
34
  BANNED_IDENTIFIER_MESSAGE_SUFFIX,
34
35
  BANNED_IDENTIFIER_SKIP_ADVISORY,
35
36
  BANNED_NOUN_COMPATIBILITY_COMMENT,
@@ -228,6 +229,8 @@ def _find_banned_noun_word(identifier: str) -> str | None:
228
229
  identifier (e.g., ``'result'`` for ``'HolidayPeakResult'``). Returns
229
230
  ``None`` when no banned noun word is present.
230
231
  """
232
+ if identifier.endswith(ALL_BLAST_RADIUS_EXCEPTION_SUFFIXES):
233
+ return None
231
234
  for each_word in _identifier_word_parts(identifier):
232
235
  if each_word in ALL_BANNED_NOUN_WORDS:
233
236
  return each_word
@@ -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
-
@@ -1,11 +1,8 @@
1
- """Library helper: resolve a directory's Git repository root.
2
-
3
- ``pii_prevention_blocker.py`` and ``pii_payload_scan.py`` import
4
- ``resolve_repository_root`` to find the repository a staged-commit scan
5
- targets, and ``session_edit_stage_gate.py`` imports it to find the
6
- repository a commit's unstaged-edit check targets. This module carries no
7
- hook entry point of its own; the native ``git-hooks/pre_commit.py`` runs
8
- the real commit-time CODE_RULES enforcement.
1
+ """Library helper that resolves a directory to its Git repository root.
2
+
3
+ ``pii_payload_scan.py`` imports ``resolve_repository_root`` for staged scans.
4
+ This module has no hook entry point. Native ``git-hooks/pre_commit.py`` shows
5
+ the local verification advisory and does not enforce CODE_RULES.
9
6
  """
10
7
 
11
8
  import subprocess
@@ -1,61 +1,16 @@
1
- """Benchmark: consolidated Bash dispatcher starts one interpreter, not N.
1
+ """Tests for the retired Bash and PowerShell blocking roster."""
2
2
 
3
- Standalone registration starts one Python process per hosted hook. The
4
- dispatcher replaces that chain with a single process that hosts every hook
5
- in-process. This test pins the before/after interpreter-start counts so a
6
- regression that re-splits the chain fails CI.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import json
12
- import subprocess
13
3
  import sys
14
4
  from pathlib import Path
15
5
 
16
- _HOOKS_DIR = str(Path(__file__).resolve().parent.parent)
17
- if _HOOKS_DIR not in sys.path:
18
- sys.path.insert(0, _HOOKS_DIR)
6
+ _HOOKS_ROOT = Path(__file__).resolve().parent.parent
7
+ if str(_HOOKS_ROOT) not in sys.path:
8
+ sys.path.insert(0, str(_HOOKS_ROOT))
19
9
 
20
- from hooks_constants.bash_pre_tool_use_dispatcher_constants import ( # noqa: E402
10
+ from hooks_constants.bash_pre_tool_use_dispatcher_constants import (
21
11
  ALL_BASH_HOSTED_HOOK_ENTRIES,
22
- BASH_TOOL_NAME,
23
12
  )
24
13
 
25
- _BLOCKING_DIR = Path(__file__).resolve().parent
26
- _HOOKS_ROOT = _BLOCKING_DIR.parent
27
- _DISPATCHER_SCRIPT = str(_BLOCKING_DIR / "bash_pre_tool_use_dispatcher.py")
28
-
29
- STANDALONE_INTERPRETER_STARTS_PER_BASH_CALL = len(ALL_BASH_HOSTED_HOOK_ENTRIES)
30
- DISPATCHER_INTERPRETER_STARTS_PER_BASH_CALL = 1
31
-
32
-
33
- def test_roster_is_large_enough_that_consolidation_saves_starts() -> None:
34
- """Consolidation only pays off when the chain hosts more than one hook."""
35
- assert STANDALONE_INTERPRETER_STARTS_PER_BASH_CALL >= 10
36
- assert DISPATCHER_INTERPRETER_STARTS_PER_BASH_CALL == 1
37
- assert STANDALONE_INTERPRETER_STARTS_PER_BASH_CALL > DISPATCHER_INTERPRETER_STARTS_PER_BASH_CALL
38
-
39
-
40
- def test_dispatcher_handles_a_bash_call_in_one_process() -> None:
41
- """One subprocess run of the dispatcher covers the full Bash hosted chain."""
42
- payload_text = json.dumps(
43
- {"tool_name": BASH_TOOL_NAME, "tool_input": {"command": "echo interpreter-start-probe"}}
44
- )
45
- completed = subprocess.run(
46
- [sys.executable, _DISPATCHER_SCRIPT],
47
- check=False,
48
- input=payload_text,
49
- capture_output=True,
50
- text=True,
51
- encoding="utf-8",
52
- )
53
- assert completed.returncode == 0
54
- assert DISPATCHER_INTERPRETER_STARTS_PER_BASH_CALL == 1
55
-
56
14
 
57
- def test_standalone_chain_would_start_one_process_per_roster_entry() -> None:
58
- """Each roster path is a real script that would own its own interpreter start."""
59
- for each_entry in ALL_BASH_HOSTED_HOOK_ENTRIES:
60
- assert (_HOOKS_ROOT / each_entry.script_relative_path).is_file()
61
- assert len(ALL_BASH_HOSTED_HOOK_ENTRIES) == STANDALONE_INTERPRETER_STARTS_PER_BASH_CALL
15
+ def test_retired_bash_roster_has_no_effective_blocking_hooks() -> None:
16
+ assert ALL_BASH_HOSTED_HOOK_ENTRIES == ()
@@ -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(