@ravshansbox/pi-next-issue 1.0.13 → 1.1.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 +2 -2
- package/package.json +1 -1
- package/src/git.ts +22 -3
- package/src/ports.ts +1 -0
- package/src/runner.ts +6 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ A [pi](https://github.com/earendil-works/pi) extension that works through the op
|
|
|
6
6
|
pi install npm:@ravshansbox/pi-next-issue
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
It needs `git` and an authenticated [`gh`](https://cli.github.com/).
|
|
9
|
+
It needs `git` and an authenticated [`gh`](https://cli.github.com/). It works with github.com and GitHub Enterprise remotes.
|
|
10
10
|
|
|
11
11
|
## Use
|
|
12
12
|
|
|
@@ -26,7 +26,7 @@ Each loop fetches the 200 oldest open issues with the `status:todo` label and no
|
|
|
26
26
|
|
|
27
27
|
1. Swap `status:todo` for `status:in-progress`.
|
|
28
28
|
2. Add a git worktree at `.worktrees/<n>` on the `<n>` branch, from the remote default branch.
|
|
29
|
-
3. The implementer agent does the work. The extension commits it, with the subject from a `commit:` line in the reply of the agent, and pushes.
|
|
29
|
+
3. The implementer agent does the work. The extension commits it, with the subject from a `commit:` line in the reply of the agent, and pushes. When a resumed issue already has a commit ahead of the base, the extension keeps it and skips the implementer.
|
|
30
30
|
4. Open a pull request that closes the issue.
|
|
31
31
|
5. Wait for the checks. On a red build, the fixer agent gets the failed logs, up to `maxCiFixes` times.
|
|
32
32
|
6. The reviewer agent judges the diff and gives a verdict with `blocking` and `minor` findings, which goes on the pull request. Any change the reviewer makes is thrown away.
|
package/package.json
CHANGED
package/src/git.ts
CHANGED
|
@@ -9,11 +9,20 @@ export async function repoRoot(cwd: string): Promise<string> {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function parseRemote(url: string): string {
|
|
12
|
-
const
|
|
13
|
-
|
|
12
|
+
const remote = url.trim();
|
|
13
|
+
const hosted =
|
|
14
|
+
/^(?:[\w+.-]+:\/\/)?(?:[^@/]+@)?([^/:]+)(?::\d+)?[:/]([^/]+)\/([^/]+?)(?:\.git)?\/?$/.exec(
|
|
15
|
+
remote,
|
|
16
|
+
);
|
|
17
|
+
if (hosted !== null) {
|
|
18
|
+
const [, host, owner, name] = hosted;
|
|
19
|
+
return host?.toLowerCase() === 'github.com' ? `${owner}/${name}` : `${host}/${owner}/${name}`;
|
|
20
|
+
}
|
|
21
|
+
const path = /[/:]([^/:]+)\/([^/]+?)(?:\.git)?\/?$/.exec(remote);
|
|
22
|
+
if (path === null) {
|
|
14
23
|
throw new Error(`cannot read the owner and name from the git remote ${url}`);
|
|
15
24
|
}
|
|
16
|
-
return `${
|
|
25
|
+
return `${path[1]}/${path[2]}`;
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
export async function remoteSlug(root: string, remote: string): Promise<string> {
|
|
@@ -77,6 +86,16 @@ export async function commit(cwd: string, message: string): Promise<boolean> {
|
|
|
77
86
|
return true;
|
|
78
87
|
}
|
|
79
88
|
|
|
89
|
+
export async function ahead(
|
|
90
|
+
cwd: string,
|
|
91
|
+
remote: string,
|
|
92
|
+
base: string,
|
|
93
|
+
): Promise<string | undefined> {
|
|
94
|
+
const range = `${remote}/${base}..HEAD`;
|
|
95
|
+
const subjects = await must('git', ['log', '--reverse', '--format=%s', range], { cwd });
|
|
96
|
+
return subjects.split('\n')[0] || undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
80
99
|
export async function push(cwd: string, remote: string, branch: string): Promise<void> {
|
|
81
100
|
await must('git', ['push', '-u', remote, `HEAD:refs/heads/${branch}`], { cwd });
|
|
82
101
|
}
|
package/src/ports.ts
CHANGED
|
@@ -20,6 +20,7 @@ export async function createPorts(root: string, config: Config): Promise<Ports>
|
|
|
20
20
|
commentOnIssue: (issue, body) => github.commentOnIssue(repo, issue, body),
|
|
21
21
|
worktree: (issue, branch) => git.worktree(root, config.remote, base, issue, branch),
|
|
22
22
|
commit: (cwd, message) => git.commit(cwd, message),
|
|
23
|
+
ahead: (cwd) => git.ahead(cwd, config.remote, base),
|
|
23
24
|
push: (cwd, branch) => git.push(cwd, config.remote, branch),
|
|
24
25
|
discard: (cwd, branch) => git.discard(cwd, config.remote, branch),
|
|
25
26
|
diff: (cwd) => git.diff(cwd, config.remote, base),
|
package/src/runner.ts
CHANGED
|
@@ -45,6 +45,7 @@ export type Ports = {
|
|
|
45
45
|
commentOnIssue(issue: number, body: string): Promise<void>;
|
|
46
46
|
worktree(issue: number, branch: string): Promise<string>;
|
|
47
47
|
commit(cwd: string, message: string): Promise<boolean>;
|
|
48
|
+
ahead(cwd: string): Promise<string | undefined>;
|
|
48
49
|
push(cwd: string, branch: string): Promise<void>;
|
|
49
50
|
discard(cwd: string, branch: string): Promise<void>;
|
|
50
51
|
diff(cwd: string): Promise<string>;
|
|
@@ -242,6 +243,11 @@ export class Runner {
|
|
|
242
243
|
switch (state.phase) {
|
|
243
244
|
case 'implement': {
|
|
244
245
|
this.stage('implement');
|
|
246
|
+
const kept = saved === undefined ? undefined : await ports.ahead(cwd);
|
|
247
|
+
if (kept !== undefined) {
|
|
248
|
+
await save({ phase: 'publish', title: kept });
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
245
251
|
const reply = await this.agent('work', cwd, implementPrompt(issue, comments));
|
|
246
252
|
const title = parseCommitSubject(reply, `feat: ${issue.title}`);
|
|
247
253
|
if (!(await ports.commit(cwd, title))) {
|