next-issue 0.13.0 → 0.14.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 +25 -5
- package/dist/pipeline.mjs +23 -6
- package/dist/prompts.mjs +13 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,7 +60,12 @@ For each open issue, oldest first:
|
|
|
60
60
|
reviewer leaves out of the findings thus gets an approval: only the findings
|
|
61
61
|
count.
|
|
62
62
|
11. With an open finding, run the fixer agent and go to step 6 again. The
|
|
63
|
-
budget is `maxReviewRounds`.
|
|
63
|
+
budget is `maxReviewRounds`. A fixer that holds every finding to be wrong
|
|
64
|
+
changes nothing and gives its reason instead. The reason goes on the pull
|
|
65
|
+
request and into the next round, where it answers every finding of its own
|
|
66
|
+
round, so the reviewer withdraws them all or repeats them all. With only
|
|
67
|
+
some findings wrong, the fixer fixes the correct ones, and the next round
|
|
68
|
+
holds the rest.
|
|
64
69
|
|
|
65
70
|
## Stops for a loop
|
|
66
71
|
|
|
@@ -68,20 +73,35 @@ The harness hands the issue to a person, with the `status:needs-human` label,
|
|
|
68
73
|
when:
|
|
69
74
|
|
|
70
75
|
- the check budget or the review budget runs out;
|
|
71
|
-
- a fixer round adds no commit, so there is no progress;
|
|
76
|
+
- a fixer round adds no commit and gives no reason, so there is no progress;
|
|
72
77
|
- the fixer reports that the checks fail for a reason the change did not cause,
|
|
73
78
|
such as a test already broken on the base branch or one that fails only under
|
|
74
79
|
load. It changes nothing and the reason reaches the pull request, rather than
|
|
75
80
|
the fixer spending its whole budget on a failure it cannot fix;
|
|
81
|
+
- a fixer disputes the findings and leaves a change behind, either in the
|
|
82
|
+
worktree or in a commit of its own. A dispute carries a reason, not a diff,
|
|
83
|
+
so the harness throws none of it away: an unclean worktree stays on the
|
|
84
|
+
disk, and a commit of the fixer stays on the local branch. Neither is
|
|
85
|
+
pushed, so the pull request shows no such change. It holds the reason of the
|
|
86
|
+
dispute, and the change is on the machine that ran the harness;
|
|
76
87
|
- the reviewer repeats a finding set from an earlier round, which shows a
|
|
77
|
-
ping-pong between the fixer and the reviewer
|
|
88
|
+
ping-pong between the fixer and the reviewer, or a finding that the fixer
|
|
89
|
+
disputed and the reviewer holds to. The person then decides;
|
|
78
90
|
- the reviewer gives no verdict, or the checks do not finish in time;
|
|
79
91
|
- an agent does not finish in `agentTimeoutMinutes`;
|
|
80
92
|
- the setup command fails, or does not finish in `setupTimeoutMinutes`;
|
|
81
93
|
- a step of the run fails with an error.
|
|
82
94
|
|
|
83
|
-
The fixer sees the findings of all earlier rounds, not only the last one
|
|
84
|
-
round two, the reviewer judges only the
|
|
95
|
+
The fixer sees the findings of all earlier rounds, not only the last one, with
|
|
96
|
+
its own disputes next to them. From round two, the reviewer judges only the
|
|
97
|
+
earlier findings and any regression.
|
|
98
|
+
|
|
99
|
+
Each agent that writes to the pull request names itself in the first line of the
|
|
100
|
+
comment: `### Review: approved`, `### Review: changes requested` and
|
|
101
|
+
`### Fixer: no change`. The account is the same for all of them, so the heading,
|
|
102
|
+
not the author, tells them apart. The agents read the comments of the issue, not
|
|
103
|
+
of the pull request, so a comment of an agent never returns to a prompt: the
|
|
104
|
+
dispute of the fixer reaches the reviewer through the state file.
|
|
85
105
|
|
|
86
106
|
## Stop after the current issue
|
|
87
107
|
|
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, hasWorktree, push, removeWorktree, revision, worktreePath, } from "./git.mjs";
|
|
4
|
+
import { addWorktree, branchName, commitAll, deleteBranch, diff, discardChanges, hasWorktree, isDirty, push, removeWorktree, revision, worktreePath, } from "./git.mjs";
|
|
5
5
|
import { commentOnPr, createPr, failedCheckLogs, findPr, issueComments, markPrReady, setLabel, waitForChecks, } from "./github.mjs";
|
|
6
6
|
import { message } from "./observe.mjs";
|
|
7
7
|
import { capDiff, fixPrompt, implementPrompt, parseCommitSubject, parseUnrelated, reviewPrompt, } from "./prompts.mjs";
|
|
@@ -20,6 +20,7 @@ export const PORTS = {
|
|
|
20
20
|
failedCheckLogs,
|
|
21
21
|
findPr,
|
|
22
22
|
hasWorktree,
|
|
23
|
+
isDirty,
|
|
23
24
|
issueComments,
|
|
24
25
|
markPrReady,
|
|
25
26
|
push,
|
|
@@ -280,8 +281,18 @@ async function reviewRound(job) {
|
|
|
280
281
|
await writeState(repo, state);
|
|
281
282
|
const fix = await fixRound(job, "The reviewer requested changes.", formatFindings(open), earlier, "review");
|
|
282
283
|
if (!fix.committed) {
|
|
283
|
-
|
|
284
|
-
|
|
284
|
+
const note = fix.unrelated;
|
|
285
|
+
if (note === undefined) {
|
|
286
|
+
await escalate(job, "The fixer added no commit for the review findings.");
|
|
287
|
+
return { done: true, report: await finish(job, "needs-human", "no fix commit") };
|
|
288
|
+
}
|
|
289
|
+
if (fix.stray === true) {
|
|
290
|
+
await escalate(job, `The fixer disputed the findings and left a change behind: ${note}`);
|
|
291
|
+
return { done: true, report: await finish(job, "needs-human", "dispute with changes") };
|
|
292
|
+
}
|
|
293
|
+
state.reviewLog.at(-1).dispute = note;
|
|
294
|
+
await writeState(repo, state);
|
|
295
|
+
await ports.commentOnPr(repo, pr, `### Fixer: no change\n\n${note}`);
|
|
285
296
|
}
|
|
286
297
|
return { done: false };
|
|
287
298
|
}
|
|
@@ -322,8 +333,9 @@ async function fixRound(job, reason, detail, earlier, kind) {
|
|
|
322
333
|
}));
|
|
323
334
|
const unrelated = parseUnrelated(result.text);
|
|
324
335
|
if (unrelated !== undefined) {
|
|
325
|
-
|
|
326
|
-
|
|
336
|
+
const stray = (await ports.revision(worktree)) !== before || (await ports.isDirty(worktree));
|
|
337
|
+
log.event("fix.unrelated", { reason, note: unrelated, stray }, "quiet");
|
|
338
|
+
return { committed: false, unrelated, stray };
|
|
327
339
|
}
|
|
328
340
|
const subject = parseCommitSubject(result.text, `fix: address feedback on issue #${issue.number}`);
|
|
329
341
|
if (!(await commitWork(job, before, subject))) {
|
|
@@ -370,5 +382,10 @@ async function setStatus(context, issue, pr, label) {
|
|
|
370
382
|
}
|
|
371
383
|
}
|
|
372
384
|
function history(rounds) {
|
|
373
|
-
return rounds.map((round) =>
|
|
385
|
+
return rounds.map((round) => {
|
|
386
|
+
const head = `Round ${round.round}:\n${round.findings}`;
|
|
387
|
+
return round.dispute === undefined
|
|
388
|
+
? head
|
|
389
|
+
: `${head}\nThe fixer disputed every finding of this round: ${round.dispute}`;
|
|
390
|
+
});
|
|
374
391
|
}
|
package/dist/prompts.mjs
CHANGED
|
@@ -33,6 +33,8 @@ export function reviewPrompt(issue, comments, diff, earlier) {
|
|
|
33
33
|
"This is a later round. Judge two things only:",
|
|
34
34
|
"whether the earlier findings below are now fixed, and whether the new commits broke something.",
|
|
35
35
|
"Do not raise a new point of taste. Do not repeat a finding that is fixed.",
|
|
36
|
+
"A `The fixer disputed every finding of this round` line answers all of the findings of that round, not one of them.",
|
|
37
|
+
"Withdraw every finding of that round if the dispute is correct. Repeat them all if the dispute is wrong.",
|
|
36
38
|
`## Earlier findings\n${earlier.join("\n")}`,
|
|
37
39
|
].join("\n");
|
|
38
40
|
return [
|
|
@@ -56,6 +58,12 @@ const UNRELATED_RULE = [
|
|
|
56
58
|
"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>`.",
|
|
57
59
|
"A person then reads it. Do not weaken, skip or re-record a test to make the checks pass.",
|
|
58
60
|
].join("\n");
|
|
61
|
+
const DISPUTE_RULE = [
|
|
62
|
+
"A finding can be wrong: it can miss a convention of this repository, ask for work that the issue does not want, or describe behaviour that the code does not have.",
|
|
63
|
+
"Establish that before you change anything. If every finding is wrong, change nothing and end your final message with one line in the form `unrelated: <one sentence on why the findings are wrong>`.",
|
|
64
|
+
"If only some of the findings are wrong, fix the correct ones and change nothing for the wrong ones. Write no `unrelated` line in that round. The next round holds only the findings that are left, and you can dispute them there.",
|
|
65
|
+
"The reviewer reads that line in the next round. Do not make a change that you hold to be wrong only to end the round.",
|
|
66
|
+
].join("\n");
|
|
59
67
|
export function fixPrompt(issue, reason, detail, earlier, kind) {
|
|
60
68
|
return [
|
|
61
69
|
`You are the fixer. Correct the work for issue #${issue.number}: ${issue.title}.`,
|
|
@@ -70,7 +78,7 @@ export function fixPrompt(issue, reason, detail, earlier, kind) {
|
|
|
70
78
|
: "",
|
|
71
79
|
"## Rules",
|
|
72
80
|
"Fix the cause, not the symptom. Keep the change minimal.",
|
|
73
|
-
kind === "checks" ? UNRELATED_RULE :
|
|
81
|
+
kind === "checks" ? UNRELATED_RULE : DISPUTE_RULE,
|
|
74
82
|
COMMIT_RULES,
|
|
75
83
|
]
|
|
76
84
|
.filter((part) => part.length > 0)
|
|
@@ -83,6 +91,9 @@ export function parseUnrelated(text) {
|
|
|
83
91
|
}
|
|
84
92
|
export function parseCommitSubject(text, fallback) {
|
|
85
93
|
const match = /^commit:\s*(.+)$/im.exec(text);
|
|
86
|
-
const subject = match?.[1]
|
|
94
|
+
const subject = match?.[1]
|
|
95
|
+
?.trim()
|
|
96
|
+
.replace(/^`+|`+$/g, "")
|
|
97
|
+
.trim();
|
|
87
98
|
return subject && subject.length > 0 ? subject : fallback;
|
|
88
99
|
}
|