@blogic-cz/agent-tools 1.7.5 → 1.9.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 (142) hide show
  1. package/README.md +2 -1
  2. package/dist/config/loader.d.ts.map +1 -1
  3. package/dist/config/types.d.ts +1 -0
  4. package/dist/config/types.d.ts.map +1 -1
  5. package/dist/credential-guard/index.d.ts.map +1 -1
  6. package/dist/db-tool/service.d.ts.map +1 -1
  7. package/dist/gh-tool/branch.d.ts +2 -3
  8. package/dist/gh-tool/branch.d.ts.map +1 -1
  9. package/dist/gh-tool/environment.d.ts +1 -1
  10. package/dist/gh-tool/environment.d.ts.map +1 -1
  11. package/dist/gh-tool/gist.d.ts +8 -28
  12. package/dist/gh-tool/gist.d.ts.map +1 -1
  13. package/dist/gh-tool/issue/commands.d.ts +11 -9
  14. package/dist/gh-tool/issue/commands.d.ts.map +1 -1
  15. package/dist/gh-tool/issue/core.d.ts +116 -9
  16. package/dist/gh-tool/issue/core.d.ts.map +1 -1
  17. package/dist/gh-tool/issue/triage.d.ts +6 -6
  18. package/dist/gh-tool/issue/triage.d.ts.map +1 -1
  19. package/dist/gh-tool/label.d.ts +20 -0
  20. package/dist/gh-tool/label.d.ts.map +1 -0
  21. package/dist/gh-tool/pr/commands.d.ts +53 -46
  22. package/dist/gh-tool/pr/commands.d.ts.map +1 -1
  23. package/dist/gh-tool/pr/core.d.ts +156 -86
  24. package/dist/gh-tool/pr/core.d.ts.map +1 -1
  25. package/dist/gh-tool/pr/review.d.ts +16 -16
  26. package/dist/gh-tool/pr/review.d.ts.map +1 -1
  27. package/dist/gh-tool/pr/snapshot.d.ts +9 -0
  28. package/dist/gh-tool/pr/snapshot.d.ts.map +1 -0
  29. package/dist/gh-tool/pr/stack-read.d.ts +1 -6
  30. package/dist/gh-tool/pr/stack-read.d.ts.map +1 -1
  31. package/dist/gh-tool/pr/stack.d.ts +3 -3
  32. package/dist/gh-tool/pr/stack.d.ts.map +1 -1
  33. package/dist/gh-tool/{api.d.ts → read/api.d.ts} +9 -3
  34. package/dist/gh-tool/read/api.d.ts.map +1 -0
  35. package/dist/gh-tool/read/cache.d.ts +44 -0
  36. package/dist/gh-tool/read/cache.d.ts.map +1 -0
  37. package/dist/gh-tool/read/catalog.d.ts +170 -0
  38. package/dist/gh-tool/read/catalog.d.ts.map +1 -0
  39. package/dist/gh-tool/read/classes.d.ts +98 -0
  40. package/dist/gh-tool/read/classes.d.ts.map +1 -0
  41. package/dist/gh-tool/read/commands.d.ts +3 -0
  42. package/dist/gh-tool/read/commands.d.ts.map +1 -0
  43. package/dist/gh-tool/read/commits.d.ts +10 -0
  44. package/dist/gh-tool/read/commits.d.ts.map +1 -0
  45. package/dist/gh-tool/read/gated.d.ts +14 -0
  46. package/dist/gh-tool/read/gated.d.ts.map +1 -0
  47. package/dist/gh-tool/read/index.d.ts +5 -0
  48. package/dist/gh-tool/read/index.d.ts.map +1 -0
  49. package/dist/gh-tool/read/issues.d.ts +80 -0
  50. package/dist/gh-tool/read/issues.d.ts.map +1 -0
  51. package/dist/gh-tool/read/keys.d.ts +64 -0
  52. package/dist/gh-tool/read/keys.d.ts.map +1 -0
  53. package/dist/gh-tool/read/porcelain.d.ts +68 -0
  54. package/dist/gh-tool/read/porcelain.d.ts.map +1 -0
  55. package/dist/gh-tool/read/pulls.d.ts +137 -0
  56. package/dist/gh-tool/read/pulls.d.ts.map +1 -0
  57. package/dist/gh-tool/read/reads.d.ts +220 -0
  58. package/dist/gh-tool/read/reads.d.ts.map +1 -0
  59. package/dist/gh-tool/read/rest.d.ts +49 -0
  60. package/dist/gh-tool/read/rest.d.ts.map +1 -0
  61. package/dist/gh-tool/read/review-guards.d.ts +20 -0
  62. package/dist/gh-tool/read/review-guards.d.ts.map +1 -0
  63. package/dist/gh-tool/read/review-model.d.ts +190 -0
  64. package/dist/gh-tool/read/review-model.d.ts.map +1 -0
  65. package/dist/gh-tool/read/reviews.d.ts +9 -0
  66. package/dist/gh-tool/read/reviews.d.ts.map +1 -0
  67. package/dist/gh-tool/read/stacks.d.ts +7 -0
  68. package/dist/gh-tool/read/stacks.d.ts.map +1 -0
  69. package/dist/gh-tool/read/store.d.ts +70 -0
  70. package/dist/gh-tool/read/store.d.ts.map +1 -0
  71. package/dist/gh-tool/{service.d.ts → read/transport.d.ts} +5 -3
  72. package/dist/gh-tool/read/transport.d.ts.map +1 -0
  73. package/dist/gh-tool/read/waits.d.ts +22 -0
  74. package/dist/gh-tool/read/waits.d.ts.map +1 -0
  75. package/dist/gh-tool/read/writes.d.ts +16 -0
  76. package/dist/gh-tool/read/writes.d.ts.map +1 -0
  77. package/dist/gh-tool/release.d.ts +10 -26
  78. package/dist/gh-tool/release.d.ts.map +1 -1
  79. package/dist/gh-tool/repo.d.ts +9 -4
  80. package/dist/gh-tool/repo.d.ts.map +1 -1
  81. package/dist/gh-tool/types.d.ts +1 -1
  82. package/dist/gh-tool/types.d.ts.map +1 -1
  83. package/dist/gh-tool/workflow.d.ts +43 -21
  84. package/dist/gh-tool/workflow.d.ts.map +1 -1
  85. package/dist/observability-tool/shared.d.ts.map +1 -1
  86. package/dist/shared/content-security.d.ts +2 -0
  87. package/dist/shared/content-security.d.ts.map +1 -1
  88. package/package.json +7 -6
  89. package/schemas/agent-tools.schema.json +5 -0
  90. package/src/api-tool/proxy-entry.ts +3 -1
  91. package/src/config/loader.ts +1 -0
  92. package/src/config/types.ts +1 -0
  93. package/src/credential-guard/index.ts +169 -1
  94. package/src/db-tool/service.ts +4 -1
  95. package/src/gh-tool/branch.ts +5 -8
  96. package/src/gh-tool/environment.ts +2 -1
  97. package/src/gh-tool/gist.ts +8 -35
  98. package/src/gh-tool/issue/commands.ts +12 -2
  99. package/src/gh-tool/issue/core.ts +58 -122
  100. package/src/gh-tool/issue/triage.ts +4 -26
  101. package/src/gh-tool/label.ts +57 -0
  102. package/src/gh-tool/main.ts +24 -4
  103. package/src/gh-tool/pr/commands.ts +17 -15
  104. package/src/gh-tool/pr/core.ts +486 -582
  105. package/src/gh-tool/pr/review.ts +106 -454
  106. package/src/gh-tool/pr/snapshot.ts +23 -0
  107. package/src/gh-tool/pr/stack-read.ts +1 -73
  108. package/src/gh-tool/pr/stack.ts +37 -36
  109. package/src/gh-tool/{api.ts → read/api.ts} +40 -7
  110. package/src/gh-tool/read/cache.ts +192 -0
  111. package/src/gh-tool/read/catalog.ts +310 -0
  112. package/src/gh-tool/read/classes.ts +31 -0
  113. package/src/gh-tool/read/commands.ts +25 -0
  114. package/src/gh-tool/read/commits.ts +28 -0
  115. package/src/gh-tool/read/gated.ts +56 -0
  116. package/src/gh-tool/read/index.ts +4 -0
  117. package/src/gh-tool/read/issues.ts +124 -0
  118. package/src/gh-tool/read/keys.ts +104 -0
  119. package/src/gh-tool/read/porcelain.ts +58 -0
  120. package/src/gh-tool/read/pulls.ts +266 -0
  121. package/src/gh-tool/read/reads.ts +306 -0
  122. package/src/gh-tool/read/rest.ts +111 -0
  123. package/src/gh-tool/read/review-guards.ts +61 -0
  124. package/src/gh-tool/read/review-model.ts +286 -0
  125. package/src/gh-tool/read/reviews.ts +114 -0
  126. package/src/gh-tool/read/stacks.ts +73 -0
  127. package/src/gh-tool/read/store.ts +504 -0
  128. package/src/gh-tool/{service.ts → read/transport.ts} +26 -6
  129. package/src/gh-tool/read/waits.ts +52 -0
  130. package/src/gh-tool/read/writes.ts +37 -0
  131. package/src/gh-tool/release.ts +15 -168
  132. package/src/gh-tool/repo.ts +26 -78
  133. package/src/gh-tool/types.ts +1 -1
  134. package/src/gh-tool/workflow.ts +123 -214
  135. package/src/observability-tool/shared.ts +2 -1
  136. package/src/shared/content-security.ts +10 -1
  137. package/src/shared/credential-patterns.ts +1 -1
  138. package/dist/gh-tool/api.d.ts.map +0 -1
  139. package/dist/gh-tool/pr/check-status-cache.d.ts +0 -6
  140. package/dist/gh-tool/pr/check-status-cache.d.ts.map +0 -1
  141. package/dist/gh-tool/service.d.ts.map +0 -1
  142. package/src/gh-tool/pr/check-status-cache.ts +0 -142
@@ -14,302 +14,52 @@ import type {
14
14
  import type { REVIEW_EVENTS } from "#gh/config";
15
15
 
16
16
  import { GitHubCommandError } from "#gh/errors";
17
- import { GitHubService } from "#gh/service";
17
+ import { GitHubService } from "#gh/read/transport";
18
+ import { repoSlug } from "#gh/read/keys";
19
+ import type { RepoRef, Tag } from "#gh/read/keys";
18
20
  import { validateOutboundText } from "#gh/text-input";
19
21
 
20
22
  import { viewPR } from "./core";
23
+ import { reads, resolveRepo, writes } from "#gh/read/index";
24
+ import { RESOLVE_THREAD_MUTATION, SUBMIT_REVIEW_MUTATION } from "#gh/read/review-model";
25
+ import type {
26
+ ThreadNode,
27
+ RawIssueComment,
28
+ EditableComment,
29
+ RequestedReviewerNode,
30
+ ResolveThreadResult,
31
+ PendingReviewsQueryResult,
32
+ PendingReviewContentQueryResult,
33
+ SubmitReviewResult,
34
+ } from "#gh/read/review-model";
35
+ const reviewTags = (
36
+ repo: RepoRef,
37
+ pr: number | null,
38
+ facet: "issue-comments" | "review-comments" | "reviews",
39
+ ): readonly Tag[] =>
40
+ pr === null
41
+ ? [`repo:${repoSlug(repo)}`]
42
+ : facet === "issue-comments"
43
+ ? [`issue-comments:${repoSlug(repo)}#${pr}`]
44
+ : facet === "reviews"
45
+ ? [
46
+ `pulls:${repoSlug(repo)}`,
47
+ `reviews:${repoSlug(repo)}#${pr}`,
48
+ `review-comments:${repoSlug(repo)}#${pr}`,
49
+ `pr:${repoSlug(repo)}#${pr}`,
50
+ `threads:${repoSlug(repo)}#${pr}`,
51
+ ]
52
+ : [`review-comments:${repoSlug(repo)}#${pr}`, `threads:${repoSlug(repo)}#${pr}`];
53
+
54
+ const fetchAllThreadNodes = (pr: number) =>
55
+ Effect.gen(function* () {
56
+ return yield* reads.reviewThreads(yield* resolveRepo(null), pr);
57
+ });
21
58
 
22
59
  // ---------------------------------------------------------------------------
23
60
  // GraphQL queries & mutations
24
61
  // ---------------------------------------------------------------------------
25
62
 
26
- const REVIEW_THREADS_QUERY = `
27
- query($owner: String!, $name: String!, $pr: Int!, $after: String) {
28
- repository(owner: $owner, name: $name) {
29
- pullRequest(number: $pr) {
30
- reviewThreads(first: 100, after: $after) {
31
- nodes {
32
- id
33
- isResolved
34
- comments(first: 100) {
35
- nodes {
36
- id
37
- databaseId
38
- path
39
- line
40
- body
41
- author { login }
42
- commit { oid }
43
- }
44
- pageInfo { hasNextPage endCursor }
45
- }
46
- }
47
- pageInfo {
48
- hasNextPage
49
- endCursor
50
- }
51
- }
52
- }
53
- }
54
- }
55
- `;
56
-
57
- const REVIEW_THREAD_COMMENTS_QUERY = `
58
- query($threadId: ID!, $after: String) {
59
- node(id: $threadId) {
60
- ... on PullRequestReviewThread {
61
- comments(first: 100, after: $after) {
62
- nodes {
63
- id
64
- databaseId
65
- path
66
- line
67
- body
68
- author { login }
69
- commit { oid }
70
- }
71
- pageInfo { hasNextPage endCursor }
72
- }
73
- }
74
- }
75
- }
76
- `;
77
-
78
- const RESOLVE_THREAD_MUTATION = `
79
- mutation($threadId: ID!) {
80
- resolveReviewThread(input: {threadId: $threadId}) {
81
- thread { id isResolved }
82
- }
83
- }
84
- `;
85
-
86
- const PENDING_REVIEWS_QUERY = `
87
- query($owner: String!, $name: String!, $pr: Int!, $after: String) {
88
- viewer { login }
89
- repository(owner: $owner, name: $name) {
90
- pullRequest(number: $pr) {
91
- reviews(first: 100, after: $after, states: [PENDING]) {
92
- nodes {
93
- id
94
- state
95
- author { login }
96
- }
97
- pageInfo { hasNextPage endCursor }
98
- }
99
- }
100
- }
101
- }
102
- `;
103
-
104
- const PENDING_REVIEW_CONTENT_QUERY = `
105
- query($reviewId: ID!, $after: String) {
106
- viewer { login }
107
- node(id: $reviewId) {
108
- ... on PullRequestReview {
109
- id
110
- state
111
- body
112
- author { login }
113
- pullRequest {
114
- number
115
- repository { nameWithOwner }
116
- }
117
- comments(first: 100, after: $after) {
118
- nodes { body }
119
- pageInfo { hasNextPage endCursor }
120
- }
121
- }
122
- }
123
- }
124
- `;
125
-
126
- const SUBMIT_REVIEW_MUTATION = `
127
- mutation($reviewId: ID!, $event: PullRequestReviewEvent!, $body: String) {
128
- submitPullRequestReview(input: { pullRequestReviewId: $reviewId, event: $event, body: $body }) {
129
- pullRequestReview { id state }
130
- }
131
- }
132
- `;
133
-
134
- const LAST_HUMAN_REVIEWER_QUERY = `
135
- query($owner: String!, $name: String!, $pr: Int!) {
136
- repository(owner: $owner, name: $name) {
137
- pullRequest(number: $pr) {
138
- reviewRequests(first: 100) {
139
- nodes {
140
- requestedReviewer {
141
- __typename
142
- ... on User { login }
143
- ... on Team { name }
144
- ... on Bot { login }
145
- }
146
- }
147
- }
148
- reviews(last: 100) {
149
- nodes {
150
- author { login }
151
- state
152
- submittedAt
153
- }
154
- }
155
- }
156
- }
157
- }
158
- `;
159
-
160
- // ---------------------------------------------------------------------------
161
- // Internal types
162
- // ---------------------------------------------------------------------------
163
-
164
- type ThreadNode = {
165
- id: string;
166
- isResolved: boolean;
167
- comments: {
168
- nodes: Array<{
169
- id: string;
170
- databaseId: number;
171
- path: string;
172
- line: number;
173
- body: string;
174
- author: { login: string };
175
- commit: { oid: string } | null;
176
- }>;
177
- pageInfo?: { hasNextPage: boolean; endCursor: string | null };
178
- };
179
- };
180
-
181
- type ThreadCommentsQueryResult = {
182
- node: { comments: ThreadNode["comments"] } | null;
183
- };
184
-
185
- type ThreadsQueryResult = {
186
- repository: {
187
- pullRequest: {
188
- reviewThreads: {
189
- nodes: ThreadNode[];
190
- pageInfo: {
191
- hasNextPage: boolean;
192
- endCursor: string | null;
193
- };
194
- };
195
- };
196
- };
197
- };
198
-
199
- type ResolveThreadResult = {
200
- resolveReviewThread: {
201
- thread: { id: string; isResolved: boolean };
202
- };
203
- };
204
-
205
- type PendingReviewsQueryResult = {
206
- viewer: { login: string };
207
- repository: {
208
- pullRequest: {
209
- reviews: {
210
- nodes: Array<{
211
- id: string;
212
- state: string;
213
- author: { login: string };
214
- }>;
215
- pageInfo: { hasNextPage: boolean; endCursor: string | null };
216
- };
217
- };
218
- };
219
- };
220
-
221
- type PendingReviewContentQueryResult = {
222
- viewer: { login: string };
223
- node: {
224
- id: string;
225
- state: string;
226
- body: string | null;
227
- author: { login: string };
228
- pullRequest: { number: number; repository: { nameWithOwner: string } };
229
- comments: {
230
- nodes: Array<{ body: string }>;
231
- pageInfo: { hasNextPage: boolean; endCursor: string | null };
232
- };
233
- } | null;
234
- };
235
-
236
- type SubmitReviewResult = {
237
- submitPullRequestReview: {
238
- pullRequestReview: { id: string; state: string };
239
- };
240
- };
241
-
242
- type RequestedReviewerNode = {
243
- __typename: string;
244
- login?: string;
245
- name?: string;
246
- } | null;
247
-
248
- type LastHumanReviewerQueryResult = {
249
- repository: {
250
- pullRequest: {
251
- reviewRequests: {
252
- nodes: Array<{ requestedReviewer: RequestedReviewerNode }>;
253
- };
254
- reviews: {
255
- nodes: Array<{
256
- author: { login: string } | null;
257
- state: string;
258
- submittedAt: string | null;
259
- }>;
260
- };
261
- };
262
- };
263
- };
264
-
265
- type RawReviewComment = {
266
- id: number;
267
- in_reply_to_id: number | null;
268
- user: { login: string };
269
- body: string;
270
- path: string;
271
- line: number;
272
- created_at: string;
273
- updated_at: string;
274
- pull_request_review_id?: number | null;
275
- commit_id?: string | null;
276
- };
277
-
278
- type RawIssueComment = {
279
- id: number;
280
- user: { login: string };
281
- body: string;
282
- created_at: string;
283
- html_url: string;
284
- };
285
-
286
- type RawPullRequestReview = {
287
- id: number;
288
- user: { login: string } | null;
289
- state: string;
290
- body: string | null;
291
- submitted_at: string | null;
292
- html_url: string;
293
- commit_id?: string | null;
294
- };
295
-
296
- type ReviewCommentById = {
297
- id: number;
298
- in_reply_to_id: number | null;
299
- pull_request_url: string;
300
- };
301
-
302
- type EditableComment = {
303
- id: number;
304
- user: { login: string };
305
- html_url: string;
306
- updated_at: string;
307
- pull_request_url?: string;
308
- issue_url?: string;
309
- };
310
-
311
- const REST_PAGE_SIZE = 100;
312
-
313
63
  const feedbackOrigin = (commitSha: string | null, currentHeadSha: string | null): FeedbackOrigin =>
314
64
  commitSha === null || currentHeadSha === null
315
65
  ? "unknown"
@@ -317,89 +67,6 @@ const feedbackOrigin = (commitSha: string | null, currentHeadSha: string | null)
317
67
  ? "current_head"
318
68
  : "pre_existing";
319
69
 
320
- const parseJson = <T>(
321
- stdout: string,
322
- command: string,
323
- parseFailurePrefix: string,
324
- ): Effect.Effect<T, GitHubCommandError> =>
325
- Effect.try({
326
- try: () => JSON.parse(stdout) as T,
327
- catch: (error) =>
328
- new GitHubCommandError({
329
- command,
330
- exitCode: 0,
331
- stderr: `${parseFailurePrefix}: ${error instanceof Error ? error.message : String(error)}`,
332
- message: `${parseFailurePrefix}: ${error instanceof Error ? error.message : String(error)}`,
333
- }),
334
- });
335
-
336
- const fetchAllRestPages = Effect.fn("pr.fetchAllRestPages")(function* <T>(
337
- endpoint: string,
338
- command: string,
339
- parseFailurePrefix: string,
340
- ) {
341
- const service = yield* GitHubService;
342
-
343
- const results: T[] = [];
344
- let page = 1;
345
-
346
- while (true) {
347
- const separator = endpoint.includes("?") ? "&" : "?";
348
- const result = yield* service.runGh([
349
- "api",
350
- `${endpoint}${separator}per_page=${REST_PAGE_SIZE}&page=${page}`,
351
- ]);
352
-
353
- const rawPage = yield* parseJson<T[]>(result.stdout, command, parseFailurePrefix);
354
- results.push(...rawPage);
355
-
356
- if (rawPage.length < REST_PAGE_SIZE) {
357
- return results;
358
- }
359
-
360
- page += 1;
361
- }
362
- });
363
-
364
- const fetchAllThreadNodes = Effect.fn("pr.fetchAllThreadNodes")(function* (pr: number) {
365
- const service = yield* GitHubService;
366
- const repoInfo = yield* service.getRepoInfo();
367
-
368
- const nodes: ThreadNode[] = [];
369
- let after: string | null = null;
370
-
371
- while (true) {
372
- const response = (yield* service.runGraphQL(REVIEW_THREADS_QUERY, {
373
- owner: repoInfo.owner,
374
- name: repoInfo.name,
375
- pr,
376
- after,
377
- })) as ThreadsQueryResult;
378
-
379
- const page = response.repository.pullRequest.reviewThreads;
380
- for (const node of page.nodes) {
381
- let commentsAfter = node.comments.pageInfo?.endCursor ?? null;
382
- while (node.comments.pageInfo?.hasNextPage && commentsAfter !== null) {
383
- const commentsResponse = (yield* service.runGraphQL(REVIEW_THREAD_COMMENTS_QUERY, {
384
- threadId: node.id,
385
- after: commentsAfter,
386
- })) as ThreadCommentsQueryResult;
387
- if (commentsResponse.node === null) break;
388
- node.comments.nodes.push(...commentsResponse.node.comments.nodes);
389
- node.comments.pageInfo = commentsResponse.node.comments.pageInfo;
390
- commentsAfter = commentsResponse.node.comments.pageInfo?.endCursor ?? null;
391
- }
392
- nodes.push(node);
393
- }
394
-
395
- if (!page.pageInfo.hasNextPage || page.pageInfo.endCursor === null) {
396
- return nodes;
397
- }
398
-
399
- after = page.pageInfo.endCursor;
400
- }
401
- });
402
-
403
70
  const enrichThreads = (
404
71
  threads: ThreadNode[],
405
72
  reviewComments: ReviewComment[],
@@ -464,7 +131,7 @@ const enrichThreads = (
464
131
  const dedupedByKey = new Map<string, number>();
465
132
  const deduped: ReviewThread[] = [];
466
133
  for (const thread of mapped) {
467
- const key = `${thread.path}${thread.line}${thread.body.trim()}`;
134
+ const key = `${thread.path}\u0000${thread.line}\u0000${thread.body.trim()}`;
468
135
  const existingIndex = dedupedByKey.get(key);
469
136
  if (existingIndex === undefined) {
470
137
  dedupedByKey.set(key, deduped.length);
@@ -546,11 +213,7 @@ export const fetchComments = Effect.fn("pr.fetchComments")(function* (
546
213
 
547
214
  const resolvedPr = pr ?? (yield* viewPR(null)).number;
548
215
 
549
- const raw = yield* fetchAllRestPages<RawReviewComment>(
550
- `repos/${repoInfo.owner}/${repoInfo.name}/pulls/${resolvedPr}/comments`,
551
- "gh-tool pr comments",
552
- "Failed to parse response",
553
- );
216
+ const raw = yield* reads.prComments({ owner: repoInfo.owner, name: repoInfo.name }, resolvedPr);
554
217
 
555
218
  const comments: ReviewComment[] = raw.map((c) => ({
556
219
  id: c.id,
@@ -589,10 +252,9 @@ export const fetchIssueComments = Effect.fn("pr.fetchIssueComments")(function* (
589
252
 
590
253
  const resolvedPr = pr ?? (yield* viewPR(null)).number;
591
254
 
592
- const raw = yield* fetchAllRestPages<RawIssueComment>(
593
- `repos/${repoInfo.owner}/${repoInfo.name}/issues/${resolvedPr}/comments`,
594
- "gh-tool pr issue-comments",
595
- "Failed to parse response",
255
+ const raw = yield* reads.issueComments(
256
+ { owner: repoInfo.owner, name: repoInfo.name },
257
+ resolvedPr,
596
258
  );
597
259
 
598
260
  let comments = raw.map(mapRawIssueComment);
@@ -632,11 +294,7 @@ export const fetchReviews = Effect.fn("pr.fetchReviews")(function* (
632
294
 
633
295
  const resolvedPr = pr ?? (yield* viewPR(null)).number;
634
296
 
635
- const raw = yield* fetchAllRestPages<RawPullRequestReview>(
636
- `repos/${repoInfo.owner}/${repoInfo.name}/pulls/${resolvedPr}/reviews`,
637
- "gh-tool pr reviews",
638
- "Failed to parse response",
639
- );
297
+ const raw = yield* reads.prReviews({ owner: repoInfo.owner, name: repoInfo.name }, resolvedPr);
640
298
 
641
299
  let reviews: PullRequestReview[] = raw
642
300
  .map((review) => ({
@@ -727,14 +385,17 @@ export const postIssueComment = Effect.fn("pr.postIssueComment")(function* (
727
385
  );
728
386
  }
729
387
 
730
- const result = yield* service.runGh([
731
- "api",
732
- "-X",
733
- "POST",
734
- `repos/${repoInfo.owner}/${repoInfo.name}/issues/${resolvedPr}/comments`,
735
- "-f",
736
- `body=${body}`,
737
- ]);
388
+ const result = yield* writes.run(
389
+ [
390
+ "api",
391
+ "-X",
392
+ "POST",
393
+ `repos/${repoInfo.owner}/${repoInfo.name}/issues/${resolvedPr}/comments`,
394
+ "-f",
395
+ `body=${body}`,
396
+ ],
397
+ reviewTags(repoInfo, resolvedPr, "issue-comments"),
398
+ );
738
399
 
739
400
  const rawComment = yield* Effect.try({
740
401
  try: () => JSON.parse(result.stdout) as RawIssueComment,
@@ -796,18 +457,7 @@ export const fetchDiscussionSummary = Effect.fn("pr.fetchDiscussionSummary")(fun
796
457
  };
797
458
  });
798
459
 
799
- const fetchReviewCommentById = Effect.fn("pr.fetchReviewCommentById")(function* (
800
- commentId: number,
801
- ) {
802
- const service = yield* GitHubService;
803
-
804
- const comment = yield* service.runGhJson<ReviewCommentById>([
805
- "api",
806
- `repos/{owner}/{repo}/pulls/comments/${commentId}`,
807
- ]);
808
-
809
- return comment;
810
- });
460
+ const fetchReviewCommentById = reads.reviewGuards.commentById;
811
461
 
812
462
  export const editComment = Effect.fn("pr.editComment")(function* (
813
463
  commentId: number,
@@ -846,8 +496,8 @@ export const editComment = Effect.fn("pr.editComment")(function* (
846
496
  }
847
497
 
848
498
  const endpoint = `repos/${repoInfo.owner}/${repoInfo.name}/${kind === "review" ? "pulls" : "issues"}/comments/${commentId}`;
849
- const target = yield* service.runGhJson<EditableComment>(["api", endpoint]);
850
- const viewer = yield* service.runGhJson<{ login: string }>(["api", "user"]);
499
+ const target = yield* reads.reviewGuards.editableComment(repoInfo, kind, commentId);
500
+ const viewer = yield* reads.reviewGuards.viewer();
851
501
  if (target.user.login.toLowerCase() !== viewer.login.toLowerCase()) {
852
502
  return yield* Effect.fail(
853
503
  new GitHubCommandError({
@@ -873,10 +523,7 @@ export const editComment = Effect.fn("pr.editComment")(function* (
873
523
  );
874
524
  }
875
525
  if (kind === "issue" && pr !== null) {
876
- const pullRequest = yield* service.runGhJson<{ number: number }>([
877
- "api",
878
- `repos/${repoInfo.owner}/${repoInfo.name}/pulls/${pr}`,
879
- ]);
526
+ const pullRequest = yield* reads.reviewGuards.pullNumber(repoInfo, pr);
880
527
  if (pullRequest.number !== pr) {
881
528
  return yield* Effect.fail(
882
529
  new GitHubCommandError({
@@ -890,14 +537,10 @@ export const editComment = Effect.fn("pr.editComment")(function* (
890
537
  }
891
538
  }
892
539
 
893
- const updated = yield* service.runGhJson<EditableComment>([
894
- "api",
895
- "-X",
896
- "PATCH",
897
- endpoint,
898
- "-f",
899
- `body=${body}`,
900
- ]);
540
+ const updated = yield* writes.json<EditableComment>(
541
+ ["api", "-X", "PATCH", endpoint, "-f", `body=${body}`],
542
+ reviewTags(repoInfo, pr, kind === "review" ? "review-comments" : "issue-comments"),
543
+ );
901
544
  return {
902
545
  commentId: updated.id,
903
546
  url: updated.html_url,
@@ -948,15 +591,18 @@ export const replyToComment = Effect.fn("pr.replyToComment")(function* (
948
591
  );
949
592
  }
950
593
 
951
- const result = yield* service
952
- .runGh([
953
- "api",
954
- "-X",
955
- "POST",
956
- `repos/${repoInfo.owner}/${repoInfo.name}/pulls/${resolvedPr}/comments/${rootCommentId}/replies`,
957
- "-f",
958
- `body=${body}`,
959
- ])
594
+ const result = yield* writes
595
+ .run(
596
+ [
597
+ "api",
598
+ "-X",
599
+ "POST",
600
+ `repos/${repoInfo.owner}/${repoInfo.name}/pulls/${resolvedPr}/comments/${rootCommentId}/replies`,
601
+ "-f",
602
+ `body=${body}`,
603
+ ],
604
+ reviewTags(repoInfo, resolvedPr, "review-comments"),
605
+ )
960
606
  .pipe(
961
607
  Effect.catchTag("GitHubCommandError", (error) => {
962
608
  if (error.stderr.includes("can only have one pending review per pull request")) {
@@ -1105,11 +751,15 @@ export const replyAndResolveComment = Effect.fn("pr.replyAndResolveComment")(fun
1105
751
  * Resolve a review thread via GraphQL mutation.
1106
752
  */
1107
753
  export const resolveThread = Effect.fn("pr.resolveThread")(function* (threadId: string) {
1108
- const service = yield* GitHubService;
754
+ const repo = yield* resolveRepo(null);
1109
755
 
1110
- const response = (yield* service.runGraphQL(RESOLVE_THREAD_MUTATION, {
1111
- threadId,
1112
- })) as ResolveThreadResult;
756
+ const response = (yield* writes.graphql(
757
+ RESOLVE_THREAD_MUTATION,
758
+ {
759
+ threadId,
760
+ },
761
+ [`repo:${repoSlug(repo)}`],
762
+ )) as ResolveThreadResult;
1113
763
 
1114
764
  return {
1115
765
  resolved: response.resolveReviewThread.thread.isResolved,
@@ -1195,11 +845,11 @@ export const createReview = Effect.fn("pr.createReview")(function* (opts: {
1195
845
  args.push("-f", `body=${opts.body}`);
1196
846
  }
1197
847
 
1198
- const result = yield* service.runGhJson<{
848
+ const result = yield* writes.json<{
1199
849
  id: number;
1200
850
  state: string;
1201
851
  html_url: string;
1202
- }>(args);
852
+ }>(args, reviewTags(repoInfo, resolvedPr, "reviews"));
1203
853
 
1204
854
  return {
1205
855
  submitted: true as const,
@@ -1235,12 +885,11 @@ export const submitPendingReview = Effect.fn("pr.submitPendingReview")(function*
1235
885
  const seenPendingCursors = new Set<string>();
1236
886
 
1237
887
  while (hasNextPage) {
1238
- const pending = (yield* service.runGraphQL(PENDING_REVIEWS_QUERY, {
1239
- owner: repoInfo.owner,
1240
- name: repoInfo.name,
1241
- pr: resolvedPr,
888
+ const pending = (yield* reads.reviewGuards.pendingReviews(
889
+ repoInfo,
890
+ resolvedPr,
1242
891
  after,
1243
- })) as PendingReviewsQueryResult;
892
+ )) as PendingReviewsQueryResult;
1244
893
 
1245
894
  if (
1246
895
  typeof pending.viewer?.login !== "string" ||
@@ -1326,10 +975,10 @@ export const submitPendingReview = Effect.fn("pr.submitPendingReview")(function*
1326
975
  let commentsHaveNextPage = true;
1327
976
  const seenCommentCursors = new Set<string>();
1328
977
  while (commentsHaveNextPage) {
1329
- const pendingReview = (yield* service.runGraphQL(PENDING_REVIEW_CONTENT_QUERY, {
1330
- reviewId: targetReviewId,
1331
- after: afterComment,
1332
- })) as PendingReviewContentQueryResult;
978
+ const pendingReview = (yield* reads.reviewGuards.pendingReviewContent(
979
+ targetReviewId,
980
+ afterComment,
981
+ )) as PendingReviewContentQueryResult;
1333
982
 
1334
983
  const review = pendingReview?.node;
1335
984
  if (
@@ -1400,11 +1049,15 @@ export const submitPendingReview = Effect.fn("pr.submitPendingReview")(function*
1400
1049
  if (commentsHaveNextPage && afterComment !== null) seenCommentCursors.add(afterComment);
1401
1050
  }
1402
1051
 
1403
- const result = (yield* service.runGraphQL(SUBMIT_REVIEW_MUTATION, {
1404
- reviewId: targetReviewId,
1405
- event: REVIEW_EVENT_API[event],
1406
- body: body ?? "",
1407
- })) as SubmitReviewResult;
1052
+ const result = (yield* writes.graphql(
1053
+ SUBMIT_REVIEW_MUTATION,
1054
+ {
1055
+ reviewId: targetReviewId,
1056
+ event: REVIEW_EVENT_API[event],
1057
+ body: body ?? "",
1058
+ },
1059
+ reviewTags(repoInfo, resolvedPr, "reviews"),
1060
+ )) as SubmitReviewResult;
1408
1061
 
1409
1062
  return {
1410
1063
  submitted: true as const,
@@ -1434,11 +1087,10 @@ export const fetchLastHumanReviewer = Effect.fn("pr.fetchLastHumanReviewer")(fun
1434
1087
 
1435
1088
  const resolvedPr = pr ?? (yield* viewPR(null)).number;
1436
1089
 
1437
- const response = (yield* service.runGraphQL(LAST_HUMAN_REVIEWER_QUERY, {
1438
- owner: repoInfo.owner,
1439
- name: repoInfo.name,
1440
- pr: resolvedPr,
1441
- })) as LastHumanReviewerQueryResult;
1090
+ const response = yield* reads.lastHumanReviewer(
1091
+ { owner: repoInfo.owner, name: repoInfo.name },
1092
+ resolvedPr,
1093
+ );
1442
1094
 
1443
1095
  const prNode = response.repository.pullRequest;
1444
1096