@henryqw/pi-pr 0.3.1 → 0.3.3
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 +0 -14
- package/extensions/pr.ts +16 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,17 +27,3 @@ Each entry is one linked `PR #number` plus one plain-language state: `<count> un
|
|
|
27
27
|
The status loads at session start, polls every 30 seconds, and refreshes after an agent successfully runs `gh pr create`, `git push`, or `/pr`. Unresolved review threads are checked every 30 seconds for 20 minutes after an open PR is first found. Each new push or remote PR update, including new comments, restarts that window. Footer and warning notification show the unresolved count when first found or increased. Last known footer count remains after review checks stop. No pull request leaves the footer blank.
|
|
28
28
|
|
|
29
29
|
`/pr` finds an open PR for current branch. When absent, it starts bundled `/skill:pi-pr-create` workflow. Agent resolves base, inspects and commits scoped changes, runs relevant validation, pushes branch, and creates or updates PR with live title and body. This workflow handles dirty worktrees; it never silently commits unrelated changes.
|
|
30
|
-
|
|
31
|
-
## Remove
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
pi remove npm:@henryqw/pi-pr
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## Development
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
npm test --workspace @henryqw/pi-pr
|
|
41
|
-
npm run typecheck --workspace @henryqw/pi-pr
|
|
42
|
-
npm run pack:check --workspace @henryqw/pi-pr
|
|
43
|
-
```
|
package/extensions/pr.ts
CHANGED
|
@@ -15,15 +15,15 @@ const CREATE_PR_SKILL_COMMAND = "skill:pi-pr-create";
|
|
|
15
15
|
const FAILED_CHECK_STATES = new Set(["ACTION_REQUIRED", "CANCELLED", "ERROR", "FAILURE", "STALE", "STARTUP_FAILURE", "TIMED_OUT"]);
|
|
16
16
|
const SUCCESSFUL_CHECK_STATES = new Set(["NEUTRAL", "SKIPPED", "SUCCESS"]);
|
|
17
17
|
|
|
18
|
-
type Lifecycle = "D" | "O" | "M" | "C";
|
|
19
18
|
type CiStatus = "success" | "running" | "failure" | "none";
|
|
20
19
|
type PullRequest = {
|
|
21
20
|
id: string;
|
|
22
21
|
number: number;
|
|
23
|
-
url:
|
|
22
|
+
url: URL;
|
|
24
23
|
headRefOid: string;
|
|
25
24
|
updatedAt: string;
|
|
26
|
-
|
|
25
|
+
state: "OPEN" | "MERGED" | "CLOSED";
|
|
26
|
+
isDraft: boolean;
|
|
27
27
|
mergeable: string;
|
|
28
28
|
reviewDecision: string | null;
|
|
29
29
|
statusCheckRollup: unknown[];
|
|
@@ -33,14 +33,10 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
33
33
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function pullRequestUrl(value: unknown):
|
|
37
|
-
if (typeof value !== "string") return undefined;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return url.protocol === "http:" || url.protocol === "https:" ? url.href : undefined;
|
|
41
|
-
} catch {
|
|
42
|
-
return undefined;
|
|
43
|
-
}
|
|
36
|
+
function pullRequestUrl(value: unknown): URL | undefined {
|
|
37
|
+
if (typeof value !== "string" || !URL.canParse(value)) return undefined;
|
|
38
|
+
const url = new URL(value);
|
|
39
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url : undefined;
|
|
44
40
|
}
|
|
45
41
|
|
|
46
42
|
export function parsePullRequest(value: unknown): PullRequest | undefined {
|
|
@@ -60,16 +56,14 @@ export function parsePullRequest(value: unknown): PullRequest | undefined {
|
|
|
60
56
|
typeof id !== "string" || !id ||
|
|
61
57
|
typeof number !== "number" || !Number.isSafeInteger(number) || number <= 0 || !url ||
|
|
62
58
|
typeof headRefOid !== "string" || !headRefOid ||
|
|
63
|
-
typeof updatedAt !== "string" || Number.isNaN(Date.parse(updatedAt)) ||
|
|
59
|
+
typeof updatedAt !== "string" || Number.isNaN(Date.parse(updatedAt)) ||
|
|
60
|
+
(state !== "MERGED" && state !== "CLOSED" && state !== "OPEN") ||
|
|
64
61
|
typeof isDraft !== "boolean" || typeof mergeable !== "string" ||
|
|
65
62
|
(reviewDecision !== null && typeof reviewDecision !== "string") ||
|
|
66
63
|
(statusCheckRollup !== null && !Array.isArray(statusCheckRollup))
|
|
67
64
|
) return undefined;
|
|
68
65
|
|
|
69
|
-
|
|
70
|
-
if (!lifecycle) return undefined;
|
|
71
|
-
|
|
72
|
-
return { id, number, url, headRefOid, updatedAt, lifecycle, mergeable, reviewDecision, statusCheckRollup: statusCheckRollup ?? [] };
|
|
66
|
+
return { id, number, url, headRefOid, updatedAt, state, isDraft, mergeable, reviewDecision, statusCheckRollup: statusCheckRollup ?? [] };
|
|
73
67
|
}
|
|
74
68
|
|
|
75
69
|
function parseUnresolvedReviewCount(value: string): number {
|
|
@@ -136,19 +130,19 @@ type Status = {
|
|
|
136
130
|
};
|
|
137
131
|
|
|
138
132
|
function statusFor(pullRequest: PullRequest, ci: CiStatus): Status {
|
|
139
|
-
if (pullRequest.
|
|
140
|
-
if (pullRequest.
|
|
133
|
+
if (pullRequest.state === "MERGED") return { text: "merged", color: "success" };
|
|
134
|
+
if (pullRequest.state === "CLOSED") return { text: "closed", color: "dim" };
|
|
141
135
|
if (pullRequest.mergeable === "CONFLICTING") return { text: "merge conflict", color: "error" };
|
|
142
136
|
if (pullRequest.reviewDecision === "CHANGES_REQUESTED") return { text: "changes requested", color: "error" };
|
|
143
137
|
if (ci === "failure") return { text: "CI failed", color: "error" };
|
|
144
138
|
if (ci === "running") return { text: "CI running", color: "warning" };
|
|
145
|
-
if (pullRequest.
|
|
139
|
+
if (pullRequest.isDraft) return { text: "draft", color: "warning" };
|
|
146
140
|
if (pullRequest.reviewDecision === "APPROVED") return { text: "approved", color: "success" };
|
|
147
141
|
return { text: "open", color: "accent" };
|
|
148
142
|
}
|
|
149
143
|
|
|
150
144
|
export function formatPullRequest(pullRequest: PullRequest, theme: ExtensionContext["ui"]["theme"], unresolved = 0): string {
|
|
151
|
-
const link = hyperlink(theme.fg("text", `PR #${pullRequest.number}`), pullRequest.url);
|
|
145
|
+
const link = hyperlink(theme.fg("text", `PR #${pullRequest.number}`), pullRequest.url.href);
|
|
152
146
|
const status = unresolved > 0
|
|
153
147
|
? { text: `${unresolved} unresolved`, color: "warning" as const }
|
|
154
148
|
: statusFor(pullRequest, ciStatus(pullRequest.statusCheckRollup));
|
|
@@ -203,7 +197,7 @@ export default function pullRequestExtension(pi: ExtensionAPI): void {
|
|
|
203
197
|
ctx.ui.setStatus("pi-pr", undefined);
|
|
204
198
|
return;
|
|
205
199
|
}
|
|
206
|
-
if (pullRequest.
|
|
200
|
+
if (pullRequest.state === "MERGED" || pullRequest.state === "CLOSED") {
|
|
207
201
|
reviewState = undefined;
|
|
208
202
|
reviewWindow = undefined;
|
|
209
203
|
ctx.ui.setStatus("pi-pr", formatPullRequest(pullRequest, ctx.ui.theme));
|
|
@@ -234,7 +228,7 @@ export default function pullRequestExtension(pi: ExtensionAPI): void {
|
|
|
234
228
|
const reviews = await pi.exec(
|
|
235
229
|
"gh",
|
|
236
230
|
[
|
|
237
|
-
"api", "graphql", "--hostname",
|
|
231
|
+
"api", "graphql", "--hostname", pullRequest.url.hostname, "--paginate",
|
|
238
232
|
"-f", `query=${REVIEW_THREADS_QUERY}`, "-F", `id=${pullRequest.id}`,
|
|
239
233
|
"--jq", "[.data.node.reviewThreads.nodes[] | select(.isResolved == false)] | length",
|
|
240
234
|
],
|