@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 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: string;
22
+ url: URL;
24
23
  headRefOid: string;
25
24
  updatedAt: string;
26
- lifecycle: Lifecycle;
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): string | undefined {
37
- if (typeof value !== "string") return undefined;
38
- try {
39
- const url = new URL(value);
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)) || typeof state !== "string" ||
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
- const lifecycle = state === "MERGED" ? "M" : state === "CLOSED" ? "C" : state === "OPEN" ? isDraft ? "D" : "O" : undefined;
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.lifecycle === "M") return { text: "merged", color: "success" };
140
- if (pullRequest.lifecycle === "C") return { text: "closed", color: "dim" };
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.lifecycle === "D") return { text: "draft", color: "warning" };
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.lifecycle === "M" || pullRequest.lifecycle === "C") {
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", new URL(pullRequest.url).hostname, "--paginate",
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
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henryqw/pi-pr",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Show the current branch pull request status in the Pi footer.",
5
5
  "keywords": [
6
6
  "pi-package",