next-issue 2.4.7 → 2.4.9
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 +6 -3
- package/dist/git.mjs +4 -0
- package/dist/github.mjs +21 -3
- package/dist/pipeline.mjs +12 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -100,7 +100,8 @@ and exits with code 1. For each issue:
|
|
|
100
100
|
## Stops for a loop
|
|
101
101
|
|
|
102
102
|
The harness hands the issue to a person, with the `status:needs-human` label,
|
|
103
|
-
when
|
|
103
|
+
and comments the reason on the pull request, or on the issue when there is no
|
|
104
|
+
pull request yet, when:
|
|
104
105
|
|
|
105
106
|
- the check budget or the review budget runs out;
|
|
106
107
|
- a fixer round adds no commit and gives no reason, so there is no progress;
|
|
@@ -124,6 +125,8 @@ when:
|
|
|
124
125
|
ping-pong between the fixer and the reviewer, or a finding that the fixer
|
|
125
126
|
disputed and the reviewer holds to. The person then decides;
|
|
126
127
|
- the reviewer gives no verdict, or the checks do not finish in time;
|
|
128
|
+
- someone pushes to the pull request while the harness waits for the checks,
|
|
129
|
+
so the pull request no longer shows the commit that the harness pushed;
|
|
127
130
|
- with `--merge`, the merge of an approved pull request fails, for example
|
|
128
131
|
because branch protection asks for a human approval;
|
|
129
132
|
- with `--merge`, a queued merge does not finish in `checkTimeoutMinutes`, or
|
|
@@ -135,8 +138,8 @@ when:
|
|
|
135
138
|
- the setup command fails, or does not finish in `setupTimeoutMinutes`;
|
|
136
139
|
- a step of the run fails with an error. A `gh` call that is safe to repeat,
|
|
137
140
|
such as a read or a label edit, is tried up to three times first when the
|
|
138
|
-
network fails. A `gh pr create
|
|
139
|
-
reply never makes a duplicate.
|
|
141
|
+
network fails. A `gh pr create`, `gh pr comment` or `gh issue comment` is
|
|
142
|
+
tried once, so a lost reply never makes a duplicate.
|
|
140
143
|
|
|
141
144
|
The fixer sees the findings of all earlier rounds, not only the last one, with
|
|
142
145
|
its own disputes next to them. From round two, the reviewer judges only the
|
package/dist/git.mjs
CHANGED
|
@@ -160,6 +160,10 @@ export async function resetToBase(cwd, base, remote) {
|
|
|
160
160
|
export async function revision(cwd) {
|
|
161
161
|
return must('git', ['rev-parse', 'HEAD'], { cwd });
|
|
162
162
|
}
|
|
163
|
+
export async function isAncestor(cwd, ancestor, descendant) {
|
|
164
|
+
const result = await run('git', ['merge-base', '--is-ancestor', ancestor, descendant], { cwd });
|
|
165
|
+
return result.code === 0;
|
|
166
|
+
}
|
|
163
167
|
export async function deleteBranch(repo, branch) {
|
|
164
168
|
const result = await run('git', ['branch', '-D', branch], { cwd: repo.root });
|
|
165
169
|
return result.code === 0;
|
package/dist/github.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { must, run } from './exec.mjs';
|
|
2
|
+
import { isAncestor } from './git.mjs';
|
|
2
3
|
const CHECK_PENDING_CODE = 8;
|
|
3
4
|
const READ_TRIES = 3;
|
|
4
5
|
const NETWORK_TRIES = 3;
|
|
@@ -214,6 +215,9 @@ export async function pollChecks(probe, options, clock = REAL_CLOCK) {
|
|
|
214
215
|
return 'timeout';
|
|
215
216
|
}
|
|
216
217
|
const state = await read(probe, options.head, rest(), () => (shown ??= clock.now()));
|
|
218
|
+
if (state === 'behind' && shown !== undefined) {
|
|
219
|
+
return 'moved';
|
|
220
|
+
}
|
|
217
221
|
if (state instanceof Error) {
|
|
218
222
|
failures += 1;
|
|
219
223
|
if (failures >= READ_TRIES) {
|
|
@@ -234,14 +238,21 @@ export async function pollChecks(probe, options, clock = REAL_CLOCK) {
|
|
|
234
238
|
}
|
|
235
239
|
async function read(probe, head, timeoutMs, shown) {
|
|
236
240
|
try {
|
|
237
|
-
if (head !== undefined
|
|
238
|
-
|
|
241
|
+
if (head !== undefined) {
|
|
242
|
+
const current = await probe.head();
|
|
243
|
+
if (current !== head) {
|
|
244
|
+
return (await probe.lags(current)) ? 'behind' : 'moved';
|
|
245
|
+
}
|
|
239
246
|
}
|
|
240
247
|
shown();
|
|
241
248
|
if ((await probe.list()) === 'none') {
|
|
242
249
|
return 'none';
|
|
243
250
|
}
|
|
244
|
-
|
|
251
|
+
const state = await probe.watch(timeoutMs);
|
|
252
|
+
if (head !== undefined && (await probe.head()) !== head) {
|
|
253
|
+
return 'moved';
|
|
254
|
+
}
|
|
255
|
+
return state;
|
|
245
256
|
}
|
|
246
257
|
catch (error) {
|
|
247
258
|
return error instanceof Error ? error : new Error(String(error));
|
|
@@ -250,6 +261,9 @@ async function read(probe, head, timeoutMs, shown) {
|
|
|
250
261
|
export function waitForChecks(repo, pr, options) {
|
|
251
262
|
return pollChecks({
|
|
252
263
|
head: () => prHead(repo, pr),
|
|
264
|
+
lags: (head) => options.head === undefined
|
|
265
|
+
? Promise.resolve(true)
|
|
266
|
+
: isAncestor(repo.root, head, options.head),
|
|
253
267
|
list: async () => ((await readChecks(repo, pr)) === 'none' ? 'none' : 'some'),
|
|
254
268
|
watch: (timeoutMs) => watchOnce(repo, pr, options.intervalSeconds, timeoutMs, options.show),
|
|
255
269
|
}, options);
|
|
@@ -287,6 +301,7 @@ function baseState(runs) {
|
|
|
287
301
|
export function waitForBaseChecks(repo, sha, options) {
|
|
288
302
|
return pollChecks({
|
|
289
303
|
head: async () => sha,
|
|
304
|
+
lags: async () => true,
|
|
290
305
|
list: async () => ((await baseRuns(repo, sha)).length === 0 ? 'none' : 'some'),
|
|
291
306
|
watch: async () => baseState(await baseRuns(repo, sha)),
|
|
292
307
|
}, options);
|
|
@@ -363,6 +378,9 @@ export async function mergeCommit(repo, pr, options, clock = REAL_CLOCK) {
|
|
|
363
378
|
export async function reopenIssue(repo, issue) {
|
|
364
379
|
await gh(repo, ['issue', 'reopen', String(issue)]);
|
|
365
380
|
}
|
|
381
|
+
export async function commentOnIssue(repo, issue, body) {
|
|
382
|
+
await ghOnce(repo, ['issue', 'comment', String(issue), '--body-file', '-'], body);
|
|
383
|
+
}
|
|
366
384
|
export async function commentOnPr(repo, pr, body) {
|
|
367
385
|
await ghOnce(repo, ['pr', 'comment', String(pr), '--body-file', '-'], body);
|
|
368
386
|
}
|
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, disputeBranches, findWorktree, HookError, isDirty, push, removeWorktree, resetToBase, revision, setAside, } from './git.mjs';
|
|
5
|
-
import { commentOnPr, createPr, failedBaseLogs, failedCheckLogs, findPr, issueComments, markPrReady, mergeCommit, mergePr, openBlockers, openIssues, prConflicts, prMerged, reopenIssue, setLabel, viewIssue, waitForBaseChecks, waitForChecks, } from './github.mjs';
|
|
5
|
+
import { commentOnIssue, commentOnPr, createPr, failedBaseLogs, failedCheckLogs, findPr, issueComments, markPrReady, mergeCommit, mergePr, openBlockers, openIssues, prConflicts, prMerged, reopenIssue, setLabel, viewIssue, waitForBaseChecks, waitForChecks, } from './github.mjs';
|
|
6
6
|
import { message } from './observe.mjs';
|
|
7
7
|
import { capDiff, fixPrompt, hookPrompt, implementPrompt, parseCommitSubject, parseUnrelated, reviewPrompt, } from './prompts.mjs';
|
|
8
8
|
import { detectSetupCommand } from './setup.mjs';
|
|
@@ -10,6 +10,7 @@ import { dropState, readState, resetState, savedIssues, writeState, } from './st
|
|
|
10
10
|
import { blockingFindings, fingerprint, formatFindings, formatVerdict, openFindings, readVerdict, VERDICT_SCHEMA, } from './verdict.mjs';
|
|
11
11
|
export const PORTS = {
|
|
12
12
|
addWorktree,
|
|
13
|
+
commentOnIssue,
|
|
13
14
|
commentOnPr,
|
|
14
15
|
commitAll,
|
|
15
16
|
createPr,
|
|
@@ -301,6 +302,10 @@ async function checkGate(job) {
|
|
|
301
302
|
await escalate(job, 'The pull request conflicts with its base, so no checks ran.');
|
|
302
303
|
return { done: true, report: await finish(job, 'needs-human', 'merge conflict') };
|
|
303
304
|
}
|
|
305
|
+
if (checks === 'moved') {
|
|
306
|
+
await escalate(job, 'Someone pushed to the pull request while the harness waited for the checks.');
|
|
307
|
+
return { done: true, report: await finish(job, 'needs-human', 'pull request moved') };
|
|
308
|
+
}
|
|
304
309
|
if (checks !== 'fail') {
|
|
305
310
|
return { done: false, fixed: false };
|
|
306
311
|
}
|
|
@@ -616,8 +621,12 @@ async function handOver(context, log, issue, state, reason) {
|
|
|
616
621
|
log.event('hand-over', { reason }, 'quiet');
|
|
617
622
|
await writeState(context.repo, state);
|
|
618
623
|
await setStatus(context, issue, state.pr, context.config.labels.needsHuman);
|
|
619
|
-
|
|
620
|
-
|
|
624
|
+
const body = `next-issue needs help: ${reason}`;
|
|
625
|
+
if (state.pr === undefined) {
|
|
626
|
+
await context.ports.commentOnIssue(context.repo, issue, body);
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
await context.ports.commentOnPr(context.repo, state.pr, body);
|
|
621
630
|
}
|
|
622
631
|
}
|
|
623
632
|
async function setStatus(context, issue, pr, label) {
|