next-issue 0.12.2 → 0.12.4

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 CHANGED
@@ -28,7 +28,9 @@ For each open issue, oldest first:
28
28
  3. Fetch the remote, fast-forward the local copy of the default branch when
29
29
  that is safe, then add a git worktree at `../<repo>-issue-<n>` on the
30
30
  `issue-<n>` branch. A branch that exists on the remote is the start, and a
31
- local branch that fell behind the remote is fast-forwarded.
31
+ local branch that fell behind the remote is fast-forwarded. A fast-forward
32
+ of the base that is not safe leaves the local base branch as it is and
33
+ writes a `base.behind` event. A new branch then starts from the remote base.
32
34
  4. Let the implementer agent do the work, then commit and push.
33
35
  5. Open a draft pull request that closes the issue.
34
36
  6. Wait for the checks, once the pull request shows the pushed commit. A
package/dist/config.mjs CHANGED
@@ -26,6 +26,9 @@ const DEFAULTS = {
26
26
  skip: "status:blocked",
27
27
  },
28
28
  };
29
+ function defaults() {
30
+ return { ...DEFAULTS, models: { ...DEFAULTS.models }, labels: { ...DEFAULTS.labels } };
31
+ }
29
32
  const FIELDS = [...Object.keys(DEFAULTS), "setupCommand"];
30
33
  const WHOLE_FIELDS = [
31
34
  ["issueLimit", 1],
@@ -44,7 +47,7 @@ const WHOLE_FIELDS = [
44
47
  const MODEL_ROLES = ["implementer", "reviewer", "fixer"];
45
48
  const LABEL_NAMES = ["ready", "inProgress", "inReview", "done", "needsHuman", "skip"];
46
49
  const FILE_DEFAULTS = {
47
- ...DEFAULTS,
50
+ ...defaults(),
48
51
  models: Object.fromEntries(MODEL_ROLES.map((role) => [role, null])),
49
52
  };
50
53
  export const CONFIG_FILE = "next-issue.config.json";
@@ -69,7 +72,7 @@ export async function loadConfig(root) {
69
72
  raw = await readFile(join(root, CONFIG_FILE), "utf8");
70
73
  }
71
74
  catch {
72
- return DEFAULTS;
75
+ return defaults();
73
76
  }
74
77
  let parsed;
75
78
  try {
@@ -87,11 +90,7 @@ export function parseConfig(value) {
87
90
  throw new Error(`${CONFIG_FILE} holds an unknown field: ${key}`);
88
91
  }
89
92
  }
90
- const config = {
91
- ...DEFAULTS,
92
- models: { ...DEFAULTS.models },
93
- labels: { ...DEFAULTS.labels },
94
- };
93
+ const config = defaults();
95
94
  if (file.remote !== undefined) {
96
95
  config.remote = text(file.remote, "remote", false);
97
96
  }
package/dist/git.mjs CHANGED
@@ -32,7 +32,7 @@ export async function addWorktree(repo, issue, base, remote) {
32
32
  const branch = branchName(issue);
33
33
  const upstream = `${remote}/${branch}`;
34
34
  await must("git", ["fetch", remote], { cwd: repo.root });
35
- await updateBase(repo, base, remote);
35
+ const baseUpdated = await updateBase(repo, base, remote);
36
36
  await must("git", ["worktree", "prune"], { cwd: repo.root });
37
37
  const pushed = await hasBranch(repo, upstream, "--remotes");
38
38
  if (!(await hasWorktree(repo, path))) {
@@ -44,7 +44,7 @@ export async function addWorktree(repo, issue, base, remote) {
44
44
  if (pushed) {
45
45
  await run("git", ["merge", "--ff-only", upstream], { cwd: path });
46
46
  }
47
- return path;
47
+ return { path, baseUpdated };
48
48
  }
49
49
  async function hasBranch(repo, name, ...flags) {
50
50
  const list = await must("git", ["branch", "--list", ...flags, name], { cwd: repo.root });
package/dist/pipeline.mjs CHANGED
@@ -102,7 +102,11 @@ async function work(context, issue, log, state) {
102
102
  await writeState(repo, state);
103
103
  });
104
104
  const comments = await log.step("comments", {}, () => ports.issueComments(repo, issue.number));
105
- const worktree = await log.step("worktree", { branch }, () => ports.addWorktree(repo, issue.number, context.base, config.remote));
105
+ const added = await log.step("worktree", { branch }, () => ports.addWorktree(repo, issue.number, context.base, config.remote));
106
+ const worktree = added.path;
107
+ if (!added.baseUpdated) {
108
+ log.event("base.behind", { base: context.base }, "quiet");
109
+ }
106
110
  const job = { context, issue, log, state, branch, worktree, comments };
107
111
  const escalate = (reason) => handOver(context, log, issue.number, state, reason);
108
112
  const finish = async (outcome, reason) => {
@@ -328,7 +332,6 @@ async function cleanUp(context, log, issue) {
328
332
  }
329
333
  async function handOver(context, log, issue, state, reason) {
330
334
  log.event("hand-over", { reason }, "quiet");
331
- state.handedOver = true;
332
335
  await writeState(context.repo, state);
333
336
  await setStatus(context, issue, state.pr, context.config.labels.needsHuman);
334
337
  if (state.pr !== undefined) {
package/dist/state.mjs CHANGED
@@ -16,9 +16,7 @@ export async function readState(repo, issue) {
16
16
  }
17
17
  }
18
18
  export function resetState(state) {
19
- const next = { ...state, ciFixes: 0, reviewRounds: 0, reviewLog: [] };
20
- delete next.handedOver;
21
- return next;
19
+ return { ...state, ciFixes: 0, reviewRounds: 0, reviewLog: [] };
22
20
  }
23
21
  export async function writeState(repo, state) {
24
22
  await mkdir(dir(repo), { recursive: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-issue",
3
- "version": "0.12.2",
3
+ "version": "0.12.4",
4
4
  "description": "Autonomous issue harness driven by the Claude Agent SDK",
5
5
  "keywords": [
6
6
  "github",