@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.
- package/.claude-plugin/marketplace.json +881 -0
- package/.codex-plugin/marketplace.json +881 -0
- package/.github/workflows/npm-publish.yml +9 -3
- package/.plugin/marketplace.json +881 -0
- package/.release-please-manifest.json +1 -1
- package/README.md +3 -2
- package/automations/bundle-index.js +16 -7
- package/automations/catalog/github-delivery-watchdog/manifest.json +79 -0
- package/automations/catalog/github-issue-to-pr/manifest.json +23 -15
- package/automations/catalog/github-issue-triage/manifest.json +4 -3
- package/automations/catalog/github-pr-reviewer/manifest.json +22 -15
- package/automations/catalog-index.js +6 -4
- package/marketplaces/openhands-extensions.json +6 -0
- package/package.json +1 -1
- package/plugins/city-weather/.claude-plugin/plugin.json +13 -0
- package/plugins/city-weather/.codex-plugin/plugin.json +13 -0
- package/plugins/cobol-modernization/.claude-plugin/plugin.json +19 -0
- package/plugins/cobol-modernization/.codex-plugin/plugin.json +19 -0
- package/plugins/issue-duplicate-checker/.claude-plugin/plugin.json +13 -0
- package/plugins/issue-duplicate-checker/.codex-plugin/plugin.json +13 -0
- package/plugins/magic-test/.claude-plugin/plugin.json +13 -0
- package/plugins/magic-test/.codex-plugin/plugin.json +13 -0
- package/plugins/migration-scoring/.claude-plugin/plugin.json +19 -0
- package/plugins/migration-scoring/.codex-plugin/plugin.json +19 -0
- package/plugins/onboarding/.claude-plugin/plugin.json +20 -0
- package/plugins/onboarding/.codex-plugin/plugin.json +20 -0
- package/plugins/onboarding/skills/agent-readiness-report/references/criteria.md +144 -0
- package/plugins/onboarding/skills/improve-agent-readiness/references/criteria.md +144 -0
- package/plugins/openhands/.claude-plugin/plugin.json +13 -0
- package/plugins/openhands/.codex-plugin/plugin.json +13 -0
- package/plugins/openhands/skills/openhands-api/.claude-plugin/plugin.json +22 -0
- package/plugins/openhands/skills/openhands-api/.codex-plugin/plugin.json +22 -0
- package/plugins/openhands/skills/openhands-api/.plugin/plugin.json +22 -0
- package/plugins/openhands/skills/openhands-api/README.md +52 -0
- package/plugins/openhands/skills/openhands-api/SKILL.md +469 -0
- package/plugins/openhands/skills/openhands-api/references/README.md +33 -0
- package/plugins/openhands/skills/openhands-api/references/TROUBLESHOOTING.md +81 -0
- package/plugins/openhands/skills/openhands-api/references/example_prompt.md +12 -0
- package/plugins/openhands/skills/openhands-api/references/windows.md +44 -0
- package/plugins/openhands/skills/openhands-api/scripts/openhands_api.py +606 -0
- package/plugins/openhands/skills/openhands-api/scripts/openhands_api.ts +252 -0
- package/plugins/openhands/skills/openhands-automation/.claude-plugin/plugin.json +19 -0
- package/plugins/openhands/skills/openhands-automation/.codex-plugin/plugin.json +19 -0
- package/plugins/openhands/skills/openhands-automation/.plugin/plugin.json +19 -0
- package/plugins/openhands/skills/openhands-automation/README.md +89 -0
- package/plugins/openhands/skills/openhands-automation/SKILL.md +899 -0
- package/plugins/openhands/skills/openhands-automation/commands/automation-create.md +8 -0
- package/plugins/openhands/skills/openhands-automation/references/ab-testing.md +185 -0
- package/plugins/openhands/skills/openhands-automation/references/custom-automation.md +832 -0
- package/plugins/openhands/skills/openhands-automation/references/security.md +84 -0
- package/plugins/openhands/skills/openhands-automation/references/windows.md +57 -0
- package/plugins/openhands/skills/openhands-sdk/.claude-plugin/plugin.json +20 -0
- package/plugins/openhands/skills/openhands-sdk/.codex-plugin/plugin.json +20 -0
- package/plugins/openhands/skills/openhands-sdk/.plugin/plugin.json +20 -0
- package/plugins/openhands/skills/openhands-sdk/README.md +22 -0
- package/plugins/openhands/skills/openhands-sdk/SKILL.md +245 -0
- package/plugins/openhands/skills/openhands-sdk/commands/sdk.md +8 -0
- package/plugins/pr-review/.claude-plugin/plugin.json +13 -0
- package/plugins/pr-review/.codex-plugin/plugin.json +13 -0
- package/plugins/pr-review/skills/code-review/.claude-plugin/plugin.json +19 -0
- package/plugins/pr-review/skills/code-review/.codex-plugin/plugin.json +19 -0
- package/plugins/pr-review/skills/code-review/.plugin/plugin.json +19 -0
- package/plugins/pr-review/skills/code-review/README.md +18 -0
- package/plugins/pr-review/skills/code-review/SKILL.md +209 -0
- package/plugins/pr-review/skills/code-review/commands/codereview-roasted.md +8 -0
- package/plugins/pr-review/skills/code-review/commands/codereview.md +8 -0
- package/plugins/pr-review/skills/code-review/references/risk-evaluation.md +41 -0
- package/plugins/pr-review/skills/code-review/references/supply-chain-security.md +31 -0
- package/plugins/pr-review/skills/github-pr-review/.claude-plugin/plugin.json +18 -0
- package/plugins/pr-review/skills/github-pr-review/.codex-plugin/plugin.json +18 -0
- package/plugins/pr-review/skills/github-pr-review/.plugin/plugin.json +18 -0
- package/plugins/pr-review/skills/github-pr-review/README.md +145 -0
- package/plugins/pr-review/skills/github-pr-review/SKILL.md +187 -0
- package/plugins/pr-review/skills/github-pr-review/commands/github-pr-review.md +8 -0
- package/plugins/pr-review/skills/github-pr-review/references/windows.md +47 -0
- package/plugins/qa-changes/.claude-plugin/plugin.json +11 -0
- package/plugins/qa-changes/.codex-plugin/plugin.json +11 -0
- package/plugins/qa-changes/skills/github-pr-review/.claude-plugin/plugin.json +18 -0
- package/plugins/qa-changes/skills/github-pr-review/.codex-plugin/plugin.json +18 -0
- package/plugins/qa-changes/skills/github-pr-review/.plugin/plugin.json +18 -0
- package/plugins/qa-changes/skills/github-pr-review/README.md +145 -0
- package/plugins/qa-changes/skills/github-pr-review/SKILL.md +187 -0
- package/plugins/qa-changes/skills/github-pr-review/commands/github-pr-review.md +8 -0
- package/plugins/qa-changes/skills/github-pr-review/references/windows.md +47 -0
- package/plugins/qa-changes/skills/qa-changes/README.md +18 -0
- package/plugins/qa-changes/skills/qa-changes/SKILL.md +229 -0
- package/plugins/qa-changes/skills/qa-changes/commands/qa-changes.md +8 -0
- package/plugins/release-notes/.claude-plugin/plugin.json +19 -0
- package/plugins/release-notes/.codex-plugin/plugin.json +19 -0
- package/plugins/release-notes/skills/release-notes/README.md +24 -0
- package/plugins/release-notes/skills/release-notes/SKILL.md +19 -0
- package/plugins/release-notes/skills/release-notes/commands/release-notes.md +8 -0
- package/plugins/vulnerability-remediation/.claude-plugin/plugin.json +19 -0
- package/plugins/vulnerability-remediation/.codex-plugin/plugin.json +19 -0
- package/plugins/vulnerability-remediation/skills/github/.claude-plugin/plugin.json +19 -0
- package/plugins/vulnerability-remediation/skills/github/.codex-plugin/plugin.json +19 -0
- package/plugins/vulnerability-remediation/skills/github/.plugin/plugin.json +19 -0
- package/plugins/vulnerability-remediation/skills/github/README.md +42 -0
- package/plugins/vulnerability-remediation/skills/github/SKILL.md +107 -0
- package/plugins/vulnerability-remediation/skills/github/references/windows.md +48 -0
- package/plugins/vulnerability-remediation/skills/github/scripts/agent_conversation.py +143 -0
- package/plugins/vulnerability-remediation/skills/github/scripts/github_client.py +230 -0
- package/plugins/vulnerability-remediation/skills/security/.claude-plugin/plugin.json +18 -0
- package/plugins/vulnerability-remediation/skills/security/.codex-plugin/plugin.json +18 -0
- package/plugins/vulnerability-remediation/skills/security/.plugin/plugin.json +18 -0
- package/plugins/vulnerability-remediation/skills/security/README.md +38 -0
- package/plugins/vulnerability-remediation/skills/security/SKILL.md +33 -0
- package/pyproject.toml +1 -1
- package/python/openhands_extensions/_version.py +1 -1
- package/skills/add-javadoc/.claude-plugin/plugin.json +18 -0
- package/skills/add-javadoc/.codex-plugin/plugin.json +18 -0
- package/skills/add-skill/.claude-plugin/plugin.json +18 -0
- package/skills/add-skill/.codex-plugin/plugin.json +18 -0
- package/skills/agent-canvas-environment/.claude-plugin/plugin.json +19 -0
- package/skills/agent-canvas-environment/.codex-plugin/plugin.json +19 -0
- package/skills/agent-creator/.claude-plugin/plugin.json +20 -0
- package/skills/agent-creator/.codex-plugin/plugin.json +20 -0
- package/skills/agent-memory/.claude-plugin/plugin.json +18 -0
- package/skills/agent-memory/.codex-plugin/plugin.json +18 -0
- package/skills/agent-sdk-builder/.claude-plugin/plugin.json +18 -0
- package/skills/agent-sdk-builder/.codex-plugin/plugin.json +18 -0
- package/skills/azure-devops/.claude-plugin/plugin.json +18 -0
- package/skills/azure-devops/.codex-plugin/plugin.json +18 -0
- package/skills/bitbucket/.claude-plugin/plugin.json +17 -0
- package/skills/bitbucket/.codex-plugin/plugin.json +17 -0
- package/skills/bitbucket-cloud/.claude-plugin/plugin.json +18 -0
- package/skills/bitbucket-cloud/.codex-plugin/plugin.json +18 -0
- package/skills/bitbucket-data-center/.claude-plugin/plugin.json +19 -0
- package/skills/bitbucket-data-center/.codex-plugin/plugin.json +19 -0
- package/skills/code-review/.claude-plugin/plugin.json +19 -0
- package/skills/code-review/.codex-plugin/plugin.json +19 -0
- package/skills/code-simplifier/.claude-plugin/plugin.json +21 -0
- package/skills/code-simplifier/.codex-plugin/plugin.json +21 -0
- package/skills/datadog/.claude-plugin/plugin.json +19 -0
- package/skills/datadog/.codex-plugin/plugin.json +19 -0
- package/skills/deno/.claude-plugin/plugin.json +18 -0
- package/skills/deno/.codex-plugin/plugin.json +18 -0
- package/skills/discord/.claude-plugin/plugin.json +18 -0
- package/skills/discord/.codex-plugin/plugin.json +18 -0
- package/skills/docker/.claude-plugin/plugin.json +17 -0
- package/skills/docker/.codex-plugin/plugin.json +17 -0
- package/skills/evidence-based-citations/.claude-plugin/plugin.json +20 -0
- package/skills/evidence-based-citations/.codex-plugin/plugin.json +20 -0
- package/skills/flarglebargle/.claude-plugin/plugin.json +16 -0
- package/skills/flarglebargle/.codex-plugin/plugin.json +16 -0
- package/skills/frontend-design/.claude-plugin/plugin.json +21 -0
- package/skills/frontend-design/.codex-plugin/plugin.json +21 -0
- package/skills/github/.claude-plugin/plugin.json +19 -0
- package/skills/github/.codex-plugin/plugin.json +19 -0
- package/skills/github/scripts/agent_conversation.py +143 -0
- package/skills/github/scripts/github_client.py +31 -5
- package/skills/github-actions/.claude-plugin/plugin.json +19 -0
- package/skills/github-actions/.codex-plugin/plugin.json +19 -0
- package/skills/github-agents-md-maintainer/.claude-plugin/plugin.json +19 -0
- package/skills/github-agents-md-maintainer/.codex-plugin/plugin.json +19 -0
- package/skills/github-agents-md-maintainer/scripts/github_client.py +230 -0
- package/skills/github-delivery-watchdog/.claude-plugin/plugin.json +9 -0
- package/skills/github-delivery-watchdog/.codex-plugin/plugin.json +9 -0
- package/skills/github-delivery-watchdog/.plugin/plugin.json +9 -0
- package/skills/github-delivery-watchdog/README.md +10 -0
- package/skills/github-delivery-watchdog/SKILL.md +35 -0
- package/skills/github-delivery-watchdog/commands/github-delivery-watchdog.md +8 -0
- package/skills/github-delivery-watchdog/scripts/github_client.py +230 -0
- package/skills/github-delivery-watchdog/scripts/worker.py +96 -0
- package/skills/github-issue-to-pr/.claude-plugin/plugin.json +19 -0
- package/skills/github-issue-to-pr/.codex-plugin/plugin.json +19 -0
- package/skills/github-issue-to-pr/SKILL.md +7 -0
- package/skills/github-issue-to-pr/scripts/agent_conversation.py +143 -0
- package/skills/github-issue-to-pr/scripts/github_client.py +230 -0
- package/skills/github-issue-to-pr/scripts/main.py +16 -10
- package/skills/github-issue-to-pr/scripts/worker.py +172 -0
- package/skills/github-issue-triage/.claude-plugin/plugin.json +9 -0
- package/skills/github-issue-triage/.codex-plugin/plugin.json +9 -0
- package/skills/github-issue-triage/SKILL.md +15 -18
- package/skills/github-issue-triage/scripts/agent_conversation.py +143 -0
- package/skills/github-issue-triage/scripts/github_client.py +230 -0
- package/skills/github-issue-triage/scripts/worker.py +81 -98
- package/skills/github-pr-review/.claude-plugin/plugin.json +18 -0
- package/skills/github-pr-review/.codex-plugin/plugin.json +18 -0
- package/skills/github-pr-reviewer/.claude-plugin/plugin.json +20 -0
- package/skills/github-pr-reviewer/.codex-plugin/plugin.json +20 -0
- package/skills/github-pr-reviewer/SKILL.md +7 -0
- package/skills/github-pr-reviewer/scripts/agent_conversation.py +143 -0
- package/skills/github-pr-reviewer/scripts/github_client.py +230 -0
- package/skills/github-pr-reviewer/scripts/main.py +20 -8
- package/skills/github-pr-reviewer/scripts/worker.py +94 -0
- package/skills/github-repo-monitor/.claude-plugin/plugin.json +22 -0
- package/skills/github-repo-monitor/.codex-plugin/plugin.json +22 -0
- package/skills/github-repo-monitor/scripts/github_client.py +230 -0
- package/skills/gitlab/.claude-plugin/plugin.json +17 -0
- package/skills/gitlab/.codex-plugin/plugin.json +17 -0
- package/skills/gitlab-issue-to-mr/.claude-plugin/plugin.json +19 -0
- package/skills/gitlab-issue-to-mr/.codex-plugin/plugin.json +19 -0
- package/skills/incident-retrospective/.claude-plugin/plugin.json +21 -0
- package/skills/incident-retrospective/.codex-plugin/plugin.json +21 -0
- package/skills/index.js +12 -3
- package/skills/iterate/.claude-plugin/plugin.json +13 -0
- package/skills/iterate/.codex-plugin/plugin.json +13 -0
- package/skills/jira-issue-to-pr/.claude-plugin/plugin.json +19 -0
- package/skills/jira-issue-to-pr/.codex-plugin/plugin.json +19 -0
- package/skills/jupyter/.claude-plugin/plugin.json +18 -0
- package/skills/jupyter/.codex-plugin/plugin.json +18 -0
- package/skills/kubernetes/.claude-plugin/plugin.json +18 -0
- package/skills/kubernetes/.codex-plugin/plugin.json +18 -0
- package/skills/learn-from-code-review/.claude-plugin/plugin.json +19 -0
- package/skills/learn-from-code-review/.codex-plugin/plugin.json +19 -0
- package/skills/linear/.claude-plugin/plugin.json +19 -0
- package/skills/linear/.codex-plugin/plugin.json +19 -0
- package/skills/linear-triage/.claude-plugin/plugin.json +21 -0
- package/skills/linear-triage/.codex-plugin/plugin.json +21 -0
- package/skills/news-digest/.claude-plugin/plugin.json +19 -0
- package/skills/news-digest/.codex-plugin/plugin.json +19 -0
- package/skills/notion/.claude-plugin/plugin.json +17 -0
- package/skills/notion/.codex-plugin/plugin.json +17 -0
- package/skills/npm/.claude-plugin/plugin.json +17 -0
- package/skills/npm/.codex-plugin/plugin.json +17 -0
- package/skills/openhands-api/.claude-plugin/plugin.json +22 -0
- package/skills/openhands-api/.codex-plugin/plugin.json +22 -0
- package/skills/openhands-automation/.claude-plugin/plugin.json +19 -0
- package/skills/openhands-automation/.codex-plugin/plugin.json +19 -0
- package/skills/openhands-enterprise-troubleshooting/.claude-plugin/plugin.json +20 -0
- package/skills/openhands-enterprise-troubleshooting/.codex-plugin/plugin.json +20 -0
- package/skills/openhands-sdk/.claude-plugin/plugin.json +20 -0
- package/skills/openhands-sdk/.codex-plugin/plugin.json +20 -0
- package/skills/pdflatex/.claude-plugin/plugin.json +18 -0
- package/skills/pdflatex/.codex-plugin/plugin.json +18 -0
- package/skills/plain-english-content/.claude-plugin/plugin.json +15 -0
- package/skills/plain-english-content/.codex-plugin/plugin.json +15 -0
- package/skills/prd/.claude-plugin/plugin.json +19 -0
- package/skills/prd/.codex-plugin/plugin.json +19 -0
- package/skills/research-brief/.claude-plugin/plugin.json +20 -0
- package/skills/research-brief/.codex-plugin/plugin.json +20 -0
- package/skills/security/.claude-plugin/plugin.json +18 -0
- package/skills/security/.codex-plugin/plugin.json +18 -0
- package/skills/skill-creator/.claude-plugin/plugin.json +17 -0
- package/skills/skill-creator/.codex-plugin/plugin.json +17 -0
- package/skills/slack-channel-monitor/.claude-plugin/plugin.json +21 -0
- package/skills/slack-channel-monitor/.codex-plugin/plugin.json +21 -0
- package/skills/slack-standup-digest/.claude-plugin/plugin.json +21 -0
- package/skills/slack-standup-digest/.codex-plugin/plugin.json +21 -0
- package/skills/spark-version-upgrade/.claude-plugin/plugin.json +20 -0
- package/skills/spark-version-upgrade/.codex-plugin/plugin.json +20 -0
- package/skills/ssh/.claude-plugin/plugin.json +18 -0
- package/skills/ssh/.codex-plugin/plugin.json +18 -0
- package/skills/swift-linux/.claude-plugin/plugin.json +17 -0
- package/skills/swift-linux/.codex-plugin/plugin.json +17 -0
- package/skills/technical-writing/.claude-plugin/plugin.json +15 -0
- package/skills/technical-writing/.codex-plugin/plugin.json +15 -0
- package/skills/theme-factory/.claude-plugin/plugin.json +19 -0
- package/skills/theme-factory/.codex-plugin/plugin.json +19 -0
- package/skills/ticket-to-code-change/.claude-plugin/plugin.json +21 -0
- package/skills/ticket-to-code-change/.codex-plugin/plugin.json +21 -0
- package/skills/upstream-fork-sync/.claude-plugin/plugin.json +21 -0
- package/skills/upstream-fork-sync/.codex-plugin/plugin.json +21 -0
- package/skills/uv/.claude-plugin/plugin.json +18 -0
- package/skills/uv/.codex-plugin/plugin.json +18 -0
- package/skills/vercel/.claude-plugin/plugin.json +18 -0
- package/skills/vercel/.codex-plugin/plugin.json +18 -0
- package/tests/fixtures/automations/capabilities.json +6 -3
- package/tests/fixtures/automations/github-pr-reviewer.json +64 -43
- package/tests/github_automation_helpers.py +7 -1
- package/tests/test_agent_conversation_dispatch.py +180 -0
- package/tests/test_automation_setup.py +3 -3
- package/tests/test_github_developer_delivery.py +150 -0
- package/tests/test_github_reviewer_delivery.py +110 -0
- package/tests/test_github_triage_delivery.py +78 -41
- package/tests/test_github_watchdog_delivery.py +233 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gitlab",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Interact with GitLab repositories, merge requests, and APIs using the GITLAB_TOKEN environment variable. Use when working with code hosted on GitLab or managing GitLab resources.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"gitlab",
|
|
14
|
+
"git",
|
|
15
|
+
"merge-request"
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gitlab-issue-to-mr",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Create an automation that implements GitLab issues when a configurable trigger label is applied. It clones the default branch, starts one OpenHands conversation per label event, then commits, pushes, and opens the merge request itself, without ever handing the agent a push credential.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"gitlab",
|
|
14
|
+
"issue",
|
|
15
|
+
"merge-request",
|
|
16
|
+
"automation",
|
|
17
|
+
"integration"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gitlab-issue-to-mr",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Create an automation that implements GitLab issues when a configurable trigger label is applied. It clones the default branch, starts one OpenHands conversation per label event, then commits, pushes, and opens the merge request itself, without ever handing the agent a push credential.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"gitlab",
|
|
14
|
+
"issue",
|
|
15
|
+
"merge-request",
|
|
16
|
+
"automation",
|
|
17
|
+
"integration"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "incident-retrospective",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Create an automation that drafts incident retrospectives by gathering incident-channel messages from Slack, collecting linked tickets from Linear, and publishing a retrospective draft to Notion with timeline, impact summary, root-cause hypotheses, and action items.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"incident",
|
|
14
|
+
"retrospective",
|
|
15
|
+
"postmortem",
|
|
16
|
+
"slack",
|
|
17
|
+
"linear",
|
|
18
|
+
"notion",
|
|
19
|
+
"automation"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "incident-retrospective",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Create an automation that drafts incident retrospectives by gathering incident-channel messages from Slack, collecting linked tickets from Linear, and publishing a retrospective draft to Notion with timeline, impact summary, root-cause hypotheses, and action items.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"incident",
|
|
14
|
+
"retrospective",
|
|
15
|
+
"postmortem",
|
|
16
|
+
"slack",
|
|
17
|
+
"linear",
|
|
18
|
+
"notion",
|
|
19
|
+
"automation"
|
|
20
|
+
]
|
|
21
|
+
}
|
package/skills/index.js
CHANGED
|
@@ -233,13 +233,22 @@ export const SKILLS_CATALOG = [
|
|
|
233
233
|
"content": "# AGENTS.md Maintainer Automation\n\nCreate a cron automation that keeps each configured repository's `AGENTS.md` -\nthe file an agent reads first when it starts work there - matching what the\nrepository actually is. It is created when missing, updated when the repository\nhas moved on, and left alone when it is still accurate.\n\nThe automation script is deterministic: scheduling, the once-per-week claim, the\nclone, the branch, the commit, the push, the pull request, and the clone's\nremoval are all handled in Python. The LLM is invoked only to read the\nrepository and write the file.\n\nA week is one unit of work per repository, so a cron that fires more often than\nintended, a retried run, or a restarted service cannot open the same pull\nrequest twice. **A repository whose previous pull request from this automation\nis still open is skipped**, because a second one would edit the same file and\nreviewing it would tell you nothing the first did not.\n\n---\n\nThe script imports shared GitHub transport from\n`scripts/github_client.py`, installed with this skill. Include it beside\n`main.py` when packaging manually, as shown below.\n\n## Prerequisites\n\n### Required secret\n\nVerify that the following secret is set in **OpenHands Settings -> Secrets**:\n\n| Secret name | Token type | Minimum permissions |\n|---|---|---|\n| `GITHUB_PERSONAL_ACCESS_TOKEN` | Classic PAT | `repo` |\n| `GITHUB_PERSONAL_ACCESS_TOKEN` | Fine-grained PAT | Contents: **Read and write**, Metadata: Read, Pull requests: **Read and write** |\n\nContents write is required because the branch is pushed, and pull request write\nbecause the pull request is opened. No issue permission is needed: this\nautomation never comments on an issue.\n\nCheck with:\n```bash\ncurl -s https://api.github.com/user \\\n -H \"Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN\" \\\n | python3 -c \"import json,sys; d=json.load(sys.stdin); print(d.get('login') or d.get('message'))\"\n```\n\nIf the token is missing or invalid, inform the user and stop.\n\n---\n\n## Setup Workflow\n\nFollow these steps in order.\n\n### Step 1 - Verify `GITHUB_PERSONAL_ACCESS_TOKEN`\n\nRun the `curl` check above.\n\n- If absent: *\"GITHUB_PERSONAL_ACCESS_TOKEN is not set. Please add it in\n OpenHands Settings -> Secrets.\"* Stop.\n- If the API returns `{\"message\": \"Bad credentials\"}`: tell the user the token is\n invalid and ask them to update it. Stop.\n\n### Step 2 - Collect repositories\n\nAsk: *\"Which GitHub repositories should have their AGENTS.md maintained?\n(Format: `owner/repo`, e.g. `myorg/backend`. List several separated by commas.)\"*\n\nValidate access to **each** repository, and confirm the token can push:\n```bash\ncurl -s \"https://api.github.com/repos/{owner}/{repo}\" \\\n -H \"Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN\" \\\n | python3 -c \"\nimport json, sys\nd = json.load(sys.stdin)\nif 'message' in d:\n print('ERROR:', d['message'])\nelse:\n print(f\\\"Accessible. Default branch: {d.get('default_branch')}. Push: {d.get('permissions',{}).get('push')}\\\")\n\"\n```\n\nRecord every accepted repository into `REPOS = [\"{owner}/{repo}\", ...]`. Each\nrepository keeps its own state and its own weekly claim, so one falling behind\nnever blocks another.\n\n### Step 3 - Collect the schedule\n\nAsk: *\"How often should AGENTS.md be checked?\n(Press Enter for the default: every Monday at 09:00 UTC, `0 9 * * 1`.)\"*\n\nDefault: `0 9 * * 1`. Record as `CRON_SCHEDULE`, and the timezone as\n`CRON_TIMEZONE` (default `UTC`).\n\nA schedule more frequent than weekly is allowed but rarely useful: the work is\nkeyed by ISO week, so extra runs inside the same week do nothing but poll.\n\n### Step 4 - Collect the pull request mode\n\nAsk: *\"Should the pull requests be opened as drafts?\n 1. Draft (default) - opened as a draft, ready for a human to mark ready\n 2. Ready for review - opened as a normal pull request\n(Press Enter for Draft)\"*\n\nMap the choice to `DRAFT_PULL_REQUEST` (`True` or `False`).\n\n### Step 5 - Collect the branch prefix\n\nAsk: *\"What branch prefix should the automation use?\n(Press Enter for the default: `openhands/agents-md`, which produces\n`openhands/agents-md-2026-W34`.)\"*\n\nRecord as `BRANCH_PREFIX`. The prefix is also how the automation recognises its\nown open pull requests, so changing it later makes it stop seeing the older ones.\n\n### Step 6 - Confirm the secret scope\n\nThe agent is handed `GITHUB_PERSONAL_ACCESS_TOKEN`, because it pushes its branch\nand opens the pull request itself. Ask: *\"Beyond the GitHub token, does reading\nthis repository need a secret of its own? (Press Enter for none.)\"*\n\nRecord the answers appended to the default, as\n`AGENT_SECRET_NAMES = [\"GITHUB_PERSONAL_ACCESS_TOKEN\", \"NAME\", ...]`. Keep it an\nallow-list: the conversation reads a whole repository, so the rest of the\ndeployment's secrets should stay out of its reach.\n\n### Step 7 - Generate the automation script\n\nRead `scripts/main.py` from this skill's directory. Apply exactly four constant\nsubstitutions near the top of the file:\n\n> The script also reads a `config.json` shipped beside it, if there is one, over\n> these constants. That is how the catalog entry\n> (`automations/catalog/github-agents-md-maintainer/`) configures an unmodified\n> copy, since a declarative host cannot rewrite Python. This setup path\n> substitutes the constants and ships no `config.json`, so the two never collide.\n\n| Placeholder | Replace with |\n|---|---|\n| `REPOS = [\"owner/repo\"]` | `REPOS = [\"{owner_repo}\", ...]` - one entry per repository from Step 2 |\n| `BRANCH_PREFIX = \"openhands/agents-md\"` | `BRANCH_PREFIX = \"{branch_prefix}\"` |\n| `DRAFT_PULL_REQUEST = True` | `DRAFT_PULL_REQUEST = {True or False}` |\n| `AGENT_SECRET_NAMES: list[str] = [\"GITHUB_PERSONAL_ACCESS_TOKEN\"]` | the list from Step 6 |\n\nUse a safe string writer such as `json.dumps(value)` when inserting user-provided\nrepository names or prefixes into Python string literals.\n\nRun these commands from this skill's directory, write the customized script\nto a temporary build directory, and validate it:\n```bash\nmkdir -p /tmp/agents-md-build\ncp -L scripts/github_client.py /tmp/agents-md-build/github_client.py\n# write the customized main.py to /tmp/agents-md-build/main.py\npython3 -m py_compile /tmp/agents-md-build/main.py && echo \"Syntax OK\"\n```\n\n### Step 8 - Package and upload\n\nDetermine the Automation backend URL and auth from the `<RUNTIME_SERVICES>`\nblock in your system context:\n- **OPENHANDS_HOST**: the Automation backend `url_from_agent`\n- **Auth**: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY`\n\n```bash\ntar -czf /tmp/agents-md.tar.gz -C /tmp/agents-md-build .\n\nTARBALL_PATH=$(curl -s -X POST \\\n \"${OPENHANDS_HOST}/api/automation/v1/uploads?name=github-agents-md-maintainer\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/gzip\" \\\n --data-binary @/tmp/agents-md.tar.gz \\\n | python3 -c \"import json,sys; print(json.load(sys.stdin)['tarball_path'])\")\n\necho \"Uploaded: $TARBALL_PATH\"\n```\n\n### Step 9 - Register the automation\n\n```bash\ncurl -s -X POST \"${OPENHANDS_HOST}/api/automation/v1\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\"name\\\": \\\"AGENTS.md Maintainer: {repo_summary}\\\",\n \\\"trigger\\\": {\\\"type\\\": \\\"cron\\\", \\\"schedule\\\": \\\"{cron_schedule}\\\", \\\"timezone\\\": \\\"{cron_timezone}\\\"},\n \\\"tarball_path\\\": \\\"$TARBALL_PATH\\\",\n \\\"entrypoint\\\": \\\"python3 main.py\\\",\n \\\"timeout\\\": 900\n }\" | python3 -m json.tool\n```\n\nRecord the returned `id`.\n\n### Step 10 - Confirm\n\nTell the user:\n\n> ✅ **AGENTS.md Maintainer** is running!\n>\n> - Automation ID: `{id}`\n> - Repositories: `{owner}/{repo}`, ... (one line each)\n> - Schedule: `{cron_schedule}` ({cron_timezone})\n> - Branch prefix: `{branch_prefix}`\n> - Pull requests: `{draft or ready for review}`\n> - State file per repository:\n> `~/.openhands/workspaces/automation-state/github_agents_md_{id}_{owner}__{repo}.json`\n>\n> Each week it reads the repository and proposes an AGENTS.md change, or reports\n> that none is needed. While one of its pull requests is still open, it stays\n> quiet - merge or close it to get the next one.\n\n---\n\n## Runtime Behaviour (per run)\n\nEach cron run executes `main.py`, which loads `config.json` if the catalog\nshipped one, checks that `git` is available, resolves and validates\n`GITHUB_PERSONAL_ACCESS_TOKEN` once, then processes every repository in `REPOS`\nindependently. One repository failing does not stop the others; the run fails\nonly if every repository fails.\n\nFor each repository:\n\n1. Loads that repository's state and reads its default branch.\n2. Computes the current ISO week, e.g. `2026-W34`, and stops here if that week\n is already recorded - which is what makes extra runs inside a week harmless.\n3. Lists open pull requests whose head branch starts with the branch prefix. If\n any exist, records the skip and moves on.\n4. Asks GitHub whether `AGENTS.md` exists on the default branch, which decides\n whether the run is a create or an update, and the pull request's title.\n5. Claims the week in state **before** the slow work, so an overlapping run\n cannot start it twice.\n6. Picks the first free branch name (`{prefix}-{period}`, else a numbered\n variant), clones the default branch shallow and single-branch into\n `{WORKSPACE_BASE}/agents-md/{owner}__{repo}/{period}`, and creates the branch.\n7. Starts an OpenHands conversation with that clone as its working directory,\n and only the secrets named in `AGENT_SECRET_NAMES` attached.\n8. When the conversation reaches `idle`, `finished`, `error`, or `stuck`:\n - Adopts the pull request the agent opened, if GitHub says one exists.\n - Records the failure and opens nothing if the conversation errored.\n - Records `no-changes` and opens nothing if there are no commits - the\n expected outcome when `AGENTS.md` is already accurate.\n - Otherwise commits what was left, pushes, and opens the pull request itself.\n - Retries a failed push or pull request on the next two runs before giving up.\n9. Removes the clone once the conversation is confirmed stopped.\n\n---\n\n## Additional Resources\n\n- **`references/state-schema.md`** - State JSON schema and the task lifecycle.\n- **`scripts/main.py`** - The complete automation script.\n\n---\n\n## Troubleshooting\n\n| Symptom | Likely cause | Fix |\n|---|---|---|\n| Nothing happens for a repository | A pull request from this automation is still open | Merge or close it; the next run proposes the following one |\n| Nothing happens after a manual dispatch | The current ISO week is already recorded in state | Wait for the next week, or clear that week's entry from the state document |\n| \"The token cannot push to ...\" | Token lacks Contents: write | Issue a token with write access, or drop the repository from `REPOS` |\n| `git is not available in the automation runtime` | The runtime image has no git | Use a runtime image that ships git |\n| Pull request says nothing changed | The agent judged AGENTS.md accurate but still committed | Read its summary in the pull request body; tighten the prompt if it keeps making cosmetic edits |\n| Every run reports `no-changes` | AGENTS.md is accurate, or the agent cannot read the repository | Open the conversation from the run log and check what it saw |\n| Clones remain under `agents-md/` | Their conversations had not stopped yet | They are removed by a later run once the conversation is terminal |",
|
|
234
234
|
"category": "automations"
|
|
235
235
|
},
|
|
236
|
+
{
|
|
237
|
+
"name": "github-delivery-watchdog",
|
|
238
|
+
"description": "Periodically check pull requests and merge only current heads with independent review, tests, and passing CI.",
|
|
239
|
+
"triggers": [
|
|
240
|
+
"/github-delivery-watchdog"
|
|
241
|
+
],
|
|
242
|
+
"content": "# GitHub delivery watchdog\n\nThis is a deterministic scheduled host command. It creates no agent or\nconversation and needs no agent profile. Configure a repository-scoped\nfine-grained PAT with Contents and Issues read/write plus Pull requests, Actions,\nCommit statuses, and Metadata read. Contents write permits merge; Issues write\nretains the review label when the branch is updated. Never put the token value in\nthe automation definition.\n\nPackage `scripts/worker.py` as `worker.py` and the shared\n`scripts/github_client.py` as `github_client.py`.\nThe catalog bundle declares these exact files. Its `config.json` supplies\n`repos`, `branch_prefix`, and the saved secret name. The shared GitHub client\nresolves only that named secret. Automation owns scheduling and cancellation.\n\nSet `branch_prefix` (default `openhands/issue`), `base_branch` (defaults to the repository's default branch),\nand `required_workflow_ids` when particular Actions workflows must run. The\nwatchdog requires `software-factory/tests` and `software-factory/review` success\nstatuses on the exact head, all other statuses and Actions passing, a current\nbase, a non-draft PR, and GitHub reporting it mergeable. Missing, pending, failed,\nor inaccessible evidence does not permit merge. A changed head requires fresh\nreview and tests. When an accepted branch is behind the base, the watchdog asks\nGitHub to update it and retains the review trigger label; it considers the new\nhead only on a later run. The merge request includes the expected head SHA.\n\nActions are optional when `required_workflow_ids` is empty; the two acceptance\nstatuses remain mandatory. Configure workflow IDs when GitHub Actions must also\nsupply evidence. Branch protection remains GitHub's final merge gate.",
|
|
243
|
+
"category": "automations"
|
|
244
|
+
},
|
|
236
245
|
{
|
|
237
246
|
"name": "github-issue-to-pr",
|
|
238
247
|
"description": "Create an automation that implements GitHub issues when a configurable trigger label is applied. Polls one or more repositories deterministically, clones the default branch, starts one OpenHands conversation per label event, then commits, pushes, and opens the pull request itself.",
|
|
239
248
|
"triggers": [
|
|
240
249
|
"/issue-to-pr:setup"
|
|
241
250
|
],
|
|
242
|
-
"content": "# GitHub Issue to PR Automation\n\nCreate a cron automation that watches one or more GitHub repositories for issues\nwith a trigger label, starts an OpenHands conversation once per label event with\nthe repository's default branch already checked out, and opens a pull request\nwith whatever the agent produced.\n\nThe automation script is deterministic: issue discovery, label-event tracking,\nstate persistence, the clone, the branch, the commit, the push, the pull request,\nthe issue comments, and the clone's removal are all handled in Python. The LLM is\ninvoked only to write the code.\n\nThe agent is told **which** issue to implement, not what it says. It fetches the\ndescription, the discussion, and whatever they link to itself, so nothing in the\nprompt goes stale between dispatch and the moment the agent reads it.\n\nThat needs read access, so the conversation is handed exactly one secret,\n`GITHUB_PERSONAL_ACCESS_TOKEN`, and no MCP servers. `AGENT_SECRET_NAMES` stays an\nallow-list: the rest of the deployment's secret store is not reachable from a\nconversation whose instructions came from an issue.\n\nThe agent also finishes the job: it commits, pushes its branch, and opens the\npull request, so the pull request appears when the agent stops rather than on the\nnext poll. The script does not trust that it happened - when the conversation\nends it asks GitHub whether the pull request exists, and opens it itself when it\ndoes not. `origin` still carries no credential, so every GitHub command the agent\nruns has to name `GITHUB_PERSONAL_ACCESS_TOKEN`; the SDK only puts a secret in the\nenvironment of a command that mentions it, and masks it in the output.\n\n---\n\nThe script imports shared GitHub transport from\n`scripts/github_client.py`, installed with this skill. Include it beside\n`main.py` when packaging manually, as shown below; catalog bundles include it\nautomatically.\n\n## Prerequisites\n\n### Required secret\n\nVerify that the following secret is set in **OpenHands Settings -> Secrets**:\n\n| Secret name | Token type | Minimum permissions |\n|---|---|---|\n| `GITHUB_PERSONAL_ACCESS_TOKEN` | Classic PAT | `repo`, plus `workflow` |\n| `GITHUB_PERSONAL_ACCESS_TOKEN` | Fine-grained PAT | Contents: **Read and write**, Metadata: Read, Issues: **Read and write**, Pull requests: **Read and write**, Workflows: **Read and write** |\n\nThe workflow scope is not optional in practice. An issue asking for a CI change\nis a normal issue, and GitHub rejects the whole push when a token without it\ntouches `.github/workflows/`: *\"refusing to allow a Personal Access Token to\ncreate or update workflow ... without `workflow` scope\"*. The branch is rejected\nin full, so the pull request never opens.\n\nContents write access is required because the script pushes the branch, and pull\nrequest write because it opens the pull request. A read-only token will poll\nhappily and then fail at the point of pushing.\n\nWhen several repositories are monitored, the token must cover all of them.\n\nCheck with:\n```bash\ncurl -s https://api.github.com/user \\\n -H \"Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN\" \\\n | python3 -c \"import json,sys; d=json.load(sys.stdin); print(d.get('login') or d.get('message'))\"\n```\n\nIf the token is missing or invalid, inform the user and stop.\n\n---\n\n## Setup Workflow\n\nFollow these steps in order.\n\n### Step 1 - Verify `GITHUB_PERSONAL_ACCESS_TOKEN`\n\nRun the `curl` check above.\n\n- If absent: *\"GITHUB_PERSONAL_ACCESS_TOKEN is not set. Please add it in\n OpenHands Settings -> Secrets.\"* Stop.\n- If the API returns `{\"message\": \"Bad credentials\"}`: tell the user the token is\n invalid and ask them to update it. Stop.\n\n### Step 2 - Collect repositories\n\nAsk: *\"Which GitHub repositories should be watched?\n(Format: `owner/repo`, e.g. `myorg/backend`. List several separated by commas to\nserve them all from one automation.)\"*\n\nValidate access to **each** repository, and confirm the token can push:\n```bash\ncurl -s \"https://api.github.com/repos/{owner}/{repo}\" \\\n -H \"Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN\" \\\n | python3 -c \"\nimport json, sys\nd = json.load(sys.stdin)\nif 'message' in d:\n print('ERROR:', d['message'])\nelse:\n perms = d.get('permissions', {})\n print(f\\\"Accessible. Default branch: {d.get('default_branch')}. Push: {perms.get('push')}\\\")\n\"\n```\n\nRecord every accepted repository into `REPOS = [\"{owner}/{repo}\", ...]`. If one\nrepository fails the check, say which and ask whether to continue without it. If\n`Push: False`, say that the automation cannot open pull requests there and ask\nfor a token with write access.\n\nEach repository is polled independently and keeps its own state, so issue numbers\nnever collide between them. The trigger label, branch prefix, and schedule are\nshared; a repository needing different settings wants its own automation.\n\n### Step 3 - Collect trigger label\n\nAsk: *\"Which issue label should trigger an implementation?\n(Press Enter for the default: `openhands`.)\"*\n\nRecord the answer as `TRIGGER_LABEL`. If the label does not exist yet, tell the\nuser that GitHub will still record the event once the label is created and\napplied to an issue.\n\nThe automation works an issue when it sees the latest matching `labeled` event\nfor that label. To ask for another attempt later, remove and re-apply the label -\nthat opens a second branch and a second pull request rather than overwriting the\nfirst.\n\n### Step 4 - Collect the pull request mode\n\nAsk: *\"Should the pull requests be opened as drafts?\n 1. Draft (default) - opened as a draft, ready for a human to mark ready\n 2. Ready for review - opened as a normal pull request\n(Press Enter for Draft)\"*\n\nMap the choice to `DRAFT_PULL_REQUEST` (`True` or `False`).\n\n### Step 5 - Collect the branch prefix\n\nAsk: *\"What branch prefix should the automation use?\n(Press Enter for the default: `openhands/issue`, which produces\n`openhands/issue-42`.)\"*\n\nRecord as `BRANCH_PREFIX`. Keep it free of spaces and of characters git rejects\nin a ref name.\n\n### Step 6 - Collect cron schedule\n\nAsk: *\"How often should the automation poll for labelled issues?\n(Press Enter for the default: every 5 minutes.\nUse a cron expression for a different interval, e.g. `0 * * * *` = hourly)\"*\n\nDefault: `*/5 * * * *`.\n\nRecord as `CRON_SCHEDULE`.\n\n### Step 7 - Confirm the secret scope\n\nThe agent is handed `GITHUB_PERSONAL_ACCESS_TOKEN`, because it reads the issue and\nits discussion itself. Ask: *\"Beyond the GitHub token, does the repository's build\nneed a secret of its own - a package registry token, for example? (Press Enter for\nnone.)\"*\n\nRecord the answers appended to the default, as\n`AGENT_SECRET_NAMES = [\"GITHUB_PERSONAL_ACCESS_TOKEN\", \"NAME\", ...]`.\n\nKeep it an allow-list. Forwarding the whole secret store would put every\ncredential in the deployment behind a prompt written by whoever opened the issue.\nIf the repositories are public and you would rather the conversation held no\ncredential at all, set the list to `[]` - the agent can still read a public issue\nunauthenticated, and private repositories then stop working.\n\n### Step 8 - Generate the automation script\n\nRead `scripts/main.py` from this skill's directory. Apply exactly five constant\nsubstitutions near the top of the file:\n\n> The script also reads a `config.json` shipped beside it, if there is one, over\n> these constants. That is how the catalog entry\n> (`automations/catalog/github-issue-to-pr/`) configures an unmodified copy,\n> since a declarative host cannot rewrite Python. This setup path substitutes the\n> constants and ships no `config.json`, so the two never collide.\n\n| Placeholder | Replace with |\n|---|---|\n| `REPOS = [\"owner/repo\"]` | `REPOS = [\"{owner_repo}\", ...]` - one entry per repository collected in Step 2 |\n| `TRIGGER_LABEL = \"openhands\"` | `TRIGGER_LABEL = \"{trigger_label}\"` |\n| `BRANCH_PREFIX = \"openhands/issue\"` | `BRANCH_PREFIX = \"{branch_prefix}\"` |\n| `DRAFT_PULL_REQUEST = True` | `DRAFT_PULL_REQUEST = {True or False}` |\n| `AGENT_SECRET_NAMES: list[str] = []` | `AGENT_SECRET_NAMES: list[str] = [\"{name}\", ...]` |\n\nLeave `MAX_NEW_PER_RUN` and `DEFAULT_OPENHANDS_URL` alone unless the user asks\nfor a different cap or a non-default OpenHands URL.\n\nA repository may be given as `owner/repo`, as a clone URL, or as an SSH remote;\nthe script normalizes each one at startup and names the value it could not read\nrather than blaming the token.\n\nUse a safe string writer such as `json.dumps(value)` when inserting user-provided\nrepository names, labels, or prefixes into Python string literals.\n`json.dumps(list_of_repos)` produces the whole `REPOS` list safely in one step.\n\nRun these commands from this skill's directory and write the customized script\nto a temporary build directory:\n```bash\nmkdir -p /tmp/issue-to-pr-build\ncp -L scripts/github_client.py /tmp/issue-to-pr-build/github_client.py\n# write the customized main.py to /tmp/issue-to-pr-build/main.py\n```\n\nValidate syntax before packaging:\n```bash\npython3 -m py_compile /tmp/issue-to-pr-build/main.py && echo \"Syntax OK\"\n```\n\nFix any syntax errors before proceeding.\n\n### Step 9 - Package and upload\n\nDetermine the Automation backend URL and auth from the `<RUNTIME_SERVICES>`\nblock in your system context:\n- **OPENHANDS_HOST**: the Automation backend `url_from_agent`\n- **Auth**: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY`\n\n```bash\ntar -czf /tmp/issue-to-pr.tar.gz -C /tmp/issue-to-pr-build .\n\nTARBALL_PATH=$(curl -s -X POST \\\n \"${OPENHANDS_HOST}/api/automation/v1/uploads?name=github-issue-to-pr\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/gzip\" \\\n --data-binary @/tmp/issue-to-pr.tar.gz \\\n | python3 -c \"import json,sys; print(json.load(sys.stdin)['tarball_path'])\")\n\necho \"Uploaded: $TARBALL_PATH\"\n```\n\n### Step 10 - Register the automation\n\n```bash\ncurl -s -X POST \"${OPENHANDS_HOST}/api/automation/v1\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\"name\\\": \\\"GitHub Issue to PR: {repo_summary} label {trigger_label}\\\",\n \\\"trigger\\\": {\\\"type\\\": \\\"cron\\\", \\\"schedule\\\": \\\"{cron_schedule}\\\"},\n \\\"tarball_path\\\": \\\"$TARBALL_PATH\\\",\n \\\"entrypoint\\\": \\\"python3 main.py\\\",\n \\\"timeout\\\": 900\n }\" | python3 -m json.tool\n```\n\nUse the single repository as `{repo_summary}` when there is one, and something\nlike `3 repos` when there are several. A poll clones a repository per queued\nissue and pushes finished branches, so the timeout allows for that; a run never\nwaits for an agent to finish, only for it to be started.\n\nRecord the returned `id`.\n\n### Step 11 - Confirm\n\nTell the user:\n\n> ✅ **GitHub Issue to PR** is running!\n>\n> - Automation ID: `{id}`\n> - Repositories: `{owner}/{repo}`, ... (one line each)\n> - Trigger label: `{trigger_label}`\n> - Branch prefix: `{branch_prefix}`\n> - Pull requests: `{draft or ready for review}`\n> - Polling schedule: `{cron_schedule}`\n> - State file per repository:\n> `~/.openhands/workspaces/automation-state/github_issue_to_pr_{id}_{owner}__{repo}.json`\n>\n> Apply the `{trigger_label}` label to an issue to queue an implementation. Each\n> label event is processed once. To ask for another attempt, remove and re-apply\n> the label - that opens a second branch and pull request.\n>\n> The agent runs without GitHub credentials; the automation pushes the branch and\n> opens the pull request once the agent has stopped.\n\n---\n\n## Runtime Behaviour (per poll)\n\nEach cron run executes `main.py`, which loads `config.json` if the catalog\nshipped one, checks that `git` is available, resolves and validates\n`GITHUB_PERSONAL_ACCESS_TOKEN` once, then processes every repository in `REPOS`\nindependently. One repository failing does not stop the\nothers; the run fails only if every repository fails.\n\nFor each repository:\n\n1. Loads that repository's state (see `references/state-schema.md`) and reads its\n default branch.\n2. Lists open issues carrying `TRIGGER_LABEL`, newest-updated first. Pull\n requests are dropped, so labelling a PR never queues an implementation.\n3. For each labelled issue, up to `MAX_NEW_PER_RUN` new ones per run:\n - Refetches the issue so a label removed since the listing does not start work.\n - Finds the latest matching GitHub `labeled` event, and skips it if that event\n has already been tracked.\n - Picks the first free branch name, `{BRANCH_PREFIX}-{number}` or a numbered\n variant of it.\n - Clones the default branch, shallow and single-branch, into\n `{WORKSPACE_BASE}/issue-to-pr/{owner}__{repo}/issue-{number}-{event_id}`,\n sets the commit identity, and creates the branch. `origin` keeps its plain\n HTTPS URL, so the workspace holds no credential.\n - Starts an OpenHands conversation **whose working directory is that clone**,\n with the issue title, body, labels, and discussion in the prompt, and only\n the secrets named in `AGENT_SECRET_NAMES` attached.\n - Comments on the issue with the branch, the label event, and the conversation\n link.\n - Records the task with `status: \"active\"`.\n - If the clone or the conversation cannot be created, the clone is removed and\n nothing is recorded, so the next poll retries the label event.\n4. For each active task:\n - Abandons a conversation that has not reached a terminal status within two\n hours, comments on the issue, and reclaims its clone.\n - When the conversation reaches `idle`, `finished`, `error`, or `stuck`:\n - Adopts the pull request the agent opened, if GitHub says one exists for\n the branch, and comments its link on the issue. Everything below is the\n path taken when it does not.\n - Skips the pull request if the issue was closed meanwhile.\n - Reports the problem on the issue if the conversation ended in `error` or\n `stuck`.\n - Commits whatever the agent left uncommitted, on top of any commits it made\n itself.\n - Posts the agent's answer on the issue, and opens no pull request, when\n there are no commits at all - that is how an agent reports an issue too\n ambiguous to implement.\n - Otherwise pushes the branch, opens the pull request (draft by default,\n titled `[#42] <issue title>`, with the agent's summary and `Closes #42` in\n the body), and comments the link on the issue.\n - A push or pull request that fails is retried on the next two polls before\n the task is reported as failed, so a transient GitHub error does not throw\n the work away.\n5. Removes the clone of every finished task, but only after confirming the\n conversation has stopped - deleting it under a running agent would remove its\n working directory. When that cannot be confirmed the directory is left alone\n and the next poll tries again.\n6. Saves that repository's state atomically.\n\nThe completion callback fires once for the whole run.\n\n---\n\n## Additional Resources\n\n- **`references/state-schema.md`** - State JSON schema, field definitions, and the\n task lifecycle.\n- **`scripts/main.py`** - The complete automation script. Customize the five\n constants at the top before packaging.\n\n---\n\n## Troubleshooting\n\n| Symptom | Likely cause | Fix |\n|---|---|---|\n| Nothing is ever queued | Trigger label not present, or applied to a pull request rather than an issue | Apply the configured label to an issue |\n| \"Bad credentials\" in run logs | Token expired | Rotate and update `GITHUB_PERSONAL_ACCESS_TOKEN` |\n| \"The token cannot push to ...\" | Token lacks Contents: write on that repository | Issue a token with write access, or drop the repository from `REPOS` |\n| Push rejected: \"refusing to allow a Personal Access Token to create or update workflow\" | The change touches `.github/workflows/` and the token has no `workflow` scope | Add the scope to the token; the next poll retries the same branch and opens the pull request |\n| 404 on repo access | Repo name wrong or no access | Re-check the entry in `REPOS` and the token's permissions |\n| `git is not available in the automation runtime` | The runtime image has no git | Use a runtime image that ships git; the script clones, commits, and pushes with it |\n| Issue commented \"did not change any code\" | The agent judged the issue too ambiguous, or made no edits | Read its answer in the comment, add the missing detail to the issue, then re-apply the label |\n| Same issue not picked up again after new comments | Its label event was already processed | Remove and re-apply the trigger label |\n| Agent reports it cannot push or open a PR | By design - it has no credentials | No action; the automation pushes and opens the pull request after the agent stops |\n| A backlog of labelled issues starts slowly | `MAX_NEW_PER_RUN` caps how many conversations one poll starts | Wait for the next polls, or raise the cap in the script |\n| Clones remain under `issue-to-pr/` | Their conversations had not stopped yet | They are removed by a later poll once the conversation is terminal |",
|
|
251
|
+
"content": "# GitHub Issue to PR Automation\n\n## Agent Canvas catalog\n\nFor new Agent Canvas installations, use the **GitHub issue to PR** catalog\nentry. Its deterministic `worker.py` scanner delegates each eligible issue to a\nstable conversation using the selected agent profile. The manual upload flow\nbelow remains for existing deployments and is deprecated for new installations.\n\nCreate a cron automation that watches one or more GitHub repositories for issues\nwith a trigger label, starts an OpenHands conversation once per label event with\nthe repository's default branch already checked out, and opens a pull request\nwith whatever the agent produced.\n\nThe automation script is deterministic: issue discovery, label-event tracking,\nstate persistence, the clone, the branch, the commit, the push, the pull request,\nthe issue comments, and the clone's removal are all handled in Python. The LLM is\ninvoked only to write the code.\n\nThe agent is told **which** issue to implement, not what it says. It fetches the\ndescription, the discussion, and whatever they link to itself, so nothing in the\nprompt goes stale between dispatch and the moment the agent reads it.\n\nThat needs read access, so the conversation is handed exactly one secret,\n`GITHUB_PERSONAL_ACCESS_TOKEN`, and no MCP servers. `AGENT_SECRET_NAMES` stays an\nallow-list: the rest of the deployment's secret store is not reachable from a\nconversation whose instructions came from an issue.\n\nThe agent also finishes the job: it commits, pushes its branch, and opens the\npull request, so the pull request appears when the agent stops rather than on the\nnext poll. The script does not trust that it happened - when the conversation\nends it asks GitHub whether the pull request exists, and opens it itself when it\ndoes not. `origin` still carries no credential, so every GitHub command the agent\nruns has to name `GITHUB_PERSONAL_ACCESS_TOKEN`; the SDK only puts a secret in the\nenvironment of a command that mentions it, and masks it in the output.\n\n---\n\nThe script imports shared GitHub transport from\n`scripts/github_client.py`, installed with this skill. Include it beside\n`main.py` when packaging manually, as shown below; catalog bundles include it\nautomatically.\n\n## Prerequisites\n\n### Required secret\n\nVerify that the following secret is set in **OpenHands Settings -> Secrets**:\n\n| Secret name | Token type | Minimum permissions |\n|---|---|---|\n| `GITHUB_PERSONAL_ACCESS_TOKEN` | Classic PAT | `repo`, plus `workflow` |\n| `GITHUB_PERSONAL_ACCESS_TOKEN` | Fine-grained PAT | Contents: **Read and write**, Metadata: Read, Issues: **Read and write**, Pull requests: **Read and write**, Workflows: **Read and write** |\n\nThe workflow scope is not optional in practice. An issue asking for a CI change\nis a normal issue, and GitHub rejects the whole push when a token without it\ntouches `.github/workflows/`: *\"refusing to allow a Personal Access Token to\ncreate or update workflow ... without `workflow` scope\"*. The branch is rejected\nin full, so the pull request never opens.\n\nContents write access is required because the script pushes the branch, and pull\nrequest write because it opens the pull request. A read-only token will poll\nhappily and then fail at the point of pushing.\n\nWhen several repositories are monitored, the token must cover all of them.\n\nCheck with:\n```bash\ncurl -s https://api.github.com/user \\\n -H \"Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN\" \\\n | python3 -c \"import json,sys; d=json.load(sys.stdin); print(d.get('login') or d.get('message'))\"\n```\n\nIf the token is missing or invalid, inform the user and stop.\n\n---\n\n## Setup Workflow\n\nFollow these steps in order.\n\n### Step 1 - Verify `GITHUB_PERSONAL_ACCESS_TOKEN`\n\nRun the `curl` check above.\n\n- If absent: *\"GITHUB_PERSONAL_ACCESS_TOKEN is not set. Please add it in\n OpenHands Settings -> Secrets.\"* Stop.\n- If the API returns `{\"message\": \"Bad credentials\"}`: tell the user the token is\n invalid and ask them to update it. Stop.\n\n### Step 2 - Collect repositories\n\nAsk: *\"Which GitHub repositories should be watched?\n(Format: `owner/repo`, e.g. `myorg/backend`. List several separated by commas to\nserve them all from one automation.)\"*\n\nValidate access to **each** repository, and confirm the token can push:\n```bash\ncurl -s \"https://api.github.com/repos/{owner}/{repo}\" \\\n -H \"Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN\" \\\n | python3 -c \"\nimport json, sys\nd = json.load(sys.stdin)\nif 'message' in d:\n print('ERROR:', d['message'])\nelse:\n perms = d.get('permissions', {})\n print(f\\\"Accessible. Default branch: {d.get('default_branch')}. Push: {perms.get('push')}\\\")\n\"\n```\n\nRecord every accepted repository into `REPOS = [\"{owner}/{repo}\", ...]`. If one\nrepository fails the check, say which and ask whether to continue without it. If\n`Push: False`, say that the automation cannot open pull requests there and ask\nfor a token with write access.\n\nEach repository is polled independently and keeps its own state, so issue numbers\nnever collide between them. The trigger label, branch prefix, and schedule are\nshared; a repository needing different settings wants its own automation.\n\n### Step 3 - Collect trigger label\n\nAsk: *\"Which issue label should trigger an implementation?\n(Press Enter for the default: `openhands`.)\"*\n\nRecord the answer as `TRIGGER_LABEL`. If the label does not exist yet, tell the\nuser that GitHub will still record the event once the label is created and\napplied to an issue.\n\nThe automation works an issue when it sees the latest matching `labeled` event\nfor that label. To ask for another attempt later, remove and re-apply the label -\nthat opens a second branch and a second pull request rather than overwriting the\nfirst.\n\n### Step 4 - Collect the pull request mode\n\nAsk: *\"Should the pull requests be opened as drafts?\n 1. Draft (default) - opened as a draft, ready for a human to mark ready\n 2. Ready for review - opened as a normal pull request\n(Press Enter for Draft)\"*\n\nMap the choice to `DRAFT_PULL_REQUEST` (`True` or `False`).\n\n### Step 5 - Collect the branch prefix\n\nAsk: *\"What branch prefix should the automation use?\n(Press Enter for the default: `openhands/issue`, which produces\n`openhands/issue-42`.)\"*\n\nRecord as `BRANCH_PREFIX`. Keep it free of spaces and of characters git rejects\nin a ref name.\n\n### Step 6 - Collect cron schedule\n\nAsk: *\"How often should the automation poll for labelled issues?\n(Press Enter for the default: every 5 minutes.\nUse a cron expression for a different interval, e.g. `0 * * * *` = hourly)\"*\n\nDefault: `*/5 * * * *`.\n\nRecord as `CRON_SCHEDULE`.\n\n### Step 7 - Confirm the secret scope\n\nThe agent is handed `GITHUB_PERSONAL_ACCESS_TOKEN`, because it reads the issue and\nits discussion itself. Ask: *\"Beyond the GitHub token, does the repository's build\nneed a secret of its own - a package registry token, for example? (Press Enter for\nnone.)\"*\n\nRecord the answers appended to the default, as\n`AGENT_SECRET_NAMES = [\"GITHUB_PERSONAL_ACCESS_TOKEN\", \"NAME\", ...]`.\n\nKeep it an allow-list. Forwarding the whole secret store would put every\ncredential in the deployment behind a prompt written by whoever opened the issue.\nIf the repositories are public and you would rather the conversation held no\ncredential at all, set the list to `[]` - the agent can still read a public issue\nunauthenticated, and private repositories then stop working.\n\n### Step 8 - Generate the automation script\n\nRead `scripts/main.py` from this skill's directory. Apply exactly five constant\nsubstitutions near the top of the file:\n\n> The script also reads a `config.json` shipped beside it, if there is one, over\n> these constants. That is how the catalog entry\n> (`automations/catalog/github-issue-to-pr/`) configures an unmodified copy,\n> since a declarative host cannot rewrite Python. This setup path substitutes the\n> constants and ships no `config.json`, so the two never collide.\n\n| Placeholder | Replace with |\n|---|---|\n| `REPOS = [\"owner/repo\"]` | `REPOS = [\"{owner_repo}\", ...]` - one entry per repository collected in Step 2 |\n| `TRIGGER_LABEL = \"openhands\"` | `TRIGGER_LABEL = \"{trigger_label}\"` |\n| `BRANCH_PREFIX = \"openhands/issue\"` | `BRANCH_PREFIX = \"{branch_prefix}\"` |\n| `DRAFT_PULL_REQUEST = True` | `DRAFT_PULL_REQUEST = {True or False}` |\n| `AGENT_SECRET_NAMES: list[str] = []` | `AGENT_SECRET_NAMES: list[str] = [\"{name}\", ...]` |\n\nLeave `MAX_NEW_PER_RUN` and `DEFAULT_OPENHANDS_URL` alone unless the user asks\nfor a different cap or a non-default OpenHands URL.\n\nA repository may be given as `owner/repo`, as a clone URL, or as an SSH remote;\nthe script normalizes each one at startup and names the value it could not read\nrather than blaming the token.\n\nUse a safe string writer such as `json.dumps(value)` when inserting user-provided\nrepository names, labels, or prefixes into Python string literals.\n`json.dumps(list_of_repos)` produces the whole `REPOS` list safely in one step.\n\nRun these commands from this skill's directory and write the customized script\nto a temporary build directory:\n```bash\nmkdir -p /tmp/issue-to-pr-build\ncp -L scripts/github_client.py /tmp/issue-to-pr-build/github_client.py\n# write the customized main.py to /tmp/issue-to-pr-build/main.py\n```\n\nValidate syntax before packaging:\n```bash\npython3 -m py_compile /tmp/issue-to-pr-build/main.py && echo \"Syntax OK\"\n```\n\nFix any syntax errors before proceeding.\n\n### Step 9 - Package and upload\n\nDetermine the Automation backend URL and auth from the `<RUNTIME_SERVICES>`\nblock in your system context:\n- **OPENHANDS_HOST**: the Automation backend `url_from_agent`\n- **Auth**: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY`\n\n```bash\ntar -czf /tmp/issue-to-pr.tar.gz -C /tmp/issue-to-pr-build .\n\nTARBALL_PATH=$(curl -s -X POST \\\n \"${OPENHANDS_HOST}/api/automation/v1/uploads?name=github-issue-to-pr\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/gzip\" \\\n --data-binary @/tmp/issue-to-pr.tar.gz \\\n | python3 -c \"import json,sys; print(json.load(sys.stdin)['tarball_path'])\")\n\necho \"Uploaded: $TARBALL_PATH\"\n```\n\n### Step 10 - Register the automation\n\n```bash\ncurl -s -X POST \"${OPENHANDS_HOST}/api/automation/v1\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\"name\\\": \\\"GitHub Issue to PR: {repo_summary} label {trigger_label}\\\",\n \\\"trigger\\\": {\\\"type\\\": \\\"cron\\\", \\\"schedule\\\": \\\"{cron_schedule}\\\"},\n \\\"tarball_path\\\": \\\"$TARBALL_PATH\\\",\n \\\"entrypoint\\\": \\\"python3 main.py\\\",\n \\\"timeout\\\": 900\n }\" | python3 -m json.tool\n```\n\nUse the single repository as `{repo_summary}` when there is one, and something\nlike `3 repos` when there are several. A poll clones a repository per queued\nissue and pushes finished branches, so the timeout allows for that; a run never\nwaits for an agent to finish, only for it to be started.\n\nRecord the returned `id`.\n\n### Step 11 - Confirm\n\nTell the user:\n\n> ✅ **GitHub Issue to PR** is running!\n>\n> - Automation ID: `{id}`\n> - Repositories: `{owner}/{repo}`, ... (one line each)\n> - Trigger label: `{trigger_label}`\n> - Branch prefix: `{branch_prefix}`\n> - Pull requests: `{draft or ready for review}`\n> - Polling schedule: `{cron_schedule}`\n> - State file per repository:\n> `~/.openhands/workspaces/automation-state/github_issue_to_pr_{id}_{owner}__{repo}.json`\n>\n> Apply the `{trigger_label}` label to an issue to queue an implementation. Each\n> label event is processed once. To ask for another attempt, remove and re-apply\n> the label - that opens a second branch and pull request.\n>\n> The agent runs without GitHub credentials; the automation pushes the branch and\n> opens the pull request once the agent has stopped.\n\n---\n\n## Runtime Behaviour (per poll)\n\nEach cron run executes `main.py`, which loads `config.json` if the catalog\nshipped one, checks that `git` is available, resolves and validates\n`GITHUB_PERSONAL_ACCESS_TOKEN` once, then processes every repository in `REPOS`\nindependently. One repository failing does not stop the\nothers; the run fails only if every repository fails.\n\nFor each repository:\n\n1. Loads that repository's state (see `references/state-schema.md`) and reads its\n default branch.\n2. Lists open issues carrying `TRIGGER_LABEL`, newest-updated first. Pull\n requests are dropped, so labelling a PR never queues an implementation.\n3. For each labelled issue, up to `MAX_NEW_PER_RUN` new ones per run:\n - Refetches the issue so a label removed since the listing does not start work.\n - Finds the latest matching GitHub `labeled` event, and skips it if that event\n has already been tracked.\n - Picks the first free branch name, `{BRANCH_PREFIX}-{number}` or a numbered\n variant of it.\n - Clones the default branch, shallow and single-branch, into\n `{WORKSPACE_BASE}/issue-to-pr/{owner}__{repo}/issue-{number}-{event_id}`,\n sets the commit identity, and creates the branch. `origin` keeps its plain\n HTTPS URL, so the workspace holds no credential.\n - Starts an OpenHands conversation **whose working directory is that clone**,\n with the issue title, body, labels, and discussion in the prompt, and only\n the secrets named in `AGENT_SECRET_NAMES` attached.\n - Comments on the issue with the branch, the label event, and the conversation\n link.\n - Records the task with `status: \"active\"`.\n - If the clone or the conversation cannot be created, the clone is removed and\n nothing is recorded, so the next poll retries the label event.\n4. For each active task:\n - Abandons a conversation that has not reached a terminal status within two\n hours, comments on the issue, and reclaims its clone.\n - When the conversation reaches `idle`, `finished`, `error`, or `stuck`:\n - Adopts the pull request the agent opened, if GitHub says one exists for\n the branch, and comments its link on the issue. Everything below is the\n path taken when it does not.\n - Skips the pull request if the issue was closed meanwhile.\n - Reports the problem on the issue if the conversation ended in `error` or\n `stuck`.\n - Commits whatever the agent left uncommitted, on top of any commits it made\n itself.\n - Posts the agent's answer on the issue, and opens no pull request, when\n there are no commits at all - that is how an agent reports an issue too\n ambiguous to implement.\n - Otherwise pushes the branch, opens the pull request (draft by default,\n titled `[#42] <issue title>`, with the agent's summary and `Closes #42` in\n the body), and comments the link on the issue.\n - A push or pull request that fails is retried on the next two polls before\n the task is reported as failed, so a transient GitHub error does not throw\n the work away.\n5. Removes the clone of every finished task, but only after confirming the\n conversation has stopped - deleting it under a running agent would remove its\n working directory. When that cannot be confirmed the directory is left alone\n and the next poll tries again.\n6. Saves that repository's state atomically.\n\nThe completion callback fires once for the whole run.\n\n---\n\n## Additional Resources\n\n- **`references/state-schema.md`** - State JSON schema, field definitions, and the\n task lifecycle.\n- **`scripts/main.py`** - The complete automation script. Customize the five\n constants at the top before packaging.\n\n---\n\n## Troubleshooting\n\n| Symptom | Likely cause | Fix |\n|---|---|---|\n| Nothing is ever queued | Trigger label not present, or applied to a pull request rather than an issue | Apply the configured label to an issue |\n| \"Bad credentials\" in run logs | Token expired | Rotate and update `GITHUB_PERSONAL_ACCESS_TOKEN` |\n| \"The token cannot push to ...\" | Token lacks Contents: write on that repository | Issue a token with write access, or drop the repository from `REPOS` |\n| Push rejected: \"refusing to allow a Personal Access Token to create or update workflow\" | The change touches `.github/workflows/` and the token has no `workflow` scope | Add the scope to the token; the next poll retries the same branch and opens the pull request |\n| 404 on repo access | Repo name wrong or no access | Re-check the entry in `REPOS` and the token's permissions |\n| `git is not available in the automation runtime` | The runtime image has no git | Use a runtime image that ships git; the script clones, commits, and pushes with it |\n| Issue commented \"did not change any code\" | The agent judged the issue too ambiguous, or made no edits | Read its answer in the comment, add the missing detail to the issue, then re-apply the label |\n| Same issue not picked up again after new comments | Its label event was already processed | Remove and re-apply the trigger label |\n| Agent reports it cannot push or open a PR | By design - it has no credentials | No action; the automation pushes and opens the pull request after the agent stops |\n| A backlog of labelled issues starts slowly | `MAX_NEW_PER_RUN` caps how many conversations one poll starts | Wait for the next polls, or raise the cap in the script |\n| Clones remain under `issue-to-pr/` | Their conversations had not stopped yet | They are removed by a later poll once the conversation is terminal |",
|
|
243
252
|
"category": "automations"
|
|
244
253
|
},
|
|
245
254
|
{
|
|
@@ -248,7 +257,7 @@ export const SKILLS_CATALOG = [
|
|
|
248
257
|
"triggers": [
|
|
249
258
|
"/github-issue-triage"
|
|
250
259
|
],
|
|
251
|
-
"content": "# GitHub issue triage\n\nPrioritize open issues and establish acceptance criteria before marking them ready for development.\n\nCreate this automation separately from implementation and review.
|
|
260
|
+
"content": "# GitHub issue triage\n\nPrioritize open issues and establish acceptance criteria before marking them ready for development.\n\nCreate this automation separately from implementation and review. Use a\nfine-grained GitHub PAT limited to the selected repositories with Issues: read and\nwrite. Never put the token value in the automation definition or prompt.\n\nPackage `worker.py` with the shared `github_client.py` and\n`agent_conversation.py` helpers. Supply `config.json` with `repos` and the saved\nGitHub secret name; the entrypoint is `python3 worker.py`. Include that secret in\nthe selected profile so the delegated agent can use GitHub. Naming it in the\nautomation does not grant it to the agent.\n\nThe scanner is an ordinary Automation host command. It submits selected issues\nthrough the shared KV-backed conversation dispatcher, which creates or resumes\none stable conversation per issue through the Software Agent SDK. Only that\nagent workspace is local or Docker. Automation owns scheduling, cancellation,\nand cleanup.\n\nHonor `Depends on: #12, #13` lines. A dependency must be closed as completed.\nPost readable acceptance criteria and rationale. Add `ready-for-dev` only when\ncriteria are actionable; preserve existing issue labels. Unclear issues stay open\nfor clarification. Do not implement code or accept pull requests.\n\nEach scheduled run submits every changed eligible issue. A failure on one issue is\nreported and does not prevent the remaining issues from being submitted.",
|
|
252
261
|
"category": "automations"
|
|
253
262
|
},
|
|
254
263
|
{
|
|
@@ -266,7 +275,7 @@ export const SKILLS_CATALOG = [
|
|
|
266
275
|
"triggers": [
|
|
267
276
|
"/pr-reviewer:setup"
|
|
268
277
|
],
|
|
269
|
-
"content": "# GitHub PR Reviewer Automation\n\nCreate a cron automation that watches one or more GitHub repositories for pull\nrequests with a review trigger label, starts an OpenHands review conversation\nonce per label event, and publishes the AI review to GitHub.\nWindows PowerShell equivalents for the setup, packaging, upload, and API-check shell snippets are in `references/windows.md`.\n\nThe automation script is deterministic: PR discovery, label-event tracking,\nstate persistence, stale-result suppression, the repository checkout, and its\nremoval are all handled in Python. The LLM is invoked only for the review\nitself.\n\nThe script prepares each review's workspace before the agent starts: the pull\nrequest's head commit is downloaded as a tarball and extracted to a directory of\nits own, which becomes the conversation's working directory. The agent is told\nnot to clone, fetch, check out, or delete anything, and the script removes the\ncheckout once the conversation has stopped. Nothing accumulates between runs.\n\n---\n\nThe script imports shared GitHub transport from\n`scripts/github_client.py`, installed with this skill. Include it beside\n`main.py` when packaging manually, as shown below; catalog bundles include it\nautomatically.\n\n## Prerequisites\n\n### Required secret\n\nVerify that the following secret is set in **OpenHands Settings -> Secrets**:\n\n| Secret name | Token type | Minimum permissions |\n|---|---|---|\n| `GITHUB_PERSONAL_ACCESS_TOKEN` | Classic PAT | `repo` for private repos or `public_repo` for public repos |\n| `GITHUB_PERSONAL_ACCESS_TOKEN` | Fine-grained PAT | Contents: Read, Metadata: Read, Pull requests: **Read and Write**, Issues: Read and Write |\n\nPull-request **write** access is required because the agent publishes a pull\nrequest review, not just an issue comment. A token with only Pull requests: Read\nwill poll happily and then fail at the point of publishing.\n\nWhen several repositories are monitored, the token must cover all of them.\n\nCheck with:\n```bash\ncurl -s https://api.github.com/user \\\n -H \"Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN\" \\\n | python3 -c \"import json,sys; d=json.load(sys.stdin); print(d.get('login') or d.get('message'))\"\n```\n\nIf the token is missing or invalid, inform the user and stop.\n\n---\n\n## Setup Workflow\n\nFollow these steps in order.\n\n### Step 1 - Verify `GITHUB_PERSONAL_ACCESS_TOKEN`\n\nRun the `curl` check above.\n\n- If absent: *\"GITHUB_PERSONAL_ACCESS_TOKEN is not set. Please add it in\n OpenHands Settings -> Secrets.\"* Stop.\n- If the API returns `{\"message\": \"Bad credentials\"}`: tell the user the\n token is invalid and ask them to update it. Stop.\n\n### Step 2 - Collect repositories\n\nAsk: *\"Which GitHub repositories should be monitored?\n(Format: `owner/repo`, e.g. `myorg/backend`. List several separated by commas to\nreview them all from one automation.)\"*\n\nValidate access to **each** repository:\n```bash\ncurl -s \"https://api.github.com/repos/{owner}/{repo}\" \\\n -H \"Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN\" \\\n | python3 -c \"\nimport json, sys\nd = json.load(sys.stdin)\nif 'message' in d:\n print('ERROR:', d['message'])\nelse:\n print(f\\\"Accessible. Private: {d.get('private')}. Permissions: {d.get('permissions')}\\\")\n\"\n```\n\nRecord every accepted repository into `REPOS = [\"{owner}/{repo}\", ...]`. If one\nrepository fails the check, say which and ask whether to continue without it.\n\nEach repository is polled independently and keeps its own state, so pull-request\nnumbers never collide between them. The trigger label, tone, and schedule are\nshared by all of them; a repository needing different settings wants its own\nautomation.\n\n### Step 3 - Collect trigger label\n\nAsk: *\"Which PR label should trigger a review?\n(Press Enter for the default: `openhands-review`.)\"*\n\nRecord the answer as `TRIGGER_LABEL`. If the label does not exist yet, tell the\nuser that GitHub will still record the event once the label is created and\napplied to a PR.\n\nThe automation reviews a PR when it sees the latest matching `labeled` event for\nthat label. To request another review later, remove and re-apply the label.\n\n### Step 4 - Collect review tone\n\nAsk: *\"What review tone should the reviewer use?\n 1. Thorough (default) - comprehensive coverage of correctness, security, tests, style\n 2. Concise - high-signal only, skips minor style feedback\n 3. Friendly - constructive and encouraging\n(Press Enter for Thorough, or type your choice or any custom style description)\"*\n\nMap the choice to `REVIEW_TONE`:\n\n| Answer | `REVIEW_TONE` | `REVIEW_STYLE_INSTRUCTIONS` |\n|---|---|---|\n| 1 / Enter | `\"thorough\"` | `\"\"` |\n| 2 | `\"concise\"` | `\"\"` |\n| 3 | `\"friendly\"` | `\"\"` |\n| Custom text, e.g. `strict but kind` | `\"thorough\"` | the custom text verbatim |\n\n### Step 5 - Collect cron schedule\n\nAsk: *\"How often should the automation poll for labeled PRs?\n(Press Enter for the default: every 5 minutes.\nUse a cron expression for a different interval, e.g. `0 * * * *` = hourly)\"*\n\nDefault: `*/5 * * * *`.\n\nRecord as `CRON_SCHEDULE`.\n\n### Step 6 - Generate the automation script\n\nRead `scripts/main.py` from this skill's directory. Apply exactly six constant\nsubstitutions near the top of the file:\n\n> The script also reads a `config.json` shipped beside it, if there is one, over\n> these constants. That is how the catalog entry\n> (`automations/catalog/github-pr-reviewer/`) configures an unmodified copy,\n> since a declarative host cannot rewrite Python. This setup path substitutes the\n> constants and ships no `config.json`, so the two never collide.\n\n| Placeholder | Replace with |\n|---|---|\n| `REPOS = [\"owner/repo\"]` | `REPOS = [\"{owner_repo}\", ...]` - one entry per repository collected in Step 2 |\n| `TRIGGER_LABEL = \"openhands-review\"` | `TRIGGER_LABEL = \"{trigger_label}\"` |\n| `REVIEW_TONE = \"thorough\"` | `REVIEW_TONE = \"{review_tone}\"` |\n| `REVIEW_STYLE_INSTRUCTIONS = \"\"` | `REVIEW_STYLE_INSTRUCTIONS = \"{style_instructions}\"` |\n| `REPO_REVIEW_GUIDE_PATH = \".agents/skills/custom-codereview-guide.md\"` | leave unchanged to auto-load a repo review guide at this path, or set to `\"\"` to disable |\n| `DEFAULT_OPENHANDS_URL = \"http://localhost:8000\"` | leave unchanged unless the user has a preference |\n\nUse a safe string writer such as `json.dumps(value)` when inserting user-provided\nrepository names, labels, or style instructions into Python string literals.\n`json.dumps(list_of_repos)` produces the whole `REPOS` list safely in one step.\n\nRun these commands from this skill's directory and write the customized script\nto a temporary build directory:\n```bash\nmkdir -p /tmp/pr-reviewer-build\ncp -L scripts/github_client.py /tmp/pr-reviewer-build/github_client.py\n# write the customized main.py to /tmp/pr-reviewer-build/main.py\n```\n\nValidate syntax before packaging:\n```bash\npython3 -m py_compile /tmp/pr-reviewer-build/main.py && echo \"Syntax OK\"\n```\n\nFix any syntax errors before proceeding.\n\n### Step 7 - Package and upload\n\nDetermine the Automation backend URL and auth from the `<RUNTIME_SERVICES>`\nblock in your system context:\n- **OPENHANDS_HOST**: the Automation backend `url_from_agent`\n- **Auth**: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY`\n\n```bash\ntar -czf /tmp/pr-reviewer.tar.gz -C /tmp/pr-reviewer-build .\n\nTARBALL_PATH=$(curl -s -X POST \\\n \"${OPENHANDS_HOST}/api/automation/v1/uploads?name=github-pr-reviewer\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/gzip\" \\\n --data-binary @/tmp/pr-reviewer.tar.gz \\\n | python3 -c \"import json,sys; print(json.load(sys.stdin)['tarball_path'])\")\n\necho \"Uploaded: $TARBALL_PATH\"\n```\n\n### Step 8 - Register the automation\n\n```bash\ncurl -s -X POST \"${OPENHANDS_HOST}/api/automation/v1\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\"name\\\": \\\"GitHub PR Reviewer: {repo_summary} label {trigger_label}\\\",\n \\\"trigger\\\": {\\\"type\\\": \\\"cron\\\", \\\"schedule\\\": \\\"{cron_schedule}\\\"},\n \\\"tarball_path\\\": \\\"$TARBALL_PATH\\\",\n \\\"entrypoint\\\": \\\"python3 main.py\\\",\n \\\"timeout\\\": 600\n }\" | python3 -m json.tool\n```\n\nUse the single repository as `{repo_summary}` when there is one, and something\nlike `3 repos` when there are several. A poll now downloads a tarball per queued\nreview, so the timeout allows for that; a run never waits for a review to\nfinish, only for it to be started.\n\nRecord the returned `id`.\n\n### Step 9 - Confirm\n\nTell the user:\n\n> ✅ **GitHub PR Reviewer** is running!\n>\n> - Automation ID: `{id}`\n> - Repositories: `{owner}/{repo}`, ... (one line each)\n> - Trigger label: `{trigger_label}`\n> - Review tone: `{tone}`\n> - Polling schedule: `{cron_schedule}`\n> - State file per repository:\n> `~/.openhands/workspaces/automation-state/github_pr_reviewer_label_event_{id}_{owner}__{repo}.json`\n>\n> Apply the `{trigger_label}` label to a pull request to queue a review. Each\n> label event is processed once. To request another review, remove and re-apply\n> the label.\n>\n> The review is published as a pull request review on the head commit, with\n> inline comments where a finding maps to a changed line.\n\n---\n\n## Runtime Behaviour (per poll)\n\nEach cron run executes `main.py`, which resolves and validates\n`GITHUB_PERSONAL_ACCESS_TOKEN` once, then processes every repository in `REPOS`\nindependently. One repository failing does not stop the others; the run fails\nonly if every repository fails.\n\nFor each repository:\n\n1. Loads that repository's state (see `references/state-schema.md`).\n2. Verifies repository access.\n3. Lists open PRs, newest-updated first.\n4. For each open PR carrying `TRIGGER_LABEL`:\n - Refetches current PR metadata to avoid acting on stale list data.\n - Finds the latest matching GitHub `labeled` issue event.\n - Skips the event if it has already been tracked.\n - Downloads the PR's head commit as a tarball and extracts it to\n `{WORKSPACE_BASE}/repositories/{owner}__{repo}/pr-{number}-{sha12}`. The\n archive is checked as it is unpacked: a single root, no absolute or `..`\n paths, and symlinks skipped rather than materialised.\n - Starts an OpenHands conversation **whose working directory is that\n checkout**, with a review prompt carrying PR metadata, the exact head SHA,\n and label event details.\n - Posts an acknowledgement comment with the label event, head SHA, and\n conversation link.\n - Records the review in state with `status: \"active\"` and the checkout path.\n - If the checkout or the conversation cannot be created, the checkout is\n removed and nothing is recorded, so the next poll retries the label event.\n5. For each active review conversation:\n - Marks it closed without posting if the PR has closed or merged.\n - Suppresses stale results if the PR head SHA changed after the review was\n queued.\n - When the conversation reaches `idle`, `finished`, `error`, or `stuck`,\n asks GitHub whether a review by the token's own user exists for that head\n SHA. If it does, the review is complete. If it does not, the agent's final\n response is posted as a comment so the work is not lost.\n - Abandons a conversation that has not reached a terminal status within two\n hours, so its checkout can be reclaimed.\n6. Removes the checkout of every finished review, but only after confirming the\n conversation has stopped - deleting it under a running agent would remove its\n working directory. When that cannot be confirmed the directory is left alone\n and the next poll tries again.\n7. Saves that repository's state atomically.\n\nThe completion callback fires once for the whole run.\n\n---\n\n## Additional Resources\n\n- **`references/state-schema.md`** - State JSON schema, field definitions, and\n review lifecycle diagram.\n- **`scripts/main.py`** - The complete automation script. Customize the five\n constants at the top before packaging.\n- **`tests/test_main.py`** - Unit tests for the checkout, its removal, and state\n handling. Run them from the skill root with `python -m pytest tests/` after\n editing the script.\n\n---\n\n## Troubleshooting\n\n| Symptom | Likely cause | Fix |\n|---|---|---|\n| Bot never queues reviews | Trigger label not present or no matching `labeled` event | Apply the configured label to the PR |\n| \"Bad credentials\" in run logs | Token expired | Rotate and update `GITHUB_PERSONAL_ACCESS_TOKEN` |\n| 404 on repo access | Repo name wrong or no access | Re-check the entry in `REPOS` and the token's permissions |\n| One repository is skipped, others work | That repository failed its access check | Read the `=== owner/repo ===` block in the run log |\n| Same PR not reviewed after new commits | Label event was already processed | Remove and re-apply the trigger label |\n| Review result never posts | Conversation still running or stuck | Open the conversation link from the acknowledgement comment |\n| Stale review suppressed | PR head SHA changed while the agent was reviewing | Re-apply the trigger label after the latest commit |\n| Review arrives as a plain comment, not a review | Publishing failed, so the script posted the text as a fallback | Check that the token has Pull requests: Read and Write |\n| Agent reports it cannot clone the repo | Prompt asked it not to; the workspace is already the checkout | No action - the code is at the head SHA in its working directory |\n| Checkouts remain under `repositories/` | Their conversations had not stopped yet | They are removed by a later poll once the conversation is terminal |",
|
|
278
|
+
"content": "# GitHub PR Reviewer Automation\n\n## Agent Canvas catalog\n\nFor new Agent Canvas installations, use the **GitHub code review** catalog\nentry. Its deterministic `worker.py` scanner delegates each labeled exact head\nto a stable conversation using the selected agent profile. The manual upload\nflow below remains for existing deployments and is deprecated for new installations.\n\nCreate a cron automation that watches one or more GitHub repositories for pull\nrequests with a review trigger label, starts an OpenHands review conversation\nonce per label event, and publishes the AI review to GitHub.\nWindows PowerShell equivalents for the setup, packaging, upload, and API-check shell snippets are in `references/windows.md`.\n\nThe automation script is deterministic: PR discovery, label-event tracking,\nstate persistence, stale-result suppression, the repository checkout, and its\nremoval are all handled in Python. The LLM is invoked only for the review\nitself.\n\nThe script prepares each review's workspace before the agent starts: the pull\nrequest's head commit is downloaded as a tarball and extracted to a directory of\nits own, which becomes the conversation's working directory. The agent is told\nnot to clone, fetch, check out, or delete anything, and the script removes the\ncheckout once the conversation has stopped. Nothing accumulates between runs.\n\n---\n\nThe script imports shared GitHub transport from\n`scripts/github_client.py`, installed with this skill. Include it beside\n`main.py` when packaging manually, as shown below; catalog bundles include it\nautomatically.\n\n## Prerequisites\n\n### Required secret\n\nVerify that the following secret is set in **OpenHands Settings -> Secrets**:\n\n| Secret name | Token type | Minimum permissions |\n|---|---|---|\n| `GITHUB_PERSONAL_ACCESS_TOKEN` | Classic PAT | `repo` for private repos or `public_repo` for public repos |\n| `GITHUB_PERSONAL_ACCESS_TOKEN` | Fine-grained PAT | Contents: Read, Metadata: Read, Pull requests: **Read and Write**, Issues: Read and Write |\n\nPull-request **write** access is required because the agent publishes a pull\nrequest review, not just an issue comment. A token with only Pull requests: Read\nwill poll happily and then fail at the point of publishing.\n\nWhen several repositories are monitored, the token must cover all of them.\n\nCheck with:\n```bash\ncurl -s https://api.github.com/user \\\n -H \"Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN\" \\\n | python3 -c \"import json,sys; d=json.load(sys.stdin); print(d.get('login') or d.get('message'))\"\n```\n\nIf the token is missing or invalid, inform the user and stop.\n\n---\n\n## Setup Workflow\n\nFollow these steps in order.\n\n### Step 1 - Verify `GITHUB_PERSONAL_ACCESS_TOKEN`\n\nRun the `curl` check above.\n\n- If absent: *\"GITHUB_PERSONAL_ACCESS_TOKEN is not set. Please add it in\n OpenHands Settings -> Secrets.\"* Stop.\n- If the API returns `{\"message\": \"Bad credentials\"}`: tell the user the\n token is invalid and ask them to update it. Stop.\n\n### Step 2 - Collect repositories\n\nAsk: *\"Which GitHub repositories should be monitored?\n(Format: `owner/repo`, e.g. `myorg/backend`. List several separated by commas to\nreview them all from one automation.)\"*\n\nValidate access to **each** repository:\n```bash\ncurl -s \"https://api.github.com/repos/{owner}/{repo}\" \\\n -H \"Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN\" \\\n | python3 -c \"\nimport json, sys\nd = json.load(sys.stdin)\nif 'message' in d:\n print('ERROR:', d['message'])\nelse:\n print(f\\\"Accessible. Private: {d.get('private')}. Permissions: {d.get('permissions')}\\\")\n\"\n```\n\nRecord every accepted repository into `REPOS = [\"{owner}/{repo}\", ...]`. If one\nrepository fails the check, say which and ask whether to continue without it.\n\nEach repository is polled independently and keeps its own state, so pull-request\nnumbers never collide between them. The trigger label, tone, and schedule are\nshared by all of them; a repository needing different settings wants its own\nautomation.\n\n### Step 3 - Collect trigger label\n\nAsk: *\"Which PR label should trigger a review?\n(Press Enter for the default: `openhands-review`.)\"*\n\nRecord the answer as `TRIGGER_LABEL`. If the label does not exist yet, tell the\nuser that GitHub will still record the event once the label is created and\napplied to a PR.\n\nThe automation reviews a PR when it sees the latest matching `labeled` event for\nthat label. To request another review later, remove and re-apply the label.\n\n### Step 4 - Collect review tone\n\nAsk: *\"What review tone should the reviewer use?\n 1. Thorough (default) - comprehensive coverage of correctness, security, tests, style\n 2. Concise - high-signal only, skips minor style feedback\n 3. Friendly - constructive and encouraging\n(Press Enter for Thorough, or type your choice or any custom style description)\"*\n\nMap the choice to `REVIEW_TONE`:\n\n| Answer | `REVIEW_TONE` | `REVIEW_STYLE_INSTRUCTIONS` |\n|---|---|---|\n| 1 / Enter | `\"thorough\"` | `\"\"` |\n| 2 | `\"concise\"` | `\"\"` |\n| 3 | `\"friendly\"` | `\"\"` |\n| Custom text, e.g. `strict but kind` | `\"thorough\"` | the custom text verbatim |\n\n### Step 5 - Collect cron schedule\n\nAsk: *\"How often should the automation poll for labeled PRs?\n(Press Enter for the default: every 5 minutes.\nUse a cron expression for a different interval, e.g. `0 * * * *` = hourly)\"*\n\nDefault: `*/5 * * * *`.\n\nRecord as `CRON_SCHEDULE`.\n\n### Step 6 - Generate the automation script\n\nRead `scripts/main.py` from this skill's directory. Apply exactly six constant\nsubstitutions near the top of the file:\n\n> The script also reads a `config.json` shipped beside it, if there is one, over\n> these constants. That is how the catalog entry\n> (`automations/catalog/github-pr-reviewer/`) configures an unmodified copy,\n> since a declarative host cannot rewrite Python. This setup path substitutes the\n> constants and ships no `config.json`, so the two never collide.\n\n| Placeholder | Replace with |\n|---|---|\n| `REPOS = [\"owner/repo\"]` | `REPOS = [\"{owner_repo}\", ...]` - one entry per repository collected in Step 2 |\n| `TRIGGER_LABEL = \"openhands-review\"` | `TRIGGER_LABEL = \"{trigger_label}\"` |\n| `REVIEW_TONE = \"thorough\"` | `REVIEW_TONE = \"{review_tone}\"` |\n| `REVIEW_STYLE_INSTRUCTIONS = \"\"` | `REVIEW_STYLE_INSTRUCTIONS = \"{style_instructions}\"` |\n| `REPO_REVIEW_GUIDE_PATH = \".agents/skills/custom-codereview-guide.md\"` | leave unchanged to auto-load a repo review guide at this path, or set to `\"\"` to disable |\n| `DEFAULT_OPENHANDS_URL = \"http://localhost:8000\"` | leave unchanged unless the user has a preference |\n\nUse a safe string writer such as `json.dumps(value)` when inserting user-provided\nrepository names, labels, or style instructions into Python string literals.\n`json.dumps(list_of_repos)` produces the whole `REPOS` list safely in one step.\n\nRun these commands from this skill's directory and write the customized script\nto a temporary build directory:\n```bash\nmkdir -p /tmp/pr-reviewer-build\ncp -L scripts/github_client.py /tmp/pr-reviewer-build/github_client.py\n# write the customized main.py to /tmp/pr-reviewer-build/main.py\n```\n\nValidate syntax before packaging:\n```bash\npython3 -m py_compile /tmp/pr-reviewer-build/main.py && echo \"Syntax OK\"\n```\n\nFix any syntax errors before proceeding.\n\n### Step 7 - Package and upload\n\nDetermine the Automation backend URL and auth from the `<RUNTIME_SERVICES>`\nblock in your system context:\n- **OPENHANDS_HOST**: the Automation backend `url_from_agent`\n- **Auth**: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY`\n\n```bash\ntar -czf /tmp/pr-reviewer.tar.gz -C /tmp/pr-reviewer-build .\n\nTARBALL_PATH=$(curl -s -X POST \\\n \"${OPENHANDS_HOST}/api/automation/v1/uploads?name=github-pr-reviewer\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/gzip\" \\\n --data-binary @/tmp/pr-reviewer.tar.gz \\\n | python3 -c \"import json,sys; print(json.load(sys.stdin)['tarball_path'])\")\n\necho \"Uploaded: $TARBALL_PATH\"\n```\n\n### Step 8 - Register the automation\n\n```bash\ncurl -s -X POST \"${OPENHANDS_HOST}/api/automation/v1\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\"name\\\": \\\"GitHub PR Reviewer: {repo_summary} label {trigger_label}\\\",\n \\\"trigger\\\": {\\\"type\\\": \\\"cron\\\", \\\"schedule\\\": \\\"{cron_schedule}\\\"},\n \\\"tarball_path\\\": \\\"$TARBALL_PATH\\\",\n \\\"entrypoint\\\": \\\"python3 main.py\\\",\n \\\"timeout\\\": 600\n }\" | python3 -m json.tool\n```\n\nUse the single repository as `{repo_summary}` when there is one, and something\nlike `3 repos` when there are several. A poll now downloads a tarball per queued\nreview, so the timeout allows for that; a run never waits for a review to\nfinish, only for it to be started.\n\nRecord the returned `id`.\n\n### Step 9 - Confirm\n\nTell the user:\n\n> ✅ **GitHub PR Reviewer** is running!\n>\n> - Automation ID: `{id}`\n> - Repositories: `{owner}/{repo}`, ... (one line each)\n> - Trigger label: `{trigger_label}`\n> - Review tone: `{tone}`\n> - Polling schedule: `{cron_schedule}`\n> - State file per repository:\n> `~/.openhands/workspaces/automation-state/github_pr_reviewer_label_event_{id}_{owner}__{repo}.json`\n>\n> Apply the `{trigger_label}` label to a pull request to queue a review. Each\n> label event is processed once. To request another review, remove and re-apply\n> the label.\n>\n> The review is published as a pull request review on the head commit, with\n> inline comments where a finding maps to a changed line.\n\n---\n\n## Runtime Behaviour (per poll)\n\nEach cron run executes `main.py`, which resolves and validates\n`GITHUB_PERSONAL_ACCESS_TOKEN` once, then processes every repository in `REPOS`\nindependently. One repository failing does not stop the others; the run fails\nonly if every repository fails.\n\nFor each repository:\n\n1. Loads that repository's state (see `references/state-schema.md`).\n2. Verifies repository access.\n3. Lists open PRs, newest-updated first.\n4. For each open PR carrying `TRIGGER_LABEL`:\n - Refetches current PR metadata to avoid acting on stale list data.\n - Finds the latest matching GitHub `labeled` issue event.\n - Skips the event if it has already been tracked.\n - Downloads the PR's head commit as a tarball and extracts it to\n `{WORKSPACE_BASE}/repositories/{owner}__{repo}/pr-{number}-{sha12}`. The\n archive is checked as it is unpacked: a single root, no absolute or `..`\n paths, and symlinks skipped rather than materialised.\n - Starts an OpenHands conversation **whose working directory is that\n checkout**, with a review prompt carrying PR metadata, the exact head SHA,\n and label event details.\n - Posts an acknowledgement comment with the label event, head SHA, and\n conversation link.\n - Records the review in state with `status: \"active\"` and the checkout path.\n - If the checkout or the conversation cannot be created, the checkout is\n removed and nothing is recorded, so the next poll retries the label event.\n5. For each active review conversation:\n - Marks it closed without posting if the PR has closed or merged.\n - Suppresses stale results if the PR head SHA changed after the review was\n queued.\n - When the conversation reaches `idle`, `finished`, `error`, or `stuck`,\n asks GitHub whether a review by the token's own user exists for that head\n SHA. If it does, the review is complete. If it does not, the agent's final\n response is posted as a comment so the work is not lost.\n - Abandons a conversation that has not reached a terminal status within two\n hours, so its checkout can be reclaimed.\n6. Removes the checkout of every finished review, but only after confirming the\n conversation has stopped - deleting it under a running agent would remove its\n working directory. When that cannot be confirmed the directory is left alone\n and the next poll tries again.\n7. Saves that repository's state atomically.\n\nThe completion callback fires once for the whole run.\n\n---\n\n## Additional Resources\n\n- **`references/state-schema.md`** - State JSON schema, field definitions, and\n review lifecycle diagram.\n- **`scripts/main.py`** - The complete automation script. Customize the five\n constants at the top before packaging.\n- **`tests/test_main.py`** - Unit tests for the checkout, its removal, and state\n handling. Run them from the skill root with `python -m pytest tests/` after\n editing the script.\n\n---\n\n## Troubleshooting\n\n| Symptom | Likely cause | Fix |\n|---|---|---|\n| Bot never queues reviews | Trigger label not present or no matching `labeled` event | Apply the configured label to the PR |\n| \"Bad credentials\" in run logs | Token expired | Rotate and update `GITHUB_PERSONAL_ACCESS_TOKEN` |\n| 404 on repo access | Repo name wrong or no access | Re-check the entry in `REPOS` and the token's permissions |\n| One repository is skipped, others work | That repository failed its access check | Read the `=== owner/repo ===` block in the run log |\n| Same PR not reviewed after new commits | Label event was already processed | Remove and re-apply the trigger label |\n| Review result never posts | Conversation still running or stuck | Open the conversation link from the acknowledgement comment |\n| Stale review suppressed | PR head SHA changed while the agent was reviewing | Re-apply the trigger label after the latest commit |\n| Review arrives as a plain comment, not a review | Publishing failed, so the script posted the text as a fallback | Check that the token has Pull requests: Read and Write |\n| Agent reports it cannot clone the repo | Prompt asked it not to; the workspace is already the checkout | No action - the code is at the head SHA in its working directory |\n| Checkouts remain under `repositories/` | Their conversations had not stopped yet | They are removed by a later poll once the conversation is terminal |",
|
|
270
279
|
"category": "automations"
|
|
271
280
|
},
|
|
272
281
|
{
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "iterate",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Iterate on a GitHub pull request — drive it through CI, code review, and QA until it is merge-ready.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": ["github", "ci", "review", "qa", "pull-request", "iterate"]
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "iterate",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Iterate on a GitHub pull request — drive it through CI, code review, and QA until it is merge-ready.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": ["github", "ci", "review", "qa", "pull-request", "iterate"]
|
|
13
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jira-issue-to-pr",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Deploy a cron-based OpenHands automation that watches a Jira Cloud project for issues labeled with a configurable label (default: create-pr) and spawns an agent conversation to create a GitHub pull request for each new issue found. The target GitHub repo is read from the ticket body - no repo parameter required at deploy time.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"jira",
|
|
14
|
+
"atlassian",
|
|
15
|
+
"github",
|
|
16
|
+
"pull-request",
|
|
17
|
+
"automation"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jira-issue-to-pr",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Deploy a cron-based OpenHands automation that watches a Jira Cloud project for issues labeled with a configurable label (default: create-pr) and spawns an agent conversation to create a GitHub pull request for each new issue found. The target GitHub repo is read from the ticket body - no repo parameter required at deploy time.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"jira",
|
|
14
|
+
"atlassian",
|
|
15
|
+
"github",
|
|
16
|
+
"pull-request",
|
|
17
|
+
"automation"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jupyter",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Read, modify, execute, and convert Jupyter notebooks programmatically. Use when working with .ipynb files for data science workflows, including editing cells, clearing outputs, or converting to oth...",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"jupyter",
|
|
14
|
+
"notebook",
|
|
15
|
+
"ipynb",
|
|
16
|
+
"data-science"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jupyter",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Read, modify, execute, and convert Jupyter notebooks programmatically. Use when working with .ipynb files for data science workflows, including editing cells, clearing outputs, or converting to oth...",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"jupyter",
|
|
14
|
+
"notebook",
|
|
15
|
+
"ipynb",
|
|
16
|
+
"data-science"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kubernetes",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Set up and manage local Kubernetes clusters using KIND (Kubernetes IN Docker). Use when testing Kubernetes applications locally or developing cloud-native workloads.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"kubernetes",
|
|
14
|
+
"k8s",
|
|
15
|
+
"kind",
|
|
16
|
+
"cloud-native"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kubernetes",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Set up and manage local Kubernetes clusters using KIND (Kubernetes IN Docker). Use when testing Kubernetes applications locally or developing cloud-native workloads.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"kubernetes",
|
|
14
|
+
"k8s",
|
|
15
|
+
"kind",
|
|
16
|
+
"cloud-native"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "learn-from-code-review",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Distill code review feedback from GitHub PRs into reusable skills and guidelines. Use when users ask to learn from code reviews, extract review patterns, or generate coding standards from historica...",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"code-review",
|
|
14
|
+
"learning",
|
|
15
|
+
"skills",
|
|
16
|
+
"guidelines",
|
|
17
|
+
"pr-feedback"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "learn-from-code-review",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Distill code review feedback from GitHub PRs into reusable skills and guidelines. Use when users ask to learn from code reviews, extract review patterns, or generate coding standards from historica...",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"code-review",
|
|
14
|
+
"learning",
|
|
15
|
+
"skills",
|
|
16
|
+
"guidelines",
|
|
17
|
+
"pr-feedback"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "linear",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Interact with Linear project management - query issues, update status, create tickets using the Linear GraphQL API.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"linear",
|
|
14
|
+
"ticket",
|
|
15
|
+
"issue",
|
|
16
|
+
"project-management",
|
|
17
|
+
"graphql"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "linear",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Interact with Linear project management - query issues, update status, create tickets using the Linear GraphQL API.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"linear",
|
|
14
|
+
"ticket",
|
|
15
|
+
"issue",
|
|
16
|
+
"project-management",
|
|
17
|
+
"graphql"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "linear-triage",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Create an automation that triages new Linear issues by inspecting title, description, team, and recent related issues. Suggests labels, priority, likely owner, and potential duplicates, then posts a clarifying comment.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"linear",
|
|
14
|
+
"triage",
|
|
15
|
+
"issues",
|
|
16
|
+
"labels",
|
|
17
|
+
"priority",
|
|
18
|
+
"automation",
|
|
19
|
+
"integration"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "linear-triage",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Create an automation that triages new Linear issues by inspecting title, description, team, and recent related issues. Suggests labels, priority, likely owner, and potential duplicates, then posts a clarifying comment.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"linear",
|
|
14
|
+
"triage",
|
|
15
|
+
"issues",
|
|
16
|
+
"labels",
|
|
17
|
+
"priority",
|
|
18
|
+
"automation",
|
|
19
|
+
"integration"
|
|
20
|
+
]
|
|
21
|
+
}
|