next-issue 0.11.1 → 0.12.1
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 -6
- package/dist/config.mjs +8 -2
- package/dist/exec.mjs +50 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,9 +85,10 @@ only between two issues, finishes the issue that it holds, deletes the file and
|
|
|
85
85
|
then writes the summary as usual. A file from an earlier run is dropped at the
|
|
86
86
|
start, so it stops nothing.
|
|
87
87
|
|
|
88
|
-
`Ctrl-C` is different: the
|
|
89
|
-
|
|
90
|
-
the
|
|
88
|
+
`Ctrl-C` is different: the harness passes the signal on to the command that runs
|
|
89
|
+
and to every child of it, so the run dies in the middle of a step. The saved
|
|
90
|
+
state lets the next run continue the issue, but the issue keeps
|
|
91
|
+
`status:in-progress` and the run writes no summary.
|
|
91
92
|
|
|
92
93
|
Every status label that the harness puts on the issue goes on the pull request
|
|
93
94
|
too, from the first review round. Thus a draft pull request shows work that the
|
|
@@ -193,7 +194,11 @@ does not know all stop the run, so a typo cannot pass without a word.
|
|
|
193
194
|
"diffMaxChars": 60000,
|
|
194
195
|
"keepRunDays": 7,
|
|
195
196
|
"draftPullRequest": true,
|
|
196
|
-
"models": {
|
|
197
|
+
"models": {
|
|
198
|
+
"implementer": null,
|
|
199
|
+
"reviewer": null,
|
|
200
|
+
"fixer": null
|
|
201
|
+
},
|
|
197
202
|
"labels": {
|
|
198
203
|
"ready": "status:todo",
|
|
199
204
|
"inProgress": "status:in-progress",
|
|
@@ -222,7 +227,7 @@ does not know all stop the run, so a typo cannot pass without a word.
|
|
|
222
227
|
| `keepRunDays` | `7` | The number of days to keep the run files; `0` keeps them all |
|
|
223
228
|
| `draftPullRequest` | `true` | Open the pull request as a draft, until the review approves |
|
|
224
229
|
| `setupCommand` | from the lock file | A shell command to run in a new worktree, before the implementer |
|
|
225
|
-
| `models` | `
|
|
230
|
+
| `models` | `null` per role | The model per agent role: `implementer`, `reviewer`, `fixer` |
|
|
226
231
|
| `labels` | see above | The names of the labels that the harness reads and sets |
|
|
227
232
|
|
|
228
233
|
An empty `labels.ready` turns the ready requirement off. The harness then
|
|
@@ -231,7 +236,8 @@ claims every open issue that no other label and no assignee holds back.
|
|
|
231
236
|
The log gives the reason for a skipped issue as `stop-label`, `not-ready`,
|
|
232
237
|
`in-flight` or `assigned`.
|
|
233
238
|
|
|
234
|
-
A role
|
|
239
|
+
A role set to `null`, or with no entry in `models`, uses the default model of
|
|
240
|
+
the SDK.
|
|
235
241
|
|
|
236
242
|
Without a `setupCommand`, the harness looks in the new worktree for a lock file
|
|
237
243
|
and runs the install command that goes with it:
|
package/dist/config.mjs
CHANGED
|
@@ -43,11 +43,17 @@ const WHOLE_FIELDS = [
|
|
|
43
43
|
];
|
|
44
44
|
const MODEL_ROLES = ["implementer", "reviewer", "fixer"];
|
|
45
45
|
const LABEL_NAMES = ["ready", "inProgress", "inReview", "done", "needsHuman", "skip"];
|
|
46
|
+
const FILE_DEFAULTS = {
|
|
47
|
+
...DEFAULTS,
|
|
48
|
+
models: Object.fromEntries(MODEL_ROLES.map((role) => [role, null])),
|
|
49
|
+
};
|
|
46
50
|
export const CONFIG_FILE = "next-issue.config.json";
|
|
47
51
|
export async function writeDefaultConfig(root, force) {
|
|
48
52
|
const path = join(root, CONFIG_FILE);
|
|
49
53
|
try {
|
|
50
|
-
await writeFile(path, `${JSON.stringify(
|
|
54
|
+
await writeFile(path, `${JSON.stringify(FILE_DEFAULTS, null, 2)}\n`, {
|
|
55
|
+
flag: force ? "w" : "wx",
|
|
56
|
+
});
|
|
51
57
|
}
|
|
52
58
|
catch (error) {
|
|
53
59
|
if (error.code === "EEXIST") {
|
|
@@ -111,7 +117,7 @@ export function parseConfig(value) {
|
|
|
111
117
|
}
|
|
112
118
|
}
|
|
113
119
|
for (const role of MODEL_ROLES) {
|
|
114
|
-
if (models[role] !== undefined) {
|
|
120
|
+
if (models[role] !== undefined && models[role] !== null) {
|
|
115
121
|
config.models[role] = text(models[role], `models.${role}`, false);
|
|
116
122
|
}
|
|
117
123
|
}
|
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
|
});
|