opencode-magi 0.0.0-dev-20260702001022 → 0.0.0-dev-20260714065541
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/commands.js +0 -4
- package/dist/magi.js +36 -68
- package/dist/tools/index.js +0 -2
- package/dist/tools/merge/action.js +4 -8
- package/dist/tools/merge/context.js +6 -12
- package/dist/tools/merge/editor.js +21 -33
- package/dist/tools/merge/index.js +12 -32
- package/dist/tools/merge/merge.js +4 -13
- package/dist/tools/merge/report.js +8 -9
- package/dist/tools/review/action.js +36 -68
- package/dist/tools/review/check.js +26 -42
- package/dist/tools/review/context.js +7 -15
- package/dist/tools/review/index.js +12 -32
- package/dist/tools/review/report.js +14 -13
- package/dist/tools/review/review.js +15 -28
- package/dist/tools/review/reviewer.js +19 -31
- package/dist/tools/triage/index.js +1 -2
- package/dist/utils/fs.js +24 -0
- package/dist/utils/index.js +1 -0
- package/package.json +5 -5
- package/dist/tools/cancel/index.js +0 -30
package/dist/commands.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
export const commands = {
|
|
2
|
-
"magi:cancel": {
|
|
3
|
-
description: "Cancel active background Magi runs",
|
|
4
|
-
template: ["Call the `magi_cancel` tool.", "PR or Issue: $ARGUMENTS"].join("\n"),
|
|
5
|
-
},
|
|
6
2
|
"magi:clear": {
|
|
7
3
|
description: [
|
|
8
4
|
"Clear inactive Magi runs, sessions, worktrees, and outputs",
|
package/dist/magi.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { createOpencodeClient } from "@opencode-ai/sdk/v2";
|
|
2
2
|
import { print } from "graphql";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
|
-
import { mkdir, readdir, readFile,
|
|
5
|
-
import { dirname, isAbsolute, join
|
|
4
|
+
import { appendFile, mkdir, readdir, readFile, writeFile, } from "node:fs/promises";
|
|
5
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
6
6
|
import { Octokit } from "octokit";
|
|
7
7
|
import { getConfig, resolvePermissions, validateConfig } from "@/config";
|
|
8
8
|
import { graphql } from "@/graphql";
|
|
9
|
-
import { command, createExec, filterEmpty, isArray, isNumber, isObject, merge, quote, } from "@/utils";
|
|
9
|
+
import { command, createExec, filterEmpty, isArray, isNumber, isObject, merge, quote, rm, } from "@/utils";
|
|
10
10
|
const active = new Set(["preparing", "running"]);
|
|
11
|
-
const backgrounds = new Map();
|
|
12
11
|
export class MagiError extends Error {
|
|
13
12
|
status;
|
|
14
13
|
constructor(status, message) {
|
|
@@ -55,45 +54,6 @@ export class Magi {
|
|
|
55
54
|
createGraphql(octokit) {
|
|
56
55
|
return graphql((document, variables) => octokit.graphql(print(document), variables));
|
|
57
56
|
}
|
|
58
|
-
registerBackground(number, controller) {
|
|
59
|
-
if (backgrounds.has(number))
|
|
60
|
-
this.cancelBackground(number);
|
|
61
|
-
backgrounds.set(number, controller);
|
|
62
|
-
}
|
|
63
|
-
unregisterBackground(number, signal) {
|
|
64
|
-
if (backgrounds.get(number)?.signal === signal)
|
|
65
|
-
backgrounds.delete(number);
|
|
66
|
-
}
|
|
67
|
-
cancelBackgrounds(numbers) {
|
|
68
|
-
const results = {
|
|
69
|
-
cancelled: [],
|
|
70
|
-
missing: [],
|
|
71
|
-
};
|
|
72
|
-
if (!numbers?.length)
|
|
73
|
-
numbers = [...backgrounds.keys()];
|
|
74
|
-
for (const number of numbers) {
|
|
75
|
-
const cancelled = this.cancelBackground(number);
|
|
76
|
-
results[cancelled ? "cancelled" : "missing"].push(number);
|
|
77
|
-
}
|
|
78
|
-
return results;
|
|
79
|
-
}
|
|
80
|
-
cancelBackground(number) {
|
|
81
|
-
const controller = backgrounds.get(number);
|
|
82
|
-
if (controller) {
|
|
83
|
-
controller.abort();
|
|
84
|
-
backgrounds.delete(number);
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
async notify(sessionID, text) {
|
|
92
|
-
await this.input.client.session.promptAsync({
|
|
93
|
-
parts: [{ synthetic: true, text, type: "text" }],
|
|
94
|
-
sessionID,
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
57
|
async clear(config) {
|
|
98
58
|
const summary = {
|
|
99
59
|
branch: 0,
|
|
@@ -177,10 +137,7 @@ export class Magi {
|
|
|
177
137
|
updatedAt: createdAt,
|
|
178
138
|
...initialState,
|
|
179
139
|
};
|
|
180
|
-
|
|
181
|
-
if (!state.sync && state.text)
|
|
182
|
-
values.push(this.notify(state.sessionId, state.text));
|
|
183
|
-
await Promise.all(values);
|
|
140
|
+
await this.createStateFile(state);
|
|
184
141
|
return state;
|
|
185
142
|
}
|
|
186
143
|
async createStateFile(state) {
|
|
@@ -200,12 +157,30 @@ export class Magi {
|
|
|
200
157
|
next.updatedAt = new Date().toISOString();
|
|
201
158
|
const prev = await this.getState(dir);
|
|
202
159
|
const state = merge(prev, next);
|
|
203
|
-
|
|
204
|
-
if (!state.sync && next.text)
|
|
205
|
-
values.push(this.notify(prev.sessionId, next.text));
|
|
206
|
-
await Promise.all(values);
|
|
160
|
+
await this.createStateFile(state);
|
|
207
161
|
return state;
|
|
208
162
|
}
|
|
163
|
+
async updateEvent(output, message) {
|
|
164
|
+
const event = {
|
|
165
|
+
createdAt: new Date().toISOString(),
|
|
166
|
+
message,
|
|
167
|
+
};
|
|
168
|
+
await appendFile(join(output, "events.jsonl"), `${JSON.stringify(event)}\n`);
|
|
169
|
+
}
|
|
170
|
+
async getEvents(output) {
|
|
171
|
+
try {
|
|
172
|
+
const content = await readFile(join(output, "events.jsonl"), "utf8");
|
|
173
|
+
return content
|
|
174
|
+
.split("\n")
|
|
175
|
+
.filter(Boolean)
|
|
176
|
+
.map((line) => JSON.parse(line));
|
|
177
|
+
}
|
|
178
|
+
catch (e) {
|
|
179
|
+
if (e instanceof Error && "code" in e && e.code === "ENOENT")
|
|
180
|
+
return [];
|
|
181
|
+
throw e;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
209
184
|
getSessionIds(state) {
|
|
210
185
|
return [
|
|
211
186
|
state.sessionId,
|
|
@@ -292,7 +267,11 @@ export class Magi {
|
|
|
292
267
|
try {
|
|
293
268
|
const path = this.getPath(value);
|
|
294
269
|
await this.exec(command("git", "worktree", "remove", "--force", quote(path)));
|
|
295
|
-
await rm(path, {
|
|
270
|
+
await rm(path, {
|
|
271
|
+
force: true,
|
|
272
|
+
prune: this.input.directory,
|
|
273
|
+
recursive: true,
|
|
274
|
+
});
|
|
296
275
|
return 1;
|
|
297
276
|
}
|
|
298
277
|
catch {
|
|
@@ -311,22 +290,11 @@ export class Magi {
|
|
|
311
290
|
async deleteOutput(path) {
|
|
312
291
|
try {
|
|
313
292
|
const resolvedPath = this.getPath(path);
|
|
314
|
-
await rm(resolvedPath, {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
break;
|
|
320
|
-
try {
|
|
321
|
-
if ((await readdir(dir)).length)
|
|
322
|
-
break;
|
|
323
|
-
await rmdir(dir);
|
|
324
|
-
}
|
|
325
|
-
catch {
|
|
326
|
-
break;
|
|
327
|
-
}
|
|
328
|
-
dir = dirname(dir);
|
|
329
|
-
}
|
|
293
|
+
await rm(resolvedPath, {
|
|
294
|
+
force: true,
|
|
295
|
+
prune: this.input.directory,
|
|
296
|
+
recursive: true,
|
|
297
|
+
});
|
|
330
298
|
return 1;
|
|
331
299
|
}
|
|
332
300
|
catch {
|
package/dist/tools/index.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { cancel } from "./cancel";
|
|
2
1
|
import { clear } from "./clear";
|
|
3
2
|
import { merge } from "./merge";
|
|
4
3
|
import { review } from "./review";
|
|
5
4
|
import { triage } from "./triage";
|
|
6
5
|
import { validate } from "./validate";
|
|
7
6
|
export const tools = {
|
|
8
|
-
cancel,
|
|
9
7
|
clear,
|
|
10
8
|
merge,
|
|
11
9
|
review,
|
|
@@ -12,12 +12,12 @@ export async function editCycles(callback) {
|
|
|
12
12
|
error: async (e, count) => {
|
|
13
13
|
if (e !== error)
|
|
14
14
|
throw e;
|
|
15
|
-
await this.
|
|
15
|
+
await this.updateEvent(`Attempt ${count} failed to edit cycles. Retrying...`);
|
|
16
16
|
},
|
|
17
17
|
retries: this.config.merge.maxThreadResolutionCycles,
|
|
18
18
|
});
|
|
19
19
|
if (!verdict || verdict === "CHANGES_REQUESTED")
|
|
20
|
-
throw new MagiError("blocked", `Reached maximum edit cycles
|
|
20
|
+
throw new MagiError("blocked", `Reached maximum edit cycles.`);
|
|
21
21
|
}
|
|
22
22
|
export async function postReplies() {
|
|
23
23
|
this.context.abort.throwIfAborted();
|
|
@@ -28,9 +28,7 @@ export async function postReplies() {
|
|
|
28
28
|
throw new MagiError("blocked", "Editor output not found.");
|
|
29
29
|
if (!output.responses.length)
|
|
30
30
|
return;
|
|
31
|
-
|
|
32
|
-
text: `Posting editor replies for ${this.getLink()}.`,
|
|
33
|
-
});
|
|
31
|
+
await this.updateEvent(`Posting editor replies.`);
|
|
34
32
|
if (!this.state.editor?.account)
|
|
35
33
|
throw new MagiError("blocked", "Editor account not found.");
|
|
36
34
|
const octokit = await this.magi.createOctokit(this.config, this.context.abort, this.state.editor.account);
|
|
@@ -44,7 +42,5 @@ export async function postReplies() {
|
|
|
44
42
|
body,
|
|
45
43
|
comment_id: commentId,
|
|
46
44
|
})));
|
|
47
|
-
|
|
48
|
-
text: `Finished posting editor replies for ${this.getLink()}.`,
|
|
49
|
-
});
|
|
45
|
+
await this.updateEvent(`Finished posting editor replies.`);
|
|
50
46
|
}
|
|
@@ -6,16 +6,14 @@ export async function fetchMergeContext() {
|
|
|
6
6
|
const output = this.state.editor?.outputs?.at(-1);
|
|
7
7
|
if (!output)
|
|
8
8
|
throw new MagiError("blocked", "Editor output not found.");
|
|
9
|
-
|
|
10
|
-
text: `Fetching merge context for ${this.getLink()}.`,
|
|
11
|
-
});
|
|
9
|
+
await this.updateEvent(`Fetching merge context.`);
|
|
12
10
|
const [comments, issues, threads] = await Promise.all([
|
|
13
11
|
getComments.call(this),
|
|
14
12
|
getClosingIssues.call(this),
|
|
15
13
|
getReviewThreads.call(this),
|
|
16
14
|
]);
|
|
17
15
|
const inlineCommentTargets = await getInlineCommentTargets.call(this);
|
|
18
|
-
|
|
16
|
+
await this.updateState({
|
|
19
17
|
pr: {
|
|
20
18
|
comments,
|
|
21
19
|
inlineCommentTargets,
|
|
@@ -27,14 +25,12 @@ export async function fetchMergeContext() {
|
|
|
27
25
|
])
|
|
28
26
|
: threads,
|
|
29
27
|
},
|
|
30
|
-
text: `Finished fetching merge context for ${this.getLink()}.`,
|
|
31
28
|
});
|
|
29
|
+
await this.updateEvent(`Finished fetching merge context.`);
|
|
32
30
|
}
|
|
33
31
|
export async function markRepliedReviewers() {
|
|
34
32
|
this.context.abort.throwIfAborted();
|
|
35
|
-
|
|
36
|
-
text: `Marking replied reviewers for ${this.getLink()}.`,
|
|
37
|
-
});
|
|
33
|
+
await this.updateEvent(`Marking replied reviewers.`);
|
|
38
34
|
const output = this.state.editor?.outputs?.at(-1);
|
|
39
35
|
if (!output)
|
|
40
36
|
throw new MagiError("blocked", "Editor output not found.");
|
|
@@ -60,10 +56,8 @@ export async function markRepliedReviewers() {
|
|
|
60
56
|
id,
|
|
61
57
|
{ status: replied.includes(id) ? "reply" : "skip" },
|
|
62
58
|
]));
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
text: `Finished marking replied reviewers for ${this.getLink()}.`,
|
|
66
|
-
});
|
|
59
|
+
await this.updateState({ reviewers });
|
|
60
|
+
await this.updateEvent(`Finished marking replied reviewers.`);
|
|
67
61
|
}
|
|
68
62
|
function createSyntheticThreads() {
|
|
69
63
|
const findings = Object.entries(this.state.reviewers ?? {}).flatMap(([reviewer, { account, outputs }]) => {
|
|
@@ -4,9 +4,7 @@ import { getMetadata } from "@/tools/review/check";
|
|
|
4
4
|
import { command, filterDuplicates, filterEmpty, quote, retry } from "@/utils";
|
|
5
5
|
export async function edit() {
|
|
6
6
|
this.context.abort.throwIfAborted();
|
|
7
|
-
|
|
8
|
-
text: `Editing ${this.getLink()}.`,
|
|
9
|
-
});
|
|
7
|
+
await this.updateEvent(`Editing.`);
|
|
10
8
|
await setAccount.call(this);
|
|
11
9
|
const unresolvedThreads = await getUnresolvedThreads.call(this);
|
|
12
10
|
const threads = this.state.dryRun
|
|
@@ -46,17 +44,17 @@ export async function edit() {
|
|
|
46
44
|
}
|
|
47
45
|
return output;
|
|
48
46
|
}, {
|
|
49
|
-
error: (_, count) => this.
|
|
47
|
+
error: (_, count) => this.updateEvent(`Attempt ${count} failed to edit. Retrying...`),
|
|
50
48
|
retries: this.config.output.repairAttempts,
|
|
51
49
|
});
|
|
52
50
|
if (!output)
|
|
53
51
|
throw new MagiError("blocked", "Invalid output for editor.");
|
|
54
|
-
|
|
52
|
+
await this.updateState({
|
|
55
53
|
editor: { outputs: [...(this.state.editor.outputs ?? []), output] },
|
|
56
|
-
text: `Finished editing ${this.getLink()}.`,
|
|
57
54
|
});
|
|
58
|
-
await this.
|
|
59
|
-
|
|
55
|
+
await this.updateEvent(`Finished editing.`);
|
|
56
|
+
await this.updateEvent(filterEmpty([
|
|
57
|
+
`Editor ${output.mode.toLocaleLowerCase()}.`,
|
|
60
58
|
output.commitSha
|
|
61
59
|
? `Commit: ${output.commitSha}${output.commitMessage ? ` ${output.commitMessage}` : ""}`
|
|
62
60
|
: undefined,
|
|
@@ -71,7 +69,7 @@ export async function edit() {
|
|
|
71
69
|
]).join("\n\n"));
|
|
72
70
|
if (output.mode === "EDITED")
|
|
73
71
|
if (this.state.dryRun) {
|
|
74
|
-
|
|
72
|
+
await this.updateState({
|
|
75
73
|
pr: {
|
|
76
74
|
files: filterDuplicates([
|
|
77
75
|
...(this.state.pr?.files ?? []),
|
|
@@ -79,26 +77,20 @@ export async function edit() {
|
|
|
79
77
|
]),
|
|
80
78
|
metadata: { head: { sha: output.commitSha } },
|
|
81
79
|
},
|
|
82
|
-
text: `Skipped pushing editor changes for ${this.getLink()} during dry run.`,
|
|
83
80
|
});
|
|
81
|
+
await this.updateEvent(`Skipped pushing editor changes during dry run.`);
|
|
84
82
|
}
|
|
85
83
|
else {
|
|
86
|
-
|
|
87
|
-
text: `Pushing editor changes for ${this.getLink()}.`,
|
|
88
|
-
});
|
|
84
|
+
await this.updateEvent(`Pushing editor changes.`);
|
|
89
85
|
const pr = await push.call(this);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
text: `Finished pushing editor changes for ${this.getLink()}.`,
|
|
93
|
-
});
|
|
86
|
+
await this.updateState({ pr });
|
|
87
|
+
await this.updateEvent(`Finished pushing editor changes.`);
|
|
94
88
|
}
|
|
95
89
|
return output.mode === "EDITED" && !this.state.dryRun;
|
|
96
90
|
}
|
|
97
91
|
export async function resolveConflict() {
|
|
98
92
|
this.context.abort.throwIfAborted();
|
|
99
|
-
|
|
100
|
-
text: `Resolving merge conflicts for ${this.getLink()}.`,
|
|
101
|
-
});
|
|
93
|
+
await this.updateEvent(`Resolving merge conflicts.`);
|
|
102
94
|
if (!this.state.pr?.metadata)
|
|
103
95
|
throw new MagiError("blocked", "PR metadata not found.");
|
|
104
96
|
const options = { cwd: this.state.worktree.path, signal: this.context.abort };
|
|
@@ -143,36 +135,32 @@ export async function resolveConflict() {
|
|
|
143
135
|
responses: [],
|
|
144
136
|
};
|
|
145
137
|
}, {
|
|
146
|
-
error: (_, count) => this.
|
|
138
|
+
error: (_, count) => this.updateEvent(`Attempt ${count} failed to resolve conflicts. Retrying...`),
|
|
147
139
|
retries: this.config.output.repairAttempts,
|
|
148
140
|
});
|
|
149
141
|
if (!output)
|
|
150
142
|
throw new MagiError("blocked", "Invalid output for conflict editor.");
|
|
151
|
-
|
|
143
|
+
await this.updateState({
|
|
152
144
|
editor: { outputs: [...(this.state.editor.outputs ?? []), output] },
|
|
153
|
-
text: `Finished resolving merge conflicts for ${this.getLink()}.`,
|
|
154
145
|
});
|
|
146
|
+
await this.updateEvent(`Finished resolving merge conflicts.`);
|
|
155
147
|
if (this.state.dryRun) {
|
|
156
|
-
|
|
148
|
+
await this.updateState({
|
|
157
149
|
pr: {
|
|
158
150
|
files: filterDuplicates([
|
|
159
|
-
...(this.state.pr
|
|
151
|
+
...(this.state.pr.files ?? []),
|
|
160
152
|
...output.filesTouched,
|
|
161
153
|
]),
|
|
162
154
|
metadata: { head: { sha: output.commitSha } },
|
|
163
155
|
},
|
|
164
|
-
text: `Skipped pushing conflict resolution for ${this.getLink()} during dry run.`,
|
|
165
156
|
});
|
|
157
|
+
await this.updateEvent(`Skipped pushing conflict resolution during dry run.`);
|
|
166
158
|
}
|
|
167
159
|
else {
|
|
168
|
-
|
|
169
|
-
text: `Pushing conflict resolution for ${this.getLink()}.`,
|
|
170
|
-
});
|
|
160
|
+
await this.updateEvent(`Pushing conflict resolution.`);
|
|
171
161
|
const pr = await push.call(this);
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
text: `Finished pushing conflict resolution for ${this.getLink()}.`,
|
|
175
|
-
});
|
|
162
|
+
await this.updateState({ pr });
|
|
163
|
+
await this.updateEvent(`Finished pushing conflict resolution.`);
|
|
176
164
|
}
|
|
177
165
|
}
|
|
178
166
|
async function setAccount() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { tool } from "@opencode-ai/plugin";
|
|
2
|
-
import {
|
|
2
|
+
import { parsePrs, split, Worker } from "@/utils";
|
|
3
3
|
import { Merge } from "./merge";
|
|
4
4
|
async function editCycle(run, { conflict, cycle } = {}) {
|
|
5
5
|
if (!run.state.editor?.sessionId)
|
|
@@ -32,7 +32,7 @@ async function editCycle(run, { conflict, cycle } = {}) {
|
|
|
32
32
|
await run.postReviews();
|
|
33
33
|
return verdict;
|
|
34
34
|
}
|
|
35
|
-
function overrideConfig(config, args, dryRun = false
|
|
35
|
+
function overrideConfig(config, args, dryRun = false) {
|
|
36
36
|
for (let index = 0; index < args.length; index++) {
|
|
37
37
|
const arg = args[index];
|
|
38
38
|
const value = args[index + 1];
|
|
@@ -42,9 +42,6 @@ function overrideConfig(config, args, dryRun = false, sync = false) {
|
|
|
42
42
|
case "--dry-run":
|
|
43
43
|
dryRun = true;
|
|
44
44
|
break;
|
|
45
|
-
case "--sync":
|
|
46
|
-
sync = true;
|
|
47
|
-
break;
|
|
48
45
|
case "--retry-api-attempts":
|
|
49
46
|
config.github.retryApiAttempts = parseInt(value);
|
|
50
47
|
break;
|
|
@@ -89,7 +86,7 @@ function overrideConfig(config, args, dryRun = false, sync = false) {
|
|
|
89
86
|
break;
|
|
90
87
|
}
|
|
91
88
|
}
|
|
92
|
-
return { config, dryRun
|
|
89
|
+
return { config, dryRun };
|
|
93
90
|
}
|
|
94
91
|
export const merge = function (magi) {
|
|
95
92
|
return {
|
|
@@ -97,23 +94,18 @@ export const merge = function (magi) {
|
|
|
97
94
|
args: {
|
|
98
95
|
dryRun: tool.schema.boolean().optional(),
|
|
99
96
|
prs: tool.schema.string(),
|
|
100
|
-
sync: tool.schema.boolean().optional(),
|
|
101
97
|
},
|
|
102
|
-
description: "
|
|
98
|
+
description: "Review, fix, and merge one or more pull requests.",
|
|
103
99
|
async execute(args, context) {
|
|
104
100
|
const prs = parsePrs(args.prs);
|
|
105
|
-
const { config, dryRun
|
|
101
|
+
const { config, dryRun } = overrideConfig(await magi.getConfig({ editor: true, reviewers: true }), split(args.prs), args.dryRun);
|
|
106
102
|
const worker = new Worker(config.review.concurrency.runs);
|
|
107
|
-
const
|
|
108
|
-
const tasks = [];
|
|
109
|
-
for (const pr of prs) {
|
|
103
|
+
const reports = await Promise.all(prs.map(async (pr) => {
|
|
110
104
|
context.abort.throwIfAborted();
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
states.push(run.state);
|
|
116
|
-
const task = worker.run(async () => {
|
|
105
|
+
return worker.run(async () => {
|
|
106
|
+
const run = await Merge.init(pr, magi, config, context, {
|
|
107
|
+
dryRun,
|
|
108
|
+
});
|
|
117
109
|
try {
|
|
118
110
|
await run.checkPr();
|
|
119
111
|
const skip = await run.checkExistingReviews();
|
|
@@ -148,20 +140,8 @@ export const merge = function (magi) {
|
|
|
148
140
|
await run.cleanup();
|
|
149
141
|
}
|
|
150
142
|
});
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
void task;
|
|
154
|
-
}
|
|
155
|
-
if (sync) {
|
|
156
|
-
const results = await Promise.all(tasks);
|
|
157
|
-
const output = filterEmpty(results.map(({ text }) => text)).join("\n\n");
|
|
158
|
-
if (results.some(({ status }) => status === "failed" || status === "cancelled"))
|
|
159
|
-
throw new Error(output);
|
|
160
|
-
return output;
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
return filterEmpty(states.map(({ text }) => text)).join("\n");
|
|
164
|
-
}
|
|
143
|
+
}));
|
|
144
|
+
return reports.join("\n\n");
|
|
165
145
|
},
|
|
166
146
|
}),
|
|
167
147
|
};
|
|
@@ -8,11 +8,6 @@ import { edit, resolveConflict } from "./editor";
|
|
|
8
8
|
import { createReport } from "./report";
|
|
9
9
|
export class Merge extends Review {
|
|
10
10
|
static async init(number, magi, config, context, options) {
|
|
11
|
-
if (!options.sync) {
|
|
12
|
-
const controller = new AbortController();
|
|
13
|
-
context = { ...context, abort: controller.signal };
|
|
14
|
-
magi.registerBackground(number, controller);
|
|
15
|
-
}
|
|
16
11
|
const url = `${config.github.url}/pull/${number}`;
|
|
17
12
|
const octokit = await magi.createOctokit(config, context.abort);
|
|
18
13
|
const graphql = magi.createGraphql(octokit);
|
|
@@ -35,8 +30,8 @@ export class Merge extends Review {
|
|
|
35
30
|
repo: quote(`${config.github.owner}/${config.github.repo}`),
|
|
36
31
|
reviewers,
|
|
37
32
|
sessionId: context.sessionID,
|
|
38
|
-
text: `Started merging [#${number}](${url}).`,
|
|
39
33
|
});
|
|
34
|
+
await magi.updateEvent(state.output, `Started merging.`);
|
|
40
35
|
const exec = createExecWithGitHubApiRetry(magi.exec, config.github.retryApiAttempts);
|
|
41
36
|
return new Merge(number, magi, config, context, octokit, graphql, exec, state);
|
|
42
37
|
}
|
|
@@ -51,18 +46,14 @@ export class Merge extends Review {
|
|
|
51
46
|
this.context.abort.throwIfAborted();
|
|
52
47
|
if (!this.state.editor)
|
|
53
48
|
throw new MagiError("blocked", "Editor not found.");
|
|
54
|
-
|
|
55
|
-
text: `Creating editor session for ${this.getLink()}.`,
|
|
56
|
-
});
|
|
49
|
+
await this.updateEvent(`Creating editor session.`);
|
|
57
50
|
const editor = {
|
|
58
51
|
sessionId: await this.magi.createSession(this.state.sessionId, `magi merge #${this.number} editor`, {
|
|
59
52
|
model: this.state.editor.model,
|
|
60
53
|
permissions: this.state.editor.permissions,
|
|
61
54
|
}),
|
|
62
55
|
};
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
text: `Finished creating editor session for ${this.getLink()}.`,
|
|
66
|
-
});
|
|
56
|
+
await this.updateState({ editor });
|
|
57
|
+
await this.updateEvent(`Finished creating editor session.`);
|
|
67
58
|
}
|
|
68
59
|
}
|
|
@@ -6,13 +6,13 @@ import { filterEmpty, toTitleCase } from "@/utils";
|
|
|
6
6
|
export async function createReport(e) {
|
|
7
7
|
if (!e) {
|
|
8
8
|
const status = "completed";
|
|
9
|
-
const text = createContent.call(this, { status });
|
|
9
|
+
const text = await createContent.call(this, { status });
|
|
10
10
|
await writeFile(join(this.state.output, "report.md"), `${text}\n`);
|
|
11
|
-
|
|
11
|
+
await this.updateState({
|
|
12
12
|
completedAt: new Date().toISOString(),
|
|
13
13
|
status,
|
|
14
|
-
text,
|
|
15
14
|
});
|
|
15
|
+
return text;
|
|
16
16
|
}
|
|
17
17
|
else {
|
|
18
18
|
const error = e instanceof Error ? e.message : "Unknown error";
|
|
@@ -21,19 +21,18 @@ export async function createReport(e) {
|
|
|
21
21
|
: this.context.abort.aborted
|
|
22
22
|
? "cancelled"
|
|
23
23
|
: "failed";
|
|
24
|
-
const text = createContent.call(this, { error, status });
|
|
24
|
+
const text = await createContent.call(this, { error, status });
|
|
25
25
|
await writeFile(join(this.state.output, "report.md"), `${text}\n`);
|
|
26
|
-
|
|
26
|
+
await this.updateState({
|
|
27
27
|
completedAt: new Date().toISOString(),
|
|
28
|
-
error,
|
|
29
28
|
status,
|
|
30
|
-
text: `${toTitleCase(status)} merging ${this.getLink()}.\n\n${text}`,
|
|
31
29
|
});
|
|
30
|
+
return text;
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
|
-
function createContent(input) {
|
|
33
|
+
async function createContent(input) {
|
|
35
34
|
return filterEmpty([
|
|
36
|
-
...createMetaContent.call(this, input),
|
|
35
|
+
...(await createMetaContent.call(this, input)),
|
|
37
36
|
...createCheckContent.call(this),
|
|
38
37
|
...createReviewerContent.call(this),
|
|
39
38
|
...createEditorContent.call(this),
|