next-issue 0.12.0 → 0.12.2
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/README.md +12 -7
- package/dist/exec.mjs +50 -1
- package/dist/pipeline.mjs +0 -1
- package/dist/prompts.mjs +1 -1
- package/dist/verdict.mjs +3 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,8 +43,11 @@ For each open issue, oldest first:
|
|
|
43
43
|
reviewer reads files and runs commands in the worktree, so it can prove a
|
|
44
44
|
finding with the tests or the type check. The harness then throws away every
|
|
45
45
|
change that the reviewer left, so no such change can reach a later commit.
|
|
46
|
-
The reviewer returns a structured verdict
|
|
47
|
-
or `minor`. The
|
|
46
|
+
The reviewer returns a structured verdict: a summary and one entry per
|
|
47
|
+
finding, each rated `blocking` or `minor`. The findings are the only ground
|
|
48
|
+
truth. The reviewer gives no approve or reject field, because such a field
|
|
49
|
+
can contradict the findings. The harness puts the result on the pull
|
|
50
|
+
request.
|
|
48
51
|
9. An open finding is any finding, blocking or minor, while a review round is
|
|
49
52
|
left. In the last round, only a blocking finding stays open: a minor finding
|
|
50
53
|
there goes on the pull request but starts no fix, because a fix in the last
|
|
@@ -52,8 +55,9 @@ For each open issue, oldest first:
|
|
|
52
55
|
finding.
|
|
53
56
|
10. No open finding means approval: mark the pull request ready for review and
|
|
54
57
|
set `status:done`. The merge stays with you. The worktree, the local branch
|
|
55
|
-
and the state file go away. The remote branch stays.
|
|
56
|
-
|
|
58
|
+
and the state file go away. The remote branch stays. A problem that the
|
|
59
|
+
reviewer leaves out of the findings thus gets an approval: only the findings
|
|
60
|
+
count.
|
|
57
61
|
11. With an open finding, run the fixer agent and go to step 6 again. The
|
|
58
62
|
budget is `maxReviewRounds`.
|
|
59
63
|
|
|
@@ -85,9 +89,10 @@ only between two issues, finishes the issue that it holds, deletes the file and
|
|
|
85
89
|
then writes the summary as usual. A file from an earlier run is dropped at the
|
|
86
90
|
start, so it stops nothing.
|
|
87
91
|
|
|
88
|
-
`Ctrl-C` is different: the
|
|
89
|
-
|
|
90
|
-
the
|
|
92
|
+
`Ctrl-C` is different: the harness passes the signal on to the command that runs
|
|
93
|
+
and to every child of it, so the run dies in the middle of a step. The saved
|
|
94
|
+
state lets the next run continue the issue, but the issue keeps
|
|
95
|
+
`status:in-progress` and the run writes no summary.
|
|
91
96
|
|
|
92
97
|
Every status label that the harness puts on the issue goes on the pull request
|
|
93
98
|
too, from the first review round. Thus a draft pull request shows work that the
|
package/dist/exec.mjs
CHANGED
|
@@ -2,6 +2,41 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
let observer;
|
|
3
3
|
let defaultTimeoutMs = 10 * 60_000;
|
|
4
4
|
const STDERR_TAIL = 200_000;
|
|
5
|
+
const groups = new Set();
|
|
6
|
+
function forward(signal) {
|
|
7
|
+
for (const pid of groups) {
|
|
8
|
+
try {
|
|
9
|
+
process.kill(-pid, signal);
|
|
10
|
+
}
|
|
11
|
+
catch { }
|
|
12
|
+
}
|
|
13
|
+
groups.clear();
|
|
14
|
+
process.off("SIGINT", onInterrupt);
|
|
15
|
+
process.off("SIGTERM", onTerminate);
|
|
16
|
+
process.kill(process.pid, signal);
|
|
17
|
+
}
|
|
18
|
+
const onInterrupt = () => forward("SIGINT");
|
|
19
|
+
const onTerminate = () => forward("SIGTERM");
|
|
20
|
+
function watch(pid) {
|
|
21
|
+
if (pid === undefined) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (groups.size === 0) {
|
|
25
|
+
process.on("SIGINT", onInterrupt);
|
|
26
|
+
process.on("SIGTERM", onTerminate);
|
|
27
|
+
}
|
|
28
|
+
groups.add(pid);
|
|
29
|
+
}
|
|
30
|
+
function unwatch(pid) {
|
|
31
|
+
if (pid === undefined) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
groups.delete(pid);
|
|
35
|
+
if (groups.size === 0) {
|
|
36
|
+
process.off("SIGINT", onInterrupt);
|
|
37
|
+
process.off("SIGTERM", onTerminate);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
5
40
|
export function setCommandObserver(next) {
|
|
6
41
|
observer = next;
|
|
7
42
|
}
|
|
@@ -14,12 +49,15 @@ export function run(command, args, options = {}) {
|
|
|
14
49
|
return new Promise((resolve, reject) => {
|
|
15
50
|
const child = spawn(command, args, {
|
|
16
51
|
cwd: options.cwd,
|
|
52
|
+
detached: true,
|
|
17
53
|
stdio: [
|
|
18
54
|
options.input === undefined ? "ignore" : "pipe",
|
|
19
55
|
options.show === true ? 2 : "pipe",
|
|
20
56
|
"pipe",
|
|
21
57
|
],
|
|
22
58
|
});
|
|
59
|
+
const pid = child.pid;
|
|
60
|
+
watch(pid);
|
|
23
61
|
let stdout = "";
|
|
24
62
|
let stderr = "";
|
|
25
63
|
let timedOut = false;
|
|
@@ -40,11 +78,21 @@ export function run(command, args, options = {}) {
|
|
|
40
78
|
});
|
|
41
79
|
const timer = setTimeout(() => {
|
|
42
80
|
timedOut = true;
|
|
43
|
-
|
|
81
|
+
if (pid === undefined) {
|
|
82
|
+
child.kill("SIGKILL");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
process.kill(-pid, "SIGKILL");
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
child.kill("SIGKILL");
|
|
90
|
+
}
|
|
44
91
|
}, limit);
|
|
45
92
|
let done = false;
|
|
46
93
|
const settle = (code) => {
|
|
47
94
|
clearTimeout(timer);
|
|
95
|
+
unwatch(pid);
|
|
48
96
|
if (done) {
|
|
49
97
|
return;
|
|
50
98
|
}
|
|
@@ -63,6 +111,7 @@ export function run(command, args, options = {}) {
|
|
|
63
111
|
};
|
|
64
112
|
child.on("error", (error) => {
|
|
65
113
|
clearTimeout(timer);
|
|
114
|
+
unwatch(pid);
|
|
66
115
|
done = true;
|
|
67
116
|
reject(error);
|
|
68
117
|
});
|
package/dist/pipeline.mjs
CHANGED
|
@@ -245,7 +245,6 @@ async function work(context, issue, log, state) {
|
|
|
245
245
|
const open = openFindings(verdict, state.reviewRounds >= config.maxReviewRounds);
|
|
246
246
|
log.event("verdict", {
|
|
247
247
|
round: state.reviewRounds,
|
|
248
|
-
verdict: verdict.verdict,
|
|
249
248
|
blocking: blocking.length,
|
|
250
249
|
minor: verdict.findings.length - blocking.length,
|
|
251
250
|
open: open.length,
|
package/dist/prompts.mjs
CHANGED
|
@@ -48,7 +48,7 @@ export function reviewPrompt(issue, comments, diff, earlier) {
|
|
|
48
48
|
"The harness discards each change that you make, so a change is only lost work.",
|
|
49
49
|
scope,
|
|
50
50
|
"Mark a finding blocking only for a wrong result, a missing part of the issue, a regression or a broken convention.",
|
|
51
|
-
"Give the result as the structured output:
|
|
51
|
+
"Give the result as the structured output: a short summary and one entry per finding. The findings decide the outcome, so a problem that you leave out counts as no problem.",
|
|
52
52
|
].join("\n\n");
|
|
53
53
|
}
|
|
54
54
|
const UNRELATED_RULE = [
|
package/dist/verdict.mjs
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
export const VERDICT_SCHEMA = {
|
|
2
2
|
type: "object",
|
|
3
3
|
properties: {
|
|
4
|
-
verdict: {
|
|
5
|
-
type: "string",
|
|
6
|
-
enum: ["approve", "request_changes"],
|
|
7
|
-
description: "approve when no blocking problem is left",
|
|
8
|
-
},
|
|
9
4
|
summary: { type: "string", description: "Short summary of the review" },
|
|
10
5
|
findings: {
|
|
11
6
|
type: "array",
|
|
@@ -24,18 +19,14 @@ export const VERDICT_SCHEMA = {
|
|
|
24
19
|
},
|
|
25
20
|
},
|
|
26
21
|
},
|
|
27
|
-
required: ["
|
|
22
|
+
required: ["summary", "findings"],
|
|
28
23
|
};
|
|
29
24
|
const SEVERITIES = ["blocking", "minor"];
|
|
30
|
-
const VERDICTS = ["approve", "request_changes"];
|
|
31
25
|
export function readVerdict(value) {
|
|
32
26
|
if (typeof value !== "object" || value === null) {
|
|
33
27
|
return undefined;
|
|
34
28
|
}
|
|
35
|
-
const {
|
|
36
|
-
if (typeof verdict !== "string" || !VERDICTS.includes(verdict)) {
|
|
37
|
-
return undefined;
|
|
38
|
-
}
|
|
29
|
+
const { summary, findings } = value;
|
|
39
30
|
if (typeof summary !== "string" || !Array.isArray(findings)) {
|
|
40
31
|
return undefined;
|
|
41
32
|
}
|
|
@@ -50,7 +41,7 @@ export function readVerdict(value) {
|
|
|
50
41
|
}
|
|
51
42
|
checked.push({ severity: severity, detail });
|
|
52
43
|
}
|
|
53
|
-
return {
|
|
44
|
+
return { summary, findings: checked };
|
|
54
45
|
}
|
|
55
46
|
export function blockingFindings(verdict) {
|
|
56
47
|
return verdict.findings.filter((finding) => finding.severity === "blocking");
|