next-issue 2.4.0 → 2.4.2
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 +68 -68
- package/dist/agents.mjs +32 -31
- package/dist/args.mjs +17 -17
- package/dist/cli.mjs +16 -14
- package/dist/config.mjs +42 -42
- package/dist/exec.mjs +26 -26
- package/dist/git.mjs +40 -40
- package/dist/github.mjs +110 -79
- package/dist/interrupt.mjs +7 -7
- package/dist/loop.mjs +5 -3
- package/dist/observe.mjs +19 -19
- package/dist/pipeline.mjs +138 -116
- package/dist/progress.mjs +22 -22
- package/dist/prompts.mjs +52 -52
- package/dist/run.mjs +28 -28
- package/dist/serve.mjs +49 -45
- package/dist/setup.mjs +13 -13
- package/dist/state.mjs +20 -20
- package/dist/summary.mjs +7 -7
- package/dist/verdict.mjs +30 -25
- package/dist/version.mjs +3 -3
- package/package.json +7 -1
package/dist/state.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { mkdir, readdir, readFile, rm, writeFile } from
|
|
2
|
-
import { join } from
|
|
3
|
-
export const STATE_DIR =
|
|
1
|
+
import { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
export const STATE_DIR = '.next-issue';
|
|
4
4
|
function dir(repo) {
|
|
5
5
|
return join(repo.root, STATE_DIR);
|
|
6
6
|
}
|
|
@@ -11,10 +11,10 @@ export async function readState(repo, issue) {
|
|
|
11
11
|
const path = file(repo, issue);
|
|
12
12
|
let text;
|
|
13
13
|
try {
|
|
14
|
-
text = await readFile(path,
|
|
14
|
+
text = await readFile(path, 'utf8');
|
|
15
15
|
}
|
|
16
16
|
catch (error) {
|
|
17
|
-
if (error.code ===
|
|
17
|
+
if (error.code === 'ENOENT') {
|
|
18
18
|
return undefined;
|
|
19
19
|
}
|
|
20
20
|
throw error;
|
|
@@ -31,34 +31,34 @@ export async function readState(repo, issue) {
|
|
|
31
31
|
}
|
|
32
32
|
return parsed;
|
|
33
33
|
}
|
|
34
|
-
const PHASES = [
|
|
34
|
+
const PHASES = ['claimed', 'implemented', 'review'];
|
|
35
35
|
function isRecord(value) {
|
|
36
|
-
return typeof value ===
|
|
36
|
+
return typeof value === 'object' && value !== null;
|
|
37
37
|
}
|
|
38
38
|
function isOptional(value, type) {
|
|
39
39
|
return value === undefined || typeof value === type;
|
|
40
40
|
}
|
|
41
41
|
function isBaseFix(value) {
|
|
42
|
-
return isRecord(value) && typeof value.sha ===
|
|
42
|
+
return isRecord(value) && typeof value.sha === 'string' && typeof value.head === 'string';
|
|
43
43
|
}
|
|
44
44
|
function isReviewRound(value) {
|
|
45
45
|
return (isRecord(value) &&
|
|
46
|
-
typeof value.round ===
|
|
47
|
-
typeof value.fingerprint ===
|
|
48
|
-
typeof value.findings ===
|
|
49
|
-
isOptional(value.dispute,
|
|
46
|
+
typeof value.round === 'number' &&
|
|
47
|
+
typeof value.fingerprint === 'string' &&
|
|
48
|
+
typeof value.findings === 'string' &&
|
|
49
|
+
isOptional(value.dispute, 'string'));
|
|
50
50
|
}
|
|
51
51
|
function isState(value) {
|
|
52
52
|
return (isRecord(value) &&
|
|
53
|
-
typeof value.issue ===
|
|
53
|
+
typeof value.issue === 'number' &&
|
|
54
54
|
PHASES.includes(value.phase) &&
|
|
55
|
-
typeof value.branch ===
|
|
56
|
-
isOptional(value.pr,
|
|
57
|
-
isOptional(value.title,
|
|
58
|
-
typeof value.ciFixes ===
|
|
59
|
-
isOptional(value.commitFixes,
|
|
55
|
+
typeof value.branch === 'string' &&
|
|
56
|
+
isOptional(value.pr, 'number') &&
|
|
57
|
+
isOptional(value.title, 'string') &&
|
|
58
|
+
typeof value.ciFixes === 'number' &&
|
|
59
|
+
isOptional(value.commitFixes, 'number') &&
|
|
60
60
|
(value.baseFix === undefined || isBaseFix(value.baseFix)) &&
|
|
61
|
-
typeof value.reviewRounds ===
|
|
61
|
+
typeof value.reviewRounds === 'number' &&
|
|
62
62
|
Array.isArray(value.reviewLog) &&
|
|
63
63
|
value.reviewLog.every(isReviewRound));
|
|
64
64
|
}
|
|
@@ -68,7 +68,7 @@ export async function savedIssues(repo) {
|
|
|
68
68
|
names = await readdir(dir(repo));
|
|
69
69
|
}
|
|
70
70
|
catch (error) {
|
|
71
|
-
if (error.code ===
|
|
71
|
+
if (error.code === 'ENOENT') {
|
|
72
72
|
return [];
|
|
73
73
|
}
|
|
74
74
|
throw error;
|
package/dist/summary.mjs
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
const OUTCOMES = [
|
|
1
|
+
const OUTCOMES = ['done', 'needs-human', 'error', 'skipped', 'stopped'];
|
|
2
2
|
export function formatSummary(summary) {
|
|
3
|
-
const tally = OUTCOMES.map((outcome) => `${summary.issues.filter((report) => report.outcome === outcome).length} ${outcome}`).join(
|
|
3
|
+
const tally = OUTCOMES.map((outcome) => `${summary.issues.filter((report) => report.outcome === outcome).length} ${outcome}`).join(', ');
|
|
4
4
|
const lines = [`run ${summary.runId}`, `issues ${summary.issues.length}: ${tally}`];
|
|
5
5
|
for (const report of summary.issues) {
|
|
6
6
|
lines.push(` ${issueLine(report)}`);
|
|
7
7
|
}
|
|
8
8
|
const roles = Object.entries(summary.roles)
|
|
9
9
|
.map(([role, totals]) => `${role} ${tokens(totals.total)} in ${totals.calls}`)
|
|
10
|
-
.join(
|
|
11
|
-
lines.push(`tokens ${tokens(summary.total.total)}${roles.length > 0 ? ` (${roles})` :
|
|
10
|
+
.join(', ');
|
|
11
|
+
lines.push(`tokens ${tokens(summary.total.total)}${roles.length > 0 ? ` (${roles})` : ''}`);
|
|
12
12
|
lines.push(`log ${summary.logFile}`);
|
|
13
|
-
return `${lines.join(
|
|
13
|
+
return `${lines.join('\n')}\n`;
|
|
14
14
|
}
|
|
15
15
|
function issueLine(report) {
|
|
16
16
|
const parts = [`#${report.issue} ${report.outcome.padEnd(11)}`];
|
|
@@ -29,12 +29,12 @@ function issueLine(report) {
|
|
|
29
29
|
if (report.reason !== undefined) {
|
|
30
30
|
parts.push(`(${report.reason})`);
|
|
31
31
|
}
|
|
32
|
-
return parts.join(
|
|
32
|
+
return parts.join(' ').trimEnd();
|
|
33
33
|
}
|
|
34
34
|
function duration(ms) {
|
|
35
35
|
const seconds = Math.round(ms / 1000);
|
|
36
36
|
const minutes = Math.floor(seconds / 60);
|
|
37
|
-
return minutes === 0 ? `${seconds}s` : `${minutes}m${String(seconds % 60).padStart(2,
|
|
37
|
+
return minutes === 0 ? `${seconds}s` : `${minutes}m${String(seconds % 60).padStart(2, '0')}s`;
|
|
38
38
|
}
|
|
39
39
|
function tokens(value) {
|
|
40
40
|
if (value < 1000) {
|
package/dist/verdict.mjs
CHANGED
|
@@ -1,42 +1,44 @@
|
|
|
1
1
|
export const VERDICT_SCHEMA = {
|
|
2
|
-
type:
|
|
2
|
+
type: 'object',
|
|
3
3
|
properties: {
|
|
4
|
-
summary: { type:
|
|
4
|
+
summary: { type: 'string', description: 'Short summary of the review' },
|
|
5
5
|
findings: {
|
|
6
|
-
type:
|
|
7
|
-
description:
|
|
6
|
+
type: 'array',
|
|
7
|
+
description: 'One entry per problem. Empty when nothing is wrong.',
|
|
8
8
|
items: {
|
|
9
|
-
type:
|
|
9
|
+
type: 'object',
|
|
10
10
|
properties: {
|
|
11
11
|
severity: {
|
|
12
|
-
type:
|
|
13
|
-
enum: [
|
|
14
|
-
description:
|
|
12
|
+
type: 'string',
|
|
13
|
+
enum: ['blocking', 'minor'],
|
|
14
|
+
description: 'blocking for a wrong result, a missing part of the issue, a regression or a broken convention; minor for taste and style',
|
|
15
15
|
},
|
|
16
|
-
detail: { type:
|
|
16
|
+
detail: { type: 'string', description: 'What is wrong and where' },
|
|
17
17
|
},
|
|
18
|
-
required: [
|
|
18
|
+
required: ['severity', 'detail'],
|
|
19
19
|
},
|
|
20
20
|
},
|
|
21
21
|
},
|
|
22
|
-
required: [
|
|
22
|
+
required: ['summary', 'findings'],
|
|
23
23
|
};
|
|
24
|
-
const SEVERITIES = [
|
|
24
|
+
const SEVERITIES = ['blocking', 'minor'];
|
|
25
25
|
export function readVerdict(value) {
|
|
26
|
-
if (typeof value !==
|
|
26
|
+
if (typeof value !== 'object' || value === null) {
|
|
27
27
|
return undefined;
|
|
28
28
|
}
|
|
29
29
|
const { summary, findings } = value;
|
|
30
|
-
if (typeof summary !==
|
|
30
|
+
if (typeof summary !== 'string' || !Array.isArray(findings)) {
|
|
31
31
|
return undefined;
|
|
32
32
|
}
|
|
33
33
|
const checked = [];
|
|
34
34
|
for (const item of findings) {
|
|
35
|
-
if (typeof item !==
|
|
35
|
+
if (typeof item !== 'object' || item === null) {
|
|
36
36
|
return undefined;
|
|
37
37
|
}
|
|
38
38
|
const { severity, detail } = item;
|
|
39
|
-
if (typeof severity !==
|
|
39
|
+
if (typeof severity !== 'string' ||
|
|
40
|
+
!SEVERITIES.includes(severity) ||
|
|
41
|
+
typeof detail !== 'string') {
|
|
40
42
|
return undefined;
|
|
41
43
|
}
|
|
42
44
|
checked.push({ severity: severity, detail });
|
|
@@ -44,27 +46,30 @@ export function readVerdict(value) {
|
|
|
44
46
|
return { summary, findings: checked };
|
|
45
47
|
}
|
|
46
48
|
export function blockingFindings(verdict) {
|
|
47
|
-
return verdict.findings.filter((finding) => finding.severity ===
|
|
49
|
+
return verdict.findings.filter((finding) => finding.severity === 'blocking');
|
|
48
50
|
}
|
|
49
51
|
export function openFindings(verdict, lastRound) {
|
|
50
52
|
return lastRound ? blockingFindings(verdict) : verdict.findings;
|
|
51
53
|
}
|
|
52
|
-
const LEFT =
|
|
54
|
+
const LEFT = 'The findings above stay as they are: no blocking finding is open and the review budget is spent.';
|
|
53
55
|
export function formatVerdict(verdict, open) {
|
|
54
56
|
const approved = open.length === 0;
|
|
55
|
-
const head = approved ?
|
|
57
|
+
const head = approved ? 'Review: approved' : 'Review: changes requested';
|
|
56
58
|
const lines = verdict.findings.map((finding) => `- **${finding.severity}** ${finding.detail}`);
|
|
57
|
-
const note = approved && verdict.findings.length > 0 ? LEFT :
|
|
58
|
-
return [`### ${head}`, verdict.summary, lines.join(
|
|
59
|
+
const note = approved && verdict.findings.length > 0 ? LEFT : '';
|
|
60
|
+
return [`### ${head}`, verdict.summary, lines.join('\n'), note]
|
|
59
61
|
.filter((part) => part.length > 0)
|
|
60
|
-
.join(
|
|
62
|
+
.join('\n\n');
|
|
61
63
|
}
|
|
62
64
|
export function formatFindings(findings) {
|
|
63
|
-
return findings.map((finding) => `- ${finding.detail}`).join(
|
|
65
|
+
return findings.map((finding) => `- ${finding.detail}`).join('\n');
|
|
64
66
|
}
|
|
65
67
|
export function fingerprint(findings) {
|
|
66
68
|
return findings
|
|
67
|
-
.map((finding) => finding.detail
|
|
69
|
+
.map((finding) => finding.detail
|
|
70
|
+
.toLowerCase()
|
|
71
|
+
.replace(/[^a-z0-9]+/g, ' ')
|
|
72
|
+
.trim())
|
|
68
73
|
.sort()
|
|
69
|
-
.join(
|
|
74
|
+
.join('|');
|
|
70
75
|
}
|
package/dist/version.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { readFile } from
|
|
2
|
-
import { join } from
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
3
|
export async function packageVersion() {
|
|
4
|
-
const raw = await readFile(join(import.meta.dirname,
|
|
4
|
+
const raw = await readFile(join(import.meta.dirname, '..', 'package.json'), 'utf8');
|
|
5
5
|
return JSON.parse(raw).version;
|
|
6
6
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-issue",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.2",
|
|
4
4
|
"description": "Autonomous issue harness driven by the pi SDK",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"github",
|
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
"start": "node src/cli.mts",
|
|
35
35
|
"test": "node --test \"test/**/*.test.mts\"",
|
|
36
36
|
"typecheck": "tsc --noEmit",
|
|
37
|
+
"lint": "oxlint --deny-warnings",
|
|
38
|
+
"lint:fix": "oxlint --fix",
|
|
39
|
+
"format": "oxfmt",
|
|
40
|
+
"format:check": "oxfmt --check",
|
|
37
41
|
"prepack": "npm run build",
|
|
38
42
|
"prepublishOnly": "npm run typecheck && npm test"
|
|
39
43
|
},
|
|
@@ -41,6 +45,8 @@
|
|
|
41
45
|
"@earendil-works/pi-ai": "^1.5.0",
|
|
42
46
|
"@types/node": "^26.6.6",
|
|
43
47
|
"@types/ws": "^8.18.2",
|
|
48
|
+
"oxfmt": "^0.74.0",
|
|
49
|
+
"oxlint": "^1.89.0",
|
|
44
50
|
"typescript": "^7.0.2"
|
|
45
51
|
},
|
|
46
52
|
"dependencies": {
|