next-issue 0.16.0 → 0.17.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 +11 -3
- package/dist/args.mjs +5 -0
- package/dist/cli.mjs +1 -1
- package/dist/github.mjs +27 -5
- package/dist/pipeline.mjs +6 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -58,8 +58,10 @@ For each open issue, oldest first:
|
|
|
58
58
|
round gets no review. Thus `maxReviewRounds` of `1` never fixes a minor
|
|
59
59
|
finding.
|
|
60
60
|
10. No open finding means approval: mark the pull request ready for review and
|
|
61
|
-
set `status:done`. The merge stays with you
|
|
62
|
-
|
|
61
|
+
set `status:done`. The merge stays with you, unless you pass `--merge`:
|
|
62
|
+
then the harness squash-merges the pull request and deletes the remote
|
|
63
|
+
branch. The worktree, the local branch and the state file go away. Without
|
|
64
|
+
`--merge`, the remote branch stays. A problem that the
|
|
63
65
|
reviewer leaves out of the findings thus gets an approval: only the findings
|
|
64
66
|
count.
|
|
65
67
|
11. With an open finding, run the fixer agent and go to step 6 again. The
|
|
@@ -91,9 +93,14 @@ when:
|
|
|
91
93
|
ping-pong between the fixer and the reviewer, or a finding that the fixer
|
|
92
94
|
disputed and the reviewer holds to. The person then decides;
|
|
93
95
|
- the reviewer gives no verdict, or the checks do not finish in time;
|
|
96
|
+
- with `--merge`, the merge of an approved pull request fails, for example
|
|
97
|
+
because branch protection asks for a human approval;
|
|
94
98
|
- an agent does not finish in `agentTimeoutMinutes`;
|
|
95
99
|
- the setup command fails, or does not finish in `setupTimeoutMinutes`;
|
|
96
|
-
- a step of the run fails with an error.
|
|
100
|
+
- a step of the run fails with an error. A `gh` call that is safe to repeat,
|
|
101
|
+
such as a read or a label edit, is tried up to three times first when the
|
|
102
|
+
network fails. A `gh pr create` or `gh pr comment` is tried once, so a lost
|
|
103
|
+
reply never makes a duplicate.
|
|
97
104
|
|
|
98
105
|
The fixer sees the findings of all earlier rounds, not only the last one, with
|
|
99
106
|
its own disputes next to them. From round two, the reviewer judges only the
|
|
@@ -335,6 +342,7 @@ that a label still holds back stays as it was.
|
|
|
335
342
|
| `--once` | Stop after the first handled issue |
|
|
336
343
|
| `--max <n>` | Handle at most n issues |
|
|
337
344
|
| `--agent <a>` | Run the agents with `claude` or `pi`, whatever the config file says |
|
|
345
|
+
| `--merge` | Squash-merge the pull request after the approval, and delete the remote branch |
|
|
338
346
|
| `--reset` | Drop the saved budgets and findings, and keep the rest of the state |
|
|
339
347
|
| `--json` | Print the machine summary instead of the text one |
|
|
340
348
|
| `--verbose` | Show every command and all agent output |
|
package/dist/args.mjs
CHANGED
|
@@ -6,6 +6,7 @@ export const USAGE = `next-issue [command] [options]
|
|
|
6
6
|
--once stop after the first handled issue
|
|
7
7
|
--max <n> handle at most n issues
|
|
8
8
|
--agent <a> run the agents with claude or pi
|
|
9
|
+
--merge squash-merge the pull request after the approval
|
|
9
10
|
--reset drop the saved budgets and findings first
|
|
10
11
|
--json print the machine summary instead of the text one
|
|
11
12
|
--force replace an existing config file, with init
|
|
@@ -21,6 +22,7 @@ export function parseArgs(argv) {
|
|
|
21
22
|
json: false,
|
|
22
23
|
reset: false,
|
|
23
24
|
force: false,
|
|
25
|
+
merge: false,
|
|
24
26
|
level: "normal",
|
|
25
27
|
};
|
|
26
28
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -40,6 +42,9 @@ export function parseArgs(argv) {
|
|
|
40
42
|
else if (arg === "--help") {
|
|
41
43
|
args.help = true;
|
|
42
44
|
}
|
|
45
|
+
else if (arg === "--merge") {
|
|
46
|
+
args.merge = true;
|
|
47
|
+
}
|
|
43
48
|
else if (arg === "--reset") {
|
|
44
49
|
args.reset = true;
|
|
45
50
|
}
|
package/dist/cli.mjs
CHANGED
|
@@ -62,7 +62,7 @@ async function init(root, force) {
|
|
|
62
62
|
}
|
|
63
63
|
async function handle(args, config, repo, recorder) {
|
|
64
64
|
const base = await defaultBranch(repo);
|
|
65
|
-
const context = { repo, config, base, reset: args.reset, recorder, ports: PORTS };
|
|
65
|
+
const context = { repo, config, base, reset: args.reset, merge: args.merge, recorder, ports: PORTS };
|
|
66
66
|
recorder.event("run.start", {
|
|
67
67
|
version: await packageVersion(),
|
|
68
68
|
repo: `${repo.owner}/${repo.name}`,
|
package/dist/github.mjs
CHANGED
|
@@ -1,16 +1,34 @@
|
|
|
1
1
|
import { must, run } from "./exec.mjs";
|
|
2
2
|
const CHECK_PENDING_CODE = 8;
|
|
3
3
|
const READ_TRIES = 3;
|
|
4
|
+
const NETWORK_TRIES = 3;
|
|
5
|
+
const NETWORK_FAULT = /unexpected EOF|connection reset|connection refused|i\/o timeout|TLS handshake timeout|HTTP 50[234]/i;
|
|
4
6
|
const KNOWN_LABELS = new Map();
|
|
5
7
|
const NO_CHECKS = /no checks reported/i;
|
|
6
8
|
function slug(repo) {
|
|
7
9
|
return `${repo.owner}/${repo.name}`;
|
|
8
10
|
}
|
|
9
|
-
async function
|
|
11
|
+
async function repeatable(args, options) {
|
|
12
|
+
for (let attempt = 1;; attempt += 1) {
|
|
13
|
+
try {
|
|
14
|
+
return await must("gh", args, options);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (attempt >= NETWORK_TRIES || !(error instanceof Error) || !NETWORK_FAULT.test(error.message)) {
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
await new Promise((resolve) => setTimeout(resolve, attempt * 1000));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function gh(repo, args) {
|
|
25
|
+
return repeatable([...args, "--repo", slug(repo)], { cwd: repo.root });
|
|
26
|
+
}
|
|
27
|
+
async function ghOnce(repo, args, input) {
|
|
10
28
|
return must("gh", [...args, "--repo", slug(repo)], { cwd: repo.root, input });
|
|
11
29
|
}
|
|
12
30
|
export async function defaultBranch(repo) {
|
|
13
|
-
const raw = await
|
|
31
|
+
const raw = await repeatable(["repo", "view", slug(repo), "--json", "defaultBranchRef"], {
|
|
14
32
|
cwd: repo.root,
|
|
15
33
|
});
|
|
16
34
|
return JSON.parse(raw).defaultBranchRef.name;
|
|
@@ -45,7 +63,7 @@ export async function issueComments(repo, issue) {
|
|
|
45
63
|
return parsed.comments.map((comment) => `@${comment.author.login}: ${comment.body}`);
|
|
46
64
|
}
|
|
47
65
|
export async function openBlockers(repo, issue) {
|
|
48
|
-
const raw = await
|
|
66
|
+
const raw = await repeatable([
|
|
49
67
|
"api",
|
|
50
68
|
"--paginate",
|
|
51
69
|
`repos/${slug(repo)}/issues/${issue}/dependencies/blocked_by`,
|
|
@@ -100,7 +118,7 @@ export async function findPr(repo, branch) {
|
|
|
100
118
|
return parsed[0]?.number;
|
|
101
119
|
}
|
|
102
120
|
export async function createPr(repo, branch, base, title, body, draft) {
|
|
103
|
-
const created = await
|
|
121
|
+
const created = await ghOnce(repo, [
|
|
104
122
|
"pr",
|
|
105
123
|
"create",
|
|
106
124
|
"--head",
|
|
@@ -240,6 +258,10 @@ export async function markPrReady(repo, pr) {
|
|
|
240
258
|
});
|
|
241
259
|
return result.code === 0;
|
|
242
260
|
}
|
|
261
|
+
export async function mergePr(repo, pr) {
|
|
262
|
+
const result = await run("gh", ["pr", "merge", String(pr), "--squash", "--delete-branch", "--repo", slug(repo)], { cwd: repo.root });
|
|
263
|
+
return result.code === 0;
|
|
264
|
+
}
|
|
243
265
|
export async function commentOnPr(repo, pr, body) {
|
|
244
|
-
await
|
|
266
|
+
await ghOnce(repo, ["pr", "comment", String(pr), "--body-file", "-"], body);
|
|
245
267
|
}
|
package/dist/pipeline.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { CODING, REVIEWING, runAgent } from "./agents.mjs";
|
|
|
2
2
|
import { managedLabels } from "./config.mjs";
|
|
3
3
|
import { run } from "./exec.mjs";
|
|
4
4
|
import { addWorktree, branchName, commitAll, deleteBranch, diff, discardChanges, hasWorktree, isDirty, push, removeWorktree, revision, worktreePath, } from "./git.mjs";
|
|
5
|
-
import { commentOnPr, createPr, failedCheckLogs, findPr, issueComments, markPrReady, openBlockers, setLabel, waitForChecks, } from "./github.mjs";
|
|
5
|
+
import { commentOnPr, createPr, failedCheckLogs, findPr, issueComments, markPrReady, mergePr, openBlockers, setLabel, waitForChecks, } from "./github.mjs";
|
|
6
6
|
import { message } from "./observe.mjs";
|
|
7
7
|
import { capDiff, fixPrompt, implementPrompt, parseCommitSubject, parseUnrelated, reviewPrompt, } from "./prompts.mjs";
|
|
8
8
|
import { detectSetupCommand } from "./setup.mjs";
|
|
@@ -23,6 +23,7 @@ export const PORTS = {
|
|
|
23
23
|
isDirty,
|
|
24
24
|
issueComments,
|
|
25
25
|
markPrReady,
|
|
26
|
+
mergePr,
|
|
26
27
|
openBlockers,
|
|
27
28
|
push,
|
|
28
29
|
removeWorktree,
|
|
@@ -288,6 +289,10 @@ async function reviewRound(job) {
|
|
|
288
289
|
if (config.draftPullRequest && !(await ports.markPrReady(repo, pr))) {
|
|
289
290
|
log.event("pull-request.undraft.fail", { pr }, "quiet");
|
|
290
291
|
}
|
|
292
|
+
if (context.merge && !(await ports.mergePr(repo, pr))) {
|
|
293
|
+
await escalate(job, "The merge of the pull request failed.");
|
|
294
|
+
return { done: true, report: await finish(job, "needs-human", "merge failed") };
|
|
295
|
+
}
|
|
291
296
|
await setStatus(context, issue.number, pr, config.labels.done);
|
|
292
297
|
return { done: true, report: await finish(job, "done") };
|
|
293
298
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-issue",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.1",
|
|
4
4
|
"description": "Autonomous issue harness driven by the Claude Agent SDK",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"github",
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
"prepublishOnly": "npm run typecheck && npm test"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@types/node": "^26.6.
|
|
40
|
+
"@types/node": "^26.6.6",
|
|
41
41
|
"typescript": "^7.0.2"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@anthropic-ai/claude-agent-sdk": "^0.3.
|
|
44
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.316"
|
|
45
45
|
}
|
|
46
46
|
}
|