@hardlydifficult/pr-analyzer 1.0.40 → 1.0.41
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 +114 -149
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @hardlydifficult/pr-analyzer
|
|
2
2
|
|
|
3
|
-
|
|
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(
|
|
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
|
-
|
|
20
|
+
"@cursor", // Bot mention command
|
|
21
|
+
"HardlyDifficult", // Owner
|
|
22
|
+
"typescript", // Repo
|
|
23
|
+
42, // PR number
|
|
25
24
|
);
|
|
26
25
|
|
|
27
|
-
console.log(pr.status);
|
|
28
|
-
console.log(pr.ciSummary);
|
|
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
|
-
##
|
|
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
|
-
###
|
|
32
|
+
### Scanning and Analyzing PRs
|
|
62
33
|
|
|
63
|
-
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
{ pr
|
|
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
|
-
###
|
|
50
|
+
### PR Status Determination
|
|
83
51
|
|
|
84
|
-
|
|
52
|
+
Core statuses are derived from GitHub data:
|
|
85
53
|
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
80
|
+
### Classification
|
|
118
81
|
|
|
119
|
-
|
|
82
|
+
Classify PRs into action buckets.
|
|
120
83
|
|
|
121
84
|
```typescript
|
|
122
85
|
import { classifyPRs } from "@hardlydifficult/pr-analyzer";
|
|
123
86
|
|
|
124
|
-
const
|
|
125
|
-
console.log(
|
|
126
|
-
console.log(
|
|
127
|
-
console.log(
|
|
128
|
-
console.log(
|
|
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
|
-
|
|
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: ["
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
Determines which actions are available for a PR based on its status.
|
|
104
|
+
### Available Actions
|
|
149
105
|
|
|
150
|
-
|
|
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
|
|
112
|
+
console.log(actions.map(a => a.label)); // e.g. ["Merge", "Enable Auto-Merge"]
|
|
163
113
|
```
|
|
164
114
|
|
|
165
|
-
|
|
115
|
+
Core actions:
|
|
166
116
|
|
|
167
|
-
|
|
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
|
-
|
|
170
|
-
import { getAvailableActions, type ActionDefinition } from "@hardlydifficult/pr-analyzer";
|
|
123
|
+
Add custom actions with `ActionDefinition`.
|
|
171
124
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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,
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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`.
|