next-issue 0.7.0 → 0.8.0
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 +4 -0
- package/dist/pipeline.mjs +22 -11
- package/dist/prompts.mjs +12 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,6 +50,10 @@ when:
|
|
|
50
50
|
|
|
51
51
|
- the check budget or the review budget runs out;
|
|
52
52
|
- a fixer round adds no commit, so there is no progress;
|
|
53
|
+
- the fixer reports that the checks fail for a reason the change did not cause,
|
|
54
|
+
such as a test already broken on the base branch or one that fails only under
|
|
55
|
+
load. It changes nothing and the reason reaches the pull request, rather than
|
|
56
|
+
the fixer spending its whole budget on a failure it cannot fix;
|
|
53
57
|
- the reviewer repeats a finding set from an earlier round, which shows a
|
|
54
58
|
ping-pong between the fixer and the reviewer;
|
|
55
59
|
- the reviewer gives no verdict, or the checks do not finish in time;
|
package/dist/pipeline.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { run } from "./exec.mjs";
|
|
|
4
4
|
import { addWorktree, branchName, commitAll, diff, hasWorktree, push, removeWorktree, worktreePath, } from "./git.mjs";
|
|
5
5
|
import { assignIssue, commentOnPr, createPr, failedCheckLogs, findPr, issueComments, markPrReady, setLabel, waitForChecks, } from "./github.mjs";
|
|
6
6
|
import { message } from "./observe.mjs";
|
|
7
|
-
import { capDiff, fixPrompt, implementPrompt, parseCommitSubject, reviewPrompt } from "./prompts.mjs";
|
|
7
|
+
import { capDiff, fixPrompt, implementPrompt, parseCommitSubject, parseUnrelated, reviewPrompt, } from "./prompts.mjs";
|
|
8
8
|
import { readState, writeState } from "./state.mjs";
|
|
9
9
|
import { blockingFindings, fingerprint, formatFindings, formatVerdict, isApproved, readVerdict, VERDICT_SCHEMA, } from "./verdict.mjs";
|
|
10
10
|
export const PORTS = {
|
|
@@ -172,10 +172,16 @@ async function work(context, issue, log, state) {
|
|
|
172
172
|
await writeState(repo, state);
|
|
173
173
|
const logs = await ports.failedCheckLogs(repo, branch, config.logMaxChars);
|
|
174
174
|
log.event("checks.logs", { chars: logs.length });
|
|
175
|
-
const
|
|
176
|
-
if (!committed) {
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
const fix = await fixRound(job, "The continuous integration checks failed.", logs, history(state.reviewLog), "checks");
|
|
176
|
+
if (!fix.committed) {
|
|
177
|
+
const note = fix.unrelated;
|
|
178
|
+
await escalate(note === undefined
|
|
179
|
+
? "The fixer added no commit for the failed checks."
|
|
180
|
+
: `The checks fail for a reason this change did not cause: ${note}`);
|
|
181
|
+
return {
|
|
182
|
+
done: true,
|
|
183
|
+
report: await finish("needs-human", note === undefined ? "no fix commit" : "unrelated failure"),
|
|
184
|
+
};
|
|
179
185
|
}
|
|
180
186
|
return { done: false, fixed: true };
|
|
181
187
|
};
|
|
@@ -248,32 +254,37 @@ async function work(context, issue, log, state) {
|
|
|
248
254
|
const earlier = history(state.reviewLog);
|
|
249
255
|
state.reviewLog.push({ round: state.reviewRounds, fingerprint: mark, findings: formatFindings(blocking) });
|
|
250
256
|
await writeState(repo, state);
|
|
251
|
-
const
|
|
252
|
-
if (!committed) {
|
|
257
|
+
const fix = await fixRound(job, "The reviewer requested changes.", formatFindings(blocking), earlier, "review");
|
|
258
|
+
if (!fix.committed) {
|
|
253
259
|
await escalate("The fixer added no commit for the review findings.");
|
|
254
260
|
return finish("needs-human", "no fix commit");
|
|
255
261
|
}
|
|
256
262
|
}
|
|
257
263
|
}
|
|
258
|
-
async function fixRound(job, reason, detail, earlier) {
|
|
264
|
+
async function fixRound(job, reason, detail, earlier, kind) {
|
|
259
265
|
const { context, issue, log, worktree, branch } = job;
|
|
260
266
|
const { ports } = context;
|
|
261
267
|
const result = await log.step("fix", { reason }, () => ports.runAgent(log, {
|
|
262
268
|
name: "fixer",
|
|
263
269
|
cwd: worktree,
|
|
264
|
-
prompt: fixPrompt(issue, reason, detail, earlier),
|
|
270
|
+
prompt: fixPrompt(issue, reason, detail, earlier, kind),
|
|
265
271
|
profile: CODING,
|
|
266
272
|
model: context.config.models.fixer,
|
|
267
273
|
timeoutMs: context.config.agentTimeoutMinutes * 60_000,
|
|
268
274
|
}));
|
|
275
|
+
const unrelated = parseUnrelated(result.text);
|
|
276
|
+
if (unrelated !== undefined) {
|
|
277
|
+
log.event("fix.unrelated", { reason, note: unrelated }, "quiet");
|
|
278
|
+
return { committed: false, unrelated };
|
|
279
|
+
}
|
|
269
280
|
const subject = parseCommitSubject(result.text, `fix: address feedback on issue #${issue.number}`);
|
|
270
281
|
if (!(await ports.commitAll(worktree, `${subject}\n\nRefs #${issue.number}`))) {
|
|
271
282
|
log.event("commit.empty", { reason }, "quiet");
|
|
272
|
-
return false;
|
|
283
|
+
return { committed: false };
|
|
273
284
|
}
|
|
274
285
|
log.event("commit", { subject });
|
|
275
286
|
await ports.push(worktree, context.config.remote, branch);
|
|
276
|
-
return true;
|
|
287
|
+
return { committed: true };
|
|
277
288
|
}
|
|
278
289
|
async function cleanUp(context, log, issue) {
|
|
279
290
|
const path = worktreePath(context.repo, issue);
|
package/dist/prompts.mjs
CHANGED
|
@@ -49,7 +49,12 @@ export function reviewPrompt(issue, comments, diff, round, earlier) {
|
|
|
49
49
|
"Give the result as the structured output: the verdict, a short summary and one entry per finding.",
|
|
50
50
|
].join("\n\n");
|
|
51
51
|
}
|
|
52
|
-
|
|
52
|
+
const UNRELATED_RULE = [
|
|
53
|
+
"The checks can fail for a reason this change did not cause: a test that is already broken on the base branch, one that exercises code the diff does not touch, or one that fails only under load.",
|
|
54
|
+
"Establish that before you fix anything. If the failure is not yours, change nothing and end your final message with one line in the form `unrelated: <one sentence on what fails and why it is not this change>`.",
|
|
55
|
+
"A person then reads it. Do not weaken, skip or re-record a test to make the checks pass.",
|
|
56
|
+
].join("\n");
|
|
57
|
+
export function fixPrompt(issue, reason, detail, earlier, kind) {
|
|
53
58
|
return [
|
|
54
59
|
`You are the fixer. Correct the work for issue #${issue.number}: ${issue.title}.`,
|
|
55
60
|
`## Reason\n${reason}`,
|
|
@@ -63,11 +68,17 @@ export function fixPrompt(issue, reason, detail, earlier) {
|
|
|
63
68
|
: "",
|
|
64
69
|
"## Rules",
|
|
65
70
|
"Fix the cause, not the symptom. Keep the change minimal.",
|
|
71
|
+
kind === "checks" ? UNRELATED_RULE : "",
|
|
66
72
|
COMMIT_RULES,
|
|
67
73
|
]
|
|
68
74
|
.filter((part) => part.length > 0)
|
|
69
75
|
.join("\n\n");
|
|
70
76
|
}
|
|
77
|
+
export function parseUnrelated(text) {
|
|
78
|
+
const match = /^unrelated:\s*(.+)$/im.exec(text);
|
|
79
|
+
const note = match?.[1]?.trim();
|
|
80
|
+
return note !== undefined && note.length > 0 ? note : undefined;
|
|
81
|
+
}
|
|
71
82
|
export function parseCommitSubject(text, fallback) {
|
|
72
83
|
const match = /^commit:\s*(.+)$/im.exec(text);
|
|
73
84
|
const subject = match?.[1]?.trim();
|