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,376 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import subprocess
5
+ import sys
6
+ from collections.abc import Callable
7
+ from pathlib import Path, PurePosixPath
8
+
9
+ SCRIPTS_DIRECTORY = Path(__file__).resolve().parents[1]
10
+ if str(SCRIPTS_DIRECTORY) not in sys.path:
11
+ sys.path.insert(0, str(SCRIPTS_DIRECTORY))
12
+
13
+ from automatic_advisory.model import (
14
+ AdvisoryRegistration,
15
+ AdvisorySettings,
16
+ ChildOutcome,
17
+ )
18
+ from automatic_advisory.runner import (
19
+ AdvisoryGitHub,
20
+ AutomaticAdvisoryRunner,
21
+ Publication,
22
+ run_verification_child,
23
+ )
24
+ from pr_verification.model import PullRequestCandidate, RepositorySettings
25
+
26
+ VALID_ADVISORY_SETTINGS_TEMPLATE = '{"version":1,"app_id":1,"installation_id":1,"private_key_path":"$ROOT","poll_seconds":0.25,"child_timeout_seconds":45.5,"registrations":[{"repository":"owner/repository","pull_request":1,"checkout":"$ROOT","manifest":"manifest.json","report":"$ROOT","state":"$ROOT","base_ref":"main","remote":"origin"}]}'
27
+
28
+
29
+ class FakeGitHub(AdvisoryGitHub):
30
+ def __init__(self, candidate: PullRequestCandidate) -> None:
31
+ self.candidate = candidate
32
+ self.all_events: list[str] = []
33
+ self.all_merge_requirements: list[bool] = []
34
+ self.remove_label_failures = 0
35
+
36
+ def list_open_candidates(
37
+ self,
38
+ repository: RepositorySettings,
39
+ *,
40
+ should_require_merge_commit: bool = True,
41
+ ) -> tuple[PullRequestCandidate, ...]:
42
+ self.all_merge_requirements.append(should_require_merge_commit)
43
+ return (self.candidate,)
44
+
45
+ def remove_label(
46
+ self, repository: RepositorySettings, pull_request_number: int, label: str
47
+ ) -> None:
48
+ self.all_events.append("remove-label")
49
+ if self.remove_label_failures:
50
+ self.remove_label_failures -= 1
51
+ raise RuntimeError("label service unavailable")
52
+
53
+
54
+ class FakePublication:
55
+ def __init__(self, status: str) -> None:
56
+ self.status = status
57
+ self.description = "local check finished"
58
+
59
+
60
+ def _run_git(repository_path: Path, *arguments: str) -> str:
61
+ completed_process = subprocess.run(
62
+ ["git", "-C", str(repository_path), *arguments],
63
+ capture_output=True,
64
+ check=False,
65
+ text=True,
66
+ )
67
+ assert completed_process.returncode == 0, completed_process.stderr
68
+ return completed_process.stdout.strip()
69
+
70
+
71
+ def _build_checkout(tmp_path: Path) -> tuple[Path, str]:
72
+ checkout_path = tmp_path / "checkout"
73
+ remote_path = tmp_path / "remote.git"
74
+ checkout_path.mkdir()
75
+ _initialize_checkout(checkout_path)
76
+ _write_checkout_files(checkout_path)
77
+ _run_git(checkout_path, "add", "tracked.txt")
78
+ _run_git(checkout_path, "add", "manifest.json")
79
+ _run_git(checkout_path, "add", ".github/ci/local_verify.py")
80
+ _run_git(checkout_path, "commit", "--quiet", "-m", "test")
81
+ _create_bare_remote(checkout_path, remote_path)
82
+ return checkout_path, _run_git(checkout_path, "rev-parse", "HEAD")
83
+
84
+
85
+ def _initialize_checkout(checkout_path: Path) -> None:
86
+ _run_git(checkout_path, "init", "--quiet")
87
+ hooks_directory = checkout_path / ".test-hooks"
88
+ hooks_directory.mkdir()
89
+ _run_git(checkout_path, "config", "--local", "core.hooksPath", str(hooks_directory))
90
+ _run_git(checkout_path, "checkout", "-b", "main")
91
+ _run_git(checkout_path, "config", "user.email", "test@example.com")
92
+ _run_git(checkout_path, "config", "user.name", "Automatic Advisory Test")
93
+
94
+
95
+ def _write_checkout_files(checkout_path: Path) -> None:
96
+ (checkout_path / "tracked.txt").write_text("clean\n", encoding="utf-8")
97
+ (checkout_path / "manifest.json").write_text(
98
+ json.dumps(
99
+ {
100
+ "version": 1,
101
+ "checks": [
102
+ {
103
+ "id": "real-child",
104
+ "argv": [sys.executable, "-c", "raise SystemExit(0)"],
105
+ "cwd": ".",
106
+ "timeout_seconds": 10,
107
+ }
108
+ ],
109
+ }
110
+ ),
111
+ encoding="utf-8",
112
+ )
113
+ _write_local_verify_wrapper(checkout_path)
114
+
115
+
116
+ def _write_local_verify_wrapper(checkout_path: Path) -> None:
117
+ wrapper_path = checkout_path / ".github" / "ci" / "local_verify.py"
118
+ wrapper_path.parent.mkdir(parents=True)
119
+ wrapper_path.write_text(
120
+ "import argparse\n"
121
+ "import os\n"
122
+ "from pathlib import Path\n"
123
+ "import subprocess\n"
124
+ "import sys\n"
125
+ "if os.environ.get('PYTEST_XDIST_AUTO_NUM_WORKERS') != '2':\n"
126
+ " raise SystemExit(9)\n"
127
+ "if str(Path.cwd()) not in os.environ.get('PYTHONPATH', '').split(os.pathsep):\n"
128
+ " raise SystemExit(10)\n"
129
+ "parser = argparse.ArgumentParser()\n"
130
+ "parser.add_argument('--base', required=True)\n"
131
+ "parser.add_argument('--executor', required=True)\n"
132
+ "parser.add_argument('--output', required=True)\n"
133
+ "arguments = parser.parse_args()\n"
134
+ "selected_manifest_path = Path(arguments.output).with_name(Path(arguments.output).stem + '.selected-manifest.json')\n"
135
+ "selected_manifest_path.parent.mkdir(parents=True, exist_ok=True)\n"
136
+ "selected_manifest_path.write_text(Path('manifest.json').read_text(encoding='utf-8'), encoding='utf-8')\n"
137
+ "raise SystemExit(subprocess.call([\n"
138
+ " sys.executable, arguments.executor, '--manifest', 'manifest.json',\n"
139
+ " '--repo', '.', '--base', arguments.base, '--output', arguments.output\n"
140
+ "]))\n",
141
+ encoding="utf-8",
142
+ )
143
+
144
+
145
+ def _create_bare_remote(checkout_path: Path, remote_path: Path) -> None:
146
+ _run_git(
147
+ checkout_path,
148
+ "clone",
149
+ "--bare",
150
+ str(checkout_path),
151
+ str(remote_path),
152
+ )
153
+ _run_git(checkout_path, "remote", "add", "origin", str(remote_path))
154
+
155
+
156
+ def _build_registration(tmp_path: Path, checkout_path: Path) -> AdvisoryRegistration:
157
+ manifest_path = checkout_path / "manifest.json"
158
+ manifest_path.write_text(
159
+ json.dumps(
160
+ {
161
+ "version": 1,
162
+ "checks": [
163
+ {
164
+ "id": "real-child",
165
+ "argv": [sys.executable, "-c", "raise SystemExit(0)"],
166
+ "cwd": ".",
167
+ "timeout_seconds": 10,
168
+ }
169
+ ],
170
+ }
171
+ ),
172
+ encoding="utf-8",
173
+ )
174
+ return AdvisoryRegistration(
175
+ RepositorySettings("JonEcho/python-automation", "unused"),
176
+ 2985,
177
+ checkout_path,
178
+ PurePosixPath("manifest.json"),
179
+ tmp_path / "reports" / "pr-2985.json",
180
+ tmp_path / "state" / "pr-2985.json",
181
+ "main",
182
+ "origin",
183
+ )
184
+
185
+
186
+ def _build_candidate(head_sha: str, merge_sha: str = "unused") -> PullRequestCandidate:
187
+ return PullRequestCandidate(
188
+ "JonEcho/python-automation",
189
+ 2985,
190
+ "main",
191
+ head_sha,
192
+ head_sha,
193
+ merge_sha,
194
+ False,
195
+ )
196
+
197
+
198
+ def _build_settings(registration: AdvisoryRegistration) -> AdvisorySettings:
199
+ return AdvisorySettings(
200
+ "https://api.github.com",
201
+ 4841271,
202
+ 159293880,
203
+ registration.state_path.parent / "private-key.pem",
204
+ 60.0,
205
+ 30.0,
206
+ (registration,),
207
+ )
208
+
209
+
210
+ def _valid_advisory_settings_fields(tmp_path: Path) -> dict[str, object]:
211
+ return json.loads(
212
+ VALID_ADVISORY_SETTINGS_TEMPLATE.replace('"$ROOT"', json.dumps(str(tmp_path)))
213
+ )
214
+
215
+
216
+ def _publish_passed(
217
+ *all_arguments: object,
218
+ ) -> Publication:
219
+ return FakePublication("passed")
220
+
221
+
222
+ def _run_recorded_child(
223
+ all_events: list[str],
224
+ registration: AdvisoryRegistration,
225
+ base_ref: str,
226
+ timeout_seconds: float,
227
+ ) -> ChildOutcome:
228
+ all_events.append("child")
229
+ child_outcome = run_verification_child(registration, base_ref, timeout_seconds)
230
+ assert child_outcome.exit_code == 0, child_outcome.stderr_text
231
+ return child_outcome
232
+
233
+
234
+ def _run_counted_child(
235
+ child_calls: list[str],
236
+ registration: AdvisoryRegistration,
237
+ base_ref: str,
238
+ timeout_seconds: float,
239
+ ) -> ChildOutcome:
240
+ assert registration.checkout_path.is_dir()
241
+ assert base_ref
242
+ assert timeout_seconds > 0
243
+ child_calls.append("child")
244
+ _write_child_artifacts(registration, 0)
245
+ return ChildOutcome(0, "", "", False)
246
+
247
+
248
+ def _write_child_artifacts(
249
+ registration: AdvisoryRegistration,
250
+ exit_code: int,
251
+ ) -> None:
252
+ registration.report_path.parent.mkdir(parents=True, exist_ok=True)
253
+ registration.report_path.write_text(
254
+ json.dumps({"exit_code": exit_code}),
255
+ encoding="utf-8",
256
+ )
257
+ registration.selected_manifest_path.write_text(
258
+ registration.manifest_absolute_path.read_text(encoding="utf-8"),
259
+ encoding="utf-8",
260
+ )
261
+
262
+
263
+ def _build_counting_runner(
264
+ registration: AdvisoryRegistration,
265
+ github: FakeGitHub,
266
+ child_calls: list[str],
267
+ publisher: Callable[..., Publication],
268
+ ) -> AutomaticAdvisoryRunner:
269
+ return AutomaticAdvisoryRunner(
270
+ _build_settings(registration),
271
+ github,
272
+ child_runner=lambda child_registration, base_ref, timeout_seconds: (
273
+ _run_counted_child(
274
+ child_calls, child_registration, base_ref, timeout_seconds
275
+ )
276
+ ),
277
+ publisher=publisher,
278
+ clock=lambda: "2026-09-05T00:00:00+00:00",
279
+ )
280
+
281
+
282
+ def _publish_recorded(
283
+ all_events: list[str],
284
+ github: FakeGitHub,
285
+ github_client: object,
286
+ repository: RepositorySettings,
287
+ pull_request_number: int,
288
+ checkout: Path,
289
+ manifest: Path,
290
+ report: Path,
291
+ ) -> FakePublication:
292
+ all_events.append("publish")
293
+ assert github_client is github
294
+ assert repository.slug == "JonEcho/python-automation"
295
+ assert pull_request_number == 2985
296
+ assert checkout.is_dir()
297
+ assert manifest.is_file()
298
+ assert report.is_file()
299
+ return FakePublication("passed")
300
+
301
+
302
+ def _build_real_runner(
303
+ registration: AdvisoryRegistration,
304
+ github: FakeGitHub,
305
+ ) -> tuple[AutomaticAdvisoryRunner, list[str]]:
306
+ all_events = github.all_events
307
+ runner = AutomaticAdvisoryRunner(
308
+ _build_settings(registration),
309
+ github,
310
+ child_runner=lambda child_registration, base_ref, timeout_seconds: (
311
+ _run_recorded_child(
312
+ all_events, child_registration, base_ref, timeout_seconds
313
+ )
314
+ ),
315
+ publisher=lambda github_client, repository, pull_request_number, checkout, manifest, report: (
316
+ _publish_recorded(
317
+ all_events,
318
+ github,
319
+ github_client,
320
+ repository,
321
+ pull_request_number,
322
+ checkout,
323
+ manifest,
324
+ report,
325
+ )
326
+ ),
327
+ clock=lambda: "2026-09-05T00:00:00+00:00",
328
+ )
329
+ return runner, all_events
330
+
331
+
332
+ def test_runner_executes_real_child_and_publishes_after_label_removal(
333
+ tmp_path: Path,
334
+ ) -> None:
335
+ checkout_path, head_sha = _build_checkout(tmp_path)
336
+ registration = _build_registration(tmp_path, checkout_path)
337
+ github = FakeGitHub(_build_candidate(head_sha))
338
+ runner, all_events = _build_real_runner(registration, github)
339
+
340
+ state = runner.run_once()[0]
341
+
342
+ assert state.status == "passed"
343
+ assert all_events == ["remove-label", "child", "publish"]
344
+ assert (
345
+ json.loads(registration.state_path.read_text(encoding="utf-8"))["head_sha"]
346
+ == head_sha
347
+ )
348
+
349
+
350
+ def test_runner_waits_for_matching_clean_checkout(tmp_path: Path) -> None:
351
+ checkout_path, head_sha = _build_checkout(tmp_path)
352
+ registration = _build_registration(tmp_path, checkout_path)
353
+ github = FakeGitHub(_build_candidate("remote-head"))
354
+ child_calls: list[str] = []
355
+ runner = _build_counting_runner(registration, github, child_calls, _publish_passed)
356
+
357
+ state = runner.run_once()[0]
358
+
359
+ assert state.status == "waiting"
360
+ assert state.head_sha == "remote-head"
361
+ assert child_calls == []
362
+ assert head_sha != state.head_sha
363
+
364
+
365
+ def test_runner_skips_an_unchanged_published_identity(tmp_path: Path) -> None:
366
+ checkout_path, head_sha = _build_checkout(tmp_path)
367
+ registration = _build_registration(tmp_path, checkout_path)
368
+ github = FakeGitHub(_build_candidate(head_sha))
369
+ child_calls: list[str] = []
370
+ runner = _build_counting_runner(registration, github, child_calls, _publish_passed)
371
+
372
+ runner.run_once()
373
+ state = runner.run_once()[0]
374
+
375
+ assert state.status == "passed"
376
+ assert child_calls == ["child"]
@@ -0,0 +1,71 @@
1
+ from __future__ import annotations
2
+
3
+ import subprocess
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ TESTS_DIRECTORY = Path(__file__).resolve().parent
8
+ if str(TESTS_DIRECTORY) not in sys.path:
9
+ sys.path.insert(0, str(TESTS_DIRECTORY))
10
+
11
+ import test_automatic_advisory as support
12
+ import test_automatic_advisory_repairs as process_support
13
+
14
+ OWNER_EXIT_TIMEOUT_SECONDS = 10.0
15
+ CHILD_START_WAIT_SECONDS = 5.0
16
+ CHILD_START_POLL_SECONDS = 0.05
17
+
18
+
19
+ def _build_owner_exit_script(
20
+ checkout_path: Path,
21
+ child_identifier_path: Path,
22
+ ) -> str:
23
+ child_script = (
24
+ "import os,time\n"
25
+ "from pathlib import Path\n"
26
+ f"Path({str(child_identifier_path)!r}).write_text(str(os.getpid()), encoding='utf-8')\n"
27
+ "time.sleep(30)\n"
28
+ )
29
+ return (
30
+ "import os,sys,time\n"
31
+ "from pathlib import Path\n"
32
+ "from types import SimpleNamespace\n"
33
+ "from automatic_advisory import execution\n"
34
+ f"registration = SimpleNamespace(checkout_path=Path({str(checkout_path)!r}))\n"
35
+ "scripts_directory = Path(execution.__file__).resolve().parents[1]\n"
36
+ "child_environment = execution._build_child_environment(registration, scripts_directory)\n"
37
+ "owned_child = execution._start_child_process(\n"
38
+ f" (sys.executable, '-c', {child_script!r}), registration, child_environment\n"
39
+ ")\n"
40
+ "if owned_child is None:\n"
41
+ " raise SystemExit(2)\n"
42
+ f"deadline = time.monotonic() + {CHILD_START_WAIT_SECONDS!r}\n"
43
+ f"while not Path({str(child_identifier_path)!r}).is_file() and time.monotonic() < deadline:\n"
44
+ f" time.sleep({CHILD_START_POLL_SECONDS!r})\n"
45
+ f"if not Path({str(child_identifier_path)!r}).is_file():\n"
46
+ " raise SystemExit(3)\n"
47
+ "os._exit(0)\n"
48
+ )
49
+
50
+
51
+ def test_owner_exit_closes_job_and_ends_active_child(tmp_path: Path) -> None:
52
+ if sys.platform != "win32":
53
+ return
54
+ child_identifier_path = tmp_path / "owned-child.pid"
55
+ owner_script = _build_owner_exit_script(tmp_path, child_identifier_path)
56
+ completed_owner = subprocess.run(
57
+ (sys.executable, "-c", owner_script),
58
+ cwd=support.SCRIPTS_DIRECTORY,
59
+ capture_output=True,
60
+ check=False,
61
+ text=True,
62
+ timeout=OWNER_EXIT_TIMEOUT_SECONDS,
63
+ )
64
+ assert child_identifier_path.is_file(), completed_owner.stderr
65
+ child_identifier = int(child_identifier_path.read_text(encoding="utf-8"))
66
+
67
+ try:
68
+ assert completed_owner.returncode == 0, completed_owner.stderr
69
+ assert process_support._wait_for_process_exit(child_identifier)
70
+ finally:
71
+ process_support._end_process_for_test(child_identifier)
@@ -0,0 +1,216 @@
1
+ from __future__ import annotations
2
+
3
+ import io
4
+ import json
5
+ import sys
6
+ from collections.abc import Callable
7
+ from pathlib import Path
8
+
9
+ import pytest
10
+
11
+ TEST_DIRECTORY = Path(__file__).resolve().parent
12
+ if str(TEST_DIRECTORY) not in sys.path:
13
+ sys.path.insert(0, str(TEST_DIRECTORY))
14
+
15
+ from automatic_advisory import cli as advisory_cli
16
+ from automatic_advisory.model import AdvisoryState
17
+ from pr_verification.lock import SupervisorLockError
18
+
19
+
20
+ class _CycleFailsOnceRunner:
21
+ def __init__(self, cycle_error: Exception) -> None:
22
+ self.cycle_error = cycle_error
23
+ self.all_cycles: list[str] = []
24
+
25
+ def run_once(self, should_rerun: bool = False) -> tuple[AdvisoryState, ...]:
26
+ self.all_cycles.append("cycle")
27
+ if len(self.all_cycles) == 1:
28
+ raise self.cycle_error
29
+ return (
30
+ AdvisoryState(
31
+ "JonEcho/python-automation",
32
+ 3087,
33
+ "passed",
34
+ "unchanged remote head and base",
35
+ None,
36
+ None,
37
+ "2026-09-06T16:00:00+00:00",
38
+ None,
39
+ "report.json",
40
+ ),
41
+ )
42
+
43
+
44
+ def _stop_polling_on_the_second_sleep(
45
+ all_sleeps: list[float],
46
+ ) -> Callable[[float], None]:
47
+ def sleeper(poll_seconds: float) -> None:
48
+ all_sleeps.append(poll_seconds)
49
+ if len(all_sleeps) == 2:
50
+ raise KeyboardInterrupt
51
+
52
+ return sleeper
53
+
54
+
55
+ @pytest.mark.parametrize(
56
+ "cycle_error",
57
+ [
58
+ SupervisorLockError("supervisor lock is held by another owner"),
59
+ OSError("state file is temporarily unavailable"),
60
+ ],
61
+ )
62
+ def test_polling_reports_a_failed_cycle_and_runs_the_next_cycle(
63
+ cycle_error: Exception,
64
+ tmp_path: Path,
65
+ ) -> None:
66
+ advisory_runner = _CycleFailsOnceRunner(cycle_error)
67
+ all_sleeps: list[float] = []
68
+ stdout = io.StringIO()
69
+
70
+ exit_code = advisory_cli.run_polling(
71
+ advisory_runner,
72
+ 1.5,
73
+ stdout,
74
+ tmp_path / "poll-errors.log",
75
+ _stop_polling_on_the_second_sleep(all_sleeps),
76
+ )
77
+
78
+ assert exit_code == 0
79
+ assert advisory_runner.all_cycles == ["cycle", "cycle"]
80
+ assert all_sleeps == [1.5, 1.5]
81
+ all_lines = stdout.getvalue().splitlines()
82
+ assert json.loads(all_lines[0]) == {"poll_error": str(cycle_error)}
83
+ assert json.loads(all_lines[1])["status"] == "passed"
84
+
85
+
86
+ class _EveryCycleFailsRunner:
87
+ def __init__(self, cycle_error: Exception) -> None:
88
+ self.cycle_error = cycle_error
89
+ self.all_cycles: list[str] = []
90
+
91
+ def run_once(self, should_rerun: bool = False) -> tuple[AdvisoryState, ...]:
92
+ self.all_cycles.append("cycle")
93
+ raise self.cycle_error
94
+
95
+
96
+ def _stop_polling_after(cycle_limit: int) -> Callable[[float], None]:
97
+ all_sleeps: list[float] = []
98
+
99
+ def sleeper(poll_seconds: float) -> None:
100
+ all_sleeps.append(poll_seconds)
101
+ if len(all_sleeps) == cycle_limit:
102
+ raise KeyboardInterrupt
103
+
104
+ return sleeper
105
+
106
+
107
+ def test_polling_logs_the_failed_cycle_line_beside_the_state_files(
108
+ tmp_path: Path,
109
+ ) -> None:
110
+ cycle_error = SupervisorLockError("supervisor lock is held by another owner")
111
+ poll_error_log_path = tmp_path / "state" / "poll-errors.log"
112
+
113
+ exit_code = advisory_cli.run_polling(
114
+ _EveryCycleFailsRunner(cycle_error),
115
+ 1.5,
116
+ io.StringIO(),
117
+ poll_error_log_path,
118
+ _stop_polling_after(1),
119
+ )
120
+
121
+ assert exit_code == 0
122
+ all_lines = poll_error_log_path.read_text(encoding="utf-8").splitlines()
123
+ assert [json.loads(each_line) for each_line in all_lines] == [
124
+ {"poll_error": str(cycle_error)}
125
+ ]
126
+
127
+
128
+ class _NumberedFailuresRunner:
129
+ def __init__(self) -> None:
130
+ self.all_cycles: list[str] = []
131
+
132
+ def run_once(self, should_rerun: bool = False) -> tuple[AdvisoryState, ...]:
133
+ self.all_cycles.append("cycle")
134
+ raise OSError(f"state file is unavailable on cycle {len(self.all_cycles)}")
135
+
136
+
137
+ def test_poll_error_log_keeps_the_newest_two_hundred_lines(tmp_path: Path) -> None:
138
+ poll_error_log_path = tmp_path / "poll-errors.log"
139
+
140
+ advisory_cli.run_polling(
141
+ _NumberedFailuresRunner(),
142
+ 0.0,
143
+ io.StringIO(),
144
+ poll_error_log_path,
145
+ _stop_polling_after(250),
146
+ )
147
+
148
+ all_lines = poll_error_log_path.read_text(encoding="utf-8").splitlines()
149
+ assert [json.loads(each_line)["poll_error"] for each_line in all_lines] == [
150
+ f"state file is unavailable on cycle {each_cycle}"
151
+ for each_cycle in range(51, 251)
152
+ ]
153
+
154
+
155
+ def test_poll_error_log_holding_non_utf8_bytes_leaves_the_poller_running(
156
+ tmp_path: Path,
157
+ ) -> None:
158
+ poll_error_log_path = tmp_path / "poll-errors.log"
159
+ poll_error_log_path.write_bytes(b"\xff\xfe not a utf-8 line\n")
160
+ advisory_runner = _EveryCycleFailsRunner(OSError("state file is unavailable"))
161
+
162
+ exit_code = advisory_cli.run_polling(
163
+ advisory_runner,
164
+ 0.0,
165
+ io.StringIO(),
166
+ poll_error_log_path,
167
+ _stop_polling_after(1),
168
+ )
169
+
170
+ assert exit_code == 0
171
+ assert advisory_runner.all_cycles == ["cycle"]
172
+ all_lines = poll_error_log_path.read_text(
173
+ encoding="utf-8", errors="replace"
174
+ ).splitlines()
175
+ assert json.loads(all_lines[-1]) == {"poll_error": "state file is unavailable"}
176
+
177
+
178
+ def test_poll_error_log_line_read_value_error_leaves_the_poller_running(
179
+ tmp_path: Path,
180
+ monkeypatch: pytest.MonkeyPatch,
181
+ ) -> None:
182
+ def raise_value_error(poll_error_log_path: Path) -> list[str]:
183
+ raise ValueError("the log holds bytes no decoder accepts")
184
+
185
+ monkeypatch.setattr(advisory_cli, "_poll_error_log_lines", raise_value_error)
186
+ advisory_runner = _EveryCycleFailsRunner(OSError("state file is unavailable"))
187
+
188
+ exit_code = advisory_cli.run_polling(
189
+ advisory_runner,
190
+ 0.0,
191
+ io.StringIO(),
192
+ tmp_path / "poll-errors.log",
193
+ _stop_polling_after(2),
194
+ )
195
+
196
+ assert exit_code == 0
197
+ assert advisory_runner.all_cycles == ["cycle", "cycle"]
198
+
199
+
200
+ def test_poll_error_log_that_cannot_be_written_leaves_the_poller_running(
201
+ tmp_path: Path,
202
+ ) -> None:
203
+ unwritable_log_path = tmp_path / "state.json" / "poll-errors.log"
204
+ unwritable_log_path.parent.write_text("not a directory\n", encoding="utf-8")
205
+ advisory_runner = _EveryCycleFailsRunner(OSError("state file is unavailable"))
206
+
207
+ exit_code = advisory_cli.run_polling(
208
+ advisory_runner,
209
+ 0.0,
210
+ io.StringIO(),
211
+ unwritable_log_path,
212
+ _stop_polling_after(2),
213
+ )
214
+
215
+ assert exit_code == 0
216
+ assert advisory_runner.all_cycles == ["cycle", "cycle"]