@ravshansbox/pi-next-issue 5.6.3 → 6.0.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 CHANGED
@@ -12,8 +12,10 @@ It needs `git`, `npx` and an authenticated [`gh`](https://cli.github.com/). It w
12
12
 
13
13
  Run this in pi, inside a git repository:
14
14
 
15
- - `/next-issue` starts a run of this repository when none is going, and stops the run when one is going. A stop takes effect after the current round, and the issue keeps its saved state, so the next run resumes it.
16
- - The `next_issue_start` tool lets the model start a run. When no run is going, it starts a run like `/next-issue`. When a run is going, it starts nothing and reports the current run, which picks up new issues by itself. It never stops a run, and it does not ask for confirmation. Its result says whether it started a run.
15
+ - `/next-issue start [flags]` starts a run of this repository when none is going. The flags are those of the `next-issue` CLI: `--once`, `--max <n>`, `--watch`, `--hunt`, `--reset`, `--verbose` and `--quiet`. For example, `/next-issue start --hunt` hunts for bugs and keeps watching, like `next-issue --hunt`. The worker refuses any other argument, or a command such as `init`, and a note says why. When a run is going, a note says so, and nothing starts or restarts.
16
+ - `/next-issue stop` stops the run of this repository. A stop takes effect after the current round, and the issue keeps its saved state, so the next run resumes it. When no run is going, a note says so.
17
+ - `/next-issue` with no argument shows the usage: `start [flags]`, `stop` and `answer`. Pi suggests these as you type after `/next-issue `.
18
+ - The `next_issue_start` tool lets the model start a run. It takes an optional `args` array with the same flags as `/next-issue start`. When no run is going, it starts a run like `/next-issue start`. When a run is going, it starts nothing and reports the current run, which picks up new issues by itself. It never stops a run, and it does not ask for confirmation. Its result says whether it started a run.
17
19
  - The `next_issue_status` tool lets the model see the run of this repository. It returns the footer line, then one line per handled issue with its outcome, for example `#190 done`. When the worker has no run of this repository, it returns `no run of <root>`. When no worker is going, it returns `no worker is running` and does not start one. It reports no other repository.
18
20
 
19
21
  The footer shows each running issue with the number of open issues at the latest fetch, its stage, the number of tool calls and the time spent on it, for example `#12/7 · review 2/5 · 37 tools · 4m12s | #13/7 · implement · 5 tools · 40s`. The open count includes the running issues and open issues the run skips. It leaves out closed issues that the run resumes after their change landed. A stage with a limited number of rounds shows the round and its limit: `review 2/5`, `review fix 2/5`, `checks fix 1/5` or `commit fix 1/10`. Other lines read, for example, `looking for issues`, `finished: 2 done` or `stopped: 1 done, 1 needs a human`. After a run ends, it keeps the last summary. In the terminal, it uses the dim colour of the theme, like the rest of the footer. A note appears when an issue is done, needs a human or fails while pi is open. Pi does not repeat the notes of a run that ended before it opened.
@@ -22,17 +24,17 @@ The footer shows each running issue with the number of open issues at the latest
22
24
 
23
25
  When an issue waits for a person, with the `needsHuman` label of the [config](../next-issue/README.md), the footer shows the count, for example `2 questions waiting`, and a note appears once for each new question. Neither interrupts what you do. Pi asks the worker for the list when it connects and every minute, and the worker also sends it after each issue of a run. A question answered on GitHub or in another pi leaves the count with the next list.
24
26
 
25
- Run `/next-issue answer` to go through the waiting questions one at a time. Pi suggests `answer` as you type after `/next-issue `. Each shows the issue, the title and the reason of the hand-over. Choose:
27
+ Run `/next-issue answer` to go through the waiting questions one at a time. Each shows the issue, the title and the reason of the hand-over. Choose:
26
28
 
27
29
  - **Retry**: takes the `needsHuman` label off the issue, so the next run resumes it.
28
30
  - **Retry with guidance**: asks for text, posts it as a comment on the issue, where the worker reads it, then takes the `needsHuman` label off.
29
31
  - **Give up**: takes the `needsHuman` label off the issue and adds the `skip` label.
30
32
 
31
- Press Escape, or leave the guidance empty, to stop. Pi reads the list again before each question, so it skips a question answered elsewhere in the meantime. The worker sends the repository and the label names with each question, from `next-issue.config.json` and its `remote`. Pi uses `gh` for the comment and the labels.
33
+ Press Escape, or leave the guidance empty, to stop. Pi reads the list again before each question, so it skips a question answered elsewhere in the meantime. The worker sends the repository and the label names with each question, from `.next-issue/config.json` and its `remote`. Pi uses `gh` for the comment and the labels.
32
34
 
33
35
  ## Create an issue
34
36
 
35
- The package ships the `create-issue` skill. Ask pi to write an issue, ask for a change in a repository with `next-issue.config.json`, or run `/skill:create-issue <problem>`. Pi then:
37
+ The package ships the `create-issue` skill. Ask pi to write an issue, ask for a change in a repository with `.next-issue/config.json`, or run `/skill:create-issue <problem>`. Pi then:
36
38
 
37
39
  1. restates the problem, reads the code when the problem touches it, and finds the open issues that must be done first;
38
40
  2. asks numbered questions until none is open;
@@ -47,7 +49,7 @@ Pi does not implement the issue in that session. The worker does the work. The b
47
49
 
48
50
  ## Worker
49
51
 
50
- The extension does no work itself. On `/next-issue` or `next_issue_start` it connects to the `next-issue` worker of the repository on the socket file `.next-issue/worker.sock` in the repository. When no worker answers, it starts one in the repository with `npx -y next-issue@<version> serve`, where `<version>` is the version of the extension, or `latest` when the extension runs from a source checkout with version `0.0.0`, in the background, and writes the output of the worker to `.next-issue/worker.log` in the repository. When the path of the socket file is too long for the system, the extension says so and starts nothing.
52
+ The extension does no work itself. On `/next-issue start`, `stop` or `answer`, or `next_issue_start`, it connects to the `next-issue` worker of the repository on the socket file `.next-issue/worker.sock` in the repository. When no worker answers, it starts one in the repository with `npx -y next-issue@<version> serve`, and passes the flags of each start to it, where `<version>` is the version of the extension, or `latest` when the extension runs from a source checkout with version `0.0.0`, in the background, and writes the output of the worker to `.next-issue/worker.log` in the repository. When the path of the socket file is too long for the system, the extension says so and starts nothing.
51
53
 
52
54
  - Each repository has its own worker. Every pi in a repository shares the worker of that repository and shows its run, whoever started it. A run, restart or upgrade in one repository never affects another.
53
55
  - Opening pi does not start a worker. It only shows a run when a worker is going.
package/index.ts CHANGED
@@ -8,8 +8,9 @@ import { errorText } from './src/errors.ts';
8
8
  import { must } from './src/exec.ts';
9
9
  import { repoRoot } from './src/git.ts';
10
10
  import type { Progress, Question } from './src/protocol.ts';
11
- import { startRun, statusOf, toggle } from './src/run.ts';
11
+ import { requestStart, requestStop, startRun, statusOf } from './src/run.ts';
12
12
  import {
13
+ type Action,
13
14
  cannotNote,
14
15
  formatQuestions,
15
16
  formatStatus,
@@ -31,6 +32,13 @@ const STATUS_KEY = 'next-issue';
31
32
  const QUESTIONS_KEY = 'next-issue-questions';
32
33
  const RENDER_MS = 1000;
33
34
  const QUESTIONS_MS = 60_000;
35
+ const SUBCOMMANDS: Action[] = ['start', 'stop', 'answer'];
36
+ const FLAGS = '--once, --max <n>, --watch, --hunt, --reset, --verbose, --quiet';
37
+ const USAGE = [
38
+ '/next-issue start [flags] start a run of this repository, with flags from: ' + FLAGS,
39
+ '/next-issue stop stop the run of this repository after the current round',
40
+ '/next-issue answer answer the waiting questions',
41
+ ].join('\n');
34
42
 
35
43
  function background(callback: () => void, ms: number): ReturnType<typeof setInterval> {
36
44
  const timer = setInterval(callback, ms);
@@ -220,13 +228,18 @@ export function createExtension(options: Options) {
220
228
  pi.registerTool({
221
229
  name: 'next_issue_start',
222
230
  label: 'Start next-issue',
223
- description:
224
- 'Start a next-issue run of the GitHub issues of this repository when none is going. Starts the worker when needed. When a run is going, starts nothing and reports that run, which picks up new issues by itself.',
231
+ description: `Start a next-issue run of the GitHub issues of this repository when none is going. Starts the worker when needed. When a run is going, starts nothing and reports that run, which picks up new issues by itself. Optional args take the flags ${FLAGS}, for example ["--hunt"] to hunt for bugs and keep watching.`,
225
232
  promptSnippet: 'Start a next-issue run of this repository when none is going',
226
- parameters: Type.Object({}),
227
- async execute(_toolCallId, _params, signal, _onUpdate, ctx) {
233
+ parameters: Type.Object({
234
+ args: Type.Optional(
235
+ Type.Array(Type.String(), {
236
+ description: `Flags for the run, from: ${FLAGS}`,
237
+ }),
238
+ ),
239
+ }),
240
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
228
241
  const { client, root, progress } = await connect(ctx);
229
- return startRun(client, root, progress, signal);
242
+ return startRun(client, root, progress, params.args ?? [], signal);
230
243
  },
231
244
  });
232
245
 
@@ -244,22 +257,33 @@ export function createExtension(options: Options) {
244
257
  });
245
258
 
246
259
  pi.registerCommand('next-issue', {
247
- description:
248
- 'Start or stop working through the GitHub issues of this repository, or answer the waiting questions with "answer"',
249
- getArgumentCompletions: (prefix) =>
250
- 'answer'.startsWith(prefix) ? [{ value: 'answer', label: 'answer' }] : null,
260
+ description: `"start [flags]" starts working through the GitHub issues of this repository, with flags from ${FLAGS}. "stop" stops the run. "answer" answers the waiting questions`,
261
+ getArgumentCompletions: (prefix) => {
262
+ const matches = SUBCOMMANDS.filter((name) => name.startsWith(prefix));
263
+ return matches.length === 0 ? null : matches.map((name) => ({ value: name, label: name }));
264
+ },
251
265
  handler: async (args, ctx) => {
252
- const answering = args.trim() === 'answer';
266
+ const [subcommand, ...flags] = args.trim().split(/\s+/).filter(Boolean);
267
+ if (subcommand === undefined || !SUBCOMMANDS.includes(subcommand as Action)) {
268
+ ctx.ui.notify(USAGE, 'info');
269
+ return;
270
+ }
271
+ const notify = (note: Note | undefined) => {
272
+ if (note !== undefined) {
273
+ ctx.ui.notify(note.text, note.level);
274
+ }
275
+ };
253
276
  try {
254
277
  const { client, root, progress } = await connect(ctx);
255
- if (answering) {
278
+ if (subcommand === 'answer') {
256
279
  await answerAll(ctx.ui, options.gh, client, root);
280
+ } else if (subcommand === 'stop') {
281
+ notify(requestStop(client, root, progress));
257
282
  } else {
258
- toggle(client, root, progress);
283
+ notify(requestStart(client, root, progress, flags));
259
284
  }
260
285
  } catch (error) {
261
- const note = cannotNote(answering ? 'answer' : 'start', errorText(error));
262
- ctx.ui.notify(note.text, note.level);
286
+ notify(cannotNote(subcommand as Action, errorText(error)));
263
287
  }
264
288
  },
265
289
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ravshansbox/pi-next-issue",
3
- "version": "5.6.3",
3
+ "version": "6.0.1",
4
4
  "description": "Pi extension that works through GitHub issues with the next-issue worker",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: create-issue
3
- description: Turn a problem into a clear GitHub issue that the next-issue worker can claim. Use when the user wants to write, file or create an issue, or asks for a change to the code of a repository that has `next-issue.config.json`.
3
+ description: Turn a problem into a clear GitHub issue that the next-issue worker can claim. Use when the user wants to write, file or create an issue, or asks for a change to the code of a repository that has `.next-issue/config.json`.
4
4
  ---
5
5
 
6
6
  # Create an issue
package/src/protocol.ts CHANGED
@@ -30,7 +30,7 @@ export type Question = {
30
30
 
31
31
  export type ClientMessage =
32
32
  | { type: 'hello'; version: string }
33
- | { type: 'start'; root: string }
33
+ | { type: 'start'; root: string; args?: string[] }
34
34
  | { type: 'stop'; root: string }
35
35
  | { type: 'questions'; root: string; id?: string };
36
36
 
package/src/run.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { going, type Progress } from './protocol.ts';
2
2
  import { accepted } from './requests.ts';
3
- import { formatStatus, handledNote } from './status.ts';
3
+ import { formatStatus, handledNote, type Note } from './status.ts';
4
4
  import type { WorkerClient } from './worker.ts';
5
5
 
6
6
  function report<Details>(text: string, details: Details) {
@@ -20,32 +20,51 @@ export async function statusOf(client: WorkerClient, root: string) {
20
20
  return report(lines.join('\n'), { root });
21
21
  }
22
22
 
23
- function sendStart(client: WorkerClient, root: string): void {
24
- client.send({ type: 'start', root });
23
+ function sendStart(client: WorkerClient, root: string, args: string[]): void {
24
+ client.send(args.length === 0 ? { type: 'start', root } : { type: 'start', root, args });
25
+ }
26
+
27
+ function goingAlready(root: string, progress: Progress): string {
28
+ return `A run of ${root} is going already, so nothing started: ${formatStatus(progress, Date.now())}. It picks up new issues by itself.`;
25
29
  }
26
30
 
27
31
  export async function startRun(
28
32
  client: WorkerClient,
29
33
  root: string,
30
34
  progress: Progress | undefined,
35
+ args: string[],
31
36
  signal: AbortSignal | undefined,
32
37
  ) {
33
38
  if (going(progress)) {
34
- return report(
35
- `A run of ${root} is going already, so nothing started: ${formatStatus(progress, Date.now())}. It picks up new issues by itself.`,
36
- { started: false, root },
37
- );
39
+ return report(goingAlready(root, progress), { started: false, root });
38
40
  }
39
41
  const accepting = accepted(client, root, signal);
40
- sendStart(client, root);
42
+ sendStart(client, root, args);
41
43
  await accepting;
42
44
  return report(`Started a next-issue run of ${root}.`, { started: true, root });
43
45
  }
44
46
 
45
- export function toggle(client: WorkerClient, root: string, progress: Progress | undefined): void {
47
+ export function requestStart(
48
+ client: WorkerClient,
49
+ root: string,
50
+ progress: Progress | undefined,
51
+ args: string[],
52
+ ): Note | undefined {
46
53
  if (going(progress)) {
47
- client.send({ type: 'stop', root });
48
- } else {
49
- sendStart(client, root);
54
+ return { text: goingAlready(root, progress), level: 'info' };
55
+ }
56
+ sendStart(client, root, args);
57
+ return undefined;
58
+ }
59
+
60
+ export function requestStop(
61
+ client: WorkerClient,
62
+ root: string,
63
+ progress: Progress | undefined,
64
+ ): Note | undefined {
65
+ if (!going(progress)) {
66
+ return { text: `No run of ${root} is going.`, level: 'info' };
50
67
  }
68
+ client.send({ type: 'stop', root });
69
+ return undefined;
51
70
  }
package/src/status.ts CHANGED
@@ -39,7 +39,9 @@ export function statusNote(progress: Progress): Note | undefined {
39
39
  return undefined;
40
40
  }
41
41
 
42
- export function cannotNote(action: 'start' | 'answer', message: string): Note {
42
+ export type Action = 'start' | 'stop' | 'answer';
43
+
44
+ export function cannotNote(action: Action, message: string): Note {
43
45
  return { text: `next-issue cannot ${action}: ${message}`, level: 'error' };
44
46
  }
45
47