next-issue 0.5.2 → 0.6.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 CHANGED
@@ -56,6 +56,17 @@ when:
56
56
  The fixer sees the findings of all earlier rounds, not only the last one. From
57
57
  round two, the reviewer judges only the earlier findings and any regression.
58
58
 
59
+ ## Stop after the current issue
60
+
61
+ Run `touch .next-issue/stop` in a second terminal. The harness reads the file
62
+ only between two issues, finishes the issue that it holds, deletes the file and
63
+ then writes the summary as usual. A file from an earlier run is dropped at the
64
+ start, so it stops nothing.
65
+
66
+ `Ctrl-C` is different: the terminal signals every child too, so the run dies in
67
+ the middle of a step. The saved state lets the next run continue the issue, but
68
+ the issue keeps `status:in-progress` and the run writes no summary.
69
+
59
70
  Every status label that the harness puts on the issue goes on the pull request
60
71
  too, from the first review round. Thus a draft pull request shows work that the
61
72
  harness has not finished, and a `status:needs-human` pull request shows work
package/dist/args.mjs ADDED
@@ -0,0 +1,73 @@
1
+ import { CONFIG_FILE } from "./config.mjs";
2
+ export const USAGE = `next-issue [command] [options]
3
+
4
+ init write ${CONFIG_FILE} with the defaults
5
+
6
+ --issue <n> handle one issue only
7
+ --once stop after the first handled issue
8
+ --max <n> handle at most n issues
9
+ --reset drop the saved budgets and findings first
10
+ --json print the machine summary instead of the text one
11
+ --force replace an existing config file, with init
12
+ --verbose show every command and all agent output
13
+ --quiet show only the milestones and the summary
14
+ --help show this text
15
+ `;
16
+ export function parseArgs(argv) {
17
+ const args = {
18
+ command: "run",
19
+ once: false,
20
+ help: false,
21
+ json: false,
22
+ reset: false,
23
+ force: false,
24
+ level: "normal",
25
+ };
26
+ for (let index = 0; index < argv.length; index += 1) {
27
+ const arg = argv[index];
28
+ if (index === 0 && !arg.startsWith("-")) {
29
+ if (arg !== "init") {
30
+ throw new Error(`Unknown command: ${arg}`);
31
+ }
32
+ args.command = "init";
33
+ }
34
+ else if (arg === "--force") {
35
+ args.force = true;
36
+ }
37
+ else if (arg === "--json") {
38
+ args.json = true;
39
+ }
40
+ else if (arg === "--help") {
41
+ args.help = true;
42
+ }
43
+ else if (arg === "--reset") {
44
+ args.reset = true;
45
+ }
46
+ else if (arg === "--once") {
47
+ args.once = true;
48
+ }
49
+ else if (arg === "--verbose") {
50
+ args.level = "verbose";
51
+ }
52
+ else if (arg === "--quiet") {
53
+ args.level = "quiet";
54
+ }
55
+ else if (arg === "--issue") {
56
+ args.issue = whole(argv[++index], arg);
57
+ }
58
+ else if (arg === "--max") {
59
+ args.max = whole(argv[++index], arg);
60
+ }
61
+ else {
62
+ throw new Error(`Unknown argument: ${arg}`);
63
+ }
64
+ }
65
+ return args;
66
+ }
67
+ function whole(value, option) {
68
+ const parsed = Number(value);
69
+ if (value === undefined || !Number.isInteger(parsed) || parsed < 1) {
70
+ throw new Error(`${option} needs a whole number of 1 or more`);
71
+ }
72
+ return parsed;
73
+ }
package/dist/cli.mjs CHANGED
@@ -1,86 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import { writeFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
- import { CONFIG_FILE, loadConfig, writeDefaultConfig } from "./config.mjs";
4
+ import { parseArgs, USAGE } from "./args.mjs";
5
+ import { loadConfig, writeDefaultConfig } from "./config.mjs";
5
6
  import { setCommandObserver } from "./exec.mjs";
6
7
  import { detectRepo, ensureIgnored, repoRoot } from "./git.mjs";
7
8
  import { currentLogin, defaultBranch, openIssues } from "./github.mjs";
8
9
  import { message, Recorder } from "./observe.mjs";
9
10
  import { processIssue } from "./pipeline.mjs";
10
- import { clearState, STATE_DIR } from "./state.mjs";
11
+ import { clearState, STATE_DIR, takeStop } from "./state.mjs";
11
12
  import { formatSummary } from "./summary.mjs";
12
- const USAGE = `next-issue [command] [options]
13
-
14
- init write ${CONFIG_FILE} with the defaults
15
-
16
- --issue <n> handle one issue only
17
- --once stop after the first handled issue
18
- --max <n> handle at most n issues
19
- --reset drop the saved budgets and findings first
20
- --json print the machine summary instead of the text one
21
- --force replace an existing config file, with init
22
- --verbose show every command and all agent output
23
- --quiet show only the milestones and the summary
24
- --help show this text
25
- `;
26
- function parseArgs(argv) {
27
- const args = {
28
- command: "run",
29
- once: false,
30
- help: false,
31
- json: false,
32
- reset: false,
33
- force: false,
34
- level: "normal",
35
- };
36
- for (let index = 0; index < argv.length; index += 1) {
37
- const arg = argv[index];
38
- if (index === 0 && !arg.startsWith("-")) {
39
- if (arg !== "init") {
40
- throw new Error(`Unknown command: ${arg}`);
41
- }
42
- args.command = "init";
43
- }
44
- else if (arg === "--force") {
45
- args.force = true;
46
- }
47
- else if (arg === "--json") {
48
- args.json = true;
49
- }
50
- else if (arg === "--help") {
51
- args.help = true;
52
- }
53
- else if (arg === "--reset") {
54
- args.reset = true;
55
- }
56
- else if (arg === "--once") {
57
- args.once = true;
58
- }
59
- else if (arg === "--verbose") {
60
- args.level = "verbose";
61
- }
62
- else if (arg === "--quiet") {
63
- args.level = "quiet";
64
- }
65
- else if (arg === "--issue") {
66
- args.issue = whole(argv[++index], arg);
67
- }
68
- else if (arg === "--max") {
69
- args.max = whole(argv[++index], arg);
70
- }
71
- else {
72
- throw new Error(`Unknown argument: ${arg}`);
73
- }
74
- }
75
- return args;
76
- }
77
- function whole(value, option) {
78
- const parsed = Number(value);
79
- if (value === undefined || !Number.isInteger(parsed) || parsed < 1) {
80
- throw new Error(`${option} needs a whole number of 1 or more`);
81
- }
82
- return parsed;
83
- }
84
13
  function write(stream, text) {
85
14
  return new Promise((resolve, reject) => {
86
15
  stream.write(text, (error) => (error === null || error === undefined ? resolve() : reject(error)));
@@ -128,6 +57,7 @@ async function handle(args, config, repo, recorder) {
128
57
  recorder.event("run.start", { repo: `${repo.owner}/${repo.name}`, base, login, log: recorder.logFile }, "quiet");
129
58
  const issues = (await openIssues(repo, config.issueLimit)).filter((issue) => args.issue === undefined || issue.number === args.issue);
130
59
  recorder.event("issues", { open: issues.length }, "quiet");
60
+ await takeStop(repo);
131
61
  const reports = [];
132
62
  for (const issue of issues) {
133
63
  const handled = reports.filter((report) => report.outcome !== "skipped").length;
@@ -139,6 +69,10 @@ async function handle(args, config, repo, recorder) {
139
69
  }
140
70
  const report = await processIssue(context, issue);
141
71
  reports.push(report);
72
+ if (await takeStop(repo)) {
73
+ recorder.event("run.stop", { issue: issue.number }, "quiet");
74
+ break;
75
+ }
142
76
  if (args.once && report.outcome !== "skipped") {
143
77
  break;
144
78
  }
package/dist/state.mjs CHANGED
@@ -22,3 +22,12 @@ export async function writeState(repo, state) {
22
22
  await mkdir(dir(repo), { recursive: true });
23
23
  await writeFile(file(repo, state.issue), `${JSON.stringify(state, null, 2)}\n`);
24
24
  }
25
+ export async function takeStop(repo) {
26
+ try {
27
+ await rm(join(dir(repo), "stop"));
28
+ return true;
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-issue",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "Autonomous issue harness driven by the Claude Agent SDK",
5
5
  "keywords": [
6
6
  "github",