@openhands/extensions 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/.agents/skills/custom-codereview-guide.md +7 -0
  2. package/.github/dependabot.yml +24 -0
  3. package/.github/scripts/check_issue_readiness.py +272 -0
  4. package/.github/scripts/check_pr_description.py +226 -0
  5. package/.github/scripts/post-readiness-comment.mjs +184 -0
  6. package/.github/scripts/refresh_linked_pr_checks.py +171 -0
  7. package/.github/workflows/issue-readiness-check.yml +142 -0
  8. package/.github/workflows/pr-description-check.yml +37 -0
  9. package/.release-please-manifest.json +1 -1
  10. package/AGENTS.md +16 -0
  11. package/README.md +10 -2
  12. package/automations/bundle-index.js +1 -1
  13. package/automations/catalog/github-agents-md-maintainer/manifest.json +5 -0
  14. package/automations/catalog/github-issue-to-pr/manifest.json +5 -0
  15. package/automations/catalog/github-pr-reviewer/manifest.json +5 -0
  16. package/automations/catalog/github-repo-monitor/manifest.json +5 -0
  17. package/automations/catalog/jira-issue-to-bitbucket-pr/manifest.json +23 -0
  18. package/automations/catalog/jira-issue-to-gitlab-mr/manifest.json +23 -0
  19. package/automations/catalog/linear-issue-to-bitbucket-pr/manifest.json +23 -0
  20. package/automations/catalog/linear-issue-to-github-pr/manifest.json +23 -0
  21. package/automations/catalog/linear-issue-to-gitlab-mr/manifest.json +23 -0
  22. package/automations/catalog/news-digest/manifest.json +5 -0
  23. package/automations/catalog/qa-changes/manifest.json +72 -0
  24. package/automations/catalog/slack-github-linear-daily-organization.json +27 -0
  25. package/automations/catalog-index.js +18 -6
  26. package/automations/catalog.schema.json +25 -0
  27. package/automations/index.d.ts +16 -0
  28. package/integrations/catalog/datadog.json +2 -2
  29. package/integrations/catalog/gitlab.json +41 -0
  30. package/integrations/catalog/notion.json +1 -1
  31. package/integrations/catalog-index.js +71 -69
  32. package/integrations/index.d.ts +1 -1
  33. package/marketplaces/openhands-extensions.json +21 -6
  34. package/package.json +1 -1
  35. package/plugins/pr-review/scripts/prompt.py +2 -0
  36. package/pyproject.toml +1 -1
  37. package/python/openhands_extensions/_version.py +1 -1
  38. package/skills/github-pr-reviewer/SKILL.md +2 -1
  39. package/skills/github-pr-reviewer/scripts/main.py +45 -4
  40. package/skills/github-pr-reviewer/tests/test_main.py +74 -0
  41. package/skills/index.js +11 -2
  42. package/skills/openhands-sdk/SKILL.md +1 -0
  43. package/skills/ticket-to-code-change/.plugin/plugin.json +21 -0
  44. package/skills/ticket-to-code-change/README.md +5 -0
  45. package/skills/ticket-to-code-change/SKILL.md +50 -0
  46. package/skills/ticket-to-code-change/commands/ticket-to-code-change-setup.md +8 -0
  47. package/tests/test_automation_setup.py +22 -0
  48. package/tests/test_catalogs.py +2 -2
  49. package/tests/test_check_issue_readiness.py +135 -0
  50. package/tests/test_check_pr_description.py +228 -0
  51. package/tests/test_pr_review_prompt.py +10 -0
  52. package/tests/test_refresh_linked_pr_checks.py +130 -0
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Post (or update) a `ready-for-dev` readiness comment on an issue.
5
+ *
6
+ * Upserts by a hidden HTML marker so repeated runs update the same comment
7
+ * instead of spamming duplicates. Uses the GITHUB_TOKEN available to the
8
+ * workflow via the GH_TOKEN env var.
9
+ *
10
+ * Usage:
11
+ * node post-readiness-comment.mjs \
12
+ * --issue-number 123 \
13
+ * --repo OpenHands/extensions \
14
+ * --reasons-file /tmp/reasons.txt \
15
+ * --ready # optional: post a "ready" message instead of "not ready"
16
+ */
17
+
18
+ import { readFileSync } from "node:fs";
19
+
20
+ const MARKER = "<!-- issue-readiness-check -->";
21
+ const API_ROOT = process.env.GITHUB_API_URL ?? "https://api.github.com";
22
+
23
+ function parseArgs(argv) {
24
+ const args = {};
25
+ for (let i = 0; i < argv.length; i += 1) {
26
+ const arg = argv[i];
27
+ if (!arg.startsWith("--")) continue;
28
+ const [rawKey, inlineValue] = arg.slice(2).split("=", 2);
29
+ const key = rawKey.replaceAll("-", "_");
30
+ if (inlineValue !== undefined) {
31
+ args[key] = inlineValue;
32
+ continue;
33
+ }
34
+ const next = argv[i + 1];
35
+ if (next && !next.startsWith("--")) {
36
+ args[key] = next;
37
+ i += 1;
38
+ } else {
39
+ args[key] = "";
40
+ }
41
+ }
42
+ return args;
43
+ }
44
+
45
+ function requireValue(name, value) {
46
+ if (!value) throw new Error(`Missing required value: ${name}`);
47
+ return value;
48
+ }
49
+
50
+ async function listComments(repo, issueNumber, token) {
51
+ // Paginate through all comments so the marker is found even on issues
52
+ // with >100 comments.
53
+ const all = [];
54
+ let page = 1;
55
+ while (true) {
56
+ const url = `${API_ROOT}/repos/${repo}/issues/${issueNumber}/comments?per_page=100&page=${page}`;
57
+ const resp = await fetch(url, {
58
+ headers: {
59
+ Authorization: `Bearer ${token}`,
60
+ Accept: "application/vnd.github+json",
61
+ "X-GitHub-Api-Version": "2022-11-28",
62
+ },
63
+ });
64
+ if (!resp.ok) {
65
+ throw new Error(`Failed to list comments: ${resp.status} ${await resp.text()}`);
66
+ }
67
+ const batch = await resp.json();
68
+ all.push(...batch);
69
+ if (batch.length < 100) break;
70
+ page += 1;
71
+ }
72
+ return all;
73
+ }
74
+
75
+ async function createComment(repo, issueNumber, body, token) {
76
+ const url = `${API_ROOT}/repos/${repo}/issues/${issueNumber}/comments`;
77
+ const resp = await fetch(url, {
78
+ method: "POST",
79
+ headers: {
80
+ Authorization: `Bearer ${token}`,
81
+ Accept: "application/vnd.github+json",
82
+ "X-GitHub-Api-Version": "2022-11-28",
83
+ "Content-Type": "application/json",
84
+ },
85
+ body: JSON.stringify({ body }),
86
+ });
87
+ if (!resp.ok) {
88
+ throw new Error(`Failed to create comment: ${resp.status} ${await resp.text()}`);
89
+ }
90
+ return resp.json();
91
+ }
92
+
93
+ async function updateComment(repo, commentId, body, token) {
94
+ const url = `${API_ROOT}/repos/${repo}/issues/comments/${commentId}`;
95
+ const resp = await fetch(url, {
96
+ method: "PATCH",
97
+ headers: {
98
+ Authorization: `Bearer ${token}`,
99
+ Accept: "application/vnd.github+json",
100
+ "X-GitHub-Api-Version": "2022-11-28",
101
+ "Content-Type": "application/json",
102
+ },
103
+ body: JSON.stringify({ body }),
104
+ });
105
+ if (!resp.ok) {
106
+ throw new Error(`Failed to update comment: ${resp.status} ${await resp.text()}`);
107
+ }
108
+ return resp.json();
109
+ }
110
+
111
+ async function main() {
112
+ const args = parseArgs(process.argv.slice(2));
113
+ const issueNumber = requireValue("issue-number", args.issue_number);
114
+ const repo = requireValue("repo", args.repo);
115
+ const token = process.env.GH_TOKEN;
116
+ if (!token) throw new Error("GH_TOKEN env var is required");
117
+
118
+ const isReady = args.ready === "" || args.ready === "true";
119
+ const reasonsFile = args.reasons_file || args.reasons;
120
+ const reasons = reasonsFile
121
+ ? readFileSync(reasonsFile, "utf8")
122
+ .split("\n")
123
+ .map((line) => line.trim())
124
+ .filter(Boolean)
125
+ : [];
126
+
127
+ const reasonList = reasons.length
128
+ ? reasons.map((r) => `- ${r}`).join("\n")
129
+ : "- The issue does not meet the ready-for-dev criteria.";
130
+
131
+ const body = isReady
132
+ ? [
133
+ MARKER,
134
+ "### \u2705 `ready-for-dev` label applied",
135
+ "",
136
+ "This issue meets the readiness criteria and is ready for contribution.",
137
+ "A contributor can now open a PR that links this issue (`Fixes #<number>`).",
138
+ "",
139
+ "_This is an automated readiness check posted by a workflow._",
140
+ ].join("\n")
141
+ : [
142
+ MARKER,
143
+ "### \u26a0\ufe0f Not yet `ready-for-dev`",
144
+ "",
145
+ "This issue does not yet meet the readiness criteria, so the",
146
+ "`ready-for-dev` label has not been applied (or has been removed).",
147
+ "The criteria are type-specific:",
148
+ "",
149
+ "**Bug reports** (`bug` label):",
150
+ "- The `### Actual Behavior` section must describe reproducible steps",
151
+ " and include a command such as `uv run pytest`, `pytest`, `python`,",
152
+ " `pip`, `npm run`, or `node`.",
153
+ "- An `### Acceptance Criteria` section with at least one checklist item (`- [ ] ...`).",
154
+ "",
155
+ "**Enhancements** (`enhancement` label):",
156
+ "- A `### Desired Behavior` section.",
157
+ "- An `### Acceptance Criteria` section with at least one checklist item.",
158
+ "",
159
+ "What is missing:",
160
+ "",
161
+ reasonList,
162
+ "",
163
+ "Edit the issue to address the gaps and the `ready-for-dev` label will be",
164
+ "applied automatically.",
165
+ "",
166
+ "_This is an automated readiness check posted by a workflow._",
167
+ ].join("\n");
168
+
169
+ const comments = await listComments(repo, issueNumber, token);
170
+ const existing = comments.find((comment) => comment.body?.includes(MARKER));
171
+
172
+ if (existing) {
173
+ await updateComment(repo, existing.id, body, token);
174
+ console.log(`Updated readiness comment ${existing.id} on issue #${issueNumber}`);
175
+ } else {
176
+ const created = await createComment(repo, issueNumber, body, token);
177
+ console.log(`Created readiness comment ${created.id} on issue #${issueNumber}`);
178
+ }
179
+ }
180
+
181
+ main().catch((error) => {
182
+ console.error(error);
183
+ process.exit(1);
184
+ });
@@ -0,0 +1,171 @@
1
+ """Re-run the PR Description Check for open PRs linked to an issue.
2
+
3
+ Invoked from the issue-readiness workflow when the `ready-for-dev` label is
4
+ added to or removed from an issue, so the PR gate reflects the settled
5
+ readiness state without waiting for the next PR event.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import os
12
+ import subprocess
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ from check_pr_description import extract_linked_issue_numbers
17
+
18
+
19
+ def _run(args: list[str]) -> subprocess.CompletedProcess[str]:
20
+ return subprocess.run(args, capture_output=True, text=True, check=False)
21
+
22
+
23
+ def _linked_open_prs(repo: str, issue_number: int) -> list[dict]:
24
+ """Return open PRs that cross-reference ``issue_number``."""
25
+ owner, name = repo.split("/", 1)
26
+ query = """
27
+ query($owner: String!, $name: String!, $num: Int!) {
28
+ repository(owner: $owner, name: $name) {
29
+ issue(number: $num) {
30
+ timelineItems(first: 100, itemTypes: [CROSS_REFERENCED_EVENT]) {
31
+ nodes {
32
+ __typename
33
+ ... on CrossReferencedEvent {
34
+ source {
35
+ __typename
36
+ ... on PullRequest { number headRefOid state }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+ }
44
+ """
45
+ result = _run(
46
+ [
47
+ "gh",
48
+ "api",
49
+ "graphql",
50
+ "-f",
51
+ f"query={query}",
52
+ "-F",
53
+ f"owner={owner}",
54
+ "-F",
55
+ f"name={name}",
56
+ "-F",
57
+ f"num={issue_number}",
58
+ "--jq",
59
+ ".data.repository.issue.timelineItems.nodes",
60
+ ]
61
+ )
62
+ if result.returncode != 0:
63
+ print(f"::warning::Could not query linked PRs: {result.stderr.strip()}")
64
+ return []
65
+ try:
66
+ nodes = json.loads(result.stdout)
67
+ except json.JSONDecodeError as exc:
68
+ print(f"::warning::Unexpected linked-PR query output: {exc}")
69
+ return []
70
+ prs = []
71
+ for node in nodes:
72
+ source = node.get("source") if isinstance(node, dict) else None
73
+ if not isinstance(source, dict):
74
+ continue
75
+ if source.get("__typename") != "PullRequest":
76
+ continue
77
+ if source.get("state") != "OPEN":
78
+ continue
79
+ prs.append(source)
80
+ return prs
81
+
82
+
83
+ def _rerun_pr_description_check(repo: str, head_sha: str) -> bool:
84
+ """Re-run the latest PR Description Check for the given head SHA."""
85
+ runs_result = _run(
86
+ [
87
+ "gh",
88
+ "api",
89
+ "-X",
90
+ "GET",
91
+ f"repos/{repo}/actions/runs",
92
+ "-f",
93
+ f"head_sha={head_sha}",
94
+ "-f",
95
+ "per_page=100",
96
+ "--jq",
97
+ r'.workflow_runs[] | select(.name=="PR Description Check") | '
98
+ r'select(.event=="pull_request_target") | "\(.id) \(.created_at)"',
99
+ ]
100
+ )
101
+ if runs_result.returncode != 0:
102
+ print(f"::warning::Could not list PR checks: {runs_result.stderr.strip()}")
103
+ return False
104
+ lines = [line.split() for line in runs_result.stdout.splitlines() if line.strip()]
105
+ if not lines:
106
+ return False
107
+ # Created-at is sortable; pick the most recent run for this commit.
108
+ latest = sorted(lines, key=lambda item: " ".join(item[1:]))[-1][0]
109
+ rerun = _run(
110
+ ["gh", "api", "-X", "POST", f"repos/{repo}/actions/runs/{latest}/rerun"]
111
+ )
112
+ if rerun.returncode != 0:
113
+ print(
114
+ f"::warning::Could not re-run PR description check ({latest}): "
115
+ f"{rerun.stderr.strip()}"
116
+ )
117
+ return False
118
+ print(f"Re-ran PR description check (run {latest}) for linked open PR.")
119
+ return True
120
+
121
+
122
+ def main() -> int:
123
+ payload = json.loads(Path(os.environ["GITHUB_EVENT_PATH"]).read_text())
124
+ if {"action", "issue", "label", "repository"} - set(payload):
125
+ return 0
126
+ action = payload["action"]
127
+ raw_label = payload.get("label")
128
+ label = raw_label.get("name") if isinstance(raw_label, dict) else None
129
+ if action not in ("labeled", "unlabeled") or label != "ready-for-dev":
130
+ return 0
131
+ raw_repo = payload.get("repository")
132
+ repo = raw_repo.get("full_name") if isinstance(raw_repo, dict) else None
133
+ raw_issue = payload.get("issue")
134
+ issue_number = raw_issue.get("number") if isinstance(raw_issue, dict) else None
135
+ if not repo or not issue_number:
136
+ return 0
137
+
138
+ print(
139
+ f"ready-for-dev {action}: refreshing PR gates linked to issue #{issue_number}."
140
+ )
141
+ refreshed = 0
142
+ for pr in _linked_open_prs(repo, issue_number):
143
+ pr_number = pr["number"]
144
+ body_result = _run(
145
+ [
146
+ "gh",
147
+ "pr",
148
+ "view",
149
+ str(pr_number),
150
+ "--repo",
151
+ repo,
152
+ "--json",
153
+ "body",
154
+ "--jq",
155
+ ".body",
156
+ ]
157
+ )
158
+ if body_result.returncode != 0:
159
+ continue
160
+ if issue_number not in extract_linked_issue_numbers(body_result.stdout):
161
+ # Cross-referenced but not treated as a linked issue by the gate;
162
+ # nothing to refresh.
163
+ continue
164
+ if _rerun_pr_description_check(repo, pr["headRefOid"]):
165
+ refreshed += 1
166
+ print(f"Refreshed PR gates for {refreshed} linked PR(s).")
167
+ return 0
168
+
169
+
170
+ if __name__ == "__main__":
171
+ sys.exit(main())
@@ -0,0 +1,142 @@
1
+ ---
2
+ name: Issue Readiness Check
3
+
4
+ # Manages the `ready-for-dev` label based on type-specific readiness criteria
5
+ # tailored to extension contributions (skills, plugins, integrations,
6
+ # automations).
7
+ #
8
+ # Bug reports (label `bug`): the Actual Behavior section must reference a
9
+ # reproducible command (`uv run pytest`, `pytest`, `python`, `pip`, `npm run`,
10
+ # or `node`), plus an Acceptance Criteria section with at least one checklist
11
+ # item.
12
+ #
13
+ # Enhancements (label `enhancement`): the body must contain Desired Behavior and
14
+ # Acceptance Criteria sections, the latter with at least one checklist item.
15
+ #
16
+ # When criteria are met the label is added (idempotently); when they are not the
17
+ # label is removed. A feedback comment is posted (or updated) in three cases:
18
+ # 1. The issue is first opened or reopened - always comment so the author
19
+ # knows whether their issue is ready or what is missing.
20
+ # 2. The label is being added (issue became ready) - comment celebrating it.
21
+ # 3. The label is being removed (issue was ready but is no longer) - comment
22
+ # explaining what changed.
23
+ # Edits that do not change the label state do not produce a new comment. All
24
+ # comments are upserted by a hidden marker so there is at most one per issue.
25
+ #
26
+ # The readiness step uses `--json`, which always exits 0, so a not-ready result
27
+ # never aborts the workflow under `set -euo pipefail`; label and comment steps
28
+ # run for both outcomes.
29
+
30
+ on:
31
+ issues:
32
+ types: [opened, edited, reopened, labeled, unlabeled]
33
+
34
+ permissions:
35
+ issues: write
36
+ pull-requests: read
37
+ contents: read
38
+
39
+ concurrency:
40
+ group: issue-readiness-${{ github.event.issue.number }}
41
+ cancel-in-progress: false
42
+
43
+ jobs:
44
+ check:
45
+ if: github.event.issue.state == 'open' && github.event.issue.pull_request == null
46
+ runs-on: ubuntu-latest
47
+ timeout-minutes: 5
48
+ steps:
49
+ - name: Checkout trusted workflow scripts
50
+ uses: actions/checkout@v4
51
+
52
+ - name: Evaluate issue readiness
53
+ id: readiness
54
+ env:
55
+ GITHUB_EVENT_PATH: ${{ github.event_path }}
56
+ run: |
57
+ set -euo pipefail
58
+ python .github/scripts/check_issue_readiness.py --json > /tmp/result.json
59
+ echo "ready=$(jq -r '.ready' /tmp/result.json)" >> "$GITHUB_OUTPUT"
60
+ # Write reasons to a file so the comment step can read them
61
+ # without shell-escaping multiline text.
62
+ jq -r '.reasons[]?' /tmp/result.json > /tmp/reasons.txt || true
63
+ echo "reasons_file=/tmp/reasons.txt" >> "$GITHUB_OUTPUT"
64
+
65
+ - name: Add ready-for-dev label
66
+ if: steps.readiness.outputs.ready == 'true'
67
+ env:
68
+ GH_TOKEN: ${{ github.token }}
69
+ run: |
70
+ set -euo pipefail
71
+ # Capture stderr — tolerate "label already present" (exit 0
72
+ # from gh) but surface unexpected errors to the step summary.
73
+ if ! gh issue edit "${{ github.event.issue.number }}" \
74
+ --repo "${{ github.repository }}" \
75
+ --add-label "ready-for-dev" 2>/tmp/gh-err.txt; then
76
+ echo "::warning::Failed to add ready-for-dev label:" >> "$GITHUB_STEP_SUMMARY"
77
+ cat /tmp/gh-err.txt >> "$GITHUB_STEP_SUMMARY"
78
+ fi
79
+
80
+ - name: Remove ready-for-dev label
81
+ if: steps.readiness.outputs.ready != 'true' && contains(github.event.issue.labels.*.name, 'ready-for-dev')
82
+ env:
83
+ GH_TOKEN: ${{ github.token }}
84
+ run: |
85
+ set -euo pipefail
86
+ # Capture stderr — tolerate "label already removed" but
87
+ # surface unexpected errors.
88
+ if ! gh issue edit "${{ github.event.issue.number }}" \
89
+ --repo "${{ github.repository }}" \
90
+ --remove-label "ready-for-dev" 2>/tmp/gh-err.txt; then
91
+ echo "::warning::Failed to remove ready-for-dev label:" >> "$GITHUB_STEP_SUMMARY"
92
+ cat /tmp/gh-err.txt >> "$GITHUB_STEP_SUMMARY"
93
+ fi
94
+
95
+ - name: Post feedback comment
96
+ if: >-
97
+ github.event.action == 'opened'
98
+ || github.event.action == 'reopened'
99
+ || (steps.readiness.outputs.ready == 'true' && !contains(github.event.issue.labels.*.name, 'ready-for-dev'))
100
+ || (steps.readiness.outputs.ready != 'true' && contains(github.event.issue.labels.*.name, 'ready-for-dev'))
101
+ env:
102
+ GH_TOKEN: ${{ github.token }}
103
+ run: |
104
+ set -euo pipefail
105
+ if [ "${{ steps.readiness.outputs.ready }}" = "true" ]; then
106
+ node .github/scripts/post-readiness-comment.mjs \
107
+ --issue-number "${{ github.event.issue.number }}" \
108
+ --repo "${{ github.repository }}" \
109
+ --ready
110
+ else
111
+ node .github/scripts/post-readiness-comment.mjs \
112
+ --issue-number "${{ github.event.issue.number }}" \
113
+ --repo "${{ github.repository }}" \
114
+ --reasons-file "${{ steps.readiness.outputs.reasons_file }}"
115
+ fi
116
+
117
+ # When the ready-for-dev label is added or removed, re-run the PR
118
+ # description check for every open PR that links this issue, so the
119
+ # `ready-for-dev` gate does not go stale between PR events. Runs after
120
+ # `check` so the label reflects the settled readiness state.
121
+ refresh-linked-pr-gates:
122
+ name: Refresh PR gates for linked issues
123
+ needs: check
124
+ if: >-
125
+ github.event.issue.state == 'open'
126
+ && github.event.issue.pull_request == null
127
+ && (github.event.action == 'labeled' || github.event.action == 'unlabeled')
128
+ && github.event.label.name == 'ready-for-dev'
129
+ runs-on: ubuntu-latest
130
+ timeout-minutes: 10
131
+ permissions:
132
+ contents: read
133
+ pull-requests: read
134
+ actions: write
135
+ steps:
136
+ - name: Checkout trusted workflow scripts
137
+ uses: actions/checkout@v4
138
+
139
+ - name: Re-run PR description checks for linked open PRs
140
+ env:
141
+ GH_TOKEN: ${{ github.token }}
142
+ run: python .github/scripts/refresh_linked_pr_checks.py
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: PR Description Check
3
+
4
+ # Use pull_request_target so fork PR descriptions can be checked, but only run
5
+ # trusted validation code from the base branch checkout below.
6
+
7
+ on:
8
+ pull_request_target:
9
+ types: [opened, edited, reopened, ready_for_review, synchronize]
10
+
11
+ permissions:
12
+ contents: read
13
+ pull-requests: read
14
+ issues: read
15
+
16
+ jobs:
17
+ validate-pr-description:
18
+ name: Validate PR description
19
+ # Draft PRs may still have incomplete descriptions; validate when review starts.
20
+ # Release PRs are generated by the trusted prepare-release workflow without
21
+ # the standard human-authored PR template.
22
+ if: >-
23
+ github.event.pull_request.draft == false &&
24
+ github.event.pull_request.user.login != 'dependabot[bot]' &&
25
+ !(github.event.pull_request.head.repo.full_name == github.repository &&
26
+ startsWith(github.event.pull_request.head.ref, 'release-please-'))
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - name: Checkout trusted workflow scripts
30
+ uses: actions/checkout@v4
31
+ with:
32
+ ref: ${{ github.event.pull_request.base.sha }}
33
+
34
+ - name: Validate PR template sections and linked-issue readiness
35
+ env:
36
+ GITHUB_TOKEN: ${{ github.token }}
37
+ run: python .github/scripts/check_pr_description.py
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.18.0"
2
+ ".": "0.19.0"
3
3
  }
package/AGENTS.md CHANGED
@@ -3,6 +3,20 @@
3
3
  This repository (`OpenHands/extensions`) is the **public extensions registry** for OpenHands.
4
4
  It contains **shareable skills and plugins** that can be loaded by OpenHands (CLI/GUI/Cloud) and by client code using the **Software Agent SDK**.
5
5
 
6
+
7
+ ## Cross-Repository Boundaries
8
+
9
+ This repository owns the public registry of reusable OpenHands skills, plugins, automations, and integrations. These extensions are consumed by OpenHands applications and SDK-based clients.
10
+
11
+ Related repositories have distinct responsibilities:
12
+
13
+ - [`OpenHands/software-agent-sdk`](https://github.com/OpenHands/software-agent-sdk) owns the Python SDK, Agent Server, agent/tool behavior, conversations, workspaces, events, and canonical API.
14
+ - [`OpenHands/typescript-client`](https://github.com/OpenHands/typescript-client) owns the browser-compatible typed Agent Server client.
15
+ - [`OpenHands/OpenHands`](https://github.com/OpenHands/OpenHands) owns Agent Canvas UI and local-stack orchestration.
16
+ - [`OpenHands/automation`](https://github.com/OpenHands/automation) owns scheduling, webhooks, run history, dispatch, and sandbox lifecycle orchestration.
17
+
18
+ Put reusable skills, plugins, automations, and integrations here; put backend execution behavior in the SDK, typed API access in `typescript-client`, application UI in Agent Canvas, and scheduling/dispatch lifecycle code in `automation`. If a PR is opened in the wrong repository, explicitly recommend closing and moving it to the owning repository. PRs must follow this repository's applicable contribution and code-review guidance.
19
+
6
20
  ## What this repo contains
7
21
 
8
22
  - `skills/` — a catalog of skills, **one directory per skill**.
@@ -110,6 +124,8 @@ When editing or adding skills in this repo, follow these rules (and add new skil
110
124
  - `scripts/sync_extensions.py` keeps generated artifacts in sync: Claude Code command files, README catalog section, coverage checks, and vendor symlinks. Run `python scripts/sync_extensions.py --check` (or just push — CI runs it) to verify everything is consistent. Run without `--check` to auto-fix. The "Quick Start" section in `README.md` (OpenHands SDK, Claude Code, and Codex setup instructions) is **manually maintained** above the auto-generated catalog markers and is intentionally not generated by the sync script.
111
125
  - The sync script uses PyYAML to parse SKILL.md frontmatter. If you add a skill with a slash trigger (e.g., `triggers: ["/mycommand"]`), the script auto-generates `commands/mycommand.md`. **Note:** Slash triggers in SKILL.md frontmatter are deprecated — prefer adding a `commands/command-name.md` file to the plugin's `commands/` directory instead. Keyword triggers (non-slash) remain the recommended way to activate skills by topic.
112
126
 
127
+ - The `ready-for-dev` gates live in `.github/workflows/issue-readiness-check.yml` and `.github/workflows/pr-description-check.yml`, backed by `.github/scripts/check_issue_readiness.py`, `check_pr_description.py`, `refresh_linked_pr_checks.py`, and `post-readiness-comment.mjs`. Issues labeled `bug` or `enhancement` must meet type-specific readiness criteria (reproducible command plus Acceptance Criteria checklist for bugs; Desired Behavior plus Acceptance Criteria checklist for enhancements) to receive the `ready-for-dev` label. Non-draft PRs must keep the `## Why`, `## Summary`, and `## How to Test` template sections filled, and any issue linked via a closing keyword or the `## Issue Number` section must carry `ready-for-dev` unless it predates the 2026-08-25 rollout. Label transitions on an issue re-run the PR Description Check for linked open PRs. Tests live in `tests/test_check_issue_readiness.py`, `tests/test_check_pr_description.py`, and `tests/test_refresh_linked_pr_checks.py`.
128
+
113
129
  ## OpenHands SDK documentation policy
114
130
 
115
131
  - **Do NOT add SDK-specific or SDK-related documentation to this repo.** The canonical source of truth for SDK documentation is the [OpenHands docs site](https://docs.openhands.dev/sdk) and its structured index at <https://docs.openhands.dev/llms.txt>.
package/README.md CHANGED
@@ -6,6 +6,13 @@ It contains reusable, shareable skills and plugins that customize agent behavior
6
6
  - Skills overview docs: https://docs.openhands.dev/overview/skills
7
7
  - SDK skill guide: https://docs.openhands.dev/sdk/guides/skill
8
8
 
9
+
10
+ ## Repository boundaries
11
+
12
+ `OpenHands/extensions` is the public registry for reusable skills, plugins, automations, and integrations. [`OpenHands/software-agent-sdk`](https://github.com/OpenHands/software-agent-sdk) owns Agent Server execution and the canonical API, [`OpenHands/typescript-client`](https://github.com/OpenHands/typescript-client) owns typed browser access to that API, [`OpenHands/OpenHands`](https://github.com/OpenHands/OpenHands) owns Agent Canvas UI, and [`OpenHands/automation`](https://github.com/OpenHands/automation) owns scheduling, webhooks, run history, dispatch, and sandbox lifecycle orchestration.
13
+
14
+ Put reusable extension artifacts here rather than in application repositories. If a PR is opened in the wrong repository, close and move it to the repository that owns the change.
15
+
9
16
  ## Repository Layout
10
17
 
11
18
  ### Skills
@@ -89,7 +96,7 @@ The JS and Python versions are kept in lock-step by `release-please` and guarded
89
96
  ## Extensions Catalog
90
97
 
91
98
  <!-- BEGIN AUTO-GENERATED CATALOG -->
92
- This repository contains **2 marketplace(s)** with **67 extensions** (57 skills, 10 plugins).
99
+ This repository contains **2 marketplace(s)** with **68 extensions** (58 skills, 10 plugins).
93
100
 
94
101
  ### large-codebase
95
102
 
@@ -108,7 +115,7 @@ OpenHands skills for interacting, improving, and refactoring large codebases
108
115
 
109
116
  Official skills and plugins for OpenHands — the open-source AI software engineer.
110
117
 
111
- **63 extensions** (55 skills, 8 plugins)
118
+ **64 extensions** (56 skills, 8 plugins)
112
119
 
113
120
  | Name | Type | Description | Commands |
114
121
  |------|------|-------------|----------|
@@ -171,6 +178,7 @@ Official skills and plugins for OpenHands — the open-source AI software engine
171
178
  | swift-linux | skill | Install and configure Swift programming language on Debian Linux for server-side development. Use when building Swift... | — |
172
179
  | technical-writing | skill | Write and revise technical explanations in flowing, direct, conversational prose that stays concise without becoming ... | — |
173
180
  | theme-factory | skill | Toolkit for styling artifacts with a theme. These artifacts can be slides, docs, reportings, HTML landing pages, etc.... | — |
181
+ | ticket-to-code-change | skill | Set up Jira or Linear ticket-to-code-change automations for GitHub, GitLab, and Bitbucket. | `/ticket-to-code-change:setup` |
174
182
  | upstream-fork-sync | skill | Keep a long-lived fork in sync with its upstream. Creates a cron automation that fetches upstream changes, rebases lo... | `/upstream-fork-sync:setup` |
175
183
  | uv | skill | Common project, dependency, and environment operations using uv. | — |
176
184
  | vercel | skill | Deploy and manage applications on Vercel, including preview deployments and deployment protection. | — |