next-issue 2.16.0 → 2.18.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 +22 -0
- package/dist/fix.mjs +10 -7
- package/dist/progress.mjs +19 -6
- package/dist/review.mjs +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,6 +7,28 @@ the [pi](https://github.com/earendil-works/pi) SDK. Run it inside a git reposito
|
|
|
7
7
|
npx -y next-issue@latest
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
The harness claims the oldest open issue with the `status:todo` label and gives it its own git worktree and branch. An implementer agent does the work, and the harness commits, pushes and opens a draft pull request. It then waits for the checks. A fixer agent repairs a red build, and a reviewer agent judges the diff. The fixer deals with any open finding, and the checks and review run again. With no open finding left, the pull request is marked ready and the issue gets `status:done`. When a budget runs out or the work cannot go on, the issue gets `status:needs-human` and goes to a person. Then the harness takes the next issue.
|
|
13
|
+
|
|
14
|
+
```mermaid
|
|
15
|
+
flowchart TD
|
|
16
|
+
claim[Claim the oldest status:todo issue] --> worktree[Add a worktree and branch]
|
|
17
|
+
worktree --> implement[Implementer does the work, harness commits and pushes]
|
|
18
|
+
implement --> pr[Open a draft pull request]
|
|
19
|
+
pr --> checks{Checks pass?}
|
|
20
|
+
checks -- no --> fixCi[Fixer repairs the build]
|
|
21
|
+
fixCi --> checks
|
|
22
|
+
checks -- yes --> review{Reviewer finds an open finding?}
|
|
23
|
+
review -- yes --> fixReview[Fixer deals with the findings]
|
|
24
|
+
fixReview --> checks
|
|
25
|
+
review -- no --> done[Mark ready, set status:done]
|
|
26
|
+
fixCi -. budget runs out .-> human[Hand over to a person: status:needs-human]
|
|
27
|
+
fixReview -. budget runs out .-> human
|
|
28
|
+
done --> claim
|
|
29
|
+
human --> claim
|
|
30
|
+
```
|
|
31
|
+
|
|
10
32
|
## What it does
|
|
11
33
|
|
|
12
34
|
Before each issue, fetch the open issues again and take the oldest one not yet
|
package/dist/fix.mjs
CHANGED
|
@@ -4,16 +4,19 @@ import { message } from './observe.mjs';
|
|
|
4
4
|
import { fixPrompt, hookPrompt, parseCommitSubject, parseUnrelated, } from './prompts.mjs';
|
|
5
5
|
import { writeState } from './state.mjs';
|
|
6
6
|
export async function fixRound(job, reason, detail, earlier, kind, force = false, from) {
|
|
7
|
-
const { context, issue, log, worktree, branch } = job;
|
|
8
|
-
const { ports } = context;
|
|
7
|
+
const { context, issue, log, state, worktree, branch } = job;
|
|
8
|
+
const { ports, config } = context;
|
|
9
9
|
const before = from ?? (await ports.revision(worktree));
|
|
10
|
-
const
|
|
10
|
+
const budget = kind === 'review'
|
|
11
|
+
? { round: state.reviewRounds, limit: config.maxReviewRounds }
|
|
12
|
+
: { round: state.ciFixes, limit: config.maxCiFixes };
|
|
13
|
+
const result = await log.step('fix', { reason, cause: kind, ...budget }, () => ports.runAgent(log, {
|
|
11
14
|
name: 'fixer',
|
|
12
15
|
cwd: worktree,
|
|
13
16
|
prompt: fixPrompt(issue, reason, detail, earlier, kind, job.reply),
|
|
14
17
|
profile: CODING,
|
|
15
|
-
model:
|
|
16
|
-
timeoutMs:
|
|
18
|
+
model: config.models.fixer,
|
|
19
|
+
timeoutMs: config.agentTimeoutMinutes * 60_000,
|
|
17
20
|
}));
|
|
18
21
|
const unrelated = parseUnrelated(result.text);
|
|
19
22
|
if (unrelated !== undefined) {
|
|
@@ -33,7 +36,7 @@ export async function fixRound(job, reason, detail, earlier, kind, force = false
|
|
|
33
36
|
log.event('commit.empty', { reason }, 'quiet');
|
|
34
37
|
return { committed: false };
|
|
35
38
|
}
|
|
36
|
-
await ports.push(worktree,
|
|
39
|
+
await ports.push(worktree, config.remote, branch, force);
|
|
37
40
|
return { committed: true };
|
|
38
41
|
}
|
|
39
42
|
export async function commitWork(job, before, subject, role) {
|
|
@@ -59,7 +62,7 @@ export async function commitWork(job, before, subject, role) {
|
|
|
59
62
|
await writeState(repo, state);
|
|
60
63
|
const output = message(error).slice(-config.logMaxChars);
|
|
61
64
|
log.event('commit.fail', { role, output }, 'quiet');
|
|
62
|
-
const fix = await log.step('commit-fix', { role }, () => ports.runAgent(log, {
|
|
65
|
+
const fix = await log.step('commit-fix', { role, round: state.commitFixes, limit: config.maxCommitFixes }, () => ports.runAgent(log, {
|
|
63
66
|
name: role,
|
|
64
67
|
cwd: worktree,
|
|
65
68
|
prompt: hookPrompt(issue, comments, role, output),
|
package/dist/progress.mjs
CHANGED
|
@@ -9,6 +9,20 @@ function toolLine(record) {
|
|
|
9
9
|
const line = typeof command === 'string' ? `${tool} ${command.split('\n')[0]}` : tool;
|
|
10
10
|
return line.length > TOOL_LINE_MAX ? `${line.slice(0, TOOL_LINE_MAX - 1)}…` : line;
|
|
11
11
|
}
|
|
12
|
+
function stageOf(step, record) {
|
|
13
|
+
const cause = record['cause'];
|
|
14
|
+
const name = step === 'fix' && typeof cause === 'string'
|
|
15
|
+
? `${cause} fix`
|
|
16
|
+
: step === 'commit-fix'
|
|
17
|
+
? 'commit fix'
|
|
18
|
+
: step;
|
|
19
|
+
const round = record['round'];
|
|
20
|
+
const limit = record['limit'];
|
|
21
|
+
if (typeof round !== 'number') {
|
|
22
|
+
return name;
|
|
23
|
+
}
|
|
24
|
+
return typeof limit === 'number' ? `${name} ${round}/${limit}` : `${name} ${round}`;
|
|
25
|
+
}
|
|
12
26
|
function change(progress, issue, update) {
|
|
13
27
|
return {
|
|
14
28
|
...progress,
|
|
@@ -45,16 +59,15 @@ export function applyEvent(progress, record) {
|
|
|
45
59
|
const issues = progress.issues.filter((current) => current.issue !== issue);
|
|
46
60
|
return { ...progress, issues, handled };
|
|
47
61
|
}
|
|
62
|
+
if (kind === 'issues') {
|
|
63
|
+
return { ...progress, open: Number(record['open']) };
|
|
64
|
+
}
|
|
48
65
|
if (kind === 'issues.fail') {
|
|
49
66
|
return { ...progress, error: String(record['error']) };
|
|
50
67
|
}
|
|
51
68
|
if (kind.endsWith('.start') && !NOT_STAGES.has(kind)) {
|
|
52
|
-
const stage = kind.slice(0, -'.start'.length);
|
|
53
|
-
|
|
54
|
-
return change(progress, issue, (current) => ({
|
|
55
|
-
...current,
|
|
56
|
-
stage: typeof round === 'number' ? `${stage} ${round}` : stage,
|
|
57
|
-
}));
|
|
69
|
+
const stage = stageOf(kind.slice(0, -'.start'.length), record);
|
|
70
|
+
return change(progress, issue, (current) => ({ ...current, stage }));
|
|
58
71
|
}
|
|
59
72
|
return progress;
|
|
60
73
|
}
|
package/dist/review.mjs
CHANGED