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,157 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from pathlib import Path, PurePosixPath
5
+
6
+ SCRIPTS_DIRECTORY = Path(__file__).resolve().parents[1]
7
+ if str(SCRIPTS_DIRECTORY) not in sys.path:
8
+ sys.path.insert(0, str(SCRIPTS_DIRECTORY))
9
+
10
+ from automatic_advisory.model import (
11
+ AdvisoryRegistration,
12
+ AdvisorySettings,
13
+ AdvisoryState,
14
+ )
15
+ from automatic_advisory.publisher import Publication
16
+ from automatic_advisory.runner import AdvisoryGitHub, AutomaticAdvisoryRunner
17
+ from automatic_advisory.state import read_state, write_state
18
+ from pr_verification.config.constants import LOCAL_CHECKS_PASSED_LABEL
19
+ from pr_verification.model import PullRequestCandidate, RepositorySettings
20
+
21
+
22
+ class ClosedPullRequestGitHub(AdvisoryGitHub):
23
+ def __init__(self, state_path: Path, failed_removal_count: int) -> None:
24
+ self.state_path = state_path
25
+ self.failed_removal_count = failed_removal_count
26
+ self.removed_labels: list[str] = []
27
+ self.observed_statuses: list[str | None] = []
28
+
29
+ def list_open_candidates(
30
+ self,
31
+ repository: RepositorySettings,
32
+ *,
33
+ should_require_merge_commit: bool = True,
34
+ ) -> tuple[PullRequestCandidate, ...]:
35
+ return ()
36
+
37
+ def remove_label(
38
+ self, repository: RepositorySettings, pull_request_number: int, label: str
39
+ ) -> None:
40
+ maybe_state = read_state(self.state_path)
41
+ self.observed_statuses.append(maybe_state.status if maybe_state else None)
42
+ self.removed_labels.append(label)
43
+ if self.failed_removal_count:
44
+ self.failed_removal_count -= 1
45
+ raise RuntimeError("label service unavailable")
46
+
47
+
48
+ def _build_registration(tmp_path: Path) -> AdvisoryRegistration:
49
+ return AdvisoryRegistration(
50
+ RepositorySettings("JonEcho/python-automation", "unused"),
51
+ 3082,
52
+ tmp_path / "checkout",
53
+ PurePosixPath("manifest.json"),
54
+ tmp_path / "report.json",
55
+ tmp_path / "state.json",
56
+ "main",
57
+ "origin",
58
+ )
59
+
60
+
61
+ def _build_runner(
62
+ registration: AdvisoryRegistration,
63
+ github: ClosedPullRequestGitHub,
64
+ ) -> AutomaticAdvisoryRunner:
65
+ settings = AdvisorySettings(
66
+ "https://api.github.com",
67
+ 4841271,
68
+ 159293880,
69
+ registration.state_path.with_name("private-key.pem"),
70
+ 60.0,
71
+ 30.0,
72
+ (registration,),
73
+ )
74
+ return AutomaticAdvisoryRunner(
75
+ settings,
76
+ github,
77
+ child_runner=_unexpected_child,
78
+ publisher=_unexpected_publisher,
79
+ clock=lambda: "2026-09-05T00:00:00+00:00",
80
+ )
81
+
82
+
83
+ def _unexpected_child(
84
+ *all_arguments: object,
85
+ **all_keyword_arguments: object,
86
+ ) -> object:
87
+ raise AssertionError("closed pull requests must not run checks")
88
+
89
+
90
+ def _unexpected_publisher(*all_arguments: object) -> Publication:
91
+ raise AssertionError("closed pull requests must not publish reports")
92
+
93
+
94
+ def _write_passed_state(registration: AdvisoryRegistration) -> None:
95
+ write_state(
96
+ registration.state_path,
97
+ AdvisoryState(
98
+ registration.repository.slug,
99
+ registration.pull_request_number,
100
+ "passed",
101
+ "local checks passed",
102
+ "old-head",
103
+ "base-head",
104
+ "2026-09-04T00:00:00+00:00",
105
+ "2026-09-09T00:00:00+00:00",
106
+ str(registration.report_path),
107
+ ),
108
+ )
109
+
110
+
111
+ def test_closed_pull_request_removes_stale_label_before_saving_closed_state(
112
+ tmp_path: Path,
113
+ ) -> None:
114
+ registration = _build_registration(tmp_path)
115
+ _write_passed_state(registration)
116
+ github = ClosedPullRequestGitHub(registration.state_path, 0)
117
+ runner = _build_runner(registration, github)
118
+
119
+ state = runner.run_once()[0]
120
+
121
+ assert state.status == "closed"
122
+ assert github.removed_labels == [LOCAL_CHECKS_PASSED_LABEL]
123
+ assert github.observed_statuses == ["passed"]
124
+ assert read_state(registration.state_path).status == "closed"
125
+
126
+
127
+ def test_closed_pull_request_retries_failed_label_removal(
128
+ tmp_path: Path,
129
+ ) -> None:
130
+ registration = _build_registration(tmp_path)
131
+ _write_passed_state(registration)
132
+ github = ClosedPullRequestGitHub(registration.state_path, 1)
133
+ runner = _build_runner(registration, github)
134
+
135
+ error_state = runner.run_once()[0]
136
+ closed_state = runner.run_once()[0]
137
+
138
+ assert error_state.status == "error"
139
+ assert closed_state.status == "closed"
140
+ assert github.removed_labels == [
141
+ LOCAL_CHECKS_PASSED_LABEL,
142
+ LOCAL_CHECKS_PASSED_LABEL,
143
+ ]
144
+ assert github.observed_statuses == ["passed", "error"]
145
+
146
+
147
+ def test_closed_pull_request_does_not_run_checks(
148
+ tmp_path: Path,
149
+ ) -> None:
150
+ registration = _build_registration(tmp_path)
151
+ github = ClosedPullRequestGitHub(registration.state_path, 0)
152
+ runner = _build_runner(registration, github)
153
+
154
+ state = runner.run_once()[0]
155
+
156
+ assert state.status == "closed"
157
+ assert github.removed_labels == [LOCAL_CHECKS_PASSED_LABEL]
@@ -462,13 +462,35 @@ def _apply_source_agent(source_root: Path, target_root: Path, description: str)
462
462
  return target_root / "Luna.toml"
463
463
 
464
464
 
465
- def _write_codex_hook_source(source_root: Path) -> None:
465
+ def _register_source_codex_enforcer(hooks_path: Path) -> None:
466
+ all_manifest = cast(
467
+ dict[str, object], json.loads(hooks_path.read_text(encoding="utf-8"))
468
+ )
469
+ all_events = cast(dict[str, object], all_manifest["hooks"])
470
+ all_pre_tool_use = cast(list[object], all_events["PreToolUse"])
471
+ all_pre_tool_use.append(
472
+ {
473
+ "matcher": materializer.codex_hook_matcher,
474
+ "hooks": [
475
+ {
476
+ "type": "command",
477
+ "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/code_rules_enforcer.py",
478
+ "timeout": materializer.codex_hook_timeout_seconds,
479
+ }
480
+ ],
481
+ }
482
+ )
483
+ hooks_path.write_text(json.dumps(all_manifest), encoding="utf-8")
484
+
485
+
486
+ def _write_codex_hook_source(source_root: Path, has_enforcer: bool) -> None:
466
487
  package_root = Path(__file__).parents[2]
467
488
  source_hooks = source_root / "hooks"
468
489
  source_hooks.mkdir(parents=True, exist_ok=True)
469
- (source_hooks / "hooks.json").write_bytes(
470
- (package_root / "hooks" / "hooks.json").read_bytes()
471
- )
490
+ hooks_path = source_hooks / "hooks.json"
491
+ hooks_path.write_bytes((package_root / "hooks" / "hooks.json").read_bytes())
492
+ if has_enforcer:
493
+ _register_source_codex_enforcer(hooks_path)
472
494
  for each_relative_path in materializer.codex_hook_dependency_manifest:
473
495
  source_path = source_root / each_relative_path
474
496
  source_path.parent.mkdir(parents=True, exist_ok=True)
@@ -479,7 +501,7 @@ def test_build_plan_rejects_reparse_hook_dependencies(
479
501
  tmp_path: Path,
480
502
  ) -> None:
481
503
  source = tmp_path / "source"
482
- _write_codex_hook_source(source)
504
+ _write_codex_hook_source(source, has_enforcer=True)
483
505
  dependency_path = source / materializer.codex_enforcer_script_relative_path
484
506
  outside_path = tmp_path / "outside.py"
485
507
  outside_path.write_text("secret", encoding="utf-8")
@@ -498,7 +520,7 @@ def test_publish_plan_preserves_modified_enforcer_hook_as_conflict(
498
520
  ) -> None:
499
521
  source = tmp_path / "source"
500
522
  target = tmp_path / "target"
501
- _write_codex_hook_source(source)
523
+ _write_codex_hook_source(source, has_enforcer=True)
502
524
  target.mkdir()
503
525
  modified_command = f'python3 "{target / materializer.codex_enforcer_script_relative_path}" --custom'
504
526
  existing_manifest = {
@@ -892,6 +914,7 @@ def _prepare_projection_fixture(
892
914
  (Path(__file__).parents[2] / "hooks" / "hooks.json").read_text(encoding="utf-8"),
893
915
  encoding="utf-8",
894
916
  )
917
+ _register_source_codex_enforcer(source_hooks)
895
918
  package_root = Path(__file__).parents[2]
896
919
  for each_relative_path in materializer.codex_hook_dependency_manifest:
897
920
  target_dependency = source / each_relative_path
@@ -1082,6 +1105,166 @@ def _assert_detached_enforcer_behavior(target: Path) -> None:
1082
1105
  assert target_entrypoint.is_file()
1083
1106
 
1084
1107
 
1108
+ def _managed_enforcer_hook(target: Path) -> dict[str, object]:
1109
+ return {
1110
+ "type": "command",
1111
+ "command": (
1112
+ f'python3 "{target / materializer.codex_enforcer_script_relative_path}"'
1113
+ ),
1114
+ "timeout": materializer.codex_hook_timeout_seconds,
1115
+ }
1116
+
1117
+
1118
+ def _foreign_same_basename_hook() -> dict[str, object]:
1119
+ return {
1120
+ "type": "command",
1121
+ "command": "python D:/custom/hooks/blocking/code_rules_enforcer.py",
1122
+ }
1123
+
1124
+
1125
+ def _codex_hook_group(
1126
+ *all_hook_records: dict[str, object],
1127
+ ) -> dict[str, object]:
1128
+ return {
1129
+ "matcher": materializer.codex_hook_matcher,
1130
+ "hooks": list(all_hook_records),
1131
+ }
1132
+
1133
+
1134
+ def _codex_hook_manifest(
1135
+ *all_hook_groups: dict[str, object],
1136
+ ) -> dict[str, object]:
1137
+ return {"hooks": {"PreToolUse": list(all_hook_groups)}}
1138
+
1139
+
1140
+ def _write_current_source_without_enforcer(source: Path) -> None:
1141
+ _write_codex_hook_source(source, has_enforcer=False)
1142
+ source_manifest = json.loads(
1143
+ (source / "hooks" / "hooks.json").read_text(encoding="utf-8")
1144
+ )
1145
+ all_source_commands = _hook_commands(source_manifest, "PreToolUse")
1146
+ assert not any(
1147
+ "code_rules_enforcer.py" in each_command
1148
+ for each_command in all_source_commands
1149
+ )
1150
+
1151
+
1152
+ def _publish_codex_hook_fixture(
1153
+ source: Path, target: Path, target_manifest: dict[str, object]
1154
+ ) -> tuple[dict[str, Any], MaterializationReport, list[PlannedFile]]:
1155
+ target.mkdir()
1156
+ target_hooks_path = target / materializer.codex_hook_manifest_target_path
1157
+ target_hooks_path.write_text(json.dumps(target_manifest), encoding="utf-8")
1158
+ config = MaterializerConfig(source, target, should_apply=True)
1159
+ planned, report = build_plan(config, all_agents=[])
1160
+ publication = publish_plan(config, planned, report)
1161
+ projected_manifest = json.loads(target_hooks_path.read_text(encoding="utf-8"))
1162
+ return projected_manifest, publication, planned
1163
+
1164
+
1165
+ def test_current_source_manifest_retires_the_managed_codex_enforcer(
1166
+ tmp_path: Path,
1167
+ ) -> None:
1168
+ source = tmp_path / "source"
1169
+ target = tmp_path / "target"
1170
+ _write_current_source_without_enforcer(source)
1171
+ foreign_hook: dict[str, object] = {
1172
+ "type": "command",
1173
+ "command": "python foreign_advisory.py",
1174
+ }
1175
+ foreign_named_hook = _foreign_same_basename_hook()
1176
+ target_manifest = _codex_hook_manifest(
1177
+ _codex_hook_group(_managed_enforcer_hook(target)),
1178
+ _codex_hook_group(foreign_hook, foreign_named_hook),
1179
+ )
1180
+
1181
+ projected_manifest, _, planned = _publish_codex_hook_fixture(
1182
+ source, target, target_manifest
1183
+ )
1184
+ assert projected_manifest["hooks"]["PreToolUse"] == [
1185
+ {
1186
+ "matcher": "apply_patch",
1187
+ "hooks": [foreign_hook, foreign_named_hook],
1188
+ }
1189
+ ]
1190
+ all_planned_paths = {each_file.target_relative_path for each_file in planned}
1191
+ assert all_planned_paths.isdisjoint(materializer.codex_hook_dependency_manifest)
1192
+
1193
+
1194
+ def test_explicit_source_enforcer_preserves_foreign_same_basename_hook(
1195
+ tmp_path: Path,
1196
+ ) -> None:
1197
+ source = tmp_path / "source"
1198
+ target = tmp_path / "target"
1199
+ _write_codex_hook_source(source, has_enforcer=True)
1200
+ managed_hook = _managed_enforcer_hook(target)
1201
+ foreign_hook = _foreign_same_basename_hook()
1202
+ target_manifest = _codex_hook_manifest(
1203
+ _codex_hook_group(managed_hook, foreign_hook)
1204
+ )
1205
+ projected_manifest, publication, _ = _publish_codex_hook_fixture(
1206
+ source, target, target_manifest
1207
+ )
1208
+ projected_hooks = projected_manifest["hooks"]["PreToolUse"][0]["hooks"]
1209
+ assert publication.conflicted == 0
1210
+ assert projected_hooks == [foreign_hook, managed_hook]
1211
+
1212
+
1213
+ @pytest.mark.parametrize("has_source_enforcer", [False, True])
1214
+ def test_posix_case_distinct_enforcer_path_remains_foreign(
1215
+ tmp_path: Path,
1216
+ monkeypatch: pytest.MonkeyPatch,
1217
+ has_source_enforcer: bool,
1218
+ ) -> None:
1219
+ source = tmp_path / "source"
1220
+ target = tmp_path / "target"
1221
+ if has_source_enforcer:
1222
+ _write_codex_hook_source(source, has_enforcer=True)
1223
+ if not has_source_enforcer:
1224
+ _write_current_source_without_enforcer(source)
1225
+ managed_hook = _managed_enforcer_hook(target)
1226
+ managed_command = cast(str, managed_hook["command"])
1227
+ foreign_command = managed_command.replace(str(target), str(target).upper())
1228
+ assert foreign_command != managed_command
1229
+ assert foreign_command.casefold() == managed_command.casefold()
1230
+ foreign_hook = {**managed_hook, "command": foreign_command}
1231
+ target_manifest = _codex_hook_manifest(
1232
+ _codex_hook_group(managed_hook, foreign_hook)
1233
+ )
1234
+ monkeypatch.setattr(materializer.os, "name", "posix")
1235
+
1236
+ projected_manifest, _, _ = _publish_codex_hook_fixture(
1237
+ source, target, target_manifest
1238
+ )
1239
+
1240
+ all_expected_commands = [foreign_command]
1241
+ if has_source_enforcer:
1242
+ all_expected_commands.append(managed_command)
1243
+ assert _hook_commands(projected_manifest, "PreToolUse") == all_expected_commands
1244
+
1245
+
1246
+ def test_windows_case_variant_enforcer_path_is_managed(
1247
+ tmp_path: Path,
1248
+ monkeypatch: pytest.MonkeyPatch,
1249
+ ) -> None:
1250
+ source = tmp_path / "source"
1251
+ target = tmp_path / "target"
1252
+ _write_current_source_without_enforcer(source)
1253
+ managed_hook = _managed_enforcer_hook(target)
1254
+ managed_command = cast(str, managed_hook["command"])
1255
+ case_variant_command = managed_command.replace(str(target), str(target).upper())
1256
+ target_manifest = _codex_hook_manifest(
1257
+ _codex_hook_group({**managed_hook, "command": case_variant_command})
1258
+ )
1259
+ monkeypatch.setattr(materializer.os, "name", "nt")
1260
+
1261
+ projected_manifest, _, _ = _publish_codex_hook_fixture(
1262
+ source, target, target_manifest
1263
+ )
1264
+
1265
+ assert projected_manifest["hooks"] == {}
1266
+
1267
+
1085
1268
  def test_exact_cli_publishes_instruction_and_additive_hook_projection(
1086
1269
  tmp_path: Path, capsys: pytest.CaptureFixture[str]
1087
1270
  ) -> None:
@@ -0,0 +1,101 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ SCRIPTS_DIRECTORY = Path(__file__).resolve().parents[1]
7
+ if str(SCRIPTS_DIRECTORY) not in sys.path:
8
+ sys.path.insert(0, str(SCRIPTS_DIRECTORY))
9
+
10
+ from pr_verification.github_labels import remove_label_if_present
11
+ from pr_verification.model import RepositorySettings
12
+
13
+ PASS_LABEL = "local-checks:passed"
14
+ FULL_PAGE_SIZE = 100
15
+
16
+
17
+ def repository_settings() -> RepositorySettings:
18
+ return RepositorySettings(
19
+ slug="owner/repository",
20
+ clone_url="https://github.test/owner/repository.git",
21
+ )
22
+
23
+
24
+ def label_page(all_label_names: list[str]) -> list[dict[str, str]]:
25
+ return [{"name": each_label_name} for each_label_name in all_label_names]
26
+
27
+
28
+ class RecordingLabelReader:
29
+ def __init__(self, all_pages: list[object]) -> None:
30
+ self.all_pages = all_pages
31
+ self.all_endpoints: list[str] = []
32
+
33
+ def __call__(self, endpoint: str) -> object:
34
+ self.all_endpoints.append(endpoint)
35
+ return self.all_pages.pop(0)
36
+
37
+
38
+ class RecordingLabelDeleter:
39
+ def __init__(self) -> None:
40
+ self.all_endpoints: list[str] = []
41
+
42
+ def __call__(self, endpoint: str) -> None:
43
+ self.all_endpoints.append(endpoint)
44
+
45
+
46
+ def test_removes_a_label_a_later_page_holds() -> None:
47
+ all_first_page_names = [
48
+ f"topic-{each_index}" for each_index in range(FULL_PAGE_SIZE)
49
+ ]
50
+ read_labels_page = RecordingLabelReader(
51
+ [label_page(all_first_page_names), label_page([PASS_LABEL])]
52
+ )
53
+ delete_label = RecordingLabelDeleter()
54
+
55
+ remove_label_if_present(
56
+ read_labels_page, delete_label, repository_settings(), 7, PASS_LABEL
57
+ )
58
+
59
+ assert read_labels_page.all_endpoints == [
60
+ "/repos/owner/repository/issues/7/labels?per_page=100&page=1",
61
+ "/repos/owner/repository/issues/7/labels?per_page=100&page=2",
62
+ ]
63
+ assert delete_label.all_endpoints == [
64
+ "/repos/owner/repository/issues/7/labels/local-checks%3Apassed"
65
+ ]
66
+
67
+
68
+ def test_reads_one_page_when_a_short_page_lacks_the_label() -> None:
69
+ read_labels_page = RecordingLabelReader([label_page(["topic"])])
70
+ delete_label = RecordingLabelDeleter()
71
+
72
+ remove_label_if_present(
73
+ read_labels_page, delete_label, repository_settings(), 7, PASS_LABEL
74
+ )
75
+
76
+ assert len(read_labels_page.all_endpoints) == 1
77
+ assert delete_label.all_endpoints == []
78
+
79
+
80
+ def test_reads_one_page_when_the_first_page_holds_the_label() -> None:
81
+ read_labels_page = RecordingLabelReader([label_page([PASS_LABEL])])
82
+ delete_label = RecordingLabelDeleter()
83
+
84
+ remove_label_if_present(
85
+ read_labels_page, delete_label, repository_settings(), 7, PASS_LABEL
86
+ )
87
+
88
+ assert len(read_labels_page.all_endpoints) == 1
89
+ assert len(delete_label.all_endpoints) == 1
90
+
91
+
92
+ def test_reads_no_further_page_when_a_page_is_not_a_list() -> None:
93
+ read_labels_page = RecordingLabelReader([{"message": "Not Found"}])
94
+ delete_label = RecordingLabelDeleter()
95
+
96
+ remove_label_if_present(
97
+ read_labels_page, delete_label, repository_settings(), 7, PASS_LABEL
98
+ )
99
+
100
+ assert len(read_labels_page.all_endpoints) == 1
101
+ assert delete_label.all_endpoints == []
@@ -0,0 +1,125 @@
1
+ from __future__ import annotations
2
+
3
+ import io
4
+ import sys
5
+ import urllib.error
6
+ import urllib.request
7
+ from collections.abc import Iterator
8
+ from contextlib import AbstractContextManager, contextmanager
9
+ from email.message import Message
10
+ from pathlib import Path
11
+ from unittest.mock import Mock
12
+
13
+ import jwt
14
+ import pytest
15
+
16
+ SCRIPTS_DIRECTORY = Path(__file__).resolve().parents[1]
17
+ if str(SCRIPTS_DIRECTORY) not in sys.path:
18
+ sys.path.insert(0, str(SCRIPTS_DIRECTORY))
19
+
20
+ from pr_verification.config.timing import HTTP_TIMEOUT_SECONDS
21
+ from pr_verification.github_parsing import GitHubError
22
+ from pr_verification.github_transport import HttpReply, encode_app_jwt, request_http
23
+
24
+
25
+ @contextmanager
26
+ def reply_context(http_reply_mock: Mock) -> Iterator[Mock]:
27
+ yield http_reply_mock
28
+
29
+
30
+ def make_http_reply_mock(
31
+ status_code: int, headers: dict[str, str], body: bytes
32
+ ) -> Mock:
33
+ http_reply_mock = Mock()
34
+ http_reply_mock.status = status_code
35
+ http_reply_mock.headers.items.return_value = list(headers.items())
36
+ http_reply_mock.read.return_value = body
37
+ return http_reply_mock
38
+
39
+
40
+ def test_request_http_returns_success_reply_and_request_details(
41
+ monkeypatch: pytest.MonkeyPatch,
42
+ ) -> None:
43
+ http_reply_mock = make_http_reply_mock(
44
+ 200, {"Content-Type": "application/json"}, b'{"ok":true}'
45
+ )
46
+ all_requests: list[tuple[urllib.request.Request, float]] = []
47
+
48
+ def open_url(
49
+ request: urllib.request.Request, timeout: float
50
+ ) -> AbstractContextManager[Mock]:
51
+ all_requests.append((request, timeout))
52
+ return reply_context(http_reply_mock)
53
+
54
+ monkeypatch.setattr(urllib.request, "urlopen", open_url)
55
+
56
+ reply = request_http(
57
+ "GET",
58
+ "https://github.test/resource",
59
+ {"Accept": "application/json"},
60
+ None,
61
+ )
62
+
63
+ assert reply == HttpReply(200, {"Content-Type": "application/json"}, b'{"ok":true}')
64
+ assert all_requests[0][0].full_url == "https://github.test/resource"
65
+ assert all_requests[0][0].get_method() == "GET"
66
+ assert all_requests[0][1] == HTTP_TIMEOUT_SECONDS
67
+
68
+
69
+ def test_request_http_returns_http_error_reply(monkeypatch: pytest.MonkeyPatch) -> None:
70
+ http_headers = Message()
71
+ http_headers["X-Request-Id"] = "request-id"
72
+ http_error = urllib.error.HTTPError(
73
+ "https://github.test/resource",
74
+ 404,
75
+ "missing",
76
+ http_headers,
77
+ io.BytesIO(b"not found"),
78
+ )
79
+
80
+ def open_url(request: urllib.request.Request, timeout: float) -> Mock:
81
+ raise http_error
82
+
83
+ monkeypatch.setattr(urllib.request, "urlopen", open_url)
84
+
85
+ reply = request_http("GET", "https://github.test/resource", {}, None)
86
+
87
+ assert reply == HttpReply(404, {"X-Request-Id": "request-id"}, b"not found")
88
+
89
+
90
+ def test_request_http_propagates_url_error_as_github_error(
91
+ monkeypatch: pytest.MonkeyPatch,
92
+ ) -> None:
93
+ def open_url(request: urllib.request.Request, timeout: float) -> Mock:
94
+ raise urllib.error.URLError("offline")
95
+
96
+ monkeypatch.setattr(urllib.request, "urlopen", open_url)
97
+
98
+ with pytest.raises(GitHubError, match="offline"):
99
+ request_http("GET", "https://github.test/resource", {}, None)
100
+
101
+
102
+ def test_encode_app_jwt_preserves_arguments_and_normalizes_bytes_and_strings(
103
+ monkeypatch: pytest.MonkeyPatch,
104
+ ) -> None:
105
+ all_calls: list[tuple[dict[str, int | str], str, str]] = []
106
+ encoded_tokens = iter((b"encoded-bytes", "encoded-string"))
107
+
108
+ def encode_jwt(
109
+ claims: dict[str, int | str], private_key: str, algorithm: str
110
+ ) -> bytes | str:
111
+ all_calls.append((claims, private_key, algorithm))
112
+ return next(encoded_tokens)
113
+
114
+ monkeypatch.setattr(jwt, "encode", encode_jwt)
115
+ claims: dict[str, int | str] = {"iss": "42", "iat": 940, "exp": 1540}
116
+
117
+ bytes_token = encode_app_jwt(claims, "private-key", "RS256")
118
+ string_token = encode_app_jwt(claims, "private-key", "RS256")
119
+
120
+ assert bytes_token == "encoded-bytes"
121
+ assert string_token == "encoded-string"
122
+ assert all_calls == [
123
+ (claims, "private-key", "RS256"),
124
+ (claims, "private-key", "RS256"),
125
+ ]