@paretools/github 0.8.5 → 0.10.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 (99) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/index.js.map +1 -1
  3. package/dist/lib/formatters.d.ts +10 -2
  4. package/dist/lib/formatters.d.ts.map +1 -1
  5. package/dist/lib/formatters.js +144 -16
  6. package/dist/lib/formatters.js.map +1 -1
  7. package/dist/lib/gh-runner.d.ts.map +1 -1
  8. package/dist/lib/gh-runner.js +2 -1
  9. package/dist/lib/gh-runner.js.map +1 -1
  10. package/dist/lib/parsers.d.ts +54 -18
  11. package/dist/lib/parsers.d.ts.map +1 -1
  12. package/dist/lib/parsers.js +426 -38
  13. package/dist/lib/parsers.js.map +1 -1
  14. package/dist/lib/path-validation.d.ts +13 -0
  15. package/dist/lib/path-validation.d.ts.map +1 -0
  16. package/dist/lib/path-validation.js +54 -0
  17. package/dist/lib/path-validation.js.map +1 -0
  18. package/dist/schemas/index.d.ts +267 -4
  19. package/dist/schemas/index.d.ts.map +1 -1
  20. package/dist/schemas/index.js +195 -4
  21. package/dist/schemas/index.js.map +1 -1
  22. package/dist/tools/api.d.ts.map +1 -1
  23. package/dist/tools/api.js +134 -8
  24. package/dist/tools/api.js.map +1 -1
  25. package/dist/tools/gist-create.d.ts.map +1 -1
  26. package/dist/tools/gist-create.js +91 -21
  27. package/dist/tools/gist-create.js.map +1 -1
  28. package/dist/tools/index.d.ts.map +1 -1
  29. package/dist/tools/index.js +6 -0
  30. package/dist/tools/index.js.map +1 -1
  31. package/dist/tools/issue-close.d.ts.map +1 -1
  32. package/dist/tools/issue-close.js +56 -9
  33. package/dist/tools/issue-close.js.map +1 -1
  34. package/dist/tools/issue-comment.d.ts.map +1 -1
  35. package/dist/tools/issue-comment.js +60 -9
  36. package/dist/tools/issue-comment.js.map +1 -1
  37. package/dist/tools/issue-create.d.ts.map +1 -1
  38. package/dist/tools/issue-create.js +95 -9
  39. package/dist/tools/issue-create.js.map +1 -1
  40. package/dist/tools/issue-list.d.ts.map +1 -1
  41. package/dist/tools/issue-list.js +139 -11
  42. package/dist/tools/issue-list.js.map +1 -1
  43. package/dist/tools/issue-update.d.ts.map +1 -1
  44. package/dist/tools/issue-update.js +142 -15
  45. package/dist/tools/issue-update.js.map +1 -1
  46. package/dist/tools/issue-view.d.ts.map +1 -1
  47. package/dist/tools/issue-view.js +26 -14
  48. package/dist/tools/issue-view.js.map +1 -1
  49. package/dist/tools/label-create.d.ts +4 -0
  50. package/dist/tools/label-create.d.ts.map +1 -0
  51. package/dist/tools/label-create.js +73 -0
  52. package/dist/tools/label-create.js.map +1 -0
  53. package/dist/tools/label-list.d.ts +4 -0
  54. package/dist/tools/label-list.d.ts.map +1 -0
  55. package/dist/tools/label-list.js +71 -0
  56. package/dist/tools/label-list.js.map +1 -0
  57. package/dist/tools/pr-checks.d.ts.map +1 -1
  58. package/dist/tools/pr-checks.js +57 -11
  59. package/dist/tools/pr-checks.js.map +1 -1
  60. package/dist/tools/pr-comment.d.ts.map +1 -1
  61. package/dist/tools/pr-comment.js +60 -9
  62. package/dist/tools/pr-comment.js.map +1 -1
  63. package/dist/tools/pr-create.d.ts.map +1 -1
  64. package/dist/tools/pr-create.js +154 -9
  65. package/dist/tools/pr-create.js.map +1 -1
  66. package/dist/tools/pr-diff.d.ts.map +1 -1
  67. package/dist/tools/pr-diff.js +63 -16
  68. package/dist/tools/pr-diff.js.map +1 -1
  69. package/dist/tools/pr-list.d.ts.map +1 -1
  70. package/dist/tools/pr-list.js +115 -11
  71. package/dist/tools/pr-list.js.map +1 -1
  72. package/dist/tools/pr-merge.d.ts.map +1 -1
  73. package/dist/tools/pr-merge.js +104 -15
  74. package/dist/tools/pr-merge.js.map +1 -1
  75. package/dist/tools/pr-review.d.ts.map +1 -1
  76. package/dist/tools/pr-review.js +41 -8
  77. package/dist/tools/pr-review.js.map +1 -1
  78. package/dist/tools/pr-update.d.ts.map +1 -1
  79. package/dist/tools/pr-update.js +157 -10
  80. package/dist/tools/pr-update.js.map +1 -1
  81. package/dist/tools/pr-view.d.ts.map +1 -1
  82. package/dist/tools/pr-view.js +27 -14
  83. package/dist/tools/pr-view.js.map +1 -1
  84. package/dist/tools/release-create.d.ts.map +1 -1
  85. package/dist/tools/release-create.js +106 -9
  86. package/dist/tools/release-create.js.map +1 -1
  87. package/dist/tools/release-list.d.ts.map +1 -1
  88. package/dist/tools/release-list.js +39 -13
  89. package/dist/tools/release-list.js.map +1 -1
  90. package/dist/tools/run-list.d.ts.map +1 -1
  91. package/dist/tools/run-list.js +111 -12
  92. package/dist/tools/run-list.js.map +1 -1
  93. package/dist/tools/run-rerun.d.ts.map +1 -1
  94. package/dist/tools/run-rerun.js +38 -8
  95. package/dist/tools/run-rerun.js.map +1 -1
  96. package/dist/tools/run-view.d.ts.map +1 -1
  97. package/dist/tools/run-view.js +64 -13
  98. package/dist/tools/run-view.js.map +1 -1
  99. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"parsers.d.ts","sourceRoot":"","sources":["../../src/lib/parsers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"parsers.d.ts","sourceRoot":"","sources":["../../src/lib/parsers.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,20 @@ export function parsePrView(json) {
9
9
  status: c.status || c.state || "unknown",
10
10
  conclusion: c.conclusion ?? null,
11
11
  }));
12
+ // P1-gap #147: Parse reviews array
13
+ const reviews = raw.reviews
14
+ ? raw.reviews.map((r) => ({
15
+ author: r.author?.login ?? "unknown",
16
+ state: r.state,
17
+ body: r.body || undefined,
18
+ submittedAt: r.submittedAt ?? undefined,
19
+ }))
20
+ : undefined;
21
+ const commits = Array.isArray(raw.commits) ? raw.commits : [];
22
+ const commitCount = commits.length > 0 ? commits.length : undefined;
23
+ const latestCommitSha = commits.length > 0
24
+ ? (commits[commits.length - 1]?.oid ?? commits[commits.length - 1]?.commit?.oid ?? undefined)
25
+ : undefined;
12
26
  return {
13
27
  number: raw.number,
14
28
  state: raw.state,
@@ -23,12 +37,29 @@ export function parsePrView(json) {
23
37
  additions: raw.additions ?? 0,
24
38
  deletions: raw.deletions ?? 0,
25
39
  changedFiles: raw.changedFiles ?? 0,
40
+ // S-gap fields
41
+ author: raw.author?.login ?? undefined,
42
+ labels: raw.labels ? raw.labels.map((l) => l.name) : undefined,
43
+ isDraft: raw.isDraft ?? undefined,
44
+ assignees: raw.assignees
45
+ ? raw.assignees.map((a) => a.login)
46
+ : undefined,
47
+ createdAt: raw.createdAt ?? undefined,
48
+ updatedAt: raw.updatedAt ?? undefined,
49
+ milestone: raw.milestone?.title ?? undefined,
50
+ projectItems: raw.projectItems
51
+ ? raw.projectItems.map((p) => p.title)
52
+ : undefined,
53
+ // P1-gap #147
54
+ reviews,
55
+ commitCount,
56
+ latestCommitSha,
26
57
  };
27
58
  }
28
59
  /**
29
60
  * Parses `gh pr list --json ...` output into structured PR list data.
30
61
  */
31
- export function parsePrList(json) {
62
+ export function parsePrList(json, totalAvailable) {
32
63
  const raw = JSON.parse(json);
33
64
  const items = Array.isArray(raw) ? raw : [];
34
65
  const prs = items.map((pr) => ({
@@ -38,62 +69,164 @@ export function parsePrList(json) {
38
69
  url: pr.url,
39
70
  headBranch: pr.headRefName,
40
71
  author: pr.author?.login ?? "",
72
+ // S-gap fields
73
+ labels: pr.labels ? pr.labels.map((l) => l.name) : undefined,
74
+ isDraft: pr.isDraft ?? undefined,
75
+ baseBranch: pr.baseRefName ?? undefined,
76
+ reviewDecision: pr.reviewDecision ?? undefined,
77
+ mergeable: pr.mergeable ?? undefined,
41
78
  }));
42
- return { prs, total: prs.length };
79
+ return { prs, total: prs.length, totalAvailable };
43
80
  }
44
81
  /**
45
82
  * Parses `gh pr create` output (URL on stdout) into structured data.
46
83
  * The gh CLI prints the new PR URL to stdout. We extract the number from it.
47
84
  */
48
- export function parsePrCreate(stdout) {
85
+ export function parsePrCreate(stdout, opts) {
49
86
  const url = stdout.trim();
50
87
  const match = url.match(/\/pull\/(\d+)$/);
51
88
  const number = match ? parseInt(match[1], 10) : 0;
52
- return { number, url };
89
+ return {
90
+ number,
91
+ url,
92
+ title: opts?.title,
93
+ baseBranch: opts?.baseBranch,
94
+ headBranch: opts?.headBranch,
95
+ draft: opts?.draft,
96
+ };
53
97
  }
54
98
  /**
55
99
  * Parses `gh pr edit` output into structured data.
56
100
  * The gh CLI prints the PR URL to stdout on success.
57
101
  */
58
- export function parsePrUpdate(stdout, number) {
102
+ export function parsePrUpdate(stdout, number, updatedFields, operations) {
59
103
  const urlMatch = stdout.match(/(https:\/\/github\.com\/[^\s]+\/pull\/\d+)/);
60
104
  const url = urlMatch ? urlMatch[1] : "";
61
- return { number, url };
105
+ return { number, url, updatedFields, operations };
62
106
  }
63
107
  /**
64
108
  * Parses `gh pr merge` output into structured data.
65
109
  * The gh CLI prints a confirmation message with the PR URL on success.
110
+ * Enhanced to detect merge commit SHA, auto-merge state, and merge method.
66
111
  */
67
- export function parsePrMerge(stdout, number, method) {
112
+ export function parsePrMerge(stdout, number, method, deleteBranch, auto, disableAuto) {
68
113
  const urlMatch = stdout.match(/(https:\/\/github\.com\/[^\s]+\/pull\/\d+)/);
69
114
  const url = urlMatch ? urlMatch[1] : "";
70
- return { number, merged: true, method, url };
115
+ // S-gap: detect if branch was deleted from output
116
+ const branchDeleted = deleteBranch !== undefined
117
+ ? /deleted branch|branch.*deleted/i.test(stdout)
118
+ ? true
119
+ : deleteBranch
120
+ : undefined;
121
+ // Extract merge commit SHA if present in output
122
+ // gh pr merge may output lines like "Merge commit SHA: abc123..." or include it in the URL
123
+ const shaMatch = stdout.match(/\b([0-9a-f]{40})\b/);
124
+ const mergeCommitSha = shaMatch ? shaMatch[1] : undefined;
125
+ // Determine the merge state
126
+ let state;
127
+ if (disableAuto) {
128
+ state = "auto-merge-disabled";
129
+ }
130
+ else if (auto || /auto-merge/i.test(stdout) || /automatically merge/i.test(stdout)) {
131
+ state = "auto-merge-enabled";
132
+ }
133
+ else {
134
+ state = "merged";
135
+ }
136
+ // Detect actual merge method from output text when possible
137
+ let detectedMethod = method;
138
+ if (/squashed and merged/i.test(stdout)) {
139
+ detectedMethod = "squash";
140
+ }
141
+ else if (/rebased and merged/i.test(stdout)) {
142
+ detectedMethod = "rebase";
143
+ }
144
+ else if (/merged.*pull request/i.test(stdout) && !/squashed|rebased/i.test(stdout)) {
145
+ detectedMethod = "merge";
146
+ }
147
+ return {
148
+ number,
149
+ merged: state === "merged",
150
+ method: detectedMethod,
151
+ url,
152
+ branchDeleted,
153
+ mergeCommitSha,
154
+ state,
155
+ };
71
156
  }
72
157
  /**
73
158
  * Parses `gh pr comment` / `gh issue comment` output into structured data.
74
159
  * The gh CLI prints the new comment URL to stdout.
160
+ * S-gap: Enhanced to include operation type, commentId, number, and body echo.
75
161
  */
76
- export function parseComment(stdout) {
162
+ export function parseComment(stdout, opts) {
77
163
  const url = stdout.trim();
78
- return { url };
164
+ // S-gap: extract commentId from URL (e.g., #issuecomment-123456)
165
+ const commentIdMatch = url.match(/#issuecomment-(\d+)/);
166
+ const commentId = commentIdMatch ? commentIdMatch[1] : undefined;
167
+ return {
168
+ url: url || undefined,
169
+ operation: opts?.operation,
170
+ commentId,
171
+ issueNumber: opts?.issueNumber,
172
+ prNumber: opts?.prNumber,
173
+ body: opts?.body,
174
+ };
79
175
  }
80
176
  /**
81
177
  * Parses `gh pr review` output into structured data.
82
178
  * The gh CLI prints a confirmation message with the PR URL on success.
179
+ * S-gap: Enhanced to include reviewId, reviewDecision, and body echo.
83
180
  */
84
- export function parsePrReview(stdout, number, event) {
181
+ export function parsePrReview(stdout, number, event, body, stderr) {
85
182
  const urlMatch = stdout.match(/(https:\/\/github\.com\/[^\s]+\/pull\/\d+)/);
86
183
  const url = urlMatch ? urlMatch[1] : "";
87
- return { number, event, url };
184
+ // P1-gap #145: Parse review event type from CLI output for confirmation
185
+ // Map the CLI event name to GitHub's review event type
186
+ const eventMap = {
187
+ approve: "APPROVE",
188
+ "request-changes": "REQUEST_CHANGES",
189
+ comment: "COMMENT",
190
+ };
191
+ const resolvedEvent = eventMap[event] ?? event;
192
+ // P1-gap #146: Classify review errors from stderr
193
+ const errorType = stderr ? classifyReviewError(stderr) : undefined;
194
+ return {
195
+ number,
196
+ event: resolvedEvent,
197
+ url,
198
+ body: body ?? undefined,
199
+ errorType,
200
+ };
201
+ }
202
+ /**
203
+ * Classifies review error from stderr into structured error types.
204
+ * P1-gap #146.
205
+ */
206
+ function classifyReviewError(stderr) {
207
+ const lower = stderr.toLowerCase();
208
+ if (/not found|could not resolve|no pull request/i.test(lower))
209
+ return "not-found";
210
+ if (/permission|forbidden|403/i.test(lower))
211
+ return "permission-denied";
212
+ if (/already reviewed|already approved|already submitted/i.test(lower))
213
+ return "already-reviewed";
214
+ if (/draft|is a draft/i.test(lower))
215
+ return "draft-pr";
216
+ if (stderr.trim())
217
+ return "unknown";
218
+ return undefined;
88
219
  }
89
220
  /**
90
221
  * Parses `gh pr checks --json ...` output into structured PR checks data.
91
222
  * Computes summary counts by bucket (pass, fail, pending, skipping, cancel).
223
+ * Deduplicates entries by check name, keeping the most recent run
224
+ * (determined by completedAt, then startedAt, with later entries winning ties).
92
225
  */
93
226
  export function parsePrChecks(json, pr) {
94
227
  const raw = JSON.parse(json);
95
228
  const items = Array.isArray(raw) ? raw : [];
96
- const checks = items.map((c) => ({
229
+ const allChecks = items.map((c) => ({
97
230
  name: c.name ?? "",
98
231
  state: c.state ?? "",
99
232
  bucket: c.bucket ?? "",
@@ -104,6 +237,25 @@ export function parsePrChecks(json, pr) {
104
237
  startedAt: c.startedAt ?? "",
105
238
  completedAt: c.completedAt ?? "",
106
239
  }));
240
+ // Deduplicate by check name, keeping the most recent run.
241
+ // For checks with the same name, prefer the one with the later completedAt
242
+ // (or startedAt as fallback). If timestamps are equal, keep the later entry
243
+ // in the array (which is typically the most recent re-run).
244
+ const deduped = new Map();
245
+ for (const check of allChecks) {
246
+ const existing = deduped.get(check.name);
247
+ if (!existing) {
248
+ deduped.set(check.name, check);
249
+ continue;
250
+ }
251
+ // Compare by completedAt first, then startedAt
252
+ const existingTime = existing.completedAt || existing.startedAt || "";
253
+ const newTime = check.completedAt || check.startedAt || "";
254
+ if (newTime >= existingTime) {
255
+ deduped.set(check.name, check);
256
+ }
257
+ }
258
+ const checks = Array.from(deduped.values());
107
259
  const summary = {
108
260
  total: checks.length,
109
261
  passed: checks.filter((c) => c.bucket === "pass").length,
@@ -128,6 +280,16 @@ export function parseIssueView(json) {
128
280
  assignees: (raw.assignees ?? []).map((a) => a.login),
129
281
  url: raw.url,
130
282
  createdAt: raw.createdAt ?? "",
283
+ // S-gap fields
284
+ stateReason: raw.stateReason ?? undefined,
285
+ author: raw.author?.login ?? undefined,
286
+ milestone: raw.milestone?.title ?? undefined,
287
+ updatedAt: raw.updatedAt ?? undefined,
288
+ closedAt: raw.closedAt ?? undefined,
289
+ isPinned: raw.isPinned ?? undefined,
290
+ projectItems: raw.projectItems
291
+ ? raw.projectItems.map((p) => p.title)
292
+ : undefined,
131
293
  };
132
294
  }
133
295
  /**
@@ -143,39 +305,71 @@ export function parseIssueList(json) {
143
305
  url: issue.url,
144
306
  labels: (issue.labels ?? []).map((l) => l.name),
145
307
  assignees: (issue.assignees ?? []).map((a) => a.login),
308
+ // S-gap fields
309
+ author: issue.author?.login ?? undefined,
310
+ createdAt: issue.createdAt ?? undefined,
311
+ milestone: issue.milestone?.title ?? undefined,
146
312
  }));
147
313
  return { issues, total: issues.length };
148
314
  }
149
315
  /**
150
316
  * Parses `gh issue create` output (URL on stdout) into structured data.
151
317
  * The gh CLI prints the new issue URL to stdout. We extract the number from it.
318
+ * S-gap: Enhanced to echo back applied labels.
152
319
  */
153
- export function parseIssueCreate(stdout) {
320
+ export function parseIssueCreate(stdout, labels) {
154
321
  const url = stdout.trim();
155
322
  const match = url.match(/\/issues\/(\d+)$/);
156
323
  const number = match ? parseInt(match[1], 10) : 0;
157
- return { number, url };
324
+ return {
325
+ number,
326
+ url,
327
+ labelsApplied: labels && labels.length > 0 ? labels : undefined,
328
+ };
158
329
  }
159
330
  /**
160
331
  * Parses `gh issue close` output into structured data.
161
- * The gh CLI prints a confirmation URL to stdout. We extract the number from it.
332
+ * The gh CLI may print a confirmation message plus URL to stdout.
333
+ * We robustly extract the issue URL using a regex rather than assuming
334
+ * the entire stdout is just a URL (handles extra text, whitespace, and
335
+ * different output formats).
336
+ * S-gap: Enhanced to include reason and commentUrl.
162
337
  */
163
- export function parseIssueClose(stdout, number) {
164
- const url = stdout.trim();
165
- return { number, state: "closed", url };
338
+ export function parseIssueClose(stdout, number, reason, comment, stderr) {
339
+ // Robust URL extraction: find the GitHub issue URL anywhere in the output
340
+ const urlMatch = stdout.match(/(https:\/\/github\.com\/[^\s]+\/issues\/\d+)/);
341
+ const url = urlMatch ? urlMatch[1] : stdout.trim();
342
+ // S-gap: Extract comment URL from output if a comment was added
343
+ const commentUrlMatch = stdout.match(/(https:\/\/github\.com\/[^\s]+#issuecomment-\d+)/);
344
+ // P1-gap #144: Detect already-closed issues
345
+ const combined = `${stdout}\n${stderr ?? ""}`;
346
+ const alreadyClosed = /already closed/i.test(combined) ||
347
+ /issue .* is already closed/i.test(combined) ||
348
+ /already been closed/i.test(combined)
349
+ ? true
350
+ : undefined;
351
+ return {
352
+ number,
353
+ state: "closed",
354
+ url,
355
+ reason: reason ?? undefined,
356
+ commentUrl: comment && commentUrlMatch ? commentUrlMatch[1] : undefined,
357
+ alreadyClosed,
358
+ };
166
359
  }
167
360
  /**
168
361
  * Parses `gh issue edit` output into structured data.
169
362
  * The gh CLI prints the issue URL to stdout on success.
170
363
  */
171
- export function parseIssueUpdate(stdout, number) {
364
+ export function parseIssueUpdate(stdout, number, updatedFields, operations) {
172
365
  const urlMatch = stdout.match(/(https:\/\/github\.com\/[^\s]+\/issues\/\d+)/);
173
366
  const url = urlMatch ? urlMatch[1] : "";
174
- return { number, url };
367
+ return { number, url, updatedFields, operations };
175
368
  }
176
369
  /**
177
370
  * Parses `gh run view --json ...` output into structured run view data.
178
371
  * Renames gh field names (e.g., databaseId → id).
372
+ * S-gap: Enhanced to include job steps, headSha, event, startedAt, attempt.
179
373
  */
180
374
  export function parseRunView(json) {
181
375
  const raw = JSON.parse(json);
@@ -183,7 +377,24 @@ export function parseRunView(json) {
183
377
  name: j.name,
184
378
  status: j.status,
185
379
  conclusion: j.conclusion ?? null,
380
+ // S-gap: Include steps array
381
+ steps: j.steps
382
+ ? j.steps.map((s) => ({
383
+ name: s.name,
384
+ status: s.status,
385
+ conclusion: s.conclusion ?? null,
386
+ }))
387
+ : undefined,
186
388
  }));
389
+ const updatedAt = raw.updatedAt ?? undefined;
390
+ const startedAt = raw.startedAt ?? undefined;
391
+ const createdAt = raw.createdAt ?? "";
392
+ let durationSeconds;
393
+ const start = startedAt ? Date.parse(startedAt) : createdAt ? Date.parse(createdAt) : NaN;
394
+ const end = updatedAt ? Date.parse(updatedAt) : NaN;
395
+ if (Number.isFinite(start) && Number.isFinite(end) && end >= start) {
396
+ durationSeconds = Math.round((end - start) / 1000);
397
+ }
187
398
  return {
188
399
  id: raw.databaseId,
189
400
  status: raw.status,
@@ -194,12 +405,19 @@ export function parseRunView(json) {
194
405
  jobs,
195
406
  url: raw.url,
196
407
  createdAt: raw.createdAt ?? "",
408
+ // P0 enrichments
409
+ headSha: raw.headSha ?? undefined,
410
+ event: raw.event ?? undefined,
411
+ startedAt,
412
+ updatedAt,
413
+ attempt: raw.attempt ?? undefined,
414
+ durationSeconds,
197
415
  };
198
416
  }
199
417
  /**
200
418
  * Parses `gh run list --json ...` output into structured run list data.
201
419
  */
202
- export function parseRunList(json) {
420
+ export function parseRunList(json, totalAvailable) {
203
421
  const raw = JSON.parse(json);
204
422
  const items = Array.isArray(raw) ? raw : [];
205
423
  const runs = items.map((r) => ({
@@ -211,40 +429,66 @@ export function parseRunList(json) {
211
429
  headBranch: r.headBranch,
212
430
  url: r.url,
213
431
  createdAt: r.createdAt ?? "",
432
+ // P1-gap #148
433
+ headSha: r.headSha ?? undefined,
434
+ event: r.event ?? undefined,
435
+ startedAt: r.startedAt ?? undefined,
436
+ attempt: r.attempt ?? undefined,
214
437
  }));
215
- return { runs, total: runs.length };
438
+ return { runs, total: runs.length, totalAvailable };
216
439
  }
217
440
  /**
218
441
  * Parses `gh run rerun` output into structured data.
219
442
  * The gh CLI prints a confirmation message to stderr on success.
220
443
  * We construct the URL from the run ID and repo info.
444
+ * S-gap: Enhanced to include job field.
221
445
  */
222
- export function parseRunRerun(stdout, stderr, runId, failedOnly) {
446
+ export function parseRunRerun(stdout, stderr, runId, failedOnly, job) {
223
447
  // gh run rerun outputs confirmation to stderr, e.g.:
224
448
  // "✓ Requested rerun of run 12345"
225
449
  // Try to extract URL from stdout/stderr if present
226
450
  const combined = `${stdout}\n${stderr}`;
227
451
  const urlMatch = combined.match(/(https:\/\/github\.com\/[^\s]+\/actions\/runs\/\d+)/);
228
452
  const url = urlMatch ? urlMatch[1] : "";
453
+ // P1-gap #149: Parse attempt number from output
454
+ const attemptMatch = combined.match(/attempt[\s#:]*?(\d+)/i);
455
+ const attempt = attemptMatch ? parseInt(attemptMatch[1], 10) : undefined;
456
+ // P1-gap #149: Extract new run URL if a different URL appears (for the new attempt)
457
+ // Sometimes gh outputs the new run URL after a rerun
458
+ const allUrls = combined.match(/https:\/\/github\.com\/[^\s]+\/actions\/runs\/\d+/g) ?? [];
459
+ const newRunUrl = allUrls.length > 1 ? allUrls[allUrls.length - 1] : undefined;
460
+ const status = job ? "requested-job" : failedOnly ? "requested-failed" : "requested-full";
229
461
  return {
230
462
  runId,
231
- status: "requested",
463
+ status,
232
464
  failedOnly,
233
465
  url,
466
+ job: job ?? undefined,
467
+ attempt,
468
+ newRunUrl,
234
469
  };
235
470
  }
236
471
  /**
237
472
  * Parses `gh release create` output (URL on stdout) into structured data.
238
473
  * The gh CLI prints the new release URL to stdout.
474
+ * S-gap: Enhanced to include title, assetsUploaded count.
239
475
  */
240
- export function parseReleaseCreate(stdout, tag, draft, prerelease) {
476
+ export function parseReleaseCreate(stdout, tag, draft, prerelease, title, assetsCount) {
241
477
  const url = stdout.trim();
242
- return { tag, url, draft, prerelease };
478
+ return {
479
+ tag,
480
+ url,
481
+ draft,
482
+ prerelease,
483
+ title: title ?? undefined,
484
+ assetsUploaded: assetsCount ?? undefined,
485
+ };
243
486
  }
244
487
  /**
245
488
  * Parses `gh release list --json ...` output into structured release list data.
489
+ * S-gap: Enhanced to include isLatest and createdAt.
246
490
  */
247
- export function parseReleaseList(json) {
491
+ export function parseReleaseList(json, totalAvailable) {
248
492
  const raw = JSON.parse(json);
249
493
  const items = Array.isArray(raw) ? raw : [];
250
494
  const releases = items.map((r) => ({
@@ -254,34 +498,178 @@ export function parseReleaseList(json) {
254
498
  prerelease: r.isPrerelease ?? false,
255
499
  publishedAt: r.publishedAt ?? "",
256
500
  url: r.url ?? "",
501
+ // S-gap fields
502
+ isLatest: r.isLatest ?? undefined,
503
+ createdAt: r.createdAt ?? undefined,
257
504
  }));
258
- return { releases, total: releases.length };
505
+ return { releases, total: releases.length, totalAvailable };
259
506
  }
260
507
  /**
261
508
  * Parses `gh api` stdout into structured API result data.
262
- * Attempts to parse stdout as JSON; falls back to raw string body.
509
+ * When `--include` is used, the output starts with HTTP headers followed by
510
+ * a blank line and then the response body. We parse the real HTTP status code
511
+ * from the status line and separate the body.
263
512
  */
264
- export function parseApi(stdout, exitCode, endpoint, method) {
265
- // gh api returns exit code 0 for success. Map to HTTP-like status codes.
513
+ export function parseApi(stdout, exitCode, endpoint, method, stderr) {
514
+ // Default: infer status from exit code
515
+ let statusCode = exitCode === 0 ? 200 : 422;
516
+ let bodyText = stdout;
517
+ let responseHeaders;
518
+ let pagination;
519
+ // When --include is passed, stdout starts with HTTP headers:
520
+ // HTTP/2.0 200 OK\r\n...headers...\r\n\r\nbody
521
+ const headerEndIndex = stdout.indexOf("\r\n\r\n");
522
+ if (headerEndIndex !== -1) {
523
+ const headerBlock = stdout.slice(0, headerEndIndex);
524
+ bodyText = stdout.slice(headerEndIndex + 4);
525
+ responseHeaders = parseHeaderBlock(headerBlock);
526
+ pagination = parsePagination(responseHeaders?.link);
527
+ // Parse status code from first line: "HTTP/1.1 200 OK" or "HTTP/2.0 200 OK"
528
+ const statusMatch = headerBlock.match(/^HTTP\/[\d.]+ (\d+)/);
529
+ if (statusMatch) {
530
+ statusCode = parseInt(statusMatch[1], 10);
531
+ }
532
+ }
533
+ else {
534
+ // Also handle \n\n separator (some environments)
535
+ const headerEndLF = stdout.indexOf("\n\n");
536
+ if (headerEndLF !== -1 && /^HTTP\/[\d.]+ \d+/.test(stdout)) {
537
+ const headerBlock = stdout.slice(0, headerEndLF);
538
+ bodyText = stdout.slice(headerEndLF + 2);
539
+ responseHeaders = parseHeaderBlock(headerBlock);
540
+ pagination = parsePagination(responseHeaders?.link);
541
+ const statusMatch = headerBlock.match(/^HTTP\/[\d.]+ (\d+)/);
542
+ if (statusMatch) {
543
+ statusCode = parseInt(statusMatch[1], 10);
544
+ }
545
+ }
546
+ }
547
+ // Keep legacy `status` as before for backward compat
266
548
  const status = exitCode === 0 ? 200 : 422;
267
549
  let body;
268
550
  try {
269
- body = JSON.parse(stdout);
551
+ body = JSON.parse(bodyText);
270
552
  }
271
553
  catch {
272
- body = stdout;
554
+ body = bodyText;
555
+ }
556
+ // P1-gap #141: Preserve error body for debugging when request failed
557
+ const errorBody = exitCode !== 0 && stderr ? parseErrorBody(stderr) : undefined;
558
+ // GraphQL responses can return 200 with an `errors` array.
559
+ const graphqlErrors = body && typeof body === "object" && "errors" in body
560
+ ? (body.errors ?? undefined)
561
+ : undefined;
562
+ return {
563
+ status,
564
+ statusCode,
565
+ body,
566
+ endpoint,
567
+ method,
568
+ responseHeaders,
569
+ pagination,
570
+ graphqlErrors,
571
+ errorBody,
572
+ };
573
+ }
574
+ function parseHeaderBlock(headerBlock) {
575
+ const lines = headerBlock.split(/\r?\n/);
576
+ const headers = {};
577
+ for (const line of lines.slice(1)) {
578
+ const idx = line.indexOf(":");
579
+ if (idx <= 0)
580
+ continue;
581
+ const key = line.slice(0, idx).trim().toLowerCase();
582
+ const value = line.slice(idx + 1).trim();
583
+ headers[key] = value;
584
+ }
585
+ return headers;
586
+ }
587
+ function parsePagination(linkHeader) {
588
+ if (!linkHeader)
589
+ return undefined;
590
+ const nextMatch = linkHeader.match(/<([^>]+)>;\s*rel=\"next\"/);
591
+ const lastMatch = linkHeader.match(/<([^>]+)>;\s*rel=\"last\"/);
592
+ return {
593
+ hasNext: !!nextMatch,
594
+ next: nextMatch?.[1],
595
+ last: lastMatch?.[1],
596
+ };
597
+ }
598
+ /**
599
+ * Attempts to parse error body from stderr output.
600
+ * gh api may output JSON error details or plain text to stderr.
601
+ */
602
+ function parseErrorBody(stderr) {
603
+ const trimmed = stderr.trim();
604
+ if (!trimmed)
605
+ return undefined;
606
+ try {
607
+ return JSON.parse(trimmed);
608
+ }
609
+ catch {
610
+ // Check if stderr contains an embedded JSON object (e.g., after a prefix message)
611
+ const jsonMatch = trimmed.match(/\{[\s\S]*\}/);
612
+ if (jsonMatch) {
613
+ try {
614
+ return JSON.parse(jsonMatch[0]);
615
+ }
616
+ catch {
617
+ // fall through
618
+ }
619
+ }
620
+ return trimmed;
273
621
  }
274
- return { status, body, endpoint, method };
275
622
  }
276
623
  /**
277
624
  * Parses `gh gist create` output (URL on stdout) into structured data.
278
625
  * The gh CLI prints the new gist URL to stdout. We extract the ID from it.
626
+ * S-gap: Enhanced to include files, description, fileCount.
279
627
  */
280
- export function parseGistCreate(stdout, isPublic) {
628
+ export function parseGistCreate(stdout, isPublic, files, description) {
281
629
  const url = stdout.trim();
282
- const match = url.match(/\/([a-f0-9]+)$/);
630
+ const match = url.match(/gist\.github\.com\/(?:[^/]+\/)?([a-f0-9]+)/i) ||
631
+ url.match(/\/([a-f0-9]+)(?:\/?$|[?#])/i);
283
632
  const id = match ? match[1] : "";
284
- return { id, url, public: isPublic };
633
+ return {
634
+ id,
635
+ url,
636
+ public: isPublic,
637
+ files: files ?? undefined,
638
+ description: description ?? undefined,
639
+ fileCount: files ? files.length : undefined,
640
+ };
641
+ }
642
+ /**
643
+ * Parses `gh label list --json ...` output into structured label list data.
644
+ */
645
+ export function parseLabelList(json) {
646
+ const raw = JSON.parse(json);
647
+ const items = Array.isArray(raw) ? raw : [];
648
+ const labels = items.map((l) => ({
649
+ name: l.name ?? "",
650
+ description: l.description ?? "",
651
+ color: l.color ?? "",
652
+ isDefault: l.isDefault ?? false,
653
+ }));
654
+ return { labels, total: labels.length };
655
+ }
656
+ /**
657
+ * Parses `gh label create` output into structured label create data.
658
+ * The gh CLI prints a confirmation message to stderr on success.
659
+ */
660
+ export function parseLabelCreate(stdout, stderr, name, description, color) {
661
+ // gh label create outputs confirmation to stderr, e.g.:
662
+ // "✓ Label "my-label" created in owner/repo"
663
+ // Try to extract a URL if present
664
+ const combined = `${stdout}\n${stderr}`;
665
+ const urlMatch = combined.match(/(https:\/\/github\.com\/[^\s]+\/labels\/[^\s]+)/);
666
+ const url = urlMatch ? urlMatch[1] : undefined;
667
+ return {
668
+ name,
669
+ description: description ?? undefined,
670
+ color: color ?? undefined,
671
+ url,
672
+ };
285
673
  }
286
674
  /**
287
675
  * Parses `gh pr diff --numstat` output into structured PR diff data.