next-issue 2.5.7 → 2.5.9

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
@@ -249,7 +249,9 @@ Three levels of console output:
249
249
  level and the extensions from your pi settings, except the `pi-next-issue`
250
250
  extension. The implementer and the fixer get the default tools of your pi
251
251
  settings, the `grep`, `find` and `ls` tools and the tools of your
252
- extensions. The reviewer gets all of them except `edit` and `write`.
252
+ extensions. The reviewer gets all of them except `edit` and `write`. The
253
+ agents run with `GIT_OPTIONAL_LOCKS=0`, so a `git status` from an agent or
254
+ an extension does not hold the index lock while the harness commits.
253
255
 
254
256
  ## Configuration
255
257
 
package/dist/agents.mjs CHANGED
@@ -17,6 +17,7 @@ export async function runAgent(recorder, request) {
17
17
  const runtime = request.runtime ?? {};
18
18
  const agentDir = runtime.agentDir ?? getAgentDir();
19
19
  const started = Date.now();
20
+ process.env.GIT_OPTIONAL_LOCKS = '0';
20
21
  log.event('agent.start', { model: request.model, timeoutMs: request.timeoutMs }, 'normal');
21
22
  const resourceLoader = new DefaultResourceLoader({
22
23
  cwd: request.cwd,
package/dist/github.mjs CHANGED
@@ -62,7 +62,7 @@ export async function openIssues(repo, limit, labels) {
62
62
  ISSUE_FIELDS,
63
63
  ]);
64
64
  const parsed = JSON.parse(raw);
65
- return parsed.map(toIssue).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
65
+ return parsed.map(toIssue);
66
66
  }
67
67
  export async function viewIssue(repo, issue) {
68
68
  const raw = await gh(repo, ['issue', 'view', String(issue), '--json', `${ISSUE_FIELDS},state`]);
@@ -100,11 +100,25 @@ export function labelArgs(add, remove, current) {
100
100
  return args;
101
101
  }
102
102
  export async function setLabel(repo, kind, number, add, remove) {
103
- await ensureLabel(repo, add);
103
+ await createUnknownLabel(repo, add);
104
104
  const current = await labelsOf(repo, kind, number);
105
- await gh(repo, [kind, 'edit', String(number), ...labelArgs(add, remove, current)]);
105
+ const args = [kind, 'edit', String(number), ...labelArgs(add, remove, current)];
106
+ try {
107
+ await gh(repo, args);
108
+ }
109
+ catch (error) {
110
+ KNOWN_LABELS.delete(slug(repo));
111
+ const created = await createUnknownLabel(repo, add);
112
+ if (created === 'known') {
113
+ throw error;
114
+ }
115
+ if (created instanceof Error) {
116
+ throw created;
117
+ }
118
+ await gh(repo, args);
119
+ }
106
120
  }
107
- async function ensureLabel(repo, label) {
121
+ async function createUnknownLabel(repo, label) {
108
122
  let known = KNOWN_LABELS.get(slug(repo));
109
123
  if (known === undefined) {
110
124
  const raw = await gh(repo, ['label', 'list', '--limit', '1000', '--json', 'name']);
@@ -112,14 +126,16 @@ async function ensureLabel(repo, label) {
112
126
  KNOWN_LABELS.set(slug(repo), known);
113
127
  }
114
128
  if (known.has(label)) {
115
- return;
129
+ return 'known';
116
130
  }
117
- const result = await run('gh', ['label', 'create', label, '--repo', slug(repo)], {
118
- cwd: repo.root,
119
- });
120
- if (result.code === 0) {
121
- known.add(label);
131
+ try {
132
+ await ghOnce(repo, ['label', 'create', label]);
133
+ }
134
+ catch (error) {
135
+ return error instanceof Error ? error : new Error(String(error));
122
136
  }
137
+ known.add(label);
138
+ return 'created';
123
139
  }
124
140
  export async function findPr(repo, branch) {
125
141
  const raw = await gh(repo, [
@@ -0,0 +1,37 @@
1
+ export function isNewer(candidate, current) {
2
+ const [leftCore = '', ...leftRest] = candidate.replace(/\+.*$/, '').split('-');
3
+ const [rightCore = '', ...rightRest] = current.replace(/\+.*$/, '').split('-');
4
+ const left = leftCore.split('.').map(Number);
5
+ const right = rightCore.split('.').map(Number);
6
+ for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
7
+ const difference = (left[index] ?? 0) - (right[index] ?? 0);
8
+ if (difference !== 0) {
9
+ return difference > 0;
10
+ }
11
+ }
12
+ const leftPrerelease = leftRest.length > 0 ? leftRest.join('-').split('.') : [];
13
+ const rightPrerelease = rightRest.length > 0 ? rightRest.join('-').split('.') : [];
14
+ if (leftPrerelease.length === 0 || rightPrerelease.length === 0) {
15
+ return leftPrerelease.length === 0 && rightPrerelease.length > 0;
16
+ }
17
+ for (let index = 0; index < Math.max(leftPrerelease.length, rightPrerelease.length); index += 1) {
18
+ const leftPart = leftPrerelease[index];
19
+ const rightPart = rightPrerelease[index];
20
+ if (leftPart === undefined || rightPart === undefined) {
21
+ return rightPart === undefined;
22
+ }
23
+ if (leftPart === rightPart) {
24
+ continue;
25
+ }
26
+ const leftNumeric = /^\d+$/.test(leftPart);
27
+ const rightNumeric = /^\d+$/.test(rightPart);
28
+ if (leftNumeric && rightNumeric) {
29
+ return Number(leftPart) > Number(rightPart);
30
+ }
31
+ if (leftNumeric || rightNumeric) {
32
+ return rightNumeric;
33
+ }
34
+ return leftPart > rightPart;
35
+ }
36
+ return false;
37
+ }
package/dist/serve.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { rmSync } from 'node:fs';
2
- import { chmod, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import { readFile, rm, writeFile } from 'node:fs/promises';
3
3
  import { createServer } from 'node:http';
4
4
  import { join } from 'node:path';
5
5
  import { getAgentDir } from '@earendil-works/pi-coding-agent';
@@ -8,6 +8,7 @@ import { parseArgs } from './args.mjs';
8
8
  import { stopChildren } from './exec.mjs';
9
9
  import { message } from './observe.mjs';
10
10
  import { applyEvent, startProgress } from './progress.mjs';
11
+ import { isNewer } from './protocol.mjs';
11
12
  import { runRepo } from './run.mjs';
12
13
  import { packageVersion } from './version.mjs';
13
14
  export function socketPaths(agentDir = getAgentDir()) {
@@ -16,17 +17,6 @@ export function socketPaths(agentDir = getAgentDir()) {
16
17
  lockPath: join(agentDir, 'next-issue.lock'),
17
18
  };
18
19
  }
19
- export function isNewer(candidate, current) {
20
- const left = candidate.split('.').map(Number);
21
- const right = current.split('.').map(Number);
22
- for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
23
- const difference = (left[index] ?? 0) - (right[index] ?? 0);
24
- if (difference !== 0) {
25
- return difference > 0;
26
- }
27
- }
28
- return false;
29
- }
30
20
  function alive(pid) {
31
21
  try {
32
22
  process.kill(pid, 0);
@@ -191,12 +181,17 @@ export async function startWorker(options) {
191
181
  });
192
182
  await new Promise((resolve, reject) => {
193
183
  server.once('error', reject);
194
- server.listen(options.socketPath, () => {
195
- server.off('error', reject);
196
- resolve();
197
- });
184
+ const umask = process.umask(0o177);
185
+ try {
186
+ server.listen(options.socketPath, () => {
187
+ server.off('error', reject);
188
+ resolve();
189
+ });
190
+ }
191
+ finally {
192
+ process.umask(umask);
193
+ }
198
194
  });
199
- await chmod(options.socketPath, 0o600);
200
195
  return { closed, close };
201
196
  }
202
197
  export async function serve() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-issue",
3
- "version": "2.5.7",
3
+ "version": "2.5.9",
4
4
  "description": "Autonomous issue harness driven by the pi SDK",
5
5
  "keywords": [
6
6
  "github",