@openhands/extensions 0.20.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 (305) hide show
  1. package/.claude-plugin/marketplace.json +881 -0
  2. package/.codex-plugin/marketplace.json +881 -0
  3. package/.github/workflows/check-extensions.yml +4 -0
  4. package/.github/workflows/npm-publish.yml +9 -3
  5. package/.github/workflows/pr-artifacts.yml +294 -0
  6. package/.github/workflows/pr-description-check.yml +4 -2
  7. package/.plugin/marketplace.json +881 -0
  8. package/.release-please-manifest.json +1 -1
  9. package/AGENTS.md +27 -3
  10. package/README.md +6 -2
  11. package/automations/bundle-index.js +22 -3
  12. package/automations/catalog/github-agents-md-maintainer/manifest.json +2 -1
  13. package/automations/catalog/github-delivery-watchdog/manifest.json +79 -0
  14. package/automations/catalog/github-issue-to-pr/manifest.json +24 -15
  15. package/automations/catalog/github-issue-triage/manifest.json +73 -0
  16. package/automations/catalog/github-pr-reviewer/manifest.json +23 -15
  17. package/automations/catalog/gitlab-issue-to-mr/manifest.json +124 -0
  18. package/automations/catalog-index.js +17 -11
  19. package/integrations/catalog/reportportal.json +76 -0
  20. package/integrations/catalog-index.js +6 -4
  21. package/marketplaces/openhands-extensions.json +42 -0
  22. package/package.json +1 -1
  23. package/plugins/city-weather/.claude-plugin/plugin.json +13 -0
  24. package/plugins/city-weather/.codex-plugin/plugin.json +13 -0
  25. package/plugins/cobol-modernization/.claude-plugin/plugin.json +19 -0
  26. package/plugins/cobol-modernization/.codex-plugin/plugin.json +19 -0
  27. package/plugins/issue-duplicate-checker/.claude-plugin/plugin.json +13 -0
  28. package/plugins/issue-duplicate-checker/.codex-plugin/plugin.json +13 -0
  29. package/plugins/magic-test/.claude-plugin/plugin.json +13 -0
  30. package/plugins/magic-test/.codex-plugin/plugin.json +13 -0
  31. package/plugins/migration-scoring/.claude-plugin/plugin.json +19 -0
  32. package/plugins/migration-scoring/.codex-plugin/plugin.json +19 -0
  33. package/plugins/onboarding/.claude-plugin/plugin.json +20 -0
  34. package/plugins/onboarding/.codex-plugin/plugin.json +20 -0
  35. package/plugins/onboarding/skills/agent-readiness-report/references/criteria.md +144 -0
  36. package/plugins/onboarding/skills/improve-agent-readiness/references/criteria.md +144 -0
  37. package/plugins/openhands/.claude-plugin/plugin.json +13 -0
  38. package/plugins/openhands/.codex-plugin/plugin.json +13 -0
  39. package/plugins/openhands/skills/openhands-api/.claude-plugin/plugin.json +22 -0
  40. package/plugins/openhands/skills/openhands-api/.codex-plugin/plugin.json +22 -0
  41. package/plugins/openhands/skills/openhands-api/.plugin/plugin.json +22 -0
  42. package/plugins/openhands/skills/openhands-api/README.md +52 -0
  43. package/plugins/openhands/skills/openhands-api/SKILL.md +469 -0
  44. package/plugins/openhands/skills/openhands-api/references/README.md +33 -0
  45. package/plugins/openhands/skills/openhands-api/references/TROUBLESHOOTING.md +81 -0
  46. package/plugins/openhands/skills/openhands-api/references/example_prompt.md +12 -0
  47. package/plugins/openhands/skills/openhands-api/references/windows.md +44 -0
  48. package/plugins/openhands/skills/openhands-api/scripts/openhands_api.py +606 -0
  49. package/plugins/openhands/skills/openhands-api/scripts/openhands_api.ts +252 -0
  50. package/plugins/openhands/skills/openhands-automation/.claude-plugin/plugin.json +19 -0
  51. package/plugins/openhands/skills/openhands-automation/.codex-plugin/plugin.json +19 -0
  52. package/plugins/openhands/skills/openhands-automation/.plugin/plugin.json +19 -0
  53. package/plugins/openhands/skills/openhands-automation/README.md +89 -0
  54. package/plugins/openhands/skills/openhands-automation/SKILL.md +899 -0
  55. package/plugins/openhands/skills/openhands-automation/commands/automation-create.md +8 -0
  56. package/plugins/openhands/skills/openhands-automation/references/ab-testing.md +185 -0
  57. package/plugins/openhands/skills/openhands-automation/references/custom-automation.md +832 -0
  58. package/plugins/openhands/skills/openhands-automation/references/security.md +84 -0
  59. package/plugins/openhands/skills/openhands-automation/references/windows.md +57 -0
  60. package/plugins/openhands/skills/openhands-sdk/.claude-plugin/plugin.json +20 -0
  61. package/plugins/openhands/skills/openhands-sdk/.codex-plugin/plugin.json +20 -0
  62. package/plugins/openhands/skills/openhands-sdk/.plugin/plugin.json +20 -0
  63. package/plugins/openhands/skills/openhands-sdk/README.md +22 -0
  64. package/plugins/openhands/skills/openhands-sdk/SKILL.md +245 -0
  65. package/plugins/openhands/skills/openhands-sdk/commands/sdk.md +8 -0
  66. package/plugins/pr-review/.claude-plugin/plugin.json +13 -0
  67. package/plugins/pr-review/.codex-plugin/plugin.json +13 -0
  68. package/plugins/pr-review/skills/code-review/.claude-plugin/plugin.json +19 -0
  69. package/plugins/pr-review/skills/code-review/.codex-plugin/plugin.json +19 -0
  70. package/plugins/pr-review/skills/code-review/.plugin/plugin.json +19 -0
  71. package/plugins/pr-review/skills/code-review/README.md +18 -0
  72. package/plugins/pr-review/skills/code-review/SKILL.md +209 -0
  73. package/plugins/pr-review/skills/code-review/commands/codereview-roasted.md +8 -0
  74. package/plugins/pr-review/skills/code-review/commands/codereview.md +8 -0
  75. package/plugins/pr-review/skills/code-review/references/risk-evaluation.md +41 -0
  76. package/plugins/pr-review/skills/code-review/references/supply-chain-security.md +31 -0
  77. package/plugins/pr-review/skills/github-pr-review/.claude-plugin/plugin.json +18 -0
  78. package/plugins/pr-review/skills/github-pr-review/.codex-plugin/plugin.json +18 -0
  79. package/plugins/pr-review/skills/github-pr-review/.plugin/plugin.json +18 -0
  80. package/plugins/pr-review/skills/github-pr-review/README.md +145 -0
  81. package/plugins/pr-review/skills/github-pr-review/SKILL.md +187 -0
  82. package/plugins/pr-review/skills/github-pr-review/commands/github-pr-review.md +8 -0
  83. package/plugins/pr-review/skills/github-pr-review/references/windows.md +47 -0
  84. package/plugins/qa-changes/.claude-plugin/plugin.json +11 -0
  85. package/plugins/qa-changes/.codex-plugin/plugin.json +11 -0
  86. package/plugins/qa-changes/skills/github-pr-review/.claude-plugin/plugin.json +18 -0
  87. package/plugins/qa-changes/skills/github-pr-review/.codex-plugin/plugin.json +18 -0
  88. package/plugins/qa-changes/skills/github-pr-review/.plugin/plugin.json +18 -0
  89. package/plugins/qa-changes/skills/github-pr-review/README.md +145 -0
  90. package/plugins/qa-changes/skills/github-pr-review/SKILL.md +187 -0
  91. package/plugins/qa-changes/skills/github-pr-review/commands/github-pr-review.md +8 -0
  92. package/plugins/qa-changes/skills/github-pr-review/references/windows.md +47 -0
  93. package/plugins/qa-changes/skills/qa-changes/README.md +18 -0
  94. package/plugins/qa-changes/skills/qa-changes/SKILL.md +229 -0
  95. package/plugins/qa-changes/skills/qa-changes/commands/qa-changes.md +8 -0
  96. package/plugins/release-notes/.claude-plugin/plugin.json +19 -0
  97. package/plugins/release-notes/.codex-plugin/plugin.json +19 -0
  98. package/plugins/release-notes/skills/release-notes/README.md +24 -0
  99. package/plugins/release-notes/skills/release-notes/SKILL.md +19 -0
  100. package/plugins/release-notes/skills/release-notes/commands/release-notes.md +8 -0
  101. package/plugins/vulnerability-remediation/.claude-plugin/plugin.json +19 -0
  102. package/plugins/vulnerability-remediation/.codex-plugin/plugin.json +19 -0
  103. package/plugins/vulnerability-remediation/skills/github/.claude-plugin/plugin.json +19 -0
  104. package/plugins/vulnerability-remediation/skills/github/.codex-plugin/plugin.json +19 -0
  105. package/plugins/vulnerability-remediation/skills/github/.plugin/plugin.json +19 -0
  106. package/plugins/vulnerability-remediation/skills/github/README.md +42 -0
  107. package/plugins/vulnerability-remediation/skills/github/SKILL.md +107 -0
  108. package/plugins/vulnerability-remediation/skills/github/references/windows.md +48 -0
  109. package/plugins/vulnerability-remediation/skills/github/scripts/agent_conversation.py +143 -0
  110. package/plugins/vulnerability-remediation/skills/github/scripts/github_client.py +230 -0
  111. package/plugins/vulnerability-remediation/skills/security/.claude-plugin/plugin.json +18 -0
  112. package/plugins/vulnerability-remediation/skills/security/.codex-plugin/plugin.json +18 -0
  113. package/plugins/vulnerability-remediation/skills/security/.plugin/plugin.json +18 -0
  114. package/plugins/vulnerability-remediation/skills/security/README.md +38 -0
  115. package/plugins/vulnerability-remediation/skills/security/SKILL.md +33 -0
  116. package/pyproject.toml +2 -2
  117. package/python/openhands_extensions/_version.py +1 -1
  118. package/skills/add-javadoc/.claude-plugin/plugin.json +18 -0
  119. package/skills/add-javadoc/.codex-plugin/plugin.json +18 -0
  120. package/skills/add-skill/.claude-plugin/plugin.json +18 -0
  121. package/skills/add-skill/.codex-plugin/plugin.json +18 -0
  122. package/skills/agent-canvas-environment/.claude-plugin/plugin.json +19 -0
  123. package/skills/agent-canvas-environment/.codex-plugin/plugin.json +19 -0
  124. package/skills/agent-creator/.claude-plugin/plugin.json +20 -0
  125. package/skills/agent-creator/.codex-plugin/plugin.json +20 -0
  126. package/skills/agent-memory/.claude-plugin/plugin.json +18 -0
  127. package/skills/agent-memory/.codex-plugin/plugin.json +18 -0
  128. package/skills/agent-sdk-builder/.claude-plugin/plugin.json +18 -0
  129. package/skills/agent-sdk-builder/.codex-plugin/plugin.json +18 -0
  130. package/skills/azure-devops/.claude-plugin/plugin.json +18 -0
  131. package/skills/azure-devops/.codex-plugin/plugin.json +18 -0
  132. package/skills/bitbucket/.claude-plugin/plugin.json +17 -0
  133. package/skills/bitbucket/.codex-plugin/plugin.json +17 -0
  134. package/skills/bitbucket-cloud/.claude-plugin/plugin.json +18 -0
  135. package/skills/bitbucket-cloud/.codex-plugin/plugin.json +18 -0
  136. package/skills/bitbucket-data-center/.claude-plugin/plugin.json +19 -0
  137. package/skills/bitbucket-data-center/.codex-plugin/plugin.json +19 -0
  138. package/skills/code-review/.claude-plugin/plugin.json +19 -0
  139. package/skills/code-review/.codex-plugin/plugin.json +19 -0
  140. package/skills/code-simplifier/.claude-plugin/plugin.json +21 -0
  141. package/skills/code-simplifier/.codex-plugin/plugin.json +21 -0
  142. package/skills/datadog/.claude-plugin/plugin.json +19 -0
  143. package/skills/datadog/.codex-plugin/plugin.json +19 -0
  144. package/skills/deno/.claude-plugin/plugin.json +18 -0
  145. package/skills/deno/.codex-plugin/plugin.json +18 -0
  146. package/skills/discord/.claude-plugin/plugin.json +18 -0
  147. package/skills/discord/.codex-plugin/plugin.json +18 -0
  148. package/skills/docker/.claude-plugin/plugin.json +17 -0
  149. package/skills/docker/.codex-plugin/plugin.json +17 -0
  150. package/skills/evidence-based-citations/.claude-plugin/plugin.json +20 -0
  151. package/skills/evidence-based-citations/.codex-plugin/plugin.json +20 -0
  152. package/skills/flarglebargle/.claude-plugin/plugin.json +16 -0
  153. package/skills/flarglebargle/.codex-plugin/plugin.json +16 -0
  154. package/skills/frontend-design/.claude-plugin/plugin.json +21 -0
  155. package/skills/frontend-design/.codex-plugin/plugin.json +21 -0
  156. package/skills/github/.claude-plugin/plugin.json +19 -0
  157. package/skills/github/.codex-plugin/plugin.json +19 -0
  158. package/skills/github/scripts/agent_conversation.py +143 -0
  159. package/skills/github/scripts/github_client.py +230 -0
  160. package/skills/github-actions/.claude-plugin/plugin.json +19 -0
  161. package/skills/github-actions/.codex-plugin/plugin.json +19 -0
  162. package/skills/github-agents-md-maintainer/.claude-plugin/plugin.json +19 -0
  163. package/skills/github-agents-md-maintainer/.codex-plugin/plugin.json +19 -0
  164. package/skills/github-agents-md-maintainer/SKILL.md +7 -1
  165. package/skills/github-agents-md-maintainer/scripts/github_client.py +230 -0
  166. package/skills/github-agents-md-maintainer/scripts/main.py +4 -40
  167. package/skills/github-delivery-watchdog/.claude-plugin/plugin.json +9 -0
  168. package/skills/github-delivery-watchdog/.codex-plugin/plugin.json +9 -0
  169. package/skills/github-delivery-watchdog/.plugin/plugin.json +9 -0
  170. package/skills/github-delivery-watchdog/README.md +10 -0
  171. package/skills/github-delivery-watchdog/SKILL.md +35 -0
  172. package/skills/github-delivery-watchdog/commands/github-delivery-watchdog.md +8 -0
  173. package/skills/github-delivery-watchdog/scripts/github_client.py +230 -0
  174. package/skills/github-delivery-watchdog/scripts/worker.py +96 -0
  175. package/skills/github-issue-to-pr/.claude-plugin/plugin.json +19 -0
  176. package/skills/github-issue-to-pr/.codex-plugin/plugin.json +19 -0
  177. package/skills/github-issue-to-pr/SKILL.md +15 -1
  178. package/skills/github-issue-to-pr/scripts/agent_conversation.py +143 -0
  179. package/skills/github-issue-to-pr/scripts/github_client.py +230 -0
  180. package/skills/github-issue-to-pr/scripts/main.py +19 -50
  181. package/skills/github-issue-to-pr/scripts/worker.py +172 -0
  182. package/skills/github-issue-triage/.claude-plugin/plugin.json +9 -0
  183. package/skills/github-issue-triage/.codex-plugin/plugin.json +9 -0
  184. package/skills/github-issue-triage/.plugin/plugin.json +9 -0
  185. package/skills/github-issue-triage/README.md +9 -0
  186. package/skills/github-issue-triage/SKILL.md +34 -0
  187. package/skills/github-issue-triage/commands/github-issue-triage.md +8 -0
  188. package/skills/github-issue-triage/scripts/agent_conversation.py +143 -0
  189. package/skills/github-issue-triage/scripts/github_client.py +230 -0
  190. package/skills/github-issue-triage/scripts/worker.py +106 -0
  191. package/skills/github-pr-review/.claude-plugin/plugin.json +18 -0
  192. package/skills/github-pr-review/.codex-plugin/plugin.json +18 -0
  193. package/skills/github-pr-reviewer/.claude-plugin/plugin.json +20 -0
  194. package/skills/github-pr-reviewer/.codex-plugin/plugin.json +20 -0
  195. package/skills/github-pr-reviewer/SKILL.md +15 -1
  196. package/skills/github-pr-reviewer/scripts/agent_conversation.py +143 -0
  197. package/skills/github-pr-reviewer/scripts/github_client.py +230 -0
  198. package/skills/github-pr-reviewer/scripts/main.py +23 -49
  199. package/skills/github-pr-reviewer/scripts/worker.py +94 -0
  200. package/skills/github-repo-monitor/.claude-plugin/plugin.json +22 -0
  201. package/skills/github-repo-monitor/.codex-plugin/plugin.json +22 -0
  202. package/skills/github-repo-monitor/SKILL.md +7 -1
  203. package/skills/github-repo-monitor/scripts/github_client.py +230 -0
  204. package/skills/github-repo-monitor/scripts/main.py +4 -46
  205. package/skills/gitlab/.claude-plugin/plugin.json +17 -0
  206. package/skills/gitlab/.codex-plugin/plugin.json +17 -0
  207. package/skills/gitlab-issue-to-mr/.claude-plugin/plugin.json +19 -0
  208. package/skills/gitlab-issue-to-mr/.codex-plugin/plugin.json +19 -0
  209. package/skills/gitlab-issue-to-mr/.plugin/plugin.json +19 -0
  210. package/skills/gitlab-issue-to-mr/README.md +92 -0
  211. package/skills/gitlab-issue-to-mr/SKILL.md +408 -0
  212. package/skills/gitlab-issue-to-mr/commands/issue-to-mr-setup.md +8 -0
  213. package/skills/gitlab-issue-to-mr/references/state-schema.md +140 -0
  214. package/skills/gitlab-issue-to-mr/scripts/main.py +1448 -0
  215. package/skills/incident-retrospective/.claude-plugin/plugin.json +21 -0
  216. package/skills/incident-retrospective/.codex-plugin/plugin.json +21 -0
  217. package/skills/index.js +52 -6
  218. package/skills/iterate/.claude-plugin/plugin.json +13 -0
  219. package/skills/iterate/.codex-plugin/plugin.json +13 -0
  220. package/skills/jira-issue-to-pr/.claude-plugin/plugin.json +19 -0
  221. package/skills/jira-issue-to-pr/.codex-plugin/plugin.json +19 -0
  222. package/skills/jupyter/.claude-plugin/plugin.json +18 -0
  223. package/skills/jupyter/.codex-plugin/plugin.json +18 -0
  224. package/skills/kubernetes/.claude-plugin/plugin.json +18 -0
  225. package/skills/kubernetes/.codex-plugin/plugin.json +18 -0
  226. package/skills/learn-from-code-review/.claude-plugin/plugin.json +19 -0
  227. package/skills/learn-from-code-review/.codex-plugin/plugin.json +19 -0
  228. package/skills/linear/.claude-plugin/plugin.json +19 -0
  229. package/skills/linear/.codex-plugin/plugin.json +19 -0
  230. package/skills/linear-triage/.claude-plugin/plugin.json +21 -0
  231. package/skills/linear-triage/.codex-plugin/plugin.json +21 -0
  232. package/skills/news-digest/.claude-plugin/plugin.json +19 -0
  233. package/skills/news-digest/.codex-plugin/plugin.json +19 -0
  234. package/skills/notion/.claude-plugin/plugin.json +17 -0
  235. package/skills/notion/.codex-plugin/plugin.json +17 -0
  236. package/skills/npm/.claude-plugin/plugin.json +17 -0
  237. package/skills/npm/.codex-plugin/plugin.json +17 -0
  238. package/skills/openhands-api/.claude-plugin/plugin.json +22 -0
  239. package/skills/openhands-api/.codex-plugin/plugin.json +22 -0
  240. package/skills/openhands-automation/.claude-plugin/plugin.json +19 -0
  241. package/skills/openhands-automation/.codex-plugin/plugin.json +19 -0
  242. package/skills/openhands-automation/README.md +1 -1
  243. package/skills/openhands-automation/SKILL.md +18 -8
  244. package/skills/openhands-enterprise-troubleshooting/.claude-plugin/plugin.json +20 -0
  245. package/skills/openhands-enterprise-troubleshooting/.codex-plugin/plugin.json +20 -0
  246. package/skills/openhands-enterprise-troubleshooting/.plugin/plugin.json +20 -0
  247. package/skills/openhands-enterprise-troubleshooting/README.md +79 -0
  248. package/skills/openhands-enterprise-troubleshooting/SKILL.md +339 -0
  249. package/skills/openhands-enterprise-troubleshooting/references/diagnostics.md +698 -0
  250. package/skills/openhands-enterprise-troubleshooting/references/support-bundle-analysis.md +570 -0
  251. package/skills/openhands-enterprise-troubleshooting/scripts/bundle_triage.py +532 -0
  252. package/skills/openhands-sdk/.claude-plugin/plugin.json +20 -0
  253. package/skills/openhands-sdk/.codex-plugin/plugin.json +20 -0
  254. package/skills/openhands-sdk/SKILL.md +2 -0
  255. package/skills/pdflatex/.claude-plugin/plugin.json +18 -0
  256. package/skills/pdflatex/.codex-plugin/plugin.json +18 -0
  257. package/skills/plain-english-content/.claude-plugin/plugin.json +15 -0
  258. package/skills/plain-english-content/.codex-plugin/plugin.json +15 -0
  259. package/skills/prd/.claude-plugin/plugin.json +19 -0
  260. package/skills/prd/.codex-plugin/plugin.json +19 -0
  261. package/skills/research-brief/.claude-plugin/plugin.json +20 -0
  262. package/skills/research-brief/.codex-plugin/plugin.json +20 -0
  263. package/skills/security/.claude-plugin/plugin.json +18 -0
  264. package/skills/security/.codex-plugin/plugin.json +18 -0
  265. package/skills/skill-creator/.claude-plugin/plugin.json +17 -0
  266. package/skills/skill-creator/.codex-plugin/plugin.json +17 -0
  267. package/skills/slack-channel-monitor/.claude-plugin/plugin.json +21 -0
  268. package/skills/slack-channel-monitor/.codex-plugin/plugin.json +21 -0
  269. package/skills/slack-standup-digest/.claude-plugin/plugin.json +21 -0
  270. package/skills/slack-standup-digest/.codex-plugin/plugin.json +21 -0
  271. package/skills/spark-version-upgrade/.claude-plugin/plugin.json +20 -0
  272. package/skills/spark-version-upgrade/.codex-plugin/plugin.json +20 -0
  273. package/skills/ssh/.claude-plugin/plugin.json +18 -0
  274. package/skills/ssh/.codex-plugin/plugin.json +18 -0
  275. package/skills/swift-linux/.claude-plugin/plugin.json +17 -0
  276. package/skills/swift-linux/.codex-plugin/plugin.json +17 -0
  277. package/skills/technical-writing/.claude-plugin/plugin.json +15 -0
  278. package/skills/technical-writing/.codex-plugin/plugin.json +15 -0
  279. package/skills/theme-factory/.claude-plugin/plugin.json +19 -0
  280. package/skills/theme-factory/.codex-plugin/plugin.json +19 -0
  281. package/skills/ticket-to-code-change/.claude-plugin/plugin.json +21 -0
  282. package/skills/ticket-to-code-change/.codex-plugin/plugin.json +21 -0
  283. package/skills/upstream-fork-sync/.claude-plugin/plugin.json +21 -0
  284. package/skills/upstream-fork-sync/.codex-plugin/plugin.json +21 -0
  285. package/skills/uv/.claude-plugin/plugin.json +18 -0
  286. package/skills/uv/.codex-plugin/plugin.json +18 -0
  287. package/skills/vercel/.claude-plugin/plugin.json +18 -0
  288. package/skills/vercel/.codex-plugin/plugin.json +18 -0
  289. package/tests/fixtures/automations/capabilities.json +6 -3
  290. package/tests/fixtures/automations/github-pr-reviewer.json +64 -43
  291. package/tests/github_automation_helpers.py +31 -0
  292. package/tests/test_agent_conversation_dispatch.py +180 -0
  293. package/tests/test_automation_setup.py +3 -3
  294. package/tests/test_github_automation_foundation.py +73 -0
  295. package/tests/test_github_developer_delivery.py +150 -0
  296. package/tests/test_github_reviewer_delivery.py +110 -0
  297. package/tests/test_github_skill_installation.py +63 -0
  298. package/tests/test_github_triage_delivery.py +90 -0
  299. package/tests/test_github_watchdog_delivery.py +233 -0
  300. package/tests/test_gitlab_issue_to_mr.py +823 -0
  301. package/.pr/custom-automation-event-trigger.png +0 -0
  302. package/.pr/custom-automation-plugin-fields.png +0 -0
  303. package/.pr/custom-automation-prompt-review.png +0 -0
  304. package/.pr/custom-automation-prompt-scheduled.png +0 -0
  305. package/.pr/custom-automation-upload-fields.png +0 -0
@@ -0,0 +1,230 @@
1
+ """Shared GitHub transport and repository operations for GitHub automations."""
2
+
3
+ import argparse
4
+ import json
5
+ import os
6
+ import re
7
+ import subprocess
8
+ from functools import cached_property
9
+ from pathlib import Path
10
+ from urllib.error import HTTPError
11
+ from urllib.parse import parse_qsl, urlencode, urlsplit
12
+ from urllib.request import Request, urlopen
13
+
14
+
15
+ def _load_secret(name: str) -> str:
16
+ """Read one named secret from the environment or configured Agent Server."""
17
+ value = os.environ.get(name)
18
+ if value:
19
+ return value
20
+
21
+ from openhands.sdk.workspace import RemoteWorkspace
22
+
23
+ workspace = RemoteWorkspace(
24
+ host=os.environ["AGENT_SERVER_URL"],
25
+ api_key=os.environ["SESSION_API_KEY"],
26
+ working_dir=os.environ.get("WORKSPACE_BASE", "/workspace"),
27
+ )
28
+ try:
29
+ secret = workspace.get_secrets([name]).get(name)
30
+ value = secret.get_value() if secret else None
31
+ finally:
32
+ workspace.reset_client()
33
+ if not value:
34
+ raise ValueError(f"The GitHub credential {name} is unavailable")
35
+ return value
36
+
37
+
38
+ def github_request(
39
+ token: str,
40
+ method: str,
41
+ path: str,
42
+ params: dict | None = None,
43
+ body: dict | None = None,
44
+ accept: str = "application/vnd.github+json",
45
+ ) -> tuple:
46
+ url = f"https://api.github.com{path}"
47
+ if params:
48
+ url = f"{url}?{urlencode(params)}"
49
+ headers = {
50
+ "Authorization": f"Bearer {token}",
51
+ "Accept": accept,
52
+ "X-GitHub-Api-Version": "2022-11-28",
53
+ "Content-Type": "application/json",
54
+ }
55
+ data = json.dumps(body).encode() if body is not None else None
56
+ req = Request(url, data=data, headers=headers, method=method)
57
+ with urlopen(req, timeout=90) as r:
58
+ raw = r.read()
59
+ return (json.loads(raw) if raw.strip() else {}), dict(r.headers)
60
+
61
+
62
+ def github_paginate(token: str, path: str, params: dict | None = None) -> list:
63
+ results = []
64
+ base_params = dict(params or {})
65
+ base_params.setdefault("per_page", 100)
66
+ for page in range(1, 101):
67
+ base_params["page"] = page
68
+ data, _ = github_request(token, "GET", path, params=base_params)
69
+ if not isinstance(data, list):
70
+ raise TypeError("Expected a paginated GitHub list")
71
+ results.extend(data)
72
+ if len(data) < int(base_params["per_page"]):
73
+ return results
74
+ raise RuntimeError("GitHub pagination exceeded limit")
75
+
76
+
77
+ class GitHubRepository:
78
+ name = "GitHub automation"
79
+
80
+ def __init__(
81
+ self,
82
+ config_path=Path("config.json"),
83
+ *,
84
+ github_token_secret,
85
+ repository=None,
86
+ conversation=None,
87
+ dispatcher=None,
88
+ ):
89
+ self.config = json.loads(Path(config_path).read_text())
90
+ self.repository = repository or self.config["repository"]
91
+ if not re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", self.repository):
92
+ raise ValueError("repository must be owner/repo")
93
+ if not re.fullmatch(r"[A-Z_][A-Z0-9_]*", github_token_secret):
94
+ raise ValueError(
95
+ "Expected the environment variable containing the GitHub token"
96
+ )
97
+ self.token_name = github_token_secret
98
+ self.token = _load_secret(github_token_secret)
99
+ self.conversation = conversation
100
+ self.conversation_id = str(conversation.id) if conversation else None
101
+ self.dispatcher = dispatcher
102
+ self.workspace = Path(os.environ["WORKSPACE_BASE"])
103
+ self.project = self.workspace
104
+ self.evidence = self.workspace / "evidence"
105
+ self.evidence.mkdir(exist_ok=True)
106
+ self._completed_dependencies = {}
107
+
108
+ @cached_property
109
+ def base_branch(self):
110
+ return self.config.get("base_branch") or self.gh("GET", "")["default_branch"]
111
+
112
+ @property
113
+ def github_instructions(self):
114
+ return (
115
+ f"Use `GH_TOKEN=${self.token_name} gh api` for GitHub requests. "
116
+ "Never print the credential value. "
117
+ f"Only {self.repository} is in scope. Work in {self.project}. "
118
+ "Do not modify the automation bundle or its configuration."
119
+ )
120
+
121
+ def gh(self, method, path, body=None):
122
+ return github_request(
123
+ self.token, method, f"/repos/{self.repository}" + path, body=body
124
+ )[0]
125
+
126
+ def shell(self, args, cwd=None, timeout=300):
127
+ result = subprocess.run(
128
+ args,
129
+ cwd=cwd or self.project,
130
+ text=True,
131
+ stdout=subprocess.PIPE,
132
+ stderr=subprocess.STDOUT,
133
+ timeout=timeout,
134
+ check=False,
135
+ )
136
+ if result.returncode:
137
+ raise RuntimeError(
138
+ f"{args[0]} failed: {result.stdout[-4000:].replace(self.token, '[REDACTED]')}"
139
+ )
140
+ return result.stdout.strip()
141
+
142
+ def comment(self, number, text):
143
+ return self.gh(
144
+ "POST",
145
+ f"/issues/{number}/comments",
146
+ {
147
+ "body": text
148
+ + f"\n\nFactory role: `{self.name}`; conversation: `{self.conversation_id}`."
149
+ + "\n\n_This comment was posted by an AI agent (OpenHands)._"
150
+ },
151
+ )
152
+
153
+ def open_issues(self):
154
+ return [
155
+ i for i in self.gh_pages("/issues?state=open") if "pull_request" not in i
156
+ ]
157
+
158
+ def statuses(self, sha):
159
+ result = {}
160
+ for item in self.gh_pages(f"/commits/{sha}/statuses"):
161
+ result.setdefault(item["context"], item["state"])
162
+ return result
163
+
164
+ def completed_dependency(self, number):
165
+ if number in self._completed_dependencies:
166
+ return self._completed_dependencies[number]
167
+ try:
168
+ dependency = self.gh("GET", f"/issues/{number}")
169
+ except HTTPError as exc:
170
+ if exc.code == 404:
171
+ return False
172
+ raise
173
+ completed = (
174
+ dependency["state"] == "closed"
175
+ and dependency.get("state_reason") == "completed"
176
+ )
177
+ self._completed_dependencies[number] = completed
178
+ return completed
179
+
180
+ def dependencies_complete(self, issue):
181
+ """Honor explicit Depends on lines; unknown/incomplete issues remain blocked."""
182
+ for line in re.findall(
183
+ "^Depends on:\\s*(.+)$",
184
+ issue.get("body") or "",
185
+ re.MULTILINE | re.IGNORECASE,
186
+ ):
187
+ for number in re.findall("#(\\d+)", line):
188
+ if not self.completed_dependency(number):
189
+ return False
190
+ return True
191
+
192
+ def gh_pages(self, endpoint):
193
+ split = urlsplit(endpoint)
194
+ return github_paginate(
195
+ self.token,
196
+ f"/repos/{self.repository}" + split.path,
197
+ params=dict(parse_qsl(split.query)),
198
+ )
199
+
200
+
201
+ def run_repositories(automation_type, conversation=None, dispatcher=None):
202
+ parser = argparse.ArgumentParser(description=automation_type.__doc__)
203
+ parser.add_argument("--github-token-secret")
204
+ args = parser.parse_args()
205
+ config = json.loads(Path("config.json").read_text())
206
+ token_name = args.github_token_secret or config.get(
207
+ "github_token_secret", "GITHUB_PERSONAL_ACCESS_TOKEN"
208
+ )
209
+ repositories = config.get("repos") or [config["repository"]]
210
+ failures = []
211
+ for repository in repositories:
212
+ options = dict(
213
+ github_token_secret=token_name,
214
+ repository=repository,
215
+ conversation=conversation,
216
+ )
217
+ if dispatcher is not None:
218
+ options["dispatcher"] = dispatcher
219
+ automation = automation_type(**options)
220
+ try:
221
+ automation.run()
222
+ except Exception as exc: # noqa: BLE001 - one repository must not block others
223
+ failures.append(repository)
224
+ print(
225
+ json.dumps({"repository": repository, "error": type(exc).__name__}),
226
+ flush=True,
227
+ )
228
+ if failures:
229
+ raise RuntimeError("Automation failed for: " + ", ".join(failures))
230
+ return str(conversation.id) if conversation else None
@@ -30,7 +30,10 @@ import urllib.error
30
30
  import urllib.request
31
31
  from collections.abc import Callable
32
32
  from pathlib import Path
33
- from urllib.parse import urlencode
33
+
34
+ from github_client import github_request as _github_request
35
+ from github_client import github_paginate as _github_paginate
36
+
34
37
 
35
38
  # Configuration. Two setup paths write it, and both end up here:
36
39
  #
@@ -341,45 +344,6 @@ def save_state(repo: str, state: dict) -> None:
341
344
  # ── GitHub REST ───────────────────────────────────────────────────────────────
342
345
 
343
346
 
344
- def _github_request(
345
- token: str,
346
- method: str,
347
- path: str,
348
- params: dict | None = None,
349
- body: dict | None = None,
350
- ) -> tuple:
351
- url = f"https://api.github.com{path}"
352
- if params:
353
- url = f"{url}?{urlencode(params)}"
354
- headers = {
355
- "Authorization": f"Bearer {token}",
356
- "Accept": "application/vnd.github+json",
357
- "X-GitHub-Api-Version": "2022-11-28",
358
- "Content-Type": "application/json",
359
- }
360
- data = json.dumps(body).encode() if body is not None else None
361
- req = urllib.request.Request(url, data=data, headers=headers, method=method)
362
- with urllib.request.urlopen(req) as r:
363
- raw = r.read()
364
- return (json.loads(raw) if raw.strip() else {}), dict(r.headers)
365
-
366
-
367
- def _github_paginate(token: str, path: str, params: dict | None = None) -> list:
368
- results = []
369
- page = 1
370
- base_params = dict(params or {})
371
- base_params.setdefault("per_page", 100)
372
- while True:
373
- base_params["page"] = page
374
- data, _ = _github_request(token, "GET", path, params=base_params)
375
- if not isinstance(data, list):
376
- break
377
- results.extend(data)
378
- if len(data) < base_params["per_page"]:
379
- break
380
- page += 1
381
- return results
382
-
383
347
 
384
348
  def _resolve_github_token() -> str:
385
349
  try:
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "github-delivery-watchdog",
3
+ "version": "1.0.0",
4
+ "description": "Periodically check pull requests and merge only current heads with independent review, tests, and passing CI.",
5
+ "author": {
6
+ "name": "OpenHands"
7
+ },
8
+ "license": "MIT"
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "github-delivery-watchdog",
3
+ "version": "1.0.0",
4
+ "description": "Periodically check pull requests and merge only current heads with independent review, tests, and passing CI.",
5
+ "author": {
6
+ "name": "OpenHands"
7
+ },
8
+ "license": "MIT"
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "github-delivery-watchdog",
3
+ "version": "1.0.0",
4
+ "description": "Periodically check pull requests and merge only current heads with independent review, tests, and passing CI.",
5
+ "author": {
6
+ "name": "OpenHands"
7
+ },
8
+ "license": "MIT"
9
+ }
@@ -0,0 +1,10 @@
1
+ # GitHub delivery watchdog
2
+
3
+ Periodically check pull requests and merge only current heads with independent review, tests, and passing CI.
4
+
5
+ Run this independently from issue triage, implementation, and review. Configure
6
+ a repository-scoped fine-grained PAT by its saved secret name. The watchdog uses
7
+ Actions and commit statuses and sends a conditional merge request for the exact
8
+ accepted head. It does not create an agent or change application code.
9
+
10
+ See [SKILL.md](SKILL.md) for permissions, acceptance gates, and configuration.
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: github-delivery-watchdog
3
+ description: Periodically check pull requests and merge only current heads with independent review, tests, and passing CI.
4
+ triggers:
5
+ - /github-delivery-watchdog
6
+ ---
7
+
8
+ # GitHub delivery watchdog
9
+
10
+ This is a deterministic scheduled host command. It creates no agent or
11
+ conversation and needs no agent profile. Configure a repository-scoped
12
+ fine-grained PAT with Contents and Issues read/write plus Pull requests, Actions,
13
+ Commit statuses, and Metadata read. Contents write permits merge; Issues write
14
+ retains the review label when the branch is updated. Never put the token value in
15
+ the automation definition.
16
+
17
+ Package `scripts/worker.py` as `worker.py` and the shared
18
+ `scripts/github_client.py` as `github_client.py`.
19
+ The catalog bundle declares these exact files. Its `config.json` supplies
20
+ `repos`, `branch_prefix`, and the saved secret name. The shared GitHub client
21
+ resolves only that named secret. Automation owns scheduling and cancellation.
22
+
23
+ Set `branch_prefix` (default `openhands/issue`), `base_branch` (defaults to the repository's default branch),
24
+ and `required_workflow_ids` when particular Actions workflows must run. The
25
+ watchdog requires `software-factory/tests` and `software-factory/review` success
26
+ statuses on the exact head, all other statuses and Actions passing, a current
27
+ base, a non-draft PR, and GitHub reporting it mergeable. Missing, pending, failed,
28
+ or inaccessible evidence does not permit merge. A changed head requires fresh
29
+ review and tests. When an accepted branch is behind the base, the watchdog asks
30
+ GitHub to update it and retains the review trigger label; it considers the new
31
+ head only on a later run. The merge request includes the expected head SHA.
32
+
33
+ Actions are optional when `required_workflow_ids` is empty; the two acceptance
34
+ statuses remain mandatory. Configure workflow IDs when GitHub Actions must also
35
+ supply evidence. Branch protection remains GitHub's final merge gate.
@@ -0,0 +1,8 @@
1
+ ---
2
+ # auto-generated by sync_extensions.py
3
+ description: Periodically check pull requests and merge only current heads with independent review, tests, and passing CI.
4
+ ---
5
+
6
+ Read and follow the complete instructions in the SKILL.md file located in this skill's directory.
7
+
8
+ $ARGUMENTS
@@ -0,0 +1,230 @@
1
+ """Shared GitHub transport and repository operations for GitHub automations."""
2
+
3
+ import argparse
4
+ import json
5
+ import os
6
+ import re
7
+ import subprocess
8
+ from functools import cached_property
9
+ from pathlib import Path
10
+ from urllib.error import HTTPError
11
+ from urllib.parse import parse_qsl, urlencode, urlsplit
12
+ from urllib.request import Request, urlopen
13
+
14
+
15
+ def _load_secret(name: str) -> str:
16
+ """Read one named secret from the environment or configured Agent Server."""
17
+ value = os.environ.get(name)
18
+ if value:
19
+ return value
20
+
21
+ from openhands.sdk.workspace import RemoteWorkspace
22
+
23
+ workspace = RemoteWorkspace(
24
+ host=os.environ["AGENT_SERVER_URL"],
25
+ api_key=os.environ["SESSION_API_KEY"],
26
+ working_dir=os.environ.get("WORKSPACE_BASE", "/workspace"),
27
+ )
28
+ try:
29
+ secret = workspace.get_secrets([name]).get(name)
30
+ value = secret.get_value() if secret else None
31
+ finally:
32
+ workspace.reset_client()
33
+ if not value:
34
+ raise ValueError(f"The GitHub credential {name} is unavailable")
35
+ return value
36
+
37
+
38
+ def github_request(
39
+ token: str,
40
+ method: str,
41
+ path: str,
42
+ params: dict | None = None,
43
+ body: dict | None = None,
44
+ accept: str = "application/vnd.github+json",
45
+ ) -> tuple:
46
+ url = f"https://api.github.com{path}"
47
+ if params:
48
+ url = f"{url}?{urlencode(params)}"
49
+ headers = {
50
+ "Authorization": f"Bearer {token}",
51
+ "Accept": accept,
52
+ "X-GitHub-Api-Version": "2022-11-28",
53
+ "Content-Type": "application/json",
54
+ }
55
+ data = json.dumps(body).encode() if body is not None else None
56
+ req = Request(url, data=data, headers=headers, method=method)
57
+ with urlopen(req, timeout=90) as r:
58
+ raw = r.read()
59
+ return (json.loads(raw) if raw.strip() else {}), dict(r.headers)
60
+
61
+
62
+ def github_paginate(token: str, path: str, params: dict | None = None) -> list:
63
+ results = []
64
+ base_params = dict(params or {})
65
+ base_params.setdefault("per_page", 100)
66
+ for page in range(1, 101):
67
+ base_params["page"] = page
68
+ data, _ = github_request(token, "GET", path, params=base_params)
69
+ if not isinstance(data, list):
70
+ raise TypeError("Expected a paginated GitHub list")
71
+ results.extend(data)
72
+ if len(data) < int(base_params["per_page"]):
73
+ return results
74
+ raise RuntimeError("GitHub pagination exceeded limit")
75
+
76
+
77
+ class GitHubRepository:
78
+ name = "GitHub automation"
79
+
80
+ def __init__(
81
+ self,
82
+ config_path=Path("config.json"),
83
+ *,
84
+ github_token_secret,
85
+ repository=None,
86
+ conversation=None,
87
+ dispatcher=None,
88
+ ):
89
+ self.config = json.loads(Path(config_path).read_text())
90
+ self.repository = repository or self.config["repository"]
91
+ if not re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", self.repository):
92
+ raise ValueError("repository must be owner/repo")
93
+ if not re.fullmatch(r"[A-Z_][A-Z0-9_]*", github_token_secret):
94
+ raise ValueError(
95
+ "Expected the environment variable containing the GitHub token"
96
+ )
97
+ self.token_name = github_token_secret
98
+ self.token = _load_secret(github_token_secret)
99
+ self.conversation = conversation
100
+ self.conversation_id = str(conversation.id) if conversation else None
101
+ self.dispatcher = dispatcher
102
+ self.workspace = Path(os.environ["WORKSPACE_BASE"])
103
+ self.project = self.workspace
104
+ self.evidence = self.workspace / "evidence"
105
+ self.evidence.mkdir(exist_ok=True)
106
+ self._completed_dependencies = {}
107
+
108
+ @cached_property
109
+ def base_branch(self):
110
+ return self.config.get("base_branch") or self.gh("GET", "")["default_branch"]
111
+
112
+ @property
113
+ def github_instructions(self):
114
+ return (
115
+ f"Use `GH_TOKEN=${self.token_name} gh api` for GitHub requests. "
116
+ "Never print the credential value. "
117
+ f"Only {self.repository} is in scope. Work in {self.project}. "
118
+ "Do not modify the automation bundle or its configuration."
119
+ )
120
+
121
+ def gh(self, method, path, body=None):
122
+ return github_request(
123
+ self.token, method, f"/repos/{self.repository}" + path, body=body
124
+ )[0]
125
+
126
+ def shell(self, args, cwd=None, timeout=300):
127
+ result = subprocess.run(
128
+ args,
129
+ cwd=cwd or self.project,
130
+ text=True,
131
+ stdout=subprocess.PIPE,
132
+ stderr=subprocess.STDOUT,
133
+ timeout=timeout,
134
+ check=False,
135
+ )
136
+ if result.returncode:
137
+ raise RuntimeError(
138
+ f"{args[0]} failed: {result.stdout[-4000:].replace(self.token, '[REDACTED]')}"
139
+ )
140
+ return result.stdout.strip()
141
+
142
+ def comment(self, number, text):
143
+ return self.gh(
144
+ "POST",
145
+ f"/issues/{number}/comments",
146
+ {
147
+ "body": text
148
+ + f"\n\nFactory role: `{self.name}`; conversation: `{self.conversation_id}`."
149
+ + "\n\n_This comment was posted by an AI agent (OpenHands)._"
150
+ },
151
+ )
152
+
153
+ def open_issues(self):
154
+ return [
155
+ i for i in self.gh_pages("/issues?state=open") if "pull_request" not in i
156
+ ]
157
+
158
+ def statuses(self, sha):
159
+ result = {}
160
+ for item in self.gh_pages(f"/commits/{sha}/statuses"):
161
+ result.setdefault(item["context"], item["state"])
162
+ return result
163
+
164
+ def completed_dependency(self, number):
165
+ if number in self._completed_dependencies:
166
+ return self._completed_dependencies[number]
167
+ try:
168
+ dependency = self.gh("GET", f"/issues/{number}")
169
+ except HTTPError as exc:
170
+ if exc.code == 404:
171
+ return False
172
+ raise
173
+ completed = (
174
+ dependency["state"] == "closed"
175
+ and dependency.get("state_reason") == "completed"
176
+ )
177
+ self._completed_dependencies[number] = completed
178
+ return completed
179
+
180
+ def dependencies_complete(self, issue):
181
+ """Honor explicit Depends on lines; unknown/incomplete issues remain blocked."""
182
+ for line in re.findall(
183
+ "^Depends on:\\s*(.+)$",
184
+ issue.get("body") or "",
185
+ re.MULTILINE | re.IGNORECASE,
186
+ ):
187
+ for number in re.findall("#(\\d+)", line):
188
+ if not self.completed_dependency(number):
189
+ return False
190
+ return True
191
+
192
+ def gh_pages(self, endpoint):
193
+ split = urlsplit(endpoint)
194
+ return github_paginate(
195
+ self.token,
196
+ f"/repos/{self.repository}" + split.path,
197
+ params=dict(parse_qsl(split.query)),
198
+ )
199
+
200
+
201
+ def run_repositories(automation_type, conversation=None, dispatcher=None):
202
+ parser = argparse.ArgumentParser(description=automation_type.__doc__)
203
+ parser.add_argument("--github-token-secret")
204
+ args = parser.parse_args()
205
+ config = json.loads(Path("config.json").read_text())
206
+ token_name = args.github_token_secret or config.get(
207
+ "github_token_secret", "GITHUB_PERSONAL_ACCESS_TOKEN"
208
+ )
209
+ repositories = config.get("repos") or [config["repository"]]
210
+ failures = []
211
+ for repository in repositories:
212
+ options = dict(
213
+ github_token_secret=token_name,
214
+ repository=repository,
215
+ conversation=conversation,
216
+ )
217
+ if dispatcher is not None:
218
+ options["dispatcher"] = dispatcher
219
+ automation = automation_type(**options)
220
+ try:
221
+ automation.run()
222
+ except Exception as exc: # noqa: BLE001 - one repository must not block others
223
+ failures.append(repository)
224
+ print(
225
+ json.dumps({"repository": repository, "error": type(exc).__name__}),
226
+ flush=True,
227
+ )
228
+ if failures:
229
+ raise RuntimeError("Automation failed for: " + ", ".join(failures))
230
+ return str(conversation.id) if conversation else None