@vercel/factory 0.0.4 → 0.0.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/dist/connectors/slack.d.mts +43 -0
- package/dist/connectors/slack.mjs +50 -0
- package/dist/connectors/slack.mjs.map +1 -0
- package/dist/eve/index.d.mts +47 -3
- package/dist/eve/index.mjs +93 -17
- package/dist/eve/index.mjs.map +1 -1
- package/dist/{github-5qQPCaXj.d.mts → github-3sbHe-fo.d.mts} +28 -2
- package/dist/github.d.mts +2 -2
- package/dist/github.mjs +47 -1
- package/dist/github.mjs.map +1 -1
- package/dist/index.d.mts +13 -1
- package/dist/index.mjs +27 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { a as NormalizedSignal } from "../index-WNGZJRRH.mjs";
|
|
2
|
+
//#region src/connectors/slack.d.ts
|
|
3
|
+
interface SlackMessageEvent {
|
|
4
|
+
teamId: string;
|
|
5
|
+
channelId: string;
|
|
6
|
+
messageTs: string;
|
|
7
|
+
threadTs: string;
|
|
8
|
+
actorId: string;
|
|
9
|
+
text: string;
|
|
10
|
+
replyAddress: string;
|
|
11
|
+
repositorySlug: string;
|
|
12
|
+
payload: unknown;
|
|
13
|
+
}
|
|
14
|
+
interface SlackMessageSignal extends NormalizedSignal<"slack"> {
|
|
15
|
+
repositorySlug: string;
|
|
16
|
+
message: {
|
|
17
|
+
teamId: string;
|
|
18
|
+
channelId: string;
|
|
19
|
+
messageTs: string;
|
|
20
|
+
threadTs: string;
|
|
21
|
+
actorId: string;
|
|
22
|
+
text: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Normalize an Eve-verified Slack message after the caller resolves its repository. */
|
|
26
|
+
declare function normalizeSlackMessage(event: SlackMessageEvent): SlackMessageSignal;
|
|
27
|
+
type SlackRepositorySelection = {
|
|
28
|
+
status: "selected";
|
|
29
|
+
repositorySlug: string;
|
|
30
|
+
} | {
|
|
31
|
+
status: "required";
|
|
32
|
+
} | {
|
|
33
|
+
status: "ambiguous";
|
|
34
|
+
repositorySlugs: readonly string[];
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Select the only configured repository, or require an explicit owner/name
|
|
38
|
+
* when a Slack surface fronts more than one repository.
|
|
39
|
+
*/
|
|
40
|
+
declare function selectSlackRepository(text: string, repositories: readonly string[]): SlackRepositorySelection;
|
|
41
|
+
//#endregion
|
|
42
|
+
export { SlackMessageEvent, SlackMessageSignal, SlackRepositorySelection, normalizeSlackMessage, selectSlackRepository };
|
|
43
|
+
//# sourceMappingURL=slack.d.mts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//#region src/connectors/slack.ts
|
|
2
|
+
/** Normalize an Eve-verified Slack message after the caller resolves its repository. */
|
|
3
|
+
function normalizeSlackMessage(event) {
|
|
4
|
+
return {
|
|
5
|
+
source: "slack",
|
|
6
|
+
deliveryId: `${event.teamId}:${event.channelId}:${event.messageTs}`,
|
|
7
|
+
repositorySlug: event.repositorySlug.toLowerCase(),
|
|
8
|
+
replyTo: {
|
|
9
|
+
channel: "slack",
|
|
10
|
+
address: event.replyAddress
|
|
11
|
+
},
|
|
12
|
+
message: {
|
|
13
|
+
teamId: event.teamId,
|
|
14
|
+
channelId: event.channelId,
|
|
15
|
+
messageTs: event.messageTs,
|
|
16
|
+
threadTs: event.threadTs,
|
|
17
|
+
actorId: event.actorId,
|
|
18
|
+
text: event.text
|
|
19
|
+
},
|
|
20
|
+
payload: event.payload
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Select the only configured repository, or require an explicit owner/name
|
|
25
|
+
* when a Slack surface fronts more than one repository.
|
|
26
|
+
*/
|
|
27
|
+
function selectSlackRepository(text, repositories) {
|
|
28
|
+
const normalized = [...new Set(repositories.map((slug) => slug.toLowerCase()))];
|
|
29
|
+
if (normalized.length === 1) return {
|
|
30
|
+
status: "selected",
|
|
31
|
+
repositorySlug: normalized[0]
|
|
32
|
+
};
|
|
33
|
+
const matches = normalized.filter((slug) => {
|
|
34
|
+
const escaped = slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
35
|
+
return new RegExp(`(^|[^a-z0-9._/-])${escaped}($|[^a-z0-9._/-])`, "i").test(text);
|
|
36
|
+
});
|
|
37
|
+
if (matches.length === 1) return {
|
|
38
|
+
status: "selected",
|
|
39
|
+
repositorySlug: matches[0]
|
|
40
|
+
};
|
|
41
|
+
if (matches.length > 1) return {
|
|
42
|
+
status: "ambiguous",
|
|
43
|
+
repositorySlugs: matches
|
|
44
|
+
};
|
|
45
|
+
return { status: "required" };
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { normalizeSlackMessage, selectSlackRepository };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=slack.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack.mjs","names":[],"sources":["../../src/connectors/slack.ts"],"sourcesContent":["import type { NormalizedSignal } from \"./index\";\n\nexport interface SlackMessageEvent {\n teamId: string;\n channelId: string;\n messageTs: string;\n threadTs: string;\n actorId: string;\n text: string;\n replyAddress: string;\n repositorySlug: string;\n payload: unknown;\n}\n\nexport interface SlackMessageSignal extends NormalizedSignal<\"slack\"> {\n repositorySlug: string;\n message: {\n teamId: string;\n channelId: string;\n messageTs: string;\n threadTs: string;\n actorId: string;\n text: string;\n };\n}\n\n/** Normalize an Eve-verified Slack message after the caller resolves its repository. */\nexport function normalizeSlackMessage(event: SlackMessageEvent): SlackMessageSignal {\n return {\n source: \"slack\",\n deliveryId: `${event.teamId}:${event.channelId}:${event.messageTs}`,\n repositorySlug: event.repositorySlug.toLowerCase(),\n replyTo: { channel: \"slack\", address: event.replyAddress },\n message: {\n teamId: event.teamId,\n channelId: event.channelId,\n messageTs: event.messageTs,\n threadTs: event.threadTs,\n actorId: event.actorId,\n text: event.text,\n },\n payload: event.payload,\n };\n}\n\nexport type SlackRepositorySelection =\n | { status: \"selected\"; repositorySlug: string }\n | { status: \"required\" }\n | { status: \"ambiguous\"; repositorySlugs: readonly string[] };\n\n/**\n * Select the only configured repository, or require an explicit owner/name\n * when a Slack surface fronts more than one repository.\n */\nexport function selectSlackRepository(\n text: string,\n repositories: readonly string[],\n): SlackRepositorySelection {\n const normalized = [...new Set(repositories.map((slug) => slug.toLowerCase()))];\n if (normalized.length === 1) {\n return { status: \"selected\", repositorySlug: normalized[0]! };\n }\n\n const matches = normalized.filter((slug) => {\n const escaped = slug.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n return new RegExp(`(^|[^a-z0-9._/-])${escaped}($|[^a-z0-9._/-])`, \"i\").test(text);\n });\n if (matches.length === 1) {\n return { status: \"selected\", repositorySlug: matches[0]! };\n }\n if (matches.length > 1) {\n return { status: \"ambiguous\", repositorySlugs: matches };\n }\n return { status: \"required\" };\n}\n"],"mappings":";;AA2BA,SAAgB,sBAAsB,OAA8C;CAClF,OAAO;EACL,QAAQ;EACR,YAAY,GAAG,MAAM,OAAO,GAAG,MAAM,UAAU,GAAG,MAAM;EACxD,gBAAgB,MAAM,eAAe,YAAY;EACjD,SAAS;GAAE,SAAS;GAAS,SAAS,MAAM;EAAa;EACzD,SAAS;GACP,QAAQ,MAAM;GACd,WAAW,MAAM;GACjB,WAAW,MAAM;GACjB,UAAU,MAAM;GAChB,SAAS,MAAM;GACf,MAAM,MAAM;EACd;EACA,SAAS,MAAM;CACjB;AACF;;;;;AAWA,SAAgB,sBACd,MACA,cAC0B;CAC1B,MAAM,aAAa,CAAC,GAAG,IAAI,IAAI,aAAa,KAAK,SAAS,KAAK,YAAY,CAAC,CAAC,CAAC;CAC9E,IAAI,WAAW,WAAW,GACxB,OAAO;EAAE,QAAQ;EAAY,gBAAgB,WAAW;CAAI;CAG9D,MAAM,UAAU,WAAW,QAAQ,SAAS;EAC1C,MAAM,UAAU,KAAK,QAAQ,uBAAuB,MAAM;EAC1D,OAAO,IAAI,OAAO,oBAAoB,QAAQ,oBAAoB,GAAG,CAAC,CAAC,KAAK,IAAI;CAClF,CAAC;CACD,IAAI,QAAQ,WAAW,GACrB,OAAO;EAAE,QAAQ;EAAY,gBAAgB,QAAQ;CAAI;CAE3D,IAAI,QAAQ,SAAS,GACnB,OAAO;EAAE,QAAQ;EAAa,iBAAiB;CAAQ;CAEzD,OAAO,EAAE,QAAQ,WAAW;AAC9B"}
|
package/dist/eve/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as Finding, k as FactoryStores, t as ApprovalPolicy } from "../approval-h5aj8HYN.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { d as GitHubReviewComment } from "../github-3sbHe-fo.mjs";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
//#region src/eve/fetch.d.ts
|
|
5
5
|
/** The subset of the eve sandbox handle a credentialed git operation needs. */
|
|
@@ -379,11 +379,17 @@ declare function invokeAgentTool(stores: FactoryStores, options: InvokeAgentOpti
|
|
|
379
379
|
};
|
|
380
380
|
//#endregion
|
|
381
381
|
//#region src/eve/review.d.ts
|
|
382
|
-
interface ReviewCommentToolOptions {
|
|
382
|
+
interface ReviewCommentToolOptions<S extends CommandSandbox = CommandSandbox> {
|
|
383
383
|
tokenForRepository: (repository: string) => Promise<string>;
|
|
384
|
+
withCredential?: (sandbox: S, repository: string, command: string) => Promise<{
|
|
385
|
+
stdout: string;
|
|
386
|
+
stderr?: string;
|
|
387
|
+
exitCode?: number;
|
|
388
|
+
}>;
|
|
384
389
|
fetch?: typeof globalThis.fetch;
|
|
390
|
+
repoPath?: string;
|
|
385
391
|
}
|
|
386
|
-
declare function reviewCommentTools(stores: FactoryStores, options: ReviewCommentToolOptions): {
|
|
392
|
+
declare function reviewCommentTools<S extends CommandSandbox>(stores: FactoryStores, options: ReviewCommentToolOptions<S>): {
|
|
387
393
|
read_review_comment: import("eve/tools").ToolDefinition<{
|
|
388
394
|
changeId: string;
|
|
389
395
|
commentId: number;
|
|
@@ -404,6 +410,44 @@ declare function reviewCommentTools(stores: FactoryStores, options: ReviewCommen
|
|
|
404
410
|
body: string;
|
|
405
411
|
}, ctx: import("eve/tools").ToolContext): Promise<GitHubReviewComment>;
|
|
406
412
|
};
|
|
413
|
+
suggest_review_fix: import("eve/tools").ToolDefinition<{
|
|
414
|
+
changeId: string;
|
|
415
|
+
commentId: number;
|
|
416
|
+
confidence: "medium";
|
|
417
|
+
replacement: string;
|
|
418
|
+
summary: string;
|
|
419
|
+
}, GitHubReviewComment> & {
|
|
420
|
+
execute(input: {
|
|
421
|
+
changeId: string;
|
|
422
|
+
commentId: number;
|
|
423
|
+
confidence: "medium";
|
|
424
|
+
replacement: string;
|
|
425
|
+
summary: string;
|
|
426
|
+
}, ctx: import("eve/tools").ToolContext): Promise<GitHubReviewComment>;
|
|
427
|
+
};
|
|
428
|
+
apply_review_fix: import("eve/tools").ToolDefinition<{
|
|
429
|
+
changeId: string;
|
|
430
|
+
commentId: number;
|
|
431
|
+
confidence: "high";
|
|
432
|
+
summary: string;
|
|
433
|
+
}, {
|
|
434
|
+
updated: true;
|
|
435
|
+
prNumber: number;
|
|
436
|
+
prUrl: string;
|
|
437
|
+
reply: GitHubReviewComment;
|
|
438
|
+
}> & {
|
|
439
|
+
execute(input: {
|
|
440
|
+
changeId: string;
|
|
441
|
+
commentId: number;
|
|
442
|
+
confidence: "high";
|
|
443
|
+
summary: string;
|
|
444
|
+
}, ctx: import("eve/tools").ToolContext): Promise<{
|
|
445
|
+
updated: true;
|
|
446
|
+
prNumber: number;
|
|
447
|
+
prUrl: string;
|
|
448
|
+
reply: GitHubReviewComment;
|
|
449
|
+
}>;
|
|
450
|
+
};
|
|
407
451
|
};
|
|
408
452
|
//#endregion
|
|
409
453
|
//#region src/eve/verification.d.ts
|
package/dist/eve/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as repositoryIdSchema, o as signalIdSchema, p as vocabularySchema, s as taskIdSchema, t as changeIdSchema } from "../id-DwCihQr4.mjs";
|
|
2
2
|
import { S as findingFingerprint, b as requiresApproval, g as dispatchTask, p as levelSchema, s as taskQuestionSchema, u as changeEvidenceSchema, w as findingSchema, y as defaultApprovalPolicy } from "../task-Cj7Ei9Nh.mjs";
|
|
3
|
-
import { answerReviewComment, readReviewComment } from "../github.mjs";
|
|
3
|
+
import { answerReviewComment, readPullRequestHead, readReviewComment } from "../github.mjs";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { defineHook } from "eve/hooks";
|
|
6
6
|
import { defineTool } from "eve/tools";
|
|
@@ -476,44 +476,120 @@ function invokeAgentTool(stores, options) {
|
|
|
476
476
|
//#endregion
|
|
477
477
|
//#region src/eve/review.ts
|
|
478
478
|
function reviewCommentTools(stores, options) {
|
|
479
|
+
const repoPath = options.repoPath ?? "repo";
|
|
479
480
|
const commentInputSchema = z.object({
|
|
480
481
|
changeId: changeIdSchema.describe("The change under review, from your starting message."),
|
|
481
482
|
commentId: z.number().int().positive().describe("The GitHub review comment id.")
|
|
482
483
|
});
|
|
483
|
-
async function context(changeId) {
|
|
484
|
-
const change = await stores.changes.get(changeId);
|
|
485
|
-
if (change === null) throw new Error(`change ${changeId} does not exist`);
|
|
486
|
-
if (change.state !== "verifying" && change.state !== "ready") throw new Error(`change is ${change.state}, not open for review`);
|
|
487
|
-
const repository = await stores.repositories.get(change.repositoryId);
|
|
488
|
-
if (repository === null) throw new Error(`repository ${change.repositoryId} does not exist`);
|
|
489
|
-
return {
|
|
490
|
-
repository: repository.slug,
|
|
491
|
-
token: await options.tokenForRepository(repository.slug),
|
|
492
|
-
fetch: options.fetch,
|
|
493
|
-
pullRequestNumber: change.prNumber
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
484
|
return {
|
|
497
485
|
read_review_comment: defineTool({
|
|
498
486
|
description: "Read an inline review comment on a change under verification or ready for human review. By default this refuses to return comments from people without write access to the repository.",
|
|
499
487
|
inputSchema: commentInputSchema,
|
|
500
488
|
async execute({ changeId, commentId }) {
|
|
489
|
+
const change = await stores.changes.get(changeId);
|
|
490
|
+
if (change === null) throw new Error(`change ${changeId} does not exist`);
|
|
491
|
+
if (change.state !== "verifying" && change.state !== "ready") throw new Error(`change is ${change.state}, not open for review`);
|
|
492
|
+
const repository = await stores.repositories.get(change.repositoryId);
|
|
493
|
+
if (repository === null) throw new Error(`repository ${change.repositoryId} does not exist`);
|
|
501
494
|
return readReviewComment({
|
|
502
|
-
|
|
495
|
+
repository: repository.slug,
|
|
496
|
+
token: await options.tokenForRepository(repository.slug),
|
|
497
|
+
fetch: options.fetch,
|
|
503
498
|
commentId
|
|
504
499
|
});
|
|
505
500
|
}
|
|
506
501
|
}),
|
|
507
502
|
answer_review_comment: defineTool({
|
|
508
|
-
description: "Reply to an inline review comment on a change under verification or ready for human review.
|
|
503
|
+
description: "Reply to an inline review comment on a change under verification or ready for human review. Use for findings and low-confidence responses, not for a concrete medium- or high-confidence fix.",
|
|
509
504
|
inputSchema: commentInputSchema.extend({ body: z.string().min(1).max(5e3).describe("The Markdown reply to post.") }),
|
|
510
505
|
async execute({ changeId, commentId, body }) {
|
|
506
|
+
const change = await stores.changes.get(changeId);
|
|
507
|
+
if (change === null) throw new Error(`change ${changeId} does not exist`);
|
|
508
|
+
if (change.state !== "verifying" && change.state !== "ready") throw new Error(`change is ${change.state}, not open for review`);
|
|
509
|
+
const repository = await stores.repositories.get(change.repositoryId);
|
|
510
|
+
if (repository === null) throw new Error(`repository ${change.repositoryId} does not exist`);
|
|
511
511
|
return answerReviewComment({
|
|
512
|
-
|
|
512
|
+
repository: repository.slug,
|
|
513
|
+
token: await options.tokenForRepository(repository.slug),
|
|
514
|
+
fetch: options.fetch,
|
|
515
|
+
pullRequestNumber: change.prNumber,
|
|
513
516
|
commentId,
|
|
514
517
|
body
|
|
515
518
|
});
|
|
516
519
|
}
|
|
520
|
+
}),
|
|
521
|
+
suggest_review_fix: defineTool({
|
|
522
|
+
description: "Post a concrete, apply-able GitHub suggested change for a medium-confidence fix. The replacement must cover exactly the lines attached to the inline review comment.",
|
|
523
|
+
inputSchema: commentInputSchema.extend({
|
|
524
|
+
confidence: z.literal("medium"),
|
|
525
|
+
replacement: z.string().min(1).max(5e3).describe("Exact replacement text for the commented lines."),
|
|
526
|
+
summary: z.string().min(1).max(1e3).describe("A brief explanation of the suggested fix.")
|
|
527
|
+
}),
|
|
528
|
+
async execute({ changeId, commentId, replacement, summary }) {
|
|
529
|
+
const change = await stores.changes.get(changeId);
|
|
530
|
+
if (change === null) throw new Error(`change ${changeId} does not exist`);
|
|
531
|
+
if (change.state !== "verifying" && change.state !== "ready") throw new Error(`change is ${change.state}, not open for review`);
|
|
532
|
+
const repository = await stores.repositories.get(change.repositoryId);
|
|
533
|
+
if (repository === null) throw new Error(`repository ${change.repositoryId} does not exist`);
|
|
534
|
+
return answerReviewComment({
|
|
535
|
+
repository: repository.slug,
|
|
536
|
+
token: await options.tokenForRepository(repository.slug),
|
|
537
|
+
fetch: options.fetch,
|
|
538
|
+
pullRequestNumber: change.prNumber,
|
|
539
|
+
commentId,
|
|
540
|
+
body: `${summary}\n\n\`\`\`suggestion\n${replacement}\n\`\`\``
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
}),
|
|
544
|
+
apply_review_fix: defineTool({
|
|
545
|
+
description: "Publish a tested, committed high-confidence fix to the existing same-repository pull request branch. Fails if the checkout is dirty, the PR changed, or the PR comes from a fork.",
|
|
546
|
+
inputSchema: commentInputSchema.extend({
|
|
547
|
+
confidence: z.literal("high"),
|
|
548
|
+
summary: z.string().min(1).max(1e3).describe("What was fixed and verified.")
|
|
549
|
+
}),
|
|
550
|
+
async execute({ changeId, commentId, summary }, ctx) {
|
|
551
|
+
const change = await stores.changes.get(changeId);
|
|
552
|
+
if (change === null) throw new Error(`change ${changeId} does not exist`);
|
|
553
|
+
if (change.state !== "verifying" && change.state !== "ready") throw new Error(`change is ${change.state}, not open for review`);
|
|
554
|
+
const repository = await stores.repositories.get(change.repositoryId);
|
|
555
|
+
if (repository === null) throw new Error(`repository ${change.repositoryId} does not exist`);
|
|
556
|
+
if (options.withCredential === void 0) throw new Error("applying review fixes is not configured");
|
|
557
|
+
const token = await options.tokenForRepository(repository.slug);
|
|
558
|
+
await readReviewComment({
|
|
559
|
+
repository: repository.slug,
|
|
560
|
+
token,
|
|
561
|
+
fetch: options.fetch,
|
|
562
|
+
commentId
|
|
563
|
+
});
|
|
564
|
+
const head = await readPullRequestHead({
|
|
565
|
+
repository: repository.slug,
|
|
566
|
+
token,
|
|
567
|
+
fetch: options.fetch,
|
|
568
|
+
pullRequestNumber: change.prNumber
|
|
569
|
+
});
|
|
570
|
+
if (head.repository !== repository.slug.toLowerCase()) throw new Error("cannot apply a fix to a pull request from a fork");
|
|
571
|
+
const sandbox = await ctx.getSandbox();
|
|
572
|
+
const git = `git -C ${repoPath}`;
|
|
573
|
+
if ((await sandbox.run({ command: `${git} status --porcelain` })).stdout.trim() !== "") throw new Error("commit the review fix before publishing");
|
|
574
|
+
if ((await sandbox.run({ command: `${git} merge-base --is-ancestor ${head.sha} HEAD` })).exitCode !== 0) throw new Error("pull request changed after it was fetched; fetch it again before applying the fix");
|
|
575
|
+
if ((await sandbox.run({ command: `${git} rev-parse HEAD` })).stdout.trim() === head.sha) throw new Error("no committed review fix to publish");
|
|
576
|
+
const push = await options.withCredential(sandbox, repository.slug, `${git} push --force-with-lease=refs/heads/${head.branch}:${head.sha} origin HEAD:refs/heads/${head.branch}`);
|
|
577
|
+
if (typeof push.exitCode === "number" && push.exitCode !== 0) throw new Error(`could not update pull request branch: ${(push.stderr ?? "").trim()}`);
|
|
578
|
+
const reply = await answerReviewComment({
|
|
579
|
+
repository: repository.slug,
|
|
580
|
+
token,
|
|
581
|
+
fetch: options.fetch,
|
|
582
|
+
pullRequestNumber: change.prNumber,
|
|
583
|
+
commentId,
|
|
584
|
+
body: summary
|
|
585
|
+
});
|
|
586
|
+
return {
|
|
587
|
+
updated: true,
|
|
588
|
+
prNumber: change.prNumber,
|
|
589
|
+
prUrl: change.prUrl,
|
|
590
|
+
reply
|
|
591
|
+
};
|
|
592
|
+
}
|
|
517
593
|
})
|
|
518
594
|
};
|
|
519
595
|
}
|