@blogic-cz/agent-tools 0.14.48 → 0.14.50
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/index.ts +4 -0
- package/src/gh-tool/pr/commands.ts +89 -8
- package/src/gh-tool/pr/index.ts +2 -0
- package/src/gh-tool/pr/review.ts +77 -0
- package/src/gh-tool/types.ts +9 -0
- package/src/gh-tool/workflow.ts +42 -15
package/package.json
CHANGED
package/src/gh-tool/index.ts
CHANGED
|
@@ -31,8 +31,10 @@ import {
|
|
|
31
31
|
prIssueCommentsLatestCommand,
|
|
32
32
|
prCommentCommand,
|
|
33
33
|
prDiscussionSummaryCommand,
|
|
34
|
+
prFeedbackCommand,
|
|
34
35
|
prReplyCommand,
|
|
35
36
|
prResolveCommand,
|
|
37
|
+
prReviewsCommand,
|
|
36
38
|
prSubmitReviewCommand,
|
|
37
39
|
prChecksCommand,
|
|
38
40
|
prChecksFailedCommand,
|
|
@@ -79,6 +81,8 @@ const prCommand = Command.make("pr", {}).pipe(
|
|
|
79
81
|
prWaitMergeableCommand,
|
|
80
82
|
prThreadsCommand,
|
|
81
83
|
prCommentsCommand,
|
|
84
|
+
prReviewsCommand,
|
|
85
|
+
prFeedbackCommand,
|
|
82
86
|
prIssueCommentsCommand,
|
|
83
87
|
prIssueCommentsLatestCommand,
|
|
84
88
|
prCommentCommand,
|
|
@@ -44,8 +44,10 @@ import {
|
|
|
44
44
|
import {
|
|
45
45
|
fetchComments,
|
|
46
46
|
fetchDiscussionSummary,
|
|
47
|
+
fetchFeedback,
|
|
47
48
|
fetchIssueComments,
|
|
48
49
|
fetchLatestIssueComment,
|
|
50
|
+
fetchReviews,
|
|
49
51
|
fetchThreads,
|
|
50
52
|
postIssueComment,
|
|
51
53
|
replyToComment,
|
|
@@ -101,13 +103,16 @@ export const parsePrNumbers = (input: string): readonly number[] =>
|
|
|
101
103
|
export const fetchReviewTriage = Effect.fn("pr.fetchReviewTriage")(function* (
|
|
102
104
|
prNumber: number | null,
|
|
103
105
|
) {
|
|
104
|
-
const [info, unresolvedThreads, visibleOpenThreads, summary, checks] = yield* Effect.all(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
const [info, unresolvedThreads, visibleOpenThreads, summary, checks, reviews] = yield* Effect.all(
|
|
107
|
+
[
|
|
108
|
+
viewPR(prNumber),
|
|
109
|
+
fetchThreads(prNumber, true),
|
|
110
|
+
fetchThreads(prNumber, false, true),
|
|
111
|
+
fetchDiscussionSummary(prNumber),
|
|
112
|
+
fetchChecks(prNumber, false, false, 0),
|
|
113
|
+
fetchReviews(prNumber, null, null, null),
|
|
114
|
+
],
|
|
115
|
+
);
|
|
111
116
|
const classification = classifyReviewTriage(summary, checks);
|
|
112
117
|
|
|
113
118
|
// Single merge-readiness verdict so agents stop re-stitching mergeable + checks + threads +
|
|
@@ -127,7 +132,16 @@ export const fetchReviewTriage = Effect.fn("pr.fetchReviewTriage")(function* (
|
|
|
127
132
|
blocking,
|
|
128
133
|
};
|
|
129
134
|
|
|
130
|
-
return {
|
|
135
|
+
return {
|
|
136
|
+
ready,
|
|
137
|
+
classification,
|
|
138
|
+
info,
|
|
139
|
+
unresolvedThreads,
|
|
140
|
+
visibleOpenThreads,
|
|
141
|
+
summary,
|
|
142
|
+
checks,
|
|
143
|
+
reviews,
|
|
144
|
+
};
|
|
131
145
|
});
|
|
132
146
|
|
|
133
147
|
export const prViewCommand = Command.make(
|
|
@@ -606,6 +620,73 @@ export const prCommentsCommand = Command.make(
|
|
|
606
620
|
),
|
|
607
621
|
).pipe(Command.withDescription("Fetch review comments for a PR (optionally filter by --since)"));
|
|
608
622
|
|
|
623
|
+
export const prReviewsCommand = Command.make(
|
|
624
|
+
"reviews",
|
|
625
|
+
{
|
|
626
|
+
author: Flag.string("author").pipe(
|
|
627
|
+
Flag.withDescription("Filter by author login substring"),
|
|
628
|
+
Flag.optional,
|
|
629
|
+
),
|
|
630
|
+
bodyContains: Flag.string("body-contains").pipe(
|
|
631
|
+
Flag.withDescription("Filter reviews by body substring"),
|
|
632
|
+
Flag.optional,
|
|
633
|
+
),
|
|
634
|
+
format: formatOption,
|
|
635
|
+
pr: Flag.integer("pr").pipe(
|
|
636
|
+
Flag.withDescription("PR number (default: current branch PR)"),
|
|
637
|
+
Flag.optional,
|
|
638
|
+
),
|
|
639
|
+
repo: repoOption,
|
|
640
|
+
state: Flag.string("state").pipe(
|
|
641
|
+
Flag.withDescription(
|
|
642
|
+
"Filter by review state (APPROVED, CHANGES_REQUESTED, COMMENTED, DISMISSED)",
|
|
643
|
+
),
|
|
644
|
+
Flag.optional,
|
|
645
|
+
),
|
|
646
|
+
},
|
|
647
|
+
({ author, bodyContains, format, pr, repo, state }) =>
|
|
648
|
+
withRepo(
|
|
649
|
+
repo,
|
|
650
|
+
Effect.gen(function* () {
|
|
651
|
+
const reviews = yield* fetchReviews(
|
|
652
|
+
Option.getOrNull(pr),
|
|
653
|
+
Option.getOrNull(author),
|
|
654
|
+
Option.getOrNull(bodyContains),
|
|
655
|
+
Option.getOrNull(state),
|
|
656
|
+
);
|
|
657
|
+
yield* logFormatted(reviews, format);
|
|
658
|
+
}),
|
|
659
|
+
),
|
|
660
|
+
).pipe(
|
|
661
|
+
Command.withDescription(
|
|
662
|
+
"Fetch submitted review summaries (state + body) for a PR — the review bodies that comments/issue-comments do not expose",
|
|
663
|
+
),
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
export const prFeedbackCommand = Command.make(
|
|
667
|
+
"feedback",
|
|
668
|
+
{
|
|
669
|
+
format: formatOption,
|
|
670
|
+
pr: Flag.integer("pr").pipe(
|
|
671
|
+
Flag.withDescription("PR number (default: current branch PR)"),
|
|
672
|
+
Flag.optional,
|
|
673
|
+
),
|
|
674
|
+
repo: repoOption,
|
|
675
|
+
},
|
|
676
|
+
({ format, pr, repo }) =>
|
|
677
|
+
withRepo(
|
|
678
|
+
repo,
|
|
679
|
+
Effect.gen(function* () {
|
|
680
|
+
const feedback = yield* fetchFeedback(Option.getOrNull(pr));
|
|
681
|
+
yield* logFormatted(feedback, format);
|
|
682
|
+
}),
|
|
683
|
+
),
|
|
684
|
+
).pipe(
|
|
685
|
+
Command.withDescription(
|
|
686
|
+
"Full review-response inventory in one call: submitted reviews + threads (with state) + inline comments + issue comments",
|
|
687
|
+
),
|
|
688
|
+
);
|
|
689
|
+
|
|
609
690
|
export const prIssueCommentsCommand = Command.make(
|
|
610
691
|
"issue-comments",
|
|
611
692
|
{
|
package/src/gh-tool/pr/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export {
|
|
|
7
7
|
prCreateCommand,
|
|
8
8
|
prDiscussionSummaryCommand,
|
|
9
9
|
prEditCommand,
|
|
10
|
+
prFeedbackCommand,
|
|
10
11
|
prIssueCommentsCommand,
|
|
11
12
|
prIssueCommentsLatestCommand,
|
|
12
13
|
prListCommand,
|
|
@@ -15,6 +16,7 @@ export {
|
|
|
15
16
|
prRerunChecksCommand,
|
|
16
17
|
prReplyAndResolveCommand,
|
|
17
18
|
prResolveCommand,
|
|
19
|
+
prReviewsCommand,
|
|
18
20
|
prStatusCommand,
|
|
19
21
|
prSubmitReviewCommand,
|
|
20
22
|
prThreadsCommand,
|
package/src/gh-tool/pr/review.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
IssueComment,
|
|
6
6
|
IssueCommentId,
|
|
7
7
|
IsoTimestamp,
|
|
8
|
+
PullRequestReview,
|
|
8
9
|
ReviewComment,
|
|
9
10
|
ReviewThread,
|
|
10
11
|
} from "#gh/types";
|
|
@@ -158,6 +159,15 @@ type RawIssueComment = {
|
|
|
158
159
|
html_url: string;
|
|
159
160
|
};
|
|
160
161
|
|
|
162
|
+
type RawPullRequestReview = {
|
|
163
|
+
id: number;
|
|
164
|
+
user: { login: string } | null;
|
|
165
|
+
state: string;
|
|
166
|
+
body: string | null;
|
|
167
|
+
submitted_at: string | null;
|
|
168
|
+
html_url: string;
|
|
169
|
+
};
|
|
170
|
+
|
|
161
171
|
type ReviewCommentById = {
|
|
162
172
|
id: number;
|
|
163
173
|
in_reply_to_id: number | null;
|
|
@@ -405,6 +415,73 @@ export const fetchIssueComments = Effect.fn("pr.fetchIssueComments")(function* (
|
|
|
405
415
|
return comments;
|
|
406
416
|
});
|
|
407
417
|
|
|
418
|
+
/**
|
|
419
|
+
* Fetch submitted review summaries (state + top-level body) for a PR via REST API.
|
|
420
|
+
* Complements `comments` (inline review comments) and `issue-comments` (discussion),
|
|
421
|
+
* which never expose the submitted review body. Optional author/state/body filters.
|
|
422
|
+
*/
|
|
423
|
+
export const fetchReviews = Effect.fn("pr.fetchReviews")(function* (
|
|
424
|
+
pr: number | null,
|
|
425
|
+
author: string | null,
|
|
426
|
+
bodyContains: string | null,
|
|
427
|
+
state: string | null,
|
|
428
|
+
) {
|
|
429
|
+
const service = yield* GitHubService;
|
|
430
|
+
const repoInfo = yield* service.getRepoInfo();
|
|
431
|
+
|
|
432
|
+
const resolvedPr = pr ?? (yield* viewPR(null)).number;
|
|
433
|
+
|
|
434
|
+
const raw = yield* fetchAllRestPages<RawPullRequestReview>(
|
|
435
|
+
`repos/${repoInfo.owner}/${repoInfo.name}/pulls/${resolvedPr}/reviews`,
|
|
436
|
+
"gh-tool pr reviews",
|
|
437
|
+
"Failed to parse response",
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
let reviews: PullRequestReview[] = raw.map((review) => ({
|
|
441
|
+
id: review.id,
|
|
442
|
+
author: review.user?.login ?? "unknown",
|
|
443
|
+
state: review.state,
|
|
444
|
+
body: review.body ?? "",
|
|
445
|
+
submittedAt: (review.submitted_at ?? null) as IsoTimestamp | null,
|
|
446
|
+
url: review.html_url,
|
|
447
|
+
}));
|
|
448
|
+
|
|
449
|
+
if (author !== null) {
|
|
450
|
+
const authorFilter = author.toLowerCase();
|
|
451
|
+
reviews = reviews.filter((review) => review.author.toLowerCase().includes(authorFilter));
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (state !== null) {
|
|
455
|
+
const stateFilter = state.toLowerCase();
|
|
456
|
+
reviews = reviews.filter((review) => review.state.toLowerCase() === stateFilter);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (bodyContains !== null) {
|
|
460
|
+
const bodyFilter = bodyContains.toLowerCase();
|
|
461
|
+
reviews = reviews.filter((review) => review.body.toLowerCase().includes(bodyFilter));
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return reviews;
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Full review-response inventory for a PR in one call: submitted review summaries,
|
|
469
|
+
* all review threads with resolution state, inline review comments, and issue
|
|
470
|
+
* (discussion) comments. Collapses the four separate fetches agents otherwise stitch.
|
|
471
|
+
*/
|
|
472
|
+
export const fetchFeedback = Effect.fn("pr.fetchFeedback")(function* (pr: number | null) {
|
|
473
|
+
const resolvedPr = pr ?? (yield* viewPR(null)).number;
|
|
474
|
+
|
|
475
|
+
const [reviews, threads, inlineComments, issueComments] = yield* Effect.all([
|
|
476
|
+
fetchReviews(resolvedPr, null, null, null),
|
|
477
|
+
fetchThreads(resolvedPr, false),
|
|
478
|
+
fetchComments(resolvedPr, null),
|
|
479
|
+
fetchIssueComments(resolvedPr, null, null, null),
|
|
480
|
+
]);
|
|
481
|
+
|
|
482
|
+
return { reviews, threads, inlineComments, issueComments };
|
|
483
|
+
});
|
|
484
|
+
|
|
408
485
|
export const fetchLatestIssueComment = Effect.fn("pr.fetchLatestIssueComment")(function* (
|
|
409
486
|
pr: number | null,
|
|
410
487
|
author: string | null,
|
package/src/gh-tool/types.ts
CHANGED
|
@@ -66,6 +66,15 @@ export type IssueComment = {
|
|
|
66
66
|
url: GitHubIssueCommentUrl;
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
+
export type PullRequestReview = {
|
|
70
|
+
id: number;
|
|
71
|
+
author: string;
|
|
72
|
+
state: string;
|
|
73
|
+
body: string;
|
|
74
|
+
submittedAt: IsoTimestamp | null;
|
|
75
|
+
url: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
69
78
|
export type CheckResult = {
|
|
70
79
|
name: string;
|
|
71
80
|
state: string;
|
package/src/gh-tool/workflow.ts
CHANGED
|
@@ -245,10 +245,39 @@ export const dispatchWorkflow = Effect.fn("workflow.dispatchWorkflow")(function*
|
|
|
245
245
|
// then fall back to a one-shot snapshot so a timeout never returns nothing.
|
|
246
246
|
const DEFAULT_WATCH_RUN_TIMEOUT_SECONDS = CI_CHECK_WATCH_TIMEOUT_MS / 1000;
|
|
247
247
|
|
|
248
|
+
export const buildWatchResult = (
|
|
249
|
+
runId: number,
|
|
250
|
+
finalState: {
|
|
251
|
+
status: string;
|
|
252
|
+
conclusion: string | null;
|
|
253
|
+
jobs: ReadonlyArray<{ name: string; status: string; conclusion: string | null }>;
|
|
254
|
+
},
|
|
255
|
+
watchStdout: string | null,
|
|
256
|
+
frames: boolean,
|
|
257
|
+
timeoutSeconds: number,
|
|
258
|
+
) => ({
|
|
259
|
+
runId,
|
|
260
|
+
status: finalState.status,
|
|
261
|
+
conclusion: finalState.conclusion,
|
|
262
|
+
jobs: finalState.jobs.map((job) => ({
|
|
263
|
+
name: job.name,
|
|
264
|
+
status: job.status,
|
|
265
|
+
conclusion: job.conclusion,
|
|
266
|
+
})),
|
|
267
|
+
...(watchStdout === null
|
|
268
|
+
? {
|
|
269
|
+
watchOutput: `(watch timed out after ${timeoutSeconds}s; status taken from snapshot — re-run to keep watching)`,
|
|
270
|
+
}
|
|
271
|
+
: frames
|
|
272
|
+
? { watchOutput: watchStdout }
|
|
273
|
+
: {}),
|
|
274
|
+
});
|
|
275
|
+
|
|
248
276
|
const watchRun = Effect.fn("workflow.watchRun")(function* (
|
|
249
277
|
runId: number,
|
|
250
278
|
repo: string | null,
|
|
251
279
|
timeoutSeconds: number,
|
|
280
|
+
frames: boolean,
|
|
252
281
|
) {
|
|
253
282
|
const gh = yield* GitHubService;
|
|
254
283
|
|
|
@@ -277,20 +306,13 @@ const watchRun = Effect.fn("workflow.watchRun")(function* (
|
|
|
277
306
|
|
|
278
307
|
const finalState = yield* viewRun(runId, repo);
|
|
279
308
|
|
|
280
|
-
return
|
|
309
|
+
return buildWatchResult(
|
|
281
310
|
runId,
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
conclusion: job.conclusion,
|
|
288
|
-
})),
|
|
289
|
-
watchOutput:
|
|
290
|
-
result === null
|
|
291
|
-
? `(watch timed out after ${timeoutSeconds}s; status taken from snapshot — re-run to keep watching)`
|
|
292
|
-
: result.stdout,
|
|
293
|
-
};
|
|
311
|
+
finalState,
|
|
312
|
+
result === null ? null : result.stdout,
|
|
313
|
+
frames,
|
|
314
|
+
timeoutSeconds,
|
|
315
|
+
);
|
|
294
316
|
});
|
|
295
317
|
|
|
296
318
|
const fetchAnnotations = Effect.fn("workflow.fetchAnnotations")(function* (opts: {
|
|
@@ -712,6 +734,11 @@ export const workflowWatchCommand = Command.make(
|
|
|
712
734
|
"watch",
|
|
713
735
|
{
|
|
714
736
|
format: formatOption,
|
|
737
|
+
frames: Flag.boolean("frames").pipe(
|
|
738
|
+
Flag.withDescription(
|
|
739
|
+
"Include the raw watch progress frames (large); omitted by default — final status/conclusion/jobs are always returned",
|
|
740
|
+
),
|
|
741
|
+
),
|
|
715
742
|
repo: repoOption,
|
|
716
743
|
run: Flag.integer("run").pipe(Flag.withDescription("Workflow run ID to watch")),
|
|
717
744
|
timeout: Flag.integer("timeout").pipe(
|
|
@@ -725,10 +752,10 @@ export const workflowWatchCommand = Command.make(
|
|
|
725
752
|
),
|
|
726
753
|
),
|
|
727
754
|
},
|
|
728
|
-
({ format, repo, run, timeout }) =>
|
|
755
|
+
({ format, frames, repo, run, timeout }) =>
|
|
729
756
|
Effect.gen(function* () {
|
|
730
757
|
const resolvedRepo = yield* resolveRepoArg(repo);
|
|
731
|
-
const result = yield* watchRun(run, resolvedRepo, timeout);
|
|
758
|
+
const result = yield* watchRun(run, resolvedRepo, timeout, frames);
|
|
732
759
|
yield* logFormatted(result, format);
|
|
733
760
|
}),
|
|
734
761
|
).pipe(Command.withDescription("Watch a workflow run until it completes, then show final status"));
|