opencode-magi 0.11.0 → 0.12.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.
- package/dist/config/validate.js +13 -18
- package/dist/constant.js +1 -5
- package/dist/tools/merge/context.js +1 -3
- package/dist/tools/merge/editor.js +0 -8
- package/dist/tools/merge/index.js +1 -1
- package/dist/tools/review/action.js +37 -7
- package/dist/tools/review/check.js +61 -21
- package/dist/tools/review/context.js +1 -5
- package/dist/tools/review/review.js +2 -1
- package/dist/utils/function.js +9 -0
- package/package.json +23 -22
- package/schema.json +110 -16
package/dist/config/validate.js
CHANGED
|
@@ -20,12 +20,12 @@ function requiredErrors(config, { creator = false, editor = false, github = true
|
|
|
20
20
|
required(github, config.github.owner, "github.owner"),
|
|
21
21
|
required(github, config.github.repo, "github.repo"),
|
|
22
22
|
required(reviewers, config.review.reviewers, "review.reviewers"),
|
|
23
|
-
...(config.review.reviewers ?? []).map((reviewer, index) => required(reviewers
|
|
23
|
+
...(config.review.reviewers ?? []).map((reviewer, index) => required(reviewers, reviewer.model, `review.reviewers[${index}].model`)),
|
|
24
24
|
required(editor, config.merge.editor, "merge.editor"),
|
|
25
25
|
required(editor, config.merge.editor?.model, "merge.editor.model"),
|
|
26
26
|
required(editor, config.merge.editor?.author, "merge.editor.author"),
|
|
27
27
|
required(voters, config.triage.voters, "triage.voters"),
|
|
28
|
-
...(config.triage.voters ?? []).map((voter, index) => required(voters
|
|
28
|
+
...(config.triage.voters ?? []).map((voter, index) => required(voters, voter.model, `triage.voters[${index}].model`)),
|
|
29
29
|
required(creator, config.triage.creator, "triage.creator"),
|
|
30
30
|
required(creator, config.triage.creator?.model, "triage.creator.model"),
|
|
31
31
|
required(creator, config.triage.creator?.author, "triage.creator.author"),
|
|
@@ -88,23 +88,18 @@ async function authErrors(config, exec) {
|
|
|
88
88
|
return filterEmpty([await authError(config, exec, config.account)]);
|
|
89
89
|
}
|
|
90
90
|
else {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
]) ?? []),
|
|
100
|
-
};
|
|
101
|
-
if (config.merge.editor)
|
|
102
|
-
accounts[config.merge.editor.account] = "merge.editor";
|
|
103
|
-
if (config.triage.creator)
|
|
104
|
-
accounts[config.triage.creator.account] = "triage.creator";
|
|
91
|
+
const reviewerAccounts = config.review.reviewers?.map(({ account }) => account) ?? [];
|
|
92
|
+
const voterAccounts = config.triage.voters?.map(({ account }) => account) ?? [];
|
|
93
|
+
const accounts = new Set(filterEmpty([
|
|
94
|
+
...reviewerAccounts,
|
|
95
|
+
...voterAccounts,
|
|
96
|
+
config.merge.editor?.account,
|
|
97
|
+
config.triage.creator?.account,
|
|
98
|
+
]));
|
|
105
99
|
return filterEmpty([
|
|
106
|
-
...duplicateErrors(
|
|
107
|
-
...(
|
|
100
|
+
...duplicateErrors(reviewerAccounts, (value) => `review.reviewers has duplicate account: ${value}`),
|
|
101
|
+
...duplicateErrors(voterAccounts, (value) => `triage.voters has duplicate account: ${value}`),
|
|
102
|
+
...(await Promise.all([...accounts].map((account) => authError(config, exec, account)))),
|
|
108
103
|
]);
|
|
109
104
|
}
|
|
110
105
|
}
|
package/dist/constant.js
CHANGED
|
@@ -95,9 +95,7 @@ function createSyntheticThreads() {
|
|
|
95
95
|
}));
|
|
96
96
|
}
|
|
97
97
|
function addSyntheticReplies(threads) {
|
|
98
|
-
const output = this.state.editor
|
|
99
|
-
if (!output)
|
|
100
|
-
throw new MagiError("blocked", "Editor output not found.");
|
|
98
|
+
const output = this.state.editor.outputs.at(-1);
|
|
101
99
|
return threads.map((thread) => ({
|
|
102
100
|
...thread,
|
|
103
101
|
comments: [
|
|
@@ -183,12 +183,6 @@ async function setAccount() {
|
|
|
183
183
|
await this.exec(command("git", "config", "user.email", quote(this.state.editor.author.email)), options);
|
|
184
184
|
}
|
|
185
185
|
async function push() {
|
|
186
|
-
if (!this.state.editor?.account)
|
|
187
|
-
throw new MagiError("blocked", "Editor account not found.");
|
|
188
|
-
if (!this.state.pr?.metadata)
|
|
189
|
-
throw new MagiError("blocked", "PR metadata not found.");
|
|
190
|
-
if (!this.state.worktree)
|
|
191
|
-
throw new MagiError("blocked", "PR worktree not found.");
|
|
192
186
|
const token = await this.magi.getGhToken(this.state.editor.account, this.context.abort);
|
|
193
187
|
const url = `https://${this.config.github.host}/${this.state.pr.metadata.head.repo.owner.login}/${this.state.pr.metadata.head.repo.name}.git`;
|
|
194
188
|
const ref = `HEAD:refs/heads/${this.state.pr.metadata.head.ref}`;
|
|
@@ -208,8 +202,6 @@ async function push() {
|
|
|
208
202
|
return getMetadata.call(this);
|
|
209
203
|
}
|
|
210
204
|
async function getConflictedFiles() {
|
|
211
|
-
if (!this.state.worktree)
|
|
212
|
-
throw new MagiError("blocked", "PR worktree not found.");
|
|
213
205
|
const options = { cwd: this.state.worktree.path, signal: this.context.abort };
|
|
214
206
|
try {
|
|
215
207
|
await this.exec(command("git", "merge", "--no-commit", "--no-ff", "FETCH_HEAD"), options);
|
|
@@ -127,7 +127,7 @@ export const merge = function (magi) {
|
|
|
127
127
|
await run.editCycles(async () => editCycle(run));
|
|
128
128
|
const automation = await run.automate();
|
|
129
129
|
if (automation === "CONFLICT" &&
|
|
130
|
-
run.config.merge.automation.conflict) {
|
|
130
|
+
run.isAutomationEnabled(run.config.merge.automation.conflict)) {
|
|
131
131
|
await run.editCycles(async (cycle) => editCycle(run, { conflict: true, cycle }));
|
|
132
132
|
await run.automate();
|
|
133
133
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MagiError } from "@/magi";
|
|
2
2
|
import { Prompt } from "@/prompts";
|
|
3
|
-
import { command, filterEmpty, isArray, isObject, isString, loop, marker, omitNullish, quote, retry, wait, Worker, } from "@/utils";
|
|
3
|
+
import { command, filterEmpty, ignoreError, isArray, isBoolean, isObject, isString, loop, marker, omitNullish, quote, retry, wait, Worker, } from "@/utils";
|
|
4
|
+
import { getConditionErrors } from "./check";
|
|
4
5
|
const events = {
|
|
5
6
|
APPROVED: "APPROVE",
|
|
6
7
|
CHANGES_REQUESTED: "REQUEST_CHANGES",
|
|
@@ -181,9 +182,8 @@ export async function automate() {
|
|
|
181
182
|
throw new MagiError("blocked", "PR verdict not found.");
|
|
182
183
|
if (!["APPROVED", "CLOSED"].includes(this.state.pr.verdict))
|
|
183
184
|
return "SKIPPED";
|
|
184
|
-
const automation = this.config[this.state.command].automation;
|
|
185
185
|
const action = this.state.pr.verdict === "APPROVED" ? "merge" : "close";
|
|
186
|
-
if (!automation[action]) {
|
|
186
|
+
if (!isAutomationEnabled.call(this, this.config[this.state.command].automation[action])) {
|
|
187
187
|
await this.updateState({ pr: { automation: "SKIPPED" } });
|
|
188
188
|
await this.updateEvent(`Skipped ${action} automation.`);
|
|
189
189
|
return "SKIPPED";
|
|
@@ -233,7 +233,24 @@ export async function automate() {
|
|
|
233
233
|
const octokit = await this.magi.createOctokit(this.config, this.context.abort, account);
|
|
234
234
|
const graphql = this.magi.createGraphql(octokit);
|
|
235
235
|
const enqueuedAt = new Date().toISOString();
|
|
236
|
-
|
|
236
|
+
try {
|
|
237
|
+
await ignoreError(() => graphql.enqueuePullRequest({ id: this.state.pr.metadata.node_id }), (e) => {
|
|
238
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
239
|
+
return /pull request is already in the queue/i.test(message);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
catch (e) {
|
|
243
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
244
|
+
if (!/pull request is closed/i.test(message))
|
|
245
|
+
throw e;
|
|
246
|
+
const { repository } = await graphql.mergeQueueStatus({
|
|
247
|
+
owner: this.config.github.owner,
|
|
248
|
+
pr: this.number,
|
|
249
|
+
repo: this.config.github.repo,
|
|
250
|
+
});
|
|
251
|
+
if (repository?.pullRequest?.state !== "MERGED")
|
|
252
|
+
throw e;
|
|
253
|
+
}
|
|
237
254
|
await this.updateEvent(`Waiting for merge queue.`);
|
|
238
255
|
const result = await waitMergeQueue.call(this, enqueuedAt);
|
|
239
256
|
if (result === "CONFLICT")
|
|
@@ -328,6 +345,18 @@ export async function automate() {
|
|
|
328
345
|
await this.updateEvent(`Finished ${action} automation.`);
|
|
329
346
|
return action === "merge" ? "MERGED" : "CLOSED";
|
|
330
347
|
}
|
|
348
|
+
export function isAutomationEnabled(conditions) {
|
|
349
|
+
if (isBoolean(conditions))
|
|
350
|
+
return conditions;
|
|
351
|
+
const edited = !this.state.dryRun &&
|
|
352
|
+
this.state.editor?.outputs?.some(({ commitSha }) => !!commitSha);
|
|
353
|
+
return conditions.every(([expected, condition]) => {
|
|
354
|
+
const errors = getConditionErrors(condition, this.state.pr.metadata, this.state.pr.files ?? []);
|
|
355
|
+
const matches = !errors.length &&
|
|
356
|
+
(!("edited" in condition) || condition.edited === !!edited);
|
|
357
|
+
return matches === expected;
|
|
358
|
+
});
|
|
359
|
+
}
|
|
331
360
|
function hasFailedChecks(checks) {
|
|
332
361
|
if (!isArray(checks))
|
|
333
362
|
return false;
|
|
@@ -388,8 +417,11 @@ async function waitMergeQueue(enqueuedAt, leftMergeQueue = false, retries = 0) {
|
|
|
388
417
|
const attempt = retries + 1;
|
|
389
418
|
const nextEnqueuedAt = new Date().toISOString();
|
|
390
419
|
await this.updateEvent(`Attempt ${attempt} failed to merge from the merge queue. Retrying...`);
|
|
391
|
-
await this.graphql.enqueuePullRequest({
|
|
420
|
+
await ignoreError(() => this.graphql.enqueuePullRequest({
|
|
392
421
|
id: this.state.pr.metadata.node_id,
|
|
422
|
+
}), (e) => {
|
|
423
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
424
|
+
return /pull request is already in the queue/i.test(message);
|
|
393
425
|
});
|
|
394
426
|
return await waitMergeQueue.call(this, nextEnqueuedAt, false, attempt);
|
|
395
427
|
}
|
|
@@ -401,8 +433,6 @@ async function waitMergeQueue(enqueuedAt, leftMergeQueue = false, retries = 0) {
|
|
|
401
433
|
async function isConflict() {
|
|
402
434
|
if (!this.state.worktree)
|
|
403
435
|
return false;
|
|
404
|
-
if (!this.state.pr?.metadata)
|
|
405
|
-
throw new MagiError("blocked", "PR metadata not found.");
|
|
406
436
|
const options = { cwd: this.state.worktree.path, signal: this.context.abort };
|
|
407
437
|
const status = await this.exec(command("git", "status", "--porcelain"), options);
|
|
408
438
|
if (status)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import picomatch from "picomatch";
|
|
2
2
|
import { MagiError } from "@/magi";
|
|
3
3
|
import { Prompt } from "@/prompts";
|
|
4
|
-
import { command, filterEmpty, isNumber, omitNullish, quote, retry, Worker, } from "@/utils";
|
|
4
|
+
import { command, filterEmpty, isBoolean, isNumber, omitNullish, quote, retry, Worker, } from "@/utils";
|
|
5
5
|
const ci = {
|
|
6
6
|
isExcluded(exclude, { name }) {
|
|
7
7
|
return exclude.some((pattern) => {
|
|
@@ -22,6 +22,65 @@ const ci = {
|
|
|
22
22
|
return !this.isFailed(check) && !this.isPassed(check);
|
|
23
23
|
},
|
|
24
24
|
};
|
|
25
|
+
function matchesValues(filter, values) {
|
|
26
|
+
if (!filter)
|
|
27
|
+
return true;
|
|
28
|
+
if ("include" in filter &&
|
|
29
|
+
!values.some((value) => filter.include.includes(value)))
|
|
30
|
+
return false;
|
|
31
|
+
return !("exclude" in filter &&
|
|
32
|
+
values.some((value) => filter.exclude.includes(value)));
|
|
33
|
+
}
|
|
34
|
+
function matchesPatterns(filter, values) {
|
|
35
|
+
if (!filter)
|
|
36
|
+
return true;
|
|
37
|
+
if ("include" in filter) {
|
|
38
|
+
const isIncluded = picomatch(filter.include, { dot: true });
|
|
39
|
+
if (!values.some((value) => isIncluded(value)))
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
if ("exclude" in filter) {
|
|
43
|
+
const isExcluded = picomatch(filter.exclude, { dot: true });
|
|
44
|
+
if (values.some((value) => isExcluded(value)))
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
export function getConditionErrors(condition, metadata, files) {
|
|
50
|
+
const errors = [];
|
|
51
|
+
const branches = condition.branches
|
|
52
|
+
? {
|
|
53
|
+
base: "base" in condition.branches ? condition.branches.base : undefined,
|
|
54
|
+
head: "head" in condition.branches ? condition.branches.head : undefined,
|
|
55
|
+
}
|
|
56
|
+
: {};
|
|
57
|
+
if (!matchesValues(condition.authors, [metadata.user.login]))
|
|
58
|
+
errors.push(`Author does not match safety filter: ${metadata.user.login}.`);
|
|
59
|
+
if (!matchesPatterns(branches.base, [metadata.base.ref]))
|
|
60
|
+
errors.push(`Base branch does not match safety filter: ${metadata.base.ref}.`);
|
|
61
|
+
if (!matchesPatterns(branches.head, [metadata.head.ref]))
|
|
62
|
+
errors.push(`Head branch does not match safety filter: ${metadata.head.ref}.`);
|
|
63
|
+
if (!matchesValues(condition.labels, metadata.labels.map(({ name }) => name)))
|
|
64
|
+
errors.push(`Labels do not match safety filter.`);
|
|
65
|
+
if (!matchesPatterns(condition.paths, files))
|
|
66
|
+
errors.push(`Paths do not match safety filter.`);
|
|
67
|
+
if (isNumber(condition.maxChangedFiles) &&
|
|
68
|
+
metadata.changed_files > condition.maxChangedFiles)
|
|
69
|
+
errors.push(`Changed files exceed limit: ${metadata.changed_files} > ${condition.maxChangedFiles}.`);
|
|
70
|
+
return errors;
|
|
71
|
+
}
|
|
72
|
+
function getSafetyErrors(conditions, metadata, files) {
|
|
73
|
+
if (isBoolean(conditions))
|
|
74
|
+
return conditions ? [] : ["Safety is disabled."];
|
|
75
|
+
return conditions.flatMap(([expected, condition], index) => {
|
|
76
|
+
const errors = getConditionErrors(condition, metadata, files);
|
|
77
|
+
if (!errors.length === expected)
|
|
78
|
+
return [];
|
|
79
|
+
if (expected)
|
|
80
|
+
return errors;
|
|
81
|
+
return [`Safety condition ${index + 1} unexpectedly matched.`];
|
|
82
|
+
});
|
|
83
|
+
}
|
|
25
84
|
export async function checkPr() {
|
|
26
85
|
this.context.abort.throwIfAborted();
|
|
27
86
|
await this.updateState({ status: "running" });
|
|
@@ -31,26 +90,7 @@ export async function checkPr() {
|
|
|
31
90
|
throw new MagiError("blocked", `PR is not open.`);
|
|
32
91
|
if (metadata.draft)
|
|
33
92
|
throw new MagiError("blocked", `PR is a draft.`);
|
|
34
|
-
const errors =
|
|
35
|
-
if (this.config.review.safety.allowAuthors.length)
|
|
36
|
-
if (!this.config.review.safety.allowAuthors.includes(metadata.user.login))
|
|
37
|
-
errors.push(`Author is not allowed: ${metadata.user.login}.`);
|
|
38
|
-
if (this.config.review.safety.requiredLabels.length) {
|
|
39
|
-
const missingLabels = this.config.review.safety.requiredLabels.filter((label) => !metadata.labels.some(({ name }) => name === label));
|
|
40
|
-
if (missingLabels.length)
|
|
41
|
-
errors.push(`Required labels missing: ${missingLabels.join(", ")}.`);
|
|
42
|
-
}
|
|
43
|
-
if (isNumber(this.config.review.safety.maxChangedFiles) &&
|
|
44
|
-
metadata.changed_files > this.config.review.safety.maxChangedFiles)
|
|
45
|
-
errors.push(`Changed files exceed limit: ${metadata.changed_files} > ${this.config.review.safety.maxChangedFiles}.`);
|
|
46
|
-
if (this.config.review.safety.blockedPaths.length) {
|
|
47
|
-
const isBlocked = picomatch(this.config.review.safety.blockedPaths, {
|
|
48
|
-
dot: true,
|
|
49
|
-
});
|
|
50
|
-
const blocked = files.filter((file) => isBlocked(file));
|
|
51
|
-
if (blocked.length)
|
|
52
|
-
errors.push(`Blocked paths changed: ${blocked.join(", ")}.`);
|
|
53
|
-
}
|
|
93
|
+
const errors = getSafetyErrors(this.config.review.safety, metadata, files);
|
|
54
94
|
if (errors.length)
|
|
55
95
|
throw new MagiError("blocked", `PR is safety blocked. ${errors.join(" ")}`);
|
|
56
96
|
if (this.config.mode === "single") {
|
|
@@ -69,8 +69,6 @@ export async function checkExistingReviews() {
|
|
|
69
69
|
for (const [id, reviewer] of Object.entries(reviewers)) {
|
|
70
70
|
if (reviewer.status !== "skip")
|
|
71
71
|
continue;
|
|
72
|
-
if (!reviewer.review)
|
|
73
|
-
throw new MagiError("blocked", `No review found for reviewer ${id}.`);
|
|
74
72
|
const single = this.config.mode === "single";
|
|
75
73
|
const author = single ? this.config.account : reviewer.account;
|
|
76
74
|
const hasUserReply = threads.some(({ comments, isResolved }) => {
|
|
@@ -86,7 +84,7 @@ export async function checkExistingReviews() {
|
|
|
86
84
|
const last = comments.at(-1);
|
|
87
85
|
return (!!last &&
|
|
88
86
|
last.author?.login !== author &&
|
|
89
|
-
(!reviewer.review
|
|
87
|
+
(!reviewer.review.submitted_at ||
|
|
90
88
|
last.createdAt.localeCompare(reviewer.review.submitted_at) > 0));
|
|
91
89
|
});
|
|
92
90
|
if (hasUserReply) {
|
|
@@ -243,8 +241,6 @@ export async function getInlineCommentTargets() {
|
|
|
243
241
|
return inlineCommentTargets;
|
|
244
242
|
}
|
|
245
243
|
async function hasCommit(sha) {
|
|
246
|
-
if (!this.state.worktree)
|
|
247
|
-
throw new MagiError("blocked", "PR worktree not found.");
|
|
248
244
|
try {
|
|
249
245
|
await this.exec(command("git", "cat-file", "-e", quote(`${sha}^{commit}`)), {
|
|
250
246
|
cwd: this.state.worktree.path,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { MagiError } from "@/magi";
|
|
3
3
|
import { createExecWithGitHubApiRetry, quote } from "@/utils";
|
|
4
|
-
import { automate, postReviews } from "./action";
|
|
4
|
+
import { automate, isAutomationEnabled, postReviews } from "./action";
|
|
5
5
|
import { checkCi, checkPr, classifyChecks, rerunChecks } from "./check";
|
|
6
6
|
import { checkExistingReviews, fetchReviewContext } from "./context";
|
|
7
7
|
import { createReport } from "./report";
|
|
@@ -62,6 +62,7 @@ export class Review {
|
|
|
62
62
|
validateFindings = validateFindings;
|
|
63
63
|
reconsiderClose = reconsiderClose;
|
|
64
64
|
postReviews = postReviews;
|
|
65
|
+
isAutomationEnabled = isAutomationEnabled;
|
|
65
66
|
automate = automate;
|
|
66
67
|
createReport = createReport;
|
|
67
68
|
async cleanup() {
|
package/dist/utils/function.js
CHANGED
|
@@ -12,6 +12,15 @@ export function wait(ms = 0, signal) {
|
|
|
12
12
|
signal?.addEventListener("abort", abort, { once: true });
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
|
+
export async function ignoreError(cb, shouldIgnore) {
|
|
16
|
+
try {
|
|
17
|
+
return await cb();
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
if (!shouldIgnore(e))
|
|
21
|
+
throw e;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
15
24
|
export async function loop(cb, ms = 0) {
|
|
16
25
|
for (;;) {
|
|
17
26
|
const value = await cb();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-magi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Multi-agent PR review and merge orchestration plugin for OpenCode.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Hirotomo Yamada <hirotomo.yamada@avap.co.jp>",
|
|
@@ -28,40 +28,40 @@
|
|
|
28
28
|
"schema.json"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@opencode-ai/plugin": "^1.18.
|
|
32
|
-
"@opencode-ai/sdk": "^1.18.
|
|
31
|
+
"@opencode-ai/plugin": "^1.18.4",
|
|
32
|
+
"@opencode-ai/sdk": "^1.18.4",
|
|
33
33
|
"ajv": "^8.20.0",
|
|
34
|
-
"graphql": "^
|
|
35
|
-
"graphql-tag": "^2.12.
|
|
34
|
+
"graphql": "^17.0.2",
|
|
35
|
+
"graphql-tag": "^2.12.7",
|
|
36
36
|
"octokit": "^5.0.5",
|
|
37
|
-
"picomatch": "^4.0.
|
|
38
|
-
"valibot": "^1.4.
|
|
37
|
+
"picomatch": "^4.0.5",
|
|
38
|
+
"valibot": "^1.4.2"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@changesets/changelog-github": "^0.7.0",
|
|
42
|
-
"@changesets/cli": "^2.
|
|
43
|
-
"@commitlint/cli": "^21.
|
|
44
|
-
"@commitlint/config-conventional": "^21.0
|
|
45
|
-
"@graphql-codegen/cli": "^7.
|
|
42
|
+
"@changesets/cli": "^2.31.1",
|
|
43
|
+
"@commitlint/cli": "^21.2.1",
|
|
44
|
+
"@commitlint/config-conventional": "^21.2.0",
|
|
45
|
+
"@graphql-codegen/cli": "^7.2.0",
|
|
46
46
|
"@graphql-codegen/typescript-generic-sdk": "^5.0.1",
|
|
47
|
-
"@graphql-codegen/typescript-operations": "^6.
|
|
47
|
+
"@graphql-codegen/typescript-operations": "^6.1.1",
|
|
48
48
|
"@limegrass/eslint-plugin-import-alias": "^1.6.1",
|
|
49
49
|
"@octokit/graphql-schema": "^15.26.1",
|
|
50
50
|
"@octokit/plugin-throttling": "^11.0.3",
|
|
51
51
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
52
|
-
"@types/node": "^
|
|
52
|
+
"@types/node": "^26.1.1",
|
|
53
53
|
"@types/picomatch": "^4.0.3",
|
|
54
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
55
|
-
"@vitest/coverage-v8": "^4.1.
|
|
56
|
-
"@vitest/ui": "^4.1.
|
|
57
|
-
"eslint-plugin-perfectionist": "^5.
|
|
54
|
+
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
55
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
56
|
+
"@vitest/ui": "^4.1.10",
|
|
57
|
+
"eslint-plugin-perfectionist": "^5.10.0",
|
|
58
58
|
"eslint-plugin-unused-imports": "^4.4.1",
|
|
59
|
-
"lefthook": "^2.1.
|
|
60
|
-
"oxfmt": "^0.
|
|
61
|
-
"oxlint": "^1.
|
|
62
|
-
"oxlint-tsgolint": "^0.
|
|
59
|
+
"lefthook": "^2.1.10",
|
|
60
|
+
"oxfmt": "^0.60.0",
|
|
61
|
+
"oxlint": "^1.75.0",
|
|
62
|
+
"oxlint-tsgolint": "^7.0.2001",
|
|
63
63
|
"rimraf": "^6.1.3",
|
|
64
|
-
"vitest": "^4.1.
|
|
64
|
+
"vitest": "^4.1.10"
|
|
65
65
|
},
|
|
66
66
|
"scripts": {
|
|
67
67
|
"clean": "rimraf node_modules dist coverage",
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
"lint:fix": "oxlint . --max-warnings=0 --fix",
|
|
75
75
|
"type:check": "tsgo --noEmit",
|
|
76
76
|
"test": "vitest run --passWithNoTests",
|
|
77
|
+
"test:coverage": "vitest run --coverage --coverage.reporter=html && open coverage/index.html",
|
|
77
78
|
"test:dev": "vitest --watch --ui --passWithNoTests",
|
|
78
79
|
"quality": "pnpm format:check && pnpm lint:check && pnpm type:check && pnpm test",
|
|
79
80
|
"release": "changeset publish",
|
package/schema.json
CHANGED
|
@@ -174,21 +174,125 @@
|
|
|
174
174
|
"variant": { "type": "string" }
|
|
175
175
|
}
|
|
176
176
|
},
|
|
177
|
+
"safetyFilter": {
|
|
178
|
+
"type": "object",
|
|
179
|
+
"additionalProperties": false,
|
|
180
|
+
"minProperties": 1,
|
|
181
|
+
"properties": {
|
|
182
|
+
"exclude": { "type": "array", "items": { "type": "string" } },
|
|
183
|
+
"include": { "type": "array", "items": { "type": "string" } }
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"safetyBranchFilter": {
|
|
187
|
+
"type": "object",
|
|
188
|
+
"additionalProperties": false,
|
|
189
|
+
"minProperties": 1,
|
|
190
|
+
"properties": {
|
|
191
|
+
"base": { "$ref": "#/$defs/safetyFilter" },
|
|
192
|
+
"head": { "$ref": "#/$defs/safetyFilter" }
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
"safety": {
|
|
196
|
+
"type": "object",
|
|
197
|
+
"additionalProperties": false,
|
|
198
|
+
"properties": {
|
|
199
|
+
"authors": { "$ref": "#/$defs/safetyFilter" },
|
|
200
|
+
"branches": { "$ref": "#/$defs/safetyBranchFilter" },
|
|
201
|
+
"labels": { "$ref": "#/$defs/safetyFilter" },
|
|
202
|
+
"maxChangedFiles": { "type": "integer", "minimum": 0 },
|
|
203
|
+
"paths": { "$ref": "#/$defs/safetyFilter" }
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
"conditions": {
|
|
207
|
+
"oneOf": [
|
|
208
|
+
{ "type": "boolean" },
|
|
209
|
+
{
|
|
210
|
+
"type": "array",
|
|
211
|
+
"items": {
|
|
212
|
+
"type": "array",
|
|
213
|
+
"minItems": 2,
|
|
214
|
+
"maxItems": 2,
|
|
215
|
+
"prefixItems": [{ "type": "boolean" }, { "$ref": "#/$defs/safety" }]
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
},
|
|
220
|
+
"automationSafety": {
|
|
221
|
+
"type": "object",
|
|
222
|
+
"additionalProperties": false,
|
|
223
|
+
"properties": {
|
|
224
|
+
"authors": { "$ref": "#/$defs/safetyFilter" },
|
|
225
|
+
"branches": { "$ref": "#/$defs/safetyBranchFilter" },
|
|
226
|
+
"labels": { "$ref": "#/$defs/safetyFilter" },
|
|
227
|
+
"paths": { "$ref": "#/$defs/safetyFilter" }
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
"automationConditions": {
|
|
231
|
+
"oneOf": [
|
|
232
|
+
{ "type": "boolean" },
|
|
233
|
+
{
|
|
234
|
+
"type": "array",
|
|
235
|
+
"items": {
|
|
236
|
+
"type": "array",
|
|
237
|
+
"minItems": 2,
|
|
238
|
+
"maxItems": 2,
|
|
239
|
+
"prefixItems": [
|
|
240
|
+
{ "type": "boolean" },
|
|
241
|
+
{ "$ref": "#/$defs/automationSafety" }
|
|
242
|
+
]
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
]
|
|
246
|
+
},
|
|
247
|
+
"mergeAutomationConditions": {
|
|
248
|
+
"oneOf": [
|
|
249
|
+
{ "type": "boolean" },
|
|
250
|
+
{
|
|
251
|
+
"type": "array",
|
|
252
|
+
"items": {
|
|
253
|
+
"type": "array",
|
|
254
|
+
"minItems": 2,
|
|
255
|
+
"maxItems": 2,
|
|
256
|
+
"prefixItems": [
|
|
257
|
+
{ "type": "boolean" },
|
|
258
|
+
{ "$ref": "#/$defs/mergeAutomationSafety" }
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
]
|
|
263
|
+
},
|
|
264
|
+
"mergeAutomationSafety": {
|
|
265
|
+
"type": "object",
|
|
266
|
+
"additionalProperties": false,
|
|
267
|
+
"properties": {
|
|
268
|
+
"authors": { "$ref": "#/$defs/safetyFilter" },
|
|
269
|
+
"branches": { "$ref": "#/$defs/safetyBranchFilter" },
|
|
270
|
+
"edited": { "type": "boolean" },
|
|
271
|
+
"labels": { "$ref": "#/$defs/safetyFilter" },
|
|
272
|
+
"paths": { "$ref": "#/$defs/safetyFilter" }
|
|
273
|
+
}
|
|
274
|
+
},
|
|
177
275
|
"automation": {
|
|
178
276
|
"type": "object",
|
|
179
277
|
"additionalProperties": false,
|
|
180
278
|
"properties": {
|
|
181
|
-
"merge": { "
|
|
182
|
-
"close": { "
|
|
279
|
+
"merge": { "$ref": "#/$defs/automationConditions", "default": true },
|
|
280
|
+
"close": { "$ref": "#/$defs/automationConditions", "default": false }
|
|
183
281
|
}
|
|
184
282
|
},
|
|
185
283
|
"mergeAutomation": {
|
|
186
284
|
"type": "object",
|
|
187
285
|
"additionalProperties": false,
|
|
188
286
|
"properties": {
|
|
189
|
-
"merge": {
|
|
190
|
-
|
|
191
|
-
|
|
287
|
+
"merge": {
|
|
288
|
+
"$ref": "#/$defs/mergeAutomationConditions",
|
|
289
|
+
"default": true
|
|
290
|
+
},
|
|
291
|
+
"close": {
|
|
292
|
+
"$ref": "#/$defs/mergeAutomationConditions",
|
|
293
|
+
"default": false
|
|
294
|
+
},
|
|
295
|
+
"conflict": { "$ref": "#/$defs/automationConditions", "default": false }
|
|
192
296
|
}
|
|
193
297
|
},
|
|
194
298
|
"reviewChecks": {
|
|
@@ -222,16 +326,6 @@
|
|
|
222
326
|
"runs": { "type": "integer", "minimum": 1, "default": 3 }
|
|
223
327
|
}
|
|
224
328
|
},
|
|
225
|
-
"safety": {
|
|
226
|
-
"type": "object",
|
|
227
|
-
"additionalProperties": false,
|
|
228
|
-
"properties": {
|
|
229
|
-
"allowAuthors": { "type": "array", "items": { "type": "string" } },
|
|
230
|
-
"blockedPaths": { "type": "array", "items": { "type": "string" } },
|
|
231
|
-
"maxChangedFiles": { "type": "integer", "minimum": 0 },
|
|
232
|
-
"requiredLabels": { "type": "array", "items": { "type": "string" } }
|
|
233
|
-
}
|
|
234
|
-
},
|
|
235
329
|
"reviewPrompts": {
|
|
236
330
|
"type": "object",
|
|
237
331
|
"additionalProperties": false,
|
|
@@ -376,7 +470,7 @@
|
|
|
376
470
|
},
|
|
377
471
|
"prompts": { "$ref": "#/$defs/reviewPrompts" },
|
|
378
472
|
"checks": { "$ref": "#/$defs/reviewChecks" },
|
|
379
|
-
"safety": { "$ref": "#/$defs/
|
|
473
|
+
"safety": { "$ref": "#/$defs/conditions" },
|
|
380
474
|
"automation": { "$ref": "#/$defs/automation" },
|
|
381
475
|
"concurrency": { "$ref": "#/$defs/concurrency" },
|
|
382
476
|
"merge": { "$ref": "#/$defs/reviewMerge" },
|