@kata-sh/cli 0.1.0 → 0.1.2

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 (199) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +156 -0
  3. package/dist/app-paths.d.ts +4 -0
  4. package/dist/app-paths.js +6 -0
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/cli.js +56 -0
  7. package/dist/loader.d.ts +2 -0
  8. package/dist/loader.js +95 -0
  9. package/dist/resource-loader.d.ts +18 -0
  10. package/dist/resource-loader.js +50 -0
  11. package/dist/wizard.d.ts +15 -0
  12. package/dist/wizard.js +159 -0
  13. package/package.json +50 -21
  14. package/pkg/dist/modes/interactive/theme/dark.json +85 -0
  15. package/pkg/dist/modes/interactive/theme/light.json +84 -0
  16. package/pkg/dist/modes/interactive/theme/theme-schema.json +335 -0
  17. package/pkg/dist/modes/interactive/theme/theme.d.ts +78 -0
  18. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  19. package/pkg/dist/modes/interactive/theme/theme.js +949 -0
  20. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -0
  21. package/pkg/package.json +8 -0
  22. package/scripts/postinstall.js +45 -0
  23. package/src/resources/AGENTS.md +108 -0
  24. package/src/resources/KATA-WORKFLOW.md +661 -0
  25. package/src/resources/agents/researcher.md +29 -0
  26. package/src/resources/agents/scout.md +56 -0
  27. package/src/resources/agents/worker.md +31 -0
  28. package/src/resources/extensions/ask-user-questions.ts +200 -0
  29. package/src/resources/extensions/bg-shell/index.ts +2758 -0
  30. package/src/resources/extensions/browser-tools/BROWSER-TOOLS-V2-PROPOSAL.md +1277 -0
  31. package/src/resources/extensions/browser-tools/core.js +1057 -0
  32. package/src/resources/extensions/browser-tools/index.ts +4916 -0
  33. package/src/resources/extensions/browser-tools/package.json +20 -0
  34. package/src/resources/extensions/context7/index.ts +428 -0
  35. package/src/resources/extensions/context7/package.json +11 -0
  36. package/src/resources/extensions/get-secrets-from-user.ts +352 -0
  37. package/src/resources/extensions/github/formatters.ts +207 -0
  38. package/src/resources/extensions/github/gh-api.ts +537 -0
  39. package/src/resources/extensions/github/index.ts +778 -0
  40. package/src/resources/extensions/kata/activity-log.ts +88 -0
  41. package/src/resources/extensions/kata/auto.ts +2786 -0
  42. package/src/resources/extensions/kata/commands.ts +355 -0
  43. package/src/resources/extensions/kata/crash-recovery.ts +85 -0
  44. package/src/resources/extensions/kata/dashboard-overlay.ts +516 -0
  45. package/src/resources/extensions/kata/docs/preferences-reference.md +103 -0
  46. package/src/resources/extensions/kata/doctor.ts +683 -0
  47. package/src/resources/extensions/kata/files.ts +730 -0
  48. package/src/resources/extensions/kata/gitignore.ts +165 -0
  49. package/src/resources/extensions/kata/guided-flow.ts +976 -0
  50. package/src/resources/extensions/kata/index.ts +556 -0
  51. package/src/resources/extensions/kata/metrics.ts +397 -0
  52. package/src/resources/extensions/kata/observability-validator.ts +408 -0
  53. package/src/resources/extensions/kata/package.json +11 -0
  54. package/src/resources/extensions/kata/paths.ts +346 -0
  55. package/src/resources/extensions/kata/preferences.ts +695 -0
  56. package/src/resources/extensions/kata/prompt-loader.ts +50 -0
  57. package/src/resources/extensions/kata/prompts/complete-milestone.md +25 -0
  58. package/src/resources/extensions/kata/prompts/complete-slice.md +27 -0
  59. package/src/resources/extensions/kata/prompts/discuss.md +151 -0
  60. package/src/resources/extensions/kata/prompts/doctor-heal.md +29 -0
  61. package/src/resources/extensions/kata/prompts/execute-task.md +64 -0
  62. package/src/resources/extensions/kata/prompts/guided-complete-slice.md +1 -0
  63. package/src/resources/extensions/kata/prompts/guided-discuss-milestone.md +3 -0
  64. package/src/resources/extensions/kata/prompts/guided-discuss-slice.md +59 -0
  65. package/src/resources/extensions/kata/prompts/guided-execute-task.md +1 -0
  66. package/src/resources/extensions/kata/prompts/guided-plan-milestone.md +23 -0
  67. package/src/resources/extensions/kata/prompts/guided-plan-slice.md +1 -0
  68. package/src/resources/extensions/kata/prompts/guided-research-slice.md +11 -0
  69. package/src/resources/extensions/kata/prompts/guided-resume-task.md +1 -0
  70. package/src/resources/extensions/kata/prompts/plan-milestone.md +47 -0
  71. package/src/resources/extensions/kata/prompts/plan-slice.md +63 -0
  72. package/src/resources/extensions/kata/prompts/queue.md +85 -0
  73. package/src/resources/extensions/kata/prompts/reassess-roadmap.md +48 -0
  74. package/src/resources/extensions/kata/prompts/replan-slice.md +39 -0
  75. package/src/resources/extensions/kata/prompts/research-milestone.md +37 -0
  76. package/src/resources/extensions/kata/prompts/research-slice.md +28 -0
  77. package/src/resources/extensions/kata/prompts/run-uat.md +109 -0
  78. package/src/resources/extensions/kata/prompts/system.md +341 -0
  79. package/src/resources/extensions/kata/session-forensics.ts +550 -0
  80. package/src/resources/extensions/kata/skill-discovery.ts +137 -0
  81. package/src/resources/extensions/kata/state.ts +509 -0
  82. package/src/resources/extensions/kata/templates/context.md +76 -0
  83. package/src/resources/extensions/kata/templates/decisions.md +8 -0
  84. package/src/resources/extensions/kata/templates/milestone-summary.md +73 -0
  85. package/src/resources/extensions/kata/templates/plan.md +133 -0
  86. package/src/resources/extensions/kata/templates/preferences.md +15 -0
  87. package/src/resources/extensions/kata/templates/project.md +31 -0
  88. package/src/resources/extensions/kata/templates/reassessment.md +28 -0
  89. package/src/resources/extensions/kata/templates/requirements.md +81 -0
  90. package/src/resources/extensions/kata/templates/research.md +46 -0
  91. package/src/resources/extensions/kata/templates/roadmap.md +118 -0
  92. package/src/resources/extensions/kata/templates/slice-context.md +58 -0
  93. package/src/resources/extensions/kata/templates/slice-summary.md +99 -0
  94. package/src/resources/extensions/kata/templates/state.md +19 -0
  95. package/src/resources/extensions/kata/templates/task-plan.md +52 -0
  96. package/src/resources/extensions/kata/templates/task-summary.md +57 -0
  97. package/src/resources/extensions/kata/templates/uat.md +54 -0
  98. package/src/resources/extensions/kata/tests/activity-log-prune.test.ts +327 -0
  99. package/src/resources/extensions/kata/tests/auto-preflight.test.ts +97 -0
  100. package/src/resources/extensions/kata/tests/auto-supervisor.test.mjs +53 -0
  101. package/src/resources/extensions/kata/tests/complete-milestone.test.ts +317 -0
  102. package/src/resources/extensions/kata/tests/cost-projection.test.ts +160 -0
  103. package/src/resources/extensions/kata/tests/derive-state-deps.test.ts +477 -0
  104. package/src/resources/extensions/kata/tests/derive-state.test.ts +1013 -0
  105. package/src/resources/extensions/kata/tests/doctor.test.ts +718 -0
  106. package/src/resources/extensions/kata/tests/idle-recovery.test.ts +490 -0
  107. package/src/resources/extensions/kata/tests/metrics-io.test.ts +254 -0
  108. package/src/resources/extensions/kata/tests/metrics.test.ts +217 -0
  109. package/src/resources/extensions/kata/tests/must-have-parser.test.ts +309 -0
  110. package/src/resources/extensions/kata/tests/parsers.test.ts +1257 -0
  111. package/src/resources/extensions/kata/tests/plan-milestone.test.ts +185 -0
  112. package/src/resources/extensions/kata/tests/plan-quality-validator.test.ts +386 -0
  113. package/src/resources/extensions/kata/tests/reassess-prompt.test.ts +208 -0
  114. package/src/resources/extensions/kata/tests/replan-slice.test.ts +686 -0
  115. package/src/resources/extensions/kata/tests/requirements.test.ts +151 -0
  116. package/src/resources/extensions/kata/tests/resolve-ts-hooks.mjs +17 -0
  117. package/src/resources/extensions/kata/tests/resolve-ts.mjs +11 -0
  118. package/src/resources/extensions/kata/tests/run-uat.test.ts +383 -0
  119. package/src/resources/extensions/kata/tests/unit-runtime.test.ts +388 -0
  120. package/src/resources/extensions/kata/tests/workspace-index.test.ts +118 -0
  121. package/src/resources/extensions/kata/tests/worktree.test.ts +222 -0
  122. package/src/resources/extensions/kata/types.ts +159 -0
  123. package/src/resources/extensions/kata/unit-runtime.ts +163 -0
  124. package/src/resources/extensions/kata/workspace-index.ts +203 -0
  125. package/src/resources/extensions/kata/worktree.ts +182 -0
  126. package/src/resources/extensions/mac-tools/index.ts +852 -0
  127. package/src/resources/extensions/mac-tools/swift-cli/Package.swift +22 -0
  128. package/src/resources/extensions/mac-tools/swift-cli/Sources/main.swift +1318 -0
  129. package/src/resources/extensions/search-the-web/cache.ts +78 -0
  130. package/src/resources/extensions/search-the-web/format.ts +258 -0
  131. package/src/resources/extensions/search-the-web/http.ts +238 -0
  132. package/src/resources/extensions/search-the-web/index.ts +68 -0
  133. package/src/resources/extensions/search-the-web/tool-fetch-page.ts +519 -0
  134. package/src/resources/extensions/search-the-web/tool-llm-context.ts +404 -0
  135. package/src/resources/extensions/search-the-web/tool-search.ts +503 -0
  136. package/src/resources/extensions/search-the-web/url-utils.ts +91 -0
  137. package/src/resources/extensions/shared/confirm-ui.ts +126 -0
  138. package/src/resources/extensions/shared/interview-ui.ts +822 -0
  139. package/src/resources/extensions/shared/next-action-ui.ts +235 -0
  140. package/src/resources/extensions/shared/progress-widget.ts +282 -0
  141. package/src/resources/extensions/shared/thinking-widget.ts +107 -0
  142. package/src/resources/extensions/shared/ui.ts +400 -0
  143. package/src/resources/extensions/shared/wizard-ui.ts +551 -0
  144. package/src/resources/extensions/slash-commands/audit.ts +92 -0
  145. package/src/resources/extensions/slash-commands/create-extension.ts +375 -0
  146. package/src/resources/extensions/slash-commands/create-slash-command.ts +280 -0
  147. package/src/resources/extensions/slash-commands/index.ts +12 -0
  148. package/src/resources/extensions/slash-commands/kata-run.ts +34 -0
  149. package/src/resources/extensions/subagent/agents.ts +126 -0
  150. package/src/resources/extensions/subagent/index.ts +1293 -0
  151. package/src/resources/skills/debug-like-expert/SKILL.md +231 -0
  152. package/src/resources/skills/debug-like-expert/references/debugging-mindset.md +253 -0
  153. package/src/resources/skills/debug-like-expert/references/hypothesis-testing.md +373 -0
  154. package/src/resources/skills/debug-like-expert/references/investigation-techniques.md +337 -0
  155. package/src/resources/skills/debug-like-expert/references/verification-patterns.md +425 -0
  156. package/src/resources/skills/debug-like-expert/references/when-to-research.md +361 -0
  157. package/src/resources/skills/frontend-design/SKILL.md +45 -0
  158. package/src/resources/skills/swiftui/SKILL.md +208 -0
  159. package/src/resources/skills/swiftui/references/animations.md +921 -0
  160. package/src/resources/skills/swiftui/references/architecture.md +1561 -0
  161. package/src/resources/skills/swiftui/references/layout-system.md +1186 -0
  162. package/src/resources/skills/swiftui/references/navigation.md +1492 -0
  163. package/src/resources/skills/swiftui/references/networking-async.md +214 -0
  164. package/src/resources/skills/swiftui/references/performance.md +1706 -0
  165. package/src/resources/skills/swiftui/references/platform-integration.md +204 -0
  166. package/src/resources/skills/swiftui/references/state-management.md +1443 -0
  167. package/src/resources/skills/swiftui/references/swiftdata.md +297 -0
  168. package/src/resources/skills/swiftui/references/testing-debugging.md +247 -0
  169. package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +218 -0
  170. package/src/resources/skills/swiftui/workflows/add-feature.md +191 -0
  171. package/src/resources/skills/swiftui/workflows/build-new-app.md +311 -0
  172. package/src/resources/skills/swiftui/workflows/debug-swiftui.md +192 -0
  173. package/src/resources/skills/swiftui/workflows/optimize-performance.md +197 -0
  174. package/src/resources/skills/swiftui/workflows/ship-app.md +203 -0
  175. package/src/resources/skills/swiftui/workflows/write-tests.md +235 -0
  176. package/dist/commands/task.d.ts +0 -9
  177. package/dist/commands/task.d.ts.map +0 -1
  178. package/dist/commands/task.js +0 -129
  179. package/dist/commands/task.js.map +0 -1
  180. package/dist/commands/task.test.d.ts +0 -2
  181. package/dist/commands/task.test.d.ts.map +0 -1
  182. package/dist/commands/task.test.js +0 -169
  183. package/dist/commands/task.test.js.map +0 -1
  184. package/dist/e2e/task-e2e.test.d.ts +0 -2
  185. package/dist/e2e/task-e2e.test.d.ts.map +0 -1
  186. package/dist/e2e/task-e2e.test.js +0 -173
  187. package/dist/e2e/task-e2e.test.js.map +0 -1
  188. package/dist/index.d.ts +0 -3
  189. package/dist/index.d.ts.map +0 -1
  190. package/dist/index.js +0 -93
  191. package/dist/index.js.map +0 -1
  192. package/dist/slug.d.ts +0 -2
  193. package/dist/slug.d.ts.map +0 -1
  194. package/dist/slug.js +0 -12
  195. package/dist/slug.js.map +0 -1
  196. package/dist/slug.test.d.ts +0 -2
  197. package/dist/slug.test.d.ts.map +0 -1
  198. package/dist/slug.test.js +0 -32
  199. package/dist/slug.test.js.map +0 -1
@@ -0,0 +1,537 @@
1
+ /**
2
+ * GitHub API layer — wraps `gh` CLI with fallback to GITHUB_TOKEN + fetch.
3
+ *
4
+ * All GitHub communication goes through this module.
5
+ * Prefers `gh api` when the CLI is available and authenticated.
6
+ * Falls back to raw REST API with GITHUB_TOKEN env var.
7
+ */
8
+
9
+ import { execSync } from "node:child_process";
10
+
11
+ // ─── Auth detection ───────────────────────────────────────────────────────────
12
+
13
+ let _useGhCli: boolean | null = null;
14
+
15
+ function hasGhCli(): boolean {
16
+ if (_useGhCli !== null) return _useGhCli;
17
+ try {
18
+ execSync("gh auth status", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
19
+ _useGhCli = true;
20
+ } catch {
21
+ _useGhCli = false;
22
+ }
23
+ return _useGhCli;
24
+ }
25
+
26
+ function getToken(): string | undefined {
27
+ return process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
28
+ }
29
+
30
+ export function isAuthenticated(): boolean {
31
+ return hasGhCli() || !!getToken();
32
+ }
33
+
34
+ export function authMethod(): string {
35
+ if (hasGhCli()) return "gh CLI";
36
+ if (getToken()) return "GITHUB_TOKEN";
37
+ return "none";
38
+ }
39
+
40
+ // ─── Repo detection ───────────────────────────────────────────────────────────
41
+
42
+ export interface RepoInfo {
43
+ owner: string;
44
+ repo: string;
45
+ fullName: string;
46
+ }
47
+
48
+ export function detectRepo(cwd: string): RepoInfo | null {
49
+ try {
50
+ const remote = execSync("git remote get-url origin", {
51
+ cwd,
52
+ encoding: "utf8",
53
+ stdio: ["pipe", "pipe", "pipe"],
54
+ }).trim();
55
+
56
+ // Handle SSH: git@github.com:owner/repo.git
57
+ // Handle HTTPS: https://github.com/owner/repo.git
58
+ const sshMatch = remote.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
59
+ if (sshMatch) {
60
+ return { owner: sshMatch[1], repo: sshMatch[2], fullName: `${sshMatch[1]}/${sshMatch[2]}` };
61
+ }
62
+
63
+ return null;
64
+ } catch {
65
+ return null;
66
+ }
67
+ }
68
+
69
+ export function getCurrentBranch(cwd: string): string | null {
70
+ try {
71
+ return execSync("git rev-parse --abbrev-ref HEAD", {
72
+ cwd,
73
+ encoding: "utf8",
74
+ stdio: ["pipe", "pipe", "pipe"],
75
+ }).trim();
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+
81
+ export function getDefaultBranch(cwd: string): string {
82
+ try {
83
+ const result = execSync("git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || echo refs/remotes/origin/main", {
84
+ cwd,
85
+ encoding: "utf8",
86
+ stdio: ["pipe", "pipe", "pipe"],
87
+ }).trim();
88
+ return result.replace("refs/remotes/origin/", "");
89
+ } catch {
90
+ return "main";
91
+ }
92
+ }
93
+
94
+ // ─── API calls ────────────────────────────────────────────────────────────────
95
+
96
+ /**
97
+ * Call the GitHub REST API. Returns parsed JSON.
98
+ *
99
+ * When method is GET and params are provided, they're appended as query params.
100
+ * When method is POST/PUT/PATCH/DELETE, params are sent as JSON body.
101
+ */
102
+ export async function ghApi<T = unknown>(
103
+ endpoint: string,
104
+ options: {
105
+ method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
106
+ params?: Record<string, string | number | boolean | string[] | undefined>;
107
+ body?: Record<string, unknown>;
108
+ cwd?: string;
109
+ } = {},
110
+ ): Promise<T> {
111
+ const method = options.method ?? "GET";
112
+
113
+ if (hasGhCli()) {
114
+ return ghCliApi<T>(endpoint, method, options.params, options.body, options.cwd);
115
+ }
116
+
117
+ const token = getToken();
118
+ if (!token) throw new Error("Not authenticated. Install gh CLI or set GITHUB_TOKEN.");
119
+
120
+ return fetchApi<T>(endpoint, method, options.params, options.body, token);
121
+ }
122
+
123
+ function shellEscape(s: string): string {
124
+ // Single-quote wrapping, escaping any existing single quotes
125
+ return "'" + s.replace(/'/g, "'\\''") + "'";
126
+ }
127
+
128
+ function ghCliApi<T>(
129
+ endpoint: string,
130
+ method: string,
131
+ params?: Record<string, string | number | boolean | string[] | undefined>,
132
+ body?: Record<string, unknown>,
133
+ cwd?: string,
134
+ ): T {
135
+ const parts = ["gh", "api", shellEscape(endpoint), "--method", method];
136
+
137
+ if (params) {
138
+ for (const [key, val] of Object.entries(params)) {
139
+ if (val === undefined) continue;
140
+ if (Array.isArray(val)) {
141
+ for (const v of val) {
142
+ parts.push("-f", shellEscape(`${key}[]=${v}`));
143
+ }
144
+ } else {
145
+ parts.push("-f", shellEscape(`${key}=${String(val)}`));
146
+ }
147
+ }
148
+ }
149
+
150
+ if (body) {
151
+ parts.push("--input", "-");
152
+ }
153
+
154
+ try {
155
+ const result = execSync(parts.join(" "), {
156
+ cwd: cwd ?? process.cwd(),
157
+ encoding: "utf8",
158
+ stdio: ["pipe", "pipe", "pipe"],
159
+ input: body ? JSON.stringify(body) : undefined,
160
+ });
161
+ if (!result.trim()) return {} as T;
162
+ return JSON.parse(result) as T;
163
+ } catch (e: unknown) {
164
+ const err = e as { stderr?: string; stdout?: string; message?: string };
165
+ const msg = err.stderr?.trim() || err.stdout?.trim() || err.message || String(e);
166
+ throw new Error(`gh api error: ${msg}`);
167
+ }
168
+ }
169
+
170
+ async function fetchApi<T>(
171
+ endpoint: string,
172
+ method: string,
173
+ params?: Record<string, string | number | boolean | string[] | undefined>,
174
+ body?: Record<string, unknown>,
175
+ token?: string,
176
+ ): Promise<T> {
177
+ let url = endpoint.startsWith("http") ? endpoint : `https://api.github.com${endpoint}`;
178
+
179
+ if (method === "GET" && params) {
180
+ const qs = new URLSearchParams();
181
+ for (const [key, val] of Object.entries(params)) {
182
+ if (val === undefined) continue;
183
+ if (Array.isArray(val)) {
184
+ for (const v of val) qs.append(key, v);
185
+ } else {
186
+ qs.set(key, String(val));
187
+ }
188
+ }
189
+ const qsStr = qs.toString();
190
+ if (qsStr) url += `?${qsStr}`;
191
+ }
192
+
193
+ const headers: Record<string, string> = {
194
+ Accept: "application/vnd.github+json",
195
+ "X-GitHub-Api-Version": "2022-11-28",
196
+ };
197
+ if (token) headers.Authorization = `Bearer ${token}`;
198
+
199
+ const res = await fetch(url, {
200
+ method,
201
+ headers,
202
+ body: method !== "GET" && body ? JSON.stringify(body) : undefined,
203
+ });
204
+
205
+ if (!res.ok) {
206
+ const text = await res.text();
207
+ throw new Error(`GitHub API ${res.status}: ${text}`);
208
+ }
209
+
210
+ const text = await res.text();
211
+ if (!text.trim()) return {} as T;
212
+ return JSON.parse(text) as T;
213
+ }
214
+
215
+ // ─── Typed API wrappers ───────────────────────────────────────────────────────
216
+
217
+ export interface GhIssue {
218
+ number: number;
219
+ title: string;
220
+ state: string;
221
+ body: string | null;
222
+ user: { login: string };
223
+ labels: { name: string; color: string }[];
224
+ assignees: { login: string }[];
225
+ milestone: { title: string; number: number } | null;
226
+ created_at: string;
227
+ updated_at: string;
228
+ closed_at: string | null;
229
+ comments: number;
230
+ html_url: string;
231
+ pull_request?: { url: string };
232
+ }
233
+
234
+ export interface GhPullRequest {
235
+ number: number;
236
+ title: string;
237
+ state: string;
238
+ body: string | null;
239
+ user: { login: string };
240
+ labels: { name: string; color: string }[];
241
+ assignees: { login: string }[];
242
+ milestone: { title: string; number: number } | null;
243
+ head: { ref: string; sha: string };
244
+ base: { ref: string };
245
+ created_at: string;
246
+ updated_at: string;
247
+ merged_at: string | null;
248
+ closed_at: string | null;
249
+ comments: number;
250
+ review_comments: number;
251
+ draft: boolean;
252
+ mergeable: boolean | null;
253
+ mergeable_state: string;
254
+ html_url: string;
255
+ diff_url: string;
256
+ requested_reviewers: { login: string }[];
257
+ }
258
+
259
+ export interface GhComment {
260
+ id: number;
261
+ body: string;
262
+ user: { login: string };
263
+ created_at: string;
264
+ updated_at: string;
265
+ html_url: string;
266
+ }
267
+
268
+ export interface GhLabel {
269
+ name: string;
270
+ color: string;
271
+ description: string | null;
272
+ }
273
+
274
+ export interface GhMilestone {
275
+ number: number;
276
+ title: string;
277
+ description: string | null;
278
+ state: string;
279
+ open_issues: number;
280
+ closed_issues: number;
281
+ due_on: string | null;
282
+ }
283
+
284
+ export interface GhReview {
285
+ id: number;
286
+ user: { login: string };
287
+ state: string;
288
+ body: string | null;
289
+ submitted_at: string;
290
+ html_url: string;
291
+ }
292
+
293
+ export interface GhCheckRun {
294
+ name: string;
295
+ status: string;
296
+ conclusion: string | null;
297
+ html_url: string;
298
+ }
299
+
300
+ // ─── Issues ───────────────────────────────────────────────────────────────────
301
+
302
+ export async function listIssues(
303
+ repo: RepoInfo,
304
+ options: {
305
+ state?: "open" | "closed" | "all";
306
+ labels?: string;
307
+ assignee?: string;
308
+ milestone?: string;
309
+ sort?: "created" | "updated" | "comments";
310
+ direction?: "asc" | "desc";
311
+ per_page?: number;
312
+ page?: number;
313
+ } = {},
314
+ ): Promise<GhIssue[]> {
315
+ const params: Record<string, string | number | undefined> = {
316
+ state: options.state ?? "open",
317
+ sort: options.sort ?? "updated",
318
+ direction: options.direction ?? "desc",
319
+ per_page: String(options.per_page ?? 30),
320
+ page: String(options.page ?? 1),
321
+ };
322
+ if (options.labels) params.labels = options.labels;
323
+ if (options.assignee) params.assignee = options.assignee;
324
+ if (options.milestone) params.milestone = options.milestone;
325
+
326
+ const issues = await ghApi<GhIssue[]>(`/repos/${repo.fullName}/issues`, { params });
327
+ // Filter out PRs (GitHub API returns PRs in issues endpoint)
328
+ return issues.filter((i) => !i.pull_request);
329
+ }
330
+
331
+ export async function getIssue(repo: RepoInfo, number: number): Promise<GhIssue> {
332
+ return ghApi<GhIssue>(`/repos/${repo.fullName}/issues/${number}`);
333
+ }
334
+
335
+ export async function createIssue(
336
+ repo: RepoInfo,
337
+ data: { title: string; body?: string; labels?: string[]; assignees?: string[]; milestone?: number },
338
+ ): Promise<GhIssue> {
339
+ return ghApi<GhIssue>(`/repos/${repo.fullName}/issues`, {
340
+ method: "POST",
341
+ body: data,
342
+ });
343
+ }
344
+
345
+ export async function updateIssue(
346
+ repo: RepoInfo,
347
+ number: number,
348
+ data: { title?: string; body?: string; state?: string; labels?: string[]; assignees?: string[]; milestone?: number | null },
349
+ ): Promise<GhIssue> {
350
+ return ghApi<GhIssue>(`/repos/${repo.fullName}/issues/${number}`, {
351
+ method: "PATCH",
352
+ body: data,
353
+ });
354
+ }
355
+
356
+ export async function addComment(repo: RepoInfo, number: number, body: string): Promise<GhComment> {
357
+ return ghApi<GhComment>(`/repos/${repo.fullName}/issues/${number}/comments`, {
358
+ method: "POST",
359
+ body: { body },
360
+ });
361
+ }
362
+
363
+ export async function listComments(repo: RepoInfo, number: number): Promise<GhComment[]> {
364
+ return ghApi<GhComment[]>(`/repos/${repo.fullName}/issues/${number}/comments`);
365
+ }
366
+
367
+ // ─── Pull Requests ────────────────────────────────────────────────────────────
368
+
369
+ export async function listPullRequests(
370
+ repo: RepoInfo,
371
+ options: {
372
+ state?: "open" | "closed" | "all";
373
+ sort?: "created" | "updated" | "popularity" | "long-running";
374
+ direction?: "asc" | "desc";
375
+ per_page?: number;
376
+ page?: number;
377
+ head?: string;
378
+ base?: string;
379
+ } = {},
380
+ ): Promise<GhPullRequest[]> {
381
+ const params: Record<string, string | number | undefined> = {
382
+ state: options.state ?? "open",
383
+ sort: options.sort ?? "updated",
384
+ direction: options.direction ?? "desc",
385
+ per_page: String(options.per_page ?? 30),
386
+ page: String(options.page ?? 1),
387
+ };
388
+ if (options.head) params.head = options.head;
389
+ if (options.base) params.base = options.base;
390
+
391
+ return ghApi<GhPullRequest[]>(`/repos/${repo.fullName}/pulls`, { params });
392
+ }
393
+
394
+ export async function getPullRequest(repo: RepoInfo, number: number): Promise<GhPullRequest> {
395
+ return ghApi<GhPullRequest>(`/repos/${repo.fullName}/pulls/${number}`);
396
+ }
397
+
398
+ export async function createPullRequest(
399
+ repo: RepoInfo,
400
+ data: { title: string; body?: string; head: string; base: string; draft?: boolean },
401
+ ): Promise<GhPullRequest> {
402
+ return ghApi<GhPullRequest>(`/repos/${repo.fullName}/pulls`, {
403
+ method: "POST",
404
+ body: data,
405
+ });
406
+ }
407
+
408
+ export async function updatePullRequest(
409
+ repo: RepoInfo,
410
+ number: number,
411
+ data: { title?: string; body?: string; state?: string; base?: string },
412
+ ): Promise<GhPullRequest> {
413
+ return ghApi<GhPullRequest>(`/repos/${repo.fullName}/pulls/${number}`, {
414
+ method: "PATCH",
415
+ body: data,
416
+ });
417
+ }
418
+
419
+ export async function getPullRequestDiff(repo: RepoInfo, number: number): Promise<string> {
420
+ if (hasGhCli()) {
421
+ try {
422
+ return execSync(`gh pr diff ${number} --repo ${repo.fullName}`, {
423
+ encoding: "utf8",
424
+ stdio: ["pipe", "pipe", "pipe"],
425
+ }).trim();
426
+ } catch (e: unknown) {
427
+ const err = e as { stderr?: string; message?: string };
428
+ throw new Error(err.stderr?.trim() || err.message || String(e));
429
+ }
430
+ }
431
+
432
+ const token = getToken();
433
+ const headers: Record<string, string> = {
434
+ Accept: "application/vnd.github.v3.diff",
435
+ "X-GitHub-Api-Version": "2022-11-28",
436
+ };
437
+ if (token) headers.Authorization = `Bearer ${token}`;
438
+
439
+ const res = await fetch(`https://api.github.com/repos/${repo.fullName}/pulls/${number}`, { headers });
440
+ if (!res.ok) throw new Error(`GitHub API ${res.status}: ${await res.text()}`);
441
+ return res.text();
442
+ }
443
+
444
+ export async function listPullRequestFiles(
445
+ repo: RepoInfo,
446
+ number: number,
447
+ ): Promise<{ filename: string; status: string; additions: number; deletions: number; changes: number }[]> {
448
+ return ghApi(`/repos/${repo.fullName}/pulls/${number}/files`);
449
+ }
450
+
451
+ // ─── Reviews ──────────────────────────────────────────────────────────────────
452
+
453
+ export async function listReviews(repo: RepoInfo, number: number): Promise<GhReview[]> {
454
+ return ghApi<GhReview[]>(`/repos/${repo.fullName}/pulls/${number}/reviews`);
455
+ }
456
+
457
+ export async function createReview(
458
+ repo: RepoInfo,
459
+ number: number,
460
+ data: { body?: string; event: "APPROVE" | "REQUEST_CHANGES" | "COMMENT" },
461
+ ): Promise<GhReview> {
462
+ return ghApi<GhReview>(`/repos/${repo.fullName}/pulls/${number}/reviews`, {
463
+ method: "POST",
464
+ body: data,
465
+ });
466
+ }
467
+
468
+ export async function requestReviewers(
469
+ repo: RepoInfo,
470
+ number: number,
471
+ reviewers: string[],
472
+ ): Promise<GhPullRequest> {
473
+ return ghApi<GhPullRequest>(`/repos/${repo.fullName}/pulls/${number}/requested_reviewers`, {
474
+ method: "POST",
475
+ body: { reviewers },
476
+ });
477
+ }
478
+
479
+ // ─── Checks ───────────────────────────────────────────────────────────────────
480
+
481
+ export async function listCheckRuns(repo: RepoInfo, ref: string): Promise<{ check_runs: GhCheckRun[] }> {
482
+ return ghApi(`/repos/${repo.fullName}/commits/${ref}/check-runs`);
483
+ }
484
+
485
+ // ─── Labels & Milestones ──────────────────────────────────────────────────────
486
+
487
+ export async function listLabels(repo: RepoInfo): Promise<GhLabel[]> {
488
+ return ghApi<GhLabel[]>(`/repos/${repo.fullName}/labels`, {
489
+ params: { per_page: "100" },
490
+ });
491
+ }
492
+
493
+ export async function createLabel(
494
+ repo: RepoInfo,
495
+ data: { name: string; color: string; description?: string },
496
+ ): Promise<GhLabel> {
497
+ return ghApi<GhLabel>(`/repos/${repo.fullName}/labels`, {
498
+ method: "POST",
499
+ body: data,
500
+ });
501
+ }
502
+
503
+ export async function listMilestones(repo: RepoInfo): Promise<GhMilestone[]> {
504
+ return ghApi<GhMilestone[]>(`/repos/${repo.fullName}/milestones`, {
505
+ params: { state: "all", per_page: "100" },
506
+ });
507
+ }
508
+
509
+ export async function createMilestone(
510
+ repo: RepoInfo,
511
+ data: { title: string; description?: string; due_on?: string },
512
+ ): Promise<GhMilestone> {
513
+ return ghApi<GhMilestone>(`/repos/${repo.fullName}/milestones`, {
514
+ method: "POST",
515
+ body: data,
516
+ });
517
+ }
518
+
519
+ // ─── Search ───────────────────────────────────────────────────────────────────
520
+
521
+ export interface GhSearchResult<T> {
522
+ total_count: number;
523
+ items: T[];
524
+ }
525
+
526
+ export async function searchIssues(
527
+ query: string,
528
+ options: { per_page?: number; page?: number } = {},
529
+ ): Promise<GhSearchResult<GhIssue>> {
530
+ return ghApi<GhSearchResult<GhIssue>>("/search/issues", {
531
+ params: {
532
+ q: query,
533
+ per_page: String(options.per_page ?? 30),
534
+ page: String(options.page ?? 1),
535
+ },
536
+ });
537
+ }