@blogic-cz/agent-tools 0.8.4 → 0.8.5
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.
- package/package.json +1 -1
- package/src/gh-tool/pr/commands.ts +17 -6
- package/src/gh-tool/pr/review.ts +67 -19
- package/src/gh-tool/types.ts +6 -0
package/package.json
CHANGED
|
@@ -228,14 +228,24 @@ export const prThreadsCommand = Command.make(
|
|
|
228
228
|
Flag.withDescription("Only show unresolved threads"),
|
|
229
229
|
Flag.withDefault(true),
|
|
230
230
|
),
|
|
231
|
+
visibleOpenOnly: Flag.boolean("visible-open-only").pipe(
|
|
232
|
+
Flag.withDescription(
|
|
233
|
+
"Show threads that still look open to humans: unresolved threads plus resolved threads with no reply",
|
|
234
|
+
),
|
|
235
|
+
Flag.withDefault(false),
|
|
236
|
+
),
|
|
231
237
|
},
|
|
232
|
-
({ format, pr, unresolvedOnly }) =>
|
|
238
|
+
({ format, pr, unresolvedOnly, visibleOpenOnly }) =>
|
|
233
239
|
Effect.gen(function* () {
|
|
234
240
|
const prNumber = Option.getOrNull(pr);
|
|
235
|
-
const threads = yield* fetchThreads(prNumber, unresolvedOnly);
|
|
241
|
+
const threads = yield* fetchThreads(prNumber, unresolvedOnly, visibleOpenOnly);
|
|
236
242
|
yield* logFormatted(threads, format);
|
|
237
243
|
}),
|
|
238
|
-
).pipe(
|
|
244
|
+
).pipe(
|
|
245
|
+
Command.withDescription(
|
|
246
|
+
"Fetch review threads for a PR (unresolved by default, or use --visible-open-only for reply-aware human-visible open items)",
|
|
247
|
+
),
|
|
248
|
+
);
|
|
239
249
|
|
|
240
250
|
export const prCommentsCommand = Command.make(
|
|
241
251
|
"comments",
|
|
@@ -443,17 +453,18 @@ export const prReviewTriageCommand = Command.make(
|
|
|
443
453
|
({ format, pr }) =>
|
|
444
454
|
Effect.gen(function* () {
|
|
445
455
|
const prNumber = Option.getOrNull(pr);
|
|
446
|
-
const [info,
|
|
456
|
+
const [info, unresolvedThreads, visibleOpenThreads, summary, checks] = yield* Effect.all([
|
|
447
457
|
viewPR(prNumber),
|
|
448
458
|
fetchThreads(prNumber, true),
|
|
459
|
+
fetchThreads(prNumber, false, true),
|
|
449
460
|
fetchDiscussionSummary(prNumber),
|
|
450
461
|
fetchChecks(prNumber, false, false, 0),
|
|
451
462
|
]);
|
|
452
|
-
yield* logFormatted({ info, unresolvedThreads
|
|
463
|
+
yield* logFormatted({ info, unresolvedThreads, visibleOpenThreads, summary, checks }, format);
|
|
453
464
|
}),
|
|
454
465
|
).pipe(
|
|
455
466
|
Command.withDescription(
|
|
456
|
-
"Composite: PR info + unresolved threads + discussion summary + checks status in one call",
|
|
467
|
+
"Composite: PR info + unresolved threads + visible-open threads + discussion summary + checks status in one call",
|
|
457
468
|
),
|
|
458
469
|
);
|
|
459
470
|
|
package/src/gh-tool/pr/review.ts
CHANGED
|
@@ -236,6 +236,66 @@ const fetchAllThreadNodes = Effect.fn("pr.fetchAllThreadNodes")(function* (pr: n
|
|
|
236
236
|
}
|
|
237
237
|
});
|
|
238
238
|
|
|
239
|
+
const enrichThreads = (threads: ThreadNode[], reviewComments: ReviewComment[]): ReviewThread[] => {
|
|
240
|
+
const repliesByRootCommentId = new Map<number, ReviewComment[]>();
|
|
241
|
+
|
|
242
|
+
for (const comment of reviewComments) {
|
|
243
|
+
if (comment.inReplyToId === null) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const replies = repliesByRootCommentId.get(comment.inReplyToId) ?? [];
|
|
248
|
+
replies.push(comment);
|
|
249
|
+
repliesByRootCommentId.set(comment.inReplyToId, replies);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return threads
|
|
253
|
+
.map((node) => {
|
|
254
|
+
const comment = node.comments.nodes[0];
|
|
255
|
+
if (!comment) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const replies = repliesByRootCommentId.get(comment.databaseId) ?? [];
|
|
260
|
+
const lastReply = replies.reduce<ReviewComment | null>((latest, current) => {
|
|
261
|
+
if (latest === null) {
|
|
262
|
+
return current;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return new Date(current.createdAt).getTime() > new Date(latest.createdAt).getTime()
|
|
266
|
+
? current
|
|
267
|
+
: latest;
|
|
268
|
+
}, null);
|
|
269
|
+
const hasReply = replies.length > 0;
|
|
270
|
+
const needsHumanReply = !hasReply;
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
threadId: node.id,
|
|
274
|
+
commentId: comment.databaseId,
|
|
275
|
+
path: comment.path,
|
|
276
|
+
line: comment.line,
|
|
277
|
+
body: comment.body,
|
|
278
|
+
isResolved: node.isResolved,
|
|
279
|
+
hasReply,
|
|
280
|
+
replyCount: replies.length,
|
|
281
|
+
needsHumanReply,
|
|
282
|
+
isVisibleOpen: !node.isResolved || needsHumanReply,
|
|
283
|
+
lastReplyAuthor: lastReply?.author ?? null,
|
|
284
|
+
lastReplyAt: lastReply?.createdAt ?? null,
|
|
285
|
+
};
|
|
286
|
+
})
|
|
287
|
+
.filter((thread): thread is ReviewThread => thread !== null);
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const fetchThreadState = Effect.fn("pr.fetchThreadState")(function* (pr: number) {
|
|
291
|
+
const [threads, reviewComments] = yield* Effect.all([
|
|
292
|
+
fetchAllThreadNodes(pr),
|
|
293
|
+
fetchComments(pr, null),
|
|
294
|
+
]);
|
|
295
|
+
|
|
296
|
+
return enrichThreads(threads, reviewComments);
|
|
297
|
+
});
|
|
298
|
+
|
|
239
299
|
// ---------------------------------------------------------------------------
|
|
240
300
|
// Handlers
|
|
241
301
|
// ---------------------------------------------------------------------------
|
|
@@ -255,29 +315,16 @@ const mapRawIssueComment = (comment: RawIssueComment): IssueComment => ({
|
|
|
255
315
|
export const fetchThreads = Effect.fn("pr.fetchThreads")(function* (
|
|
256
316
|
pr: number | null,
|
|
257
317
|
unresolvedOnly: boolean,
|
|
318
|
+
visibleOpenOnly = false,
|
|
258
319
|
) {
|
|
259
320
|
const resolvedPr = pr ?? (yield* viewPR(null)).number;
|
|
260
|
-
const threads = yield*
|
|
261
|
-
|
|
262
|
-
const mapped: ReviewThread[] = threads
|
|
263
|
-
.map((node) => {
|
|
264
|
-
const comment = node.comments.nodes[0];
|
|
265
|
-
if (!comment) {
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
321
|
+
const threads = yield* fetchThreadState(resolvedPr);
|
|
268
322
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
path: comment.path,
|
|
273
|
-
line: comment.line,
|
|
274
|
-
body: comment.body,
|
|
275
|
-
isResolved: node.isResolved,
|
|
276
|
-
};
|
|
277
|
-
})
|
|
278
|
-
.filter((thread): thread is ReviewThread => thread !== null);
|
|
323
|
+
if (visibleOpenOnly) {
|
|
324
|
+
return threads.filter((thread) => thread.isVisibleOpen);
|
|
325
|
+
}
|
|
279
326
|
|
|
280
|
-
return unresolvedOnly ?
|
|
327
|
+
return unresolvedOnly ? threads.filter((thread) => !thread.isResolved) : threads;
|
|
281
328
|
});
|
|
282
329
|
|
|
283
330
|
/**
|
|
@@ -453,6 +500,7 @@ export const fetchDiscussionSummary = Effect.fn("pr.fetchDiscussionSummary")(fun
|
|
|
453
500
|
latestIssueComment,
|
|
454
501
|
reviewCommentsCount: reviewComments.length,
|
|
455
502
|
reviewThreadsCount: threads.length,
|
|
503
|
+
visibleOpenReviewThreadsCount: threads.filter((thread) => thread.isVisibleOpen).length,
|
|
456
504
|
repliedReviewThreadsCount: threads.length - unrepliedReviewThreadsCount,
|
|
457
505
|
unrepliedReviewThreadsCount,
|
|
458
506
|
resolvedUnrepliedReviewThreadsCount: threads.filter(
|
package/src/gh-tool/types.ts
CHANGED
|
@@ -20,6 +20,12 @@ export type ReviewThread = {
|
|
|
20
20
|
line: number;
|
|
21
21
|
body: string;
|
|
22
22
|
isResolved: boolean;
|
|
23
|
+
hasReply: boolean;
|
|
24
|
+
replyCount: number;
|
|
25
|
+
needsHumanReply: boolean;
|
|
26
|
+
isVisibleOpen: boolean;
|
|
27
|
+
lastReplyAuthor: string | null;
|
|
28
|
+
lastReplyAt: string | null;
|
|
23
29
|
};
|
|
24
30
|
|
|
25
31
|
export type ReviewComment = {
|