@openhands/extensions 0.21.0 → 0.22.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 (267) hide show
  1. package/.claude-plugin/marketplace.json +881 -0
  2. package/.codex-plugin/marketplace.json +881 -0
  3. package/.github/workflows/npm-publish.yml +9 -3
  4. package/.plugin/marketplace.json +881 -0
  5. package/.release-please-manifest.json +1 -1
  6. package/README.md +3 -2
  7. package/automations/bundle-index.js +16 -7
  8. package/automations/catalog/github-delivery-watchdog/manifest.json +79 -0
  9. package/automations/catalog/github-issue-to-pr/manifest.json +23 -15
  10. package/automations/catalog/github-issue-triage/manifest.json +4 -3
  11. package/automations/catalog/github-pr-reviewer/manifest.json +22 -15
  12. package/automations/catalog-index.js +6 -4
  13. package/marketplaces/openhands-extensions.json +6 -0
  14. package/package.json +1 -1
  15. package/plugins/city-weather/.claude-plugin/plugin.json +13 -0
  16. package/plugins/city-weather/.codex-plugin/plugin.json +13 -0
  17. package/plugins/cobol-modernization/.claude-plugin/plugin.json +19 -0
  18. package/plugins/cobol-modernization/.codex-plugin/plugin.json +19 -0
  19. package/plugins/issue-duplicate-checker/.claude-plugin/plugin.json +13 -0
  20. package/plugins/issue-duplicate-checker/.codex-plugin/plugin.json +13 -0
  21. package/plugins/magic-test/.claude-plugin/plugin.json +13 -0
  22. package/plugins/magic-test/.codex-plugin/plugin.json +13 -0
  23. package/plugins/migration-scoring/.claude-plugin/plugin.json +19 -0
  24. package/plugins/migration-scoring/.codex-plugin/plugin.json +19 -0
  25. package/plugins/onboarding/.claude-plugin/plugin.json +20 -0
  26. package/plugins/onboarding/.codex-plugin/plugin.json +20 -0
  27. package/plugins/onboarding/skills/agent-readiness-report/references/criteria.md +144 -0
  28. package/plugins/onboarding/skills/improve-agent-readiness/references/criteria.md +144 -0
  29. package/plugins/openhands/.claude-plugin/plugin.json +13 -0
  30. package/plugins/openhands/.codex-plugin/plugin.json +13 -0
  31. package/plugins/openhands/skills/openhands-api/.claude-plugin/plugin.json +22 -0
  32. package/plugins/openhands/skills/openhands-api/.codex-plugin/plugin.json +22 -0
  33. package/plugins/openhands/skills/openhands-api/.plugin/plugin.json +22 -0
  34. package/plugins/openhands/skills/openhands-api/README.md +52 -0
  35. package/plugins/openhands/skills/openhands-api/SKILL.md +469 -0
  36. package/plugins/openhands/skills/openhands-api/references/README.md +33 -0
  37. package/plugins/openhands/skills/openhands-api/references/TROUBLESHOOTING.md +81 -0
  38. package/plugins/openhands/skills/openhands-api/references/example_prompt.md +12 -0
  39. package/plugins/openhands/skills/openhands-api/references/windows.md +44 -0
  40. package/plugins/openhands/skills/openhands-api/scripts/openhands_api.py +606 -0
  41. package/plugins/openhands/skills/openhands-api/scripts/openhands_api.ts +252 -0
  42. package/plugins/openhands/skills/openhands-automation/.claude-plugin/plugin.json +19 -0
  43. package/plugins/openhands/skills/openhands-automation/.codex-plugin/plugin.json +19 -0
  44. package/plugins/openhands/skills/openhands-automation/.plugin/plugin.json +19 -0
  45. package/plugins/openhands/skills/openhands-automation/README.md +89 -0
  46. package/plugins/openhands/skills/openhands-automation/SKILL.md +899 -0
  47. package/plugins/openhands/skills/openhands-automation/commands/automation-create.md +8 -0
  48. package/plugins/openhands/skills/openhands-automation/references/ab-testing.md +185 -0
  49. package/plugins/openhands/skills/openhands-automation/references/custom-automation.md +832 -0
  50. package/plugins/openhands/skills/openhands-automation/references/security.md +84 -0
  51. package/plugins/openhands/skills/openhands-automation/references/windows.md +57 -0
  52. package/plugins/openhands/skills/openhands-sdk/.claude-plugin/plugin.json +20 -0
  53. package/plugins/openhands/skills/openhands-sdk/.codex-plugin/plugin.json +20 -0
  54. package/plugins/openhands/skills/openhands-sdk/.plugin/plugin.json +20 -0
  55. package/plugins/openhands/skills/openhands-sdk/README.md +22 -0
  56. package/plugins/openhands/skills/openhands-sdk/SKILL.md +245 -0
  57. package/plugins/openhands/skills/openhands-sdk/commands/sdk.md +8 -0
  58. package/plugins/pr-review/.claude-plugin/plugin.json +13 -0
  59. package/plugins/pr-review/.codex-plugin/plugin.json +13 -0
  60. package/plugins/pr-review/skills/code-review/.claude-plugin/plugin.json +19 -0
  61. package/plugins/pr-review/skills/code-review/.codex-plugin/plugin.json +19 -0
  62. package/plugins/pr-review/skills/code-review/.plugin/plugin.json +19 -0
  63. package/plugins/pr-review/skills/code-review/README.md +18 -0
  64. package/plugins/pr-review/skills/code-review/SKILL.md +209 -0
  65. package/plugins/pr-review/skills/code-review/commands/codereview-roasted.md +8 -0
  66. package/plugins/pr-review/skills/code-review/commands/codereview.md +8 -0
  67. package/plugins/pr-review/skills/code-review/references/risk-evaluation.md +41 -0
  68. package/plugins/pr-review/skills/code-review/references/supply-chain-security.md +31 -0
  69. package/plugins/pr-review/skills/github-pr-review/.claude-plugin/plugin.json +18 -0
  70. package/plugins/pr-review/skills/github-pr-review/.codex-plugin/plugin.json +18 -0
  71. package/plugins/pr-review/skills/github-pr-review/.plugin/plugin.json +18 -0
  72. package/plugins/pr-review/skills/github-pr-review/README.md +145 -0
  73. package/plugins/pr-review/skills/github-pr-review/SKILL.md +187 -0
  74. package/plugins/pr-review/skills/github-pr-review/commands/github-pr-review.md +8 -0
  75. package/plugins/pr-review/skills/github-pr-review/references/windows.md +47 -0
  76. package/plugins/qa-changes/.claude-plugin/plugin.json +11 -0
  77. package/plugins/qa-changes/.codex-plugin/plugin.json +11 -0
  78. package/plugins/qa-changes/skills/github-pr-review/.claude-plugin/plugin.json +18 -0
  79. package/plugins/qa-changes/skills/github-pr-review/.codex-plugin/plugin.json +18 -0
  80. package/plugins/qa-changes/skills/github-pr-review/.plugin/plugin.json +18 -0
  81. package/plugins/qa-changes/skills/github-pr-review/README.md +145 -0
  82. package/plugins/qa-changes/skills/github-pr-review/SKILL.md +187 -0
  83. package/plugins/qa-changes/skills/github-pr-review/commands/github-pr-review.md +8 -0
  84. package/plugins/qa-changes/skills/github-pr-review/references/windows.md +47 -0
  85. package/plugins/qa-changes/skills/qa-changes/README.md +18 -0
  86. package/plugins/qa-changes/skills/qa-changes/SKILL.md +229 -0
  87. package/plugins/qa-changes/skills/qa-changes/commands/qa-changes.md +8 -0
  88. package/plugins/release-notes/.claude-plugin/plugin.json +19 -0
  89. package/plugins/release-notes/.codex-plugin/plugin.json +19 -0
  90. package/plugins/release-notes/skills/release-notes/README.md +24 -0
  91. package/plugins/release-notes/skills/release-notes/SKILL.md +19 -0
  92. package/plugins/release-notes/skills/release-notes/commands/release-notes.md +8 -0
  93. package/plugins/vulnerability-remediation/.claude-plugin/plugin.json +19 -0
  94. package/plugins/vulnerability-remediation/.codex-plugin/plugin.json +19 -0
  95. package/plugins/vulnerability-remediation/skills/github/.claude-plugin/plugin.json +19 -0
  96. package/plugins/vulnerability-remediation/skills/github/.codex-plugin/plugin.json +19 -0
  97. package/plugins/vulnerability-remediation/skills/github/.plugin/plugin.json +19 -0
  98. package/plugins/vulnerability-remediation/skills/github/README.md +42 -0
  99. package/plugins/vulnerability-remediation/skills/github/SKILL.md +107 -0
  100. package/plugins/vulnerability-remediation/skills/github/references/windows.md +48 -0
  101. package/plugins/vulnerability-remediation/skills/github/scripts/agent_conversation.py +143 -0
  102. package/plugins/vulnerability-remediation/skills/github/scripts/github_client.py +230 -0
  103. package/plugins/vulnerability-remediation/skills/security/.claude-plugin/plugin.json +18 -0
  104. package/plugins/vulnerability-remediation/skills/security/.codex-plugin/plugin.json +18 -0
  105. package/plugins/vulnerability-remediation/skills/security/.plugin/plugin.json +18 -0
  106. package/plugins/vulnerability-remediation/skills/security/README.md +38 -0
  107. package/plugins/vulnerability-remediation/skills/security/SKILL.md +33 -0
  108. package/pyproject.toml +1 -1
  109. package/python/openhands_extensions/_version.py +1 -1
  110. package/skills/add-javadoc/.claude-plugin/plugin.json +18 -0
  111. package/skills/add-javadoc/.codex-plugin/plugin.json +18 -0
  112. package/skills/add-skill/.claude-plugin/plugin.json +18 -0
  113. package/skills/add-skill/.codex-plugin/plugin.json +18 -0
  114. package/skills/agent-canvas-environment/.claude-plugin/plugin.json +19 -0
  115. package/skills/agent-canvas-environment/.codex-plugin/plugin.json +19 -0
  116. package/skills/agent-creator/.claude-plugin/plugin.json +20 -0
  117. package/skills/agent-creator/.codex-plugin/plugin.json +20 -0
  118. package/skills/agent-memory/.claude-plugin/plugin.json +18 -0
  119. package/skills/agent-memory/.codex-plugin/plugin.json +18 -0
  120. package/skills/agent-sdk-builder/.claude-plugin/plugin.json +18 -0
  121. package/skills/agent-sdk-builder/.codex-plugin/plugin.json +18 -0
  122. package/skills/azure-devops/.claude-plugin/plugin.json +18 -0
  123. package/skills/azure-devops/.codex-plugin/plugin.json +18 -0
  124. package/skills/bitbucket/.claude-plugin/plugin.json +17 -0
  125. package/skills/bitbucket/.codex-plugin/plugin.json +17 -0
  126. package/skills/bitbucket-cloud/.claude-plugin/plugin.json +18 -0
  127. package/skills/bitbucket-cloud/.codex-plugin/plugin.json +18 -0
  128. package/skills/bitbucket-data-center/.claude-plugin/plugin.json +19 -0
  129. package/skills/bitbucket-data-center/.codex-plugin/plugin.json +19 -0
  130. package/skills/code-review/.claude-plugin/plugin.json +19 -0
  131. package/skills/code-review/.codex-plugin/plugin.json +19 -0
  132. package/skills/code-simplifier/.claude-plugin/plugin.json +21 -0
  133. package/skills/code-simplifier/.codex-plugin/plugin.json +21 -0
  134. package/skills/datadog/.claude-plugin/plugin.json +19 -0
  135. package/skills/datadog/.codex-plugin/plugin.json +19 -0
  136. package/skills/deno/.claude-plugin/plugin.json +18 -0
  137. package/skills/deno/.codex-plugin/plugin.json +18 -0
  138. package/skills/discord/.claude-plugin/plugin.json +18 -0
  139. package/skills/discord/.codex-plugin/plugin.json +18 -0
  140. package/skills/docker/.claude-plugin/plugin.json +17 -0
  141. package/skills/docker/.codex-plugin/plugin.json +17 -0
  142. package/skills/evidence-based-citations/.claude-plugin/plugin.json +20 -0
  143. package/skills/evidence-based-citations/.codex-plugin/plugin.json +20 -0
  144. package/skills/flarglebargle/.claude-plugin/plugin.json +16 -0
  145. package/skills/flarglebargle/.codex-plugin/plugin.json +16 -0
  146. package/skills/frontend-design/.claude-plugin/plugin.json +21 -0
  147. package/skills/frontend-design/.codex-plugin/plugin.json +21 -0
  148. package/skills/github/.claude-plugin/plugin.json +19 -0
  149. package/skills/github/.codex-plugin/plugin.json +19 -0
  150. package/skills/github/scripts/agent_conversation.py +143 -0
  151. package/skills/github/scripts/github_client.py +31 -5
  152. package/skills/github-actions/.claude-plugin/plugin.json +19 -0
  153. package/skills/github-actions/.codex-plugin/plugin.json +19 -0
  154. package/skills/github-agents-md-maintainer/.claude-plugin/plugin.json +19 -0
  155. package/skills/github-agents-md-maintainer/.codex-plugin/plugin.json +19 -0
  156. package/skills/github-agents-md-maintainer/scripts/github_client.py +230 -0
  157. package/skills/github-delivery-watchdog/.claude-plugin/plugin.json +9 -0
  158. package/skills/github-delivery-watchdog/.codex-plugin/plugin.json +9 -0
  159. package/skills/github-delivery-watchdog/.plugin/plugin.json +9 -0
  160. package/skills/github-delivery-watchdog/README.md +10 -0
  161. package/skills/github-delivery-watchdog/SKILL.md +35 -0
  162. package/skills/github-delivery-watchdog/commands/github-delivery-watchdog.md +8 -0
  163. package/skills/github-delivery-watchdog/scripts/github_client.py +230 -0
  164. package/skills/github-delivery-watchdog/scripts/worker.py +96 -0
  165. package/skills/github-issue-to-pr/.claude-plugin/plugin.json +19 -0
  166. package/skills/github-issue-to-pr/.codex-plugin/plugin.json +19 -0
  167. package/skills/github-issue-to-pr/SKILL.md +7 -0
  168. package/skills/github-issue-to-pr/scripts/agent_conversation.py +143 -0
  169. package/skills/github-issue-to-pr/scripts/github_client.py +230 -0
  170. package/skills/github-issue-to-pr/scripts/main.py +16 -10
  171. package/skills/github-issue-to-pr/scripts/worker.py +172 -0
  172. package/skills/github-issue-triage/.claude-plugin/plugin.json +9 -0
  173. package/skills/github-issue-triage/.codex-plugin/plugin.json +9 -0
  174. package/skills/github-issue-triage/SKILL.md +15 -18
  175. package/skills/github-issue-triage/scripts/agent_conversation.py +143 -0
  176. package/skills/github-issue-triage/scripts/github_client.py +230 -0
  177. package/skills/github-issue-triage/scripts/worker.py +81 -98
  178. package/skills/github-pr-review/.claude-plugin/plugin.json +18 -0
  179. package/skills/github-pr-review/.codex-plugin/plugin.json +18 -0
  180. package/skills/github-pr-reviewer/.claude-plugin/plugin.json +20 -0
  181. package/skills/github-pr-reviewer/.codex-plugin/plugin.json +20 -0
  182. package/skills/github-pr-reviewer/SKILL.md +7 -0
  183. package/skills/github-pr-reviewer/scripts/agent_conversation.py +143 -0
  184. package/skills/github-pr-reviewer/scripts/github_client.py +230 -0
  185. package/skills/github-pr-reviewer/scripts/main.py +20 -8
  186. package/skills/github-pr-reviewer/scripts/worker.py +94 -0
  187. package/skills/github-repo-monitor/.claude-plugin/plugin.json +22 -0
  188. package/skills/github-repo-monitor/.codex-plugin/plugin.json +22 -0
  189. package/skills/github-repo-monitor/scripts/github_client.py +230 -0
  190. package/skills/gitlab/.claude-plugin/plugin.json +17 -0
  191. package/skills/gitlab/.codex-plugin/plugin.json +17 -0
  192. package/skills/gitlab-issue-to-mr/.claude-plugin/plugin.json +19 -0
  193. package/skills/gitlab-issue-to-mr/.codex-plugin/plugin.json +19 -0
  194. package/skills/incident-retrospective/.claude-plugin/plugin.json +21 -0
  195. package/skills/incident-retrospective/.codex-plugin/plugin.json +21 -0
  196. package/skills/index.js +12 -3
  197. package/skills/iterate/.claude-plugin/plugin.json +13 -0
  198. package/skills/iterate/.codex-plugin/plugin.json +13 -0
  199. package/skills/jira-issue-to-pr/.claude-plugin/plugin.json +19 -0
  200. package/skills/jira-issue-to-pr/.codex-plugin/plugin.json +19 -0
  201. package/skills/jupyter/.claude-plugin/plugin.json +18 -0
  202. package/skills/jupyter/.codex-plugin/plugin.json +18 -0
  203. package/skills/kubernetes/.claude-plugin/plugin.json +18 -0
  204. package/skills/kubernetes/.codex-plugin/plugin.json +18 -0
  205. package/skills/learn-from-code-review/.claude-plugin/plugin.json +19 -0
  206. package/skills/learn-from-code-review/.codex-plugin/plugin.json +19 -0
  207. package/skills/linear/.claude-plugin/plugin.json +19 -0
  208. package/skills/linear/.codex-plugin/plugin.json +19 -0
  209. package/skills/linear-triage/.claude-plugin/plugin.json +21 -0
  210. package/skills/linear-triage/.codex-plugin/plugin.json +21 -0
  211. package/skills/news-digest/.claude-plugin/plugin.json +19 -0
  212. package/skills/news-digest/.codex-plugin/plugin.json +19 -0
  213. package/skills/notion/.claude-plugin/plugin.json +17 -0
  214. package/skills/notion/.codex-plugin/plugin.json +17 -0
  215. package/skills/npm/.claude-plugin/plugin.json +17 -0
  216. package/skills/npm/.codex-plugin/plugin.json +17 -0
  217. package/skills/openhands-api/.claude-plugin/plugin.json +22 -0
  218. package/skills/openhands-api/.codex-plugin/plugin.json +22 -0
  219. package/skills/openhands-automation/.claude-plugin/plugin.json +19 -0
  220. package/skills/openhands-automation/.codex-plugin/plugin.json +19 -0
  221. package/skills/openhands-enterprise-troubleshooting/.claude-plugin/plugin.json +20 -0
  222. package/skills/openhands-enterprise-troubleshooting/.codex-plugin/plugin.json +20 -0
  223. package/skills/openhands-sdk/.claude-plugin/plugin.json +20 -0
  224. package/skills/openhands-sdk/.codex-plugin/plugin.json +20 -0
  225. package/skills/pdflatex/.claude-plugin/plugin.json +18 -0
  226. package/skills/pdflatex/.codex-plugin/plugin.json +18 -0
  227. package/skills/plain-english-content/.claude-plugin/plugin.json +15 -0
  228. package/skills/plain-english-content/.codex-plugin/plugin.json +15 -0
  229. package/skills/prd/.claude-plugin/plugin.json +19 -0
  230. package/skills/prd/.codex-plugin/plugin.json +19 -0
  231. package/skills/research-brief/.claude-plugin/plugin.json +20 -0
  232. package/skills/research-brief/.codex-plugin/plugin.json +20 -0
  233. package/skills/security/.claude-plugin/plugin.json +18 -0
  234. package/skills/security/.codex-plugin/plugin.json +18 -0
  235. package/skills/skill-creator/.claude-plugin/plugin.json +17 -0
  236. package/skills/skill-creator/.codex-plugin/plugin.json +17 -0
  237. package/skills/slack-channel-monitor/.claude-plugin/plugin.json +21 -0
  238. package/skills/slack-channel-monitor/.codex-plugin/plugin.json +21 -0
  239. package/skills/slack-standup-digest/.claude-plugin/plugin.json +21 -0
  240. package/skills/slack-standup-digest/.codex-plugin/plugin.json +21 -0
  241. package/skills/spark-version-upgrade/.claude-plugin/plugin.json +20 -0
  242. package/skills/spark-version-upgrade/.codex-plugin/plugin.json +20 -0
  243. package/skills/ssh/.claude-plugin/plugin.json +18 -0
  244. package/skills/ssh/.codex-plugin/plugin.json +18 -0
  245. package/skills/swift-linux/.claude-plugin/plugin.json +17 -0
  246. package/skills/swift-linux/.codex-plugin/plugin.json +17 -0
  247. package/skills/technical-writing/.claude-plugin/plugin.json +15 -0
  248. package/skills/technical-writing/.codex-plugin/plugin.json +15 -0
  249. package/skills/theme-factory/.claude-plugin/plugin.json +19 -0
  250. package/skills/theme-factory/.codex-plugin/plugin.json +19 -0
  251. package/skills/ticket-to-code-change/.claude-plugin/plugin.json +21 -0
  252. package/skills/ticket-to-code-change/.codex-plugin/plugin.json +21 -0
  253. package/skills/upstream-fork-sync/.claude-plugin/plugin.json +21 -0
  254. package/skills/upstream-fork-sync/.codex-plugin/plugin.json +21 -0
  255. package/skills/uv/.claude-plugin/plugin.json +18 -0
  256. package/skills/uv/.codex-plugin/plugin.json +18 -0
  257. package/skills/vercel/.claude-plugin/plugin.json +18 -0
  258. package/skills/vercel/.codex-plugin/plugin.json +18 -0
  259. package/tests/fixtures/automations/capabilities.json +6 -3
  260. package/tests/fixtures/automations/github-pr-reviewer.json +64 -43
  261. package/tests/github_automation_helpers.py +7 -1
  262. package/tests/test_agent_conversation_dispatch.py +180 -0
  263. package/tests/test_automation_setup.py +3 -3
  264. package/tests/test_github_developer_delivery.py +150 -0
  265. package/tests/test_github_reviewer_delivery.py +110 -0
  266. package/tests/test_github_triage_delivery.py +78 -41
  267. package/tests/test_github_watchdog_delivery.py +233 -0
@@ -0,0 +1,233 @@
1
+ """Contract tests for independent watchdog delivery."""
2
+
3
+ import json
4
+ import subprocess
5
+ import sys
6
+ from urllib.error import HTTPError
7
+
8
+ import pytest
9
+ from github_automation_helpers import ROOT, worker
10
+ from openhands.sdk.skills import install_skill
11
+
12
+
13
+ def test_installed_watchdog_skill_imports_worker(tmp_path):
14
+ name = "github-delivery-watchdog"
15
+ install_skill(source=str(ROOT / "skills" / name), installed_dir=tmp_path)
16
+ result = subprocess.run(
17
+ [sys.executable, "-c", "import worker"],
18
+ cwd=tmp_path / name / "scripts",
19
+ text=True,
20
+ capture_output=True,
21
+ check=False,
22
+ )
23
+ assert result.returncode == 0, result.stderr
24
+
25
+
26
+ @pytest.mark.parametrize(
27
+ "changes",
28
+ [
29
+ {"head": {"sha": "b" * 40, "ref": "openhands/issue-1"}},
30
+ {"draft": True},
31
+ {"mergeable": None},
32
+ {"state": "closed"},
33
+ {"base": {"sha": "c" * 40, "ref": "release"}},
34
+ ],
35
+ )
36
+ def test_watchdog_rejects_ineligible_pr_before_merge(tmp_path, monkeypatch, changes):
37
+ cls = worker("github-delivery-watchdog", tmp_path, monkeypatch).DeliveryWatchdog
38
+ run = object.__new__(cls)
39
+ run.config = {"base_branch": "main"}
40
+ pr = {
41
+ "state": "open",
42
+ "draft": False,
43
+ "mergeable": True,
44
+ "head": {"sha": "a" * 40, "ref": "openhands/issue-1"},
45
+ "base": {"ref": "main", "sha": "c" * 40},
46
+ **changes,
47
+ }
48
+ calls = []
49
+ run.gh = lambda *args: calls.append(args) or pr
50
+ assert run.merge(1, "a" * 40) is None
51
+ assert calls == [("GET", "/pulls/1")]
52
+
53
+
54
+ @pytest.mark.parametrize("base_branch", ["main", "release"])
55
+ @pytest.mark.parametrize(
56
+ "review,ci,comparison,merged",
57
+ [
58
+ ("success", True, "ahead", True),
59
+ ("failure", True, "ahead", False),
60
+ ("success", False, "ahead", False),
61
+ ("success", True, "behind", False),
62
+ ],
63
+ )
64
+ def test_watchdog_requires_exact_acceptance_ci_and_current_base(
65
+ tmp_path, monkeypatch, review, ci, comparison, merged, base_branch
66
+ ):
67
+ cls = worker("github-delivery-watchdog", tmp_path, monkeypatch).DeliveryWatchdog
68
+ run = object.__new__(cls)
69
+ run.config = {}
70
+ sha = "a" * 40
71
+ calls = []
72
+
73
+ def gh(method, path, body=None):
74
+ calls.append((method, path, body))
75
+ if path == "":
76
+ return {"default_branch": base_branch}
77
+ if path == "/pulls/1":
78
+ return {
79
+ "state": "open",
80
+ "draft": False,
81
+ "mergeable": True,
82
+ "head": {"sha": sha, "ref": "openhands/issue-1"},
83
+ "base": {"ref": base_branch, "sha": "c" * 40},
84
+ }
85
+ if "/compare/" in path:
86
+ return {"status": comparison}
87
+ return {"merged": True}
88
+
89
+ run.gh = gh
90
+ run.statuses = lambda sha: {
91
+ "software-factory/tests": "success",
92
+ "software-factory/review": review,
93
+ }
94
+ run.ci_passed = lambda sha: ci
95
+ assert bool(run.merge(1, sha)) is merged
96
+ if merged:
97
+ assert calls[-1] == (
98
+ "PUT",
99
+ "/pulls/1/merge",
100
+ {"sha": sha, "merge_method": "squash"},
101
+ )
102
+ else:
103
+ assert not any(c[0] == "PUT" for c in calls)
104
+
105
+
106
+ def test_watchdog_updates_accepted_branch_for_fresh_review(tmp_path, monkeypatch):
107
+ cls = worker("github-delivery-watchdog", tmp_path, monkeypatch).DeliveryWatchdog
108
+ run = object.__new__(cls)
109
+ run.config = {}
110
+ sha = "a" * 40
111
+ calls = []
112
+
113
+ def gh(method, path, body=None):
114
+ calls.append((method, path, body))
115
+ if path == "":
116
+ return {"default_branch": "main"}
117
+ if path == "/pulls/1":
118
+ return {
119
+ "state": "open",
120
+ "draft": False,
121
+ "mergeable": True,
122
+ "head": {"sha": sha, "ref": "openhands/issue-1"},
123
+ "base": {"ref": "main", "sha": "c" * 40},
124
+ }
125
+ if "/compare/" in path:
126
+ return {"status": "diverged", "behind_by": 1}
127
+ return {"message": "Updating pull request branch."}
128
+
129
+ run.gh = gh
130
+ run.statuses = lambda sha: {
131
+ "software-factory/tests": "success",
132
+ "software-factory/review": "success",
133
+ }
134
+ run.ci_passed = lambda sha: True
135
+
136
+ assert run.merge(1, sha) == {"message": "Updating pull request branch."}
137
+ assert calls[-2:] == [
138
+ ("POST", "/issues/1/labels", {"labels": ["openhands-review"]}),
139
+ ("PUT", "/pulls/1/update-branch", {"expected_head_sha": sha}),
140
+ ]
141
+
142
+
143
+ @pytest.mark.parametrize(
144
+ "error",
145
+ [
146
+ HTTPError("https://api.github.com", 409, "Conflict", {}, None),
147
+ TimeoutError("GitHub unavailable"),
148
+ json.JSONDecodeError("Invalid response", "", 0),
149
+ KeyError("head"),
150
+ ],
151
+ )
152
+ def test_watchdog_continues_after_a_pr_api_failure(tmp_path, monkeypatch, error):
153
+ module = worker("github-delivery-watchdog", tmp_path, monkeypatch)
154
+ run = object.__new__(module.DeliveryWatchdog)
155
+ run.gh_pages = lambda path: [
156
+ {"number": 1, "head": {"sha": "a"}},
157
+ {"number": 2, "head": {"sha": "b"}},
158
+ ]
159
+ visited = []
160
+
161
+ def merge(number, sha):
162
+ visited.append(number)
163
+ if number == 1:
164
+ raise error
165
+ return {"merged": True}
166
+
167
+ run.merge = merge
168
+ run.run()
169
+ assert visited == [1, 2]
170
+
171
+
172
+ @pytest.mark.parametrize("required,expected", [([], True), ([123], False)])
173
+ def test_actions_are_optional_but_configured_workflows_are_required(
174
+ tmp_path, monkeypatch, required, expected
175
+ ):
176
+ module = worker("github-delivery-watchdog", tmp_path, monkeypatch)
177
+ run = object.__new__(module.DeliveryWatchdog)
178
+ run.config = {"required_workflow_ids": required}
179
+ run.gh = lambda *args: {"workflow_runs": [], "total_count": 0}
180
+ assert run.ci_passed("a" * 40) is expected
181
+
182
+
183
+ @pytest.mark.parametrize(
184
+ "changes,expected",
185
+ [
186
+ ({}, True),
187
+ ({"conclusion": "failure"}, False),
188
+ ({"status": "in_progress"}, False),
189
+ ({"workflow_id": 2}, False),
190
+ ({"event": "push"}, False),
191
+ ({"head_branch": "other-branch"}, False),
192
+ ({"head_sha": "other-commit"}, False),
193
+ ],
194
+ )
195
+ def test_new_actions_execution_supersedes_only_its_own_trigger(
196
+ tmp_path, monkeypatch, changes, expected
197
+ ):
198
+ module = worker("github-delivery-watchdog", tmp_path, monkeypatch)
199
+ run = object.__new__(module.DeliveryWatchdog)
200
+ run.config = {"required_workflow_ids": [1]}
201
+ old = {
202
+ "id": 10,
203
+ "run_number": 1,
204
+ "workflow_id": 1,
205
+ "head_sha": "sha",
206
+ "event": "pull_request",
207
+ "head_branch": "feature",
208
+ "status": "completed",
209
+ "conclusion": "failure",
210
+ }
211
+ new = {**old, "id": 11, "run_number": 2, "conclusion": "success", **changes}
212
+ # Deliberately unsorted: correctness must not depend on API response order.
213
+ run.gh = lambda *args: {"workflow_runs": [old, new], "total_count": 2}
214
+ assert run.ci_passed("sha") is expected
215
+
216
+
217
+ def test_native_actions_rerun_keeps_its_run_identity(tmp_path, monkeypatch):
218
+ module = worker("github-delivery-watchdog", tmp_path, monkeypatch)
219
+ run = object.__new__(module.DeliveryWatchdog)
220
+ run.config = {"required_workflow_ids": [1]}
221
+ result = {
222
+ "id": 10,
223
+ "run_number": 1,
224
+ "run_attempt": 2,
225
+ "workflow_id": 1,
226
+ "head_sha": "sha",
227
+ "event": "pull_request",
228
+ "head_branch": "feature",
229
+ "status": "completed",
230
+ "conclusion": "success",
231
+ }
232
+ run.gh = lambda *args: {"workflow_runs": [result], "total_count": 1}
233
+ assert run.ci_passed("sha")