@hardlydifficult/pr-analyzer 1.0.40 → 1.0.42

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.
Files changed (2) hide show
  1. package/README.md +114 -149
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @hardlydifficult/pr-analyzer
2
2
 
3
- A GitHub PR analyzer that classifies pull requests by status and determines available actions (merge, mark ready, auto-merge).
3
+ GitHub PR analyzer that classifies pull requests and determines available actions like merge, mark ready, and auto-merge.
4
4
 
5
5
  ## Installation
6
6
 
@@ -11,212 +11,177 @@ npm install @hardlydifficult/pr-analyzer
11
11
  ## Quick Start
12
12
 
13
13
  ```typescript
14
- import { GitHubClient } from "@hardlydifficult/github";
15
14
  import { scanSinglePR } from "@hardlydifficult/pr-analyzer";
15
+ import { GitHubClient } from "@hardlydifficult/github";
16
16
 
17
- const client = new GitHubClient({ token: process.env.GITHUB_TOKEN! });
18
-
17
+ const client = new GitHubClient(process.env.GITHUB_TOKEN!);
19
18
  const pr = await scanSinglePR(
20
19
  client,
21
- "@cursor",
22
- "HardlyDifficult",
23
- "typescript",
24
- 123
20
+ "@cursor", // Bot mention command
21
+ "HardlyDifficult", // Owner
22
+ "typescript", // Repo
23
+ 42, // PR number
25
24
  );
26
25
 
27
- console.log(pr.status); // e.g., "ready_to_merge"
28
- console.log(pr.ciSummary); // e.g., "CI passed: 2 checks"
29
-
30
- const actions = getAvailableActions(pr);
31
- console.log(actions.map(a => a.label)); // e.g., ["Merge"]
26
+ console.log(pr.status); // e.g. "ready_to_merge"
27
+ console.log(pr.ciSummary); // e.g. "CI passed: 1 checks"
32
28
  ```
33
29
 
34
- ## Analysis
35
-
36
- Analyzes a PR's status by fetching CI checks, comments, reviews, and merge conflicts, then determines its readiness.
37
-
38
- ### `analyzePR`
39
-
40
- Analyzes a single PR and returns detailed status.
41
-
42
- ```typescript
43
- import { GitHubClient } from "@hardlydifficult/github";
44
- import { analyzePR } from "@hardlydifficult/pr-analyzer";
45
-
46
- const client = new GitHubClient({ token: process.env.GITHUB_TOKEN! });
47
-
48
- const pr = await client
49
- .repo("owner", "repo")
50
- .pr(42)
51
- .get();
52
-
53
- const result = await analyzePR(client, "owner", "repo", pr, "@bot");
54
- console.log(result.status); // "ready_to_merge", "ci_failed", etc.
55
- console.log(result.ciSummary); // Human-readable CI summary
56
- console.log(result.hasConflicts); // true if merge conflicts exist
57
- console.log(result.waitingOnBot); // true if PR is waiting on bot response
58
- console.log(result.daysSinceUpdate); // Days since last update
59
- ```
30
+ ## Core Features
60
31
 
61
- ### `analyzeAll`
32
+ ### Scanning and Analyzing PRs
62
33
 
63
- Analyzes multiple discovered PRs, skipping those that fail.
34
+ Scan a single PR in real-time using GitHub client, repository owner, repo name, and PR number.
64
35
 
65
36
  ```typescript
66
- import { analyzeAll } from "@hardlydifficult/pr-analyzer";
37
+ import { scanSinglePR, analyzeAll } from "@hardlydifficult/pr-analyzer";
38
+ import type { DiscoveredPR, AnalyzerHooks, Logger } from "@hardlydifficult/pr-analyzer";
67
39
 
68
- interface DiscoveredPR {
69
- pr: PullRequest;
70
- repoOwner: string;
71
- repoName: string;
72
- }
40
+ // Scan a single PR
41
+ const pr = await scanSinglePR(client, "@bot", "owner", "repo", 42);
73
42
 
74
- const prs: DiscoveredPR[] = [
75
- { pr: pr1, repoOwner: "owner", repoName: "repo" },
76
- { pr: pr2, repoOwner: "owner", repoName: "other" },
43
+ // Analyze a batch of discovered PRs
44
+ const discovered: DiscoveredPR[] = [
45
+ { pr, repoOwner: "owner", repoName: "repo" },
77
46
  ];
78
-
79
- const results = await analyzeAll(prs, client, "@bot", logger);
47
+ const results = await analyzeAll(discovered, client, "@bot", console as Logger);
80
48
  ```
81
49
 
82
- ### `scanSinglePR`
50
+ ### PR Status Determination
83
51
 
84
- Real-time PR scan for event handling (e.g., webhook, cron).
52
+ Core statuses are derived from GitHub data:
85
53
 
86
- ```typescript
87
- import { scanSinglePR } from "@hardlydifficult/pr-analyzer";
54
+ - `"draft"` — PR is in draft state
55
+ - `"ci_running"` CI checks are in progress
56
+ - `"ci_failed"` — At least one CI check failed
57
+ - `"needs_review"` — No reviewer approval yet
58
+ - `"changes_requested"` — A reviewer requested changes
59
+ - `"approved"` — At least one reviewer approved
60
+ - `"has_conflicts"` — Merge conflicts detected
61
+ - `"ready_to_merge"` — CI passed, no conflicts, approved
62
+ - `"waiting_on_bot"` — Bot was mentioned and has not replied
88
63
 
89
- const pr = await scanSinglePR(
90
- client,
91
- "@cursor",
92
- "owner",
93
- "repo",
94
- 123
95
- );
96
- ```
97
-
98
- ### Hooks
99
-
100
- Customize status resolution with hooks:
64
+ You can extend status determination via `AnalyzerHooks.resolveStatus`.
101
65
 
102
66
  ```typescript
103
- import { analyzePR, type AnalyzerHooks } from "@hardlydifficult/pr-analyzer";
104
-
105
67
  const hooks: AnalyzerHooks = {
106
- resolveStatus(coreStatus, details) {
107
- if (coreStatus === "ci_failed") {
68
+ resolveStatus: (coreStatus, details) => {
69
+ if (coreStatus === "ci_failed" && details.checks.some(c => c.name === "CI")) {
108
70
  return "ai_processing";
109
71
  }
110
- return undefined;
72
+ return undefined; // keep core status
111
73
  },
112
74
  };
113
75
 
114
- const result = await analyzePR(client, "owner", "repo", pr, "@bot", hooks);
76
+ const pr = await analyzePR(client, "owner", "repo", pr, "@bot", hooks);
77
+ console.log(pr.status); // e.g. "ai_processing"
115
78
  ```
116
79
 
117
- ## Classification
80
+ ### Classification
118
81
 
119
- Classifies PRs into buckets based on status: `readyForHuman`, `needsBotBump`, `inProgress`, `blocked`.
82
+ Classify PRs into action buckets.
120
83
 
121
84
  ```typescript
122
85
  import { classifyPRs } from "@hardlydifficult/pr-analyzer";
123
86
 
124
- const buckets = classifyPRs(results);
125
- console.log(buckets.readyForHuman.length); // PRs needing human action
126
- console.log(buckets.needsBotBump.length); // PRs waiting on bot
127
- console.log(buckets.inProgress.length); // PRs with CI running
128
- console.log(buckets.blocked.length); // PRs stuck (draft, failed, conflicts)
87
+ const result = classifyPRs(results);
88
+ console.log(result.readyForHuman); // PRs needing human review/merge
89
+ console.log(result.inProgress); // PRs with CI running
90
+ console.log(result.blocked); // PRs blocked (draft, failed CI, conflicts)
91
+ console.log(result.needsBotBump); // PRs waiting on bot response
129
92
  ```
130
93
 
131
- ### Extending Classification
132
-
133
- Add custom statuses to buckets via `ClassificationConfig`:
94
+ Extend classification buckets with custom statuses via `ClassificationConfig`.
134
95
 
135
96
  ```typescript
136
97
  const config: ClassificationConfig = {
137
- readyForHuman: ["custom_ready"],
138
98
  inProgress: ["ai_processing"],
139
- blocked: ["custom_blocked"],
140
- needsBotBump: ["custom_waiting"],
99
+ blocked: ["security_review"],
141
100
  };
142
-
143
- const buckets = classifyPRs(results, config);
101
+ const result = classifyPRs(results, config);
144
102
  ```
145
103
 
146
- ## Actions
147
-
148
- Determines which actions are available for a PR based on its status.
104
+ ### Available Actions
149
105
 
150
- ### Core Actions
151
-
152
- | Type | Available When | Description |
153
- |------|----------------|-------------|
154
- | `"merge"` | `ready_to_merge`, `approved` | Squash and merge |
155
- | `"mark_ready"` | `draft` with CI passed & no conflicts | Mark draft as ready |
156
- | `"enable_auto_merge"` | `ci_running`, `needs_review` | Enable auto-merge |
106
+ Determine available actions for a PR.
157
107
 
158
108
  ```typescript
159
- import { getAvailableActions } from "@hardlydifficult/pr-analyzer";
109
+ import { getAvailableActions, PR_ACTIONS } from "@hardlydifficult/pr-analyzer";
160
110
 
161
111
  const actions = getAvailableActions(pr);
162
- // e.g., [{ type: "merge", label: "Merge", description: "Squash and merge this PR" }]
112
+ console.log(actions.map(a => a.label)); // e.g. ["Merge", "Enable Auto-Merge"]
163
113
  ```
164
114
 
165
- ### Custom Actions
115
+ Core actions:
166
116
 
167
- Define extra actions that trigger on conditions:
117
+ | Type | Label | Description |
118
+ |------------------|---------------|------------------------------------------|
119
+ | `"merge"` | `"Merge"` | Squash and merge this PR |
120
+ | `"mark_ready"` | `"Mark Ready"`| Mark this draft PR as ready for review |
121
+ | `"enable_auto_merge"` | `"Enable Auto-Merge"` | Enable GitHub auto-merge when checks pass |
168
122
 
169
- ```typescript
170
- import { getAvailableActions, type ActionDefinition } from "@hardlydifficult/pr-analyzer";
123
+ Add custom actions with `ActionDefinition`.
171
124
 
172
- const fixCiAction: ActionDefinition = {
173
- type: "fix_ci",
174
- label: "Fix CI",
175
- description: "Post @cursor fix CI comment",
176
- when: (pr, ctx) => pr.status === "ci_failed" && ctx["isWorkPR"] === true,
177
- };
125
+ ```typescript
126
+ const extraActions: ActionDefinition[] = [
127
+ {
128
+ type: "fix_ci",
129
+ label: "Fix CI",
130
+ description: "Post @cursor fix CI comment",
131
+ when: (pr, ctx) => pr.status === "ci_failed" && ctx["isWorkPR"] === true,
132
+ },
133
+ ];
178
134
 
179
- const actions = getAvailableActions(pr, [fixCiAction], { isWorkPR: true });
135
+ const actions = getAvailableActions(pr, extraActions, { isWorkPR: true });
180
136
  ```
181
137
 
182
- ## Types
183
-
184
- ### Core Statuses
185
-
186
- | Status | Meaning |
187
- |--------|---------|
188
- | `"draft"` | Draft PR |
189
- | `"ci_running"` | CI checks in progress |
190
- | `"ci_failed"` | CI checks failed |
191
- | `"needs_review"` | CI passed, no reviews yet |
192
- | `"changes_requested"` | Review requests changes |
193
- | `"approved"` | Review approved |
194
- | `"has_conflicts"` | Merge conflicts |
195
- | `"ready_to_merge"` | CI passed, approved, no conflicts |
196
- | `"waiting_on_bot"` | Bot mentioned but not replied |
197
-
198
- ### ScannedPR
138
+ ## Types and Interfaces
199
139
 
200
140
  ```typescript
201
- interface ScannedPR {
202
- pr: PullRequest;
203
- repo: Repository;
204
- status: string; // core or custom status
205
- ciStatus: CIStatus;
206
- ciSummary: string;
207
- hasConflicts: boolean;
208
- waitingOnBot: boolean;
209
- daysSinceUpdate: number;
210
- }
141
+ import type {
142
+ ScannedPR,
143
+ CIStatus,
144
+ ScanResult,
145
+ AnalyzerHooks,
146
+ ClassificationConfig,
147
+ ActionDefinition,
148
+ CorePRStatus,
149
+ Logger,
150
+ } from "@hardlydifficult/pr-analyzer";
211
151
  ```
212
152
 
213
- ### CIStatus
214
-
215
- ```typescript
216
- interface CIStatus {
217
- isRunning: boolean;
218
- hasFailed: boolean;
219
- allPassed: boolean;
220
- summary: string;
221
- }
222
- ```
153
+ | Interface/Type | Purpose |
154
+ |---------------------|---------|
155
+ | `ScannedPR` | Full PR data after analysis |
156
+ | `CIStatus` | CI check summary (running, failed, passed) |
157
+ | `ScanResult` | PRs grouped into buckets |
158
+ | `AnalyzerHooks` | Extend status determination |
159
+ | `ClassificationConfig` | Extend classification buckets |
160
+ | `ActionDefinition` | Define custom PR actions |
161
+ | `CorePRStatus` | Built-in status types |
162
+ | `Logger` | Interface for logging info/errors |
163
+
164
+ ## Appendix
165
+
166
+ ### Core Status Priority
167
+
168
+ Status determination follows this priority:
169
+
170
+ 1. Draft PR → `"draft"`
171
+ 2. CI running → `"ci_running"`
172
+ 3. CI failed → `"ci_failed"`
173
+ 4. Merge conflicts → `"has_conflicts"`
174
+ 5. Waiting on bot → `"waiting_on_bot"`
175
+ 6. Changes requested → `"changes_requested"`
176
+ 7. CI passed and no conflicts → `"ready_to_merge"`
177
+ 8. At least one approval → `"approved"`
178
+ 9. Default → `"needs_review"`
179
+
180
+ ### CI Check Status Detection
181
+
182
+ - **Running**: `status: "in_progress"` or `status: "queued"` or `conclusion: null`
183
+ - **Failed**: `status: "completed"` and `conclusion` is `"failure"`, `"timed_out"`, `"cancelled"`, or `"action_required"`
184
+ - **Passed**: `status: "completed"` and `conclusion` is `"success"`, `"skipped"`, or `"neutral"`
185
+ - **Uncategorized checks**: Treated as running if no definitive state
186
+
187
+ Bot detection includes common bots: `cursor`, `cursor-bot`, `github-actions`, `dependabot`, `renovate`, `codecov`, `vercel`, `claude`, plus any username ending in `[bot]` or containing `bot`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardlydifficult/pr-analyzer",
3
- "version": "1.0.40",
3
+ "version": "1.0.42",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [