next-issue 0.15.0 → 0.16.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 +6 -4
- package/dist/github.mjs +13 -0
- package/dist/pipeline.mjs +20 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,8 +22,10 @@ For each open issue, oldest first:
|
|
|
22
22
|
1. Claim the issue only when it holds the `status:todo` label and holds no
|
|
23
23
|
`status:in-progress` or `status:in-review` label. A `status:done`,
|
|
24
24
|
`status:needs-human` or `status:blocked` label always stops the claim, a
|
|
25
|
-
resume too. An issue
|
|
26
|
-
|
|
25
|
+
resume too. An issue that GitHub lists as blocked by an open issue waits,
|
|
26
|
+
because its blocker closes only when the pull request of the blocker merges.
|
|
27
|
+
An issue with saved state under `.next-issue/` resumes instead, whatever its
|
|
28
|
+
other labels and blockers.
|
|
27
29
|
2. Add `status:in-progress` to the issue.
|
|
28
30
|
3. Fetch the remote, fast-forward the local copy of the default branch when
|
|
29
31
|
that is safe, then add a git worktree at `../<repo>-issue-<n>` on the
|
|
@@ -264,8 +266,8 @@ does not know all stop the run, so a typo cannot pass without a word.
|
|
|
264
266
|
An empty `labels.ready` turns the ready requirement off. The harness then
|
|
265
267
|
claims every open issue that no other label holds back.
|
|
266
268
|
|
|
267
|
-
The log gives the reason for a skipped issue as `stop-label`, `not-ready
|
|
268
|
-
`in-flight
|
|
269
|
+
The log gives the reason for a skipped issue as `stop-label`, `not-ready`,
|
|
270
|
+
`in-flight` or `blocked`, the last one with the open blockers.
|
|
269
271
|
|
|
270
272
|
A role set to `null`, or with no entry in `models`, uses the default model of
|
|
271
273
|
the agent.
|
package/dist/github.mjs
CHANGED
|
@@ -44,6 +44,19 @@ export async function issueComments(repo, issue) {
|
|
|
44
44
|
const parsed = JSON.parse(raw);
|
|
45
45
|
return parsed.comments.map((comment) => `@${comment.author.login}: ${comment.body}`);
|
|
46
46
|
}
|
|
47
|
+
export async function openBlockers(repo, issue) {
|
|
48
|
+
const raw = await must("gh", [
|
|
49
|
+
"api",
|
|
50
|
+
"--paginate",
|
|
51
|
+
`repos/${slug(repo)}/issues/${issue}/dependencies/blocked_by`,
|
|
52
|
+
"--jq",
|
|
53
|
+
'.[] | select(.state == "open") | (.repository_url | split("/") | .[-2:] | join("/")) + "#" + (.number | tostring)',
|
|
54
|
+
], { cwd: repo.root });
|
|
55
|
+
return raw
|
|
56
|
+
.split("\n")
|
|
57
|
+
.filter((line) => line.length > 0)
|
|
58
|
+
.map((ref) => (ref.startsWith(`${slug(repo)}#`) ? ref.slice(slug(repo).length) : ref));
|
|
59
|
+
}
|
|
47
60
|
async function labelsOf(repo, kind, number) {
|
|
48
61
|
const raw = await gh(repo, [kind, "view", String(number), "--json", "labels"]);
|
|
49
62
|
const parsed = JSON.parse(raw);
|
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, hasWorktree, isDirty, push, removeWorktree, revision, worktreePath, } from "./git.mjs";
|
|
5
|
-
import { commentOnPr, createPr, failedCheckLogs, findPr, issueComments, markPrReady, setLabel, waitForChecks, } from "./github.mjs";
|
|
5
|
+
import { commentOnPr, createPr, failedCheckLogs, findPr, issueComments, markPrReady, openBlockers, setLabel, waitForChecks, } from "./github.mjs";
|
|
6
6
|
import { message } from "./observe.mjs";
|
|
7
7
|
import { capDiff, fixPrompt, implementPrompt, parseCommitSubject, parseUnrelated, reviewPrompt, } from "./prompts.mjs";
|
|
8
8
|
import { detectSetupCommand } from "./setup.mjs";
|
|
@@ -23,6 +23,7 @@ export const PORTS = {
|
|
|
23
23
|
isDirty,
|
|
24
24
|
issueComments,
|
|
25
25
|
markPrReady,
|
|
26
|
+
openBlockers,
|
|
26
27
|
push,
|
|
27
28
|
removeWorktree,
|
|
28
29
|
revision,
|
|
@@ -55,7 +56,21 @@ export async function processIssue(context, issue) {
|
|
|
55
56
|
const skip = skipReason(issue, context.config, saved !== undefined);
|
|
56
57
|
if (skip !== undefined) {
|
|
57
58
|
log.event("issue.skip", { title: issue.title, reason: skip });
|
|
58
|
-
return
|
|
59
|
+
return skipped(issue);
|
|
60
|
+
}
|
|
61
|
+
if (saved === undefined) {
|
|
62
|
+
let blockers;
|
|
63
|
+
try {
|
|
64
|
+
blockers = await context.ports.openBlockers(context.repo, issue.number);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
log.event("issue.error", { error: message(error), ms: Date.now() - started }, "quiet");
|
|
68
|
+
return { ...skipped(issue), outcome: "error", reason: message(error), ms: Date.now() - started };
|
|
69
|
+
}
|
|
70
|
+
if (blockers.length > 0) {
|
|
71
|
+
log.event("issue.skip", { title: issue.title, reason: "blocked", blockers });
|
|
72
|
+
return skipped(issue);
|
|
73
|
+
}
|
|
59
74
|
}
|
|
60
75
|
log.event("issue.start", { title: issue.title, createdAt: issue.createdAt }, "quiet");
|
|
61
76
|
const fresh = {
|
|
@@ -92,6 +107,9 @@ export async function processIssue(context, issue) {
|
|
|
92
107
|
};
|
|
93
108
|
}
|
|
94
109
|
}
|
|
110
|
+
function skipped(issue) {
|
|
111
|
+
return { issue: issue.number, outcome: "skipped", ciFixes: 0, reviewRounds: 0, ms: 0 };
|
|
112
|
+
}
|
|
95
113
|
async function work(context, issue, log, state) {
|
|
96
114
|
const started = Date.now();
|
|
97
115
|
const { repo, config, ports } = context;
|