next-issue 2.4.5 → 2.4.7
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 +10 -4
- package/dist/git.mjs +7 -3
- package/dist/github.mjs +11 -0
- package/dist/pipeline.mjs +13 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,9 +30,12 @@ and exits with code 1. For each issue:
|
|
|
30
30
|
state keeps the branch name that the state holds, and a worktree that
|
|
31
31
|
holds that branch already stays in use. A branch that exists on the
|
|
32
32
|
remote is the start, and a local branch that fell behind the remote is
|
|
33
|
-
fast-forwarded.
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
fast-forwarded. An issue with no saved state does not reuse a local
|
|
34
|
+
`<n>` branch: it resets that branch, and any worktree that holds it, to
|
|
35
|
+
the remote branch, or to the remote base when there is none. A
|
|
36
|
+
fast-forward of the base that is not safe leaves the local base branch as
|
|
37
|
+
it is and writes a `base.behind` event. A new branch then starts from the
|
|
38
|
+
remote base.
|
|
36
39
|
4. Let the implementer agent do the work, then commit and push. When a git
|
|
37
40
|
hook stops a commit, the agent that made the change gets the hook output,
|
|
38
41
|
fixes it, and the harness commits again. A `commit:` line in the reply of
|
|
@@ -48,7 +51,10 @@ and exits with code 1. For each issue:
|
|
|
48
51
|
while no check failed counts as such a read, not as a red build. A repository that reports no
|
|
49
52
|
check keeps asking for `checkGraceSeconds`, because a new pull request often
|
|
50
53
|
shows no check yet, and only then goes straight to the review. The later
|
|
51
|
-
gates of the same issue ask once.
|
|
54
|
+
gates of the same issue ask once. GitHub runs no check on a pull request
|
|
55
|
+
that conflicts with its base, so no check and a conflict hand the issue to
|
|
56
|
+
a person. While GitHub still works out whether the pull request can merge,
|
|
57
|
+
the run asks again until `checkTimeoutMinutes`.
|
|
52
58
|
7. On a red build, give the failed job logs to the fixer agent, then go to step
|
|
53
59
|
6 again. The budget is `maxCiFixes`.
|
|
54
60
|
8. Let the reviewer agent judge the diff, at most `diffMaxChars` of it. The
|
package/dist/git.mjs
CHANGED
|
@@ -29,7 +29,7 @@ export const WORKTREE_DIR = '.worktrees';
|
|
|
29
29
|
export function worktreePath(repo, issue) {
|
|
30
30
|
return join(repo.root, WORKTREE_DIR, String(issue));
|
|
31
31
|
}
|
|
32
|
-
export async function addWorktree(repo, issue, branch, base, remote) {
|
|
32
|
+
export async function addWorktree(repo, issue, branch, base, remote, start) {
|
|
33
33
|
const upstream = `${remote}/${branch}`;
|
|
34
34
|
await must('git', ['fetch', remote], { cwd: repo.root });
|
|
35
35
|
const baseUpdated = await updateBase(repo, base, remote);
|
|
@@ -37,12 +37,16 @@ export async function addWorktree(repo, issue, branch, base, remote) {
|
|
|
37
37
|
const pushed = await hasBranch(repo, upstream, '--remotes');
|
|
38
38
|
const existing = await findWorktree(repo, branch);
|
|
39
39
|
const path = existing ?? worktreePath(repo, issue);
|
|
40
|
+
const target = pushed ? upstream : `${remote}/${base}`;
|
|
40
41
|
if (existing === undefined) {
|
|
41
|
-
const args = (await hasBranch(repo, branch))
|
|
42
|
+
const args = !start && (await hasBranch(repo, branch))
|
|
42
43
|
? ['worktree', 'add', path, branch]
|
|
43
|
-
: ['worktree', 'add', '-
|
|
44
|
+
: ['worktree', 'add', '-B', branch, path, target];
|
|
44
45
|
await must('git', args, { cwd: repo.root });
|
|
45
46
|
}
|
|
47
|
+
else if (start) {
|
|
48
|
+
await discardChanges(path, target);
|
|
49
|
+
}
|
|
46
50
|
if (pushed) {
|
|
47
51
|
await run('git', ['merge', '--ff-only', upstream], { cwd: path });
|
|
48
52
|
}
|
package/dist/github.mjs
CHANGED
|
@@ -330,6 +330,17 @@ export async function mergePr(repo, pr) {
|
|
|
330
330
|
const result = await run('gh', ['pr', 'merge', String(pr), '--squash', '--delete-branch', '--repo', slug(repo)], { cwd: repo.root });
|
|
331
331
|
return result.code === 0;
|
|
332
332
|
}
|
|
333
|
+
export async function prConflicts(repo, pr, options, clock = REAL_CLOCK) {
|
|
334
|
+
const deadline = clock.now() + options.timeoutMs;
|
|
335
|
+
for (;;) {
|
|
336
|
+
const mergeable = (await gh(repo, ['pr', 'view', String(pr), '--json', 'mergeable', '--jq', '.mergeable'])).trim();
|
|
337
|
+
const rest = deadline - clock.now();
|
|
338
|
+
if (mergeable !== 'UNKNOWN' || rest <= 0) {
|
|
339
|
+
return mergeable === 'CONFLICTING';
|
|
340
|
+
}
|
|
341
|
+
await clock.sleep(Math.min(options.intervalSeconds * 1000, rest));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
333
344
|
export async function prMerged(repo, pr) {
|
|
334
345
|
return ((await gh(repo, ['pr', 'view', String(pr), '--json', 'state', '--jq', '.state'])).trim() ===
|
|
335
346
|
'MERGED');
|
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, prMerged, reopenIssue, setLabel, viewIssue, waitForBaseChecks, waitForChecks, } from './github.mjs';
|
|
5
|
+
import { 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';
|
|
@@ -29,6 +29,7 @@ export const PORTS = {
|
|
|
29
29
|
mergePr,
|
|
30
30
|
openBlockers,
|
|
31
31
|
openIssues,
|
|
32
|
+
prConflicts,
|
|
32
33
|
prMerged,
|
|
33
34
|
push,
|
|
34
35
|
removeWorktree,
|
|
@@ -132,7 +133,7 @@ export async function processIssue(context, issue) {
|
|
|
132
133
|
};
|
|
133
134
|
const state = saved === undefined ? fresh : context.reset ? resetState(saved) : saved;
|
|
134
135
|
try {
|
|
135
|
-
const report = await work(context, issue, log, state);
|
|
136
|
+
const report = await work(context, issue, log, state, saved === undefined);
|
|
136
137
|
log.event('issue.end', { outcome: report.outcome, reason: report.reason, ms: report.ms }, 'quiet');
|
|
137
138
|
return report;
|
|
138
139
|
}
|
|
@@ -159,7 +160,7 @@ export async function processIssue(context, issue) {
|
|
|
159
160
|
function skipped(issue) {
|
|
160
161
|
return { issue: issue.number, outcome: 'skipped', ciFixes: 0, reviewRounds: 0, ms: 0 };
|
|
161
162
|
}
|
|
162
|
-
async function work(context, issue, log, state) {
|
|
163
|
+
async function work(context, issue, log, state, start) {
|
|
163
164
|
const started = Date.now();
|
|
164
165
|
const { repo, config, ports } = context;
|
|
165
166
|
const branch = state.branch;
|
|
@@ -168,7 +169,7 @@ async function work(context, issue, log, state) {
|
|
|
168
169
|
await writeState(repo, state);
|
|
169
170
|
});
|
|
170
171
|
const comments = await log.step('comments', {}, () => ports.issueComments(repo, issue.number));
|
|
171
|
-
const added = await log.step('worktree', { branch }, () => ports.addWorktree(repo, issue.number, branch, context.base, config.remote));
|
|
172
|
+
const added = await log.step('worktree', { branch }, () => ports.addWorktree(repo, issue.number, branch, context.base, config.remote, start));
|
|
172
173
|
const worktree = added.path;
|
|
173
174
|
if (!added.baseUpdated) {
|
|
174
175
|
log.event('base.behind', { base: context.base }, 'quiet');
|
|
@@ -292,6 +293,14 @@ async function checkGate(job) {
|
|
|
292
293
|
await escalate(job, 'The checks did not finish in time.');
|
|
293
294
|
return { done: true, report: await finish(job, 'needs-human', 'checks timeout') };
|
|
294
295
|
}
|
|
296
|
+
if (checks === 'none' &&
|
|
297
|
+
(await ports.prConflicts(repo, state.pr, {
|
|
298
|
+
intervalSeconds: config.checkIntervalSeconds,
|
|
299
|
+
timeoutMs: config.checkTimeoutMinutes * 60_000,
|
|
300
|
+
}))) {
|
|
301
|
+
await escalate(job, 'The pull request conflicts with its base, so no checks ran.');
|
|
302
|
+
return { done: true, report: await finish(job, 'needs-human', 'merge conflict') };
|
|
303
|
+
}
|
|
295
304
|
if (checks !== 'fail') {
|
|
296
305
|
return { done: false, fixed: false };
|
|
297
306
|
}
|