@laitszkin/apollo-toolkit 2.0.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 (204) hide show
  1. package/AGENTS.md +62 -0
  2. package/CHANGELOG.md +100 -0
  3. package/LICENSE +21 -0
  4. package/README.md +144 -0
  5. package/align-project-documents/SKILL.md +94 -0
  6. package/align-project-documents/agents/openai.yaml +4 -0
  7. package/analyse-app-logs/LICENSE +21 -0
  8. package/analyse-app-logs/README.md +126 -0
  9. package/analyse-app-logs/SKILL.md +121 -0
  10. package/analyse-app-logs/agents/openai.yaml +4 -0
  11. package/analyse-app-logs/references/investigation-checklist.md +58 -0
  12. package/analyse-app-logs/references/log-signal-patterns.md +52 -0
  13. package/answering-questions-with-research/SKILL.md +46 -0
  14. package/answering-questions-with-research/agents/openai.yaml +4 -0
  15. package/bin/apollo-toolkit.js +7 -0
  16. package/commit-and-push/LICENSE +21 -0
  17. package/commit-and-push/README.md +26 -0
  18. package/commit-and-push/SKILL.md +70 -0
  19. package/commit-and-push/agents/openai.yaml +4 -0
  20. package/commit-and-push/references/branch-naming.md +15 -0
  21. package/commit-and-push/references/commit-messages.md +19 -0
  22. package/deep-research-topics/LICENSE +21 -0
  23. package/deep-research-topics/README.md +43 -0
  24. package/deep-research-topics/SKILL.md +84 -0
  25. package/deep-research-topics/agents/openai.yaml +4 -0
  26. package/develop-new-features/LICENSE +21 -0
  27. package/develop-new-features/README.md +52 -0
  28. package/develop-new-features/SKILL.md +105 -0
  29. package/develop-new-features/agents/openai.yaml +4 -0
  30. package/develop-new-features/references/testing-e2e.md +35 -0
  31. package/develop-new-features/references/testing-integration.md +42 -0
  32. package/develop-new-features/references/testing-property-based.md +44 -0
  33. package/develop-new-features/references/testing-unit.md +37 -0
  34. package/discover-edge-cases/CHANGELOG.md +19 -0
  35. package/discover-edge-cases/LICENSE +21 -0
  36. package/discover-edge-cases/README.md +87 -0
  37. package/discover-edge-cases/SKILL.md +124 -0
  38. package/discover-edge-cases/agents/openai.yaml +4 -0
  39. package/discover-edge-cases/references/architecture-edge-cases.md +41 -0
  40. package/discover-edge-cases/references/code-edge-cases.md +46 -0
  41. package/docs-to-voice/.env.example +106 -0
  42. package/docs-to-voice/CHANGELOG.md +71 -0
  43. package/docs-to-voice/LICENSE +21 -0
  44. package/docs-to-voice/README.md +118 -0
  45. package/docs-to-voice/SKILL.md +107 -0
  46. package/docs-to-voice/agents/openai.yaml +4 -0
  47. package/docs-to-voice/scripts/docs_to_voice.py +1385 -0
  48. package/docs-to-voice/scripts/docs_to_voice.sh +11 -0
  49. package/docs-to-voice/tests/test_docs_to_voice_api_max_chars.py +210 -0
  50. package/docs-to-voice/tests/test_docs_to_voice_sentence_timeline.py +115 -0
  51. package/docs-to-voice/tests/test_docs_to_voice_settings.py +43 -0
  52. package/docs-to-voice/tests/test_docs_to_voice_speech_rate.py +57 -0
  53. package/enhance-existing-features/CHANGELOG.md +35 -0
  54. package/enhance-existing-features/LICENSE +21 -0
  55. package/enhance-existing-features/README.md +54 -0
  56. package/enhance-existing-features/SKILL.md +120 -0
  57. package/enhance-existing-features/agents/openai.yaml +4 -0
  58. package/enhance-existing-features/references/e2e-tests.md +25 -0
  59. package/enhance-existing-features/references/integration-tests.md +30 -0
  60. package/enhance-existing-features/references/property-based-tests.md +33 -0
  61. package/enhance-existing-features/references/unit-tests.md +29 -0
  62. package/feature-propose/LICENSE +21 -0
  63. package/feature-propose/README.md +23 -0
  64. package/feature-propose/SKILL.md +107 -0
  65. package/feature-propose/agents/openai.yaml +4 -0
  66. package/feature-propose/references/enhancement-features.md +25 -0
  67. package/feature-propose/references/important-features.md +25 -0
  68. package/feature-propose/references/mvp-features.md +25 -0
  69. package/feature-propose/references/performance-features.md +25 -0
  70. package/financial-research/SKILL.md +208 -0
  71. package/financial-research/agents/openai.yaml +4 -0
  72. package/financial-research/assets/weekly_market_report_template.md +45 -0
  73. package/fix-github-issues/SKILL.md +98 -0
  74. package/fix-github-issues/agents/openai.yaml +4 -0
  75. package/fix-github-issues/scripts/list_issues.py +148 -0
  76. package/fix-github-issues/tests/test_list_issues.py +127 -0
  77. package/generate-spec/LICENSE +21 -0
  78. package/generate-spec/README.md +61 -0
  79. package/generate-spec/SKILL.md +96 -0
  80. package/generate-spec/agents/openai.yaml +4 -0
  81. package/generate-spec/references/templates/checklist.md +78 -0
  82. package/generate-spec/references/templates/spec.md +55 -0
  83. package/generate-spec/references/templates/tasks.md +35 -0
  84. package/generate-spec/scripts/create-specs +123 -0
  85. package/harden-app-security/CHANGELOG.md +27 -0
  86. package/harden-app-security/LICENSE +21 -0
  87. package/harden-app-security/README.md +46 -0
  88. package/harden-app-security/SKILL.md +127 -0
  89. package/harden-app-security/agents/openai.yaml +4 -0
  90. package/harden-app-security/references/agent-attack-catalog.md +117 -0
  91. package/harden-app-security/references/common-software-attack-catalog.md +168 -0
  92. package/harden-app-security/references/red-team-extreme-scenarios.md +81 -0
  93. package/harden-app-security/references/risk-checklist.md +78 -0
  94. package/harden-app-security/references/security-test-patterns-agent.md +101 -0
  95. package/harden-app-security/references/security-test-patterns-finance.md +88 -0
  96. package/harden-app-security/references/test-snippets.md +73 -0
  97. package/improve-observability/SKILL.md +114 -0
  98. package/improve-observability/agents/openai.yaml +4 -0
  99. package/learn-skill-from-conversations/CHANGELOG.md +15 -0
  100. package/learn-skill-from-conversations/LICENSE +22 -0
  101. package/learn-skill-from-conversations/README.md +47 -0
  102. package/learn-skill-from-conversations/SKILL.md +85 -0
  103. package/learn-skill-from-conversations/agents/openai.yaml +4 -0
  104. package/learn-skill-from-conversations/scripts/extract_recent_conversations.py +369 -0
  105. package/learn-skill-from-conversations/tests/test_extract_recent_conversations.py +176 -0
  106. package/learning-error-book/SKILL.md +112 -0
  107. package/learning-error-book/agents/openai.yaml +4 -0
  108. package/learning-error-book/assets/error_book_template.md +66 -0
  109. package/learning-error-book/scripts/render_markdown_to_pdf.py +367 -0
  110. package/lib/cli.js +338 -0
  111. package/lib/installer.js +225 -0
  112. package/maintain-project-constraints/SKILL.md +109 -0
  113. package/maintain-project-constraints/agents/openai.yaml +4 -0
  114. package/maintain-skill-catalog/README.md +18 -0
  115. package/maintain-skill-catalog/SKILL.md +66 -0
  116. package/maintain-skill-catalog/agents/openai.yaml +4 -0
  117. package/novel-to-short-video/CHANGELOG.md +53 -0
  118. package/novel-to-short-video/LICENSE +21 -0
  119. package/novel-to-short-video/README.md +63 -0
  120. package/novel-to-short-video/SKILL.md +233 -0
  121. package/novel-to-short-video/agents/openai.yaml +4 -0
  122. package/novel-to-short-video/references/plan-template.md +71 -0
  123. package/novel-to-short-video/references/roles-json.md +41 -0
  124. package/open-github-issue/LICENSE +21 -0
  125. package/open-github-issue/README.md +97 -0
  126. package/open-github-issue/SKILL.md +119 -0
  127. package/open-github-issue/agents/openai.yaml +4 -0
  128. package/open-github-issue/scripts/open_github_issue.py +380 -0
  129. package/open-github-issue/tests/test_open_github_issue.py +159 -0
  130. package/open-source-pr-workflow/CHANGELOG.md +32 -0
  131. package/open-source-pr-workflow/LICENSE +21 -0
  132. package/open-source-pr-workflow/README.md +23 -0
  133. package/open-source-pr-workflow/SKILL.md +123 -0
  134. package/open-source-pr-workflow/agents/openai.yaml +4 -0
  135. package/openai-text-to-image-storyboard/.env.example +10 -0
  136. package/openai-text-to-image-storyboard/CHANGELOG.md +49 -0
  137. package/openai-text-to-image-storyboard/LICENSE +21 -0
  138. package/openai-text-to-image-storyboard/README.md +99 -0
  139. package/openai-text-to-image-storyboard/SKILL.md +107 -0
  140. package/openai-text-to-image-storyboard/agents/openai.yaml +4 -0
  141. package/openai-text-to-image-storyboard/scripts/generate_storyboard_images.py +763 -0
  142. package/package.json +36 -0
  143. package/record-spending/SKILL.md +113 -0
  144. package/record-spending/agents/openai.yaml +4 -0
  145. package/record-spending/references/account-format.md +33 -0
  146. package/record-spending/references/workbook-layout.md +84 -0
  147. package/resolve-review-comments/SKILL.md +122 -0
  148. package/resolve-review-comments/agents/openai.yaml +4 -0
  149. package/resolve-review-comments/references/adoption-criteria.md +23 -0
  150. package/resolve-review-comments/scripts/review_threads.py +425 -0
  151. package/resolve-review-comments/tests/test_review_threads.py +74 -0
  152. package/review-change-set/LICENSE +21 -0
  153. package/review-change-set/README.md +55 -0
  154. package/review-change-set/SKILL.md +103 -0
  155. package/review-change-set/agents/openai.yaml +4 -0
  156. package/review-codebases/LICENSE +21 -0
  157. package/review-codebases/README.md +67 -0
  158. package/review-codebases/SKILL.md +109 -0
  159. package/review-codebases/agents/openai.yaml +4 -0
  160. package/scripts/install_skills.ps1 +283 -0
  161. package/scripts/install_skills.sh +262 -0
  162. package/scripts/validate_openai_agent_config.py +194 -0
  163. package/scripts/validate_skill_frontmatter.py +110 -0
  164. package/specs-to-project-docs/LICENSE +21 -0
  165. package/specs-to-project-docs/README.md +57 -0
  166. package/specs-to-project-docs/SKILL.md +111 -0
  167. package/specs-to-project-docs/agents/openai.yaml +4 -0
  168. package/specs-to-project-docs/references/templates/architecture.md +29 -0
  169. package/specs-to-project-docs/references/templates/configuration.md +29 -0
  170. package/specs-to-project-docs/references/templates/developer-guide.md +33 -0
  171. package/specs-to-project-docs/references/templates/docs-index.md +39 -0
  172. package/specs-to-project-docs/references/templates/features.md +25 -0
  173. package/specs-to-project-docs/references/templates/getting-started.md +38 -0
  174. package/specs-to-project-docs/references/templates/readme.md +49 -0
  175. package/systematic-debug/LICENSE +21 -0
  176. package/systematic-debug/README.md +81 -0
  177. package/systematic-debug/SKILL.md +59 -0
  178. package/systematic-debug/agents/openai.yaml +4 -0
  179. package/text-to-short-video/.env.example +36 -0
  180. package/text-to-short-video/LICENSE +21 -0
  181. package/text-to-short-video/README.md +82 -0
  182. package/text-to-short-video/SKILL.md +221 -0
  183. package/text-to-short-video/agents/openai.yaml +4 -0
  184. package/text-to-short-video/scripts/enforce_video_aspect_ratio.py +350 -0
  185. package/version-release/CHANGELOG.md +53 -0
  186. package/version-release/LICENSE +21 -0
  187. package/version-release/README.md +28 -0
  188. package/version-release/SKILL.md +94 -0
  189. package/version-release/agents/openai.yaml +4 -0
  190. package/version-release/references/branch-naming.md +15 -0
  191. package/version-release/references/changelog-writing.md +8 -0
  192. package/version-release/references/commit-messages.md +19 -0
  193. package/version-release/references/readme-writing.md +12 -0
  194. package/version-release/references/semantic-versioning.md +12 -0
  195. package/video-production/CHANGELOG.md +104 -0
  196. package/video-production/LICENSE +18 -0
  197. package/video-production/README.md +68 -0
  198. package/video-production/SKILL.md +213 -0
  199. package/video-production/agents/openai.yaml +4 -0
  200. package/video-production/references/plan-template.md +54 -0
  201. package/video-production/references/roles-json.md +41 -0
  202. package/weekly-financial-event-report/SKILL.md +195 -0
  203. package/weekly-financial-event-report/agents/openai.yaml +4 -0
  204. package/weekly-financial-event-report/assets/financial_event_report_template.md +53 -0
@@ -0,0 +1,380 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import json
6
+ import os
7
+ import re
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ import tempfile
12
+ from pathlib import Path
13
+ from urllib import error, request
14
+
15
+ GITHUB_API_BASE = "https://api.github.com"
16
+ README_ACCEPT = "application/vnd.github.raw+json"
17
+ JSON_ACCEPT = "application/vnd.github+json"
18
+ DEFAULT_REPRO_ZH = "尚未穩定重現;需補充更多執行期資料。"
19
+ DEFAULT_REPRO_EN = "Not yet reliably reproducible; more runtime evidence is required."
20
+ ISSUE_TYPE_PROBLEM = "problem"
21
+ ISSUE_TYPE_FEATURE = "feature"
22
+
23
+
24
+ def parse_args() -> argparse.Namespace:
25
+ parser = argparse.ArgumentParser(
26
+ description=(
27
+ "Publish a structured GitHub issue or feature proposal. "
28
+ "Auth order: gh CLI login -> GitHub token -> draft only."
29
+ )
30
+ )
31
+ parser.add_argument("--title", required=True, help="Issue title")
32
+ parser.add_argument(
33
+ "--issue-type",
34
+ choices=[ISSUE_TYPE_PROBLEM, ISSUE_TYPE_FEATURE],
35
+ default=ISSUE_TYPE_PROBLEM,
36
+ help="Structured issue type to publish.",
37
+ )
38
+ parser.add_argument(
39
+ "--problem-description",
40
+ help="Issue section content: problem description",
41
+ )
42
+ parser.add_argument(
43
+ "--suspected-cause",
44
+ help="Issue section content: suspected cause",
45
+ )
46
+ parser.add_argument(
47
+ "--reproduction",
48
+ help="Issue section content: reproduction conditions (optional)",
49
+ )
50
+ parser.add_argument(
51
+ "--proposal",
52
+ help="Issue section content: feature proposal summary (optional; defaults to title)",
53
+ )
54
+ parser.add_argument(
55
+ "--reason",
56
+ help="Issue section content: why the feature is needed",
57
+ )
58
+ parser.add_argument(
59
+ "--suggested-architecture",
60
+ help="Issue section content: suggested architecture for the feature",
61
+ )
62
+ parser.add_argument(
63
+ "--repo",
64
+ help="Target repository in owner/repo format. Defaults to origin remote.",
65
+ )
66
+ parser.add_argument(
67
+ "--dry-run",
68
+ action="store_true",
69
+ help="Build and print payload only, without creating an issue.",
70
+ )
71
+ return parser.parse_args()
72
+
73
+
74
+ def validate_issue_content_args(args: argparse.Namespace) -> None:
75
+ if args.issue_type == ISSUE_TYPE_FEATURE:
76
+ if not (args.reason or "").strip():
77
+ raise SystemExit("Feature issues require --reason.")
78
+ if not (args.suggested_architecture or "").strip():
79
+ raise SystemExit("Feature issues require --suggested-architecture.")
80
+ return
81
+
82
+ if not (args.problem_description or "").strip():
83
+ raise SystemExit("Problem issues require --problem-description.")
84
+ if not (args.suspected_cause or "").strip():
85
+ raise SystemExit("Problem issues require --suspected-cause.")
86
+
87
+
88
+ def run_command(args: list[str]) -> subprocess.CompletedProcess[str]:
89
+ return subprocess.run(args, check=False, capture_output=True, text=True)
90
+
91
+
92
+ def has_gh_auth() -> bool:
93
+ if shutil.which("gh") is None:
94
+ return False
95
+ result = run_command(["gh", "auth", "status"])
96
+ return result.returncode == 0
97
+
98
+
99
+ def get_token() -> str | None:
100
+ for key in ("GITHUB_TOKEN", "GH_TOKEN"):
101
+ value = os.getenv(key, "").strip()
102
+ if value:
103
+ return value
104
+ return None
105
+
106
+
107
+ def resolve_repo(explicit_repo: str | None) -> str:
108
+ if explicit_repo:
109
+ return validate_repo(explicit_repo)
110
+
111
+ remote_result = run_command(["git", "remote", "get-url", "origin"])
112
+ if remote_result.returncode != 0:
113
+ raise SystemExit("Unable to resolve origin remote. Pass --repo owner/repo.")
114
+
115
+ remote = remote_result.stdout.strip()
116
+ match = re.search(
117
+ r"github\.com[:/](?P<owner>[A-Za-z0-9_.-]+)/(?P<repo>[A-Za-z0-9_.-]+?)(?:\.git)?$",
118
+ remote,
119
+ )
120
+ if not match:
121
+ raise SystemExit("Origin remote is not a GitHub repository. Pass --repo owner/repo.")
122
+
123
+ return f"{match.group('owner')}/{match.group('repo')}"
124
+
125
+
126
+ def validate_repo(repo: str) -> str:
127
+ candidate = repo.strip()
128
+ if not re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", candidate):
129
+ raise SystemExit("Invalid repo format. Use owner/repo.")
130
+ return candidate
131
+
132
+
133
+ def github_request(
134
+ method: str,
135
+ path: str,
136
+ *,
137
+ token: str | None,
138
+ accept: str,
139
+ payload: dict | None = None,
140
+ ) -> str:
141
+ headers = {
142
+ "Accept": accept,
143
+ "User-Agent": "open-github-issue-skill",
144
+ "X-GitHub-Api-Version": "2022-11-28",
145
+ }
146
+ data = None
147
+
148
+ if token:
149
+ headers["Authorization"] = f"Bearer {token}"
150
+
151
+ if payload is not None:
152
+ data = json.dumps(payload).encode("utf-8")
153
+ headers["Content-Type"] = "application/json"
154
+
155
+ req = request.Request(
156
+ url=f"{GITHUB_API_BASE}{path}",
157
+ data=data,
158
+ headers=headers,
159
+ method=method,
160
+ )
161
+
162
+ try:
163
+ with request.urlopen(req, timeout=30) as response:
164
+ return response.read().decode("utf-8")
165
+ except error.HTTPError as exc:
166
+ detail = exc.read().decode("utf-8", errors="replace")
167
+ raise RuntimeError(f"GitHub API {exc.code} {path}: {detail}") from exc
168
+ except error.URLError as exc:
169
+ raise RuntimeError(f"GitHub API request failed for {path}: {exc.reason}") from exc
170
+
171
+
172
+ def fetch_remote_readme(repo: str, gh_authenticated: bool, token: str | None) -> str:
173
+ if gh_authenticated:
174
+ result = run_command(
175
+ ["gh", "api", "-H", f"Accept: {README_ACCEPT}", f"repos/{repo}/readme"]
176
+ )
177
+ if result.returncode == 0:
178
+ return result.stdout
179
+
180
+ try:
181
+ return github_request(
182
+ "GET",
183
+ f"/repos/{repo}/readme",
184
+ token=token,
185
+ accept=README_ACCEPT,
186
+ )
187
+ except RuntimeError:
188
+ return ""
189
+
190
+
191
+ def detect_issue_language(readme_content: str) -> str:
192
+ if not readme_content.strip():
193
+ return "en"
194
+
195
+ chinese_chars = len(re.findall(r"[\u4e00-\u9fff]", readme_content))
196
+ language_chars = len(re.findall(r"[A-Za-z\u4e00-\u9fff]", readme_content))
197
+
198
+ if chinese_chars >= 20 and chinese_chars / max(language_chars, 1) >= 0.08:
199
+ return "zh"
200
+ return "en"
201
+
202
+
203
+ def build_issue_body(
204
+ *,
205
+ issue_type: str,
206
+ language: str,
207
+ title: str,
208
+ problem_description: str | None,
209
+ suspected_cause: str | None,
210
+ reproduction: str | None,
211
+ proposal: str | None,
212
+ reason: str | None,
213
+ suggested_architecture: str | None,
214
+ ) -> str:
215
+ if issue_type == ISSUE_TYPE_FEATURE:
216
+ proposal_text = (proposal or title).strip()
217
+ reason_text = (reason or "").strip()
218
+ architecture_text = (suggested_architecture or "").strip()
219
+
220
+ if language == "zh":
221
+ return (
222
+ "### 功能提案\n"
223
+ f"{proposal_text}\n\n"
224
+ "### 原因\n"
225
+ f"{reason_text}\n\n"
226
+ "### 建議架構\n"
227
+ f"{architecture_text}\n"
228
+ )
229
+
230
+ return (
231
+ "### Feature Proposal\n"
232
+ f"{proposal_text}\n\n"
233
+ "### Why This Is Needed\n"
234
+ f"{reason_text}\n\n"
235
+ "### Suggested Architecture\n"
236
+ f"{architecture_text}\n"
237
+ )
238
+
239
+ if language == "zh":
240
+ repro_text = (reproduction or DEFAULT_REPRO_ZH).strip()
241
+ return (
242
+ "### 問題描述\n"
243
+ f"{(problem_description or '').strip()}\n\n"
244
+ "### 推測原因\n"
245
+ f"{(suspected_cause or '').strip()}\n\n"
246
+ "### 重現條件(如有)\n"
247
+ f"{repro_text}\n"
248
+ )
249
+
250
+ repro_text = (reproduction or DEFAULT_REPRO_EN).strip()
251
+ return (
252
+ "### Problem Description\n"
253
+ f"{(problem_description or '').strip()}\n\n"
254
+ "### Suspected Cause\n"
255
+ f"{(suspected_cause or '').strip()}\n\n"
256
+ "### Reproduction Conditions (if available)\n"
257
+ f"{repro_text}\n"
258
+ )
259
+
260
+
261
+ def create_issue_with_gh(repo: str, title: str, body: str) -> str:
262
+ tmp_file: Path | None = None
263
+ try:
264
+ with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".md", delete=False) as handle:
265
+ handle.write(body)
266
+ tmp_file = Path(handle.name)
267
+
268
+ result = run_command(
269
+ [
270
+ "gh",
271
+ "issue",
272
+ "create",
273
+ "--repo",
274
+ repo,
275
+ "--title",
276
+ title,
277
+ "--body-file",
278
+ str(tmp_file),
279
+ ]
280
+ )
281
+ if result.returncode != 0:
282
+ raise RuntimeError(result.stderr.strip() or "gh issue create failed")
283
+
284
+ url_match = re.search(r"https://github\.com/[^\s]+/issues/\d+", result.stdout)
285
+ return url_match.group(0) if url_match else result.stdout.strip()
286
+ finally:
287
+ if tmp_file and tmp_file.exists():
288
+ tmp_file.unlink()
289
+
290
+
291
+ def create_issue_with_token(repo: str, title: str, body: str, token: str) -> str:
292
+ response = github_request(
293
+ "POST",
294
+ f"/repos/{repo}/issues",
295
+ token=token,
296
+ accept=JSON_ACCEPT,
297
+ payload={"title": title, "body": body},
298
+ )
299
+ parsed = json.loads(response)
300
+ issue_url = parsed.get("html_url", "")
301
+ if not issue_url:
302
+ raise RuntimeError("Issue created but response did not include html_url")
303
+ return issue_url
304
+
305
+
306
+ def main() -> int:
307
+ args = parse_args()
308
+ validate_issue_content_args(args)
309
+
310
+ gh_authenticated = has_gh_auth()
311
+ token = get_token()
312
+ repo = resolve_repo(args.repo)
313
+
314
+ readme_content = fetch_remote_readme(repo, gh_authenticated, token)
315
+ language = detect_issue_language(readme_content)
316
+
317
+ issue_body = build_issue_body(
318
+ issue_type=args.issue_type,
319
+ language=language,
320
+ title=args.title,
321
+ problem_description=args.problem_description,
322
+ suspected_cause=args.suspected_cause,
323
+ reproduction=args.reproduction,
324
+ proposal=args.proposal,
325
+ reason=args.reason,
326
+ suggested_architecture=args.suggested_architecture,
327
+ )
328
+
329
+ mode = "draft-only"
330
+ issue_url = ""
331
+ publish_error = ""
332
+
333
+ if args.dry_run:
334
+ mode = "dry-run"
335
+ elif gh_authenticated:
336
+ try:
337
+ issue_url = create_issue_with_gh(repo, args.title, issue_body)
338
+ mode = "gh-cli"
339
+ except RuntimeError as exc:
340
+ if token:
341
+ try:
342
+ issue_url = create_issue_with_token(repo, args.title, issue_body, token)
343
+ mode = "github-token"
344
+ except RuntimeError as token_exc:
345
+ publish_error = str(token_exc)
346
+ else:
347
+ publish_error = str(exc)
348
+ elif token:
349
+ try:
350
+ issue_url = create_issue_with_token(repo, args.title, issue_body, token)
351
+ mode = "github-token"
352
+ except RuntimeError as exc:
353
+ publish_error = str(exc)
354
+
355
+ output = {
356
+ "repo": repo,
357
+ "issue_type": args.issue_type,
358
+ "language": "zh" if language == "zh" else "en",
359
+ "mode": mode,
360
+ "issue_url": issue_url,
361
+ "issue_title": args.title,
362
+ "issue_body": issue_body,
363
+ "publish_error": publish_error,
364
+ }
365
+ print(json.dumps(output, ensure_ascii=False))
366
+
367
+ if mode == "draft-only":
368
+ if publish_error:
369
+ print(f"Issue publish failed. Return draft only: {publish_error}", file=sys.stderr)
370
+ else:
371
+ print(
372
+ "No authenticated gh CLI session and no GitHub token found. "
373
+ "Return draft issue body only.",
374
+ file=sys.stderr,
375
+ )
376
+ return 0
377
+
378
+
379
+ if __name__ == "__main__":
380
+ raise SystemExit(main())
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.util
6
+ import io
7
+ import json
8
+ import unittest
9
+ from argparse import Namespace
10
+ from pathlib import Path
11
+ from unittest.mock import patch
12
+
13
+
14
+ SCRIPT_PATH = Path(__file__).resolve().parents[1] / "scripts" / "open_github_issue.py"
15
+ SPEC = importlib.util.spec_from_file_location("open_github_issue", SCRIPT_PATH)
16
+ MODULE = importlib.util.module_from_spec(SPEC)
17
+ SPEC.loader.exec_module(MODULE)
18
+
19
+
20
+ class OpenGitHubIssueTests(unittest.TestCase):
21
+ def test_validate_repo_rejects_invalid_format(self) -> None:
22
+ with self.assertRaises(SystemExit):
23
+ MODULE.validate_repo("owner-only")
24
+
25
+ def test_detect_issue_language_prefers_chinese_when_threshold_met(self) -> None:
26
+ readme = "這是一個中文專案說明。" * 10
27
+
28
+ self.assertEqual(MODULE.detect_issue_language(readme), "zh")
29
+
30
+ def test_detect_issue_language_defaults_to_english_for_sparse_chinese(self) -> None:
31
+ readme = "English README with only 少量中文 and mostly latin text." * 2
32
+
33
+ self.assertEqual(MODULE.detect_issue_language(readme), "en")
34
+
35
+ def test_build_issue_body_uses_default_reproduction_text(self) -> None:
36
+ zh_body = MODULE.build_issue_body(
37
+ issue_type=MODULE.ISSUE_TYPE_PROBLEM,
38
+ language="zh",
39
+ title="[Log] 問題",
40
+ problem_description="問題",
41
+ suspected_cause="原因",
42
+ reproduction=None,
43
+ proposal=None,
44
+ reason=None,
45
+ suggested_architecture=None,
46
+ )
47
+ en_body = MODULE.build_issue_body(
48
+ issue_type=MODULE.ISSUE_TYPE_PROBLEM,
49
+ language="en",
50
+ title="[Log] problem",
51
+ problem_description="problem",
52
+ suspected_cause="cause",
53
+ reproduction=None,
54
+ proposal=None,
55
+ reason=None,
56
+ suggested_architecture=None,
57
+ )
58
+
59
+ self.assertIn(MODULE.DEFAULT_REPRO_ZH, zh_body)
60
+ self.assertIn(MODULE.DEFAULT_REPRO_EN, en_body)
61
+
62
+ def test_build_issue_body_supports_feature_issue_sections(self) -> None:
63
+ zh_body = MODULE.build_issue_body(
64
+ issue_type=MODULE.ISSUE_TYPE_FEATURE,
65
+ language="zh",
66
+ title="[Feature] 匯出事故時間線",
67
+ problem_description=None,
68
+ suspected_cause=None,
69
+ reproduction=None,
70
+ proposal="新增事故時間線匯出功能",
71
+ reason="減少手動整理 postmortem 成本",
72
+ suggested_architecture="重用時間線查詢服務,新增共用匯出模組",
73
+ )
74
+ en_body = MODULE.build_issue_body(
75
+ issue_type=MODULE.ISSUE_TYPE_FEATURE,
76
+ language="en",
77
+ title="[Feature] Export incident timeline",
78
+ problem_description=None,
79
+ suspected_cause=None,
80
+ reproduction=None,
81
+ proposal="Allow exporting incident timelines",
82
+ reason="Support postmortem handoff",
83
+ suggested_architecture="Add shared exporters and UI action",
84
+ )
85
+
86
+ self.assertIn("### 功能提案", zh_body)
87
+ self.assertIn("### 建議架構", zh_body)
88
+ self.assertIn("### Feature Proposal", en_body)
89
+ self.assertIn("### Suggested Architecture", en_body)
90
+
91
+ def test_validate_issue_content_args_requires_feature_fields(self) -> None:
92
+ with self.assertRaises(SystemExit):
93
+ MODULE.validate_issue_content_args(
94
+ Namespace(
95
+ issue_type=MODULE.ISSUE_TYPE_FEATURE,
96
+ reason="",
97
+ suggested_architecture="shared module",
98
+ problem_description=None,
99
+ suspected_cause=None,
100
+ )
101
+ )
102
+
103
+ with self.assertRaises(SystemExit):
104
+ MODULE.validate_issue_content_args(
105
+ Namespace(
106
+ issue_type=MODULE.ISSUE_TYPE_FEATURE,
107
+ reason="important now",
108
+ suggested_architecture="",
109
+ problem_description=None,
110
+ suspected_cause=None,
111
+ )
112
+ )
113
+
114
+ MODULE.validate_issue_content_args(
115
+ Namespace(
116
+ issue_type=MODULE.ISSUE_TYPE_FEATURE,
117
+ reason="important now",
118
+ suggested_architecture="shared module",
119
+ problem_description=None,
120
+ suspected_cause=None,
121
+ )
122
+ )
123
+
124
+ def test_main_dry_run_returns_structured_json_without_publish_attempt(self) -> None:
125
+ args = Namespace(
126
+ title="[Log] sample",
127
+ issue_type=MODULE.ISSUE_TYPE_PROBLEM,
128
+ problem_description="problem",
129
+ suspected_cause="handler.py:12",
130
+ reproduction=None,
131
+ proposal=None,
132
+ reason=None,
133
+ suggested_architecture=None,
134
+ repo="owner/repo",
135
+ dry_run=True,
136
+ )
137
+
138
+ with patch.object(MODULE, "parse_args", return_value=args), patch.object(
139
+ MODULE, "has_gh_auth", return_value=False
140
+ ), patch.object(MODULE, "get_token", return_value=None), patch.object(
141
+ MODULE, "fetch_remote_readme", return_value="中文說明" * 20
142
+ ), patch.object(MODULE, "create_issue_with_gh") as create_with_gh, patch.object(
143
+ MODULE, "create_issue_with_token"
144
+ ) as create_with_token, patch("sys.stdout", new_callable=io.StringIO) as stdout:
145
+ code = MODULE.main()
146
+
147
+ create_with_gh.assert_not_called()
148
+ create_with_token.assert_not_called()
149
+ self.assertEqual(code, 0)
150
+
151
+ payload = json.loads(stdout.getvalue())
152
+ self.assertEqual(payload["mode"], "dry-run")
153
+ self.assertEqual(payload["issue_type"], MODULE.ISSUE_TYPE_PROBLEM)
154
+ self.assertEqual(payload["language"], "zh")
155
+ self.assertIn("### 問題描述", payload["issue_body"])
156
+
157
+
158
+ if __name__ == "__main__":
159
+ unittest.main()
@@ -0,0 +1,32 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on Keep a Changelog and this project follows Semantic Versioning.
6
+
7
+ ## [0.1.3] - 2026-02-22
8
+
9
+ ### Changed
10
+ - Switch the PR workflow default to open PRs directly without waiting for draft confirmation.
11
+ - Show PR drafts only when the user explicitly asks to review before PR creation.
12
+ - Align SKILL.md, README highlights, and agent default prompt with the new draft-handling behavior.
13
+
14
+ ## [0.1.2] - 2026-02-20
15
+
16
+ ### Changed
17
+ - Update branch naming convention from `codex/feature/{feature_name}` to `codex/{change_type}/{changes}` across skill docs and agent prompt.
18
+ - Remove the required "Dependent Skill Updates" PR-body section so PR content stays focused on the change itself.
19
+ - Clarify that internal skill/tool workflow details should not appear in PR title/body content.
20
+ - Align README highlights and agent default prompt with the PR-content-only rule.
21
+
22
+ ## [0.1.1] - 2026-02-20
23
+
24
+ ### Changed
25
+ - Require running `edge-case-test-fixer` and `code-simplifier` in sequence before opening a PR when code-affecting changes exist.
26
+ - Add a required "Dependent Skill Updates" section to PR bodies to capture outputs from both dependency skills.
27
+ - Align agent default prompt and README highlights with the dependency-skill workflow.
28
+
29
+ ## [0.1.0] - 2026-02-20
30
+
31
+ ### Added
32
+ - Initial open-source PR workflow skill with branch naming, fork targeting defaults, and required PR content sections.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 LTKSunny
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,23 @@
1
+ # open-source-pr-workflow
2
+
3
+ A Codex skill focused on preparing and opening review-ready pull requests for open-source projects.
4
+
5
+ ## What this repository contains
6
+
7
+ - `SKILL.md`: The main workflow definition.
8
+ - `agents/openai.yaml`: Agent integration settings.
9
+
10
+ ## Highlights
11
+
12
+ - Enforces branch naming convention: `codex/{change_type}/{changes}`
13
+ - Focuses on PR preparation/creation instead of feature implementation
14
+ - Opens PRs directly by default; only shows drafts when the user explicitly asks to review first
15
+ - Defaults forked repositories to open PRs against the upstream parent repository (unless user explicitly requests the fork)
16
+ - Requires clear PR sections for motivation, engineering rationale, and test results
17
+ - For code changes, requires running `discover-edge-cases`, resolving confirmed findings, then running `code-simplifier` before opening PR
18
+ - PR content should focus only on change motivation, decisions, and validation (no internal skill/tool process notes)
19
+ - Encourages minimal, verifiable, review-ready contributions
20
+
21
+ ## License
22
+
23
+ Released under the MIT License. See `LICENSE` for details.