@openhands/extensions 0.24.0 → 0.26.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 (133) hide show
  1. package/.claude-plugin/marketplace.json +30 -0
  2. package/.codex-plugin/marketplace.json +30 -0
  3. package/.github/pull_request_template.md +17 -2
  4. package/.github/scripts/check_pr_description.py +64 -5
  5. package/.github/scripts/markdown_sections.py +95 -0
  6. package/.github/workflows/build-vscode-app.yml +30 -0
  7. package/.github/workflows/check-extensions.yml +3 -0
  8. package/.github/workflows/tests.yml +26 -0
  9. package/.plugin/marketplace.json +30 -0
  10. package/.release-please-manifest.json +1 -1
  11. package/AGENTS.md +3 -2
  12. package/README.md +10 -3
  13. package/apps/README.md +20 -0
  14. package/apps/catalog.json +10 -0
  15. package/apps/index.d.ts +11 -0
  16. package/apps/index.js +5 -0
  17. package/apps/vscode/README.md +13 -0
  18. package/apps/vscode/canvas-extension.json +19 -0
  19. package/apps/vscode/extension.js +114 -0
  20. package/apps/vscode/scripts/package.mjs +84 -0
  21. package/automations/bundle-index.js +18 -14
  22. package/automations/catalog/github-agents-md-maintainer/manifest.json +1 -1
  23. package/automations/catalog/github-pr-reviewer/manifest.json +19 -8
  24. package/automations/catalog/github-stale-ci-pr-closer/manifest.json +71 -0
  25. package/automations/catalog/gitlab-issue-to-mr/manifest.json +1 -1
  26. package/automations/catalog/news-digest/manifest.json +2 -2
  27. package/automations/catalog-index.js +2 -0
  28. package/index.d.ts +2 -0
  29. package/index.js +1 -0
  30. package/marketplaces/openhands-extensions.json +30 -0
  31. package/package.json +9 -1
  32. package/plugins/openhands/skills/openhands-api/README.md +1 -1
  33. package/plugins/openhands/skills/openhands-api/SKILL.md +3 -3
  34. package/plugins/openhands/skills/openhands-api/scripts/openhands_api.py +13 -0
  35. package/plugins/openhands/skills/openhands-automation/SKILL.md +66 -4
  36. package/plugins/openhands/skills/openhands-automation/references/custom-automation.md +21 -7
  37. package/plugins/openhands/skills/openhands-sdk/SKILL.md +1 -0
  38. package/plugins/pr-review/skills/github-pr-review/SKILL.md +12 -2
  39. package/plugins/qa-changes/skills/github-pr-review/SKILL.md +12 -2
  40. package/plugins/runtime-api-configs/.claude-plugin/plugin.json +20 -0
  41. package/plugins/runtime-api-configs/.codex-plugin/plugin.json +20 -0
  42. package/plugins/runtime-api-configs/.plugin/plugin.json +20 -0
  43. package/plugins/runtime-api-configs/README.md +59 -0
  44. package/plugins/runtime-api-configs/SKILL.md +240 -0
  45. package/plugins/runtime-api-configs/scripts/warm_runtime_configs.py +331 -0
  46. package/plugins/runtime-api-configs/tests/test_warm_runtime_configs.py +338 -0
  47. package/plugins/vulnerability-remediation/skills/github/scripts/agent_conversation.py +223 -43
  48. package/plugins/vulnerability-remediation/skills/github/scripts/github_client.py +183 -15
  49. package/pyproject.toml +1 -1
  50. package/python/openhands_extensions/_version.py +1 -1
  51. package/scripts/check_plugin_resources.py +116 -0
  52. package/scripts/sync_extensions.py +32 -5
  53. package/skills/canvas-extension-api/SKILL.md +34 -3
  54. package/skills/canvas-extension-api/references/backend-safety.md +14 -0
  55. package/skills/canvas-extension-api/references/connections.md +24 -0
  56. package/skills/canvas-extension-api/references/sidecar-pattern.md +49 -10
  57. package/skills/canvas-extension-api/references/v1-contract.md +53 -3
  58. package/skills/canvas-extension-api/scripts/validate-extension.mjs +52 -0
  59. package/skills/canvas-extension-api/scripts/validate-extension.test.mjs +192 -1
  60. package/skills/github/scripts/agent_conversation.py +223 -43
  61. package/skills/github/scripts/github_client.py +183 -15
  62. package/skills/github-agents-md-maintainer/README.md +7 -0
  63. package/skills/github-agents-md-maintainer/SKILL.md +13 -6
  64. package/skills/github-agents-md-maintainer/scripts/github_client.py +183 -15
  65. package/skills/github-agents-md-maintainer/scripts/main.py +199 -29
  66. package/skills/github-delivery-watchdog/scripts/github_client.py +183 -15
  67. package/skills/github-issue-to-pr/scripts/agent_conversation.py +223 -43
  68. package/skills/github-issue-to-pr/scripts/github_client.py +183 -15
  69. package/skills/github-issue-to-pr/scripts/worker.py +10 -1
  70. package/skills/github-issue-triage/scripts/agent_conversation.py +223 -43
  71. package/skills/github-issue-triage/scripts/github_client.py +183 -15
  72. package/skills/github-pr-review/SKILL.md +12 -2
  73. package/skills/github-pr-reviewer/README.md +36 -0
  74. package/skills/github-pr-reviewer/SKILL.md +147 -4
  75. package/skills/github-pr-reviewer/scripts/agent_conversation.py +223 -43
  76. package/skills/github-pr-reviewer/scripts/github_client.py +183 -15
  77. package/skills/github-pr-reviewer/scripts/main.py +17 -1
  78. package/skills/github-pr-reviewer/scripts/worker.py +842 -57
  79. package/skills/github-repo-monitor/scripts/github_client.py +183 -15
  80. package/skills/github-repo-monitor/scripts/main.py +32 -1
  81. package/skills/github-stale-ci-pr-closer/.claude-plugin/plugin.json +9 -0
  82. package/skills/github-stale-ci-pr-closer/.codex-plugin/plugin.json +9 -0
  83. package/skills/github-stale-ci-pr-closer/.plugin/plugin.json +9 -0
  84. package/skills/github-stale-ci-pr-closer/README.md +9 -0
  85. package/skills/github-stale-ci-pr-closer/SKILL.md +24 -0
  86. package/skills/github-stale-ci-pr-closer/commands/github-stale-ci-pr-closer.md +8 -0
  87. package/skills/github-stale-ci-pr-closer/scripts/worker.py +508 -0
  88. package/skills/github-stale-ci-pr-closer/tests/test_worker.py +353 -0
  89. package/skills/gitlab-issue-to-mr/README.md +12 -0
  90. package/skills/gitlab-issue-to-mr/SKILL.md +13 -6
  91. package/skills/gitlab-issue-to-mr/scripts/main.py +217 -42
  92. package/skills/incident-retrospective/SKILL.md +5 -5
  93. package/skills/index.js +34 -16
  94. package/skills/jira-issue-to-pr/README.md +12 -4
  95. package/skills/jira-issue-to-pr/SKILL.md +129 -18
  96. package/skills/jira-issue-to-pr/references/setup.md +8 -0
  97. package/skills/jira-issue-to-pr/scripts/main.py +181 -54
  98. package/skills/jira-issue-to-pr/scripts/setup_webhook.py +290 -0
  99. package/skills/linear-triage/SKILL.md +5 -5
  100. package/skills/news-digest/README.md +6 -0
  101. package/skills/news-digest/SKILL.md +19 -9
  102. package/skills/news-digest/commands/news-digest-setup.md +1 -1
  103. package/skills/news-digest/scripts/main.py +133 -15
  104. package/skills/openhands-api/README.md +1 -1
  105. package/skills/openhands-api/SKILL.md +3 -3
  106. package/skills/openhands-api/scripts/openhands_api.py +13 -0
  107. package/skills/openhands-automation/SKILL.md +66 -4
  108. package/skills/openhands-automation/references/custom-automation.md +21 -7
  109. package/skills/openhands-sdk/SKILL.md +1 -0
  110. package/skills/research-brief/SKILL.md +5 -5
  111. package/skills/slack-standup-digest/SKILL.md +5 -5
  112. package/skills/ticket-to-code-change/SKILL.md +6 -3
  113. package/skills/use-jev/.claude-plugin/plugin.json +8 -0
  114. package/skills/use-jev/.codex-plugin/plugin.json +8 -0
  115. package/skills/use-jev/.plugin/plugin.json +8 -0
  116. package/skills/use-jev/README.md +66 -0
  117. package/skills/use-jev/SKILL.md +66 -0
  118. package/tests/fixtures/automations/github-pr-reviewer.json +39 -24
  119. package/tests/test_agent_conversation_dispatch.py +212 -0
  120. package/tests/test_apps_catalog.py +26 -0
  121. package/tests/test_automation_setup.py +44 -0
  122. package/tests/test_check_pr_description.py +89 -6
  123. package/tests/test_github_automation_foundation.py +158 -0
  124. package/tests/test_github_repo_monitor.py +107 -0
  125. package/tests/test_github_reviewer_delivery.py +2247 -6
  126. package/tests/test_github_stale_ci_pr_closer.py +56 -0
  127. package/tests/test_jira_issue_to_pr.py +312 -0
  128. package/tests/test_jira_setup_webhook.py +309 -0
  129. package/tests/test_openhands_api_python.py +37 -0
  130. package/tests/test_skill_plugin_loading.py +25 -15
  131. package/tests/test_sync_extensions.py +102 -0
  132. package/tests/test_vscode_app.py +33 -0
  133. package/tests/vscode-app.test.mjs +43 -0
@@ -12,24 +12,62 @@ from urllib.parse import parse_qsl, urlencode, urlsplit
12
12
  from urllib.request import Request, urlopen
13
13
 
14
14
 
15
+ # The secret name of the GitHub token an OpenHands Cloud or Enterprise user's
16
+ # connected GitHub integration provides: how this run's sandbox serves it, and
17
+ # how a conversation sees it in its environment.
18
+ CLOUD_GITHUB_TOKEN_SECRET = "github_token"
19
+ CLOUD_GITHUB_TOKEN_ENV = "GITHUB_TOKEN"
20
+
21
+
22
+ def is_cloud_run() -> bool:
23
+ """Whether this run is on OpenHands Cloud or Enterprise.
24
+
25
+ The automation service hands a run the Agent Server URL only on a local
26
+ Agent Canvas; elsewhere the run talks to the OpenHands API instead.
27
+ """
28
+ return bool(os.environ.get("OPENHANDS_CLOUD_API_URL")) and not os.environ.get(
29
+ "AGENT_SERVER_URL"
30
+ )
31
+
32
+
33
+ def _load_cloud_secret(name: str) -> str | None:
34
+ """Read one named secret of this run's sandbox from the OpenHands API."""
35
+ api = os.environ["OPENHANDS_CLOUD_API_URL"].rstrip("/")
36
+ request = Request(
37
+ f"{api}/api/v1/sandboxes/{os.environ['SANDBOX_ID']}/settings/secrets/{name}",
38
+ headers={"X-Session-API-Key": os.environ["SESSION_API_KEY"]},
39
+ )
40
+ try:
41
+ with urlopen(request, timeout=90) as response:
42
+ return response.read().decode().strip()
43
+ except HTTPError as exc:
44
+ if exc.code == 404:
45
+ return None
46
+ raise
47
+
48
+
15
49
  def _load_secret(name: str) -> str:
16
- """Read one named secret from the environment or configured Agent Server."""
50
+ """Read one named secret from the environment, or from the configured Agent
51
+ Server on a local run and the OpenHands API on a cloud one."""
17
52
  value = os.environ.get(name)
18
53
  if value:
19
54
  return value
20
55
 
21
- from openhands.sdk.workspace import RemoteWorkspace
56
+ if is_cloud_run():
57
+ value = _load_cloud_secret(name)
58
+ else:
59
+ from openhands.sdk.workspace import RemoteWorkspace
22
60
 
23
- workspace = RemoteWorkspace(
24
- host=os.environ["AGENT_SERVER_URL"],
25
- api_key=os.environ["SESSION_API_KEY"],
26
- working_dir=os.environ.get("WORKSPACE_BASE", "/workspace"),
27
- )
28
- try:
29
- secret = workspace.get_secrets([name]).get(name)
30
- value = secret.get_value() if secret else None
31
- finally:
32
- workspace.reset_client()
61
+ workspace = RemoteWorkspace(
62
+ host=os.environ["AGENT_SERVER_URL"],
63
+ api_key=os.environ["SESSION_API_KEY"],
64
+ working_dir=os.environ.get("WORKSPACE_BASE", "/workspace"),
65
+ )
66
+ try:
67
+ secret = workspace.get_secrets([name]).get(name)
68
+ value = secret.get_value() if secret else None
69
+ finally:
70
+ workspace.reset_client()
33
71
  if not value:
34
72
  raise ValueError(f"The GitHub credential {name} is unavailable")
35
73
  return value
@@ -95,11 +133,22 @@ class GitHubRepository:
95
133
  "Expected the environment variable containing the GitHub token"
96
134
  )
97
135
  self.token_name = github_token_secret
98
- self.token = _load_secret(github_token_secret)
136
+ try:
137
+ self.token = _load_secret(github_token_secret)
138
+ except ValueError:
139
+ if not is_cloud_run():
140
+ raise
141
+ # No secret is saved under that name, so use the user's connected
142
+ # GitHub integration, under the name a conversation sees it by.
143
+ self.token = _load_cloud_secret(CLOUD_GITHUB_TOKEN_SECRET)
144
+ if not self.token:
145
+ raise
146
+ self.token_name = CLOUD_GITHUB_TOKEN_ENV
99
147
  self.conversation = conversation
100
148
  self.conversation_id = str(conversation.id) if conversation else None
101
149
  self.dispatcher = dispatcher
102
- self.workspace = Path(os.environ["WORKSPACE_BASE"])
150
+ # A cloud run is not given a workspace base; its sandbox has /workspace.
151
+ self.workspace = Path(os.environ.get("WORKSPACE_BASE", "/workspace"))
103
152
  self.project = self.workspace
104
153
  self.evidence = self.workspace / "evidence"
105
154
  self.evidence.mkdir(exist_ok=True)
@@ -111,10 +160,13 @@ class GitHubRepository:
111
160
 
112
161
  @property
113
162
  def github_instructions(self):
163
+ # A cloud conversation runs in a sandbox of its own, so this run's
164
+ # workspace path means nothing to it.
165
+ where = "your working directory" if is_cloud_run() else self.project
114
166
  return (
115
167
  f"Use `GH_TOKEN=${self.token_name} gh api` for GitHub requests. "
116
168
  "Never print the credential value. "
117
- f"Only {self.repository} is in scope. Work in {self.project}. "
169
+ f"Only {self.repository} is in scope. Work in {where}. "
118
170
  "Do not modify the automation bundle or its configuration."
119
171
  )
120
172
 
@@ -165,6 +217,122 @@ class GitHubRepository:
165
217
  result.setdefault(item["context"], item["state"])
166
218
  return result
167
219
 
220
+ def check_runs(self, sha):
221
+ """Return every check run GitHub reported for one commit SHA.
222
+
223
+ Reading check runs needs no branch-protection or ruleset access, so this
224
+ is the head-eligibility signal the automation's own token can always
225
+ see. The endpoint answers with an object rather than a list, so it
226
+ paginates through ``gh`` instead of ``gh_pages``.
227
+ """
228
+ runs = []
229
+ for page in range(1, 101):
230
+ data = self.gh(
231
+ "GET", f"/commits/{sha}/check-runs?per_page=100&page={page}"
232
+ )
233
+ batch = data.get("check_runs") or []
234
+ runs.extend(batch)
235
+ if len(runs) >= int(data.get("total_count") or 0) or not batch:
236
+ return runs
237
+ raise RuntimeError("GitHub check-run pagination exceeded limit")
238
+
239
+ def required_check_contexts(self, number):
240
+ """Return the status contexts GitHub marks required on one pull request.
241
+
242
+ `isRequired` is the merge-policy source of truth and is PR-scoped, so it
243
+ is correct for a stacked PR whose symbolic base branch carries no branch
244
+ rules of its own. It is a field on each context in the head's status
245
+ check rollup and takes the pull request number, so a single GraphQL query
246
+ answers with the required check runs and any required commit statuses,
247
+ each already attributed to the exact head. Only contexts GitHub itself
248
+ reports as required are returned, so an optional workflow that fails
249
+ before creating any check run is not present.
250
+ """
251
+ owner, name = self.repository.split("/", 1)
252
+ query = (
253
+ "query($owner:String!,$name:String!,$number:Int!){"
254
+ "repository(owner:$owner,name:$name){"
255
+ "pullRequest(number:$number){"
256
+ "commits(last:1){"
257
+ "nodes{"
258
+ "commit{"
259
+ "statusCheckRollup{"
260
+ "contexts(first:100){"
261
+ "pageInfo{hasNextPage}"
262
+ "nodes{"
263
+ "__typename"
264
+ " ... on CheckRun{name isRequired(pullRequestNumber:$number)}"
265
+ " ... on StatusContext{context isRequired(pullRequestNumber:$number)}"
266
+ "}"
267
+ "}"
268
+ "}"
269
+ "}"
270
+ "}"
271
+ "}"
272
+ "}"
273
+ "}"
274
+ "}"
275
+ )
276
+ data = self.api(
277
+ "POST",
278
+ "/graphql",
279
+ body={
280
+ "query": query,
281
+ "variables": {"owner": owner, "name": name, "number": number},
282
+ },
283
+ )
284
+ if data.get("errors"):
285
+ raise RuntimeError(
286
+ f"GitHub required-check query failed: {data['errors'][0].get('message')}"
287
+ )
288
+ nodes = (
289
+ (((data.get("data") or {}).get("repository") or {}).get("pullRequest") or {})
290
+ .get("commits", {})
291
+ .get("nodes")
292
+ ) or []
293
+ contexts = (
294
+ ((nodes[0].get("commit") or {}).get("statusCheckRollup") or {}).get(
295
+ "contexts", {}
296
+ )
297
+ if nodes
298
+ else {}
299
+ )
300
+ if (contexts.get("pageInfo") or {}).get("hasNextPage"):
301
+ raise RuntimeError(
302
+ "GitHub required-check rollup exceeds one page; the required "
303
+ "set may be incomplete"
304
+ )
305
+ return [
306
+ {
307
+ "name": node.get("name") or node.get("context"),
308
+ "kind": node.get("__typename"),
309
+ }
310
+ for node in (contexts.get("nodes") or [])
311
+ if node.get("isRequired") and (node.get("name") or node.get("context"))
312
+ ]
313
+
314
+ def workflow_runs(self, sha):
315
+ """Return every Actions workflow run GitHub reported for one commit SHA.
316
+
317
+ A workflow that fails before any job starts - a workflow-level error, or
318
+ a `pull_request` run whose jobs never materialize - still records a
319
+ failed check suite, but it contributes no check runs, so the commit's
320
+ check-run rollup and `gh pr checks` both report success. Reading the
321
+ workflow runs directly is the only way the gate can see that failure.
322
+ Like the check-run endpoint this answers with an object, so it paginates
323
+ manually.
324
+ """
325
+ runs = []
326
+ for page in range(1, 101):
327
+ data = self.gh(
328
+ "GET", f"/actions/runs?head_sha={sha}&per_page=100&page={page}"
329
+ )
330
+ batch = data.get("workflow_runs") or []
331
+ runs.extend(batch)
332
+ if len(runs) >= int(data.get("total_count") or 0) or not batch:
333
+ return runs
334
+ raise RuntimeError("GitHub workflow-run pagination exceeded limit")
335
+
168
336
  def completed_dependency(self, number):
169
337
  if number in self._completed_dependencies:
170
338
  return self._completed_dependencies[number]
@@ -33,6 +33,7 @@ from pathlib import Path
33
33
  import time
34
34
  import urllib.error
35
35
  import urllib.request
36
+ from urllib.parse import quote
36
37
  from datetime import datetime, timedelta, timezone
37
38
 
38
39
  from github_client import github_request as _github_request
@@ -394,11 +395,41 @@ def _fetch_settings(agent_url: str, api_key: str) -> dict:
394
395
  raise RuntimeError(f"GET /api/settings failed: {exc.code}") from exc
395
396
 
396
397
 
398
+ def _fetch_profile_llm(agent_url: str, api_key: str, profile: str) -> dict | None:
399
+ """Fetch one LLM profile's config, or None when it no longer exists.
400
+
401
+ Uses X-Expose-Secrets: plaintext so the config carries a real api_key,
402
+ mirroring _fetch_settings. Only 404 falls back; other errors raise.
403
+ """
404
+ req = urllib.request.Request(
405
+ f"{agent_url}/api/profiles/{quote(profile, safe='')}",
406
+ headers={"X-Session-API-Key": api_key, "X-Expose-Secrets": "plaintext"},
407
+ )
408
+ try:
409
+ with urllib.request.urlopen(req) as r:
410
+ return json.loads(r.read())["config"]
411
+ except urllib.error.HTTPError as exc:
412
+ if exc.code == 404:
413
+ print(f"profile {profile!r} not found; falling back to active profile")
414
+ return None
415
+ raise
416
+
417
+
397
418
  def _get_agent_dict(agent_url: str, api_key: str) -> dict:
398
- """Fetch configured agent settings for conversation creation."""
419
+ """Fetch configured agent settings for conversation creation.
420
+
421
+ When AUTOMATION_MODEL names an LLM profile, resolve that profile instead
422
+ of the active one so the automation runs on its configured model.
423
+ """
399
424
  data = _fetch_settings(agent_url, api_key)
400
425
  agent_settings = data.get("agent_settings", {})
401
426
  llm = agent_settings.get("llm", {})
427
+ profile = os.environ.get("AUTOMATION_MODEL")
428
+ if profile:
429
+ profile_llm = _fetch_profile_llm(agent_url, api_key, profile)
430
+ if profile_llm is not None:
431
+ llm = profile_llm
432
+ llm["usage_id"] = f"profile:{profile}"
402
433
  # settings["agent_settings"]["agent"] reflects the full-app agent registry
403
434
  # (e.g. "CodeActAgent", "BrowsingAgent"). The automation SDK is a separate
404
435
  # runtime whose only valid kind is "Agent" — never forward that value.
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "github-stale-ci-pr-closer",
3
+ "version": "1.0.0",
4
+ "description": "Warn and close abandoned pull requests whose required CI remains failing.",
5
+ "author": {
6
+ "name": "OpenHands"
7
+ },
8
+ "license": "MIT"
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "github-stale-ci-pr-closer",
3
+ "version": "1.0.0",
4
+ "description": "Warn and close abandoned pull requests whose required CI remains failing.",
5
+ "author": {
6
+ "name": "OpenHands"
7
+ },
8
+ "license": "MIT"
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "github-stale-ci-pr-closer",
3
+ "version": "1.0.0",
4
+ "description": "Warn and close abandoned pull requests whose required CI remains failing.",
5
+ "author": {
6
+ "name": "OpenHands"
7
+ },
8
+ "license": "MIT"
9
+ }
@@ -0,0 +1,9 @@
1
+ # GitHub stale CI pull request closer
2
+
3
+ This extension runs a deterministic scheduled maintenance pass over configured
4
+ GitHub repositories. It warns after seven days of continuously failing required
5
+ CI and closes after another seven days without author follow-up. Optional checks
6
+ do not affect the decision, and no LLM slot is used.
7
+
8
+ Conversation and review automation state are unaffected. Warning and closure
9
+ comments carry stable markers so retries remain idempotent.
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: github-stale-ci-pr-closer
3
+ description: Warn and close abandoned pull requests whose required CI remains failing.
4
+ triggers:
5
+ - /github-stale-ci-pr-closer
6
+ ---
7
+
8
+ # GitHub stale CI pull request closer
9
+
10
+ This deterministic scheduled automation scans every open, non-draft pull
11
+ request in its configured repositories. It reads the required status checks
12
+ from the GitHub rules that apply to the pull request's base branch.
13
+
14
+ - After required CI remains failing for seven days, it posts one marked warning.
15
+ - After seven more days, it closes the pull request only when required CI still
16
+ fails and the author has neither pushed nor commented since the warning.
17
+ - Passing or pending CI, a draft conversion, closure, merge, or author follow-up
18
+ cancels the pending close. Continued failure starts a fresh warning window.
19
+
20
+ The automation creates no agent or conversation. Configure a repository-scoped
21
+ fine-grained PAT with Metadata: Read, Actions: Read, Commit statuses: Read,
22
+ Issues: Read and Write, and Pull requests: Read and Write. The token also needs
23
+ read access to repository rules. Package `scripts/worker.py` as `worker.py` and
24
+ the shared `skills/github/scripts/github_client.py` as `github_client.py`.
@@ -0,0 +1,8 @@
1
+ ---
2
+ # auto-generated by sync_extensions.py
3
+ description: Warn and close abandoned pull requests whose required CI remains failing.
4
+ ---
5
+
6
+ Read and follow the complete instructions in the SKILL.md file located in this skill's directory.
7
+
8
+ $ARGUMENTS