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,137 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import time
5
+ from collections.abc import Callable
6
+ from pathlib import Path
7
+
8
+ from . import github_api, github_transport
9
+ from .config.constants import (
10
+ APP_EXPIRATION_CLAIM,
11
+ APP_ISSUED_AT_CLAIM,
12
+ APP_ISSUER_CLAIM,
13
+ APP_JWT_ALGORITHM,
14
+ CONTENTS_PERMISSION_KEY,
15
+ HTTP_CREATED,
16
+ INSTALLATION_TOKEN_ENDPOINT_TEMPLATE,
17
+ ISSUES_PERMISSION_KEY,
18
+ PERMISSIONS_KEY,
19
+ PULL_REQUESTS_PERMISSION_KEY,
20
+ READ_PERMISSION,
21
+ REPOSITORIES_KEY,
22
+ STATUSES_PERMISSION_KEY,
23
+ TOKEN_KEY,
24
+ TOKEN_RESOURCE_NAME,
25
+ UTF8_ENCODING,
26
+ WRITE_PERMISSION,
27
+ )
28
+ from .config.timing import (
29
+ APP_JWT_CLOCK_SKEW_SECONDS,
30
+ APP_JWT_LIFETIME_SECONDS,
31
+ )
32
+ from .github_api import authorized_headers, request_json
33
+ from .github_parsing import require_mapping, require_text
34
+ from .github_transport import (
35
+ HttpRequester,
36
+ JwtEncoder,
37
+ encode_app_jwt,
38
+ request_http,
39
+ )
40
+ from .model import RepositorySettings
41
+
42
+ GitHubApi = github_api.GitHubApi
43
+ GitHubError = github_api.GitHubError
44
+ HttpReply = github_transport.HttpReply
45
+
46
+
47
+ def _build_installation_token_body(
48
+ repository: RepositorySettings, should_write_issue_labels: bool
49
+ ) -> bytes:
50
+ all_permissions = {
51
+ CONTENTS_PERMISSION_KEY: READ_PERMISSION,
52
+ PULL_REQUESTS_PERMISSION_KEY: READ_PERMISSION,
53
+ STATUSES_PERMISSION_KEY: WRITE_PERMISSION,
54
+ }
55
+ if should_write_issue_labels:
56
+ all_permissions[ISSUES_PERMISSION_KEY] = WRITE_PERMISSION
57
+ all_permissions[PULL_REQUESTS_PERMISSION_KEY] = WRITE_PERMISSION
58
+ token_request = {
59
+ REPOSITORIES_KEY: [repository.name],
60
+ PERMISSIONS_KEY: all_permissions,
61
+ }
62
+ return json.dumps(token_request).encode(UTF8_ENCODING)
63
+
64
+
65
+ class GitHubAppAuthenticator:
66
+ def __init__(
67
+ self,
68
+ api_url: str,
69
+ app_id: int,
70
+ installation_id: int,
71
+ private_key_path: Path,
72
+ requester: HttpRequester = request_http,
73
+ jwt_encoder: JwtEncoder = encode_app_jwt,
74
+ clock: Callable[[], float] = time.time,
75
+ ) -> None:
76
+ self.api_url = api_url
77
+ self.app_id = app_id
78
+ self.installation_id = installation_id
79
+ self.private_key_path = private_key_path
80
+ self.requester = requester
81
+ self.jwt_encoder = jwt_encoder
82
+ self.clock = clock
83
+
84
+ def issue_repository_api(
85
+ self,
86
+ repository: RepositorySettings,
87
+ *,
88
+ should_write_issue_labels: bool = False,
89
+ ) -> GitHubApi:
90
+ """Create a repository-scoped API client.
91
+
92
+ Args:
93
+ repository: Repository to scope.
94
+
95
+ Returns:
96
+ Authenticated API client.
97
+ """
98
+ installation_token = self._request_repository_token(
99
+ repository, should_write_issue_labels
100
+ )
101
+ return GitHubApi(self.api_url, installation_token, self.requester)
102
+
103
+ def _request_repository_token(
104
+ self, repository: RepositorySettings, should_write_issue_labels: bool
105
+ ) -> str:
106
+ app_token = self.create_app_token()
107
+ endpoint = INSTALLATION_TOKEN_ENDPOINT_TEMPLATE.format(
108
+ installation_id=self.installation_id
109
+ )
110
+ request_body = _build_installation_token_body(
111
+ repository, should_write_issue_labels
112
+ )
113
+ token_payload = request_json(
114
+ self.requester,
115
+ "POST",
116
+ self.api_url + endpoint,
117
+ authorized_headers(app_token),
118
+ request_body,
119
+ HTTP_CREATED,
120
+ )
121
+ token_mapping = require_mapping(token_payload, TOKEN_RESOURCE_NAME)
122
+ return require_text(token_mapping, TOKEN_KEY, TOKEN_RESOURCE_NAME)
123
+
124
+ def create_app_token(self) -> str:
125
+ """Create an app JWT for token exchange.
126
+
127
+ Returns:
128
+ The encoded app token.
129
+ """
130
+ current_time = int(self.clock())
131
+ all_claims: dict[str, int | str] = {
132
+ APP_ISSUER_CLAIM: str(self.app_id),
133
+ APP_ISSUED_AT_CLAIM: current_time - APP_JWT_CLOCK_SKEW_SECONDS,
134
+ APP_EXPIRATION_CLAIM: current_time + APP_JWT_LIFETIME_SECONDS,
135
+ }
136
+ private_key = self.private_key_path.read_text(encoding=UTF8_ENCODING)
137
+ return self.jwt_encoder(all_claims, private_key, APP_JWT_ALGORITHM)
@@ -0,0 +1,376 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+
5
+ from .branch_refs import (
6
+ GetJson,
7
+ _resolve_candidate_base_shas,
8
+ replace_candidate_base_sha,
9
+ )
10
+ from .config.constants import (
11
+ ACCEPT_HEADER,
12
+ API_VERSION_HEADER,
13
+ AUTHORIZATION_HEADER,
14
+ BEARER_PREFIX,
15
+ COMMIT_ENDPOINT_TEMPLATE,
16
+ COMMIT_PARENTS_KEY,
17
+ CONTENT_TYPE_HEADER,
18
+ GITHUB_ACCEPT_TYPE,
19
+ GITHUB_API_VERSION,
20
+ GITHUB_JSON_ERROR_TEMPLATE,
21
+ GITHUB_PAGE_SIZE,
22
+ GITHUB_REQUEST_ERROR_TEMPLATE,
23
+ GITHUB_SHAPE_ERROR_TEMPLATE,
24
+ HTTP_CREATED,
25
+ HTTP_NO_CONTENT,
26
+ HTTP_OK,
27
+ ISSUE_LABELS_ENDPOINT_TEMPLATE,
28
+ ISSUE_LABELS_KEY,
29
+ JSON_CONTENT_TYPE,
30
+ PULL_ENDPOINT_TEMPLATE,
31
+ PULL_SHA_KEY,
32
+ PULLS_ENDPOINT_TEMPLATE,
33
+ STATUS_CONTEXT_KEY,
34
+ STATUS_DESCRIPTION_KEY,
35
+ STATUS_ENDPOINT_TEMPLATE,
36
+ STATUS_STATE_KEY,
37
+ UTF8_ENCODING,
38
+ )
39
+ from .github_labels import DeleteLabel, ReadLabelsPage, remove_label_if_present
40
+ from .github_parsing import (
41
+ GitHubError,
42
+ _parse_pull_candidates,
43
+ _require_pull_list,
44
+ parse_candidate,
45
+ require_mapping,
46
+ require_text,
47
+ )
48
+ from .github_transport import HttpReply, HttpRequester, request_http
49
+ from .model import PullRequestCandidate, RepositorySettings, StatusState
50
+
51
+
52
+ def _build_pull_page_endpoint(repository: RepositorySettings, page: int) -> str:
53
+ return PULLS_ENDPOINT_TEMPLATE.format(
54
+ repository=repository.slug,
55
+ page_size=GITHUB_PAGE_SIZE,
56
+ page=page,
57
+ )
58
+
59
+
60
+ def _build_status_body(state: StatusState, context: str, description: str) -> bytes:
61
+ status_payload = {
62
+ STATUS_STATE_KEY: state.value,
63
+ STATUS_CONTEXT_KEY: context,
64
+ STATUS_DESCRIPTION_KEY: description,
65
+ }
66
+ return json.dumps(status_payload).encode(UTF8_ENCODING)
67
+
68
+
69
+ def _list_open_candidates(
70
+ get_json: GetJson,
71
+ repository: RepositorySettings,
72
+ should_require_merge_commit: bool,
73
+ ) -> tuple[PullRequestCandidate, ...]:
74
+ all_candidates: list[PullRequestCandidate] = []
75
+ page = 1
76
+ while True:
77
+ endpoint = _build_pull_page_endpoint(repository, page)
78
+ raw_page = _require_pull_list(get_json(endpoint))
79
+ all_candidates.extend(
80
+ _parse_pull_candidates(
81
+ repository.slug,
82
+ raw_page,
83
+ should_require_merge_commit,
84
+ )
85
+ )
86
+ if len(raw_page) < GITHUB_PAGE_SIZE:
87
+ return _resolve_candidate_base_shas(
88
+ all_candidates,
89
+ repository,
90
+ get_json,
91
+ should_require_merge_commit,
92
+ )
93
+ page += 1
94
+
95
+
96
+ class GitHubApi:
97
+ def __init__(
98
+ self,
99
+ api_url: str,
100
+ installation_token: str,
101
+ requester: HttpRequester = request_http,
102
+ ) -> None:
103
+ self.api_url = api_url
104
+ self.installation_token = installation_token
105
+ self.requester = requester
106
+
107
+ def list_open_candidates(
108
+ self,
109
+ repository: RepositorySettings,
110
+ *,
111
+ should_require_merge_commit: bool = True,
112
+ ) -> tuple[PullRequestCandidate, ...]:
113
+ """List recognized pull requests.
114
+
115
+ Args:
116
+ repository: Repository to query.
117
+
118
+ Returns:
119
+ Recognized pull requests.
120
+
121
+ Raises:
122
+ GitHubError: If GitHub data is invalid.
123
+ """
124
+ return _list_open_candidates(
125
+ self.get_json,
126
+ repository,
127
+ should_require_merge_commit,
128
+ )
129
+
130
+ def get_candidate(
131
+ self,
132
+ repository: RepositorySettings,
133
+ pull_request_number: int,
134
+ *,
135
+ should_require_merge_commit: bool = True,
136
+ ) -> PullRequestCandidate:
137
+ """Read one pull request.
138
+
139
+ Args:
140
+ repository: Repository to query.
141
+
142
+ Returns:
143
+ Pull request metadata.
144
+ """
145
+ endpoint = PULL_ENDPOINT_TEMPLATE.format(
146
+ repository=repository.slug,
147
+ pull_number=pull_request_number,
148
+ )
149
+ candidate = parse_candidate(
150
+ repository.slug,
151
+ self.get_json(endpoint),
152
+ should_require_merge_commit=should_require_merge_commit,
153
+ )
154
+ if should_require_merge_commit:
155
+ return candidate
156
+ return replace_candidate_base_sha(candidate, repository, self.get_json)
157
+
158
+ def get_commit_parents(
159
+ self, repository: RepositorySettings, commit_sha: str
160
+ ) -> tuple[str, ...]:
161
+ """Read commit parent SHAs.
162
+
163
+ Args:
164
+ repository: Repository to query.
165
+
166
+ Returns:
167
+ Parent commit SHAs.
168
+
169
+ Raises:
170
+ GitHubError: If GitHub data is invalid.
171
+ """
172
+ endpoint = COMMIT_ENDPOINT_TEMPLATE.format(
173
+ repository=repository.slug,
174
+ commit_sha=commit_sha,
175
+ )
176
+ commit_mapping = require_mapping(self.get_json(endpoint), "commit")
177
+ raw_parents = commit_mapping.get(COMMIT_PARENTS_KEY)
178
+ if not isinstance(raw_parents, list):
179
+ raise GitHubError(GITHUB_SHAPE_ERROR_TEMPLATE.format(resource="commit"))
180
+ return tuple(
181
+ require_text(require_mapping(each_parent, "commit"), PULL_SHA_KEY, "commit")
182
+ for each_parent in raw_parents
183
+ )
184
+
185
+ def post_status(
186
+ self,
187
+ repository: RepositorySettings,
188
+ commit_sha: str,
189
+ state: StatusState,
190
+ context: str,
191
+ description: str,
192
+ ) -> None:
193
+ """Post one advisory commit status.
194
+
195
+ Args:
196
+ commit_sha: Commit receiving the status.
197
+
198
+ Raises:
199
+ GitHubError: If GitHub rejects the request.
200
+ """
201
+ endpoint = STATUS_ENDPOINT_TEMPLATE.format(
202
+ repository=repository.slug,
203
+ commit_sha=commit_sha,
204
+ )
205
+ request_body = _build_status_body(state, context, description)
206
+ request_json(
207
+ self.requester,
208
+ "POST",
209
+ self.api_url + endpoint,
210
+ authorized_headers(self.installation_token),
211
+ request_body,
212
+ HTTP_CREATED,
213
+ )
214
+
215
+ def add_label(
216
+ self,
217
+ repository: RepositorySettings,
218
+ pull_request_number: int,
219
+ label: str,
220
+ ) -> None:
221
+ """Add one label to a pull request.
222
+
223
+ Args:
224
+ label: Label to add.
225
+
226
+ Raises:
227
+ GitHubError: If GitHub rejects the request.
228
+ """
229
+ endpoint = ISSUE_LABELS_ENDPOINT_TEMPLATE.format(
230
+ repository=repository.slug,
231
+ pull_number=pull_request_number,
232
+ )
233
+ request_body = json.dumps({ISSUE_LABELS_KEY: [label]}).encode(UTF8_ENCODING)
234
+ request_json(
235
+ self.requester,
236
+ "POST",
237
+ self.api_url + endpoint,
238
+ authorized_headers(self.installation_token),
239
+ request_body,
240
+ HTTP_OK,
241
+ )
242
+
243
+ def remove_label(
244
+ self,
245
+ repository: RepositorySettings,
246
+ pull_request_number: int,
247
+ label: str,
248
+ ) -> None:
249
+ """Remove one label from a pull request.
250
+
251
+ Args:
252
+ repository: Repository containing the pull request.
253
+ pull_request_number: Pull request number.
254
+ label: Label to remove.
255
+
256
+ Raises:
257
+ GitHubError: If GitHub rejects the request.
258
+ """
259
+ all_headers = authorized_headers(self.installation_token)
260
+ remove_label_if_present(
261
+ _labels_page_reader(self.requester, self.api_url, all_headers),
262
+ _label_deleter(self.requester, self.api_url, all_headers),
263
+ repository,
264
+ pull_request_number,
265
+ label,
266
+ )
267
+
268
+ def get_json(self, endpoint: str) -> object:
269
+ """Read one JSON resource.
270
+
271
+ Args:
272
+ endpoint: Repository API path.
273
+
274
+ Returns:
275
+ Decoded JSON payload.
276
+ """
277
+ return request_json(
278
+ self.requester,
279
+ "GET",
280
+ self.api_url + endpoint,
281
+ authorized_headers(self.installation_token),
282
+ None,
283
+ HTTP_OK,
284
+ )
285
+
286
+
287
+ def authorized_headers(token: str) -> dict[str, str]:
288
+ """Build headers for an installation token request.
289
+
290
+ Args:
291
+ token: Installation token.
292
+
293
+ Returns:
294
+ Request headers.
295
+ """
296
+ return {
297
+ AUTHORIZATION_HEADER: BEARER_PREFIX + token,
298
+ ACCEPT_HEADER: GITHUB_ACCEPT_TYPE,
299
+ CONTENT_TYPE_HEADER: JSON_CONTENT_TYPE,
300
+ API_VERSION_HEADER: GITHUB_API_VERSION,
301
+ }
302
+
303
+
304
+ def _labels_page_reader(
305
+ requester: HttpRequester, api_url: str, all_headers: dict[str, str]
306
+ ) -> ReadLabelsPage:
307
+ def read_labels_page(endpoint: str) -> object:
308
+ return request_json(
309
+ requester, "GET", api_url + endpoint, all_headers, None, HTTP_OK
310
+ )
311
+
312
+ return read_labels_page
313
+
314
+
315
+ def _label_deleter(
316
+ requester: HttpRequester, api_url: str, all_headers: dict[str, str]
317
+ ) -> DeleteLabel:
318
+ def delete_label(endpoint: str) -> None:
319
+ _delete_label(requester, api_url + endpoint, all_headers)
320
+
321
+ return delete_label
322
+
323
+
324
+ def _delete_label(
325
+ requester: HttpRequester, url: str, all_headers: dict[str, str]
326
+ ) -> None:
327
+ reply = requester("DELETE", url, all_headers, None)
328
+ if reply.status_code in (HTTP_OK, HTTP_NO_CONTENT):
329
+ return
330
+ raise GitHubError(
331
+ GITHUB_REQUEST_ERROR_TEMPLATE.format(
332
+ method="DELETE", url=url, status_code=reply.status_code
333
+ )
334
+ )
335
+
336
+
337
+ def _decode_json_reply(reply: HttpReply, url: str) -> object:
338
+ try:
339
+ return json.loads(reply.body.decode(UTF8_ENCODING))
340
+ except (UnicodeDecodeError, json.JSONDecodeError) as error:
341
+ raise GitHubError(GITHUB_JSON_ERROR_TEMPLATE.format(url=url)) from error
342
+
343
+
344
+ def _raise_unexpected_status(method: str, url: str, status_code: int) -> None:
345
+ raise GitHubError(
346
+ GITHUB_REQUEST_ERROR_TEMPLATE.format(
347
+ method=method, url=url, status_code=status_code
348
+ )
349
+ )
350
+
351
+
352
+ def request_json(
353
+ requester: HttpRequester,
354
+ method: str,
355
+ url: str,
356
+ all_headers: dict[str, str],
357
+ body: bytes | None,
358
+ expected_status: int,
359
+ ) -> object:
360
+ """Send a request and decode JSON.
361
+ Args:
362
+ requester: Request function.
363
+ method: HTTP method.
364
+ url: Request URL.
365
+ all_headers: Headers.
366
+ body: Optional body.
367
+ expected_status: Required status.
368
+ Returns:
369
+ Decoded JSON payload.
370
+ Raises:
371
+ GitHubError: If status or JSON is invalid.
372
+ """
373
+ reply = requester(method, url, all_headers, body)
374
+ if reply.status_code != expected_status:
375
+ _raise_unexpected_status(method, url, reply.status_code)
376
+ return _decode_json_reply(reply, url)
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+
3
+ import urllib.parse
4
+ from collections.abc import Callable
5
+
6
+ from .config.constants import (
7
+ GITHUB_PAGE_SIZE,
8
+ ISSUE_LABEL_ENDPOINT_TEMPLATE,
9
+ ISSUE_LABELS_PAGE_ENDPOINT_TEMPLATE,
10
+ )
11
+ from .model import RepositorySettings
12
+
13
+ ReadLabelsPage = Callable[[str], object]
14
+ DeleteLabel = Callable[[str], None]
15
+
16
+
17
+ def _label_is_present(all_labels: object, label: str) -> bool:
18
+ if not isinstance(all_labels, list):
19
+ return False
20
+ return any(
21
+ isinstance(each_label, dict) and each_label.get("name") == label
22
+ for each_label in all_labels
23
+ )
24
+
25
+
26
+ def _label_page_endpoint(
27
+ repository: RepositorySettings, pull_request_number: int, page: int
28
+ ) -> str:
29
+ return ISSUE_LABELS_PAGE_ENDPOINT_TEMPLATE.format(
30
+ repository=repository.slug,
31
+ pull_number=pull_request_number,
32
+ page_size=GITHUB_PAGE_SIZE,
33
+ page=page,
34
+ )
35
+
36
+
37
+ def _issue_label_endpoint(
38
+ repository: RepositorySettings, pull_request_number: int, label: str
39
+ ) -> str:
40
+ return ISSUE_LABEL_ENDPOINT_TEMPLATE.format(
41
+ repository=repository.slug,
42
+ pull_number=pull_request_number,
43
+ label=urllib.parse.quote(label, safe=""),
44
+ )
45
+
46
+
47
+ def _label_is_on_any_page(
48
+ read_labels_page: ReadLabelsPage,
49
+ repository: RepositorySettings,
50
+ pull_request_number: int,
51
+ label: str,
52
+ ) -> bool:
53
+ page = 1
54
+ while True:
55
+ raw_page = read_labels_page(
56
+ _label_page_endpoint(repository, pull_request_number, page)
57
+ )
58
+ if _label_is_present(raw_page, label):
59
+ return True
60
+ if not isinstance(raw_page, list) or len(raw_page) < GITHUB_PAGE_SIZE:
61
+ return False
62
+ page += 1
63
+
64
+
65
+ def remove_label_if_present(
66
+ read_labels_page: ReadLabelsPage,
67
+ delete_label: DeleteLabel,
68
+ repository: RepositorySettings,
69
+ pull_request_number: int,
70
+ label: str,
71
+ ) -> None:
72
+ """Delete one label from a pull request when a labels page still carries it.
73
+
74
+ Pages /issues/{n}/labels at per_page=100 until the label is found or a
75
+ short page ends the walk.
76
+
77
+ Args:
78
+ read_labels_page: Reads one labels endpoint and returns its parsed body.
79
+ delete_label: Deletes the label the endpoint names.
80
+ repository: Repository holding the pull request.
81
+ pull_request_number: Pull request number.
82
+ label: Label to remove.
83
+ """
84
+ if not _label_is_on_any_page(
85
+ read_labels_page, repository, pull_request_number, label
86
+ ):
87
+ return
88
+ delete_label(_issue_label_endpoint(repository, pull_request_number, label))