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,400 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import signal
6
+ import subprocess
7
+ import sys
8
+ import time
9
+ from pathlib import Path
10
+ from types import SimpleNamespace
11
+
12
+ import pytest
13
+
14
+ TESTS_DIRECTORY = Path(__file__).resolve().parent
15
+ if str(TESTS_DIRECTORY) not in sys.path:
16
+ sys.path.insert(0, str(TESTS_DIRECTORY))
17
+
18
+ import test_automatic_advisory as support
19
+ from automatic_advisory import cli, git, publisher
20
+ from pr_verification.model import RepositorySettings, StatusState
21
+
22
+ PROCESS_RUNNER_TIMEOUT_SECONDS = 10.0
23
+ PRODUCTION_CHILD_TIMEOUT_SECONDS = 3.0
24
+ PROCESS_EXIT_WAIT_SECONDS = 2.0
25
+ PROCESS_POLL_SECONDS = 0.05
26
+
27
+
28
+ class _SuccessfulPublicationAdapter:
29
+ status = StatusState.SUCCESS
30
+ description = "passed"
31
+
32
+
33
+ def _publisher_module_recording_paths(
34
+ all_published_manifest_paths: list[Path],
35
+ ) -> SimpleNamespace:
36
+ def record_publication(
37
+ _github: object,
38
+ _repository: RepositorySettings,
39
+ _pull_request_number: int,
40
+ _checkout_path: Path,
41
+ manifest_path: Path,
42
+ _report_path: Path,
43
+ ) -> _SuccessfulPublicationAdapter:
44
+ all_published_manifest_paths.append(manifest_path)
45
+ return _SuccessfulPublicationAdapter()
46
+
47
+ return SimpleNamespace(publish_local_report=record_publication)
48
+
49
+
50
+ def _build_counted_runner(
51
+ registration: support.AdvisoryRegistration,
52
+ github: support.FakeGitHub,
53
+ child_calls: list[str],
54
+ ) -> support.AutomaticAdvisoryRunner:
55
+ def run_child(
56
+ child_registration: support.AdvisoryRegistration,
57
+ base_ref: str,
58
+ timeout_seconds: float,
59
+ ) -> support.ChildOutcome:
60
+ return support._run_counted_child(
61
+ child_calls,
62
+ child_registration,
63
+ base_ref,
64
+ timeout_seconds,
65
+ )
66
+
67
+ return support.AutomaticAdvisoryRunner(
68
+ support._build_settings(registration),
69
+ github,
70
+ child_runner=run_child,
71
+ clock=lambda: "2026-09-05T00:00:00+00:00",
72
+ )
73
+
74
+
75
+ def _build_recovering_cli_runner(
76
+ monkeypatch: pytest.MonkeyPatch,
77
+ registration: support.AdvisoryRegistration,
78
+ github: support.FakeGitHub,
79
+ authentication_attempts: list[str],
80
+ child_calls: list[str],
81
+ ) -> support.AutomaticAdvisoryRunner:
82
+ def issue_repository_api(*arguments: object, **options: object) -> object:
83
+ authentication_attempts.append("attempt")
84
+ if len(authentication_attempts) == 1:
85
+ raise RuntimeError("offline")
86
+ return github
87
+
88
+ monkeypatch.setattr(cli, "_issue_repository_api", issue_repository_api)
89
+ runner = cli._build_runner(support._build_settings(registration))
90
+ counted_runner = _build_counted_runner(registration, github, child_calls)
91
+ runner.child_runner = counted_runner.child_runner
92
+ runner.publisher = support._publish_passed
93
+ return runner
94
+
95
+
96
+ def _run_report_only_child(
97
+ registration: support.AdvisoryRegistration,
98
+ ) -> support.ChildOutcome:
99
+ registration.report_path.parent.mkdir(parents=True, exist_ok=True)
100
+ registration.report_path.write_text(
101
+ json.dumps({"exit_code": 0}),
102
+ encoding="utf-8",
103
+ )
104
+ return support.ChildOutcome(0, "", "", False)
105
+
106
+
107
+ def _record_unexpected_publication(
108
+ publisher_calls: list[str],
109
+ *all_arguments: object,
110
+ ) -> support.Publication:
111
+ publisher_calls.append("publish")
112
+ return support.FakePublication("passed")
113
+
114
+
115
+ def test_publisher_adapter_returns_github_status_text(
116
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
117
+ ) -> None:
118
+ class PublicationAdapter:
119
+ status = StatusState.SUCCESS
120
+ description = "passed"
121
+
122
+ class PublisherModule:
123
+ publish_local_report = staticmethod(lambda *arguments: PublicationAdapter())
124
+
125
+ monkeypatch.setattr(publisher, "import_module", lambda name: PublisherModule)
126
+ registration = support._build_registration(tmp_path, tmp_path)
127
+ publication = publisher._publish_report(
128
+ object(),
129
+ registration.repository,
130
+ registration.pull_request_number,
131
+ registration.checkout_path,
132
+ registration.manifest_absolute_path,
133
+ registration.report_path,
134
+ )
135
+
136
+ assert publication.status == "success"
137
+
138
+
139
+ def test_real_publisher_adapter_runs_once_for_unchanged_identity(
140
+ monkeypatch: pytest.MonkeyPatch,
141
+ tmp_path: Path,
142
+ ) -> None:
143
+ all_published_manifest_paths: list[Path] = []
144
+ publisher_module = _publisher_module_recording_paths(all_published_manifest_paths)
145
+ monkeypatch.setattr(publisher, "import_module", lambda name: publisher_module)
146
+ checkout_path, head_sha = support._build_checkout(tmp_path)
147
+ registration = support._build_registration(tmp_path, checkout_path)
148
+ github = support.FakeGitHub(support._build_candidate(head_sha))
149
+ child_calls: list[str] = []
150
+ runner = _build_counted_runner(registration, github, child_calls)
151
+ first_state = runner.run_once()[0]
152
+ second_state = runner.run_once()[0]
153
+
154
+ assert first_state.status == "passed"
155
+ assert second_state.status == "passed"
156
+ assert child_calls == ["child"]
157
+ assert all_published_manifest_paths == [registration.selected_manifest_path]
158
+
159
+
160
+ def test_build_runner_run_once_persists_offline_then_recovers(
161
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
162
+ ) -> None:
163
+ checkout_path, head_sha = support._build_checkout(tmp_path)
164
+ registration = support._build_registration(tmp_path, checkout_path)
165
+ github = support.FakeGitHub(support._build_candidate(head_sha))
166
+ authentication_attempts: list[str] = []
167
+ child_calls: list[str] = []
168
+ runner = _build_recovering_cli_runner(
169
+ monkeypatch,
170
+ registration,
171
+ github,
172
+ authentication_attempts,
173
+ child_calls,
174
+ )
175
+
176
+ assert authentication_attempts == []
177
+ offline_state = runner.run_once()[0]
178
+ persisted_offline_state = json.loads(
179
+ registration.state_path.read_text(encoding="utf-8")
180
+ )
181
+ passed_state = runner.run_once()[0]
182
+
183
+ assert offline_state.status == "offline"
184
+ assert persisted_offline_state["status"] == "offline"
185
+ assert passed_state.status == "passed"
186
+ assert authentication_attempts == ["attempt", "attempt", "attempt"]
187
+ assert child_calls == ["child"]
188
+
189
+
190
+ def test_runner_marks_missing_selected_manifest_incomplete(tmp_path: Path) -> None:
191
+ checkout_path, head_sha = support._build_checkout(tmp_path)
192
+ registration = support._build_registration(tmp_path, checkout_path)
193
+ github = support.FakeGitHub(support._build_candidate(head_sha))
194
+ publisher_calls: list[str] = []
195
+
196
+ runner = support.AutomaticAdvisoryRunner(
197
+ support._build_settings(registration),
198
+ github,
199
+ child_runner=lambda child_registration, _base_ref, _timeout_seconds: (
200
+ _run_report_only_child(child_registration)
201
+ ),
202
+ publisher=lambda *all_arguments: _record_unexpected_publication(
203
+ publisher_calls,
204
+ *all_arguments,
205
+ ),
206
+ clock=lambda: "2026-09-05T00:00:00+00:00",
207
+ )
208
+
209
+ state = runner.run_once()[0]
210
+
211
+ assert state.status == "error"
212
+ assert state.reason == "verification child did not produce a report"
213
+ assert publisher_calls == []
214
+
215
+
216
+ def test_fetch_base_returns_false_after_git_timeout(
217
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
218
+ ) -> None:
219
+ registration = support._build_registration(tmp_path, tmp_path)
220
+ candidate = support._build_candidate("head")
221
+ captured_options: dict[str, object] = {}
222
+
223
+ def timeout_run(*arguments: object, **options: object) -> object:
224
+ captured_options.update(options)
225
+ raise subprocess.TimeoutExpired("git", 30.0)
226
+
227
+ monkeypatch.setattr(git.subprocess, "run", timeout_run)
228
+
229
+ assert git._fetch_base(registration, candidate) is False
230
+ assert captured_options["timeout"] == 30.0
231
+ captured_environment = captured_options["env"]
232
+ assert isinstance(captured_environment, dict)
233
+ assert captured_environment.get("GIT_TERMINAL_PROMPT") == "0"
234
+
235
+
236
+ def test_fetch_base_updates_dedicated_and_remote_tracking_refs(
237
+ monkeypatch: pytest.MonkeyPatch,
238
+ tmp_path: Path,
239
+ ) -> None:
240
+ registration = support._build_registration(tmp_path, tmp_path)
241
+ candidate = support._build_candidate("head")
242
+ all_fetch_arguments: list[tuple[str, ...]] = []
243
+
244
+ def record_fetch(
245
+ fetch_arguments: tuple[str, ...],
246
+ **options: object,
247
+ ) -> subprocess.CompletedProcess[bytes]:
248
+ all_fetch_arguments.append(fetch_arguments)
249
+ return subprocess.CompletedProcess(fetch_arguments, 0, b"", b"")
250
+
251
+ monkeypatch.setattr(git.subprocess, "run", record_fetch)
252
+
253
+ assert git._fetch_base(registration, candidate) is True
254
+ assert all_fetch_arguments == [
255
+ (
256
+ "git",
257
+ "fetch",
258
+ "--force",
259
+ "--no-tags",
260
+ "origin",
261
+ "+refs/heads/main:refs/cde/advisory-base/2985",
262
+ "+refs/heads/main:refs/remotes/origin/main",
263
+ )
264
+ ]
265
+
266
+
267
+ def _build_descendant_parent_script(
268
+ descendant_identifier_path: Path,
269
+ parent_action: str,
270
+ ) -> str:
271
+ descendant_script = "import time; time.sleep(30)"
272
+ return (
273
+ "import subprocess,sys,time\n"
274
+ "from pathlib import Path\n"
275
+ f"child_process = subprocess.Popen((sys.executable, '-c', {descendant_script!r}))\n"
276
+ f"Path({str(descendant_identifier_path)!r}).write_text(str(child_process.pid), encoding='utf-8')\n"
277
+ f"{parent_action}\n"
278
+ )
279
+
280
+
281
+ def _build_process_runner_script(
282
+ checkout_path: Path,
283
+ descendant_identifier_path: Path,
284
+ parent_action: str,
285
+ ) -> str:
286
+ parent_script = _build_descendant_parent_script(
287
+ descendant_identifier_path,
288
+ parent_action,
289
+ )
290
+ return (
291
+ "import os,sys\n"
292
+ "from pathlib import Path\n"
293
+ "from types import SimpleNamespace\n"
294
+ "from automatic_advisory import execution\n"
295
+ f"registration = SimpleNamespace(checkout_path=Path({str(checkout_path)!r}))\n"
296
+ "scripts_directory = Path(execution.__file__).resolve().parents[1]\n"
297
+ "child_environment = execution._build_child_environment(registration, scripts_directory)\n"
298
+ "child_process = execution._start_child_process(\n"
299
+ f" (sys.executable, '-c', {parent_script!r}), registration, child_environment\n"
300
+ ")\n"
301
+ "if child_process is None:\n"
302
+ " raise SystemExit(2)\n"
303
+ f"child_outcome = execution._collect_child_outcome(child_process, {PRODUCTION_CHILD_TIMEOUT_SECONDS!r})\n"
304
+ "if not child_outcome.timed_out:\n"
305
+ " raise SystemExit(3)\n"
306
+ )
307
+
308
+
309
+ def _is_process_running(process_identifier: int) -> bool:
310
+ if sys.platform == "win32":
311
+ completed_process = subprocess.run(
312
+ ("tasklist", "/FI", f"PID eq {process_identifier}", "/FO", "CSV", "/NH"),
313
+ capture_output=True,
314
+ check=False,
315
+ text=True,
316
+ timeout=PROCESS_EXIT_WAIT_SECONDS,
317
+ )
318
+ return f'"{process_identifier}"' in completed_process.stdout
319
+ try:
320
+ os.kill(process_identifier, 0)
321
+ except ProcessLookupError:
322
+ return False
323
+ except PermissionError:
324
+ return True
325
+ return True
326
+
327
+
328
+ def _wait_for_process_exit(process_identifier: int) -> bool:
329
+ deadline = time.monotonic() + PROCESS_EXIT_WAIT_SECONDS
330
+ while time.monotonic() < deadline:
331
+ if not _is_process_running(process_identifier):
332
+ return True
333
+ time.sleep(PROCESS_POLL_SECONDS)
334
+ return not _is_process_running(process_identifier)
335
+
336
+
337
+ def _end_process_for_test(process_identifier: int) -> None:
338
+ if not _is_process_running(process_identifier):
339
+ return
340
+ if sys.platform == "win32":
341
+ subprocess.run(
342
+ ("taskkill", "/T", "/F", "/PID", str(process_identifier)),
343
+ capture_output=True,
344
+ check=False,
345
+ timeout=PROCESS_EXIT_WAIT_SECONDS,
346
+ )
347
+ return
348
+ os.kill(process_identifier, signal.SIGKILL)
349
+
350
+
351
+ @pytest.mark.parametrize(
352
+ "parent_action",
353
+ ("time.sleep(30)", "raise SystemExit(0)"),
354
+ ids=("timed-parent", "exited-parent"),
355
+ )
356
+ def test_production_launch_timeout_ends_descendant_and_keeps_runner_alive(
357
+ tmp_path: Path,
358
+ parent_action: str,
359
+ ) -> None:
360
+ descendant_identifier_path = tmp_path / "descendant.pid"
361
+ runner_script = _build_process_runner_script(
362
+ tmp_path,
363
+ descendant_identifier_path,
364
+ parent_action,
365
+ )
366
+ completed_runner = subprocess.run(
367
+ (sys.executable, "-c", runner_script),
368
+ cwd=support.SCRIPTS_DIRECTORY,
369
+ capture_output=True,
370
+ check=False,
371
+ text=True,
372
+ timeout=PROCESS_RUNNER_TIMEOUT_SECONDS,
373
+ )
374
+ assert descendant_identifier_path.is_file(), completed_runner.stderr
375
+ descendant_identifier = int(descendant_identifier_path.read_text(encoding="utf-8"))
376
+
377
+ try:
378
+ assert completed_runner.returncode == 0, completed_runner.stderr
379
+ assert _wait_for_process_exit(descendant_identifier)
380
+ finally:
381
+ _end_process_for_test(descendant_identifier)
382
+
383
+
384
+ def test_detached_start_resolves_relative_settings_before_spawn(
385
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
386
+ ) -> None:
387
+ all_launches: list[tuple[tuple[str, ...], dict[str, object]]] = []
388
+ monkeypatch.chdir(tmp_path)
389
+ monkeypatch.setattr(
390
+ cli.subprocess,
391
+ "Popen",
392
+ lambda arguments, **options: all_launches.append((tuple(arguments), options)),
393
+ )
394
+
395
+ assert cli.start_polling(Path("settings.json")) == 0
396
+
397
+ launch_arguments = all_launches[0][0]
398
+ assert launch_arguments[launch_arguments.index("--settings") + 1] == str(
399
+ tmp_path / "settings.json"
400
+ )
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ TEST_DIRECTORY = Path(__file__).resolve().parent
10
+ if str(TEST_DIRECTORY) not in sys.path:
11
+ sys.path.insert(0, str(TEST_DIRECTORY))
12
+
13
+ import test_automatic_advisory as support
14
+ from automatic_advisory import configuration as advisory_configuration
15
+
16
+
17
+ @pytest.mark.parametrize("field_name", ["poll_seconds", "child_timeout_seconds"])
18
+ @pytest.mark.parametrize("invalid_seconds", [float("nan"), float("inf"), float("-inf")])
19
+ def test_configuration_rejects_nonfinite_timing(
20
+ tmp_path: Path, field_name: str, invalid_seconds: float
21
+ ) -> None:
22
+ settings_path = tmp_path / "settings.json"
23
+ all_settings = support._valid_advisory_settings_fields(tmp_path)
24
+ settings_path.write_text(json.dumps(all_settings), encoding="utf-8")
25
+ valid_settings = advisory_configuration.load_advisory_settings(settings_path)
26
+ assert valid_settings.poll_seconds == 0.25
27
+ assert valid_settings.child_timeout_seconds == 45.5
28
+ all_settings[field_name] = invalid_seconds
29
+ settings_path.write_text(json.dumps(all_settings), encoding="utf-8")
30
+
31
+ with pytest.raises(
32
+ advisory_configuration.AdvisoryConfigurationError,
33
+ match=f"{field_name} must be positive",
34
+ ):
35
+ advisory_configuration.load_advisory_settings(settings_path)
36
+
37
+
38
+ def test_poll_error_log_sits_beside_the_registration_state_file(tmp_path: Path) -> None:
39
+ checkout_path = tmp_path / "checkout"
40
+ checkout_path.mkdir()
41
+ registration = support._build_registration(tmp_path, checkout_path)
42
+ settings = support._build_settings(registration)
43
+
44
+ assert (
45
+ settings.poll_error_log_path
46
+ == registration.state_path.parent / "poll-errors.log"
47
+ )
48
+ assert settings.poll_error_log_path.parent == settings.poll_lock_root.parent
@@ -0,0 +1,163 @@
1
+ """The advisory poller and every process it starts stay off the screen on Windows."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import subprocess
7
+ import sys
8
+ import time
9
+ from pathlib import Path
10
+
11
+ import pytest
12
+
13
+ TESTS_DIRECTORY = Path(__file__).resolve().parent
14
+ if str(TESTS_DIRECTORY) not in sys.path:
15
+ sys.path.insert(0, str(TESTS_DIRECTORY))
16
+
17
+ import test_automatic_advisory as support
18
+ from automatic_advisory import checkout, cli, execution, git, window_flags
19
+
20
+ WINDOWS_ONLY = pytest.mark.skipif(
21
+ sys.platform != "win32", reason="Windows console flags"
22
+ )
23
+ PROBE_WAIT_SECONDS = 10.0
24
+ PROBE_POLL_SECONDS = 0.1
25
+
26
+ CHILD_REPORT_SCRIPT = (
27
+ "import ctypes, json, sys;"
28
+ "handle = ctypes.windll.kernel32.GetConsoleWindow();"
29
+ "visible = bool(ctypes.windll.user32.IsWindowVisible(handle)) if handle else False;"
30
+ "sys.stdout.write(json.dumps({'visible': visible}))"
31
+ )
32
+
33
+ PARENT_PROBE_SCRIPT = """
34
+ import json, subprocess, sys
35
+ from pathlib import Path
36
+ completed = subprocess.run(
37
+ [sys.executable, "-c", {child_script!r}], capture_output=True, text=True, check=False
38
+ )
39
+ Path(sys.argv[1]).write_text(completed.stdout, encoding="utf-8")
40
+ """
41
+
42
+
43
+ def test_flags_are_zero_off_windows(monkeypatch: pytest.MonkeyPatch) -> None:
44
+ monkeypatch.setattr(window_flags.sys, "platform", "linux")
45
+ assert window_flags.hidden_window_creation_flags() == 0
46
+ assert window_flags.detached_poller_creation_flags() == 0
47
+
48
+
49
+ @WINDOWS_ONLY
50
+ def test_hidden_window_creation_flags_is_create_no_window() -> None:
51
+ assert window_flags.hidden_window_creation_flags() == subprocess.CREATE_NO_WINDOW
52
+
53
+
54
+ @WINDOWS_ONLY
55
+ def test_poller_flags_keep_a_hidden_console_instead_of_none() -> None:
56
+ poller_flags = window_flags.detached_poller_creation_flags()
57
+ assert poller_flags & subprocess.DETACHED_PROCESS == 0
58
+ assert poller_flags & subprocess.CREATE_NO_WINDOW
59
+ assert poller_flags & subprocess.CREATE_NEW_PROCESS_GROUP
60
+
61
+
62
+ @WINDOWS_ONLY
63
+ def test_poller_grandchild_gets_no_visible_console(tmp_path: Path) -> None:
64
+ parent_script_path = tmp_path / "parent_probe.py"
65
+ parent_script_path.write_text(
66
+ PARENT_PROBE_SCRIPT.format(child_script=CHILD_REPORT_SCRIPT), encoding="utf-8"
67
+ )
68
+ report_path = tmp_path / "grandchild.json"
69
+ subprocess.Popen(
70
+ [sys.executable, str(parent_script_path), str(report_path)],
71
+ stdin=subprocess.DEVNULL,
72
+ stdout=subprocess.DEVNULL,
73
+ stderr=subprocess.DEVNULL,
74
+ creationflags=window_flags.detached_poller_creation_flags(),
75
+ )
76
+ assert _read_probe_report(report_path) == {"visible": False}
77
+
78
+
79
+ def _read_probe_report(report_path: Path) -> dict[str, bool]:
80
+ deadline = time.monotonic() + PROBE_WAIT_SECONDS
81
+ while time.monotonic() < deadline:
82
+ if report_path.exists():
83
+ return json.loads(report_path.read_text(encoding="utf-8"))
84
+ time.sleep(PROBE_POLL_SECONDS)
85
+ raise AssertionError("the probe parent never wrote its report")
86
+
87
+
88
+ def test_git_fetch_hides_its_window(
89
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
90
+ ) -> None:
91
+ registration = support._build_registration(tmp_path, tmp_path)
92
+ candidate = support._build_candidate("head")
93
+ captured_options: dict[str, object] = {}
94
+
95
+ def record_run(
96
+ *arguments: object, **options: object
97
+ ) -> subprocess.CompletedProcess[bytes]:
98
+ captured_options.update(options)
99
+ return subprocess.CompletedProcess(arguments, 0, b"", b"")
100
+
101
+ monkeypatch.setattr(git.subprocess, "run", record_run)
102
+
103
+ assert git._fetch_base(registration, candidate) is True
104
+ assert (
105
+ captured_options["creationflags"] == window_flags.hidden_window_creation_flags()
106
+ )
107
+
108
+
109
+ def test_checkout_inspection_hides_its_window(
110
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
111
+ ) -> None:
112
+ all_captured_flags: list[object] = []
113
+
114
+ def record_run(
115
+ *arguments: object, **options: object
116
+ ) -> subprocess.CompletedProcess[str]:
117
+ all_captured_flags.append(options["creationflags"])
118
+ return subprocess.CompletedProcess(arguments, 0, "abc123\n", "")
119
+
120
+ monkeypatch.setattr(checkout.subprocess, "run", record_run)
121
+
122
+ local_checkout = checkout.read_local_checkout(tmp_path)
123
+
124
+ assert local_checkout.head_sha == "abc123"
125
+ assert all_captured_flags == [window_flags.hidden_window_creation_flags()] * 2
126
+
127
+
128
+ def test_windows_process_host_spawn_hides_its_window(
129
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
130
+ ) -> None:
131
+ registration = support._build_registration(tmp_path, tmp_path)
132
+ captured_options: dict[str, object] = {}
133
+
134
+ def record_popen(*arguments: object, **options: object) -> object:
135
+ captured_options.update(options)
136
+ return object()
137
+
138
+ monkeypatch.setattr(execution.subprocess, "Popen", record_popen)
139
+
140
+ execution._spawn_windows_process_host(("python", "-c", "pass"), registration, {})
141
+
142
+ expected_flags = window_flags.hidden_window_creation_flags() | getattr(
143
+ subprocess, "BELOW_NORMAL_PRIORITY_CLASS", 0
144
+ )
145
+ assert captured_options["creationflags"] == expected_flags
146
+
147
+
148
+ def test_start_polling_launches_with_the_poller_flags(
149
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
150
+ ) -> None:
151
+ captured_options: dict[str, object] = {}
152
+
153
+ def record_popen(*arguments: object, **options: object) -> object:
154
+ captured_options.update(options)
155
+ return object()
156
+
157
+ monkeypatch.setattr(cli.subprocess, "Popen", record_popen)
158
+
159
+ assert cli.start_polling(tmp_path / "settings.json") == 0
160
+ assert (
161
+ captured_options["creationflags"]
162
+ == window_flags.detached_poller_creation_flags()
163
+ )