@laitszkin/apollo-toolkit 3.13.2 → 3.14.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 (154) hide show
  1. package/AGENTS.md +7 -7
  2. package/CHANGELOG.md +27 -0
  3. package/CLAUDE.md +8 -8
  4. package/analyse-app-logs/SKILL.md +3 -3
  5. package/bin/apollo-toolkit.ts +7 -0
  6. package/codex/codex-memory-manager/SKILL.md +2 -2
  7. package/codex/learn-skill-from-conversations/SKILL.md +3 -3
  8. package/dist/bin/apollo-toolkit.d.ts +2 -0
  9. package/dist/bin/apollo-toolkit.js +7 -0
  10. package/dist/lib/cli.d.ts +41 -0
  11. package/dist/lib/cli.js +655 -0
  12. package/dist/lib/installer.d.ts +59 -0
  13. package/dist/lib/installer.js +404 -0
  14. package/dist/lib/tool-runner.d.ts +19 -0
  15. package/dist/lib/tool-runner.js +536 -0
  16. package/dist/lib/tools/architecture.d.ts +2 -0
  17. package/dist/lib/tools/architecture.js +34 -0
  18. package/dist/lib/tools/create-specs.d.ts +2 -0
  19. package/dist/lib/tools/create-specs.js +175 -0
  20. package/dist/lib/tools/docs-to-voice.d.ts +2 -0
  21. package/dist/lib/tools/docs-to-voice.js +705 -0
  22. package/dist/lib/tools/enforce-video-aspect-ratio.d.ts +2 -0
  23. package/dist/lib/tools/enforce-video-aspect-ratio.js +312 -0
  24. package/dist/lib/tools/extract-conversations.d.ts +2 -0
  25. package/dist/lib/tools/extract-conversations.js +105 -0
  26. package/dist/lib/tools/extract-pdf-text.d.ts +2 -0
  27. package/dist/lib/tools/extract-pdf-text.js +92 -0
  28. package/dist/lib/tools/filter-logs.d.ts +2 -0
  29. package/dist/lib/tools/filter-logs.js +94 -0
  30. package/dist/lib/tools/find-github-issues.d.ts +2 -0
  31. package/dist/lib/tools/find-github-issues.js +176 -0
  32. package/dist/lib/tools/generate-storyboard-images.d.ts +2 -0
  33. package/dist/lib/tools/generate-storyboard-images.js +419 -0
  34. package/dist/lib/tools/log-cli-utils.d.ts +35 -0
  35. package/dist/lib/tools/log-cli-utils.js +233 -0
  36. package/dist/lib/tools/open-github-issue.d.ts +2 -0
  37. package/dist/lib/tools/open-github-issue.js +750 -0
  38. package/dist/lib/tools/read-github-issue.d.ts +2 -0
  39. package/dist/lib/tools/read-github-issue.js +134 -0
  40. package/dist/lib/tools/render-error-book.d.ts +2 -0
  41. package/dist/lib/tools/render-error-book.js +265 -0
  42. package/dist/lib/tools/render-katex.d.ts +2 -0
  43. package/dist/lib/tools/render-katex.js +294 -0
  44. package/dist/lib/tools/review-threads.d.ts +2 -0
  45. package/dist/lib/tools/review-threads.js +491 -0
  46. package/dist/lib/tools/search-logs.d.ts +2 -0
  47. package/dist/lib/tools/search-logs.js +164 -0
  48. package/dist/lib/tools/sync-memory-index.d.ts +2 -0
  49. package/dist/lib/tools/sync-memory-index.js +113 -0
  50. package/dist/lib/tools/validate-openai-agent-config.d.ts +2 -0
  51. package/dist/lib/tools/validate-openai-agent-config.js +184 -0
  52. package/dist/lib/tools/validate-skill-frontmatter.d.ts +2 -0
  53. package/dist/lib/tools/validate-skill-frontmatter.js +118 -0
  54. package/dist/lib/types.d.ts +82 -0
  55. package/dist/lib/types.js +2 -0
  56. package/dist/lib/updater.d.ts +34 -0
  57. package/dist/lib/updater.js +112 -0
  58. package/dist/lib/utils/format.d.ts +2 -0
  59. package/dist/lib/utils/format.js +6 -0
  60. package/dist/lib/utils/terminal.d.ts +12 -0
  61. package/dist/lib/utils/terminal.js +26 -0
  62. package/docs-to-voice/SKILL.md +0 -1
  63. package/generate-spec/SKILL.md +1 -1
  64. package/katex/SKILL.md +1 -2
  65. package/lib/cli.ts +780 -0
  66. package/lib/installer.ts +466 -0
  67. package/lib/tool-runner.ts +561 -0
  68. package/lib/tools/architecture.ts +34 -0
  69. package/lib/tools/create-specs.ts +204 -0
  70. package/lib/tools/docs-to-voice.ts +799 -0
  71. package/lib/tools/enforce-video-aspect-ratio.ts +368 -0
  72. package/lib/tools/extract-conversations.ts +114 -0
  73. package/lib/tools/extract-pdf-text.ts +99 -0
  74. package/lib/tools/filter-logs.ts +118 -0
  75. package/lib/tools/find-github-issues.ts +211 -0
  76. package/lib/tools/generate-storyboard-images.ts +455 -0
  77. package/lib/tools/log-cli-utils.ts +262 -0
  78. package/lib/tools/open-github-issue.ts +930 -0
  79. package/lib/tools/read-github-issue.ts +179 -0
  80. package/lib/tools/render-error-book.ts +300 -0
  81. package/lib/tools/render-katex.ts +325 -0
  82. package/lib/tools/review-threads.ts +590 -0
  83. package/lib/tools/search-logs.ts +200 -0
  84. package/lib/tools/sync-memory-index.ts +114 -0
  85. package/lib/tools/validate-openai-agent-config.ts +209 -0
  86. package/lib/tools/validate-skill-frontmatter.ts +124 -0
  87. package/lib/types.ts +90 -0
  88. package/lib/updater.ts +165 -0
  89. package/lib/utils/format.ts +7 -0
  90. package/lib/utils/terminal.ts +22 -0
  91. package/open-github-issue/SKILL.md +2 -2
  92. package/optimise-skill/SKILL.md +1 -1
  93. package/package.json +13 -4
  94. package/resources/project-architecture/assets/architecture.css +764 -0
  95. package/resources/project-architecture/assets/viewer.client.js +144 -0
  96. package/resources/project-architecture/index.html +42 -0
  97. package/review-spec-related-changes/SKILL.md +1 -1
  98. package/solve-issues-found-during-review/SKILL.md +2 -1
  99. package/tsconfig.json +28 -0
  100. package/analyse-app-logs/scripts/__pycache__/filter_logs_by_time.cpython-312.pyc +0 -0
  101. package/analyse-app-logs/scripts/__pycache__/log_cli_utils.cpython-312.pyc +0 -0
  102. package/analyse-app-logs/scripts/__pycache__/search_logs.cpython-312.pyc +0 -0
  103. package/analyse-app-logs/scripts/filter_logs_by_time.py +0 -64
  104. package/analyse-app-logs/scripts/log_cli_utils.py +0 -112
  105. package/analyse-app-logs/scripts/search_logs.py +0 -137
  106. package/analyse-app-logs/tests/test_filter_logs_by_time.py +0 -95
  107. package/analyse-app-logs/tests/test_search_logs.py +0 -100
  108. package/codex/codex-memory-manager/scripts/extract_recent_conversations.py +0 -369
  109. package/codex/codex-memory-manager/scripts/sync_memory_index.py +0 -130
  110. package/codex/codex-memory-manager/tests/test_extract_recent_conversations.py +0 -177
  111. package/codex/codex-memory-manager/tests/test_memory_template.py +0 -37
  112. package/codex/codex-memory-manager/tests/test_sync_memory_index.py +0 -84
  113. package/codex/learn-skill-from-conversations/scripts/extract_recent_conversations.py +0 -369
  114. package/codex/learn-skill-from-conversations/tests/test_extract_recent_conversations.py +0 -177
  115. package/docs-to-voice/scripts/__pycache__/docs_to_voice.cpython-312.pyc +0 -0
  116. package/docs-to-voice/scripts/docs_to_voice.py +0 -1385
  117. package/docs-to-voice/scripts/docs_to_voice.sh +0 -11
  118. package/docs-to-voice/tests/test_docs_to_voice_api_max_chars.py +0 -210
  119. package/docs-to-voice/tests/test_docs_to_voice_sentence_timeline.py +0 -115
  120. package/docs-to-voice/tests/test_docs_to_voice_settings.py +0 -43
  121. package/docs-to-voice/tests/test_docs_to_voice_shell_wrapper.py +0 -51
  122. package/docs-to-voice/tests/test_docs_to_voice_speech_rate.py +0 -57
  123. package/generate-spec/scripts/__pycache__/create-specscpython-312.pyc +0 -0
  124. package/generate-spec/scripts/create-specs +0 -215
  125. package/generate-spec/tests/test_create_specs.py +0 -200
  126. package/init-project-html/scripts/architecture-bootstrap-render.js +0 -16
  127. package/init-project-html/scripts/architecture.js +0 -296
  128. package/katex/scripts/__pycache__/render_katex.cpython-312.pyc +0 -0
  129. package/katex/scripts/render_katex.py +0 -247
  130. package/katex/scripts/render_katex.sh +0 -11
  131. package/katex/tests/test_render_katex.py +0 -174
  132. package/learning-error-book/scripts/render_error_book_json_to_pdf.py +0 -590
  133. package/learning-error-book/tests/test_render_error_book_json_to_pdf.py +0 -134
  134. package/open-github-issue/scripts/__pycache__/open_github_issue.cpython-312.pyc +0 -0
  135. package/open-github-issue/scripts/open_github_issue.py +0 -705
  136. package/open-github-issue/tests/test_open_github_issue.py +0 -381
  137. package/openai-text-to-image-storyboard/scripts/generate_storyboard_images.py +0 -763
  138. package/openai-text-to-image-storyboard/tests/test_generate_storyboard_images.py +0 -177
  139. package/read-github-issue/scripts/__pycache__/find_issues.cpython-312.pyc +0 -0
  140. package/read-github-issue/scripts/__pycache__/read_issue.cpython-312.pyc +0 -0
  141. package/read-github-issue/scripts/find_issues.py +0 -148
  142. package/read-github-issue/scripts/read_issue.py +0 -108
  143. package/read-github-issue/tests/test_find_issues.py +0 -127
  144. package/read-github-issue/tests/test_read_issue.py +0 -109
  145. package/resolve-review-comments/scripts/__pycache__/review_threads.cpython-312.pyc +0 -0
  146. package/resolve-review-comments/scripts/review_threads.py +0 -425
  147. package/resolve-review-comments/tests/test_review_threads.py +0 -74
  148. package/scripts/validate_openai_agent_config.py +0 -209
  149. package/scripts/validate_skill_frontmatter.py +0 -131
  150. package/text-to-short-video/scripts/__pycache__/enforce_video_aspect_ratio.cpython-312.pyc +0 -0
  151. package/text-to-short-video/scripts/enforce_video_aspect_ratio.py +0 -350
  152. package/text-to-short-video/tests/test_enforce_video_aspect_ratio.py +0 -194
  153. package/weekly-financial-event-report/scripts/extract_pdf_text_pdfkit.swift +0 -99
  154. package/weekly-financial-event-report/tests/test_extract_pdf_text_pdfkit.py +0 -64
@@ -0,0 +1,750 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.openGitHubIssueHandler = openGitHubIssueHandler;
37
+ // @ts-nocheck
38
+ const node_child_process_1 = require("node:child_process");
39
+ const node_fs_1 = require("node:fs");
40
+ const node_https_1 = require("node:https");
41
+ const node_os_1 = require("node:os");
42
+ const node_path_1 = require("node:path");
43
+ const GITHUB_API_BASE = 'https://api.github.com';
44
+ const README_ACCEPT = 'application/vnd.github.raw+json';
45
+ const JSON_ACCEPT = 'application/vnd.github+json';
46
+ const DEFAULT_REPRO_ZH = '尚未穩定重現;需補充更多執行期資料。';
47
+ const DEFAULT_REPRO_EN = 'Not yet reliably reproducible; more runtime evidence is required.';
48
+ const ISSUE_TYPE_PROBLEM = 'problem';
49
+ const ISSUE_TYPE_FEATURE = 'feature';
50
+ const ISSUE_TYPE_PERFORMANCE = 'performance';
51
+ const ISSUE_TYPE_SECURITY = 'security';
52
+ const ISSUE_TYPE_DOCS = 'docs';
53
+ const ISSUE_TYPE_OBSERVABILITY = 'observability';
54
+ const ISSUE_TYPES = [
55
+ ISSUE_TYPE_PROBLEM,
56
+ ISSUE_TYPE_FEATURE,
57
+ ISSUE_TYPE_PERFORMANCE,
58
+ ISSUE_TYPE_SECURITY,
59
+ ISSUE_TYPE_DOCS,
60
+ ISSUE_TYPE_OBSERVABILITY,
61
+ ];
62
+ const PROBLEM_BDD_MARKER_GROUPS = [
63
+ [
64
+ /Expected Behavior\s*\(BDD\)/i,
65
+ /Current Behavior\s*\(BDD\)/i,
66
+ /Behavior Gap/i,
67
+ ],
68
+ [
69
+ /預期行為\s*[((]BDD[))]/i,
70
+ /(?:目前|當前)行為\s*[((]BDD[))]/i,
71
+ /行為(?:落差|差異)/i,
72
+ ],
73
+ ];
74
+ const TEXT_FIELDS = [
75
+ 'title',
76
+ 'problem_description',
77
+ 'suspected_cause',
78
+ 'reproduction',
79
+ 'proposal',
80
+ 'reason',
81
+ 'suggested_architecture',
82
+ 'impact',
83
+ 'evidence',
84
+ 'suggested_action',
85
+ 'affected_scope',
86
+ ];
87
+ const PAYLOAD_FIELDS = new Set([
88
+ 'title',
89
+ 'issue_type',
90
+ 'problem_description',
91
+ 'suspected_cause',
92
+ 'reproduction',
93
+ 'proposal',
94
+ 'reason',
95
+ 'suggested_architecture',
96
+ 'impact',
97
+ 'evidence',
98
+ 'suggested_action',
99
+ 'severity',
100
+ 'affected_scope',
101
+ 'repo',
102
+ 'dry_run',
103
+ ]);
104
+ function parseArgs(argv) {
105
+ const args = {
106
+ payloadFile: null,
107
+ title: null,
108
+ issueType: null,
109
+ problemDescription: null,
110
+ suspectedCause: null,
111
+ reproduction: null,
112
+ proposal: null,
113
+ reason: null,
114
+ suggestedArchitecture: null,
115
+ impact: null,
116
+ evidence: null,
117
+ suggestedAction: null,
118
+ severity: null,
119
+ affectedScope: null,
120
+ repo: null,
121
+ dryRun: false,
122
+ };
123
+ let i = 0;
124
+ while (i < argv.length) {
125
+ const arg = argv[i];
126
+ switch (arg) {
127
+ case '--payload-file':
128
+ if (i + 1 < argv.length)
129
+ args.payloadFile = argv[++i];
130
+ break;
131
+ case '--title':
132
+ if (i + 1 < argv.length)
133
+ args.title = argv[++i];
134
+ break;
135
+ case '--issue-type':
136
+ if (i + 1 < argv.length)
137
+ args.issueType = argv[++i];
138
+ break;
139
+ case '--problem-description':
140
+ if (i + 1 < argv.length)
141
+ args.problemDescription = argv[++i];
142
+ break;
143
+ case '--suspected-cause':
144
+ if (i + 1 < argv.length)
145
+ args.suspectedCause = argv[++i];
146
+ break;
147
+ case '--reproduction':
148
+ if (i + 1 < argv.length)
149
+ args.reproduction = argv[++i];
150
+ break;
151
+ case '--proposal':
152
+ if (i + 1 < argv.length)
153
+ args.proposal = argv[++i];
154
+ break;
155
+ case '--reason':
156
+ if (i + 1 < argv.length)
157
+ args.reason = argv[++i];
158
+ break;
159
+ case '--suggested-architecture':
160
+ if (i + 1 < argv.length)
161
+ args.suggestedArchitecture = argv[++i];
162
+ break;
163
+ case '--impact':
164
+ if (i + 1 < argv.length)
165
+ args.impact = argv[++i];
166
+ break;
167
+ case '--evidence':
168
+ if (i + 1 < argv.length)
169
+ args.evidence = argv[++i];
170
+ break;
171
+ case '--suggested-action':
172
+ if (i + 1 < argv.length)
173
+ args.suggestedAction = argv[++i];
174
+ break;
175
+ case '--severity':
176
+ if (i + 1 < argv.length)
177
+ args.severity = argv[++i];
178
+ break;
179
+ case '--affected-scope':
180
+ if (i + 1 < argv.length)
181
+ args.affectedScope = argv[++i];
182
+ break;
183
+ case '--repo':
184
+ if (i + 1 < argv.length)
185
+ args.repo = argv[++i];
186
+ break;
187
+ case '--dry-run':
188
+ args.dryRun = true;
189
+ break;
190
+ default:
191
+ if (!arg.startsWith('-')) {
192
+ // unsupported positional — ignore per Python behavior
193
+ }
194
+ break;
195
+ }
196
+ i++;
197
+ }
198
+ return args;
199
+ }
200
+ function runCommand(cmd, cmdArgs) {
201
+ return new Promise((resolve) => {
202
+ (0, node_child_process_1.execFile)(cmd, cmdArgs, { maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
203
+ if (error) {
204
+ resolve({
205
+ stdout: stdout || '',
206
+ stderr: stderr || '',
207
+ exitCode: error.status ?? 1,
208
+ });
209
+ }
210
+ else {
211
+ resolve({ stdout: stdout || '', stderr: stderr || '', exitCode: 0 });
212
+ }
213
+ });
214
+ });
215
+ }
216
+ function normalizeKey(key) {
217
+ return key.replace(/-/g, '_');
218
+ }
219
+ function readPayloadFile(rawPath) {
220
+ let rawContent;
221
+ let context;
222
+ if (rawPath === '-') {
223
+ // We cannot read stdin here easily; throw clear error
224
+ throw new Error('stdin payload (-) is not supported in handler mode; use a file path');
225
+ }
226
+ else {
227
+ rawContent = (0, node_fs_1.readFileSync)(rawPath, 'utf-8');
228
+ context = rawPath;
229
+ }
230
+ let payload;
231
+ try {
232
+ payload = JSON.parse(rawContent);
233
+ }
234
+ catch (exc) {
235
+ throw new Error(`Invalid JSON payload in ${context}: ${exc.message}`);
236
+ }
237
+ if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {
238
+ throw new Error(`Invalid JSON payload in ${context}: top-level value must be an object.`);
239
+ }
240
+ const normalized = {};
241
+ for (const [rawKey, value] of Object.entries(payload)) {
242
+ const key = normalizeKey(rawKey);
243
+ if (!PAYLOAD_FIELDS.has(key)) {
244
+ throw new Error(`Unsupported payload key: ${rawKey}`);
245
+ }
246
+ normalized[key] = value;
247
+ }
248
+ return normalized;
249
+ }
250
+ function readAtFileValue(fieldName, value) {
251
+ if (value === null)
252
+ return null;
253
+ if (value.startsWith('@@'))
254
+ return value.slice(1);
255
+ if (value === '@-') {
256
+ throw new Error('stdin reading (@-) is not supported in handler mode');
257
+ }
258
+ if (value.startsWith('@') && value.length > 1) {
259
+ const filePath = value.slice(1);
260
+ try {
261
+ return (0, node_fs_1.readFileSync)(filePath, 'utf-8');
262
+ }
263
+ catch (exc) {
264
+ throw new Error(`Unable to read @${fieldName} file ${filePath}: ${exc.message}`);
265
+ }
266
+ }
267
+ return value;
268
+ }
269
+ function requireNonEmpty(value, message) {
270
+ if (!(value || '').trim()) {
271
+ throw new Error(message);
272
+ }
273
+ }
274
+ function hasRequiredProblemBddSections(problemDescription) {
275
+ const normalized = problemDescription.trim();
276
+ return PROBLEM_BDD_MARKER_GROUPS.some((group) => group.every((pattern) => pattern.test(normalized)));
277
+ }
278
+ function hasGhAuth() {
279
+ return runCommand('gh', ['auth', 'status']).then((r) => r.exitCode === 0);
280
+ }
281
+ function getToken(env) {
282
+ return env.GITHUB_TOKEN || env.GH_TOKEN || null;
283
+ }
284
+ function resolveRepo(explicitRepo) {
285
+ if (explicitRepo) {
286
+ return validateRepo(explicitRepo);
287
+ }
288
+ // Try to resolve from git remote
289
+ const result = runCommand('git', [
290
+ 'remote', 'get-url', 'origin',
291
+ ]);
292
+ // This is sync because it's called in a non-async context flow
293
+ // Actually, let me use the promisified version — but we need to make the caller async
294
+ // For now, throw a helpful error
295
+ throw new Error('--repo is required in handler mode. Unable to auto-detect from git remote in-process.');
296
+ }
297
+ function validateRepo(repo) {
298
+ const candidate = repo.trim();
299
+ if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(candidate)) {
300
+ throw new Error('Invalid repo format. Use owner/repo.');
301
+ }
302
+ return candidate;
303
+ }
304
+ // ---- HTTP helpers ----
305
+ function githubRequest(method, path, token, accept, payload) {
306
+ return new Promise((resolve, reject) => {
307
+ const headers = {
308
+ Accept: accept,
309
+ 'User-Agent': 'open-github-issue-skill',
310
+ 'X-GitHub-Api-Version': '2022-11-28',
311
+ };
312
+ if (token) {
313
+ headers['Authorization'] = `Bearer ${token}`;
314
+ }
315
+ let body;
316
+ if (payload !== undefined) {
317
+ body = JSON.stringify(payload);
318
+ headers['Content-Type'] = 'application/json';
319
+ }
320
+ const url = new URL(`${GITHUB_API_BASE}${path}`);
321
+ const req = (0, node_https_1.request)(url, {
322
+ method,
323
+ headers,
324
+ }, (res) => {
325
+ let data = '';
326
+ res.on('data', (chunk) => {
327
+ data += chunk.toString('utf-8');
328
+ });
329
+ res.on('end', () => {
330
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
331
+ resolve(data);
332
+ }
333
+ else {
334
+ const detail = data || 'unknown error';
335
+ reject(new Error(`GitHub API ${res.statusCode} ${path}: ${detail}`));
336
+ }
337
+ });
338
+ });
339
+ req.on('error', (err) => {
340
+ reject(new Error(`GitHub API request failed for ${path}: ${err.message}`));
341
+ });
342
+ if (body !== undefined) {
343
+ req.write(body);
344
+ }
345
+ req.end();
346
+ });
347
+ }
348
+ async function fetchRemoteReadme(repo, ghAuthenticated, token) {
349
+ if (ghAuthenticated) {
350
+ const result = await runCommand('gh', [
351
+ 'api',
352
+ '-H',
353
+ `Accept: ${README_ACCEPT}`,
354
+ `repos/${repo}/readme`,
355
+ ]);
356
+ if (result.exitCode === 0) {
357
+ return result.stdout;
358
+ }
359
+ }
360
+ try {
361
+ return await githubRequest('GET', `/repos/${repo}/readme`, token, README_ACCEPT);
362
+ }
363
+ catch {
364
+ return '';
365
+ }
366
+ }
367
+ function detectIssueLanguage(readmeContent) {
368
+ if (!readmeContent.trim())
369
+ return 'en';
370
+ const chineseChars = (readmeContent.match(/[一-鿿]/g) || []).length;
371
+ const languageChars = (readmeContent.match(/[A-Za-z一-鿿]/g) || []).length;
372
+ if (chineseChars >= 20 && languageChars > 0 && chineseChars / languageChars >= 0.08) {
373
+ return 'zh';
374
+ }
375
+ return 'en';
376
+ }
377
+ // ---- Issue body builder ----
378
+ function buildIssueBody(params) {
379
+ const { issueType, language, title, problemDescription, suspectedCause, reproduction, proposal, reason, suggestedArchitecture, impact, evidence, suggestedAction, severity, affectedScope, } = params;
380
+ if (issueType === ISSUE_TYPE_FEATURE) {
381
+ const proposalText = (proposal || title).trim();
382
+ const reasonText = (reason || '').trim();
383
+ const architectureText = (suggestedArchitecture || '').trim();
384
+ if (language === 'zh') {
385
+ return ('### 功能提案\n' +
386
+ `${proposalText}\n\n` +
387
+ '### 原因\n' +
388
+ `${reasonText}\n\n` +
389
+ '### 建議架構\n' +
390
+ `${architectureText}\n`);
391
+ }
392
+ return ('### Feature Proposal\n' +
393
+ `${proposalText}\n\n` +
394
+ '### Why This Is Needed\n' +
395
+ `${reasonText}\n\n` +
396
+ '### Suggested Architecture\n' +
397
+ `${architectureText}\n`);
398
+ }
399
+ if (issueType === ISSUE_TYPE_PERFORMANCE) {
400
+ if (language === 'zh') {
401
+ return ('### 效能問題\n' +
402
+ `${(problemDescription || '').trim()}\n\n` +
403
+ '### 影響\n' +
404
+ `${(impact || '').trim()}\n\n` +
405
+ '### 證據\n' +
406
+ `${(evidence || '').trim()}\n\n` +
407
+ '### 建議行動\n' +
408
+ `${(suggestedAction || '').trim()}\n`);
409
+ }
410
+ return ('### Performance Problem\n' +
411
+ `${(problemDescription || '').trim()}\n\n` +
412
+ '### Impact\n' +
413
+ `${(impact || '').trim()}\n\n` +
414
+ '### Evidence\n' +
415
+ `${(evidence || '').trim()}\n\n` +
416
+ '### Suggested Action\n' +
417
+ `${(suggestedAction || '').trim()}\n`);
418
+ }
419
+ if (issueType === ISSUE_TYPE_SECURITY) {
420
+ if (language === 'zh') {
421
+ return ('### 安全風險\n' +
422
+ `${(problemDescription || '').trim()}\n\n` +
423
+ '### 嚴重程度\n' +
424
+ `${(severity || '').trim()}\n\n` +
425
+ '### 受影響範圍\n' +
426
+ `${(affectedScope || '').trim()}\n\n` +
427
+ '### 影響\n' +
428
+ `${(impact || '').trim()}\n\n` +
429
+ '### 證據\n' +
430
+ `${(evidence || '').trim()}\n\n` +
431
+ '### 建議緩解\n' +
432
+ `${(suggestedAction || '').trim()}\n`);
433
+ }
434
+ return ('### Security Risk\n' +
435
+ `${(problemDescription || '').trim()}\n\n` +
436
+ '### Severity\n' +
437
+ `${(severity || '').trim()}\n\n` +
438
+ '### Affected Scope\n' +
439
+ `${(affectedScope || '').trim()}\n\n` +
440
+ '### Impact\n' +
441
+ `${(impact || '').trim()}\n\n` +
442
+ '### Evidence\n' +
443
+ `${(evidence || '').trim()}\n\n` +
444
+ '### Suggested Mitigation\n' +
445
+ `${(suggestedAction || '').trim()}\n`);
446
+ }
447
+ if (issueType === ISSUE_TYPE_DOCS) {
448
+ if (language === 'zh') {
449
+ return ('### 文件缺口\n' +
450
+ `${(problemDescription || '').trim()}\n\n` +
451
+ '### 證據\n' +
452
+ `${(evidence || '').trim()}\n\n` +
453
+ '### 建議更新\n' +
454
+ `${(suggestedAction || '').trim()}\n`);
455
+ }
456
+ return ('### Documentation Gap\n' +
457
+ `${(problemDescription || '').trim()}\n\n` +
458
+ '### Evidence\n' +
459
+ `${(evidence || '').trim()}\n\n` +
460
+ '### Suggested Update\n' +
461
+ `${(suggestedAction || '').trim()}\n`);
462
+ }
463
+ if (issueType === ISSUE_TYPE_OBSERVABILITY) {
464
+ if (language === 'zh') {
465
+ return ('### 可觀測性缺口\n' +
466
+ `${(problemDescription || '').trim()}\n\n` +
467
+ '### 影響\n' +
468
+ `${(impact || '').trim()}\n\n` +
469
+ '### 證據\n' +
470
+ `${(evidence || '').trim()}\n\n` +
471
+ '### 建議儀表化\n' +
472
+ `${(suggestedAction || '').trim()}\n`);
473
+ }
474
+ return ('### Observability Gap\n' +
475
+ `${(problemDescription || '').trim()}\n\n` +
476
+ '### Impact\n' +
477
+ `${(impact || '').trim()}\n\n` +
478
+ '### Evidence\n' +
479
+ `${(evidence || '').trim()}\n\n` +
480
+ '### Suggested Instrumentation\n' +
481
+ `${(suggestedAction || '').trim()}\n`);
482
+ }
483
+ // Default: problem
484
+ if (language === 'zh') {
485
+ const reproText = (reproduction || DEFAULT_REPRO_ZH).trim();
486
+ return ('### 問題描述\n' +
487
+ `${(problemDescription || '').trim()}\n\n` +
488
+ '### 推測原因\n' +
489
+ `${(suspectedCause || '').trim()}\n\n` +
490
+ '### 重現條件(如有)\n' +
491
+ `${reproText}\n`);
492
+ }
493
+ const reproText = (reproduction || DEFAULT_REPRO_EN).trim();
494
+ return ('### Problem Description\n' +
495
+ `${(problemDescription || '').trim()}\n\n` +
496
+ '### Suspected Cause\n' +
497
+ `${(suspectedCause || '').trim()}\n\n` +
498
+ '### Reproduction Conditions (if available)\n' +
499
+ `${reproText}\n`);
500
+ }
501
+ // ---- Issue creation ----
502
+ async function createIssueWithGh(repo, title, body) {
503
+ // Write body to a temp file
504
+ const tmpFile = (0, node_path_1.join)((0, node_os_1.tmpdir)(), `issue-${Date.now()}.md`);
505
+ const { writeFileSync, unlinkSync } = await Promise.resolve().then(() => __importStar(require('node:fs')));
506
+ writeFileSync(tmpFile, body, 'utf-8');
507
+ try {
508
+ const result = await runCommand('gh', [
509
+ 'issue',
510
+ 'create',
511
+ '--repo',
512
+ repo,
513
+ '--title',
514
+ title,
515
+ '--body-file',
516
+ tmpFile,
517
+ ]);
518
+ if (result.exitCode !== 0) {
519
+ throw new Error(result.stderr.trim() || 'gh issue create failed');
520
+ }
521
+ const urlMatch = result.stdout.match(/https:\/\/github\.com\/[^\s]+\/issues\/\d+/);
522
+ return urlMatch ? urlMatch[0] : result.stdout.trim();
523
+ }
524
+ finally {
525
+ try {
526
+ unlinkSync(tmpFile);
527
+ }
528
+ catch {
529
+ // ignore cleanup errors
530
+ }
531
+ }
532
+ }
533
+ async function createIssueWithToken(repo, title, body, token) {
534
+ const response = await githubRequest('POST', `/repos/${repo}/issues`, token, JSON_ACCEPT, {
535
+ title,
536
+ body,
537
+ });
538
+ const parsed = JSON.parse(response);
539
+ const issueUrl = parsed.html_url;
540
+ if (!issueUrl) {
541
+ throw new Error('Issue created but response did not include html_url');
542
+ }
543
+ return issueUrl;
544
+ }
545
+ // ---- Validation ----
546
+ function validateIssueContent(args) {
547
+ const issueType = args.issueType || ISSUE_TYPE_PROBLEM;
548
+ if (issueType === ISSUE_TYPE_FEATURE) {
549
+ requireNonEmpty(args.reason, 'Feature issues require --reason.');
550
+ requireNonEmpty(args.suggestedArchitecture, 'Feature issues require --suggested-architecture.');
551
+ return;
552
+ }
553
+ if (issueType === ISSUE_TYPE_PERFORMANCE) {
554
+ requireNonEmpty(args.problemDescription, 'Performance issues require --problem-description.');
555
+ requireNonEmpty(args.impact, 'Performance issues require --impact.');
556
+ requireNonEmpty(args.evidence, 'Performance issues require --evidence.');
557
+ requireNonEmpty(args.suggestedAction, 'Performance issues require --suggested-action.');
558
+ return;
559
+ }
560
+ if (issueType === ISSUE_TYPE_SECURITY) {
561
+ requireNonEmpty(args.problemDescription, 'Security issues require --problem-description.');
562
+ requireNonEmpty(args.affectedScope, 'Security issues require --affected-scope.');
563
+ requireNonEmpty(args.impact, 'Security issues require --impact.');
564
+ requireNonEmpty(args.evidence, 'Security issues require --evidence.');
565
+ requireNonEmpty(args.suggestedAction, 'Security issues require --suggested-action.');
566
+ requireNonEmpty(args.severity, 'Security issues require --severity.');
567
+ return;
568
+ }
569
+ if (issueType === ISSUE_TYPE_DOCS) {
570
+ requireNonEmpty(args.problemDescription, 'Docs issues require --problem-description.');
571
+ requireNonEmpty(args.evidence, 'Docs issues require --evidence.');
572
+ requireNonEmpty(args.suggestedAction, 'Docs issues require --suggested-action.');
573
+ return;
574
+ }
575
+ if (issueType === ISSUE_TYPE_OBSERVABILITY) {
576
+ requireNonEmpty(args.problemDescription, 'Observability issues require --problem-description.');
577
+ requireNonEmpty(args.impact, 'Observability issues require --impact.');
578
+ requireNonEmpty(args.evidence, 'Observability issues require --evidence.');
579
+ requireNonEmpty(args.suggestedAction, 'Observability issues require --suggested-action.');
580
+ return;
581
+ }
582
+ // Problem issue
583
+ requireNonEmpty(args.problemDescription, 'Problem issues require --problem-description.');
584
+ requireNonEmpty(args.suspectedCause, 'Problem issues require --suspected-cause.');
585
+ if (!hasRequiredProblemBddSections(args.problemDescription || '')) {
586
+ throw new Error('Problem issues require --problem-description to include ' +
587
+ 'Expected Behavior (BDD), Current Behavior (BDD), and Behavior Gap sections.');
588
+ }
589
+ }
590
+ function hydrateArgs(args) {
591
+ const result = { ...args };
592
+ // Load from payload file if provided
593
+ if (result.payloadFile) {
594
+ const payload = readPayloadFile(result.payloadFile);
595
+ for (const [key, value] of Object.entries(payload)) {
596
+ if (key === 'dry_run') {
597
+ if (typeof value !== 'boolean') {
598
+ throw new Error("Payload field 'dry_run' must be a boolean.");
599
+ }
600
+ if (!result.dryRun) {
601
+ result.dryRun = value;
602
+ }
603
+ continue;
604
+ }
605
+ // String fields
606
+ if (TEXT_FIELDS.includes(key)) {
607
+ if (value !== null && typeof value !== 'string') {
608
+ throw new Error(`Payload field '${key}' must be a string or null.`);
609
+ }
610
+ }
611
+ else if (typeof value !== 'string') {
612
+ throw new Error(`Payload field '${key}' must be a string.`);
613
+ }
614
+ const currentVal = result[key];
615
+ if (currentVal === null || currentVal === '') {
616
+ result[key] = value;
617
+ }
618
+ }
619
+ }
620
+ // Set default issue type
621
+ if (!result.issueType) {
622
+ result.issueType = ISSUE_TYPE_PROBLEM;
623
+ }
624
+ if (!ISSUE_TYPES.includes(result.issueType)) {
625
+ throw new Error(`Invalid issue_type: ${result.issueType}`);
626
+ }
627
+ // Resolve @-prefixed file values
628
+ for (const fieldName of TEXT_FIELDS) {
629
+ const resultObj = result;
630
+ const val = resultObj[fieldName];
631
+ resultObj[fieldName] = readAtFileValue(fieldName, val);
632
+ }
633
+ // Title is required
634
+ if (!(result.title || '').trim()) {
635
+ throw new Error('Issue title is required. Pass --title or include title in --payload-file.');
636
+ }
637
+ return result;
638
+ }
639
+ async function resolveRepoAsync(explicitRepo, context) {
640
+ if (explicitRepo)
641
+ return validateRepo(explicitRepo);
642
+ // Try to resolve from git remote
643
+ const result = await runCommand('git', ['remote', 'get-url', 'origin']);
644
+ if (result.exitCode !== 0) {
645
+ context.stderr.write('Unable to resolve origin remote. Pass --repo owner/repo.\n');
646
+ throw new Error('--repo resolution failed');
647
+ }
648
+ const remote = result.stdout.trim();
649
+ const match = remote.match(/github\.com[:/](?<owner>[A-Za-z0-9_.-]+)\/(?<repo>[A-Za-z0-9_.-]+?)(?:\.git)?$/);
650
+ if (!match?.groups) {
651
+ context.stderr.write('Origin remote is not a GitHub repository. Pass --repo owner/repo.\n');
652
+ throw new Error('--repo resolution failed');
653
+ }
654
+ return `${match.groups.owner}/${match.groups.repo}`;
655
+ }
656
+ async function openGitHubIssueHandler(argv, context) {
657
+ const { stdout, stderr, env } = context;
658
+ let args;
659
+ try {
660
+ args = hydrateArgs(parseArgs(argv));
661
+ validateIssueContent(args);
662
+ }
663
+ catch (err) {
664
+ stderr.write(`Error: ${err.message}\n`);
665
+ return 1;
666
+ }
667
+ const ghAuthenticated = await hasGhAuth();
668
+ const token = getToken(env);
669
+ let repo;
670
+ try {
671
+ repo = await resolveRepoAsync(args.repo, context);
672
+ }
673
+ catch {
674
+ return 1;
675
+ }
676
+ const readmeContent = await fetchRemoteReadme(repo, ghAuthenticated, token);
677
+ const language = detectIssueLanguage(readmeContent);
678
+ const issueBody = buildIssueBody({
679
+ issueType: args.issueType || ISSUE_TYPE_PROBLEM,
680
+ language,
681
+ title: args.title || '',
682
+ problemDescription: args.problemDescription,
683
+ suspectedCause: args.suspectedCause,
684
+ reproduction: args.reproduction,
685
+ proposal: args.proposal,
686
+ reason: args.reason,
687
+ suggestedArchitecture: args.suggestedArchitecture,
688
+ impact: args.impact,
689
+ evidence: args.evidence,
690
+ suggestedAction: args.suggestedAction,
691
+ severity: args.severity,
692
+ affectedScope: args.affectedScope,
693
+ });
694
+ let mode = 'draft-only';
695
+ let issueUrl = '';
696
+ let publishError = '';
697
+ if (args.dryRun) {
698
+ mode = 'dry-run';
699
+ }
700
+ else if (ghAuthenticated) {
701
+ try {
702
+ issueUrl = await createIssueWithGh(repo, args.title || '', issueBody);
703
+ mode = 'gh-cli';
704
+ }
705
+ catch (exc) {
706
+ if (token) {
707
+ try {
708
+ issueUrl = await createIssueWithToken(repo, args.title || '', issueBody, token);
709
+ mode = 'github-token';
710
+ }
711
+ catch (tokenExc) {
712
+ publishError = tokenExc.message;
713
+ }
714
+ }
715
+ else {
716
+ publishError = exc.message;
717
+ }
718
+ }
719
+ }
720
+ else if (token) {
721
+ try {
722
+ issueUrl = await createIssueWithToken(repo, args.title || '', issueBody, token);
723
+ mode = 'github-token';
724
+ }
725
+ catch (exc) {
726
+ publishError = exc.message;
727
+ }
728
+ }
729
+ const output = {
730
+ repo,
731
+ issue_type: args.issueType || ISSUE_TYPE_PROBLEM,
732
+ language: language === 'zh' ? 'zh' : 'en',
733
+ mode,
734
+ issue_url: issueUrl,
735
+ issue_title: args.title || '',
736
+ issue_body: issueBody,
737
+ publish_error: publishError,
738
+ };
739
+ stdout.write(JSON.stringify(output, null, 2) + '\n');
740
+ if (mode === 'draft-only') {
741
+ if (publishError) {
742
+ stderr.write(`Issue publish failed. Return draft only: ${publishError}\n`);
743
+ }
744
+ else {
745
+ stderr.write('No authenticated gh CLI session and no GitHub token found. ' +
746
+ 'Return draft issue body only.\n');
747
+ }
748
+ }
749
+ return 0;
750
+ }