next-issue 2.4.8 → 2.4.10
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 -1
- package/dist/git.mjs +10 -0
- package/dist/github.mjs +18 -3
- package/dist/pipeline.mjs +13 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,7 +41,10 @@ and exits with code 1. For each issue:
|
|
|
41
41
|
fixes it, and the harness commits again. A `commit:` line in the reply of
|
|
42
42
|
the agent replaces the subject, so a hook that rejects the subject can pass.
|
|
43
43
|
The budget is `maxCommitFixes`. Any other git failure in the commit stops
|
|
44
|
-
the run with an error.
|
|
44
|
+
the run with an error. An implementer that makes no change hands the issue
|
|
45
|
+
to a person, unless the branch is already ahead of the base, for example
|
|
46
|
+
after the state file was lost: the run then carries on with the commits on
|
|
47
|
+
the branch, and the newest of them gives the title of the pull request.
|
|
45
48
|
5. Open a draft pull request that closes the issue. Its title is the subject
|
|
46
49
|
of the commit that starts it, so a squash merge gives a conventional
|
|
47
50
|
commit.
|
|
@@ -125,6 +128,8 @@ pull request yet, when:
|
|
|
125
128
|
ping-pong between the fixer and the reviewer, or a finding that the fixer
|
|
126
129
|
disputed and the reviewer holds to. The person then decides;
|
|
127
130
|
- the reviewer gives no verdict, or the checks do not finish in time;
|
|
131
|
+
- someone pushes to the pull request while the harness waits for the checks,
|
|
132
|
+
so the pull request no longer shows the commit that the harness pushed;
|
|
128
133
|
- with `--merge`, the merge of an approved pull request fails, for example
|
|
129
134
|
because branch protection asks for a human approval;
|
|
130
135
|
- with `--merge`, a queued merge does not finish in `checkTimeoutMinutes`, or
|
package/dist/git.mjs
CHANGED
|
@@ -157,9 +157,19 @@ export async function resetToBase(cwd, base, remote) {
|
|
|
157
157
|
await must('git', ['fetch', remote, base], { cwd });
|
|
158
158
|
await must('git', ['reset', '--hard', `${remote}/${base}`], { cwd });
|
|
159
159
|
}
|
|
160
|
+
export async function latestSubject(cwd, base, remote) {
|
|
161
|
+
const subject = await must('git', ['log', '-1', '--format=%s', `${remote}/${base}..HEAD`], {
|
|
162
|
+
cwd,
|
|
163
|
+
});
|
|
164
|
+
return subject === '' ? undefined : subject;
|
|
165
|
+
}
|
|
160
166
|
export async function revision(cwd) {
|
|
161
167
|
return must('git', ['rev-parse', 'HEAD'], { cwd });
|
|
162
168
|
}
|
|
169
|
+
export async function isAncestor(cwd, ancestor, descendant) {
|
|
170
|
+
const result = await run('git', ['merge-base', '--is-ancestor', ancestor, descendant], { cwd });
|
|
171
|
+
return result.code === 0;
|
|
172
|
+
}
|
|
163
173
|
export async function deleteBranch(repo, branch) {
|
|
164
174
|
const result = await run('git', ['branch', '-D', branch], { cwd: repo.root });
|
|
165
175
|
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);
|
package/dist/pipeline.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CODING, REVIEWING, runAgent } from './agents.mjs';
|
|
2
2
|
import { managedLabels } from './config.mjs';
|
|
3
3
|
import { run } from './exec.mjs';
|
|
4
|
-
import { addWorktree, branchName, commitAll, deleteBranch, diff, discardChanges, disputeBranches, findWorktree, HookError, isDirty, push, removeWorktree, resetToBase, revision, setAside, } from './git.mjs';
|
|
4
|
+
import { addWorktree, branchName, commitAll, deleteBranch, diff, discardChanges, disputeBranches, findWorktree, HookError, isDirty, latestSubject, push, removeWorktree, resetToBase, revision, setAside, } from './git.mjs';
|
|
5
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';
|
|
@@ -25,6 +25,7 @@ export const PORTS = {
|
|
|
25
25
|
findWorktree,
|
|
26
26
|
isDirty,
|
|
27
27
|
issueComments,
|
|
28
|
+
latestSubject,
|
|
28
29
|
markPrReady,
|
|
29
30
|
mergeCommit,
|
|
30
31
|
mergePr,
|
|
@@ -211,8 +212,13 @@ async function work(context, issue, log, state, start) {
|
|
|
211
212
|
}));
|
|
212
213
|
const subject = parseCommitSubject(result.text, `fix: resolve issue #${issue.number}`);
|
|
213
214
|
if (!(await commitWork(job, before, subject, 'implementer'))) {
|
|
214
|
-
await
|
|
215
|
-
|
|
215
|
+
const earlier = await ports.latestSubject(worktree, context.base, config.remote);
|
|
216
|
+
if (earlier === undefined) {
|
|
217
|
+
await escalate(job, 'The implementer produced no change.');
|
|
218
|
+
return finish(job, 'needs-human', 'no implementation commit');
|
|
219
|
+
}
|
|
220
|
+
log.event('implement.earlier', { subject: earlier }, 'quiet');
|
|
221
|
+
state.title = earlier;
|
|
216
222
|
}
|
|
217
223
|
state.phase = 'implemented';
|
|
218
224
|
await writeState(repo, state);
|
|
@@ -302,6 +308,10 @@ async function checkGate(job) {
|
|
|
302
308
|
await escalate(job, 'The pull request conflicts with its base, so no checks ran.');
|
|
303
309
|
return { done: true, report: await finish(job, 'needs-human', 'merge conflict') };
|
|
304
310
|
}
|
|
311
|
+
if (checks === 'moved') {
|
|
312
|
+
await escalate(job, 'Someone pushed to the pull request while the harness waited for the checks.');
|
|
313
|
+
return { done: true, report: await finish(job, 'needs-human', 'pull request moved') };
|
|
314
|
+
}
|
|
305
315
|
if (checks !== 'fail') {
|
|
306
316
|
return { done: false, fixed: false };
|
|
307
317
|
}
|