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,392 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ import pytest
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 pr_verification.github import (
14
+ GitHubApi,
15
+ GitHubAppAuthenticator,
16
+ GitHubError,
17
+ HttpReply,
18
+ )
19
+ from pr_verification.model import RepositorySettings, StatusState
20
+
21
+
22
+ class RecordingRequester:
23
+ def __init__(self, all_replies: list[HttpReply]) -> None:
24
+ self.all_replies = all_replies
25
+ self.all_requests: list[tuple[str, str, dict[str, str], bytes | None]] = []
26
+
27
+ def __call__(
28
+ self,
29
+ method: str,
30
+ url: str,
31
+ headers: dict[str, str],
32
+ body: bytes | None,
33
+ ) -> HttpReply:
34
+ self.all_requests.append((method, url, headers, body))
35
+ return self.all_replies.pop(0)
36
+
37
+
38
+ def repository_settings() -> RepositorySettings:
39
+ return RepositorySettings(
40
+ slug="owner/repository",
41
+ clone_url="https://github.test/owner/repository.git",
42
+ )
43
+
44
+
45
+ def pull_payload(
46
+ head_sha: str = "head",
47
+ base_ref: str = "main",
48
+ ) -> dict[str, object]:
49
+ return {
50
+ "number": 7,
51
+ "draft": True,
52
+ "merge_commit_sha": "merge",
53
+ "base": {"ref": base_ref, "sha": "base"},
54
+ "head": {"sha": head_sha},
55
+ }
56
+
57
+
58
+ def branch_payload(branch_sha: str = "current-base") -> dict[str, object]:
59
+ return {
60
+ "ref": "refs/heads/main",
61
+ "object": {"sha": branch_sha, "type": "commit"},
62
+ }
63
+
64
+
65
+ def _issue_repository_api(
66
+ tmp_path: Path,
67
+ requester: RecordingRequester,
68
+ all_claims: list[dict[str, int | str]],
69
+ ) -> GitHubApi:
70
+ private_key_path = tmp_path / "app.pem"
71
+ private_key_path.write_text("private-key", encoding="utf-8")
72
+
73
+ def encode_jwt(
74
+ claims: dict[str, int | str], private_key: str, algorithm: str
75
+ ) -> str:
76
+ all_claims.append(claims)
77
+ assert private_key == "private-key"
78
+ assert algorithm == "RS256"
79
+ return "app-jwt"
80
+
81
+ authenticator = GitHubAppAuthenticator(
82
+ "https://api.github.test",
83
+ 42,
84
+ 84,
85
+ private_key_path,
86
+ requester=requester,
87
+ jwt_encoder=encode_jwt,
88
+ clock=lambda: 1000.0,
89
+ )
90
+ return authenticator.issue_repository_api(repository_settings())
91
+
92
+
93
+ def test_lists_draft_pull_requests() -> None:
94
+ requester = RecordingRequester(
95
+ [HttpReply(200, {}, json.dumps([pull_payload()]).encode("utf-8"))]
96
+ )
97
+ github = GitHubApi("https://api.github.test", "installation-token", requester)
98
+
99
+ all_candidates = github.list_open_candidates(repository_settings())
100
+
101
+ assert len(all_candidates) == 1
102
+ assert all_candidates[0].is_draft is True
103
+ assert "state=open" in requester.all_requests[0][1]
104
+
105
+
106
+ def test_reads_merge_commit_parents_in_order() -> None:
107
+ requester = RecordingRequester(
108
+ [
109
+ HttpReply(
110
+ 200,
111
+ {},
112
+ json.dumps(
113
+ {"sha": "merge", "parents": [{"sha": "base"}, {"sha": "head"}]}
114
+ ).encode("utf-8"),
115
+ )
116
+ ]
117
+ )
118
+ github = GitHubApi("https://api.github.test", "installation-token", requester)
119
+
120
+ all_parent_shas = github.get_commit_parents(repository_settings(), "merge")
121
+
122
+ assert all_parent_shas == ("base", "head")
123
+
124
+
125
+ def test_posts_status_to_recognized_merge_sha() -> None:
126
+ requester = RecordingRequester([HttpReply(201, {}, b"{}")])
127
+ github = GitHubApi("https://api.github.test", "installation-token", requester)
128
+
129
+ github.post_status(
130
+ repository_settings(),
131
+ "merge",
132
+ StatusState.SUCCESS,
133
+ "local/verify",
134
+ "All required checks passed",
135
+ )
136
+
137
+ method, url, headers, body = requester.all_requests[0]
138
+ assert method == "POST"
139
+ assert url.endswith("/repos/owner/repository/statuses/merge")
140
+ assert headers["Authorization"] == "Bearer installation-token"
141
+ assert json.loads(body or b"{}")["context"] == "local/verify"
142
+
143
+
144
+ def test_adds_and_removes_one_label_without_replacing_other_labels() -> None:
145
+ requester = RecordingRequester(
146
+ [
147
+ HttpReply(200, {}, b"[]"),
148
+ HttpReply(200, {}, json.dumps([{"name": "local-checks:passed"}]).encode()),
149
+ HttpReply(204, {}, b""),
150
+ ]
151
+ )
152
+ github = GitHubApi("https://api.github.test", "installation-token", requester)
153
+
154
+ github.add_label(repository_settings(), 7, "local-checks:passed")
155
+ github.remove_label(repository_settings(), 7, "local-checks:passed")
156
+
157
+ add_method, add_url, _, add_body = requester.all_requests[0]
158
+ read_method, read_url, _, read_body = requester.all_requests[1]
159
+ remove_method, remove_url, _, remove_body = requester.all_requests[2]
160
+ assert add_method == "POST"
161
+ assert add_url.endswith("/repos/owner/repository/issues/7/labels")
162
+ assert json.loads(add_body or b"{}") == {"labels": ["local-checks:passed"]}
163
+ assert read_method == "GET"
164
+ assert read_url.endswith(
165
+ "/repos/owner/repository/issues/7/labels?per_page=100&page=1"
166
+ )
167
+ assert read_body is None
168
+ assert remove_method == "DELETE"
169
+ assert remove_url.endswith(
170
+ "/repos/owner/repository/issues/7/labels/local-checks%3Apassed"
171
+ )
172
+ assert remove_body is None
173
+
174
+
175
+ def test_removes_label_when_github_returns_ok() -> None:
176
+ requester = RecordingRequester(
177
+ [
178
+ HttpReply(200, {}, json.dumps([{"name": "local-checks:passed"}]).encode()),
179
+ HttpReply(200, {}, b"{}"),
180
+ ]
181
+ )
182
+ github = GitHubApi("https://api.github.test", "installation-token", requester)
183
+
184
+ github.remove_label(repository_settings(), 7, "local-checks:passed")
185
+
186
+ assert requester.all_requests[1][0] == "DELETE"
187
+
188
+
189
+ def test_ignores_label_that_is_already_absent() -> None:
190
+ requester = RecordingRequester([HttpReply(200, {}, b"[]")])
191
+ github = GitHubApi("https://api.github.test", "installation-token", requester)
192
+
193
+ github.remove_label(repository_settings(), 7, "local-checks:passed")
194
+
195
+ assert len(requester.all_requests) == 1
196
+
197
+
198
+ def test_raises_when_label_delete_is_forbidden() -> None:
199
+ requester = RecordingRequester(
200
+ [
201
+ HttpReply(200, {}, json.dumps([{"name": "local-checks:passed"}]).encode()),
202
+ HttpReply(403, {}, b"{}"),
203
+ ]
204
+ )
205
+ github = GitHubApi("https://api.github.test", "installation-token", requester)
206
+
207
+ try:
208
+ github.remove_label(repository_settings(), 7, "local-checks:passed")
209
+ except GitHubError as error:
210
+ assert "403" in str(error)
211
+ else:
212
+ raise AssertionError("Expected forbidden label deletion to fail")
213
+
214
+
215
+ def test_app_token_is_scoped_to_one_repository_and_required_permissions(
216
+ tmp_path: Path,
217
+ ) -> None:
218
+ requester = RecordingRequester(
219
+ [
220
+ HttpReply(
221
+ 201, {}, json.dumps({"token": "installation-token"}).encode("utf-8")
222
+ )
223
+ ]
224
+ )
225
+ all_claims: list[dict[str, int | str]] = []
226
+ github = _issue_repository_api(tmp_path, requester, all_claims)
227
+
228
+ request_body = json.loads(requester.all_requests[0][3] or b"{}")
229
+ assert github.installation_token == "installation-token"
230
+ assert request_body == {
231
+ "repositories": ["repository"],
232
+ "permissions": {
233
+ "contents": "read",
234
+ "pull_requests": "read",
235
+ "statuses": "write",
236
+ },
237
+ }
238
+ assert all_claims == [{"iss": "42", "iat": 940, "exp": 1540}]
239
+
240
+
241
+ def test_app_token_opt_in_adds_issue_write_permission(tmp_path: Path) -> None:
242
+ private_key_path = tmp_path / "app.pem"
243
+ private_key_path.write_text("private-key", encoding="utf-8")
244
+ requester = RecordingRequester(
245
+ [HttpReply(201, {}, json.dumps({"token": "token"}).encode("utf-8"))]
246
+ )
247
+ authenticator = GitHubAppAuthenticator(
248
+ "https://api.github.test",
249
+ 42,
250
+ 84,
251
+ private_key_path,
252
+ requester=requester,
253
+ jwt_encoder=lambda claims, private_key, algorithm: "app-jwt",
254
+ )
255
+
256
+ authenticator.issue_repository_api(
257
+ repository_settings(), should_write_issue_labels=True
258
+ )
259
+
260
+ request_body = json.loads(requester.all_requests[0][3] or b"{}")
261
+ assert request_body == {
262
+ "repositories": ["repository"],
263
+ "permissions": {
264
+ "contents": "read",
265
+ "pull_requests": "write",
266
+ "statuses": "write",
267
+ "issues": "write",
268
+ },
269
+ }
270
+
271
+
272
+ def test_ignores_pull_until_github_recognizes_merge_sha() -> None:
273
+ unrecognized_pull = pull_payload()
274
+ unrecognized_pull["merge_commit_sha"] = None
275
+ requester = RecordingRequester(
276
+ [HttpReply(200, {}, json.dumps([unrecognized_pull]).encode("utf-8"))]
277
+ )
278
+ github = GitHubApi("https://api.github.test", "installation-token", requester)
279
+
280
+ all_candidates = github.list_open_candidates(repository_settings())
281
+
282
+ assert all_candidates == ()
283
+
284
+
285
+ def test_lists_pull_without_merge_sha_for_advisory_reads() -> None:
286
+ unrecognized_pull = pull_payload()
287
+ unrecognized_pull["merge_commit_sha"] = None
288
+ requester = RecordingRequester(
289
+ [
290
+ HttpReply(200, {}, json.dumps([unrecognized_pull]).encode("utf-8")),
291
+ HttpReply(200, {}, json.dumps(branch_payload()).encode("utf-8")),
292
+ ]
293
+ )
294
+ github = GitHubApi("https://api.github.test", "installation-token", requester)
295
+
296
+ all_candidates = github.list_open_candidates(
297
+ repository_settings(), should_require_merge_commit=False
298
+ )
299
+
300
+ assert len(all_candidates) == 1
301
+ assert all_candidates[0].merge_sha == ""
302
+ assert all_candidates[0].base_sha == "current-base"
303
+
304
+
305
+ def test_get_candidate_uses_current_branch_sha_for_advisory_reads() -> None:
306
+ requester = RecordingRequester(
307
+ [
308
+ HttpReply(
309
+ 200,
310
+ {},
311
+ json.dumps(pull_payload(base_ref="release/#7")).encode("utf-8"),
312
+ ),
313
+ HttpReply(
314
+ 200,
315
+ {},
316
+ json.dumps(branch_payload("new-base")).encode("utf-8"),
317
+ ),
318
+ ]
319
+ )
320
+ github = GitHubApi("https://api.github.test", "installation-token", requester)
321
+
322
+ candidate = github.get_candidate(
323
+ repository_settings(),
324
+ 7,
325
+ should_require_merge_commit=False,
326
+ )
327
+
328
+ assert candidate.base_sha == "new-base"
329
+ assert requester.all_requests[1][1].endswith(
330
+ "/repos/owner/repository/git/ref/heads/release/%237"
331
+ )
332
+
333
+
334
+ def test_list_resolves_each_unique_base_ref_once() -> None:
335
+ second_pull = pull_payload("second-head")
336
+ second_pull["number"] = 8
337
+ requester = RecordingRequester(
338
+ [
339
+ HttpReply(
340
+ 200,
341
+ {},
342
+ json.dumps([pull_payload(), second_pull]).encode("utf-8"),
343
+ ),
344
+ HttpReply(200, {}, json.dumps(branch_payload()).encode("utf-8")),
345
+ ]
346
+ )
347
+ github = GitHubApi("https://api.github.test", "installation-token", requester)
348
+
349
+ all_candidates = github.list_open_candidates(
350
+ repository_settings(),
351
+ should_require_merge_commit=False,
352
+ )
353
+ all_branch_requests = [
354
+ each_request
355
+ for each_request in requester.all_requests
356
+ if "/git/ref/heads/" in each_request[1]
357
+ ]
358
+
359
+ assert [each_candidate.base_sha for each_candidate in all_candidates] == [
360
+ "current-base",
361
+ "current-base",
362
+ ]
363
+ assert len(all_branch_requests) == 1
364
+
365
+
366
+ def test_advisory_candidate_propagates_branch_read_failure() -> None:
367
+ requester = RecordingRequester(
368
+ [
369
+ HttpReply(200, {}, json.dumps(pull_payload()).encode("utf-8")),
370
+ HttpReply(500, {}, b"{}"),
371
+ ]
372
+ )
373
+ github = GitHubApi("https://api.github.test", "installation-token", requester)
374
+
375
+ with pytest.raises(GitHubError, match="500"):
376
+ github.get_candidate(
377
+ repository_settings(),
378
+ 7,
379
+ should_require_merge_commit=False,
380
+ )
381
+
382
+
383
+ def test_strict_candidate_keeps_pull_base_without_branch_read() -> None:
384
+ requester = RecordingRequester(
385
+ [HttpReply(200, {}, json.dumps(pull_payload()).encode("utf-8"))]
386
+ )
387
+ github = GitHubApi("https://api.github.test", "installation-token", requester)
388
+
389
+ candidate = github.get_candidate(repository_settings(), 7)
390
+
391
+ assert candidate.base_sha == "base"
392
+ assert len(requester.all_requests) == 1
@@ -0,0 +1,168 @@
1
+ from __future__ import annotations
2
+
3
+ import subprocess
4
+ import sys
5
+ import time
6
+ from pathlib import Path
7
+ from typing import cast
8
+
9
+ import pytest
10
+
11
+ SCRIPTS_DIRECTORY = Path(__file__).resolve().parents[1]
12
+ if str(SCRIPTS_DIRECTORY) not in sys.path:
13
+ sys.path.insert(0, str(SCRIPTS_DIRECTORY))
14
+
15
+ from pr_verification import lock as lock_module
16
+ from pr_verification.lock import SupervisorLock, SupervisorLockError
17
+
18
+ LOCK_CONTENDER_SCRIPT = """
19
+ import sys
20
+ import time
21
+ from pathlib import Path
22
+
23
+ scripts_directory = Path(sys.argv[1])
24
+ cache_root = Path(sys.argv[2])
25
+ result_path = Path(sys.argv[3])
26
+ start_path = Path(sys.argv[4])
27
+ sys.path.insert(0, str(scripts_directory))
28
+
29
+ from pr_verification.lock import SupervisorLock, SupervisorLockError
30
+
31
+ while not start_path.exists():
32
+ time.sleep(0.01)
33
+
34
+ try:
35
+ with SupervisorLock(cache_root):
36
+ result_path.write_text("acquired", encoding="utf-8")
37
+ while True:
38
+ time.sleep(1)
39
+ except SupervisorLockError:
40
+ result_path.write_text("contended", encoding="utf-8")
41
+ """
42
+ RESULT_WAIT_SECONDS = 5
43
+ PROCESS_WAIT_SECONDS = 10
44
+ POLL_INTERVAL_SECONDS = 0.01
45
+ LEGACY_LOCK_CONTENT = "legacy owner metadata"
46
+ BASE_PYTHON_EXECUTABLE = cast(str, getattr(sys, "_base_executable", sys.executable))
47
+
48
+
49
+ def _start_lock_contender(
50
+ cache_root: Path, result_path: Path, start_path: Path
51
+ ) -> subprocess.Popen[str]:
52
+ return subprocess.Popen(
53
+ [
54
+ BASE_PYTHON_EXECUTABLE,
55
+ "-c",
56
+ LOCK_CONTENDER_SCRIPT,
57
+ str(SCRIPTS_DIRECTORY),
58
+ str(cache_root),
59
+ str(result_path),
60
+ str(start_path),
61
+ ],
62
+ stdout=subprocess.PIPE,
63
+ stderr=subprocess.PIPE,
64
+ text=True,
65
+ )
66
+
67
+
68
+ def _wait_for_lock_outcomes(all_result_paths: list[Path]) -> list[str]:
69
+ deadline = time.monotonic() + RESULT_WAIT_SECONDS
70
+ while time.monotonic() < deadline:
71
+ if all(each_path.is_file() for each_path in all_result_paths):
72
+ return [
73
+ each_path.read_text(encoding="utf-8") for each_path in all_result_paths
74
+ ]
75
+ time.sleep(POLL_INTERVAL_SECONDS)
76
+ raise AssertionError("lock contenders did not report within five seconds")
77
+
78
+
79
+ def _terminate_owned_child(child_process: subprocess.Popen[str]) -> None:
80
+ if child_process.poll() is None:
81
+ child_process.terminate()
82
+ child_process.wait(timeout=PROCESS_WAIT_SECONDS)
83
+
84
+
85
+ def _start_simultaneous_lock_contenders(
86
+ cache_root: Path, all_result_paths: list[Path], start_path: Path
87
+ ) -> list[subprocess.Popen[str]]:
88
+ all_child_processes = [
89
+ _start_lock_contender(cache_root, each_result_path, start_path)
90
+ for each_result_path in all_result_paths
91
+ ]
92
+ start_path.touch()
93
+ return all_child_processes
94
+
95
+
96
+ def _stop_initial_lock_processes(
97
+ all_child_processes: list[subprocess.Popen[str]], all_outcomes: list[str]
98
+ ) -> None:
99
+ contender_index = all_outcomes.index("contended")
100
+ all_child_processes[contender_index].wait(timeout=PROCESS_WAIT_SECONDS)
101
+ owner_index = all_outcomes.index("acquired")
102
+ _terminate_owned_child(all_child_processes[owner_index])
103
+
104
+
105
+ def test_second_supervisor_cannot_own_same_cache_root(tmp_path: Path) -> None:
106
+ first_lock = SupervisorLock(tmp_path)
107
+ second_lock = SupervisorLock(tmp_path)
108
+
109
+ with first_lock, pytest.raises(SupervisorLockError):
110
+ second_lock.acquire()
111
+
112
+
113
+ def test_process_lock_survives_contention_and_owner_termination(
114
+ tmp_path: Path,
115
+ ) -> None:
116
+ lock_path = tmp_path / "supervisor.lock"
117
+ lock_path.write_text(LEGACY_LOCK_CONTENT, encoding="utf-8")
118
+ start_path = tmp_path / "start"
119
+ all_result_paths = [tmp_path / "first.result", tmp_path / "second.result"]
120
+ all_child_processes = _start_simultaneous_lock_contenders(
121
+ tmp_path, all_result_paths, start_path
122
+ )
123
+ try:
124
+ all_outcomes = _wait_for_lock_outcomes(all_result_paths)
125
+ assert sorted(all_outcomes) == ["acquired", "contended"]
126
+ _stop_initial_lock_processes(all_child_processes, all_outcomes)
127
+
128
+ recovery_result_path = tmp_path / "recovery.result"
129
+ recovery_process = _start_lock_contender(
130
+ tmp_path, recovery_result_path, start_path
131
+ )
132
+ all_child_processes.append(recovery_process)
133
+ assert _wait_for_lock_outcomes([recovery_result_path]) == ["acquired"]
134
+ assert lock_path.is_file()
135
+ _terminate_owned_child(recovery_process)
136
+ assert lock_path.read_text(encoding="utf-8") == LEGACY_LOCK_CONTENT
137
+ finally:
138
+ for each_child_process in all_child_processes:
139
+ _terminate_owned_child(each_child_process)
140
+
141
+
142
+ def test_lock_io_error_is_presented_as_supervisor_lock_error(
143
+ tmp_path: Path,
144
+ ) -> None:
145
+ (tmp_path / "supervisor.lock").mkdir()
146
+
147
+ with pytest.raises(
148
+ SupervisorLockError,
149
+ match="Verification supervisor lock file is unavailable",
150
+ ) as raised_error:
151
+ SupervisorLock(tmp_path).acquire()
152
+
153
+ assert isinstance(raised_error.value.__cause__, OSError)
154
+
155
+
156
+ def test_contention_error_survives_a_failing_descriptor_close(
157
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
158
+ ) -> None:
159
+ def close_fails(file_descriptor: int) -> None:
160
+ raise OSError("close failed")
161
+
162
+ first_lock = SupervisorLock(tmp_path)
163
+ with first_lock:
164
+ monkeypatch.setattr(lock_module.os, "close", close_fails)
165
+ with pytest.raises(SupervisorLockError) as raised:
166
+ SupervisorLock(tmp_path).acquire()
167
+ monkeypatch.undo()
168
+ assert str(raised.value) == lock_module.SUPERVISOR_LOCK_ERROR
@@ -96,7 +96,6 @@ def _comment_policy_surfaces(package_root: Path) -> tuple[Path, ...]:
96
96
  package_root / "system-prompts" / "software-engineer.xml",
97
97
  package_root / ".agents" / "agents" / "clean-coder.md",
98
98
  package_root / ".agents" / "agents" / "code-quality-agent.md",
99
- package_root / "_shared" / "pr-loop" / "code-rules-gate.md",
100
99
  package_root / "audit-rubrics" / "category_rubrics" / "category-j-code-rules-compliance.md",
101
100
  package_root / "audit-rubrics" / "prompts" / "category-j-code-rules-compliance.md",
102
101
  package_root / "audit-rubrics" / "category_rubrics" / "category-l-behavior-equivalence.md",
@@ -160,7 +159,7 @@ def _category_l_policy_texts() -> tuple[str, str, str]:
160
159
  )
161
160
 
162
161
 
163
- def _comment_policy_summary_texts() -> tuple[str, str, str, str]:
162
+ def _comment_policy_summary_texts() -> tuple[str, str, str]:
164
163
  clean_coder = _PACKAGE_ROOT / ".agents" / "agents" / "clean-coder.md"
165
164
  category_j = (
166
165
  _PACKAGE_ROOT
@@ -169,24 +168,21 @@ def _comment_policy_summary_texts() -> tuple[str, str, str, str]:
169
168
  / "category-j-code-rules-compliance.md"
170
169
  )
171
170
  code_quality = _PACKAGE_ROOT / ".agents" / "agents" / "code-quality-agent.md"
172
- gate = _PACKAGE_ROOT / "_shared" / "pr-loop" / "code-rules-gate.md"
173
171
  return tuple(
174
172
  each_path.read_text(encoding="utf-8").lower()
175
- for each_path in (clean_coder, category_j, code_quality, gate)
173
+ for each_path in (clean_coder, category_j, code_quality)
176
174
  )
177
175
 
178
176
 
179
177
  def test_comment_policy_uses_changed_comment_names_and_type_directives() -> None:
180
178
  category_l_rubric, category_l_prompt, audit_categories = _category_l_policy_texts()
181
- clean_coder, category_j, code_quality, gate = _comment_policy_summary_texts()
179
+ clean_coder, category_j, code_quality = _comment_policy_summary_texts()
182
180
  assert "| l7 | changed-comment handling" in category_l_rubric
183
181
  assert "**l7. changed-comment handling**" in category_l_prompt
184
182
  assert '"axis_name": "changed-comment handling"' in audit_categories
185
183
  assert "no type-ignore directives" in clean_coder
186
184
  assert "no `# type: ignore` directives" in category_j
187
185
  assert "production and test code" in code_quality
188
- assert "comments tied to touched code are removed" in gate
189
- assert "comments tied to untouched code remain unchanged" in gate
190
186
 
191
187
 
192
188
  def test_comment_policy_removes_directive_justification_guidance() -> None: