@ravshansbox/pi-next-issue 1.0.10 → 1.0.12

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
@@ -39,7 +39,7 @@ The implementer and the reviewer read the issue title, the body and the comments
39
39
 
40
40
  Each agent is a pi session in the worktree. It uses the model and thinking level from your pi settings, and loads all of your extensions except this one.
41
41
 
42
- The state of each issue is saved in `.worktrees/<n>.json`. A run holds `.worktrees/run.lock`, which keeps the process id, so that only one pi at a time works on a repository. A second run fails at once. A lock whose process is no longer alive is taken over. If the operating system gives that process id to another process, the lock stays; remove `.worktrees/run.lock` by hand when no run is going. The extension adds `/.worktrees/` to `.git/info/exclude`.
42
+ The state of each issue is saved in `.worktrees/<n>.json`. Each loop removes the saved state of an issue that is closed. A run holds `.worktrees/run.lock`, which keeps the process id, so that only one pi at a time works on a repository. A second run fails at once. A lock whose process is no longer alive is taken over. If the operating system gives that process id to another process, the lock stays; remove `.worktrees/run.lock` by hand when no run is going. The extension adds `/.worktrees/` to `.git/info/exclude`.
43
43
 
44
44
  ## Config
45
45
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ravshansbox/pi-next-issue",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "Pi extension that works through GitHub issues in the background",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/github.ts CHANGED
@@ -38,7 +38,10 @@ type RawIssue = {
38
38
 
39
39
  const ISSUE_FIELDS = 'number,title,body,labels,createdAt';
40
40
 
41
- export async function openIssues(repo: Repo, saved: number[]): Promise<Issue[]> {
41
+ export async function openIssues(
42
+ repo: Repo,
43
+ saved: number[],
44
+ ): Promise<{ open: Issue[]; closed: number[] }> {
42
45
  const raw = await gh(repo, [
43
46
  'issue',
44
47
  'list',
@@ -55,6 +58,7 @@ export async function openIssues(repo: Repo, saved: number[]): Promise<Issue[]>
55
58
  ]);
56
59
  const listed = JSON.parse(raw) as RawIssue[];
57
60
  const known = new Set(listed.map((item) => item.number));
61
+ const closed: number[] = [];
58
62
  for (const number of saved.filter((item) => !known.has(item))) {
59
63
  const view = await run(
60
64
  'gh',
@@ -67,9 +71,11 @@ export async function openIssues(repo: Repo, saved: number[]): Promise<Issue[]>
67
71
  const item = JSON.parse(view.stdout) as RawIssue & { state: string };
68
72
  if (item.state === 'OPEN') {
69
73
  listed.push(item);
74
+ } else {
75
+ closed.push(number);
70
76
  }
71
77
  }
72
- return listed
78
+ const open = listed
73
79
  .sort((a, b) => a.createdAt.localeCompare(b.createdAt))
74
80
  .map((item) => ({
75
81
  number: item.number,
@@ -77,6 +83,7 @@ export async function openIssues(repo: Repo, saved: number[]): Promise<Issue[]>
77
83
  body: item.body ?? '',
78
84
  labels: item.labels.map((label) => label.name),
79
85
  }));
86
+ return { open, closed };
80
87
  }
81
88
 
82
89
  export async function comments(repo: Repo, issue: number): Promise<string[]> {
@@ -227,15 +234,24 @@ export async function failedLogs(repo: Repo, pr: number, maxChars: number): Prom
227
234
  const failed = (await readChecks(repo, pr)).filter(
228
235
  (check) => check.bucket === 'fail' || check.bucket === 'cancel',
229
236
  );
230
- const share = Math.max(1, Math.floor(maxChars / Math.max(1, failed.length)));
231
- const parts: string[] = [];
237
+ const groups = new Map<string, { names: string[]; runId: string | undefined }>();
232
238
  for (const check of failed) {
233
- parts.push(`## ${check.name}`);
234
239
  const runId = /\/actions\/runs\/(\d+)/.exec(check.link)?.[1];
235
- if (runId !== undefined) {
236
- const logs = await run('gh', ['run', 'view', runId, '--log-failed', '--repo', repo.slug], {
237
- cwd: repo.root,
238
- });
240
+ const key = runId ?? `check:${check.name}`;
241
+ const group = groups.get(key) ?? { names: [], runId };
242
+ group.names.push(check.name);
243
+ groups.set(key, group);
244
+ }
245
+ const share = Math.max(1, Math.floor(maxChars / Math.max(1, groups.size)));
246
+ const parts: string[] = [];
247
+ for (const group of groups.values()) {
248
+ parts.push(`## ${group.names.join(', ')}`);
249
+ if (group.runId !== undefined) {
250
+ const logs = await run(
251
+ 'gh',
252
+ ['run', 'view', group.runId, '--log-failed', '--repo', repo.slug],
253
+ { cwd: repo.root },
254
+ );
239
255
  parts.push((logs.stdout || logs.stderr).slice(-share));
240
256
  }
241
257
  }
package/src/ports.ts CHANGED
@@ -10,7 +10,11 @@ export async function createPorts(root: string, config: Config): Promise<Ports>
10
10
  const base = await github.defaultBranch(repo);
11
11
  const timeoutMs = config.checkTimeoutMinutes * 60_000;
12
12
  return {
13
- openIssues: async () => github.openIssues(repo, await state.savedIssues(root)),
13
+ openIssues: async () => {
14
+ const { open, closed } = await github.openIssues(repo, await state.savedIssues(root));
15
+ await Promise.all(closed.map((issue) => state.dropState(root, issue)));
16
+ return open;
17
+ },
14
18
  comments: (issue) => github.comments(repo, issue),
15
19
  setLabel: (issue, add, remove) => github.setLabel(repo, issue, add, remove),
16
20
  commentOnIssue: (issue, body) => github.commentOnIssue(repo, issue, body),